diff --git a/.golangci.yml b/.golangci.yml index 0efaab1974..1466bdec11 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -49,7 +49,7 @@ linters: - tparallel - unconvert - unparam - # - wsl + - wsl - asasalint #- errorlint causes stack overflow. TODO: recheck after each golangci update diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index e96a0a0425..c24265fdec 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -51,6 +51,7 @@ func JSON(reader io.Reader) (ABI, error) { if err := dec.Decode(&abi); err != nil { return ABI{}, err } + return abi, nil } @@ -67,12 +68,15 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { if err != nil { return nil, err } + return arguments, nil } + method, exist := abi.Methods[name] if !exist { return nil, fmt.Errorf("method '%s' not found", name) } + arguments, err := method.Inputs.Pack(args...) if err != nil { return nil, err @@ -85,18 +89,23 @@ func (abi ABI) getArguments(name string, data []byte) (Arguments, error) { // since there can't be naming collisions with contracts and events, // we need to decide whether we're calling a method or an event var args Arguments + if method, ok := abi.Methods[name]; ok { if len(data)%32 != 0 { return nil, fmt.Errorf("abi: improperly formatted output: %q - Bytes: %+v", data, data) } + args = method.Outputs } + if event, ok := abi.Events[name]; ok { args = event.Inputs } + if args == nil { return nil, fmt.Errorf("abi: could not locate named method or event: %s", name) } + return args, nil } @@ -106,6 +115,7 @@ func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) { if err != nil { return nil, err } + return args.Unpack(data) } @@ -117,10 +127,12 @@ func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) erro if err != nil { return err } + unpacked, err := args.Unpack(data) if err != nil { return err } + return args.Copy(v, unpacked) } @@ -130,6 +142,7 @@ func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) if err != nil { return err } + return args.UnpackIntoMap(v, data) } @@ -153,12 +166,15 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { // declared as anonymous. Anonymous bool } + if err := json.Unmarshal(data, &fields); err != nil { return err } + abi.Methods = make(map[string]Method) abi.Events = make(map[string]Event) abi.Errors = make(map[string]Error) + for _, field := range fields { switch field.Type { case "constructor": @@ -172,6 +188,7 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { if abi.HasFallback() { return errors.New("only single fallback is allowed") } + abi.Fallback = NewMethod("", "", Fallback, field.StateMutability, field.Constant, field.Payable, nil, nil) case "receive": // New introduced function type in v0.6.0, check more detail @@ -179,9 +196,11 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { if abi.HasReceive() { return errors.New("only single receive is allowed") } + if field.StateMutability != "payable" { return errors.New("the statemutability of receive can only be payable") } + abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil) case "event": name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok }) @@ -194,6 +213,7 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name) } } + return nil } @@ -203,11 +223,13 @@ func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { if len(sigdata) < 4 { return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata)) } + for _, method := range abi.Methods { if bytes.Equal(method.ID, sigdata[:4]) { return &method, nil } } + return nil, fmt.Errorf("no method with id: %#x", sigdata[:4]) } @@ -219,6 +241,7 @@ func (abi *ABI) EventByID(topic common.Hash) (*Event, error) { return &event, nil } } + return nil, fmt.Errorf("no event with id: %#x", topic.Hex()) } @@ -243,6 +266,7 @@ func UnpackRevert(data []byte) (string, error) { if len(data) < 4 { return "", errors.New("invalid data for unpacking") } + if !bytes.Equal(data[:4], revertSelector) { return "", errors.New("invalid data for unpacking") } @@ -252,9 +276,11 @@ func UnpackRevert(data []byte) (string, error) { if err != nil { return "", err } + unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:]) if err != nil { return "", err } + return unpacked[0].(string), nil } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 96c11e0964..9059d9a729 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -134,6 +134,7 @@ func TestReader(t *testing.T) { if !exist { t.Errorf("Missing expected method %v", name) } + if !reflect.DeepEqual(gotM, expM) { t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM) } @@ -144,6 +145,7 @@ func TestReader(t *testing.T) { if !exist { t.Errorf("Found extra method %v", name) } + if !reflect.DeepEqual(gotM, expM) { t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM) } @@ -152,11 +154,14 @@ func TestReader(t *testing.T) { func TestInvalidABI(t *testing.T) { json := `[{ "type" : "function", "name" : "", "constant" : fals }]` + _, err := JSON(strings.NewReader(json)) if err == nil { t.Fatal("invalid json should produce error") } + json2 := `[{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "typ" : "uint256" } ] }]` + _, err = JSON(strings.NewReader(json2)) if err == nil { t.Fatal("invalid json should produce error") @@ -177,6 +182,7 @@ func TestConstructor(t *testing.T) { if err != nil { t.Fatal(err) } + if !reflect.DeepEqual(abi.Constructor, method) { t.Error("Missing expected constructor") } @@ -185,6 +191,7 @@ func TestConstructor(t *testing.T) { if err != nil { t.Error(err) } + unpacked, err := abi.Constructor.Inputs.Unpack(packed) if err != nil { t.Error(err) @@ -193,6 +200,7 @@ func TestConstructor(t *testing.T) { if !reflect.DeepEqual(unpacked[0], big.NewInt(1)) { t.Error("Unable to pack/unpack from constructor") } + if !reflect.DeepEqual(unpacked[1], big.NewInt(2)) { t.Error("Unable to pack/unpack from constructor") } @@ -226,6 +234,7 @@ func TestTestNumbers(t *testing.T) { i := new(int) *i = 1000 + if _, err := abi.Pack("send", i); err == nil { t.Errorf("expected send( ptr ) to throw, requires *big.Int instead of *int") } @@ -238,6 +247,7 @@ func TestTestNumbers(t *testing.T) { func TestMethodSignature(t *testing.T) { m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil) exp := "foo(string,string)" + if m.Sig != exp { t.Error("signature mismatch", exp, "!=", m.Sig) } @@ -249,6 +259,7 @@ func TestMethodSignature(t *testing.T) { m = NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", Uint256, false}}, nil) exp = "foo(uint256)" + if m.Sig != exp { t.Error("signature mismatch", exp, "!=", m.Sig) } @@ -268,6 +279,7 @@ func TestMethodSignature(t *testing.T) { }) m = NewMethod("foo", "foo", Function, "", false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil) exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)" + if m.Sig != exp { t.Error("signature mismatch", exp, "!=", m.Sig) } @@ -275,10 +287,12 @@ func TestMethodSignature(t *testing.T) { func TestOverloadedMethodSignature(t *testing.T) { json := `[{"constant":true,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"}],"name":"bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"},{"indexed":false,"name":"j","type":"uint256"}],"name":"bar","type":"event"}]` + abi, err := JSON(strings.NewReader(json)) if err != nil { t.Fatal(err) } + check := func(name string, expect string, method bool) { if method { if abi.Methods[name].Sig != expect { @@ -298,10 +312,12 @@ func TestOverloadedMethodSignature(t *testing.T) { func TestCustomErrors(t *testing.T) { json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]` + abi, err := JSON(strings.NewReader(json)) if err != nil { t.Fatal(err) } + check := func(name string, expect string) { if abi.Errors[name].Sig != expect { t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig) @@ -325,6 +341,7 @@ func TestMultiPack(t *testing.T) { if err != nil { t.Fatal(err) } + if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } @@ -337,6 +354,7 @@ func ExampleJSON() { if err != nil { panic(err) } + out, err := abi.Pack("isBar", common.HexToAddress("01")) if err != nil { panic(err) @@ -361,6 +379,7 @@ func TestInputVariableInputLength(t *testing.T) { // test one string strin := "hello world" + strpack, err := abi.Pack("strOne", strin) if err != nil { t.Error(err) @@ -393,6 +412,7 @@ func TestInputVariableInputLength(t *testing.T) { // test two strings str1 := "hello" str2 := "world" + str2pack, err := abi.Pack("strTwo", str1, str2) if err != nil { t.Error(err) @@ -422,6 +442,7 @@ func TestInputVariableInputLength(t *testing.T) { // test two strings, first > 32, second < 32 str1 = strings.Repeat("a", 33) + str2pack, err = abi.Pack("strTwo", str1, str2) if err != nil { t.Error(err) @@ -447,6 +468,7 @@ func TestInputVariableInputLength(t *testing.T) { // test two strings, first > 32, second >32 str1 = strings.Repeat("a", 33) str2 = strings.Repeat("a", 33) + str2pack, err = abi.Pack("strTwo", str1, str2) if err != nil { t.Error(err) @@ -484,6 +506,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { // test string, fixed array uint256[2] strin := "hello world" arrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)} + fixedArrStrPack, err := abi.Pack("fixedArrStr", strin, arrin) if err != nil { t.Error(err) @@ -497,6 +520,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { strvalue := common.RightPadBytes([]byte(strin), 32) arrinvalue1 := common.LeftPadBytes(arrin[0].Bytes(), 32) arrinvalue2 := common.LeftPadBytes(arrin[1].Bytes(), 32) + exp := append(offset, arrinvalue1...) exp = append(exp, arrinvalue2...) exp = append(exp, append(length, strvalue...)...) @@ -510,6 +534,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { // test byte array, fixed array uint256[2] bytesin := []byte(strin) arrin = [2]*big.Int{big.NewInt(1), big.NewInt(2)} + fixedArrBytesPack, err := abi.Pack("fixedArrBytes", bytesin, arrin) if err != nil { t.Error(err) @@ -523,6 +548,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { strvalue = common.RightPadBytes([]byte(strin), 32) arrinvalue1 = common.LeftPadBytes(arrin[0].Bytes(), 32) arrinvalue2 = common.LeftPadBytes(arrin[1].Bytes(), 32) + exp = append(offset, arrinvalue1...) exp = append(exp, arrinvalue2...) exp = append(exp, append(length, strvalue...)...) @@ -537,6 +563,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { strin = "hello world" fixedarrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)} dynarrin := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + mixedArrStrPack, err := abi.Pack("mixedArrStr", strin, fixedarrin, dynarrin) if err != nil { t.Error(err) @@ -557,6 +584,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { dynarrinvalue1 := common.LeftPadBytes(dynarrin[0].Bytes(), 32) dynarrinvalue2 := common.LeftPadBytes(dynarrin[1].Bytes(), 32) dynarrinvalue3 := common.LeftPadBytes(dynarrin[2].Bytes(), 32) + exp = append(stroffset, fixedarrinvalue1...) exp = append(exp, fixedarrinvalue2...) exp = append(exp, dynarroffset...) @@ -576,6 +604,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { strin = "hello world" fixedarrin1 := [2]*big.Int{big.NewInt(1), big.NewInt(2)} fixedarrin2 := [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + doubleFixedArrStrPack, err := abi.Pack("doubleFixedArrStr", strin, fixedarrin1, fixedarrin2) if err != nil { t.Error(err) @@ -592,6 +621,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { fixedarrin2value1 := common.LeftPadBytes(fixedarrin2[0].Bytes(), 32) fixedarrin2value2 := common.LeftPadBytes(fixedarrin2[1].Bytes(), 32) fixedarrin2value3 := common.LeftPadBytes(fixedarrin2[2].Bytes(), 32) + exp = append(stroffset, fixedarrin1value1...) exp = append(exp, fixedarrin1value2...) exp = append(exp, fixedarrin2value1...) @@ -610,6 +640,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { fixedarrin1 = [2]*big.Int{big.NewInt(1), big.NewInt(2)} dynarrin = []*big.Int{big.NewInt(1), big.NewInt(2)} fixedarrin2 = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + multipleMixedArrStrPack, err := abi.Pack("multipleMixedArrStr", strin, fixedarrin1, dynarrin, fixedarrin2) if err != nil { t.Error(err) @@ -631,6 +662,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { fixedarrin2value1 = common.LeftPadBytes(fixedarrin2[0].Bytes(), 32) fixedarrin2value2 = common.LeftPadBytes(fixedarrin2[1].Bytes(), 32) fixedarrin2value3 = common.LeftPadBytes(fixedarrin2[2].Bytes(), 32) + exp = append(stroffset, fixedarrin1value1...) exp = append(exp, fixedarrin1value2...) exp = append(exp, dynarroffset...) @@ -703,20 +735,25 @@ func TestBareEvents(t *testing.T) { t.Errorf("could not found event %s", name) continue } + if got.Anonymous != exp.Anonymous { t.Errorf("invalid anonymous indication for event %s, want %v, got %v", name, exp.Anonymous, got.Anonymous) } + if len(got.Inputs) != len(exp.Args) { t.Errorf("invalid number of args, want %d, got %d", len(exp.Args), len(got.Inputs)) continue } + for i, arg := range exp.Args { if arg.Name != got.Inputs[i].Name { t.Errorf("events[%s].Input[%d] has an invalid name, want %s, got %s", name, i, arg.Name, got.Inputs[i].Name) } + if arg.Indexed != got.Inputs[i].Indexed { t.Errorf("events[%s].Input[%d] has an invalid indexed indication, want %v, got %v", name, i, arg.Indexed, got.Inputs[i].Indexed) } + if arg.Type.T != got.Inputs[i].Type.T { t.Errorf("events[%s].Input[%d] has an invalid type, want %x, got %x", name, i, arg.Type.T, got.Inputs[i].Type.T) } @@ -740,16 +777,19 @@ func TestBareEvents(t *testing.T) { // receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} func TestUnpackEvent(t *testing.T) { const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` + abi, err := JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) if err != nil { t.Fatal(err) } + if len(data)%32 == 0 { t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) } @@ -759,6 +799,7 @@ func TestUnpackEvent(t *testing.T) { Amount *big.Int Memo []byte } + var ev ReceivedEvent err = abi.UnpackIntoInterface(&ev, "received", data) @@ -769,7 +810,9 @@ func TestUnpackEvent(t *testing.T) { type ReceivedAddrEvent struct { Sender common.Address } + var receivedAddrEv ReceivedAddrEvent + err = abi.UnpackIntoInterface(&receivedAddrEv, "receivedAddr", data) if err != nil { t.Error(err) @@ -778,16 +821,19 @@ func TestUnpackEvent(t *testing.T) { func TestUnpackEventIntoMap(t *testing.T) { const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` + abi, err := JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) if err != nil { t.Fatal(err) } + if len(data)%32 == 0 { t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) } @@ -798,18 +844,23 @@ func TestUnpackEventIntoMap(t *testing.T) { "amount": big.NewInt(1), "memo": []byte{88}, } + if err := abi.UnpackIntoMap(receivedMap, "received", data); err != nil { t.Error(err) } + if len(receivedMap) != 3 { t.Error("unpacked `received` map expected to have length 3") } + if receivedMap["sender"] != expectedReceivedMap["sender"] { t.Error("unpacked `received` map does not match expected map") } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { t.Error("unpacked `received` map does not match expected map") } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { t.Error("unpacked `received` map does not match expected map") } @@ -818,9 +869,11 @@ func TestUnpackEventIntoMap(t *testing.T) { if err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data); err != nil { t.Error(err) } + if len(receivedAddrMap) != 1 { t.Error("unpacked `receivedAddr` map expected to have length 1") } + if receivedAddrMap["sender"] != expectedReceivedMap["sender"] { t.Error("unpacked `receivedAddr` map does not match expected map") } @@ -828,15 +881,19 @@ func TestUnpackEventIntoMap(t *testing.T) { func TestUnpackMethodIntoMap(t *testing.T) { const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` + abi, err := JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } + const hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001580000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) if err != nil { t.Fatal(err) } + if len(data)%32 != 0 { t.Errorf("len(data) is %d, want a multiple of 32", len(data)) } @@ -846,6 +903,7 @@ func TestUnpackMethodIntoMap(t *testing.T) { if err = abi.UnpackIntoMap(receiveMap, "receive", data); err != nil { t.Error(err) } + if len(receiveMap) > 0 { t.Error("unpacked `receive` map expected to have length 0") } @@ -855,9 +913,11 @@ func TestUnpackMethodIntoMap(t *testing.T) { if err = abi.UnpackIntoMap(sendMap, "send", data); err != nil { t.Error(err) } + if len(sendMap) != 1 { t.Error("unpacked `send` map expected to have length 1") } + if sendMap["amount"].(*big.Int).Cmp(big.NewInt(1)) != 0 { t.Error("unpacked `send` map expected `amount` value of 1") } @@ -867,9 +927,11 @@ func TestUnpackMethodIntoMap(t *testing.T) { if err = abi.UnpackIntoMap(getMap, "get", data); err != nil { t.Error(err) } + if len(getMap) != 1 { t.Error("unpacked `get` map expected to have length 1") } + expectedBytes := []byte{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, 96, 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, 1, 88, 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, 96, 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, 1, 88, 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, 96, 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, 1, 88, 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, 96, 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, 1, 88, 0} if !bytes.Equal(getMap["hash"].([]byte), expectedBytes) { t.Errorf("unpacked `get` map expected `hash` value of %v", expectedBytes) @@ -879,18 +941,23 @@ func TestUnpackMethodIntoMap(t *testing.T) { func TestUnpackIntoMapNamingConflict(t *testing.T) { // Two methods have the same name var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` + abi, err := JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } + var hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) if err != nil { t.Fatal(err) } + if len(data)%32 == 0 { t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) } + getMap := map[string]interface{}{} if err = abi.UnpackIntoMap(getMap, "get", data); err == nil { t.Error("naming conflict between two methods; error expected") @@ -898,18 +965,23 @@ func TestUnpackIntoMapNamingConflict(t *testing.T) { // Two events have the same name abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"received","type":"event"}]` + abi, err = JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } + hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err = hex.DecodeString(hexdata) if err != nil { t.Fatal(err) } + if len(data)%32 == 0 { t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) } + receivedMap := map[string]interface{}{} if err = abi.UnpackIntoMap(receivedMap, "received", data); err != nil { t.Error("naming conflict between two events; no error expected") @@ -917,43 +989,54 @@ func TestUnpackIntoMapNamingConflict(t *testing.T) { // Method and event have the same name abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` + abi, err = JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } + if len(data)%32 == 0 { t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) } + if err = abi.UnpackIntoMap(receivedMap, "received", data); err == nil { t.Error("naming conflict between an event and a method; error expected") } // Conflict is case sensitive abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` + abi, err = JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } + if len(data)%32 == 0 { t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) } + expectedReceivedMap := map[string]interface{}{ "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "amount": big.NewInt(1), "memo": []byte{88}, } + if err = abi.UnpackIntoMap(receivedMap, "Received", data); err != nil { t.Error(err) } + if len(receivedMap) != 3 { t.Error("unpacked `received` map expected to have length 3") } + if receivedMap["sender"] != expectedReceivedMap["sender"] { t.Error("unpacked `received` map does not match expected map") } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { t.Error("unpacked `received` map does not match expected map") } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { t.Error("unpacked `received` map does not match expected map") } @@ -964,12 +1047,15 @@ func TestABI_MethodById(t *testing.T) { if err != nil { t.Fatal(err) } + for name, m := range abi.Methods { a := fmt.Sprintf("%v", m) + m2, err := abi.MethodById(m.ID) if err != nil { t.Fatalf("Failed to look up ABI method: %v", err) } + b := fmt.Sprintf("%v", m2) if a != b { t.Errorf("Method %v (id %x) not 'findable' by id in ABI", name, m.ID) @@ -983,9 +1069,11 @@ func TestABI_MethodById(t *testing.T) { if _, err := abi.MethodById([]byte{0x00}); err == nil { t.Errorf("Expected error, too short to decode data") } + if _, err := abi.MethodById([]byte{}); err == nil { t.Errorf("Expected error, too short to decode data") } + if _, err := abi.MethodById(nil); err == nil { t.Errorf("Expected error, nil is short to decode data") } @@ -1040,6 +1128,7 @@ func TestABI_EventById(t *testing.T) { if err != nil { t.Fatalf("Failed to look up ABI method: %v, test #%d", err, testnum) } + if event == nil { t.Errorf("We should find a event for topic %s, test #%d", topicID.Hex(), testnum) } else if event.ID != topicID { @@ -1047,10 +1136,12 @@ func TestABI_EventById(t *testing.T) { } unknowntopicID := crypto.Keccak256Hash([]byte("unknownEvent")) + unknownEvent, err := abi.EventByID(unknowntopicID) if err == nil { t.Errorf("EventByID should return an error if a topic is not found, test #%d", testnum) } + if unknownEvent != nil { t.Errorf("We should not find any event for topic %s, test #%d", unknowntopicID.Hex(), testnum) } @@ -1061,19 +1152,24 @@ func TestABI_EventById(t *testing.T) { // conflict and that the second transfer method will be renamed transfer1. func TestDoubleDuplicateMethodNames(t *testing.T) { abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]` + contractAbi, err := JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } + if _, ok := contractAbi.Methods["transfer"]; !ok { t.Fatalf("Could not find original method") } + if _, ok := contractAbi.Methods["transfer0"]; !ok { t.Fatalf("Could not find duplicate method") } + if _, ok := contractAbi.Methods["transfer1"]; !ok { t.Fatalf("Could not find duplicate method") } + if _, ok := contractAbi.Methods["transfer2"]; ok { t.Fatalf("Should not have found extra method") } @@ -1090,19 +1186,24 @@ func TestDoubleDuplicateMethodNames(t *testing.T) { // } func TestDoubleDuplicateEventNames(t *testing.T) { abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]` + contractAbi, err := JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } + if _, ok := contractAbi.Events["send"]; !ok { t.Fatalf("Could not find original event") } + if _, ok := contractAbi.Events["send0"]; !ok { t.Fatalf("Could not find duplicate event") } + if _, ok := contractAbi.Events["send1"]; !ok { t.Fatalf("Could not find duplicate event") } + if _, ok := contractAbi.Events["send2"]; ok { t.Fatalf("Should not have found extra event") } @@ -1117,6 +1218,7 @@ func TestDoubleDuplicateEventNames(t *testing.T) { // } func TestUnnamedEventParam(t *testing.T) { abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]` + contractAbi, err := JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) @@ -1126,9 +1228,11 @@ func TestUnnamedEventParam(t *testing.T) { if !ok { t.Fatalf("Could not find event") } + if event.Inputs[0].Name != "arg0" { t.Fatalf("Could not find input") } + if event.Inputs[1].Name != "arg1" { t.Fatalf("Could not find input") } @@ -1146,6 +1250,7 @@ func TestUnpackRevert(t *testing.T) { {"08c379a1", "", errors.New("invalid data for unpacking")}, {"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil}, } + for index, c := range cases { t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) { got, err := UnpackRevert(common.Hex2Bytes(c.input)) @@ -1153,11 +1258,14 @@ func TestUnpackRevert(t *testing.T) { if err == nil { t.Fatalf("Expected non-nil error") } + if err.Error() != c.expectErr.Error() { t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err) } + return } + if c.expect != got { t.Fatalf("Output mismatch, want %v, got %v", c.expect, got) } diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 2e48d539e0..345d6fcbd8 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -45,6 +45,7 @@ type ArgumentMarshaling struct { // UnmarshalJSON implements json.Unmarshaler interface. func (argument *Argument) UnmarshalJSON(data []byte) error { var arg ArgumentMarshaling + err := json.Unmarshal(data, &arg) if err != nil { return fmt.Errorf("argument json err: %v", err) @@ -54,6 +55,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error { if err != nil { return err } + argument.Name = arg.Name argument.Indexed = arg.Indexed @@ -63,11 +65,13 @@ func (argument *Argument) UnmarshalJSON(data []byte) error { // NonIndexed returns the arguments with indexed arguments filtered out. func (arguments Arguments) NonIndexed() Arguments { var ret []Argument + for _, arg := range arguments { if !arg.Indexed { ret = append(ret, arg) } } + return ret } @@ -82,8 +86,10 @@ func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { if len(arguments.NonIndexed()) != 0 { return nil, errors.New("abi: attempting to unmarshall an empty string while arguments are expected") } + return make([]interface{}, 0), nil } + return arguments.UnpackValues(data) } @@ -93,19 +99,24 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) if v == nil { return errors.New("abi: cannot unpack into a nil map") } + if len(data) == 0 { if len(arguments.NonIndexed()) != 0 { return errors.New("abi: attempting to unmarshall an empty string while arguments are expected") } + return nil // Nothing to unmarshal, return } + marshalledValues, err := arguments.UnpackValues(data) if err != nil { return err } + for i, arg := range arguments.NonIndexed() { v[arg.Name] = marshalledValues[i] } + return nil } @@ -115,15 +126,19 @@ func (arguments Arguments) Copy(v interface{}, values []interface{}) error { if reflect.Ptr != reflect.ValueOf(v).Kind() { return fmt.Errorf("abi: Unpack(non-pointer %T)", v) } + if len(values) == 0 { if len(arguments.NonIndexed()) != 0 { return errors.New("abi: attempting to copy no values while arguments are expected") } + return nil // Nothing to copy, return } + if arguments.isTuple() { return arguments.copyTuple(v, values) } + return arguments.copyAtomic(v, values[0]) } @@ -135,6 +150,7 @@ func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{ if dst.Kind() == reflect.Struct { return set(dst.Field(0), src) } + return set(dst, src) } @@ -149,16 +165,20 @@ func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface for i, arg := range nonIndexedArgs { argNames[i] = arg.Name } + var err error + abi2struct, err := mapArgNamesToStructFields(argNames, value) if err != nil { return err } + for i, arg := range nonIndexedArgs { field := value.FieldByName(abi2struct[arg.Name]) if !field.IsValid() { return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name) } + if err := set(field, reflect.ValueOf(marshalledValues[i])); err != nil { return err } @@ -167,6 +187,7 @@ func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface if value.Len() < len(marshalledValues) { return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len()) } + for i := range nonIndexedArgs { if err := set(value.Index(i), reflect.ValueOf(marshalledValues[i])); err != nil { return err @@ -175,6 +196,7 @@ func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface default: return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", value.Type()) } + return nil } @@ -184,12 +206,14 @@ func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { nonIndexedArgs := arguments.NonIndexed() retval := make([]interface{}, 0, len(nonIndexedArgs)) + virtualArgs := 0 for index, arg := range nonIndexedArgs { marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) if err != nil { return nil, err } + if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. @@ -207,8 +231,10 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { // coded as just like uint256,bool,uint256 virtualArgs += getTypeSize(arg.Type)/32 - 1 } + retval = append(retval, marshalledValue) } + return retval, nil } @@ -234,7 +260,9 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { for _, abiArg := range abiArgs { inputOffset += getTypeSize(abiArg.Type) } + var ret []byte + for i, a := range args { input := abiArgs[i] // pack the input @@ -269,5 +297,6 @@ func ToCamelCase(input string) string { parts[i] = strings.ToUpper(s[:1]) + s[1:] } } + return strings.Join(parts, "") } diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index 494dc88a57..e8ffc50218 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -44,14 +44,17 @@ var ErrNotAuthorized = errors.New("not authorized to sign this account") // 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 := io.ReadAll(keyin) if err != nil { return nil, err } + key, err := keystore.DecryptKey(json, passphrase) if err != nil { return nil, err } + return NewKeyedTransactor(key.PrivateKey), nil } @@ -61,7 +64,9 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { // 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(address common.Address, tx *types.Transaction) (*types.Transaction, error) { @@ -84,8 +89,10 @@ func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account // 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(address common.Address, tx *types.Transaction) (*types.Transaction, error) { @@ -109,10 +116,12 @@ func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.I if err != nil { return nil, err } + key, err := keystore.DecryptKey(json, passphrase) if err != nil { return nil, err } + return NewKeyedTransactorWithChainID(key.PrivateKey, chainID) } @@ -122,7 +131,9 @@ func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accou if chainID == nil { return nil, ErrNoChainID } + signer := types.LatestSignerForChainID(chainID) + return &TransactOpts{ From: account.Address, Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { @@ -143,10 +154,13 @@ func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accou // 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.LatestSignerForChainID(chainID) + return &TransactOpts{ From: keyAddr, Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { diff --git a/accounts/abi/bind/backends/bor_simulated.go b/accounts/abi/bind/backends/bor_simulated.go index d5cbec3b88..03130d2969 100644 --- a/accounts/abi/bind/backends/bor_simulated.go +++ b/accounts/abi/bind/backends/bor_simulated.go @@ -15,6 +15,7 @@ func (fb *filterBackend) GetBorBlockReceipt(ctx context.Context, hash common.Has if number == nil { return nil, nil } + receipt := rawdb.ReadRawBorReceipt(fb.db, hash, *number) if receipt == nil { return nil, nil diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 0a968df984..73c9ce7eb3 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -99,6 +99,7 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis block := backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) backend.rollback(block) + return backend } @@ -171,11 +172,14 @@ func (b *SimulatedBackend) Fork(ctx context.Context, parent common.Hash) error { if len(b.pendingBlock.Transactions()) != 0 { return errors.New("pending block dirty") } + block, err := b.blockByHash(ctx, parent) if err != nil { return err } + b.rollback(block) + return nil } @@ -184,10 +188,12 @@ func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber * if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number) == 0 { return b.blockchain.State() } + block, err := b.blockByNumber(ctx, blockNumber) if err != nil { return nil, err } + return b.blockchain.StateAt(block.Root()) } @@ -241,6 +247,7 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres } val := stateDB.GetState(contract, key) + return val[:], nil } @@ -253,6 +260,7 @@ func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common if receipt == nil { return nil, ethereum.NotFound } + return receipt, nil } @@ -268,10 +276,12 @@ func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common. if tx != nil { return tx, true, nil } + tx, _, _, _ = rawdb.ReadTransaction(b.database, txHash) if tx != nil { return tx, false, nil } + return nil, false, ethereum.NotFound } @@ -406,9 +416,11 @@ func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Ad func newRevertError(result *core.ExecutionResult) *revertError { reason, errUnpack := abi.UnpackRevert(result.Revert()) err := errors.New("execution reverted") + if errUnpack == nil { err = fmt.Errorf("execution reverted: %v", reason) } + return &revertError{ error: err, reason: hexutil.Encode(result.Revert()), @@ -441,10 +453,12 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 { return nil, errBlockNumberUnsupported } + stateDB, err := b.blockchain.State() if err != nil { return nil, err } + res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB) if err != nil { return nil, err @@ -453,6 +467,7 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM if len(res.Revert()) > 0 { return nil, newRevertError(res) } + return res.Return(), res.Err } @@ -470,6 +485,7 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu if len(res.Revert()) > 0 { return nil, newRevertError(res) } + return res.Return(), res.Err } @@ -491,6 +507,7 @@ func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error if b.pendingBlock.Header().BaseFee != nil { return b.pendingBlock.Header().BaseFee, nil } + return big.NewInt(1), nil } @@ -512,6 +529,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs hi uint64 cap uint64 ) + if call.Gas >= params.TxGas { hi = call.Gas } else { @@ -519,6 +537,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs } // Normalize the max fee per gas the call is willing to spend. var feeCap *big.Int + if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) { return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } else if call.GasPrice != nil { @@ -531,24 +550,30 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs // Recap the highest gas allowance with account's balance. if feeCap.BitLen() != 0 { balance := b.pendingState.GetBalance(call.From) // from can't be nil + available := new(big.Int).Set(balance) if call.Value != nil { if call.Value.Cmp(available) >= 0 { return 0, core.ErrInsufficientFundsForTransfer } + available.Sub(available, call.Value) } + allowance := new(big.Int).Div(available, feeCap) if allowance.IsUint64() && hi > allowance.Uint64() { transfer := call.Value if transfer == nil { transfer = new(big.Int) } + log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, "sent", transfer, "feecap", feeCap, "fundable", allowance) + hi = allowance.Uint64() } } + cap = hi // Create a helper to check if a gas allowance results in an executable transaction @@ -563,8 +588,10 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit } + return true, nil, err // Bail out } + return res.Failed(), res, nil } // Execute the binary search and hone in on an executable gas limit @@ -578,6 +605,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs if err != nil { return 0, err } + if failed { lo = mid } else { @@ -590,17 +618,20 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs if err != nil { return 0, err } + if failed { if result != nil && result.Err != vm.ErrOutOfGas { if len(result.Revert()) > 0 { return 0, newRevertError(result) } + return 0, result.Err } // Otherwise, the specified gas cap is too low return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) } } + return hi, nil } @@ -611,12 +642,14 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) { return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } + head := b.blockchain.CurrentHeader() if !b.blockchain.Config().IsLondon(head.Number) { // If there's no basefee, then it must be a non-1559 execution if call.GasPrice == nil { call.GasPrice = new(big.Int) } + call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice } else { // A basefee is provided, necessitating 1559-type execution @@ -628,6 +661,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM if call.GasFeeCap == nil { call.GasFeeCap = new(big.Int) } + if call.GasTipCap == nil { call.GasTipCap = new(big.Int) } @@ -642,6 +676,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM if call.Gas == 0 { call.Gas = 50000000 } + if call.Value == nil { call.Value = new(big.Int) } @@ -687,10 +722,12 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa } // Check transaction validity signer := types.MakeSigner(b.blockchain.Config(), block.Number()) + sender, err := types.Sender(signer, tx) if err != nil { return fmt.Errorf("invalid transaction: %v", err) } + nonce := b.pendingState.GetNonce(sender) if tx.Nonce() != nonce { return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce) @@ -701,6 +738,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa for _, tx := range b.pendingBlock.Transactions() { block.AddTxWithChain(b.blockchain, tx) } + block.AddTxWithChain(b.blockchain, tx) }) stateDB, _ := b.blockchain.State() @@ -708,6 +746,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) b.pendingReceipts = receipts[0] + return nil } @@ -726,6 +765,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter if query.FromBlock != nil { from = query.FromBlock.Int64() } + to := int64(-1) if query.ToBlock != nil { to = query.ToBlock.Int64() @@ -738,10 +778,12 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter if err != nil { return nil, err } + res := make([]types.Log, len(logs)) for i, nLog := range logs { res[i] = *nLog } + return res, nil } @@ -758,6 +800,7 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere // Since we're getting logs in batches, we need to flatten them into a plain stream return event.NewSubscription(func(quit <-chan struct{}) error { defer sub.Unsubscribe() + for { select { case logs := <-sink: @@ -787,6 +830,7 @@ func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *type return event.NewSubscription(func(quit <-chan struct{}) error { defer sub.Unsubscribe() + for { select { case head := <-sink: @@ -856,6 +900,7 @@ func (fb *filterBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNum if block := fb.backend.pendingBlock; block != nil { return block.Header(), nil } + return nil, nil case rpc.LatestBlockNumber: return fb.bc.CurrentHeader(), nil @@ -889,6 +934,7 @@ func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (typ if number == nil { return nil, nil } + return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil } diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 2a6fec1461..bd0b95de6f 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -44,6 +44,7 @@ func TestSimulatedBackend(t *testing.T) { defer goleak.VerifyNone(t, leak.IgnoreList()...) var gasLimit uint64 = 8000029 + key, _ := crypto.GenerateKey() // nolint: gosec auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) genAlloc := make(core.GenesisAlloc) @@ -59,6 +60,7 @@ func TestSimulatedBackend(t *testing.T) { if isPending { t.Fatal("transaction should not be pending") } + if err != ethereum.NotFound { t.Fatalf("err should be `ethereum.NotFound` but received %v", err) } @@ -68,6 +70,7 @@ func TestSimulatedBackend(t *testing.T) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) code := `6060604052600a8060106000396000f360606040526008565b00` + var gas uint64 = 3000000 tx := types.NewContractCreation(0, big.NewInt(0), gas, gasPrice, common.FromHex(code)) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key) @@ -79,18 +82,22 @@ func TestSimulatedBackend(t *testing.T) { txHash = tx.Hash() _, isPending, err = sim.TransactionByHash(context.Background(), txHash) + if err != nil { t.Fatalf("error getting transaction with hash: %v", txHash.String()) } + if !isPending { t.Fatal("transaction should have pending status") } sim.Commit() + _, isPending, err = sim.TransactionByHash(context.Background(), txHash) if err != nil { t.Fatalf("error getting transaction with hash: %v", txHash.String()) } + if isPending { t.Fatal("transaction should not have pending status") } @@ -128,6 +135,7 @@ func simTestBackend(testAddr common.Address) *SimulatedBackend { func TestNewSimulatedBackend(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) expectedBal := big.NewInt(10000000000000000) + sim := simTestBackend(testAddr) defer sim.Close() @@ -140,6 +148,7 @@ func TestNewSimulatedBackend(t *testing.T) { } stateDB, _ := sim.blockchain.State() + bal := stateDB.GetBalance(testAddr) if bal.Cmp(expectedBal) != 0 { t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal) @@ -153,9 +162,11 @@ func TestAdjustTime(t *testing.T) { defer sim.Close() prevTime := sim.pendingBlock.Time() + if err := sim.AdjustTime(time.Second); err != nil { t.Error(err) } + newTime := sim.pendingBlock.Time() if newTime-prevTime != uint64(time.Second.Seconds()) { @@ -172,33 +183,41 @@ func TestNewAdjustTimeFail(t *testing.T) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) } + sim.SendTransaction(context.Background(), signedTx) // AdjustTime should fail on non-empty block if err := sim.AdjustTime(time.Second); err == nil { t.Error("Expected adjust time to error on non-empty block") } + sim.Commit() prevTime := sim.pendingBlock.Time() + if err := sim.AdjustTime(time.Minute); err != nil { t.Error(err) } + newTime := sim.pendingBlock.Time() if newTime-prevTime != uint64(time.Minute.Seconds()) { t.Errorf("adjusted time not equal to a minute. prev: %v, new: %v", prevTime, newTime) } // Put a transaction after adjusting time tx2 := types.NewTransaction(1, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx2, err := types.SignTx(tx2, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) } + sim.SendTransaction(context.Background(), signedTx2) sim.Commit() + newTime = sim.pendingBlock.Time() if newTime-prevTime >= uint64(time.Minute.Seconds()) { t.Errorf("time adjusted, but shouldn't be: prev: %v, new: %v", prevTime, newTime) @@ -208,8 +227,10 @@ func TestNewAdjustTimeFail(t *testing.T) { func TestBalanceAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) expectedBal := big.NewInt(10000000000000000) + sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() bal, err := sim.BalanceAt(bgCtx, testAddr, nil) @@ -227,12 +248,14 @@ func TestBlockByHash(t *testing.T) { core.GenesisAlloc{}, 10000000, ) defer sim.Close() + bgCtx := context.Background() block, err := sim.BlockByNumber(bgCtx, nil) if err != nil { t.Errorf("could not get recent block: %v", err) } + blockByHash, err := sim.BlockByHash(bgCtx, block.Hash()) if err != nil { t.Errorf("could not get recent block: %v", err) @@ -248,12 +271,14 @@ func TestBlockByNumber(t *testing.T) { core.GenesisAlloc{}, 10000000, ) defer sim.Close() + bgCtx := context.Background() block, err := sim.BlockByNumber(bgCtx, nil) if err != nil { t.Errorf("could not get recent block: %v", err) } + if block.NumberU64() != 0 { t.Errorf("did not get most recent block, instead got block number %v", block.NumberU64()) } @@ -265,6 +290,7 @@ func TestBlockByNumber(t *testing.T) { if err != nil { t.Errorf("could not get recent block: %v", err) } + if block.NumberU64() != 1 { t.Errorf("did not get most recent block, instead got block number %v", block.NumberU64()) } @@ -273,6 +299,7 @@ func TestBlockByNumber(t *testing.T) { if err != nil { t.Errorf("could not get block by number: %v", err) } + if blockByNumber.Hash() != block.Hash() { t.Errorf("did not get the same block with height of 1 as before") } @@ -283,6 +310,7 @@ func TestNonceAt(t *testing.T) { sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() nonce, err := sim.NonceAt(bgCtx, testAddr, big.NewInt(0)) @@ -299,6 +327,7 @@ func TestNonceAt(t *testing.T) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewTransaction(nonce, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) @@ -309,6 +338,7 @@ func TestNonceAt(t *testing.T) { if err != nil { t.Errorf("could not add tx to pending block: %v", err) } + sim.Commit() newNonce, err := sim.NonceAt(bgCtx, testAddr, big.NewInt(1)) @@ -326,6 +356,7 @@ func TestNonceAt(t *testing.T) { if err != nil { t.Fatalf("could not get nonce for test addr: %v", err) } + if newNonce != nonce+uint64(1) { t.Fatalf("received incorrect nonce. expected 1, got %v", nonce) } @@ -336,6 +367,7 @@ func TestSendTransaction(t *testing.T) { sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() // create a signed transaction to send @@ -343,6 +375,7 @@ func TestSendTransaction(t *testing.T) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) @@ -353,6 +386,7 @@ func TestSendTransaction(t *testing.T) { if err != nil { t.Errorf("could not add tx to pending block: %v", err) } + sim.Commit() block, err := sim.BlockByNumber(bgCtx, big.NewInt(1)) @@ -374,6 +408,7 @@ func TestTransactionByHash(t *testing.T) { }, 10000000, ) defer sim.Close() + bgCtx := context.Background() // create a signed transaction to send @@ -381,6 +416,7 @@ func TestTransactionByHash(t *testing.T) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) @@ -397,9 +433,11 @@ func TestTransactionByHash(t *testing.T) { if err != nil { t.Errorf("could not get transaction by hash %v: %v", signedTx.Hash(), err) } + if !pending { t.Errorf("expected transaction to be in pending state") } + if receivedTx.Hash() != signedTx.Hash() { t.Errorf("did not received committed transaction. expected hash %v got hash %v", signedTx.Hash(), receivedTx.Hash()) } @@ -411,9 +449,11 @@ func TestTransactionByHash(t *testing.T) { if err != nil { t.Errorf("could not get transaction by hash %v: %v", signedTx.Hash(), err) } + if pending { t.Errorf("expected transaction to not be in pending state") } + if receivedTx.Hash() != signedTx.Hash() { t.Errorf("did not received committed transaction. expected hash %v got hash %v", signedTx.Hash(), receivedTx.Hash()) } @@ -431,6 +471,7 @@ func TestEstimateGas(t *testing.T) { } */ const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + const contractBin = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033" key, _ := crypto.GenerateKey() @@ -514,15 +555,18 @@ func TestEstimateGas(t *testing.T) { Data: common.Hex2Bytes("e09fface"), }, 21275, nil, nil}, } + for _, c := range cases { got, err := sim.EstimateGas(context.Background(), c.message) if c.expectError != nil { if err == nil { t.Fatalf("Expect error, got nil") } + if c.expectError.Error() != err.Error() { t.Fatalf("Expect error, want %v, got %v", c.expectError, err) } + if c.expectData != nil { if err, ok := err.(*revertError); !ok { t.Fatalf("Expect revert error, got %T", err) @@ -530,8 +574,10 @@ func TestEstimateGas(t *testing.T) { t.Fatalf("Error data mismatch, want %v, got %v", c.expectData, err.ErrorData()) } } + continue } + if got != c.expect { t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got) } @@ -546,6 +592,7 @@ func TestEstimateGasWithPrice(t *testing.T) { defer sim.Close() recipient := common.HexToAddress("deadbeef") + var cases = []struct { name string message ethereum.CallMsg @@ -608,20 +655,25 @@ func TestEstimateGasWithPrice(t *testing.T) { Data: nil, }, params.TxGas, errors.New("gas required exceeds allowance (20999)")}, // 20999=(2.2ether-0.1ether-1wei)/(1e14) } + for i, c := range cases { got, err := sim.EstimateGas(context.Background(), c.message) if c.expectError != nil { if err == nil { t.Fatalf("test %d: expect error, got nil", i) } + if c.expectError.Error() != err.Error() { t.Fatalf("test %d: expect error, want %v, got %v", i, c.expectError, err) } + continue } + if c.expectError == nil && err != nil { t.Fatalf("test %d: didn't expect error, got %v", i, err) } + if got != c.expect { t.Fatalf("test %d: gas estimation mismatch, want %d, got %d", i, c.expect, got) } @@ -633,12 +685,14 @@ func TestHeaderByHash(t *testing.T) { sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() header, err := sim.HeaderByNumber(bgCtx, nil) if err != nil { t.Errorf("could not get recent block: %v", err) } + headerByHash, err := sim.HeaderByHash(bgCtx, header.Hash()) if err != nil { t.Errorf("could not get recent block: %v", err) @@ -654,12 +708,14 @@ func TestHeaderByNumber(t *testing.T) { sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() latestBlockHeader, err := sim.HeaderByNumber(bgCtx, nil) if err != nil { t.Errorf("could not get header for tip of chain: %v", err) } + if latestBlockHeader == nil { t.Errorf("received a nil block header") } else if latestBlockHeader.Number.Uint64() != uint64(0) { @@ -681,6 +737,7 @@ func TestHeaderByNumber(t *testing.T) { if blockHeader.Hash() != latestBlockHeader.Hash() { t.Errorf("block header and latest block header are not the same") } + if blockHeader.Number.Int64() != int64(1) { t.Errorf("did not get blockheader for block 1. instead got block %v", blockHeader.Number.Int64()) } @@ -700,7 +757,9 @@ func TestTransactionCount(t *testing.T) { sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() + currentBlock, err := sim.BlockByNumber(bgCtx, nil) if err != nil || currentBlock == nil { t.Error("could not get current block") @@ -719,6 +778,7 @@ func TestTransactionCount(t *testing.T) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) @@ -752,12 +812,14 @@ func TestTransactionInBlock(t *testing.T) { sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() transaction, err := sim.TransactionInBlock(bgCtx, sim.pendingBlock.Hash(), uint(0)) if err == nil && err != errTransactionDoesNotExist { t.Errorf("expected a transaction does not exist error to be received but received %v", err) } + if transaction != nil { t.Errorf("expected transaction to be nil but received %v", transaction) } @@ -776,6 +838,7 @@ func TestTransactionInBlock(t *testing.T) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) @@ -798,6 +861,7 @@ func TestTransactionInBlock(t *testing.T) { if err == nil && err != errTransactionDoesNotExist { t.Errorf("expected a transaction does not exist error to be received but received %v", err) } + if transaction != nil { t.Errorf("expected transaction to be nil but received %v", transaction) } @@ -817,6 +881,7 @@ func TestPendingNonceAt(t *testing.T) { sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() // expect pending nonce to be 0 since account has not been used @@ -834,6 +899,7 @@ func TestPendingNonceAt(t *testing.T) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) @@ -857,10 +923,12 @@ func TestPendingNonceAt(t *testing.T) { // make a new transaction with a nonce of 1 tx = types.NewTransaction(uint64(1), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err = types.SignTx(tx, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) } + err = sim.SendTransaction(bgCtx, signedTx) if err != nil { t.Errorf("could not send tx: %v", err) @@ -882,6 +950,7 @@ func TestTransactionReceipt(t *testing.T) { sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() // create a signed transaction to send @@ -889,6 +958,7 @@ func TestTransactionReceipt(t *testing.T) { gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) if err != nil { t.Errorf("could not sign tx: %v", err) @@ -899,6 +969,7 @@ func TestTransactionReceipt(t *testing.T) { if err != nil { t.Errorf("could not add tx to pending block: %v", err) } + sim.Commit() receipt, err := sim.TransactionReceipt(bgCtx, signedTx.Hash()) @@ -917,11 +988,14 @@ func TestSuggestGasPrice(t *testing.T) { 10000000, ) defer sim.Close() + bgCtx := context.Background() + gasPrice, err := sim.SuggestGasPrice(bgCtx) if err != nil { t.Errorf("could not get gas price: %v", err) } + if gasPrice.Uint64() != sim.pendingBlock.Header().BaseFee.Uint64() { t.Errorf("gas price was not expected value of %v. actual: %v", sim.pendingBlock.Header().BaseFee.Uint64(), gasPrice.Uint64()) } @@ -929,13 +1003,17 @@ func TestSuggestGasPrice(t *testing.T) { func TestPendingCodeAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() + code, err := sim.CodeAt(bgCtx, testAddr, nil) if err != nil { t.Errorf("could not get code at test addr: %v", err) } + if len(code) != 0 { t.Errorf("got code for account that does not have contract code") } @@ -944,7 +1022,9 @@ func TestPendingCodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } + 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) @@ -954,6 +1034,7 @@ func TestPendingCodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } + if len(code) == 0 { t.Errorf("did not get code for account that has contract code") } @@ -965,13 +1046,17 @@ func TestPendingCodeAt(t *testing.T) { func TestCodeAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() + code, err := sim.CodeAt(bgCtx, testAddr, nil) if err != nil { t.Errorf("could not get code at test addr: %v", err) } + if len(code) != 0 { t.Errorf("got code for account that does not have contract code") } @@ -980,17 +1065,21 @@ func TestCodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } + 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) } sim.Commit() + code, err = sim.CodeAt(bgCtx, contractAddr, nil) if err != nil { t.Errorf("could not get code at test addr: %v", err) } + if len(code) == 0 { t.Errorf("did not get code for account that has contract code") } @@ -1005,15 +1094,19 @@ func TestCodeAt(t *testing.T) { // receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} func TestPendingAndCallContract(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() parsed, err := abi.JSON(strings.NewReader(abiJSON)) if err != nil { t.Errorf("could not get code at test addr: %v", err) } + 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) @@ -1033,6 +1126,7 @@ func TestPendingAndCallContract(t *testing.T) { if err != nil { t.Errorf("could not call receive method on contract: %v", err) } + if len(res) == 0 { t.Errorf("result of contract call was empty: %v", res) } @@ -1053,6 +1147,7 @@ func TestPendingAndCallContract(t *testing.T) { if err != nil { t.Errorf("could not call receive method on contract: %v", err) } + if len(res) == 0 { t.Errorf("result of contract call was empty: %v", res) } @@ -1089,8 +1184,10 @@ contract Reverter { }*/ func TestCallContractRevert(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) defer sim.Close() + bgCtx := context.Background() reverterABI := `[{"inputs": [],"name": "noRevert","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertASM","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertNoString","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertString","outputs": [],"stateMutability": "pure","type": "function"}]` @@ -1100,7 +1197,9 @@ func TestCallContractRevert(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } + 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) @@ -1139,14 +1238,17 @@ func TestCallContractRevert(t *testing.T) { if err == nil { t.Errorf("call to %v was not reverted", key) } + if res != nil { t.Errorf("result from %v was not nil: %v", key, res) } + if val != nil { rerr, ok := err.(*revertError) if !ok { t.Errorf("expect revert error") } + if rerr.Error() != "execution reverted: "+val.(string) { t.Errorf("error was malformed: got %v want %v", rerr.Error(), val) } @@ -1157,17 +1259,21 @@ func TestCallContractRevert(t *testing.T) { } } } + input, err := parsed.Pack("noRevert") if err != nil { t.Errorf("could not pack noRevert function on contract: %v", err) } + res, err := cl(input) if err != nil { t.Error("call to noRevert was reverted") } + if res == nil { t.Errorf("result from noRevert was nil") } + sim.Commit() } } @@ -1184,6 +1290,7 @@ func TestCallContractRevert(t *testing.T) { // having a chain length of just n+1 means that a reorg occurred. func TestFork(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) defer sim.Close() // 1. @@ -1237,15 +1344,18 @@ const callableBin = "6080604052348015600f57600080fd5b5060998061001e6000396000f3f // 10. Check that the event was reborn. func TestForkLogsReborn(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) defer sim.Close() // 1. parsed, _ := abi.JSON(strings.NewReader(callableAbi)) auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) + _, _, contract, err := bind.DeployContract(auth, parsed, common.FromHex(callableBin), sim) if err != nil { t.Errorf("deploying contract: %v", err) } + sim.Commit() // 2. logs, sub, err := contract.WatchLogs(nil, "Called") @@ -1260,12 +1370,14 @@ func TestForkLogsReborn(t *testing.T) { if err != nil { t.Errorf("transacting: %v", err) } + sim.Commit() // 5. log := <-logs if log.TxHash != tx.Hash() { t.Error("wrong event tx hash") } + if log.Removed { t.Error("Event should be included") } @@ -1281,6 +1393,7 @@ func TestForkLogsReborn(t *testing.T) { if log.TxHash != tx.Hash() { t.Error("wrong event tx hash") } + if !log.Removed { t.Error("Event should be removed") } @@ -1288,12 +1401,14 @@ func TestForkLogsReborn(t *testing.T) { if err := sim.SendTransaction(context.Background(), tx); err != nil { t.Errorf("sending transaction: %v", err) } + sim.Commit() // 10. log = <-logs if log.TxHash != tx.Hash() { t.Error("wrong event tx hash") } + if log.Removed { t.Error("Event should be included") } @@ -1310,6 +1425,7 @@ func TestForkLogsReborn(t *testing.T) { // 6. Check that the TX is now included in block 2. func TestForkResendTx(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) defer sim.Close() // 1. @@ -1333,9 +1449,11 @@ func TestForkResendTx(t *testing.T) { } // 5. sim.Commit() + if err := sim.SendTransaction(context.Background(), tx); err != nil { t.Errorf("sending transaction: %v", err) } + sim.Commit() // 6. receipt, _ = sim.TransactionReceipt(context.Background(), tx.Hash()) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index b03f431f77..7c696219a1 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -97,14 +97,17 @@ type MetaData struct { func (m *MetaData) GetAbi() (*abi.ABI, error) { m.mu.Lock() defer m.mu.Unlock() + if m.ab != nil { return m.ab, nil } + if parsed, err := abi.JSON(strings.NewReader(m.ABI)); err != nil { return nil, err } else { m.ab = &parsed } + return m.ab, nil } @@ -141,11 +144,14 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co if err != nil { return common.Address{}, nil, nil, err } + tx, err := c.transact(opts, nil, append(bytecode, input...)) if err != nil { return common.Address{}, nil, nil, err } + c.address = crypto.CreateAddress(opts.From, tx.Nonce()) + return c.address, tx, c, nil } @@ -158,6 +164,7 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri if opts == nil { opts = new(CallOpts) } + if results == nil { results = new([]interface{}) } @@ -166,21 +173,25 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri if err != nil { return err } + var ( msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input} ctx = ensureContext(opts.Context) code []byte output []byte ) + if opts.Pending { pb, ok := c.caller.(PendingContractCaller) if !ok { return ErrNoPendingState } + output, err = pb.PendingCallContract(ctx, msg) if err != nil { return err } + if len(output) == 0 { // Make sure we have a contract to operate on, and bail out otherwise. if code, err = pb.PendingCodeAt(ctx, c.address); err != nil { @@ -194,6 +205,7 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri if err != nil { return err } + if len(output) == 0 { // Make sure we have a contract to operate on, and bail out otherwise. if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { @@ -207,9 +219,12 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri if len(*results) == 0 { res, err := c.abi.Unpack(method, output) *results = res + return err } + res := *results + return c.abi.UnpackIntoInterface(res[0], method, output) } @@ -254,6 +269,7 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add if err != nil { return nil, err } + gasTipCap = tip } // Estimate FeeCap @@ -264,13 +280,16 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add new(big.Int).Mul(head.BaseFee, big.NewInt(basefeeWiggleMultiplier)), ) } + if gasFeeCap.Cmp(gasTipCap) < 0 { return nil, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", gasFeeCap, gasTipCap) } // Estimate GasLimit gasLimit := opts.GasLimit + if opts.GasLimit == 0 { var err error + gasLimit, err = c.estimateGasLimit(opts, contract, input, nil, gasTipCap, gasFeeCap, value) if err != nil { return nil, err @@ -281,6 +300,7 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add if err != nil { return nil, err } + baseTx := &types.DynamicFeeTx{ To: contract, Nonce: nonce, @@ -290,6 +310,7 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add Value: value, Data: input, } + return types.NewTx(baseTx), nil } @@ -309,12 +330,15 @@ func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Addr if err != nil { return nil, err } + gasPrice = price } // Estimate GasLimit gasLimit := opts.GasLimit + if opts.GasLimit == 0 { var err error + gasLimit, err = c.estimateGasLimit(opts, contract, input, gasPrice, nil, nil, value) if err != nil { return nil, err @@ -325,6 +349,7 @@ func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Addr if err != nil { return nil, err } + baseTx := &types.LegacyTx{ To: contract, Nonce: nonce, @@ -333,6 +358,7 @@ func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Addr Value: value, Data: input, } + return types.NewTx(baseTx), nil } @@ -345,6 +371,7 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad return 0, ErrNoCode } } + msg := ethereum.CallMsg{ From: opts.From, To: contract, @@ -354,6 +381,7 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad Value: value, Data: input, } + return c.transactor.EstimateGas(ensureContext(opts.Context), msg) } @@ -376,6 +404,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i rawTx *types.Transaction err error ) + if opts.GasPrice != nil { rawTx, err = c.createLegacyTx(opts, contract, input) } else if opts.GasFeeCap != nil && opts.GasTipCap != nil { @@ -391,6 +420,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i rawTx, err = c.createLegacyTx(opts, contract, input) } } + if err != nil { return nil, err } @@ -398,16 +428,20 @@ 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(opts.From, rawTx) if err != nil { return nil, err } + if opts.NoSend { return signedTx, nil } + if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { return nil, err } + return signedTx, nil } @@ -443,6 +477,7 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int if err != nil { return nil, nil, err } + sub, err := event.NewSubscription(func(quit <-chan struct{}) error { for _, log := range buff { select { @@ -451,12 +486,14 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int return nil } } + return nil }), nil if err != nil { return nil, nil, err } + return logs, sub, nil } @@ -484,10 +521,12 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter if opts.Start != nil { config.FromBlock = new(big.Int).SetUint64(*opts.Start) } + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) if err != nil { return nil, nil, err } + return logs, sub, nil } @@ -497,20 +536,25 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) if len(log.Topics) == 0 { return errNoEventSignature } + if log.Topics[0] != c.abi.Events[event].ID { return errEventSignatureMismatch } + if len(log.Data) > 0 { if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { return err } } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { if arg.Indexed { indexed = append(indexed, arg) } } + return abi.ParseTopics(out, indexed, log.Topics[1:]) } @@ -520,20 +564,25 @@ func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event strin if len(log.Topics) == 0 { return errNoEventSignature } + if log.Topics[0] != c.abi.Events[event].ID { return errEventSignatureMismatch } + if len(log.Data) > 0 { if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { return err } } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { if arg.Indexed { indexed = append(indexed, arg) } } + return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:]) } @@ -543,5 +592,6 @@ func ensureContext(ctx context.Context) context.Context { if ctx == nil { return context.Background() } + return ctx } diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 8bb388b044..2a2f6e9b1f 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -214,6 +214,7 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { if err != nil { t.Fatal(err) } + hash := crypto.Keccak256Hash(sliceBytes) topics := []common.Hash{ crypto.Keccak256Hash([]byte("received(string[],address,uint256,bytes)")), @@ -239,6 +240,7 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { if err != nil { t.Fatal(err) } + hash := crypto.Keccak256Hash(arrBytes) topics := []common.Hash{ crypto.Keccak256Hash([]byte("received(address[2],address,uint256,bytes)")), @@ -265,7 +267,9 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)")) functionSelector := hash[:4] functionTyBytes := append(addrBytes, functionSelector...) + var functionTy [24]byte + copy(functionTy[:], functionTyBytes[0:24]) topics := []common.Hash{ crypto.Keccak256Hash([]byte("received(function,address,uint256,bytes)")), @@ -361,6 +365,7 @@ func unpackAndCheck(t *testing.T, bc *bind.BoundContract, expected map[string]in if len(received) != len(expected) { t.Fatalf("unpacked map length %v not equal expected length of %v", len(received), len(expected)) } + for name, elem := range expected { if !reflect.DeepEqual(elem, received[name]) { t.Errorf("field %v does not match expected, want %v, got %v", name, elem, received[name]) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 05cca8e90b..ac2eeb5d51 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -92,6 +92,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] // isLib is the map used to flag each encountered library as such isLib = make(map[string]struct{}) ) + for i := 0; i < len(types); i++ { // Parse the actual ABI to generate the binding for evmABI, err := abi.JSON(strings.NewReader(abis[i])) @@ -103,6 +104,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] if unicode.IsSpace(r) { return -1 } + return r }, abis[i]) @@ -139,28 +141,35 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] if !original.IsConstant() { identifiers = transactIdentifiers } + if identifiers[normalizedName] { return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) } + identifiers[normalizedName] = true normalized.Name = normalizedName normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { if input.Name == "" || isKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } + if hasStruct(input.Type) { bindStructType[lang](input.Type, structs) } } + normalized.Outputs = make([]abi.Argument, len(original.Outputs)) copy(normalized.Outputs, original.Outputs) + for j, output := range normalized.Outputs { if output.Name != "" { normalized.Outputs[j].Name = capitalise(output.Name) } + if hasStruct(output.Type) { bindStructType[lang](output.Type, structs) } @@ -172,6 +181,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} } } + for _, original := range evmABI.Events { // Skip anonymous events as they don't support explicit filtering if original.Anonymous { @@ -185,12 +195,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] if eventIdentifiers[normalizedName] { return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) } + eventIdentifiers[normalizedName] = true normalized.Name = normalizedName used := make(map[string]bool) normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { if input.Name == "" || isKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) @@ -202,8 +214,10 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] used[capitalise(normalized.Inputs[j].Name)] = true break } + normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index) } + if hasStruct(input.Type) { bindStructType[lang](input.Type, structs) } @@ -215,9 +229,11 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] if evmABI.HasFallback() { fallback = &tmplMethod{Original: evmABI.Fallback} } + if evmABI.HasReceive() { receive = &tmplMethod{Original: evmABI.Receive} } + contracts[types[i]] = &tmplContract{ Type: capitalise(types[i]), InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), @@ -241,6 +257,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] if err != nil { log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err) } + if matched { contracts[types[i]].Libraries[pattern] = name // keep track that this type is a library @@ -271,6 +288,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] "capitalise": capitalise, "decapitalise": decapitalise, } + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) if err := tmpl.Execute(buffer, data); err != nil { return "", err @@ -281,6 +299,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] if err != nil { return "", fmt.Errorf("%v\n%s", err, buffer) } + return string(code), nil } // For all others just return as is for now @@ -304,6 +323,7 @@ func bindBasicTypeGo(kind abi.Type) string { case "8", "16", "32", "64": return fmt.Sprintf("%sint%s", parts[1], parts[2]) } + return "*big.Int" case abi.FixedBytesTy: return fmt.Sprintf("[%d]byte", kind.Size) @@ -353,6 +373,7 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { if bound == "string" || bound == "[]byte" { bound = "common.Hash" } + return bound } @@ -378,26 +399,32 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { if s, exist := structs[id]; exist { return s.Name } + var ( names = make(map[string]bool) fields []*tmplField ) + for i, elem := range kind.TupleElems { name := capitalise(kind.TupleRawNames[i]) name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] }) names[name] = true + fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem}) } + name := kind.TupleRawName if name == "" { name = fmt.Sprintf("Struct%d", len(structs)) } + name = capitalise(name) structs[id] = &tmplStruct{ Name: name, Fields: fields, } + return name case abi.ArrayTy: return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs) @@ -420,6 +447,7 @@ func alias(aliases map[string]string, n string) string { if alias, exist := aliases[n]; exist { return alias } + return n } @@ -439,6 +467,7 @@ func decapitalise(input string) string { } goForm := abi.ToCamelCase(input) + return strings.ToLower(goForm[:1]) + goForm[1:] } @@ -448,7 +477,9 @@ func structured(args abi.Arguments) bool { if len(args) < 2 { return false } + exists := make(map[string]bool) + for _, out := range args { // If the name is anonymous, we can't organize into a struct if out.Name == "" { @@ -460,8 +491,10 @@ func structured(args abi.Arguments) bool { if field == "" || exists[field] { return false } + exists[field] = true } + return true } diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 17e8a341b5..c86afa03b0 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -2071,6 +2071,7 @@ func TestGolangBindings(t *testing.T) { if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } + if err = os.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+".go"), []byte(bind), 0600); err != nil { t.Fatalf("test %d: failed to write binding: %v", i, err) } @@ -2095,23 +2096,29 @@ func TestGolangBindings(t *testing.T) { // Convert the package to go modules and use the current source for go-ethereum moder := exec.Command(gocmd, "mod", "init", "bindtest") moder.Dir = pkg + if out, err := moder.CombinedOutput(); err != nil { t.Fatalf("failed to convert binding test to modules: %v\n%s", err, out) } + pwd, _ := os.Getwd() 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 + if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("failed to run binding test: %v\n%s", err, out) } diff --git a/accounts/abi/bind/util.go b/accounts/abi/bind/util.go index b931fbb04d..2638066b91 100644 --- a/accounts/abi/bind/util.go +++ b/accounts/abi/bind/util.go @@ -34,6 +34,7 @@ func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*ty defer queryTicker.Stop() logger := log.New("hash", tx.Hash()) + for { receipt, err := b.TransactionReceipt(ctx, tx.Hash()) if err == nil { @@ -61,10 +62,12 @@ func WaitDeployed(ctx context.Context, b DeployBackend, tx *types.Transaction) ( if tx.To() != nil { return common.Address{}, errors.New("tx is not contract creation") } + receipt, err := WaitMined(ctx, b, tx) if err != nil { return common.Address{}, err } + if receipt.ContractAddress == (common.Address{}) { return common.Address{}, errors.New("zero address") } @@ -75,5 +78,6 @@ func WaitDeployed(ctx context.Context, b DeployBackend, tx *types.Transaction) ( if err == nil && len(code) == 0 { err = ErrNoCodeAfterDeploy } + return receipt.ContractAddress, err } diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go index 75fbc91ceb..f9f96a04a8 100644 --- a/accounts/abi/bind/util_test.go +++ b/accounts/abi/bind/util_test.go @@ -76,8 +76,10 @@ func TestWaitDeployed(t *testing.T) { mined = make(chan struct{}) ctx = context.Background() ) + go func() { address, err = bind.WaitDeployed(ctx, backend, tx) + close(mined) }() @@ -90,6 +92,7 @@ func TestWaitDeployed(t *testing.T) { if err != test.wantErr { t.Errorf("test %q: error mismatch: want %q, got %q", name, test.wantErr, err) } + if address != test.wantAddress { t.Errorf("test %q: unexpected contract address %s", name, address.Hex()) } @@ -115,10 +118,12 @@ func TestWaitDeployedCornerCases(t *testing.T) { code := "6060604052600a8060106000396000f360606040526008565b00" tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code)) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() backend.SendTransaction(ctx, tx) backend.Commit() + notContentCreation := errors.New("tx is not contract creation") if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContentCreation.Error() { t.Errorf("error missmatch: want %q, got %q, ", notContentCreation, err) diff --git a/accounts/abi/error.go b/accounts/abi/error.go index f53c996def..5976d859e6 100644 --- a/accounts/abi/error.go +++ b/accounts/abi/error.go @@ -46,6 +46,7 @@ func NewError(name string, inputs Arguments) Error { // and precompute string and sig representation. names := make([]string, len(inputs)) types := make([]string, len(inputs)) + for i, input := range inputs { if input.Name == "" { inputs[i] = Argument{ @@ -86,8 +87,10 @@ func (e *Error) Unpack(data []byte) (interface{}, error) { if len(data) < 4 { return "", errors.New("invalid data for unpacking") } + if !bytes.Equal(data[:4], e.ID[:4]) { return "", errors.New("invalid data for unpacking") } + return e.Inputs.Unpack(data[4:]) } diff --git a/accounts/abi/error_handling.go b/accounts/abi/error_handling.go index c106e9ac43..e237183e4f 100644 --- a/accounts/abi/error_handling.go +++ b/accounts/abi/error_handling.go @@ -40,6 +40,7 @@ func formatSliceString(kind reflect.Kind, sliceSize int) string { if sliceSize == -1 { return fmt.Sprintf("[]%v", kind) } + return fmt.Sprintf("[%d]%v", sliceSize, kind) } @@ -63,6 +64,7 @@ func sliceTypeCheck(t Type, val reflect.Value) error { if val.Type().Elem().Kind() != t.Elem.GetType().Kind() { return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) } + return nil } diff --git a/accounts/abi/event.go b/accounts/abi/event.go index f9457b86af..e4e93305d0 100644 --- a/accounts/abi/event.go +++ b/accounts/abi/event.go @@ -64,6 +64,7 @@ func NewEvent(name, rawName string, anonymous bool, inputs Arguments) Event { // and precompute string and sig representation. names := make([]string, len(inputs)) types := make([]string, len(inputs)) + for i, input := range inputs { if input.Name == "" { inputs[i] = Argument{ diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index 8f73419496..294294c7d0 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -149,11 +149,14 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` abi, err := JSON(strings.NewReader(definition)) require.NoError(t, err) + var b bytes.Buffer + var i uint8 = 1 for ; i <= 3; i++ { b.Write(packNum(reflect.ValueOf(i))) } + unpacked, err := abi.Unpack("test", b.Bytes()) require.NoError(t, err) require.Equal(t, [2]uint8{1, 2}, unpacked[0]) @@ -209,6 +212,7 @@ func TestEventTupleUnpack(t *testing.T) { bigintExpected2 := big.NewInt(2218516807680) bigintExpected3 := big.NewInt(1000001) addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268") + var testCases = []struct { data string dest interface{} @@ -343,24 +347,33 @@ func TestEventTupleUnpack(t *testing.T) { func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, assert *assert.Assertions) error { data, err := hex.DecodeString(hexData) assert.NoError(err, "Hex data should be a correct hex-string") + var e Event + assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI") a := ABI{Events: map[string]Event{"e": e}} + return a.UnpackIntoInterface(dest, "e", data) } // TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder. func TestEventUnpackIndexed(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` + type testStruct struct { Value1 uint8 // indexed Value2 uint8 } + abi, err := JSON(strings.NewReader(definition)) require.NoError(t, err) + var b bytes.Buffer + b.Write(packNum(reflect.ValueOf(uint8(8)))) + var rst testStruct + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) require.Equal(t, uint8(0), rst.Value1) require.Equal(t, uint8(8), rst.Value2) @@ -369,13 +382,17 @@ func TestEventUnpackIndexed(t *testing.T) { // TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input. func TestEventIndexedWithArrayUnpack(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]` + type testStruct struct { Value1 [2]uint8 // indexed Value2 string } + abi, err := JSON(strings.NewReader(definition)) require.NoError(t, err) + var b bytes.Buffer + stringOut := "abc" // number of fields that will be encoded * 32 b.Write(packNum(reflect.ValueOf(32))) @@ -383,6 +400,7 @@ func TestEventIndexedWithArrayUnpack(t *testing.T) { b.Write(common.RightPadBytes([]byte(stringOut), 32)) var rst testStruct + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) require.Equal(t, [2]uint8{0, 0}, rst.Value1) require.Equal(t, stringOut, rst.Value2) diff --git a/accounts/abi/method.go b/accounts/abi/method.go index f69e3ee9b5..9b62d6e232 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -97,10 +97,12 @@ func NewMethod(name string, rawName string, funType FunctionType, mutability str inputNames = make([]string, len(inputs)) outputNames = make([]string, len(outputs)) ) + for i, input := range inputs { inputNames[i] = fmt.Sprintf("%v %v", input.Type, input.Name) types[i] = input.Type.String() } + for i, output := range outputs { outputNames[i] = output.Type.String() if len(output.Name) > 0 { @@ -113,6 +115,7 @@ func NewMethod(name string, rawName string, funType FunctionType, mutability str sig string id []byte ) + if funType == Function { sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ",")) id = crypto.Keccak256([]byte(sig))[:4] @@ -123,9 +126,11 @@ func NewMethod(name string, rawName string, funType FunctionType, mutability str if state == "nonpayable" { state = "" } + if state != "" { state = state + " " } + identity := fmt.Sprintf("function %v", rawName) if funType == Fallback { identity = "fallback" @@ -134,6 +139,7 @@ func NewMethod(name string, rawName string, funType FunctionType, mutability str } else if funType == Constructor { identity = "constructor" } + str := fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputNames, ", "), state, strings.Join(outputNames, ", ")) return Method{ diff --git a/accounts/abi/method_test.go b/accounts/abi/method_test.go index 395a528965..08c519d22e 100644 --- a/accounts/abi/method_test.go +++ b/accounts/abi/method_test.go @@ -91,6 +91,7 @@ func TestMethodString(t *testing.T) { } else { got = abi.Methods[test.method].String() } + if got != test.expectation { t.Errorf("expected string to be %s, got %s", test.expectation, got) } @@ -131,6 +132,7 @@ func TestMethodSig(t *testing.T) { expect: "complexTuple((uint256,uint256)[5][])", }, } + abi, err := JSON(strings.NewReader(methoddata)) if err != nil { t.Fatal(err) diff --git a/accounts/abi/pack.go b/accounts/abi/pack.go index 0cd91cb4fa..972e6fd52c 100644 --- a/accounts/abi/pack.go +++ b/accounts/abi/pack.go @@ -51,19 +51,23 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) { if reflectValue.Bool() { return math.PaddedBigBytes(common.Big1, 32), nil } + return math.PaddedBigBytes(common.Big0, 32), nil case BytesTy: if reflectValue.Kind() == reflect.Array { reflectValue = mustArrayToByteSlice(reflectValue) } + if reflectValue.Type() != reflect.TypeOf([]byte{}) { return []byte{}, errors.New("Bytes type is neither slice nor array") } + return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil case FixedBytesTy, FunctionTy: if reflectValue.Kind() == reflect.Array { reflectValue = mustArrayToByteSlice(reflectValue) } + return common.RightPadBytes(reflectValue.Bytes(), 32), nil default: return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T) diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go index 5c7cb1cc1a..fe0bf5aa83 100644 --- a/accounts/abi/pack_test.go +++ b/accounts/abi/pack_test.go @@ -38,17 +38,21 @@ func TestPack(t *testing.T) { if err != nil { t.Fatalf("invalid hex %s: %v", test.packed, err) } + inDef := fmt.Sprintf(`[{ "name" : "method", "type": "function", "inputs": %s}]`, test.def) inAbi, err := JSON(strings.NewReader(inDef)) + if err != nil { t.Fatalf("invalid ABI definition %s, %v", inDef, err) } + var packed []byte packed, err = inAbi.Pack("method", test.unpacked) if err != nil { t.Fatalf("test %d (%v) failed: %v", i, test.def, err) } + if !reflect.DeepEqual(packed[4:], encb) { t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, encb, packed[4:]) } @@ -76,6 +80,7 @@ func TestMethodPack(t *testing.T) { } var addrA, addrB = common.Address{1}, common.Address{2} + sig = abi.Methods["sliceAddress"].ID sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) @@ -86,11 +91,13 @@ func TestMethodPack(t *testing.T) { if err != nil { t.Fatal(err) } + if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } var addrC, addrD = common.Address{3}, common.Address{4} + sig = abi.Methods["sliceMultiAddress"].ID sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...) @@ -105,6 +112,7 @@ func TestMethodPack(t *testing.T) { if err != nil { t.Fatal(err) } + if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } @@ -132,10 +140,12 @@ func TestMethodPack(t *testing.T) { sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) sig = append(sig, common.LeftPadBytes(addrC[:], 32)...) sig = append(sig, common.LeftPadBytes(addrD[:], 32)...) + packed, err = abi.Pack("nestedArray", a, []common.Address{addrC, addrD}) if err != nil { t.Fatal(err) } + if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } @@ -148,10 +158,12 @@ func TestMethodPack(t *testing.T) { sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + packed, err = abi.Pack("nestedArray2", [2][]uint8{{1}, {1}}) if err != nil { t.Fatal(err) } + if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } @@ -167,10 +179,12 @@ func TestMethodPack(t *testing.T) { sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + packed, err = abi.Pack("nestedSlice", [][]uint8{{1, 2}, {1, 2}}) if err != nil { t.Fatal(err) } + if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 1f84b111a3..72bc485d7c 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -47,6 +47,7 @@ func ConvertType(in interface{}, proto interface{}) interface{} { if err := set(reflect.ValueOf(proto), reflect.ValueOf(in)); err != nil { panic(err) } + return proto } @@ -56,6 +57,7 @@ func indirect(v reflect.Value) reflect.Value { if v.Kind() == reflect.Ptr && v.Elem().Type() != reflect.TypeOf(big.Int{}) { return indirect(v.Elem()) } + return v } @@ -74,6 +76,7 @@ func reflectIntType(unsigned bool, size int) reflect.Type { return reflect.TypeOf(uint64(0)) } } + switch size { case 8: return reflect.TypeOf(int8(0)) @@ -84,6 +87,7 @@ func reflectIntType(unsigned bool, size int) reflect.Type { case 64: return reflect.TypeOf(int64(0)) } + return reflect.TypeOf(&big.Int{}) } @@ -92,6 +96,7 @@ func reflectIntType(unsigned bool, size int) reflect.Type { func mustArrayToByteSlice(value reflect.Value) reflect.Value { slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len()) reflect.Copy(slice, value) + return slice } @@ -101,6 +106,7 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value { // strict ruleset as bare `reflect` does. func set(dst, src reflect.Value) error { dstType, srcType := dst.Type(), src.Type() + switch { case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Ptr || dst.Elem().CanSet()): return set(dst.Elem(), src) @@ -117,6 +123,7 @@ func set(dst, src reflect.Value) error { default: return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type()) } + return nil } @@ -130,10 +137,12 @@ func setSlice(dst, src reflect.Value) error { return err } } + if dst.CanSet() { dst.Set(slice) return nil } + return errors.New("Cannot set slice, destination not settable") } @@ -141,20 +150,25 @@ func setArray(dst, src reflect.Value) error { if src.Kind() == reflect.Ptr { return set(dst, indirect(src)) } + array := reflect.New(dst.Type()).Elem() + min := src.Len() if src.Len() > dst.Len() { min = dst.Len() } + for i := 0; i < min; i++ { if err := set(array.Index(i), src.Index(i)); err != nil { return err } } + if dst.CanSet() { dst.Set(array) return nil } + return errors.New("Cannot set array, destination not settable") } @@ -162,13 +176,16 @@ func setStruct(dst, src reflect.Value) error { for i := 0; i < src.NumField(); i++ { srcField := src.Field(i) dstField := dst.Field(i) + if !dstField.IsValid() || !srcField.IsValid() { return fmt.Errorf("Could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField) } + if err := set(dstField, srcField); err != nil { return err } } + return nil } @@ -206,6 +223,7 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri } // check which argument field matches with the abi tag. found := false + for _, arg := range argNames { if arg == tagName { if abi2struct[arg] != "" { @@ -241,6 +259,7 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri value.FieldByName(structFieldName).IsValid() { return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", argName) } + continue } @@ -260,5 +279,6 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri struct2abi[structFieldName] = argName } } + return abi2struct, nil } diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go index 76ef1ad2aa..d661e80eef 100644 --- a/accounts/abi/reflect_test.go +++ b/accounts/abi/reflect_test.go @@ -181,6 +181,7 @@ func TestReflectNameToStruct(t *testing.T) { if err != nil { t.Fatalf("Unexpected error: %v", err) } + for fname := range test.want { if m[fname] != test.want[fname] { t.Fatalf("Incorrect value for field %s: expected %v, got %v", fname, test.want[fname], m[fname]) @@ -217,6 +218,7 @@ func TestConvertType(t *testing.T) { if out.X.Cmp(big.NewInt(1)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out.X, big.NewInt(1)) } + if out.Y.Cmp(big.NewInt(2)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out.Y, big.NewInt(2)) } @@ -226,16 +228,20 @@ func TestConvertType(t *testing.T) { val2.Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) val2.Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) val2.Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out2 := *ConvertType(val2.Interface(), new([]T)).(*[]T) if out2[0].X.Cmp(big.NewInt(1)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) } + if out2[0].Y.Cmp(big.NewInt(2)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) } + if out2[1].X.Cmp(big.NewInt(3)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) } + if out2[1].Y.Cmp(big.NewInt(4)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) } @@ -245,16 +251,20 @@ func TestConvertType(t *testing.T) { val3.Elem().Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) val3.Elem().Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) val3.Elem().Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out3 := *ConvertType(val3.Interface(), new([2]T)).(*[2]T) if out3[0].X.Cmp(big.NewInt(1)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) } + if out3[0].Y.Cmp(big.NewInt(2)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) } + if out3[1].X.Cmp(big.NewInt(3)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) } + if out3[1].Y.Cmp(big.NewInt(4)) != 0 { t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) } diff --git a/accounts/abi/selector_parser.go b/accounts/abi/selector_parser.go index 048c87eca6..1bfa004f48 100644 --- a/accounts/abi/selector_parser.go +++ b/accounts/abi/selector_parser.go @@ -42,18 +42,23 @@ func parseToken(unescapedSelector string, isIdent bool) (string, string, error) if len(unescapedSelector) == 0 { return "", "", fmt.Errorf("empty token") } + firstChar := unescapedSelector[0] position := 1 + if !(isAlpha(firstChar) || (isIdent && isIdentifierSymbol(firstChar))) { return "", "", fmt.Errorf("invalid token start: %c", firstChar) } + for position < len(unescapedSelector) { char := unescapedSelector[position] if !(isAlpha(char) || isDigit(char) || (isIdent && isIdentifierSymbol(char))) { break } + position++ } + return unescapedSelector[:position], unescapedSelector[position:], nil } @@ -70,16 +75,20 @@ func parseElementaryType(unescapedSelector string) (string, string, error) { for len(rest) > 0 && rest[0] == '[' { parsedType = parsedType + string(rest[0]) rest = rest[1:] + for len(rest) > 0 && isDigit(rest[0]) { parsedType = parsedType + string(rest[0]) rest = rest[1:] } + if len(rest) == 0 || rest[0] != ']' { return "", "", fmt.Errorf("failed to parse array: expected ']', got %c", unescapedSelector[0]) } + parsedType = parsedType + string(rest[0]) rest = rest[1:] } + return parsedType, rest, nil } @@ -87,18 +96,23 @@ func parseCompositeType(unescapedSelector string) ([]interface{}, string, error) if len(unescapedSelector) == 0 || unescapedSelector[0] != '(' { return nil, "", fmt.Errorf("expected '(', got %c", unescapedSelector[0]) } + parsedType, rest, err := parseType(unescapedSelector[1:]) if err != nil { return nil, "", fmt.Errorf("failed to parse type: %v", err) } + result := []interface{}{parsedType} + for len(rest) > 0 && rest[0] != ')' { parsedType, rest, err = parseType(rest[1:]) if err != nil { return nil, "", fmt.Errorf("failed to parse type: %v", err) } + result = append(result, parsedType) } + if len(rest) == 0 || rest[0] != ')' { return nil, "", fmt.Errorf("expected ')', got '%s'", rest) } @@ -106,6 +120,7 @@ func parseCompositeType(unescapedSelector string) ([]interface{}, string, error) if len(rest) >= 3 && rest[1] == '[' && rest[2] == ']' { return append(result, "[]"), rest[3:], nil } + return result, rest[1:], nil } @@ -113,6 +128,7 @@ func parseType(unescapedSelector string) (interface{}, string, error) { if len(unescapedSelector) == 0 { return nil, "", fmt.Errorf("empty type") } + if unescapedSelector[0] == '(' { return parseCompositeType(unescapedSelector) } else { @@ -122,6 +138,7 @@ func parseType(unescapedSelector string) (interface{}, string, error) { func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) { arguments := make([]ArgumentMarshaling, 0) + for i, arg := range args { // generate dummy name to avoid unmarshal issues name := fmt.Sprintf("name%d", i) @@ -134,15 +151,18 @@ func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) { } // nolint:goconst tupleType := "tuple" + if len(subArgs) != 0 && subArgs[len(subArgs)-1].Type == "[]" { subArgs = subArgs[:len(subArgs)-1] tupleType = "tuple[]" } + arguments = append(arguments, ArgumentMarshaling{name, tupleType, tupleType, subArgs, false}) } else { return nil, fmt.Errorf("failed to assemble args: unexpected type %T", arg) } } + return arguments, nil } @@ -155,7 +175,9 @@ func ParseSelector(unescapedSelector string) (SelectorMarshaling, error) { if err != nil { return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err) } + args := []interface{}{} + if len(rest) >= 2 && rest[0] == '(' && rest[1] == ')' { rest = rest[2:] } else { @@ -164,6 +186,7 @@ func ParseSelector(unescapedSelector string) (SelectorMarshaling, error) { return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err) } } + if len(rest) > 0 { return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': unexpected string '%s'", unescapedSelector, rest) } diff --git a/accounts/abi/selector_parser_test.go b/accounts/abi/selector_parser_test.go index f6f134492b..5d3c0336d4 100644 --- a/accounts/abi/selector_parser_test.go +++ b/accounts/abi/selector_parser_test.go @@ -26,6 +26,7 @@ import ( func TestParseSelector(t *testing.T) { mkType := func(types ...interface{}) []ArgumentMarshaling { var result []ArgumentMarshaling + for i, typeOrComponents := range types { name := fmt.Sprintf("name%d", i) if typeName, ok := typeOrComponents.(string); ok { @@ -38,8 +39,10 @@ func TestParseSelector(t *testing.T) { log.Fatalf("unexpected type %T", typeOrComponents) } } + return result } + tests := []struct { input string name string @@ -65,6 +68,7 @@ func TestParseSelector(t *testing.T) { if err != nil { t.Errorf("test %d: failed to parse selector '%v': %v", i, tt.input, err) } + if selector.Name != tt.name { t.Errorf("test %d: unexpected function name: '%s' != '%s'", i, selector.Name, tt.name) } @@ -72,6 +76,7 @@ func TestParseSelector(t *testing.T) { if selector.Type != "function" { t.Errorf("test %d: unexpected type: '%s' != '%s'", i, selector.Type, "function") } + if !reflect.DeepEqual(selector.Inputs, tt.args) { t.Errorf("test %d: unexpected args: '%v' != '%v'", i, selector.Inputs, tt.args) } diff --git a/accounts/abi/topics.go b/accounts/abi/topics.go index 360df7d5e8..7144c50534 100644 --- a/accounts/abi/topics.go +++ b/accounts/abi/topics.go @@ -30,6 +30,7 @@ import ( // MakeTopics converts a filter query argument list into a filter topic set. func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) { topics := make([][]common.Hash, len(query)) + for i, filter := range query { for _, rule := range filter { var topic common.Hash @@ -81,9 +82,9 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) { // // We only convert stringS and bytes to hash, still need to deal with // array(both fixed-size and dynamic-size) and struct. - // Attempt to generate the topic from funky types val := reflect.ValueOf(rule) + switch { // static byte array case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8: @@ -92,9 +93,11 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) { return nil, fmt.Errorf("unsupported indexed type: %T", rule) } } + topics[i] = append(topics[i], topic) } } + return topics, nil } @@ -105,9 +108,11 @@ func genIntType(rule int64, size uint) []byte { // extended to common.HashLength bytes. topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255} } + for i := uint(0); i < size; i++ { topic[common.HashLength-i-1] = byte(rule >> (i * 8)) } + return topic[:] } @@ -143,7 +148,9 @@ func parseTopicWithSetter(fields Arguments, topics []common.Hash, setter func(Ar if !arg.Indexed { return errors.New("non-indexed field in topic reconstruction") } + var reconstr interface{} + switch arg.Type.T { case TupleTy: return errors.New("tuple type in topic reconstruction") @@ -155,11 +162,14 @@ func parseTopicWithSetter(fields Arguments, topics []common.Hash, setter func(Ar if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 { return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes()) } + var tmp [24]byte + copy(tmp[:], topics[i][8:32]) reconstr = tmp default: var err error + reconstr, err = toGoType(0, arg.Type, topics[i].Bytes()) if err != nil { return err diff --git a/accounts/abi/topics_test.go b/accounts/abi/topics_test.go index 30cf21d0b8..b4bcda24c1 100644 --- a/accounts/abi/topics_test.go +++ b/accounts/abi/topics_test.go @@ -29,6 +29,7 @@ func TestMakeTopics(t *testing.T) { type args struct { query [][]interface{} } + tests := []struct { name string args args @@ -123,6 +124,7 @@ func TestMakeTopics(t *testing.T) { t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr) return } + if !reflect.DeepEqual(got, tt.want) { t.Errorf("makeTopics() = %v, want %v", got, tt.want) } @@ -355,6 +357,7 @@ func TestParseTopics(t *testing.T) { if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr) } + resultObj := tt.args.resultObj() if !reflect.DeepEqual(createObj, resultObj) { t.Errorf("parseTopics() = %v, want %v", createObj, resultObj) @@ -372,6 +375,7 @@ func TestParseTopicsIntoMap(t *testing.T) { if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr) } + resultMap := tt.args.resultMap() if !reflect.DeepEqual(outMap, resultMap) { t.Errorf("parseTopicsIntoMap() = %v, want %v", outMap, resultMap) diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 015558b3e7..ca2b15324e 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -72,6 +72,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty if strings.Count(t, "[") != strings.Count(t, "]") { return Type{}, fmt.Errorf("invalid arg type in abi") } + typ.stringKind = t // if there are brackets, get ready to go into slice/array mode and @@ -84,6 +85,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty } // recursively embed the type i := strings.LastIndex(t, "[") + embeddedType, err := NewType(t[:i], subInternal, components) if err != nil { return Type{}, err @@ -103,14 +105,17 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty // is an array typ.T = ArrayTy typ.Elem = &embeddedType + typ.Size, err = strconv.Atoi(intz[0]) if err != nil { return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) } + typ.stringKind = embeddedType.stringKind + sliced } else { return Type{}, fmt.Errorf("invalid formatting of array type") } + return typ, err } // parse the type and size of the abi-type. @@ -118,12 +123,15 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty if len(matches) == 0 { return Type{}, fmt.Errorf("invalid type '%v'", t) } + parsedType := matches[0] // varSize is the size of the variable var varSize int + if len(parsedType[3]) > 0 { var err error + varSize, err = strconv.Atoi(parsedType[2]) if err != nil { return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) @@ -157,6 +165,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty if varSize > 32 { return Type{}, fmt.Errorf("unsupported arg type: %s", t) } + typ.T = FixedBytesTy typ.Size = varSize } @@ -168,7 +177,9 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty expression string // canonical parameter expression used = make(map[string]bool) ) + expression += "(" + for idx, c := range components { cType, err := NewType(c.Type, c.InternalType, c.Components) if err != nil { @@ -182,6 +193,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty } fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] }) + if err != nil { return Type{}, err } @@ -191,18 +203,22 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty if !isValidFieldName(fieldName) { return Type{}, fmt.Errorf("field %d has invalid name", idx) } + fields = append(fields, reflect.StructField{ Name: fieldName, // reflect.StructOf will panic for any exported field. Type: cType.GetType(), Tag: reflect.StructTag("json:\"" + c.Name + "\""), }) + elems = append(elems, &cType) names = append(names, c.Name) + expression += cType.stringKind if idx != len(components)-1 { expression += "," } } + expression += ")" typ.TupleType = reflect.StructOf(fields) @@ -291,23 +307,29 @@ func (t Type) pack(v reflect.Value) ([]byte, error) { // calculate offset if any offset := 0 offsetReq := isDynamicType(*t.Elem) + if offsetReq { offset = getTypeSize(*t.Elem) * v.Len() } + var tail []byte + for i := 0; i < v.Len(); i++ { val, err := t.Elem.pack(v.Index(i)) if err != nil { return nil, err } + if !offsetReq { ret = append(ret, val...) continue } + ret = append(ret, packNum(reflect.ValueOf(offset))...) offset += len(val) tail = append(tail, val...) } + return append(ret, tail...), nil case TupleTy: // (T1,...,Tk) for k >= 0 and any types T1, …, Tk @@ -328,16 +350,20 @@ func (t Type) pack(v reflect.Value) ([]byte, error) { for _, elem := range t.TupleElems { offset += getTypeSize(*elem) } + var ret, tail []byte + for i, elem := range t.TupleElems { field := v.FieldByName(fieldmap[t.TupleRawNames[i]]) if !field.IsValid() { return nil, fmt.Errorf("field %s for tuple not found in the given struct", t.TupleRawNames[i]) } + val, err := elem.pack(field) if err != nil { return nil, err } + if isDynamicType(*elem) { ret = append(ret, packNum(reflect.ValueOf(offset))...) tail = append(tail, val...) @@ -346,6 +372,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) { ret = append(ret, val...) } } + return append(ret, tail...), nil default: @@ -373,8 +400,10 @@ func isDynamicType(t Type) bool { return true } } + return false } + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem)) } @@ -392,14 +421,17 @@ func getTypeSize(t Type) int { if t.Elem.T == ArrayTy || t.Elem.T == TupleTy { return t.Size * getTypeSize(*t.Elem) } + return t.Size * 32 } else if t.T == TupleTy && !isDynamicType(t) { total := 0 for _, elem := range t.TupleElems { total += getTypeSize(*elem) } + return total } + return 32 } diff --git a/accounts/abi/type_test.go b/accounts/abi/type_test.go index 655751902c..1c5b798012 100644 --- a/accounts/abi/type_test.go +++ b/accounts/abi/type_test.go @@ -110,6 +110,7 @@ func TestTypeRegexp(t *testing.T) { if err != nil { t.Errorf("type %q: failed to parse type string: %v", tt.blob, err) } + if !reflect.DeepEqual(typ, tt.kind) { t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", tt.blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(tt.kind))) } @@ -288,6 +289,7 @@ func TestTypeCheck(t *testing.T) { if err.Error() != test.err { t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err) } + continue } @@ -296,6 +298,7 @@ func TestTypeCheck(t *testing.T) { t.Errorf("%d failed. Expected no err but got: %v", i, err) continue } + if err == nil && len(test.err) != 0 { t.Errorf("%d failed. Expected err: %v but got none", i, test.err) continue @@ -323,9 +326,11 @@ func TestInternalType(t *testing.T) { blob := "tuple" typ, err := NewType(blob, internalType, components) + if err != nil { t.Errorf("type %q: failed to parse type string: %v", blob, err) } + if !reflect.DeepEqual(typ, kind) { t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(kind))) } diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 1a333f8754..5e92288be1 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -40,6 +40,7 @@ func ReadInteger(typ Type, b []byte) (interface{}, error) { if typ.T == UintTy { u64, isu64 := ret.Uint64(), ret.IsUint64() + switch typ.Size { case 8: if !isu64 || u64 > math.MaxUint8 { @@ -81,6 +82,7 @@ func ReadInteger(typ Type, b []byte) (interface{}, error) { } i64, isi64 := ret.Int64(), ret.IsInt64() + switch typ.Size { case 8: if !isi64 || i64 < math.MinInt8 || i64 > math.MaxInt8 { @@ -108,7 +110,6 @@ func ReadInteger(typ Type, b []byte) (interface{}, error) { return i64, nil default: // the only case left for integer is int256 - return ret, nil } } @@ -120,6 +121,7 @@ func readBool(word []byte) (bool, error) { return false, errBadBool } } + switch word[31] { case 0: return false, nil @@ -137,11 +139,13 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { if t.T != FunctionTy { return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array") } + if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 { err = fmt.Errorf("abi: got improperly encoded function type, got %v", word) } else { copy(funcTy[:], word[0:24]) } + return } @@ -154,6 +158,7 @@ func ReadFixedBytes(t Type, word []byte) (interface{}, error) { array := reflect.New(t.GetType()).Elem() reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) + return array.Interface(), nil } @@ -162,6 +167,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) if size < 0 { return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) } + if start+32*size > len(output) { return nil, fmt.Errorf("abi: cannot marshal into go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) } @@ -199,12 +205,14 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) func forTupleUnpack(t Type, output []byte) (interface{}, error) { retval := reflect.New(t.GetType()).Elem() + virtualArgs := 0 for index, elem := range t.TupleElems { marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) if err != nil { return nil, err } + if elem.T == ArrayTy && !isDynamicType(*elem) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. @@ -222,8 +230,10 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) { // coded as just like uint256,bool,uint256 virtualArgs += getTypeSize(*elem)/32 - 1 } + retval.Field(index).Set(reflect.ValueOf(marshalledValue)) } + return retval.Interface(), nil } @@ -257,8 +267,10 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { if err != nil { return nil, err } + return forTupleUnpack(t, output[begin:]) } + return forTupleUnpack(t, output[index:]) case SliceTy: return forEachUnpack(t, output[begin:], 0, length) @@ -268,8 +280,10 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { if offset > uint64(len(output)) { return nil, fmt.Errorf("abi: toGoType offset greater than output length: offset: %d, len(output): %d", offset, len(output)) } + return forEachUnpack(t, output[offset:], 0, t.Size) } + return forEachUnpack(t, output[index:], 0, t.Size) case StringTy: // variable arrays are written at the end of the return bytes return string(output[begin : begin+length]), nil @@ -296,6 +310,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { bigOffsetEnd := new(big.Int).SetBytes(output[index : index+32]) bigOffsetEnd.Add(bigOffsetEnd, common.Big32) + outputLength := big.NewInt(int64(len(output))) if bigOffsetEnd.Cmp(outputLength) > 0 { @@ -317,8 +332,10 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err if totalSize.Cmp(outputLength) > 0 { return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %v require %v", outputLength, totalSize) } + start = int(bigOffsetEnd.Uint64()) length = int(lengthBig.Uint64()) + return } @@ -330,8 +347,10 @@ func tuplePointsTo(index int, output []byte) (start int, err error) { if offset.Cmp(outputLen) > 0 { return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen) } + if offset.BitLen() > 63 { return 0, fmt.Errorf("abi offset larger than int64: %v", offset) } + return int(offset.Uint64()), nil } diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 6c2624fb30..184950a6f7 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -38,18 +38,22 @@ func TestUnpack(t *testing.T) { //Unpack def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) abi, err := JSON(strings.NewReader(def)) + if err != nil { t.Fatalf("invalid ABI definition %s: %v", def, err) } + encb, err := hex.DecodeString(test.packed) if err != nil { t.Fatalf("invalid hex %s: %v", test.packed, err) } + out, err := abi.Unpack("method", encb) if err != nil { t.Errorf("test %d (%v) failed: %v", i, test.def, err) return } + if !reflect.DeepEqual(test.unpacked, ConvertType(out[0], test.unpacked)) { t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.unpacked, out[0]) } @@ -74,6 +78,7 @@ func (test unpackTest) checkError(err error) error { } else if len(test.err) > 0 { return fmt.Errorf("expected err: %v but got none", test.err) } + return nil } @@ -229,19 +234,24 @@ func TestLocalUnpackTests(t *testing.T) { //Unpack def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) abi, err := JSON(strings.NewReader(def)) + if err != nil { t.Fatalf("invalid ABI definition %s: %v", def, err) } + encb, err := hex.DecodeString(test.enc) if err != nil { t.Fatalf("invalid hex %s: %v", test.enc, err) } + outptr := reflect.New(reflect.TypeOf(test.want)) + err = abi.UnpackIntoInterface(outptr.Interface(), "method", encb) if err := test.checkError(err); err != nil { t.Errorf("test %d (%v) failed: %v", i, test.def, err) return } + out := outptr.Elem().Interface() if !reflect.DeepEqual(test.want, out) { t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out) @@ -269,13 +279,16 @@ func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) { if err != nil { t.Fatal(err) } + if len(out32) != 2 { t.Fatalf("expected array with 2 values, got %d", len(out32)) } + expected := common.Hex2Bytes("3078313233343536373839300000000000000000000000000000000000000000") if !bytes.Equal(out32[0][:], expected) { t.Errorf("expected %x, got %x\n", expected, out32[0]) } + expected = common.Hex2Bytes("3078303938373635343332310000000000000000000000000000000000000000") if !bytes.Equal(out32[1][:], expected) { t.Errorf("expected %x, got %x\n", expected, out32[1]) @@ -286,13 +299,16 @@ func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) { if err != nil { t.Fatal(err) } + if len(out15) != 2 { t.Fatalf("expected array with 2 values, got %d", len(out15)) } + expected = common.Hex2Bytes("307830313233343500000000000000") if !bytes.Equal(out15[0][:], expected) { t.Errorf("expected %x, got %x\n", expected, out15[0]) } + expected = common.Hex2Bytes("307839383736353400000000000000") if !bytes.Equal(out15[1][:], expected) { t.Errorf("expected %x, got %x\n", expected, out15[1]) @@ -307,6 +323,7 @@ type methodMultiOutput struct { func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) { const definition = `[ { "name" : "multi", "type": "function", "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` + var expected = methodMultiOutput{big.NewInt(1), "hello"} abi, err := JSON(strings.NewReader(definition)) @@ -317,6 +334,7 @@ func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOut buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) buff.Write(common.RightPadBytes([]byte(expected.String), 32)) + return abi, buff.Bytes(), expected } @@ -333,6 +351,7 @@ func TestMethodMultiReturn(t *testing.T) { abi, data, expected := methodMultiReturn(require.New(t)) bigint := new(big.Int) + var testCases = []struct { dest interface{} expected interface{} @@ -384,11 +403,13 @@ func TestMethodMultiReturn(t *testing.T) { "abi: insufficient number of arguments for unpack, want 2, got 1", "Can not unpack into a slice with wrong types", }} + for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { require := require.New(t) err := abi.UnpackIntoInterface(tc.dest, "multi", data) + if tc.error == "" { require.Nil(err, "Should be able to unpack method outputs.") require.Equal(tc.expected, tc.dest) @@ -401,22 +422,27 @@ func TestMethodMultiReturn(t *testing.T) { func TestMultiReturnWithArray(t *testing.T) { const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]` + abi, err := JSON(strings.NewReader(definition)) if err != nil { t.Fatal(err) } + buff := new(bytes.Buffer) buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009")) buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008")) ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9} ret2, ret2Exp := new(uint64), uint64(8) + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } + if !reflect.DeepEqual(*ret1, ret1Exp) { t.Error("array result", *ret1, "!= Expected", ret1Exp) } + if *ret2 != ret2Exp { t.Error("int result", *ret2, "!= Expected", ret2Exp) } @@ -424,10 +450,12 @@ func TestMultiReturnWithArray(t *testing.T) { func TestMultiReturnWithStringArray(t *testing.T) { const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]` + abi, err := JSON(strings.NewReader(definition)) if err != nil { t.Fatal(err) } + buff := new(bytes.Buffer) buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000")) @@ -436,18 +464,23 @@ func TestMultiReturnWithStringArray(t *testing.T) { ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f") ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"} ret4, ret4Exp := new(bool), false + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } + if !reflect.DeepEqual(*ret1, ret1Exp) { t.Error("big.Int array result", *ret1, "!= Expected", ret1Exp) } + if !reflect.DeepEqual(*ret2, ret2Exp) { t.Error("address result", *ret2, "!= Expected", ret2Exp) } + if !reflect.DeepEqual(*ret3, ret3Exp) { t.Error("string array result", *ret3, "!= Expected", ret3Exp) } + if !reflect.DeepEqual(*ret4, ret4Exp) { t.Error("bool result", *ret4, "!= Expected", ret4Exp) } @@ -455,10 +488,12 @@ func TestMultiReturnWithStringArray(t *testing.T) { func TestMultiReturnWithStringSlice(t *testing.T) { const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]` + abi, err := JSON(strings.NewReader(definition)) if err != nil { t.Fatal(err) } + buff := new(bytes.Buffer) buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // output[0] offset buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000120")) // output[1] offset @@ -472,14 +507,18 @@ func TestMultiReturnWithStringSlice(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // output[1] length buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000064")) // output[1][0] value buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000065")) // output[1][1] value + ret1, ret1Exp := new([]string), []string{"ethereum", "go-ethereum"} ret2, ret2Exp := new([]*big.Int), []*big.Int{big.NewInt(100), big.NewInt(101)} + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } + if !reflect.DeepEqual(*ret1, ret1Exp) { t.Error("string slice result", *ret1, "!= Expected", ret1Exp) } + if !reflect.DeepEqual(*ret2, ret2Exp) { t.Error("uint256 slice result", *ret2, "!= Expected", ret2Exp) } @@ -491,10 +530,12 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { // after such nested array argument should be read with the correct offset, // so that it does not read content from the previous array argument. const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]` + abi, err := JSON(strings.NewReader(definition)) if err != nil { t.Fatal(err) } + buff := new(bytes.Buffer) // construct the test array, each 3 char element is joined with 61 '0' chars, // to from the ((3 + 61) * 0.5) = 32 byte elements in the array. @@ -514,12 +555,15 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { {{0x411, 0x412, 0x413}, {0x421, 0x422, 0x423}}, } ret2, ret2Exp := new(uint64), uint64(0x9876) + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } + if !reflect.DeepEqual(*ret1, ret1Exp) { t.Error("array result", *ret1, "!= Expected", ret1Exp) } + if *ret2 != ret2Exp { t.Error("int result", *ret2, "!= Expected", ret2Exp) } @@ -541,6 +585,7 @@ func TestUnmarshal(t *testing.T) { if err != nil { t.Fatal(err) } + buff := new(bytes.Buffer) // marshall mixed bytes (mixedBytes) @@ -568,6 +613,7 @@ func TestUnmarshal(t *testing.T) { // marshal int var Int *big.Int + err = abi.UnpackIntoInterface(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) if err != nil { t.Error(err) @@ -579,6 +625,7 @@ func TestUnmarshal(t *testing.T) { // marshal bool var Bool bool + err = abi.UnpackIntoInterface(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) if err != nil { t.Error(err) @@ -596,6 +643,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(bytesOut) var Bytes []byte + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) @@ -663,6 +711,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.RightPadBytes([]byte("hello"), 32)) var hash common.Hash + err = abi.UnpackIntoInterface(&hash, "fixed", buff.Bytes()) if err != nil { t.Error(err) @@ -676,6 +725,7 @@ func TestUnmarshal(t *testing.T) { // marshal error buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err == nil { t.Error("expected error") @@ -692,10 +742,12 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003")) // marshal int array var intArray [3]*big.Int + err = abi.UnpackIntoInterface(&intArray, "intArraySingle", buff.Bytes()) if err != nil { t.Error(err) } + var testAgainstIntArray [3]*big.Int testAgainstIntArray[0] = big.NewInt(1) testAgainstIntArray[1] = big.NewInt(2) @@ -713,6 +765,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) var outAddr []common.Address + err = abi.UnpackIntoInterface(&outAddr, "addressSliceSingle", buff.Bytes()) if err != nil { t.Fatal("didn't expect error:", err) @@ -740,6 +793,7 @@ func TestUnmarshal(t *testing.T) { A []common.Address B []common.Address } + err = abi.UnpackIntoInterface(&outAddrStruct, "addressSliceDouble", buff.Bytes()) if err != nil { t.Fatal("didn't expect error:", err) @@ -760,6 +814,7 @@ func TestUnmarshal(t *testing.T) { if outAddrStruct.B[0] != (common.Address{2}) { t.Errorf("expected %x, got %x", common.Address{2}, outAddrStruct.B[0]) } + if outAddrStruct.B[1] != (common.Address{3}) { t.Errorf("expected %x, got %x", common.Address{3}, outAddrStruct.B[1]) } @@ -776,10 +831,12 @@ func TestUnmarshal(t *testing.T) { func TestUnpackTuple(t *testing.T) { const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]` + abi, err := JSON(strings.NewReader(simpleTuple)) if err != nil { t.Fatal(err) } + buff := new(bytes.Buffer) buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // ret[a] = 1 @@ -790,9 +847,11 @@ func TestUnpackTuple(t *testing.T) { A *big.Int B *big.Int } + type r struct { Result v } + var ret0 = new(r) err = abi.UnpackIntoInterface(ret0, "tuple", buff.Bytes()) @@ -802,6 +861,7 @@ func TestUnpackTuple(t *testing.T) { if ret0.Result.A.Cmp(big.NewInt(1)) != 0 { t.Errorf("unexpected value unpacked: want %x, got %x", 1, ret0.Result.A) } + if ret0.Result.B.Cmp(big.NewInt(-1)) != 0 { t.Errorf("unexpected value unpacked: want %x, got %x", -1, ret0.Result.B) } @@ -818,6 +878,7 @@ func TestUnpackTuple(t *testing.T) { if err != nil { t.Fatal(err) } + buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // s offset buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")) // t.X = 0 @@ -851,7 +912,9 @@ func TestUnpackTuple(t *testing.T) { FieldT T `abi:"t"` A *big.Int } + var ret Ret + var expected = Ret{ FieldS: S{ A: big.NewInt(1), @@ -871,6 +934,7 @@ func TestUnpackTuple(t *testing.T) { if err != nil { t.Error(err) } + if reflect.DeepEqual(ret, expected) { t.Error("unexpected unpack value") } @@ -932,13 +996,16 @@ func TestOOMMaliciousInput(t *testing.T) { for i, test := range oomTests { def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) abi, err := JSON(strings.NewReader(def)) + if err != nil { t.Fatalf("invalid ABI definition %s: %v", def, err) } + encb, err := hex.DecodeString(test.enc) if err != nil { t.Fatalf("invalid hex: %s" + test.enc) } + _, err = abi.Methods["method"].Outputs.UnpackValues(encb) if err == nil { t.Fatalf("Expected error on malicious input, test %d", i) diff --git a/accounts/accounts.go b/accounts/accounts.go index 93dbd1d20f..ec16e8e610 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -199,6 +199,7 @@ func TextAndHash(data []byte) ([]byte, string) { msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) hasher := sha3.NewLegacyKeccak256() hasher.Write([]byte(msg)) + return hasher.Sum(nil), msg } diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index e8274f9f04..c987f29636 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -26,6 +26,7 @@ import ( func TestTextHash(t *testing.T) { hash := TextHash([]byte("Hello Joe")) want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b") + if !bytes.Equal(hash, want) { t.Fatalf("wrong hash: %x", hash) } diff --git a/accounts/external/backend.go b/accounts/external/backend.go index d403b7e562..bfcbe2b594 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -45,6 +45,7 @@ func NewExternalBackend(endpoint string) (*ExternalBackend, error) { if err != nil { return nil, err } + return &ExternalBackend{ signers: []accounts.Wallet{signer}, }, nil @@ -73,6 +74,7 @@ func NewExternalSigner(endpoint string) (*ExternalSigner, error) { if err != nil { return nil, err } + extsigner := &ExternalSigner{ client: client, endpoint: endpoint, @@ -82,7 +84,9 @@ func NewExternalSigner(endpoint string) (*ExternalSigner, error) { if err != nil { return nil, err } + extsigner.status = fmt.Sprintf("ok [version=%v]", version) + return extsigner, nil } @@ -107,11 +111,13 @@ func (api *ExternalSigner) Close() error { func (api *ExternalSigner) Accounts() []accounts.Account { var accnts []accounts.Account + res, err := api.listAccounts() if err != nil { log.Error("account listing failed", "error", err) return accnts } + for _, addr := range res { accnts = append(accnts, accounts.Account{ URL: accounts.URL{ @@ -121,26 +127,31 @@ func (api *ExternalSigner) Accounts() []accounts.Account { Address: addr, }) } + api.cacheMu.Lock() api.cache = accnts api.cacheMu.Unlock() + return accnts } func (api *ExternalSigner) Contains(account accounts.Account) bool { api.cacheMu.RLock() defer api.cacheMu.RUnlock() + if api.cache == nil { // If we haven't already fetched the accounts, it's time to do so now api.cacheMu.RUnlock() api.Accounts() api.cacheMu.RLock() } + for _, a := range api.cache { if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) { return true } } + return false } @@ -155,6 +166,7 @@ func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain eth // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { var res hexutil.Bytes + var signAddress = common.NewMixedcaseAddress(account.Address) if err := api.client.Call(&res, "account_signData", mimeType, @@ -166,11 +178,13 @@ func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, d if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) { res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use } + return res, nil } func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) { var signature hexutil.Bytes + var signAddress = common.NewMixedcaseAddress(account.Address) if err := api.client.Call(&signature, "account_signData", accounts.MimetypeTextPlain, @@ -178,11 +192,13 @@ func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]by hexutil.Encode(text)); err != nil { return nil, err } + if signature[64] == 27 || signature[64] == 28 { // If clef is used as a backend, it may already have transformed // the signature to ethereum-type signature. signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1 } + return signature, nil } @@ -198,11 +214,14 @@ type signTransactionResult struct { // transaction overrides the chainID parameter. func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { data := hexutil.Bytes(tx.Data()) + var to *common.MixedcaseAddress + if tx.To() != nil { t := common.NewMixedcaseAddress(*tx.To()) to = &t } + args := &apitypes.SendTxArgs{ Data: &data, Nonce: hexutil.Uint64(tx.Nonce()), @@ -225,19 +244,23 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio if chainID != nil && chainID.Sign() != 0 { args.ChainID = (*hexutil.Big)(chainID) } + if tx.Type() != types.LegacyTxType { // However, if the user asked for a particular chain id, then we should // use that instead. if tx.ChainId().Sign() != 0 { args.ChainID = (*hexutil.Big)(tx.ChainId()) } + accessList := tx.AccessList() args.AccessList = &accessList } + var res signTransactionResult if err := api.client.Call(&res, "account_signTransaction", args); err != nil { return nil, err } + return res.Tx, nil } @@ -257,6 +280,7 @@ func (api *ExternalSigner) listAccounts() ([]common.Address, error) { if err := api.client.Call(&res, "account_list"); err != nil { return nil, err } + return res, nil } @@ -265,5 +289,6 @@ func (api *ExternalSigner) pingVersion() (string, error) { if err := api.client.Call(&v, "account_version"); err != nil { return "", err } + return v, nil } diff --git a/accounts/hd.go b/accounts/hd.go index daca75ebbc..8b3a714063 100644 --- a/accounts/hd.go +++ b/accounts/hd.go @@ -70,6 +70,7 @@ func ParseDerivationPath(path string) (DerivationPath, error) { // Handle absolute or relative paths components := strings.Split(path, "/") + switch { case len(components) == 0: return nil, errors.New("empty derivation path") @@ -87,9 +88,11 @@ func ParseDerivationPath(path string) (DerivationPath, error) { if len(components) == 0 { return nil, errors.New("empty derivation path") // Empty relative paths } + for _, component := range components { // Ignore any user added whitespace component = strings.TrimSpace(component) + var value uint32 // Handle hardened paths @@ -102,18 +105,22 @@ func ParseDerivationPath(path string) (DerivationPath, error) { if !ok { return nil, fmt.Errorf("invalid component: %s", component) } + max := math.MaxUint32 - value if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 { if value == 0 { return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max) } + return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max) } + value += uint32(bigval.Uint64()) // Append and repeat result = append(result, value) } + return result, nil } @@ -121,17 +128,21 @@ func ParseDerivationPath(path string) (DerivationPath, error) { // to its canonical representation. func (path DerivationPath) String() string { result := "m" + for _, component := range path { var hardened bool + if component >= 0x80000000 { component -= 0x80000000 hardened = true } + result = fmt.Sprintf("%s/%d", result, component) if hardened { result += "'" } } + return result } @@ -143,11 +154,14 @@ func (path DerivationPath) MarshalJSON() ([]byte, error) { // UnmarshalJSON a json-serialized string back into a derivation path func (path *DerivationPath) UnmarshalJSON(b []byte) error { var dp string + var err error if err = json.Unmarshal(b, &dp); err != nil { return err } + *path, err = ParseDerivationPath(dp) + return err } @@ -158,6 +172,7 @@ func DefaultIterator(base DerivationPath) func() DerivationPath { 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 @@ -172,6 +187,7 @@ func LedgerLiveIterator(base DerivationPath) func() DerivationPath { 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]++ diff --git a/accounts/hd_test.go b/accounts/hd_test.go index 0743bbe666..ef8e172ec0 100644 --- a/accounts/hd_test.go +++ b/accounts/hd_test.go @@ -81,6 +81,7 @@ 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) diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go index 002813fca8..3f8214059b 100644 --- a/accounts/keystore/account_cache.go +++ b/accounts/keystore/account_cache.go @@ -60,6 +60,7 @@ func (err *AmbiguousAddrError) Error() string { files += ", " } } + return fmt.Sprintf("multiple keys match address (%s)", files) } @@ -83,6 +84,7 @@ func newAccountCache(keydir string) (*accountCache, chan struct{}) { fileC: fileCache{all: mapset.NewThreadUnsafeSet[string]()}, } ac.watcher = newWatcher(ac) + return ac, ac.notify } @@ -92,6 +94,7 @@ func (ac *accountCache) accounts() []accounts.Account { defer ac.mu.Unlock() cpy := make([]accounts.Account, len(ac.all)) copy(cpy, ac.all) + return cpy } @@ -99,6 +102,7 @@ func (ac *accountCache) hasAddress(addr common.Address) bool { ac.maybeReload() ac.mu.Lock() defer ac.mu.Unlock() + return len(ac.byAddr[addr]) > 0 } @@ -139,6 +143,7 @@ func (ac *accountCache) deleteByFile(path string) { if i < len(ac.all) && ac.all[i].URL.Path == path { removed := ac.all[i] ac.all = append(ac.all[:i], ac.all[i+1:]...) + if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { delete(ac.byAddr, removed.Address) } else { @@ -152,6 +157,7 @@ func (ac *accountCache) deleteByFile(path string) { func (ac *accountCache) watcherStarted() bool { ac.mu.Lock() defer ac.mu.Unlock() + return ac.watcher.running || ac.watcher.runEnded } @@ -161,6 +167,7 @@ func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.A return append(slice[:i], slice[i+1:]...) } } + return slice } @@ -173,20 +180,24 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) { if (a.Address != common.Address{}) { matches = ac.byAddr[a.Address] } + if a.URL.Path != "" { // If only the basename is specified, complete the path. if !strings.ContainsRune(a.URL.Path, filepath.Separator) { a.URL.Path = filepath.Join(ac.keydir, a.URL.Path) } + for i := range matches { if matches[i].URL == a.URL { return matches[i], nil } } + if (a.Address == common.Address{}) { return accounts.Account{}, ErrNoMatch } } + switch len(matches) { case 1: return matches[0], nil @@ -196,6 +207,7 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) { err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} copy(err.Matches, matches) sort.Sort(accountsByURL(err.Matches)) + return accounts.Account{}, err } } @@ -207,6 +219,7 @@ func (ac *accountCache) maybeReload() { ac.mu.Unlock() return // A watcher is running and will keep the cache up-to-date. } + if ac.throttle == nil { ac.throttle = time.NewTimer(0) } else { @@ -227,9 +240,11 @@ func (ac *accountCache) maybeReload() { func (ac *accountCache) close() { ac.mu.Lock() ac.watcher.close() + if ac.throttle != nil { ac.throttle.Stop() } + if ac.notify != nil { close(ac.notify) ac.notify = nil @@ -246,6 +261,7 @@ func (ac *accountCache) scanAccounts() error { log.Debug("Failed to reload keystore contents", "err", err) return err } + if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 { return nil } @@ -256,18 +272,21 @@ func (ac *accountCache) scanAccounts() error { Address string `json:"address"` } ) + readAccount := func(path string) *accounts.Account { fd, err := os.Open(path) if err != nil { log.Trace("Failed to open keystore file", "path", path, "err", err) return nil } + defer fd.Close() buf.Reset(fd) // Parse the address. key.Address = "" err = json.NewDecoder(buf).Decode(&key) addr := common.HexToAddress(key.Address) + switch { case err != nil: log.Debug("Failed to decode keystore key", "path", path, "err", err) @@ -279,6 +298,7 @@ func (ac *accountCache) scanAccounts() error { URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}, } } + return nil } // Process all the file diffs @@ -289,15 +309,19 @@ func (ac *accountCache) scanAccounts() error { ac.add(*a) } } + for _, path := range deletes.ToSlice() { ac.deleteByFile(path) } + for _, path := range updates.ToSlice() { ac.deleteByFile(path) + if a := readAccount(path); a != nil { ac.add(*a) } } + end := time.Now() select { @@ -305,5 +329,6 @@ func (ac *accountCache) scanAccounts() error { default: } log.Trace("Handled keystore changes", "time", end.Sub(start)) + return nil } diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go index aa71c12018..c9d0a5bc07 100644 --- a/accounts/keystore/account_cache_test.go +++ b/accounts/keystore/account_cache_test.go @@ -62,6 +62,7 @@ func waitWatcherStart(ks *KeyStore) bool { return true } } + return false } @@ -76,9 +77,11 @@ func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { default: return fmt.Errorf("wasn't notified of new accounts") } + return nil } } + return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts) } @@ -89,6 +92,7 @@ func TestWatchNewFile(t *testing.T) { // Ensure the watcher is started before adding any files. ks.Accounts() + if !waitWatcherStart(ks) { t.Fatal("keystore watcher didn't start in time") } @@ -115,6 +119,7 @@ func TestWatchNoDir(t *testing.T) { // Create ks but not the directory that it watches. dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int())) ks := NewKeyStore(dir, LightScryptN, LightScryptP) + list := ks.Accounts() if len(list) > 0 { t.Error("initial account list not empty:", list) @@ -126,6 +131,7 @@ func TestWatchNoDir(t *testing.T) { // Create the directory and copy a key file into it. os.MkdirAll(dir, 0700) defer os.RemoveAll(dir) + file := filepath.Join(dir, "aaa") if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { t.Fatal(err) @@ -134,6 +140,7 @@ func TestWatchNoDir(t *testing.T) { // ks should see the account. wantAccounts := []accounts.Account{cachetestAccounts[0]} wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { list = ks.Accounts() if reflect.DeepEqual(list, wantAccounts) { @@ -143,8 +150,10 @@ func TestWatchNoDir(t *testing.T) { default: t.Fatalf("wasn't notified of new accounts") } + return } + time.Sleep(d) } t.Errorf("\ngot %v\nwant %v", list, wantAccounts) @@ -152,6 +161,7 @@ func TestWatchNoDir(t *testing.T) { func TestCacheInitialReload(t *testing.T) { cache, _ := newAccountCache(cachetestDir) + accounts := cache.accounts() if !reflect.DeepEqual(accounts, cachetestAccounts) { t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts)) @@ -203,15 +213,18 @@ func TestCacheAddDeleteOrder(t *testing.T) { wantAccounts := make([]accounts.Account, len(accs)) copy(wantAccounts, accs) sort.Sort(accountsByURL(wantAccounts)) + list := cache.accounts() if !reflect.DeepEqual(list, wantAccounts) { t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) } + for _, a := range accs { if !cache.hasAddress(a.Address) { t.Errorf("expected hasAccount(%x) to return true", a.Address) } } + if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) { t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) } @@ -229,14 +242,17 @@ func TestCacheAddDeleteOrder(t *testing.T) { wantAccounts[5], } list = cache.accounts() + if !reflect.DeepEqual(list, wantAccountsAfterDelete) { t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete)) } + for _, a := range wantAccountsAfterDelete { if !cache.hasAddress(a.Address) { t.Errorf("expected hasAccount(%x) to return true", a.Address) } } + if cache.hasAddress(wantAccounts[0].Address) { t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address) } @@ -273,6 +289,7 @@ func TestCacheFind(t *testing.T) { Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")}, } + tests := []struct { Query accounts.Account WantResult accounts.Account @@ -308,6 +325,7 @@ func TestCacheFind(t *testing.T) { t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError) continue } + if a != test.WantResult { t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult) continue @@ -328,6 +346,7 @@ func TestUpdatedKeyfileContents(t *testing.T) { if len(list) > 0 { t.Error("initial account list not empty:", list) } + if !waitWatcherStart(ks) { t.Fatal("keystore watcher didn't start in time") } @@ -344,6 +363,7 @@ func TestUpdatedKeyfileContents(t *testing.T) { // ks should see the account. wantAccounts := []accounts.Account{cachetestAccounts[0]} wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { t.Error(err) return @@ -356,11 +376,14 @@ func TestUpdatedKeyfileContents(t *testing.T) { t.Fatal(err) return } + wantAccounts = []accounts.Account{cachetestAccounts[1]} wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { t.Errorf("First replacement failed") t.Error(err) + return } @@ -372,11 +395,14 @@ func TestUpdatedKeyfileContents(t *testing.T) { t.Fatal(err) return } + wantAccounts = []accounts.Account{cachetestAccounts[2]} wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { t.Errorf("Second replacement failed") t.Error(err) + return } @@ -388,9 +414,11 @@ func TestUpdatedKeyfileContents(t *testing.T) { t.Fatal(err) return } + if err := waitForAccounts([]accounts.Account{}, ks); err != nil { t.Errorf("Emptying account file failed") t.Error(err) + return } } @@ -401,5 +429,6 @@ func forceCopyFile(dst, src string) error { if err != nil { return err } + return os.WriteFile(dst, data, 0644) } diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go index 87cc73a921..3c07031a3f 100644 --- a/accounts/keystore/file_cache.go +++ b/accounts/keystore/file_cache.go @@ -45,6 +45,7 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string] if err != nil { return nil, nil, nil, err } + t1 := time.Now() fc.mu.Lock() @@ -55,6 +56,7 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string] mods := mapset.NewThreadUnsafeSet[string]() var newLastMod time.Time + for _, fi := range files { path := filepath.Join(keyDir, fi.Name()) // Skip any non-key files from the folder @@ -69,14 +71,17 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string] if err != nil { return nil, nil, nil, err } + modified := info.ModTime() if modified.After(fc.lastMod) { mods.Add(path) } + if modified.After(newLastMod) { newLastMod = modified } } + t2 := time.Now() // Update the tracked files and return the three sets @@ -89,6 +94,7 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string] // Report on the scanning stats and return log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2)) + return creates, deletes, updates, nil } @@ -102,5 +108,6 @@ func nonKeyFile(fi os.DirEntry) bool { if fi.IsDir() || !fi.Type().IsRegular() { return true } + return false } diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go index 9b2ac14712..b785589563 100644 --- a/accounts/keystore/key.go +++ b/accounts/keystore/key.go @@ -98,26 +98,32 @@ func (k *Key) MarshalJSON() (j []byte, err error) { version, } j, err = json.Marshal(jStruct) + return j, err } func (k *Key) UnmarshalJSON(j []byte) (err error) { keyJSON := new(plainKeyJSON) + err = json.Unmarshal(j, &keyJSON) if err != nil { return err } u := new(uuid.UUID) + *u, err = uuid.Parse(keyJSON.Id) if err != nil { return err } + k.Id = *u + addr, err := hex.DecodeString(keyJSON.Address) if err != nil { return err } + privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey) if err != nil { return err @@ -134,11 +140,13 @@ func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { if err != nil { panic(fmt.Sprintf("Could not create random uuid: %v", err)) } + key := &Key{ Id: id, Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), PrivateKey: privateKeyECDSA, } + return key } @@ -147,19 +155,24 @@ func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { // retry until the first byte is 0. func NewKeyForDirectICAP(rand io.Reader) *Key { randBytes := make([]byte, 64) + _, err := rand.Read(randBytes) if err != nil { panic("key generation: could not read from random source: " + err.Error()) } + reader := bytes.NewReader(randBytes) + privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader) if err != nil { panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) } + key := newKeyFromECDSA(privateKeyECDSA) if !strings.HasPrefix(key.Address.Hex(), "0x00") { return NewKeyForDirectICAP(rand) } + return key } @@ -168,6 +181,7 @@ func newKey(rand io.Reader) (*Key, error) { if err != nil { return nil, err } + return newKeyFromECDSA(privateKeyECDSA), nil } @@ -176,6 +190,7 @@ func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Accou if err != nil { return nil, accounts.Account{}, err } + a := accounts.Account{ Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}, @@ -184,6 +199,7 @@ func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Accou zeroKey(key.PrivateKey) return nil, a, err } + return key, a, err } @@ -200,12 +216,16 @@ func writeTemporaryKeyFile(file string, content []byte) (string, error) { if err != nil { return "", err } + if _, err := f.Write(content); err != nil { f.Close() os.Remove(f.Name()) + return "", err } + f.Close() + return f.Name(), nil } @@ -214,6 +234,7 @@ func writeKeyFile(file string, content []byte) error { if err != nil { return err } + return os.Rename(name, file) } @@ -226,12 +247,14 @@ func keyFileName(keyAddr common.Address) string { func toISO8601(t time.Time) string { var tz string + name, offset := t.Zone() if name == "UTC" { tz = "Z" } else { tz = fmt.Sprintf("%03d00", offset/3600) } + return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz) } diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index 0ffcf376a5..5612c288a5 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -84,6 +84,7 @@ func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore { keydir, _ = filepath.Abs(keydir) ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}} ks.init(keydir) + return ks } @@ -93,6 +94,7 @@ func NewPlaintextKeyStore(keydir string) *KeyStore { keydir, _ = filepath.Abs(keydir) ks := &KeyStore{storage: &keyStorePlain{keydir}} ks.init(keydir) + return ks } @@ -114,6 +116,7 @@ func (ks *KeyStore) init(keydir string) { // Create the initial list of wallets from the cache accs := ks.cache.accounts() ks.wallets = make([]accounts.Wallet, len(accs)) + for i := 0; i < len(accs); i++ { ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks} } @@ -130,6 +133,7 @@ func (ks *KeyStore) Wallets() []accounts.Wallet { cpy := make([]accounts.Wallet, len(ks.wallets)) copy(cpy, ks.wallets) + return cpy } @@ -158,12 +162,14 @@ func (ks *KeyStore) refreshWallets() { events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) wallets = append(wallets, wallet) + continue } // If the account is the same as the first wallet, keep it if ks.wallets[0].Accounts()[0] == account { wallets = append(wallets, ks.wallets[0]) ks.wallets = ks.wallets[1:] + continue } } @@ -171,6 +177,7 @@ func (ks *KeyStore) refreshWallets() { for _, wallet := range ks.wallets { events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) } + ks.wallets = wallets ks.mu.Unlock() @@ -195,6 +202,7 @@ func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscripti ks.updating = true go ks.updater() } + return sub } @@ -218,6 +226,7 @@ func (ks *KeyStore) updater() { if ks.updateScope.Count() == 0 { ks.updating = false ks.mu.Unlock() + return } ks.mu.Unlock() @@ -244,6 +253,7 @@ func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error { if key != nil { zeroKey(key.PrivateKey) } + if err != nil { return err } @@ -255,6 +265,7 @@ func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error { ks.cache.delete(a) ks.refreshWallets() } + return err } @@ -285,6 +296,7 @@ func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *b } // Depending on the presence of the chain ID, sign with 2718 or homestead signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, unlockedKey.PrivateKey) } @@ -296,7 +308,9 @@ func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string if err != nil { return nil, err } + defer zeroKey(key.PrivateKey) + return crypto.Sign(hash, key.PrivateKey) } @@ -310,6 +324,7 @@ func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, defer zeroKey(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) } @@ -327,6 +342,7 @@ func (ks *KeyStore) Lock(addr common.Address) error { } else { ks.mu.Unlock() } + return nil } @@ -345,6 +361,7 @@ func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout t ks.mu.Lock() defer ks.mu.Unlock() + u, found := ks.unlocked[a.Address] if found { if u.abort == nil { @@ -356,13 +373,16 @@ func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout t // Terminate the expire goroutine and replace it below. close(u.abort) } + if timeout > 0 { u = &unlocked{Key: key, abort: make(chan struct{})} go ks.expire(a.Address, u, timeout) } else { u = &unlocked{Key: key} } + ks.unlocked[a.Address] = u + return nil } @@ -372,6 +392,7 @@ func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) { ks.cache.mu.Lock() a, err := ks.cache.find(a) ks.cache.mu.Unlock() + return a, err } @@ -380,7 +401,9 @@ func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.A if err != nil { return a, nil, err } + key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth) + return a, key, err } @@ -415,6 +438,7 @@ func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) { // than waiting for file system notifications to pick it up. ks.cache.add(account) ks.refreshWallets() + return account, nil } @@ -424,12 +448,14 @@ func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) if err != nil { return nil, err } + var N, P int if store, ok := ks.storage.(*keyStorePassphrase); ok { N, P = store.scryptN, store.scryptP } else { N, P = StandardScryptN, StandardScryptP } + return EncryptKey(key, newPassphrase, N, P) } @@ -439,9 +465,11 @@ func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (ac if key != nil && key.PrivateKey != nil { defer zeroKey(key.PrivateKey) } + if err != nil { return accounts.Account{}, err } + ks.importMu.Lock() defer ks.importMu.Unlock() @@ -450,6 +478,7 @@ func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (ac Address: key.Address, }, ErrAccountAlreadyExists } + return ks.importKey(key, newPassphrase) } @@ -464,6 +493,7 @@ func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (acco Address: key.Address, }, ErrAccountAlreadyExists } + return ks.importKey(key, passphrase) } @@ -472,8 +502,10 @@ func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, er if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil { return accounts.Account{}, err } + ks.cache.add(a) ks.refreshWallets() + return a, nil } @@ -483,6 +515,7 @@ func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) if err != nil { return err } + return ks.storage.StoreKey(a.URL.Path, key, newPassphrase) } @@ -493,8 +526,10 @@ func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (account if err != nil { return a, err } + ks.cache.add(a) ks.refreshWallets() + return a, nil } @@ -503,6 +538,7 @@ func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (account func (ks *KeyStore) isUpdating() bool { ks.mu.RLock() defer ks.mu.RUnlock() + return ks.updating } diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go index f90d809b55..c7784bcaf8 100644 --- a/accounts/keystore/keystore_test.go +++ b/accounts/keystore/keystore_test.go @@ -42,28 +42,36 @@ func TestKeyStore(t *testing.T) { if err != nil { t.Fatal(err) } + if !strings.HasPrefix(a.URL.Path, dir) { t.Errorf("account file %s doesn't have dir prefix", a.URL) } + stat, err := os.Stat(a.URL.Path) if err != nil { t.Fatalf("account file %s doesn't exist (%v)", a.URL, err) } + if runtime.GOOS != "windows" && stat.Mode() != 0600 { t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600) } + if !ks.HasAddress(a.Address) { t.Errorf("HasAccount(%x) should've returned true", a.Address) } + if err := ks.Update(a, "foo", "bar"); err != nil { t.Errorf("Update error: %v", err) } + if err := ks.Delete(a, "bar"); err != nil { t.Errorf("Delete error: %v", err) } + if common.FileExist(a.URL.Path) { t.Errorf("account file %s should be gone after Delete", a.URL) } + if ks.HasAddress(a.Address) { t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address) } @@ -73,6 +81,7 @@ func TestSign(t *testing.T) { _, ks := tmpKeyStore(t, true) pass := "" // not used but required by API + a1, err := ks.NewAccount(pass) if err != nil { t.Fatal(err) @@ -80,6 +89,7 @@ func TestSign(t *testing.T) { if err := ks.Unlock(a1, ""); err != nil { t.Fatal(err) } + if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil { t.Fatal(err) } @@ -89,6 +99,7 @@ func TestSignWithPassphrase(t *testing.T) { _, ks := tmpKeyStore(t, true) pass := "passwd" + acc, err := ks.NewAccount(pass) if err != nil { t.Fatal(err) @@ -117,6 +128,7 @@ func TestTimedUnlock(t *testing.T) { _, ks := tmpKeyStore(t, true) pass := "foo" + a1, err := ks.NewAccount(pass) if err != nil { t.Fatal(err) @@ -141,6 +153,7 @@ func TestTimedUnlock(t *testing.T) { // Signing fails again after automatic locking time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) if err != ErrLocked { t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) @@ -152,6 +165,7 @@ func TestOverrideUnlock(t *testing.T) { _, ks := tmpKeyStore(t, false) pass := "foo" + a1, err := ks.NewAccount(pass) if err != nil { t.Fatal(err) @@ -181,6 +195,7 @@ func TestOverrideUnlock(t *testing.T) { // Signing fails again after automatic locking time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) if err != ErrLocked { t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) @@ -201,6 +216,7 @@ func TestSignRace(t *testing.T) { if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { t.Fatal("could not unlock the test account", err) } + end := time.Now().Add(500 * time.Millisecond) for time.Now().Before(end) { if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked { @@ -209,6 +225,7 @@ func TestSignRace(t *testing.T) { t.Errorf("Sign error: %v", err) return } + time.Sleep(1 * time.Millisecond) } t.Errorf("Account did not lock within the timeout") @@ -225,8 +242,10 @@ func waitForKsUpdating(t *testing.T, ks *KeyStore, wantStatus bool, maxTime time if ks.isUpdating() == wantStatus { return true } + time.Sleep(25 * time.Millisecond) } + return false } @@ -250,6 +269,7 @@ func TestWalletNotifierLifecycle(t *testing.T) { for i := 0; i < len(subs); i++ { // Create a new subscription subs[i] = ks.Subscribe(updates) + if !waitForKsUpdating(t, ks, true, 250*time.Millisecond) { t.Errorf("sub %d: wallet notifier not running after subscription", i) } @@ -267,6 +287,7 @@ func TestWalletNotifierLifecycle(t *testing.T) { } // Unsubscribe the last one and ensure the updater terminates eventually. subs[len(subs)-1].Unsubscribe() + if !waitForKsUpdating(t, ks, false, 4*time.Second) { t.Errorf("wallet notifier didn't terminate after unsubscribe") } @@ -288,7 +309,9 @@ func TestWalletNotifications(t *testing.T) { updates = make(chan accounts.WalletEvent) sub = ks.Subscribe(updates) ) + defer sub.Unsubscribe() + go func() { for { select { @@ -306,6 +329,7 @@ func TestWalletNotifications(t *testing.T) { live = make(map[common.Address]accounts.Account) wantEvents []walletEvent ) + for i := 0; i < 1024; i++ { if create := len(live) == 0 || rand.Int()%4 > 0; create { // Add a new account and ensure wallet notifications arrives @@ -313,6 +337,7 @@ func TestWalletNotifications(t *testing.T) { if err != nil { t.Fatalf("failed to create test account: %v", err) } + live[account.Address] = account wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account}) } else { @@ -322,9 +347,11 @@ func TestWalletNotifications(t *testing.T) { account = a break } + if err := ks.Delete(account, ""); err != nil { t.Fatalf("failed to delete test account: %v", err) } + delete(live, account.Address) wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account}) } @@ -332,9 +359,11 @@ func TestWalletNotifications(t *testing.T) { // Shut down the event collector and check events. sub.Unsubscribe() + for ev := range updates { events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) } + checkAccounts(t, live, ks.Wallets()) checkEvents(t, wantEvents, events) } @@ -342,16 +371,20 @@ func TestWalletNotifications(t *testing.T) { // TestImportExport tests the import functionality of a keystore. func TestImportECDSA(t *testing.T) { _, ks := tmpKeyStore(t, true) + key, err := crypto.GenerateKey() if err != nil { t.Fatalf("failed to generate key: %v", key) } + if _, err = ks.ImportECDSA(key, "old"); err != nil { t.Errorf("importing failed: %v", err) } + if _, err = ks.ImportECDSA(key, "old"); err == nil { t.Errorf("importing same key twice succeeded") } + if _, err = ks.ImportECDSA(key, "new"); err == nil { t.Errorf("importing same key twice succeeded") } @@ -360,25 +393,31 @@ func TestImportECDSA(t *testing.T) { // TestImportECDSA tests the import and export functionality of a keystore. func TestImportExport(t *testing.T) { _, ks := tmpKeyStore(t, true) + acc, err := ks.NewAccount("old") if err != nil { t.Fatalf("failed to create account: %v", acc) } + json, err := ks.Export(acc, "old", "new") if err != nil { t.Fatalf("failed to export account: %v", acc) } + _, ks2 := tmpKeyStore(t, true) if _, err = ks2.Import(json, "old", "old"); err == nil { t.Errorf("importing with invalid password succeeded") } + acc2, err := ks2.Import(json, "new", "new") if err != nil { t.Errorf("importing failed: %v", err) } + if acc.Address != acc2.Address { t.Error("imported account does not match exported account") } + if _, err = ks2.Import(json, "new", "new"); err == nil { t.Errorf("importing a key twice succeeded") } @@ -388,27 +427,36 @@ func TestImportExport(t *testing.T) { // This test should fail under -race if importing races. func TestImportRace(t *testing.T) { _, ks := tmpKeyStore(t, true) + acc, err := ks.NewAccount("old") if err != nil { t.Fatalf("failed to create account: %v", acc) } + json, err := ks.Export(acc, "old", "new") if err != nil { t.Fatalf("failed to export account: %v", acc) } + _, ks2 := tmpKeyStore(t, true) + var atom uint32 + var wg sync.WaitGroup + wg.Add(2) + for i := 0; i < 2; i++ { go func() { defer wg.Done() + if _, err := ks2.Import(json, "new", "new"); err != nil { atomic.AddUint32(&atom, 1) } }() } wg.Wait() + if atom != 1 { t.Errorf("Import is racy") } @@ -420,11 +468,14 @@ func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, walle t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live)) return } + liveList := make([]accounts.Account, 0, len(live)) for _, account := range live { liveList = append(liveList, account) } + sort.Sort(accountsByURL(liveList)) + for j, wallet := range wallets { if accs := wallet.Accounts(); len(accs) != 1 { t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs)) @@ -438,12 +489,15 @@ func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, walle func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) { for _, wantEv := range want { nmatch := 0 + for ; len(have) > 0; nmatch++ { if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a { break } + have = have[1:] } + if nmatch == 0 { t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address) } @@ -452,9 +506,11 @@ func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) { func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) { d := t.TempDir() + newKs := NewPlaintextKeyStore if encrypted { newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) } } + return d, newKs(d) } diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index 1701fbf536..4621d66750 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -85,6 +85,7 @@ func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) if err != nil { return nil, err } + key, err := DecryptKey(keyjson, auth) if err != nil { return nil, err @@ -93,6 +94,7 @@ func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) if key.Address != addr { return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr) } + return key, nil } @@ -112,6 +114,7 @@ func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) er if err != nil { return err } + if !ks.skipKeyFileVerification { // Verify that we can decrypt the file with the given password. _, err = ks.GetKey(key.Address, tmpName, auth) @@ -126,6 +129,7 @@ func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) er return fmt.Errorf(msg, tmpName, err) } } + return os.Rename(tmpName, filename) } @@ -133,6 +137,7 @@ func (ks keyStorePassphrase) JoinPath(filename string) string { if filepath.IsAbs(filename) { return filename } + return filepath.Join(ks.keysDirPath, filename) } @@ -142,20 +147,24 @@ func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) if _, err := io.ReadFull(rand.Reader, salt); err != nil { panic("reading from crypto/rand failed: " + err.Error()) } + derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen) if err != nil { return CryptoJSON{}, err } + encryptKey := derivedKey[:16] iv := make([]byte, aes.BlockSize) // 16 if _, err := io.ReadFull(rand.Reader, iv); err != nil { panic("reading from crypto/rand failed: " + err.Error()) } + cipherText, err := aesCTRXOR(encryptKey, data, iv) if err != nil { return CryptoJSON{}, err } + mac := crypto.Keccak256(derivedKey[16:32], cipherText) scryptParamsJSON := make(map[string]interface{}, 5) @@ -176,6 +185,7 @@ func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) KDFParams: scryptParamsJSON, MAC: hex.EncodeToString(mac), } + return cryptoStruct, nil } @@ -183,16 +193,19 @@ func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) // blob that can be decrypted later on. func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) + cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP) if err != nil { return nil, err } + encryptedKeyJSONV3 := encryptedKeyJSONV3{ hex.EncodeToString(key.Address[:]), cryptoStruct, key.Id.String(), version, } + return json.Marshal(encryptedKeyJSONV3) } @@ -208,28 +221,34 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) { keyBytes, keyId []byte err error ) + if version, ok := m["version"].(string); ok && version == "1" { k := new(encryptedKeyJSONV1) if err := json.Unmarshal(keyjson, k); err != nil { return nil, err } + keyBytes, keyId, err = decryptKeyV1(k, auth) } else { k := new(encryptedKeyJSONV3) if err := json.Unmarshal(keyjson, k); err != nil { return nil, err } + keyBytes, keyId, err = decryptKeyV3(k, auth) } // Handle any decryption errors and return the key if err != nil { return nil, err } + key := crypto.ToECDSAUnsafe(keyBytes) + id, err := uuid.FromBytes(keyId) if err != nil { return nil, err } + return &Key{ Id: id, Address: crypto.PubkeyToAddress(key.PublicKey), @@ -241,6 +260,7 @@ func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) { if cryptoJson.Cipher != "aes-128-ctr" { return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher) } + mac, err := hex.DecodeString(cryptoJson.MAC) if err != nil { return nil, err @@ -270,6 +290,7 @@ func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) { if err != nil { return nil, err } + return plainText, err } @@ -277,15 +298,19 @@ func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byt if keyProtected.Version != version { return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version) } + 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 } + return plainText, keyId, err } @@ -294,7 +319,9 @@ func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byt if err != nil { return nil, nil, err } + keyId = keyUUID[:] + mac, err := hex.DecodeString(keyProtected.Crypto.MAC) if err != nil { return nil, nil, err @@ -324,29 +351,36 @@ func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byt if err != nil { return nil, nil, err } + return plainText, keyId, err } func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) { authArray := []byte(auth) + salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string)) if err != nil { return nil, err } + dkLen := ensureInt(cryptoJSON.KDFParams["dklen"]) if cryptoJSON.KDF == keyHeaderKDF { n := ensureInt(cryptoJSON.KDFParams["n"]) r := ensureInt(cryptoJSON.KDFParams["r"]) p := ensureInt(cryptoJSON.KDFParams["p"]) + return scrypt.Key(authArray, salt, n, r, p, dkLen) } else if cryptoJSON.KDF == "pbkdf2" { c := ensureInt(cryptoJSON.KDFParams["c"]) + prf := cryptoJSON.KDFParams["prf"].(string) if prf != "hmac-sha256" { return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf) } + key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New) + return key, nil } @@ -361,5 +395,6 @@ func ensureInt(x interface{}) int { if !ok { res = int(x.(float64)) } + return res } diff --git a/accounts/keystore/passphrase_test.go b/accounts/keystore/passphrase_test.go index 1356b31780..efdfd85526 100644 --- a/accounts/keystore/passphrase_test.go +++ b/accounts/keystore/passphrase_test.go @@ -34,6 +34,7 @@ func TestKeyEncryptDecrypt(t *testing.T) { if err != nil { t.Fatal(err) } + password := "" address := common.HexToAddress("45dea0fb0bba44f4fcf290bba71fd57d7117cbb8") @@ -48,6 +49,7 @@ func TestKeyEncryptDecrypt(t *testing.T) { if err != nil { t.Fatalf("test %d: json key failed to decrypt: %v", i, err) } + if key.Address != address { t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address) } diff --git a/accounts/keystore/plain.go b/accounts/keystore/plain.go index f62a133ce1..bd017bc8bd 100644 --- a/accounts/keystore/plain.go +++ b/accounts/keystore/plain.go @@ -34,14 +34,18 @@ func (ks keyStorePlain) GetKey(addr common.Address, filename, auth string) (*Key if err != nil { return nil, err } + defer fd.Close() + key := new(Key) if err := json.NewDecoder(fd).Decode(key); err != nil { return nil, err } + if key.Address != addr { return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr) } + return key, nil } @@ -50,6 +54,7 @@ func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error { if err != nil { return err } + return writeKeyFile(filename, content) } @@ -57,5 +62,6 @@ func (ks keyStorePlain) JoinPath(filename string) string { if filepath.IsAbs(filename) { return filename } + return filepath.Join(ks.keysDirPath, filename) } diff --git a/accounts/keystore/plain_test.go b/accounts/keystore/plain_test.go index 93165d5cd3..ff4cd508ab 100644 --- a/accounts/keystore/plain_test.go +++ b/accounts/keystore/plain_test.go @@ -36,6 +36,7 @@ func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) { } else { ks = &keyStorePlain{d} } + return d, ks } @@ -43,17 +44,21 @@ func TestKeyStorePlain(t *testing.T) { _, ks := tmpKeyStoreIface(t, false) pass := "" // not used but required by API + k1, account, err := storeNewKey(ks, rand.Reader, pass) if err != nil { t.Fatal(err) } + k2, err := ks.GetKey(k1.Address, account.URL.Path, pass) if err != nil { t.Fatal(err) } + if !reflect.DeepEqual(k1.Address, k2.Address) { t.Fatal(err) } + if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { t.Fatal(err) } @@ -63,17 +68,21 @@ func TestKeyStorePassphrase(t *testing.T) { _, ks := tmpKeyStoreIface(t, true) pass := "foo" + k1, account, err := storeNewKey(ks, rand.Reader, pass) if err != nil { t.Fatal(err) } + k2, err := ks.GetKey(k1.Address, account.URL.Path, pass) if err != nil { t.Fatal(err) } + if !reflect.DeepEqual(k1.Address, k2.Address) { t.Fatal(err) } + if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { t.Fatal(err) } @@ -83,10 +92,12 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) { _, ks := tmpKeyStoreIface(t, true) pass := "foo" + k1, account, err := storeNewKey(ks, rand.Reader, pass) if err != nil { t.Fatal(err) } + if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt { t.Fatalf("wrong error for invalid password\ngot %q\nwant %q", err, ErrDecrypt) } @@ -100,13 +111,16 @@ func TestImportPreSaleKey(t *testing.T) { // with password "foo" fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}" pass := "foo" + account, _, err := importPreSaleKey(ks, []byte(fileContent), pass) if err != nil { t.Fatal(err) } + if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") { t.Errorf("imported account has wrong address %x", account.Address) } + if !strings.HasPrefix(account.URL.Path, dir) { t.Errorf("imported account file not in keystore directory: %q", account.URL) } @@ -182,15 +196,19 @@ func TestV1_1(t *testing.T) { func TestV1_2(t *testing.T) { t.Parallel() + ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP, true} addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e") file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e" + k, err := ks.GetKey(addr, file, "g") if err != nil { t.Fatal(err) } + privHex := hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)) expectedHex := "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d" + if privHex != expectedHex { t.Fatal(fmt.Errorf("Unexpected privkey: %v, expected %v", privHex, expectedHex)) } @@ -201,6 +219,7 @@ func testDecryptV3(test KeyStoreTestV3, t *testing.T) { if err != nil { t.Fatal(err) } + privHex := hex.EncodeToString(privBytes) if test.Priv != privHex { t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex)) @@ -212,6 +231,7 @@ func testDecryptV1(test KeyStoreTestV1, t *testing.T) { if err != nil { t.Fatal(err) } + privHex := hex.EncodeToString(privBytes) if test.Priv != privHex { t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex)) @@ -220,24 +240,29 @@ func testDecryptV1(test KeyStoreTestV1, t *testing.T) { func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 { tests := make(map[string]KeyStoreTestV3) + err := common.LoadJSON(file, &tests) if err != nil { t.Fatal(err) } + return tests } func loadKeyStoreTestV1(file string, t *testing.T) map[string]KeyStoreTestV1 { tests := make(map[string]KeyStoreTestV1) + err := common.LoadJSON(file, &tests) if err != nil { t.Fatal(err) } + return tests } func TestKeyForDirectICAP(t *testing.T) { t.Parallel() + key := NewKeyForDirectICAP(rand.Reader) if !strings.HasPrefix(key.Address.Hex(), "0x00") { t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex()) diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go index 0664dc2cdd..6e2561a97e 100644 --- a/accounts/keystore/presale.go +++ b/accounts/keystore/presale.go @@ -37,10 +37,12 @@ func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accou if err != nil { return accounts.Account{}, nil, err } + key.Id, err = uuid.NewRandom() if err != nil { return accounts.Account{}, nil, err } + a := accounts.Account{ Address: key.Address, URL: accounts.URL{ @@ -49,6 +51,7 @@ func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accou }, } err = keyStore.StoreKey(a.URL.Path, key, password) + return a, key, err } @@ -59,17 +62,21 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error Email string BtcAddr string }{} + err = json.Unmarshal(fileContent, &preSaleKeyStruct) if err != nil { return nil, err } + encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed) if err != nil { return nil, errors.New("invalid hex in encSeed") } + if len(encSeedBytes) < 16 { return nil, errors.New("invalid encSeed, too short") } + iv := encSeedBytes[:16] cipherText := encSeedBytes[16:] /* @@ -81,10 +88,12 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error */ passBytes := []byte(password) derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New) + plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv) if err != nil { return nil, err } + ethPriv := crypto.Keccak256(plainText) ecKey := crypto.ToECDSAUnsafe(ethPriv) @@ -95,9 +104,11 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error } derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x" expectedAddr := preSaleKeyStruct.EthAddr + if derivedAddr != expectedAddr { err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr) } + return key, err } @@ -107,9 +118,11 @@ func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { if err != nil { return nil, err } + stream := cipher.NewCTR(aesBlock, iv) outText := make([]byte, len(inText)) stream.XORKeyStream(outText, inText) + return outText, err } @@ -118,13 +131,16 @@ func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) { if err != nil { return nil, err } + decrypter := cipher.NewCBCDecrypter(aesBlock, iv) paddedPlaintext := make([]byte, len(cipherText)) decrypter.CryptBlocks(paddedPlaintext, cipherText) + plaintext := pkcs7Unpad(paddedPlaintext) if plaintext == nil { return nil, ErrDecrypt } + return plaintext, err } @@ -146,5 +162,6 @@ func pkcs7Unpad(in []byte) []byte { return nil } } + return in[:len(in)-int(padding)] } diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go index 1066095f6d..670fca00f7 100644 --- a/accounts/keystore/wallet.go +++ b/accounts/keystore/wallet.go @@ -46,6 +46,7 @@ func (w *keystoreWallet) Status() (string, error) { if _, ok := w.keystore.unlocked[w.account.Address]; ok { return "Unlocked", nil } + return "Locked", nil } diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go index f7bcfae2cc..11468aaca1 100644 --- a/accounts/keystore/watch.go +++ b/accounts/keystore/watch.go @@ -52,6 +52,7 @@ func (w *watcher) start() { if w.starting || w.running { return } + w.starting = true go w.loop() } @@ -68,6 +69,7 @@ func (w *watcher) loop() { w.runEnded = true w.ac.mu.Unlock() }() + logger := log.New("path", w.ac.keydir) // Create new watcher. @@ -76,7 +78,9 @@ func (w *watcher) loop() { log.Error("Failed to start filesystem watcher", "err", err) return } + defer watcher.Close() + if err := watcher.Add(w.ac.keydir); err != nil { logger.Warn("Failed to watch keystore folder", "err", err) return @@ -102,6 +106,7 @@ func (w *watcher) loop() { <-debounce.C } defer debounce.Stop() + for { select { case <-w.quit: @@ -113,6 +118,7 @@ func (w *watcher) loop() { // Trigger the scan (with delay), if not already triggered if !rescanTriggered { debounce.Reset(debounceDuration) + rescanTriggered = true } // The fsnotify library does provide more granular event-info, it @@ -123,9 +129,11 @@ func (w *watcher) loop() { if !ok { return } + log.Info("Filsystem watcher error", "err", err) case <-debounce.C: w.ac.scanAccounts() + rescanTriggered = false } } diff --git a/accounts/manager.go b/accounts/manager.go index a0b5c329cd..d04593b3ce 100644 --- a/accounts/manager.go +++ b/accounts/manager.go @@ -87,10 +87,12 @@ func NewManager(config *Config, backends ...Backend) *Manager { quit: make(chan chan error), term: make(chan struct{}), } + for _, backend := range backends { kind := reflect.TypeOf(backend) am.backends[kind] = append(am.backends[kind], backend) } + go am.update() return am @@ -100,6 +102,7 @@ func NewManager(config *Config, backends ...Backend) *Manager { func (am *Manager) Close() error { errc := make(chan error) am.quit <- errc + return <-errc } @@ -113,6 +116,7 @@ func (am *Manager) Config() *Config { func (am *Manager) AddBackend(backend Backend) { done := make(chan struct{}) am.newBackends <- newBackendEvent{backend, done} + <-done } @@ -125,6 +129,7 @@ func (am *Manager) update() { for _, sub := range am.updaters { sub.Unsubscribe() } + am.updaters = nil am.lock.Unlock() }() @@ -161,6 +166,7 @@ func (am *Manager) update() { // Signals event emitters the loop is not receiving values // to prevent them from getting stuck. close(am.term) + return } } @@ -186,6 +192,7 @@ func (am *Manager) Wallets() []Wallet { func (am *Manager) walletsNoLock() []Wallet { cpy := make([]Wallet, len(am.wallets)) copy(cpy, am.wallets) + return cpy } @@ -198,11 +205,13 @@ func (am *Manager) Wallet(url string) (Wallet, error) { if err != nil { return nil, err } + for _, wallet := range am.walletsNoLock() { if wallet.URL() == parsed { return wallet, nil } } + return nil, ErrUnknownWallet } @@ -212,11 +221,13 @@ func (am *Manager) Accounts() []common.Address { defer am.lock.RUnlock() addresses := make([]common.Address, 0) // return [] instead of nil if empty + for _, wallet := range am.wallets { for _, account := range wallet.Accounts() { addresses = append(addresses, account.Address) } } + return addresses } @@ -232,6 +243,7 @@ func (am *Manager) Find(account Account) (Wallet, error) { return wallet, nil } } + return nil, ErrUnknownAccount } @@ -252,8 +264,10 @@ func merge(slice []Wallet, wallets ...Wallet) []Wallet { slice = append(slice, wallet) continue } + slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...) } + return slice } @@ -266,7 +280,9 @@ func drop(slice []Wallet, wallets ...Wallet) []Wallet { // Wallet not found, may happen during startup continue } + slice = append(slice[:n], slice[n+1:]...) } + return slice } diff --git a/accounts/scwallet/apdu.go b/accounts/scwallet/apdu.go index bd3660604e..c5c4c911df 100644 --- a/accounts/scwallet/apdu.go +++ b/accounts/scwallet/apdu.go @@ -36,26 +36,33 @@ func (ca commandAPDU) serialize() ([]byte, error) { if err := binary.Write(buf, binary.BigEndian, ca.Cla); err != nil { return nil, err } + if err := binary.Write(buf, binary.BigEndian, ca.Ins); err != nil { return nil, err } + if err := binary.Write(buf, binary.BigEndian, ca.P1); err != nil { return nil, err } + if err := binary.Write(buf, binary.BigEndian, ca.P2); err != nil { return nil, err } + if len(ca.Data) > 0 { if err := binary.Write(buf, binary.BigEndian, uint8(len(ca.Data))); err != nil { return nil, err } + if err := binary.Write(buf, binary.BigEndian, ca.Data); err != nil { return nil, err } } + if err := binary.Write(buf, binary.BigEndian, ca.Le); err != nil { return nil, err } + return buf.Bytes(), nil } @@ -77,11 +84,14 @@ func (ra *responseAPDU) deserialize(data []byte) error { if err := binary.Read(buf, binary.BigEndian, &ra.Data); err != nil { return err } + if err := binary.Read(buf, binary.BigEndian, &ra.Sw1); err != nil { return err } + if err := binary.Read(buf, binary.BigEndian, &ra.Sw2); err != nil { return err } + return nil } diff --git a/accounts/scwallet/hub.go b/accounts/scwallet/hub.go index f9dcf58e19..062a2a770c 100644 --- a/accounts/scwallet/hub.go +++ b/accounts/scwallet/hub.go @@ -88,11 +88,13 @@ type Hub struct { func (hub *Hub) readPairings() error { hub.pairings = make(map[string]smartcardPairing) + pairingFile, err := os.Open(filepath.Join(hub.datadir, "smartcards.json")) if err != nil { if os.IsNotExist(err) { return nil } + return err } @@ -100,6 +102,7 @@ func (hub *Hub) readPairings() error { if err != nil { return err } + var pairings []smartcardPairing if err := json.Unmarshal(pairingData, &pairings); err != nil { return err @@ -108,6 +111,7 @@ func (hub *Hub) readPairings() error { for _, pairing := range pairings { hub.pairings[string(pairing.PublicKey)] = pairing } + return nil } @@ -139,6 +143,7 @@ func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing { if pairing, ok := hub.pairings[string(wallet.PublicKey)]; ok { return &pairing } + return nil } @@ -148,6 +153,7 @@ func (hub *Hub) setPairing(wallet *Wallet, pairing *smartcardPairing) error { } else { hub.pairings[string(wallet.PublicKey)] = *pairing } + return hub.writePairings() } @@ -157,6 +163,7 @@ func NewHub(daemonPath string, scheme string, datadir string) (*Hub, error) { if err != nil { return nil, err } + hub := &Hub{ scheme: scheme, context: context, @@ -167,7 +174,9 @@ func NewHub(daemonPath string, scheme string, datadir string) (*Hub, error) { if err := hub.readPairings(); err != nil { return nil, err } + hub.refreshWallets() + return hub, nil } @@ -184,7 +193,9 @@ func (hub *Hub) Wallets() []accounts.Wallet { for _, wallet := range hub.wallets { cpy = append(cpy, wallet) } + sort.Sort(accounts.WalletsByURL(cpy)) + return cpy } @@ -225,8 +236,10 @@ func (hub *Hub) refreshWallets() { if err := wallet.ping(); err == nil { continue } + wallet.Close() events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + delete(hub.wallets, reader) } // New card detected, try to connect to it @@ -235,10 +248,12 @@ func (hub *Hub) refreshWallets() { log.Debug("Failed to open smart card", "reader", reader, "err", err) continue } + wallet := NewWallet(hub, card) if err = wallet.connect(); err != nil { log.Debug("Failed to connect to smart card", "reader", reader, "err", err) card.Disconnect(pcsc.LeaveCard) + continue } // Card connected, start tracking in amongs the wallets @@ -250,9 +265,11 @@ func (hub *Hub) refreshWallets() { if _, ok := seen[reader]; !ok { wallet.Close() events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + delete(hub.wallets, reader) } } + hub.refreshed = time.Now() hub.stateLock.Unlock() @@ -276,6 +293,7 @@ func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { hub.updating = true go hub.updater() } + return sub } @@ -295,6 +313,7 @@ func (hub *Hub) updater() { if hub.updateScope.Count() == 0 { hub.updating = false hub.stateLock.Unlock() + return } hub.stateLock.Unlock() diff --git a/accounts/scwallet/securechannel.go b/accounts/scwallet/securechannel.go index b1b533eb72..a103b19b69 100644 --- a/accounts/scwallet/securechannel.go +++ b/accounts/scwallet/securechannel.go @@ -67,11 +67,14 @@ func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSes if err != nil { return nil, err } + cardPublic, err := crypto.UnmarshalPubkey(keyData) if err != nil { 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.Bytes(), @@ -108,6 +111,7 @@ func (s *SecureChannelSession) Pair(pairingPassword []byte) error { md.Reset() md.Write(secretHash[:]) md.Write(cardChallenge) + response, err = s.pair(pairP1LastStep, md.Sum(nil)) if err != nil { return err @@ -132,9 +136,11 @@ func (s *SecureChannelSession) Unpair() error { if err != nil { return err } + s.PairingKey = nil // Close channel s.iv = nil + return nil } @@ -177,6 +183,7 @@ func (s *SecureChannelSession) mutuallyAuthenticate() error { if err != nil { return err } + if response.Sw1 != 0x90 || response.Sw2 != 0x00 { return fmt.Errorf("got unexpected response from MUTUALLY_AUTHENTICATE: %#x%x", response.Sw1, response.Sw2) } @@ -222,6 +229,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b if err != nil { return nil, err } + meta := [16]byte{cla, ins, p1, p2, byte(len(data) + scBlockSize)} if err = s.updateIV(meta[:], data); err != nil { return nil, err @@ -245,6 +253,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b rmeta := [16]byte{byte(len(response.Data))} rmac := response.Data[:len(s.iv)] rdata := response.Data[len(s.iv):] + plainData, err := s.decryptAPDU(rdata) if err != nil { return nil, err @@ -253,6 +262,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b if err = s.updateIV(rmeta[:], rdata); err != nil { return nil, err } + if !bytes.Equal(s.iv, rmac) { return nil, fmt.Errorf("invalid MAC in response") } @@ -272,6 +282,7 @@ func (s *SecureChannelSession) encryptAPDU(data []byte) ([]byte, error) { if len(data) > maxPayloadSize { return nil, fmt.Errorf("payload of %d bytes exceeds maximum of %d", len(data), maxPayloadSize) } + data = pad(data, 0x80) ret := make([]byte, len(data)) @@ -280,8 +291,10 @@ func (s *SecureChannelSession) encryptAPDU(data []byte) ([]byte, error) { if err != nil { return nil, err } + crypter := cipher.NewCBCEncrypter(a, s.iv) crypter.CryptBlocks(ret, data) + return ret, nil } @@ -290,6 +303,7 @@ func pad(data []byte, terminator byte) []byte { padded := make([]byte, (len(data)/16+1)*16) copy(padded, data) padded[len(data)] = terminator + return padded } @@ -304,6 +318,7 @@ func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) { crypter := cipher.NewCBCDecrypter(a, s.iv) crypter.CryptBlocks(ret, data) + return unpad(ret, 0x80) } @@ -319,6 +334,7 @@ func unpad(data []byte, terminator byte) ([]byte, error) { return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i]) } } + return nil, fmt.Errorf("expected end of padding, got 0") } @@ -326,14 +342,17 @@ func unpad(data []byte, terminator byte) ([]byte, error) { // each message exchanged. func (s *SecureChannelSession) updateIV(meta, data []byte) error { data = pad(data, 0) + a, err := aes.NewCipher(s.sessionMacKey) if err != nil { return err } + crypter := cipher.NewCBCEncrypter(a, make([]byte, 16)) crypter.CryptBlocks(meta, meta) crypter.CryptBlocks(data, data) // The first 16 bytes of the last block is the MAC s.iv = data[len(data)-32 : len(data)-16] + return nil } diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 6a40e28ae6..53fde7ec94 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -132,6 +132,7 @@ func NewWallet(hub *Hub, card *pcsc.Card) *Wallet { Hub: hub, card: card, } + return wallet } @@ -202,6 +203,7 @@ func (w *Wallet) connect() error { Wallet: w, Channel: channel, } + return nil } @@ -222,6 +224,7 @@ func (w *Wallet) doselect() (*applicationInfo, error) { if _, err := asn1.UnmarshalWithParams(response.Data, appinfo, "tag:4"); err != nil { return nil, err } + return appinfo, nil } @@ -234,9 +237,11 @@ func (w *Wallet) ping() error { if !w.session.paired() { return nil } + if _, err := w.session.walletStatus(); err != nil { return err } + return nil } @@ -245,6 +250,7 @@ func (w *Wallet) release() error { if w.session != nil { return w.session.release() } + return nil } @@ -254,13 +260,16 @@ func (w *Wallet) pair(puk []byte) error { if w.session.paired() { return fmt.Errorf("wallet already paired") } + pairing, err := w.session.pair(puk) if err != nil { return err } + if err = w.Hub.setPairing(w, &pairing); err != nil { return err } + return w.session.authenticate(pairing) } @@ -272,15 +281,19 @@ func (w *Wallet) Unpair(pin []byte) error { if !w.session.paired() { return fmt.Errorf("wallet %x not paired", w.PublicKey) } + if err := w.session.verifyPin(pin); err != nil { return fmt.Errorf("failed to verify pin: %s", err) } + if err := w.session.unpair(); err != nil { return fmt.Errorf("failed to unpair: %s", err) } + if err := w.Hub.setPairing(w, nil); err != nil { return err } + return nil } @@ -310,6 +323,7 @@ func (w *Wallet) Status() (string, error) { if err != nil { return fmt.Sprintf("Failed: %v", err), err } + switch { case !w.session.verified && status.PinRetryCount == 0 && status.PukRetryCount == 0: return "Bricked, waiting for full wipe", nil @@ -384,6 +398,7 @@ func (w *Wallet) Open(passphrase string) error { w.log.Error("PIN needs to be at least 6 digits") return ErrPINNeeded } + if err := w.session.verifyPin([]byte(passphrase)); err != nil { return err } @@ -392,6 +407,7 @@ func (w *Wallet) Open(passphrase string) error { w.log.Error("PUK needs to be at least 12 digits") return ErrPINUnblockNeeded } + if err := w.session.unblockPin([]byte(passphrase)); err != nil { return err } @@ -417,6 +433,7 @@ func (w *Wallet) Close() error { // Terminate the self-derivations var derr error + if dQuit != nil { errc := make(chan error) dQuit <- errc @@ -432,6 +449,7 @@ func (w *Wallet) Close() error { if err := w.release(); err != nil { return err } + return derr } @@ -447,6 +465,7 @@ func (w *Wallet) selfDerive() { errc chan error err error ) + for errc == nil && err == nil { // Wait until either derivation or termination is requested select { @@ -461,8 +480,10 @@ func (w *Wallet) selfDerive() { if w.session == nil || w.deriveChain == nil { w.lock.Unlock() reqc <- struct{}{} + continue } + pairing := w.Hub.pairing(w) // Device lock obtained, derive the next batch of accounts @@ -475,6 +496,7 @@ func (w *Wallet) selfDerive() { context = context.Background() ) + for i := 0; i < len(nextAddrs); i++ { for empty := false; !empty; { // Retrieve the next derived Ethereum account @@ -483,6 +505,7 @@ func (w *Wallet) selfDerive() { w.log.Warn("Smartcard wallet account derivation failed", "err", err) break } + nextAddrs[i] = nextAcc.Address } // Check the account's status against the current chain state @@ -490,11 +513,13 @@ func (w *Wallet) selfDerive() { balance *big.Int nonce uint64 ) + balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil) if err != nil { w.log.Warn("Smartcard wallet balance retrieval failed", "err", err) break } + nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil) if err != nil { w.log.Warn("Smartcard wallet nonce retrieval failed", "err", err) @@ -503,6 +528,7 @@ func (w *Wallet) selfDerive() { // If the next account is empty, stop self-derivation, but add for the last base path if balance.Sign() == 0 && nonce == 0 { empty = true + if i < len(nextAddrs)-1 { break } @@ -516,6 +542,7 @@ func (w *Wallet) selfDerive() { if _, known := pairing.Accounts[nextAddrs[i]]; !known || !empty || nextAddrs[i] != w.deriveNextAddrs[i] { w.log.Info("Smartcard wallet discovered new account", "address", nextAddrs[i], "path", path, "balance", balance, "nonce", nonce) } + pairing.Accounts[nextAddrs[i]] = path // Fetch the next potential account @@ -538,6 +565,7 @@ func (w *Wallet) selfDerive() { // Notify the user of termination and loop after a bit of time (to avoid trashing) reqc <- struct{}{} + if err == nil { select { case errc = <-w.deriveQuit: @@ -577,9 +605,12 @@ func (w *Wallet) Accounts() []accounts.Account { for address, path := range pairing.Accounts { ret = append(ret, w.makeAccount(address, path)) } + sort.Sort(accounts.AccountsByURL(ret)) + return ret } + return nil } @@ -599,6 +630,7 @@ func (w *Wallet) Contains(account accounts.Account) bool { _, ok := pairing.Accounts[account.Address] return ok } + return false } @@ -625,6 +657,7 @@ func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun if pin { pairing := w.Hub.pairing(w) pairing.Accounts[account.Address] = path + if err := w.Hub.setPairing(w, pairing); err != nil { return accounts.Account{}, err } @@ -656,6 +689,7 @@ func (w *Wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.Chai w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base)) copy(w.deriveNextPaths[i][:], base[:]) } + w.deriveNextAddrs = make([]common.Address, len(bases)) w.deriveChain = chain } @@ -701,10 +735,12 @@ func (w *Wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { signer := types.LatestSignerForChainID(chainID) hash := signer.Hash(tx) + sig, err := w.signHash(account, hash[:]) if err != nil { return nil, err } + return tx.WithSignature(signer, sig) } @@ -759,6 +795,7 @@ func (w *Wallet) SignTxWithPassphrase(account accounts.Account, passphrase strin return nil, err } } + return w.SignTx(account, tx, chainID) } @@ -815,6 +852,7 @@ func (s *Session) unpair() error { if !s.verified { return fmt.Errorf("unpair requires that the PIN be verified") } + return s.Channel.Unpair() } @@ -823,7 +861,9 @@ func (s *Session) verifyPin(pin []byte) error { if _, err := s.Channel.transmitEncrypted(claSCWallet, insVerifyPin, 0, 0, pin); err != nil { return err } + s.verified = true + return nil } @@ -833,7 +873,9 @@ func (s *Session) unblockPin(pukpin []byte) error { if _, err := s.Channel.transmitEncrypted(claSCWallet, insUnblockPin, 0, 0, pukpin); err != nil { return err } + s.verified = true + return nil } @@ -852,8 +894,10 @@ func (s *Session) authenticate(pairing smartcardPairing) error { if !bytes.Equal(s.Wallet.PublicKey, pairing.PublicKey) { return fmt.Errorf("cannot pair using another wallet's pairing; %x != %x", s.Wallet.PublicKey, pairing.PublicKey) } + s.Channel.PairingKey = pairing.PairingKey s.Channel.PairingIndex = pairing.PairingIndex + return s.Channel.Open() } @@ -875,6 +919,7 @@ func (s *Session) walletStatus() (*walletStatus, error) { if _, err := asn1.UnmarshalWithParams(response.Data, status, "tag:3"); err != nil { return nil, err } + return status, nil } @@ -886,8 +931,10 @@ func (s *Session) derivationPath() (accounts.DerivationPath, error) { if err != nil { return nil, err } + buf := bytes.NewReader(response.Data) path := make(accounts.DerivationPath, len(response.Data)/4) + return path, binary.Read(buf, binary.BigEndian, &path) } @@ -906,6 +953,7 @@ func (s *Session) initialize(seed []byte) error { if err != nil { return err } + if status == "Online" { return fmt.Errorf("card is already initialized, cowardly refusing to proceed") } @@ -927,6 +975,7 @@ func (s *Session) initialize(seed []byte) error { id.PublicKey = crypto.FromECDSAPub(&key.PublicKey) id.PrivateKey = seed[:32] id.ChainCode = seed[32:] + data, err := asn1.Marshal(id) if err != nil { return err @@ -936,6 +985,7 @@ func (s *Session) initialize(seed []byte) error { data[0] = 0xA1 _, err = s.Channel.transmitEncrypted(claSCWallet, insLoadKey, 0x02, 0, data) + return err } @@ -947,6 +997,7 @@ func (s *Session) derive(path accounts.DerivationPath) (accounts.Account, error) } var p1 uint8 + switch startingPoint { case derivationpath.StartingPointMaster: p1 = P1DeriveKeyFromMaster @@ -979,6 +1030,7 @@ func (s *Session) derive(path accounts.DerivationPath) (accounts.Account, error) if _, err := asn1.UnmarshalWithParams(response.Data, sigdata, "tag:0"); err != nil { return accounts.Account{}, err } + rbytes, sbytes := sigdata.Signature.R.Bytes(), sigdata.Signature.S.Bytes() sig := make([]byte, 65) copy(sig[32-len(rbytes):32], rbytes) @@ -987,10 +1039,12 @@ func (s *Session) derive(path accounts.DerivationPath) (accounts.Account, error) if err := confirmPublicKey(sig, sigdata.PublicKey); err != nil { return accounts.Account{}, err } + pub, err := crypto.UnmarshalPubkey(sigdata.PublicKey) if err != nil { return accounts.Account{}, err } + return s.Wallet.makeAccount(crypto.PubkeyToAddress(*pub), path), nil } @@ -1010,10 +1064,12 @@ func (s *Session) publicKey() ([]byte, error) { if err != nil { return nil, err } + keys := new(keyExport) if _, err := asn1.UnmarshalWithParams(response.Data, keys, "tag:1"); err != nil { return nil, err } + return keys.PublicKey, nil } @@ -1031,16 +1087,19 @@ type signatureData struct { // recovering the v value. func (s *Session) sign(path accounts.DerivationPath, hash []byte) ([]byte, error) { startTime := time.Now() + _, err := s.derive(path) if err != nil { return nil, err } + deriveTime := time.Now() response, err := s.Channel.transmitEncrypted(claSCWallet, insSign, signP1PrecomputedHash, signP2OnlyBlock, hash) if err != nil { return nil, err } + sigdata := new(signatureData) if _, err := asn1.UnmarshalWithParams(response.Data, sigdata, "tag:0"); err != nil { return nil, err @@ -1056,6 +1115,7 @@ func (s *Session) sign(path accounts.DerivationPath, hash []byte) ([]byte, error if err != nil { return nil, err } + log.Debug("Signed using smartcard", "deriveTime", deriveTime.Sub(startTime), "signingTime", time.Since(deriveTime)) return sig, nil @@ -1071,6 +1131,7 @@ func confirmPublicKey(sig, pubkey []byte) error { // recover the v value and produce a recoverable signature. func makeRecoverableSignature(hash, sig, expectedPubkey []byte) ([]byte, error) { var libraryError error + for v := 0; v < 2; v++ { sig[64] = byte(v) if pubkey, err := crypto.Ecrecover(hash, sig); err == nil { @@ -1081,8 +1142,10 @@ func makeRecoverableSignature(hash, sig, expectedPubkey []byte) ([]byte, error) libraryError = err } } + if libraryError != nil { return nil, libraryError } + return nil, ErrPubkeyMismatch } diff --git a/accounts/url.go b/accounts/url.go index 39b00e5b44..249254827a 100644 --- a/accounts/url.go +++ b/accounts/url.go @@ -46,6 +46,7 @@ func parseURL(url string) (URL, error) { if len(parts) != 2 || parts[0] == "" { return URL{}, errors.New("protocol scheme missing") } + return URL{ Scheme: parts[0], Path: parts[1], @@ -57,6 +58,7 @@ func (u URL) String() string { if u.Scheme != "" { return fmt.Sprintf("%s://%s", u.Scheme, u.Path) } + return u.Path } @@ -66,6 +68,7 @@ func (u URL) TerminalString() string { if len(url) > 32 { return url[:31] + ".." } + return url } @@ -77,16 +80,20 @@ func (u URL) MarshalJSON() ([]byte, error) { // UnmarshalJSON parses url. func (u *URL) UnmarshalJSON(input []byte) error { var textURL string + err := json.Unmarshal(input, &textURL) if err != nil { return err } + url, err := parseURL(textURL) if err != nil { return err } + u.Scheme = url.Scheme u.Path = url.Path + return nil } @@ -99,5 +106,6 @@ func (u URL) Cmp(url URL) int { if u.Scheme == url.Scheme { return strings.Compare(u.Path, url.Path) } + return strings.Compare(u.Scheme, url.Scheme) } diff --git a/accounts/url_test.go b/accounts/url_test.go index 239aa06d22..d5b7f79719 100644 --- a/accounts/url_test.go +++ b/accounts/url_test.go @@ -25,9 +25,11 @@ func TestURLParsing(t *testing.T) { if err != nil { t.Errorf("unexpected error: %v", err) } + if url.Scheme != "https" { t.Errorf("expected: %v, got: %v", "https", url.Scheme) } + if url.Path != "ethereum.org" { t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path) } @@ -53,10 +55,12 @@ func TestURLString(t *testing.T) { func TestURLMarshalJSON(t *testing.T) { url := URL{Scheme: "https", Path: "ethereum.org"} + json, err := url.MarshalJSON() if err != nil { t.Errorf("unexpcted error: %v", err) } + if string(json) != "\"https://ethereum.org\"" { t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json)) } @@ -64,13 +68,16 @@ func TestURLMarshalJSON(t *testing.T) { func TestURLUnmarshalJSON(t *testing.T) { url := &URL{} + err := url.UnmarshalJSON([]byte("\"https://ethereum.org\"")) if err != nil { t.Errorf("unexpcted error: %v", err) } + if url.Scheme != "https" { t.Errorf("expected: %v, got: %v", "https", url.Scheme) } + if url.Path != "ethereum.org" { t.Errorf("expected: %v, got: %v", "https", url.Path) } diff --git a/accounts/usbwallet/hub.go b/accounts/usbwallet/hub.go index 2139967228..af86eff672 100644 --- a/accounts/usbwallet/hub.go +++ b/accounts/usbwallet/hub.go @@ -112,6 +112,7 @@ func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, if !usb.Supported() { return nil, errors.New("unsupported platform") } + hub := &Hub{ scheme: scheme, vendorID: vendorID, @@ -122,6 +123,7 @@ func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, quit: make(chan chan error), } hub.refreshWallets() + return hub, nil } @@ -136,6 +138,7 @@ func (hub *Hub) Wallets() []accounts.Wallet { cpy := make([]accounts.Wallet, len(hub.wallets)) copy(cpy, hub.wallets) + return cpy } @@ -170,17 +173,22 @@ func (hub *Hub) refreshWallets() { return } } + infos, err := usb.Enumerate(hub.vendorID, 0) if err != nil { failcount := atomic.AddUint32(&hub.enumFails, 1) + if runtime.GOOS == "linux" { // See rationale before the enumeration why this is needed and only on Linux. hub.commsLock.Unlock() } + log.Error("Failed to enumerate USB devices", "hub", hub.scheme, "vendor", hub.vendorID, "failcount", failcount, "err", err) + return } + atomic.StoreUint32(&hub.enumFails, 0) for _, info := range infos { @@ -192,6 +200,7 @@ func (hub *Hub) refreshWallets() { } } } + if runtime.GOOS == "linux" { // See rationale before the enumeration why this is needed and only on Linux. hub.commsLock.Unlock() @@ -225,12 +234,14 @@ func (hub *Hub) refreshWallets() { events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) wallets = append(wallets, wallet) + continue } // If the device is the same as the first wallet, keep it if hub.wallets[0].URL().Cmp(url) == 0 { wallets = append(wallets, hub.wallets[0]) hub.wallets = hub.wallets[1:] + continue } } @@ -238,6 +249,7 @@ func (hub *Hub) refreshWallets() { for _, wallet := range hub.wallets { events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) } + hub.refreshed = time.Now() hub.wallets = wallets hub.stateLock.Unlock() @@ -263,6 +275,7 @@ func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { hub.updating = true go hub.updater() } + return sub } @@ -282,6 +295,7 @@ func (hub *Hub) updater() { if hub.updateScope.Count() == 0 { hub.updating = false hub.stateLock.Unlock() + return } hub.stateLock.Unlock() diff --git a/accounts/usbwallet/ledger.go b/accounts/usbwallet/ledger.go index 723df0f2b3..f3a4e16962 100644 --- a/accounts/usbwallet/ledger.go +++ b/accounts/usbwallet/ledger.go @@ -94,12 +94,15 @@ func (w *ledgerDriver) Status() (string, error) { if w.failure != nil { return fmt.Sprintf("Failed: %v", w.failure), w.failure } + if w.browser { return "Ethereum app in browser mode", w.failure } + if w.offline() { return "Ethereum app offline", w.failure } + return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure } @@ -122,12 +125,14 @@ func (w *ledgerDriver) Open(device io.ReadWriter, passphrase string) error { if err == errLedgerReplyInvalidHeader { w.browser = true } + return nil } // Try to resolve the Ethereum app's version, will fail prior to v1.0.2 if w.version, err = w.ledgerVersion(); err != nil { w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1 } + return nil } @@ -145,6 +150,7 @@ func (w *ledgerDriver) Heartbeat() error { w.failure = err return err } + return nil } @@ -215,12 +221,15 @@ func (w *ledgerDriver) ledgerVersion() ([3]byte, error) { if err != nil { return [3]byte{}, err } + if len(reply) != 4 { return [3]byte{}, errLedgerInvalidVersionReply } // Cache the version for future reference var version [3]byte + copy(version[:], reply[1:]) + return version, nil } @@ -259,6 +268,7 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er // Flatten the derivation path into the Ledger request path := make([]byte, 1+4*len(derivationPath)) path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { binary.BigEndian.PutUint32(path[1+4*i:], component) } @@ -271,12 +281,14 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er if len(reply) < 1 || len(reply) < 1+int(reply[0]) { return common.Address{}, errors.New("reply lacks public key entry") } + reply = reply[1+int(reply[0]):] // Extract the Ethereum hex address string if len(reply) < 1 || len(reply) < 1+int(reply[0]) { return common.Address{}, errors.New("reply lacks address entry") } + hexstr := reply[1 : 1+int(reply[0])] // Decode the hex sting into an Ethereum address and return @@ -284,6 +296,7 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er if _, err = hex.Decode(address[:], hexstr); err != nil { return common.Address{}, err } + return address, nil } @@ -325,6 +338,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction // Flatten the derivation path into the Ledger request path := make([]byte, 1+4*len(derivationPath)) path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { binary.BigEndian.PutUint32(path[1+4*i:], component) } @@ -333,6 +347,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction txrlp []byte err error ) + if chainID == nil { if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil { return common.Address{}, nil, err @@ -342,6 +357,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction return common.Address{}, nil, err } } + payload := append(path, txrlp...) // Send the request and wait for the response @@ -374,6 +390,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction if len(reply) != crypto.SignatureLength { return common.Address{}, nil, errors.New("reply lacks signature") } + signature := append(reply[1:], reply[0]) // Create the correct signer and signature transform based on the chain ID @@ -384,14 +401,17 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction signer = types.NewEIP155Signer(chainID) signature[64] -= byte(chainID.Uint64()*2 + 35) } + signed, err := tx.WithSignature(signer, signature) if err != nil { return common.Address{}, nil, err } + sender, err := types.Sender(signer, signed) if err != nil { return common.Address{}, nil, err } + return sender, signed, nil } @@ -426,6 +446,7 @@ func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHas // Flatten the derivation path into the Ledger request path := make([]byte, 1+4*len(derivationPath)) path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { binary.BigEndian.PutUint32(path[1+4*i:], component) } @@ -451,7 +472,9 @@ func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHas if len(reply) != crypto.SignatureLength { return nil, errors.New("reply lacks signature") } + signature := append(reply[1:], reply[0]) + return signature, nil } @@ -515,18 +538,22 @@ func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 l } // Send over to the device w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk)) + if _, err := w.device.Write(chunk); err != nil { return nil, err } } // Stream the reply back from the wallet in 64 byte chunks var reply []byte + chunk = chunk[:64] // Yeah, we surely have enough space + for { // Read the next chunk from the Ledger wallet if _, err := io.ReadFull(w.device, chunk); err != nil { return nil, err } + w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk)) // Make sure the transport header matches @@ -550,5 +577,6 @@ func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 l break } } + return reply[:len(reply)-2], nil } diff --git a/accounts/usbwallet/trezor.go b/accounts/usbwallet/trezor.go index 7b4889ba7a..0201048ebd 100644 --- a/accounts/usbwallet/trezor.go +++ b/accounts/usbwallet/trezor.go @@ -73,12 +73,15 @@ func (w *trezorDriver) Status() (string, error) { if w.failure != nil { return fmt.Sprintf("Failed: %v", w.failure), w.failure } + if w.device == nil { return "Closed", w.failure } + if w.pinwait { return fmt.Sprintf("Trezor v%d.%d.%d '%s' waiting for PIN", w.version[0], w.version[1], w.version[2], w.label), w.failure } + return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure } @@ -107,12 +110,14 @@ func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error { if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil { return err } + w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()} w.label = features.GetLabel() // Do a manual ping, forcing the device to ask for its PIN and Passphrase askPin := true askPassphrase := true + res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success)) if err != nil { return err @@ -125,6 +130,7 @@ func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error { case 1: w.pinwait = false w.passphrasewait = true + return ErrTrezorPassphraseNeeded case 2: return nil // responded with trezor.Success @@ -134,10 +140,12 @@ func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error { if w.pinwait { w.pinwait = false res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest)) + if err != nil { w.failure = err return err } + if res == 1 { w.passphrasewait = true return ErrTrezorPassphraseNeeded @@ -167,6 +175,7 @@ func (w *trezorDriver) Heartbeat() error { w.failure = err return err } + return nil } @@ -182,6 +191,7 @@ func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio if w.device == nil { return common.Address{}, nil, accounts.ErrWalletClosed } + return w.trezorSign(path, tx, chainID) } @@ -204,6 +214,7 @@ func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, er if addr := address.GetAddressHex(); len(addr) > 0 { // Newer firmwares use hexadecimal formats return common.HexToAddress(addr), nil } + return common.Address{}, errors.New("missing derived address") } @@ -222,17 +233,20 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction Value: tx.Value().Bytes(), DataLength: &length, } + if to := tx.To(); to != nil { // Non contract deploy, set recipient explicitly hex := to.Hex() request.ToHex = &hex // Newer firmwares (old will ignore) request.ToBin = (*to)[:] // Older firmwares (new will ignore) } + if length > 1024 { // Send the data chunked if that was requested request.DataInitialChunk, data = data[:1024], data[1024:] } else { request.DataInitialChunk, data = data, nil } + if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?) id := uint32(chainID.Int64()) request.ChainId = &id @@ -242,6 +256,7 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction if _, err := w.trezorExchange(request, response); err != nil { return common.Address{}, nil, err } + for response.DataLength != nil && int(*response.DataLength) <= len(data) { chunk := data[:*response.DataLength] data = data[*response.DataLength:] @@ -254,6 +269,7 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 { return common.Address{}, nil, errors.New("reply lacks signature") } + signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV())) // Create the correct signer and signature transform based on the chain ID @@ -271,10 +287,12 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction if err != nil { return common.Address{}, nil, err } + sender, err := types.Sender(signer, signed) if err != nil { return common.Address{}, nil, err } + return sender, signed, nil } @@ -287,6 +305,7 @@ func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Messag if err != nil { return 0, err } + payload := make([]byte, 8+len(data)) copy(payload, []byte{0x23, 0x23}) binary.BigEndian.PutUint16(payload[2:], trezor.Type(req)) @@ -309,6 +328,7 @@ func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Messag } // Send over to the device w.log.Trace("Data chunk sent to the Trezor", "chunk", hexutil.Bytes(chunk)) + if _, err := w.device.Write(chunk); err != nil { return 0, err } @@ -318,11 +338,13 @@ func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Messag kind uint16 reply []byte ) + for { // Read the next chunk from the Trezor wallet if _, err := io.ReadFull(w.device, chunk); err != nil { return 0, err } + w.log.Trace("Data chunk received from the Trezor", "chunk", hexutil.Bytes(chunk)) // Make sure the transport header matches @@ -354,20 +376,25 @@ func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Messag if err := proto.Unmarshal(reply, failure); err != nil { return 0, err } + return 0, errors.New("trezor: " + failure.GetMessage()) } + if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) { // Trezor is waiting for user confirmation, ack and wait for the next message return w.trezorExchange(&trezor.ButtonAck{}, results...) } + for i, res := range results { if trezor.Type(res) == kind { return i, proto.Unmarshal(reply, res) } } + expected := make([]string, len(results)) for i, res := range results { expected[i] = trezor.Name(trezor.Type(res)) } + return 0, fmt.Errorf("trezor: expected reply types %s, got %s", expected, trezor.Name(kind)) } diff --git a/accounts/usbwallet/trezor/messages-common.pb.go b/accounts/usbwallet/trezor/messages-common.pb.go index b396c6d8b5..c163bb5788 100644 --- a/accounts/usbwallet/trezor/messages-common.pb.go +++ b/accounts/usbwallet/trezor/messages-common.pb.go @@ -74,6 +74,7 @@ var Failure_FailureType_value = map[string]int32{ func (x Failure_FailureType) Enum() *Failure_FailureType { p := new(Failure_FailureType) *p = x + return p } @@ -86,7 +87,9 @@ func (x *Failure_FailureType) UnmarshalJSON(data []byte) error { if err != nil { return err } + *x = Failure_FailureType(value) + return nil } @@ -155,6 +158,7 @@ var ButtonRequest_ButtonRequestType_value = map[string]int32{ func (x ButtonRequest_ButtonRequestType) Enum() *ButtonRequest_ButtonRequestType { p := new(ButtonRequest_ButtonRequestType) *p = x + return p } @@ -167,7 +171,9 @@ func (x *ButtonRequest_ButtonRequestType) UnmarshalJSON(data []byte) error { if err != nil { return err } + *x = ButtonRequest_ButtonRequestType(value) + return nil } @@ -200,6 +206,7 @@ var PinMatrixRequest_PinMatrixRequestType_value = map[string]int32{ func (x PinMatrixRequest_PinMatrixRequestType) Enum() *PinMatrixRequest_PinMatrixRequestType { p := new(PinMatrixRequest_PinMatrixRequestType) *p = x + return p } @@ -212,7 +219,9 @@ func (x *PinMatrixRequest_PinMatrixRequestType) UnmarshalJSON(data []byte) error if err != nil { return err } + *x = PinMatrixRequest_PinMatrixRequestType(value) + return nil } @@ -259,6 +268,7 @@ func (m *Success) GetMessage() string { if m != nil && m.Message != nil { return *m.Message } + return "" } @@ -302,6 +312,7 @@ func (m *Failure) GetCode() Failure_FailureType { if m != nil && m.Code != nil { return *m.Code } + return Failure_Failure_UnexpectedMessage } @@ -309,6 +320,7 @@ func (m *Failure) GetMessage() string { if m != nil && m.Message != nil { return *m.Message } + return "" } @@ -353,6 +365,7 @@ func (m *ButtonRequest) GetCode() ButtonRequest_ButtonRequestType { if m != nil && m.Code != nil { return *m.Code } + return ButtonRequest_ButtonRequest_Other } @@ -360,6 +373,7 @@ func (m *ButtonRequest) GetData() string { if m != nil && m.Data != nil { return *m.Data } + return "" } @@ -437,6 +451,7 @@ func (m *PinMatrixRequest) GetType() PinMatrixRequest_PinMatrixRequestType { if m != nil && m.Type != nil { return *m.Type } + return PinMatrixRequest_PinMatrixRequestType_Current } @@ -479,6 +494,7 @@ func (m *PinMatrixAck) GetPin() string { if m != nil && m.Pin != nil { return *m.Pin } + return "" } @@ -522,6 +538,7 @@ func (m *PassphraseRequest) GetOnDevice() bool { if m != nil && m.OnDevice != nil { return *m.OnDevice } + return false } @@ -565,6 +582,7 @@ func (m *PassphraseAck) GetPassphrase() string { if m != nil && m.Passphrase != nil { return *m.Passphrase } + return "" } @@ -572,6 +590,7 @@ func (m *PassphraseAck) GetState() []byte { if m != nil { return m.State } + return nil } @@ -614,6 +633,7 @@ func (m *PassphraseStateRequest) GetState() []byte { if m != nil { return m.State } + return nil } @@ -696,6 +716,7 @@ func (m *HDNodeType) GetDepth() uint32 { if m != nil && m.Depth != nil { return *m.Depth } + return 0 } @@ -703,6 +724,7 @@ func (m *HDNodeType) GetFingerprint() uint32 { if m != nil && m.Fingerprint != nil { return *m.Fingerprint } + return 0 } @@ -710,6 +732,7 @@ func (m *HDNodeType) GetChildNum() uint32 { if m != nil && m.ChildNum != nil { return *m.ChildNum } + return 0 } @@ -717,6 +740,7 @@ func (m *HDNodeType) GetChainCode() []byte { if m != nil { return m.ChainCode } + return nil } @@ -724,6 +748,7 @@ func (m *HDNodeType) GetPrivateKey() []byte { if m != nil { return m.PrivateKey } + return nil } @@ -731,6 +756,7 @@ func (m *HDNodeType) GetPublicKey() []byte { if m != nil { return m.PublicKey } + return nil } diff --git a/accounts/usbwallet/trezor/messages-ethereum.pb.go b/accounts/usbwallet/trezor/messages-ethereum.pb.go index 230a48279d..6c15429f60 100644 --- a/accounts/usbwallet/trezor/messages-ethereum.pb.go +++ b/accounts/usbwallet/trezor/messages-ethereum.pb.go @@ -63,6 +63,7 @@ func (m *EthereumGetPublicKey) GetAddressN() []uint32 { if m != nil { return m.AddressN } + return nil } @@ -70,6 +71,7 @@ func (m *EthereumGetPublicKey) GetShowDisplay() bool { if m != nil && m.ShowDisplay != nil { return *m.ShowDisplay } + return false } @@ -113,6 +115,7 @@ func (m *EthereumPublicKey) GetNode() *HDNodeType { if m != nil { return m.Node } + return nil } @@ -120,6 +123,7 @@ func (m *EthereumPublicKey) GetXpub() string { if m != nil && m.Xpub != nil { return *m.Xpub } + return "" } @@ -165,6 +169,7 @@ func (m *EthereumGetAddress) GetAddressN() []uint32 { if m != nil { return m.AddressN } + return nil } @@ -172,6 +177,7 @@ func (m *EthereumGetAddress) GetShowDisplay() bool { if m != nil && m.ShowDisplay != nil { return *m.ShowDisplay } + return false } @@ -215,6 +221,7 @@ func (m *EthereumAddress) GetAddressBin() []byte { if m != nil { return m.AddressBin } + return nil } @@ -222,6 +229,7 @@ func (m *EthereumAddress) GetAddressHex() string { if m != nil && m.AddressHex != nil { return *m.AddressHex } + return "" } @@ -278,6 +286,7 @@ func (m *EthereumSignTx) GetAddressN() []uint32 { if m != nil { return m.AddressN } + return nil } @@ -285,6 +294,7 @@ func (m *EthereumSignTx) GetNonce() []byte { if m != nil { return m.Nonce } + return nil } @@ -292,6 +302,7 @@ func (m *EthereumSignTx) GetGasPrice() []byte { if m != nil { return m.GasPrice } + return nil } @@ -299,6 +310,7 @@ func (m *EthereumSignTx) GetGasLimit() []byte { if m != nil { return m.GasLimit } + return nil } @@ -306,6 +318,7 @@ func (m *EthereumSignTx) GetToBin() []byte { if m != nil { return m.ToBin } + return nil } @@ -313,6 +326,7 @@ func (m *EthereumSignTx) GetToHex() string { if m != nil && m.ToHex != nil { return *m.ToHex } + return "" } @@ -320,6 +334,7 @@ func (m *EthereumSignTx) GetValue() []byte { if m != nil { return m.Value } + return nil } @@ -327,6 +342,7 @@ func (m *EthereumSignTx) GetDataInitialChunk() []byte { if m != nil { return m.DataInitialChunk } + return nil } @@ -334,6 +350,7 @@ func (m *EthereumSignTx) GetDataLength() uint32 { if m != nil && m.DataLength != nil { return *m.DataLength } + return 0 } @@ -341,6 +358,7 @@ func (m *EthereumSignTx) GetChainId() uint32 { if m != nil && m.ChainId != nil { return *m.ChainId } + return 0 } @@ -348,6 +366,7 @@ func (m *EthereumSignTx) GetTxType() uint32 { if m != nil && m.TxType != nil { return *m.TxType } + return 0 } @@ -396,6 +415,7 @@ func (m *EthereumTxRequest) GetDataLength() uint32 { if m != nil && m.DataLength != nil { return *m.DataLength } + return 0 } @@ -403,6 +423,7 @@ func (m *EthereumTxRequest) GetSignatureV() uint32 { if m != nil && m.SignatureV != nil { return *m.SignatureV } + return 0 } @@ -410,6 +431,7 @@ func (m *EthereumTxRequest) GetSignatureR() []byte { if m != nil { return m.SignatureR } + return nil } @@ -417,6 +439,7 @@ func (m *EthereumTxRequest) GetSignatureS() []byte { if m != nil { return m.SignatureS } + return nil } @@ -459,6 +482,7 @@ func (m *EthereumTxAck) GetDataChunk() []byte { if m != nil { return m.DataChunk } + return nil } @@ -504,6 +528,7 @@ func (m *EthereumSignMessage) GetAddressN() []uint32 { if m != nil { return m.AddressN } + return nil } @@ -511,6 +536,7 @@ func (m *EthereumSignMessage) GetMessage() []byte { if m != nil { return m.Message } + return nil } @@ -555,6 +581,7 @@ func (m *EthereumMessageSignature) GetAddressBin() []byte { if m != nil { return m.AddressBin } + return nil } @@ -562,6 +589,7 @@ func (m *EthereumMessageSignature) GetSignature() []byte { if m != nil { return m.Signature } + return nil } @@ -569,6 +597,7 @@ func (m *EthereumMessageSignature) GetAddressHex() string { if m != nil && m.AddressHex != nil { return *m.AddressHex } + return "" } @@ -616,6 +645,7 @@ func (m *EthereumVerifyMessage) GetAddressBin() []byte { if m != nil { return m.AddressBin } + return nil } @@ -623,6 +653,7 @@ func (m *EthereumVerifyMessage) GetSignature() []byte { if m != nil { return m.Signature } + return nil } @@ -630,6 +661,7 @@ func (m *EthereumVerifyMessage) GetMessage() []byte { if m != nil { return m.Message } + return nil } @@ -637,6 +669,7 @@ func (m *EthereumVerifyMessage) GetAddressHex() string { if m != nil && m.AddressHex != nil { return *m.AddressHex } + return "" } diff --git a/accounts/usbwallet/trezor/messages-management.pb.go b/accounts/usbwallet/trezor/messages-management.pb.go index 91bfca1e3f..0d493ce6b7 100644 --- a/accounts/usbwallet/trezor/messages-management.pb.go +++ b/accounts/usbwallet/trezor/messages-management.pb.go @@ -46,6 +46,7 @@ var ApplySettings_PassphraseSourceType_value = map[string]int32{ func (x ApplySettings_PassphraseSourceType) Enum() *ApplySettings_PassphraseSourceType { p := new(ApplySettings_PassphraseSourceType) *p = x + return p } @@ -58,7 +59,9 @@ func (x *ApplySettings_PassphraseSourceType) UnmarshalJSON(data []byte) error { if err != nil { return err } + *x = ApplySettings_PassphraseSourceType(value) + return nil } @@ -94,6 +97,7 @@ var RecoveryDevice_RecoveryDeviceType_value = map[string]int32{ func (x RecoveryDevice_RecoveryDeviceType) Enum() *RecoveryDevice_RecoveryDeviceType { p := new(RecoveryDevice_RecoveryDeviceType) *p = x + return p } @@ -106,7 +110,9 @@ func (x *RecoveryDevice_RecoveryDeviceType) UnmarshalJSON(data []byte) error { if err != nil { return err } + *x = RecoveryDevice_RecoveryDeviceType(value) + return nil } @@ -139,6 +145,7 @@ var WordRequest_WordRequestType_value = map[string]int32{ func (x WordRequest_WordRequestType) Enum() *WordRequest_WordRequestType { p := new(WordRequest_WordRequestType) *p = x + return p } @@ -151,7 +158,9 @@ func (x *WordRequest_WordRequestType) UnmarshalJSON(data []byte) error { if err != nil { return err } + *x = WordRequest_WordRequestType(value) + return nil } @@ -200,6 +209,7 @@ func (m *Initialize) GetState() []byte { if m != nil { return m.State } + return nil } @@ -207,6 +217,7 @@ func (m *Initialize) GetSkipPassphrase() bool { if m != nil && m.SkipPassphrase != nil { return *m.SkipPassphrase } + return false } @@ -310,6 +321,7 @@ func (m *Features) GetVendor() string { if m != nil && m.Vendor != nil { return *m.Vendor } + return "" } @@ -317,6 +329,7 @@ func (m *Features) GetMajorVersion() uint32 { if m != nil && m.MajorVersion != nil { return *m.MajorVersion } + return 0 } @@ -324,6 +337,7 @@ func (m *Features) GetMinorVersion() uint32 { if m != nil && m.MinorVersion != nil { return *m.MinorVersion } + return 0 } @@ -331,6 +345,7 @@ func (m *Features) GetPatchVersion() uint32 { if m != nil && m.PatchVersion != nil { return *m.PatchVersion } + return 0 } @@ -338,6 +353,7 @@ func (m *Features) GetBootloaderMode() bool { if m != nil && m.BootloaderMode != nil { return *m.BootloaderMode } + return false } @@ -345,6 +361,7 @@ func (m *Features) GetDeviceId() string { if m != nil && m.DeviceId != nil { return *m.DeviceId } + return "" } @@ -352,6 +369,7 @@ func (m *Features) GetPinProtection() bool { if m != nil && m.PinProtection != nil { return *m.PinProtection } + return false } @@ -359,6 +377,7 @@ func (m *Features) GetPassphraseProtection() bool { if m != nil && m.PassphraseProtection != nil { return *m.PassphraseProtection } + return false } @@ -366,6 +385,7 @@ func (m *Features) GetLanguage() string { if m != nil && m.Language != nil { return *m.Language } + return "" } @@ -373,6 +393,7 @@ func (m *Features) GetLabel() string { if m != nil && m.Label != nil { return *m.Label } + return "" } @@ -380,6 +401,7 @@ func (m *Features) GetInitialized() bool { if m != nil && m.Initialized != nil { return *m.Initialized } + return false } @@ -387,6 +409,7 @@ func (m *Features) GetRevision() []byte { if m != nil { return m.Revision } + return nil } @@ -394,6 +417,7 @@ func (m *Features) GetBootloaderHash() []byte { if m != nil { return m.BootloaderHash } + return nil } @@ -401,6 +425,7 @@ func (m *Features) GetImported() bool { if m != nil && m.Imported != nil { return *m.Imported } + return false } @@ -408,6 +433,7 @@ func (m *Features) GetPinCached() bool { if m != nil && m.PinCached != nil { return *m.PinCached } + return false } @@ -415,6 +441,7 @@ func (m *Features) GetPassphraseCached() bool { if m != nil && m.PassphraseCached != nil { return *m.PassphraseCached } + return false } @@ -422,6 +449,7 @@ func (m *Features) GetFirmwarePresent() bool { if m != nil && m.FirmwarePresent != nil { return *m.FirmwarePresent } + return false } @@ -429,6 +457,7 @@ func (m *Features) GetNeedsBackup() bool { if m != nil && m.NeedsBackup != nil { return *m.NeedsBackup } + return false } @@ -436,6 +465,7 @@ func (m *Features) GetFlags() uint32 { if m != nil && m.Flags != nil { return *m.Flags } + return 0 } @@ -443,6 +473,7 @@ func (m *Features) GetModel() string { if m != nil && m.Model != nil { return *m.Model } + return "" } @@ -450,6 +481,7 @@ func (m *Features) GetFwMajor() uint32 { if m != nil && m.FwMajor != nil { return *m.FwMajor } + return 0 } @@ -457,6 +489,7 @@ func (m *Features) GetFwMinor() uint32 { if m != nil && m.FwMinor != nil { return *m.FwMinor } + return 0 } @@ -464,6 +497,7 @@ func (m *Features) GetFwPatch() uint32 { if m != nil && m.FwPatch != nil { return *m.FwPatch } + return 0 } @@ -471,6 +505,7 @@ func (m *Features) GetFwVendor() string { if m != nil && m.FwVendor != nil { return *m.FwVendor } + return "" } @@ -478,6 +513,7 @@ func (m *Features) GetFwVendorKeys() []byte { if m != nil { return m.FwVendorKeys } + return nil } @@ -485,6 +521,7 @@ func (m *Features) GetUnfinishedBackup() bool { if m != nil && m.UnfinishedBackup != nil { return *m.UnfinishedBackup } + return false } @@ -492,6 +529,7 @@ func (m *Features) GetNoBackup() bool { if m != nil && m.NoBackup != nil { return *m.NoBackup } + return false } @@ -577,6 +615,7 @@ func (m *ApplySettings) GetLanguage() string { if m != nil && m.Language != nil { return *m.Language } + return "" } @@ -584,6 +623,7 @@ func (m *ApplySettings) GetLabel() string { if m != nil && m.Label != nil { return *m.Label } + return "" } @@ -591,6 +631,7 @@ func (m *ApplySettings) GetUsePassphrase() bool { if m != nil && m.UsePassphrase != nil { return *m.UsePassphrase } + return false } @@ -598,6 +639,7 @@ func (m *ApplySettings) GetHomescreen() []byte { if m != nil { return m.Homescreen } + return nil } @@ -605,6 +647,7 @@ func (m *ApplySettings) GetPassphraseSource() ApplySettings_PassphraseSourceType if m != nil && m.PassphraseSource != nil { return *m.PassphraseSource } + return ApplySettings_ASK } @@ -612,6 +655,7 @@ func (m *ApplySettings) GetAutoLockDelayMs() uint32 { if m != nil && m.AutoLockDelayMs != nil { return *m.AutoLockDelayMs } + return 0 } @@ -619,6 +663,7 @@ func (m *ApplySettings) GetDisplayRotation() uint32 { if m != nil && m.DisplayRotation != nil { return *m.DisplayRotation } + return 0 } @@ -663,6 +708,7 @@ func (m *ApplyFlags) GetFlags() uint32 { if m != nil && m.Flags != nil { return *m.Flags } + return 0 } @@ -707,6 +753,7 @@ func (m *ChangePin) GetRemove() bool { if m != nil && m.Remove != nil { return *m.Remove } + return false } @@ -753,6 +800,7 @@ func (m *Ping) GetMessage() string { if m != nil && m.Message != nil { return *m.Message } + return "" } @@ -760,6 +808,7 @@ func (m *Ping) GetButtonProtection() bool { if m != nil && m.ButtonProtection != nil { return *m.ButtonProtection } + return false } @@ -767,6 +816,7 @@ func (m *Ping) GetPinProtection() bool { if m != nil && m.PinProtection != nil { return *m.PinProtection } + return false } @@ -774,6 +824,7 @@ func (m *Ping) GetPassphraseProtection() bool { if m != nil && m.PassphraseProtection != nil { return *m.PassphraseProtection } + return false } @@ -853,6 +904,7 @@ func (m *GetEntropy) GetSize() uint32 { if m != nil && m.Size != nil { return *m.Size } + return 0 } @@ -895,6 +947,7 @@ func (m *Entropy) GetEntropy() []byte { if m != nil { return m.Entropy } + return nil } @@ -984,6 +1037,7 @@ func (m *LoadDevice) GetMnemonic() string { if m != nil && m.Mnemonic != nil { return *m.Mnemonic } + return "" } @@ -991,6 +1045,7 @@ func (m *LoadDevice) GetNode() *HDNodeType { if m != nil { return m.Node } + return nil } @@ -998,6 +1053,7 @@ func (m *LoadDevice) GetPin() string { if m != nil && m.Pin != nil { return *m.Pin } + return "" } @@ -1005,6 +1061,7 @@ func (m *LoadDevice) GetPassphraseProtection() bool { if m != nil && m.PassphraseProtection != nil { return *m.PassphraseProtection } + return false } @@ -1012,6 +1069,7 @@ func (m *LoadDevice) GetLanguage() string { if m != nil && m.Language != nil { return *m.Language } + return Default_LoadDevice_Language } @@ -1019,6 +1077,7 @@ func (m *LoadDevice) GetLabel() string { if m != nil && m.Label != nil { return *m.Label } + return "" } @@ -1026,6 +1085,7 @@ func (m *LoadDevice) GetSkipChecksum() bool { if m != nil && m.SkipChecksum != nil { return *m.SkipChecksum } + return false } @@ -1033,6 +1093,7 @@ func (m *LoadDevice) GetU2FCounter() uint32 { if m != nil && m.U2FCounter != nil { return *m.U2FCounter } + return 0 } @@ -1088,6 +1149,7 @@ func (m *ResetDevice) GetDisplayRandom() bool { if m != nil && m.DisplayRandom != nil { return *m.DisplayRandom } + return false } @@ -1095,6 +1157,7 @@ func (m *ResetDevice) GetStrength() uint32 { if m != nil && m.Strength != nil { return *m.Strength } + return Default_ResetDevice_Strength } @@ -1102,6 +1165,7 @@ func (m *ResetDevice) GetPassphraseProtection() bool { if m != nil && m.PassphraseProtection != nil { return *m.PassphraseProtection } + return false } @@ -1109,6 +1173,7 @@ func (m *ResetDevice) GetPinProtection() bool { if m != nil && m.PinProtection != nil { return *m.PinProtection } + return false } @@ -1116,6 +1181,7 @@ func (m *ResetDevice) GetLanguage() string { if m != nil && m.Language != nil { return *m.Language } + return Default_ResetDevice_Language } @@ -1123,6 +1189,7 @@ func (m *ResetDevice) GetLabel() string { if m != nil && m.Label != nil { return *m.Label } + return "" } @@ -1130,6 +1197,7 @@ func (m *ResetDevice) GetU2FCounter() uint32 { if m != nil && m.U2FCounter != nil { return *m.U2FCounter } + return 0 } @@ -1137,6 +1205,7 @@ func (m *ResetDevice) GetSkipBackup() bool { if m != nil && m.SkipBackup != nil { return *m.SkipBackup } + return false } @@ -1144,6 +1213,7 @@ func (m *ResetDevice) GetNoBackup() bool { if m != nil && m.NoBackup != nil { return *m.NoBackup } + return false } @@ -1255,6 +1325,7 @@ func (m *EntropyAck) GetEntropy() []byte { if m != nil { return m.Entropy } + return nil } @@ -1310,6 +1381,7 @@ func (m *RecoveryDevice) GetWordCount() uint32 { if m != nil && m.WordCount != nil { return *m.WordCount } + return 0 } @@ -1317,6 +1389,7 @@ func (m *RecoveryDevice) GetPassphraseProtection() bool { if m != nil && m.PassphraseProtection != nil { return *m.PassphraseProtection } + return false } @@ -1324,6 +1397,7 @@ func (m *RecoveryDevice) GetPinProtection() bool { if m != nil && m.PinProtection != nil { return *m.PinProtection } + return false } @@ -1331,6 +1405,7 @@ func (m *RecoveryDevice) GetLanguage() string { if m != nil && m.Language != nil { return *m.Language } + return Default_RecoveryDevice_Language } @@ -1338,6 +1413,7 @@ func (m *RecoveryDevice) GetLabel() string { if m != nil && m.Label != nil { return *m.Label } + return "" } @@ -1345,6 +1421,7 @@ func (m *RecoveryDevice) GetEnforceWordlist() bool { if m != nil && m.EnforceWordlist != nil { return *m.EnforceWordlist } + return false } @@ -1352,6 +1429,7 @@ func (m *RecoveryDevice) GetType() RecoveryDevice_RecoveryDeviceType { if m != nil && m.Type != nil { return *m.Type } + return RecoveryDevice_RecoveryDeviceType_ScrambledWords } @@ -1359,6 +1437,7 @@ func (m *RecoveryDevice) GetU2FCounter() uint32 { if m != nil && m.U2FCounter != nil { return *m.U2FCounter } + return 0 } @@ -1366,6 +1445,7 @@ func (m *RecoveryDevice) GetDryRun() bool { if m != nil && m.DryRun != nil { return *m.DryRun } + return false } @@ -1409,6 +1489,7 @@ func (m *WordRequest) GetType() WordRequest_WordRequestType { if m != nil && m.Type != nil { return *m.Type } + return WordRequest_WordRequestType_Plain } @@ -1453,6 +1534,7 @@ func (m *WordAck) GetWord() string { if m != nil && m.Word != nil { return *m.Word } + return "" } @@ -1496,6 +1578,7 @@ func (m *SetU2FCounter) GetU2FCounter() uint32 { if m != nil && m.U2FCounter != nil { return *m.U2FCounter } + return 0 } diff --git a/accounts/usbwallet/trezor/messages.pb.go b/accounts/usbwallet/trezor/messages.pb.go index af0c957144..880f49d37a 100644 --- a/accounts/usbwallet/trezor/messages.pb.go +++ b/accounts/usbwallet/trezor/messages.pb.go @@ -636,6 +636,7 @@ var MessageType_value = map[string]int32{ func (x MessageType) Enum() *MessageType { p := new(MessageType) *p = x + return p } @@ -648,7 +649,9 @@ func (x *MessageType) UnmarshalJSON(data []byte) error { if err != nil { return err } + *x = MessageType(value) + return nil } diff --git a/accounts/usbwallet/trezor/trezor.go b/accounts/usbwallet/trezor/trezor.go index 7e756e609b..6f75129704 100644 --- a/accounts/usbwallet/trezor/trezor.go +++ b/accounts/usbwallet/trezor/trezor.go @@ -66,5 +66,6 @@ func Name(kind uint16) string { if len(name) < 12 { return name } + return name[12:] } diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 0e399a6d09..75e9288912 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -133,6 +133,7 @@ func (w *wallet) Status() (string, error) { if w.device == nil { return "Closed", failure } + return status, failure } @@ -152,6 +153,7 @@ func (w *wallet) Open(passphrase string) error { if err != nil { return err } + w.device = device w.commsLock = make(chan struct{}, 1) w.commsLock <- struct{}{} // Enable lock @@ -187,6 +189,7 @@ func (w *wallet) heartbeat() { errc chan error err error ) + for errc == nil && err == nil { // Wait until termination is requested or the heartbeat cycle arrives select { @@ -203,6 +206,7 @@ func (w *wallet) heartbeat() { w.stateLock.RUnlock() continue } + <-w.commsLock // Don't lock state while resolving version err = w.driver.Heartbeat() w.commsLock <- struct{}{} @@ -233,6 +237,7 @@ func (w *wallet) Close() error { // Terminate the health checks var herr error + if hQuit != nil { errc := make(chan error) hQuit <- errc @@ -240,6 +245,7 @@ func (w *wallet) Close() error { } // Terminate the self-derivations var derr error + if dQuit != nil { errc := make(chan error) dQuit <- errc @@ -256,9 +262,11 @@ func (w *wallet) Close() error { if err := w.close(); err != nil { return err } + if herr != nil { return herr } + return derr } @@ -276,6 +284,7 @@ func (w *wallet) close() error { w.device = nil w.accounts, w.paths = nil, nil + return w.driver.Close() } @@ -298,6 +307,7 @@ func (w *wallet) Accounts() []accounts.Account { cpy := make([]accounts.Account, len(w.accounts)) copy(cpy, w.accounts) + return cpy } @@ -313,6 +323,7 @@ func (w *wallet) selfDerive() { errc chan error err error ) + for errc == nil && err == nil { // Wait until either derivation or termination is requested select { @@ -327,6 +338,7 @@ func (w *wallet) selfDerive() { if w.device == nil || w.deriveChain == nil { w.stateLock.RUnlock() reqc <- struct{}{} + continue } select { @@ -334,6 +346,7 @@ func (w *wallet) selfDerive() { default: w.stateLock.RUnlock() reqc <- struct{}{} + continue } // Device lock obtained, derive the next batch of accounts @@ -346,6 +359,7 @@ func (w *wallet) selfDerive() { context = context.Background() ) + for i := 0; i < len(nextAddrs); i++ { for empty := false; !empty; { // Retrieve the next derived Ethereum account @@ -360,11 +374,13 @@ func (w *wallet) selfDerive() { balance *big.Int nonce uint64 ) + balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil) if err != nil { w.log.Warn("USB wallet balance retrieval failed", "err", err) break } + nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil) if err != nil { w.log.Warn("USB wallet nonce retrieval failed", "err", err) @@ -374,6 +390,7 @@ func (w *wallet) selfDerive() { // unless the account was empty. path := make(accounts.DerivationPath, len(nextPaths[i])) copy(path[:], nextPaths[i][:]) + if balance.Sign() == 0 && nonce == 0 { empty = true // If it indeed was empty, make a log output for it anyway. In the case @@ -385,6 +402,7 @@ func (w *wallet) selfDerive() { break } } + paths = append(paths, path) account := accounts.Account{ Address: nextAddrs[i], @@ -423,6 +441,7 @@ func (w *wallet) selfDerive() { // Notify the user of termination and loop after a bit of time (to avoid trashing) reqc <- struct{}{} + if err == nil { select { case errc = <-w.deriveQuit: @@ -448,6 +467,7 @@ func (w *wallet) Contains(account accounts.Account) bool { defer w.stateLock.RUnlock() _, exists := w.paths[account.Address] + return exists } @@ -462,6 +482,7 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun w.stateLock.RUnlock() return accounts.Account{}, accounts.ErrWalletClosed } + <-w.commsLock // Avoid concurrent hardware access address, err := w.driver.Derive(path) w.commsLock <- struct{}{} @@ -472,6 +493,7 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun if err != nil { return accounts.Account{}, err } + account := accounts.Account{ Address: address, URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, @@ -488,6 +510,7 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun w.paths[address] = make(accounts.DerivationPath, len(path)) copy(w.paths[address], path) } + return account, nil } @@ -514,6 +537,7 @@ func (w *wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.Chai w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base)) copy(w.deriveNextPaths[i][:], base[:]) } + w.deriveNextAddrs = make([]common.Address, len(bases)) w.deriveChain = chain } @@ -546,6 +570,7 @@ func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte } // All infos gathered and metadata checks out, request signing <-w.commsLock + defer func() { w.commsLock <- struct{}{} }() // Ensure the device isn't screwed with while user confirmation is pending @@ -564,6 +589,7 @@ func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte if err != nil { return nil, err } + return signature, nil } @@ -600,6 +626,7 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID } // All infos gathered and metadata checks out, request signing <-w.commsLock + defer func() { w.commsLock <- struct{}{} }() // Ensure the device isn't screwed with while user confirmation is pending @@ -618,9 +645,11 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID if err != nil { return nil, err } + if sender != account.Address { return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) } + return signed, nil } diff --git a/beacon/engine/errors.go b/beacon/engine/errors.go index 49a1e099a8..25f46c38bc 100644 --- a/beacon/engine/errors.go +++ b/beacon/engine/errors.go @@ -35,6 +35,7 @@ func (e *EngineAPIError) ErrorData() interface{} { if e.err == nil { return nil } + return struct { Error string `json:"err"` }{e.err.Error()} diff --git a/beacon/engine/gen_blockparams.go b/beacon/engine/gen_blockparams.go index 0dd2b52597..6c40b6c1e9 100644 --- a/beacon/engine/gen_blockparams.go +++ b/beacon/engine/gen_blockparams.go @@ -21,11 +21,13 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) { SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` Withdrawals []*types.Withdrawal `json:"withdrawals"` } + var enc PayloadAttributes enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Random = p.Random enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.Withdrawals = p.Withdrawals + return json.Marshal(&enc) } @@ -37,24 +39,32 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` Withdrawals []*types.Withdrawal `json:"withdrawals"` } + var dec PayloadAttributes if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Timestamp == nil { return errors.New("missing required field 'timestamp' for PayloadAttributes") } + p.Timestamp = uint64(*dec.Timestamp) + if dec.Random == nil { return errors.New("missing required field 'prevRandao' for PayloadAttributes") } + p.Random = *dec.Random + if dec.SuggestedFeeRecipient == nil { return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributes") } + p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient if dec.Withdrawals != nil { p.Withdrawals = dec.Withdrawals } + return nil } diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index 336dfc6cc7..8e0241ae2b 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -33,6 +33,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` Withdrawals []*types.Withdrawal `json:"withdrawals"` } + var enc ExecutableData enc.ParentHash = e.ParentHash enc.FeeRecipient = e.FeeRecipient @@ -47,13 +48,16 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { enc.ExtraData = e.ExtraData enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas) enc.BlockHash = e.BlockHash + if e.Transactions != nil { enc.Transactions = make([]hexutil.Bytes, len(e.Transactions)) for k, v := range e.Transactions { enc.Transactions[k] = v } } + enc.Withdrawals = e.Withdrawals + return json.Marshal(&enc) } @@ -76,71 +80,102 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` Withdrawals []*types.Withdrawal `json:"withdrawals"` } + var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.ParentHash == nil { return errors.New("missing required field 'parentHash' for ExecutableData") } + e.ParentHash = *dec.ParentHash + if dec.FeeRecipient == nil { return errors.New("missing required field 'feeRecipient' for ExecutableData") } + e.FeeRecipient = *dec.FeeRecipient + if dec.StateRoot == nil { return errors.New("missing required field 'stateRoot' for ExecutableData") } + e.StateRoot = *dec.StateRoot + if dec.ReceiptsRoot == nil { return errors.New("missing required field 'receiptsRoot' for ExecutableData") } + e.ReceiptsRoot = *dec.ReceiptsRoot + if dec.LogsBloom == nil { return errors.New("missing required field 'logsBloom' for ExecutableData") } + e.LogsBloom = *dec.LogsBloom + if dec.Random == nil { return errors.New("missing required field 'prevRandao' for ExecutableData") } + e.Random = *dec.Random + if dec.Number == nil { return errors.New("missing required field 'blockNumber' for ExecutableData") } + e.Number = uint64(*dec.Number) + if dec.GasLimit == nil { return errors.New("missing required field 'gasLimit' for ExecutableData") } + e.GasLimit = uint64(*dec.GasLimit) + if dec.GasUsed == nil { return errors.New("missing required field 'gasUsed' for ExecutableData") } + e.GasUsed = uint64(*dec.GasUsed) + if dec.Timestamp == nil { return errors.New("missing required field 'timestamp' for ExecutableData") } + e.Timestamp = uint64(*dec.Timestamp) + if dec.ExtraData == nil { return errors.New("missing required field 'extraData' for ExecutableData") } + e.ExtraData = *dec.ExtraData + if dec.BaseFeePerGas == nil { return errors.New("missing required field 'baseFeePerGas' for ExecutableData") } + e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas) + if dec.BlockHash == nil { return errors.New("missing required field 'blockHash' for ExecutableData") } + e.BlockHash = *dec.BlockHash + if dec.Transactions == nil { return errors.New("missing required field 'transactions' for ExecutableData") } + e.Transactions = make([][]byte, len(dec.Transactions)) for k, v := range dec.Transactions { e.Transactions[k] = v } + if dec.Withdrawals != nil { e.Withdrawals = dec.Withdrawals } + return nil } diff --git a/beacon/engine/gen_epe.go b/beacon/engine/gen_epe.go index cc66cef6cd..214ac2d506 100644 --- a/beacon/engine/gen_epe.go +++ b/beacon/engine/gen_epe.go @@ -18,9 +18,11 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` } + var enc ExecutionPayloadEnvelope enc.ExecutionPayload = e.ExecutionPayload enc.BlockValue = (*hexutil.Big)(e.BlockValue) + return json.Marshal(&enc) } @@ -30,17 +32,23 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` } + var dec ExecutionPayloadEnvelope if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.ExecutionPayload == nil { return errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope") } + e.ExecutionPayload = dec.ExecutionPayload + if dec.BlockValue == nil { return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope") } + e.BlockValue = (*big.Int)(dec.BlockValue) + return nil } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 07ebe544b4..7e3f2577fe 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -115,6 +115,7 @@ func (b *PayloadID) UnmarshalText(input []byte) error { if err != nil { return fmt.Errorf("invalid payload id %q: %w", input, err) } + return nil } @@ -134,18 +135,22 @@ func encodeTransactions(txs []*types.Transaction) [][]byte { for i, tx := range txs { enc[i], _ = tx.MarshalBinary() } + return enc } func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { var txs = make([]*types.Transaction, len(enc)) + for i, encTx := range enc { var tx types.Transaction if err := tx.UnmarshalBinary(encTx); err != nil { return nil, fmt.Errorf("invalid transaction %d: %v", i, err) } + txs[i] = &tx } + return txs, nil } @@ -164,9 +169,11 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { if err != nil { return nil, err } + if len(params.ExtraData) > 32 { return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) } + if len(params.LogsBloom) != 256 { return nil, fmt.Errorf("invalid logsBloom length: %v", len(params.LogsBloom)) } @@ -178,10 +185,12 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { // ExecutableData before withdrawals are enabled by marshaling // Withdrawals as the json null value. var withdrawalsRoot *common.Hash + if params.Withdrawals != nil { h := types.DeriveSha(types.Withdrawals(params.Withdrawals), trie.NewStackTrie(nil)) withdrawalsRoot = &h } + header := &types.Header{ ParentHash: params.ParentHash, UncleHash: types.EmptyUncleHash, @@ -200,10 +209,12 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { MixDigest: params.Random, WithdrawalsHash: withdrawalsRoot, } + block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals) if block.Hash() != params.BlockHash { return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) } + return block, nil } @@ -227,6 +238,7 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadE ExtraData: block.Extra(), Withdrawals: block.Withdrawals(), } + return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees} } diff --git a/cmd/abidump/main.go b/cmd/abidump/main.go index ae1ac64139..616152c18e 100644 --- a/cmd/abidump/main.go +++ b/cmd/abidump/main.go @@ -41,8 +41,10 @@ func parse(data []byte) { if err != nil { die(err) } + messages := apitypes.ValidationMessages{} db.ValidateCallData(nil, data, &messages) + for _, m := range messages.Messages { fmt.Printf("%v: %v\n", m.Typ, m.Message) } @@ -56,10 +58,12 @@ func main() { switch { case flag.NArg() == 1: hexdata := flag.Arg(0) + data, err := hex.DecodeString(strings.TrimPrefix(hexdata, "0x")) if err != nil { die(err) } + parse(data) default: fmt.Fprintln(os.Stderr, "Error: one argument needed") diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index a98bfd2c7f..15d98ab798 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -99,6 +99,7 @@ func abigen(c *cli.Context) error { if c.String(pkgFlag.Name) == "" { utils.Fatalf("No destination package specified (--pkg)") } + var lang bind.Lang switch c.String(langFlag.Name) { @@ -116,6 +117,7 @@ func abigen(c *cli.Context) error { libs = make(map[string]string) aliases = make(map[string]string) ) + if c.String(abiFlag.Name) != "" { // Load up the ABI, optional bytecode and type name from the parameters var ( @@ -129,9 +131,11 @@ func abigen(c *cli.Context) error { } else { abi, err = os.ReadFile(input) } + if err != nil { utils.Fatalf("Failed to read input ABI: %v", err) } + abis = append(abis, string(abi)) var bin []byte @@ -140,26 +144,31 @@ func abigen(c *cli.Context) error { if bin, err = os.ReadFile(binFile); err != nil { utils.Fatalf("Failed to read input bytecode: %v", err) } + if strings.Contains(string(bin), "//") { utils.Fatalf("Contract has additional library references, please use other mode(e.g. --combined-json) to catch library infos") } } + bins = append(bins, string(bin)) kind := c.String(typeFlag.Name) if kind == "" { kind = c.String(pkgFlag.Name) } + types = append(types, kind) } else { // Generate the list of types to exclude from binding var exclude *nameFilter + if c.IsSet(excFlag.Name) { var err error if exclude, err = newNameFilter(strings.Split(c.String(excFlag.Name), ",")...); err != nil { utils.Fatalf("Failed to parse excludes: %v", err) } } + var contracts map[string]*compiler.Contract if c.IsSet(jsonFlag.Name) { @@ -168,14 +177,17 @@ func abigen(c *cli.Context) error { jsonOutput []byte err error ) + if input == "-" { jsonOutput, err = io.ReadAll(os.Stdin) } else { jsonOutput, err = os.ReadFile(input) } + if err != nil { utils.Fatalf("Failed to read combined-json: %v", err) } + contracts, err = compiler.ParseCombinedJSON(jsonOutput, "", "", "", "") if err != nil { utils.Fatalf("Failed to read contract information from json output: %v", err) @@ -186,14 +198,17 @@ func abigen(c *cli.Context) error { // fully qualified name is of the form : nameParts := strings.Split(name, ":") typeName := nameParts[len(nameParts)-1] + if exclude != nil && exclude.Matches(name) { fmt.Fprintf(os.Stderr, "excluding: %v\n", name) continue } + abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse if err != nil { utils.Fatalf("Failed to parse ABIs from compiler output: %v", err) } + abis = append(abis, string(abi)) bins = append(bins, contract.Code) sigs = append(sigs, contract.Hashes) @@ -214,6 +229,7 @@ func abigen(c *cli.Context) error { // foo=bar,foo2=bar2 // foo:bar,foo2:bar2 re := regexp.MustCompile(`(?:(\w+)[:=](\w+))`) + submatches := re.FindAllStringSubmatch(c.String(aliasFlag.Name), -1) for _, match := range submatches { aliases[match[1]] = match[2] @@ -233,6 +249,7 @@ func abigen(c *cli.Context) error { if err := os.WriteFile(c.String(outFlag.Name), []byte(code), 0600); err != nil { utils.Fatalf("Failed to write ABI binding: %v", err) } + return nil } diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index 748113aa48..ea3cf974cd 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -49,6 +49,7 @@ func main() { nodeKey *ecdsa.PrivateKey err error ) + flag.Parse() glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) @@ -60,15 +61,18 @@ func main() { if err != nil { utils.Fatalf("-nat: %v", err) } + switch { case *genKey != "": nodeKey, err = crypto.GenerateKey() if err != nil { utils.Fatalf("could not generate key: %v", err) } + if err = crypto.SaveECDSA(*genKey, nodeKey); err != nil { utils.Fatalf("%v", err) } + if !*writeAddr { return } @@ -103,6 +107,7 @@ func main() { if err != nil { utils.Fatalf("-ResolveUDPAddr: %v", err) } + conn, err := net.ListenUDP("udp", addr) if err != nil { utils.Fatalf("-ListenUDP: %v", err) @@ -113,6 +118,7 @@ func main() { if !realaddr.IP.IsLoopback() { go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") } + if ext, err := natm.ExternalIP(); err == nil { realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port} } @@ -126,6 +132,7 @@ func main() { PrivateKey: nodeKey, NetRestrict: restrictList, } + if *runv5 { if _, err := discover.ListenV5(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) @@ -143,6 +150,7 @@ func printNotice(nodeKey *ecdsa.PublicKey, addr net.UDPAddr) { if addr.IP.IsUnspecified() { addr.IP = net.IP{127, 0, 0, 1} } + n := enode.NewV4(nodeKey, addr.IP, 0, addr.Port) fmt.Println(n.URLv4()) fmt.Println("Note: you're using cmd/bootnode, a developer tool.") diff --git a/cmd/checkpoint-admin/common.go b/cmd/checkpoint-admin/common.go index 82e246a269..c1da32d8c7 100644 --- a/cmd/checkpoint-admin/common.go +++ b/cmd/checkpoint-admin/common.go @@ -17,6 +17,7 @@ package main import ( + "github.com/urfave/cli/v2" "strconv" "github.com/ethereum/go-ethereum/accounts" @@ -36,6 +37,7 @@ func newClient(ctx *cli.Context) *ethclient.Client { if err != nil { utils.Fatalf("Failed to connect to Ethereum node: %v", err) } + return client } @@ -45,6 +47,7 @@ func newRPCClient(url string) *rpc.Client { if err != nil { utils.Fatalf("Failed to connect to Ethereum node: %v", err) } + return client } @@ -55,6 +58,7 @@ func getContractAddr(client *rpc.Client) common.Address { if err := client.Call(&addr, "les_getCheckpointContractAddress"); err != nil { utils.Fatalf("Failed to fetch checkpoint oracle address: %v", err) } + return common.HexToAddress(addr) } @@ -70,6 +74,7 @@ func getCheckpoint(ctx *cli.Context, client *rpc.Client) *params.TrustedCheckpoi if err := client.Call(&result, "les_getCheckpoint", index); err != nil { utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err) } + checkpoint = ¶ms.TrustedCheckpoint{ SectionIndex: index, SectionHead: common.HexToHash(result[0]), @@ -78,14 +83,17 @@ func getCheckpoint(ctx *cli.Context, client *rpc.Client) *params.TrustedCheckpoi } } else { var result [4]string + err := client.Call(&result, "les_latestCheckpoint") if err != nil { utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err) } + index, err := strconv.ParseUint(result[0], 0, 64) if err != nil { utils.Fatalf("Failed to parse checkpoint index %v", err) } + checkpoint = ¶ms.TrustedCheckpoint{ SectionIndex: index, SectionHead: common.HexToHash(result[1]), @@ -93,6 +101,7 @@ func getCheckpoint(ctx *cli.Context, client *rpc.Client) *params.TrustedCheckpoi BloomRoot: common.HexToHash(result[3]), } } + return checkpoint } @@ -103,10 +112,12 @@ func newContract(client *rpc.Client) (common.Address, *checkpointoracle.Checkpoi if addr == (common.Address{}) { utils.Fatalf("No specified registrar contract address") } + contract, err := checkpointoracle.NewCheckpointOracle(addr, ethclient.NewClient(client)) if err != nil { utils.Fatalf("Failed to setup registrar contract %s: %v", addr, err) } + return addr, contract } @@ -116,5 +127,6 @@ func newClefSigner(ctx *cli.Context) *bind.TransactOpts { if err != nil { utils.Fatalf("Failed to create clef signer %v", err) } + return bind.NewClefTransactor(clef, accounts.Account{Address: common.HexToAddress(ctx.String(signerFlag.Name))}) } diff --git a/cmd/checkpoint-admin/exec.go b/cmd/checkpoint-admin/exec.go index dae4301431..84c00ec0eb 100644 --- a/cmd/checkpoint-admin/exec.go +++ b/cmd/checkpoint-admin/exec.go @@ -87,10 +87,12 @@ var commandPublish = &cli.Command{ func deploy(ctx *cli.Context) error { // Gather all the addresses that should be permitted to sign var addrs []common.Address + for _, account := range strings.Split(ctx.String(signersFlag.Name), ",") { if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) { utils.Fatalf("Invalid account in --signers: '%s'", trimmed) } + addrs = append(addrs, common.HexToAddress(account)) } // Retrieve and validate the signing threshold @@ -100,9 +102,11 @@ func deploy(ctx *cli.Context) error { } // Print a summary to ensure the user understands what they're signing fmt.Printf("Deploying new checkpoint oracle:\n\n") + for i, addr := range addrs { fmt.Printf("Admin %d => %s\n", i+1, addr.Hex()) } + fmt.Printf("\nSignatures needed to publish: %d\n", needed) // setup clef signer, create an abigen transactor and an RPC client @@ -110,11 +114,13 @@ func deploy(ctx *cli.Context) error { // Deploy the checkpoint oracle fmt.Println("Sending deploy request to Clef...") + oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)), big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed))) if err != nil { utils.Fatalf("Failed to deploy checkpoint oracle %v", err) } + log.Info("Deployed checkpoint oracle", "address", oracle, "tx", tx.Hash().Hex()) return nil @@ -133,22 +139,27 @@ func sign(ctx *cli.Context) error { node *rpc.Client oracle *checkpointoracle.CheckpointOracle ) + if !ctx.IsSet(nodeURLFlag.Name) { // Offline mode signing offline = true + if !ctx.IsSet(hashFlag.Name) { utils.Fatalf("Please specify the checkpoint hash (--hash) to sign in offline mode") } + chash = common.HexToHash(ctx.String(hashFlag.Name)) if !ctx.IsSet(indexFlag.Name) { utils.Fatalf("Please specify checkpoint index (--index) to sign in offline mode") } + cindex = ctx.Uint64(indexFlag.Name) if !ctx.IsSet(oracleFlag.Name) { utils.Fatalf("Please specify oracle address (--oracle) to sign in offline mode") } + address = common.HexToAddress(ctx.String(oracleFlag.Name)) } else { // Interactive mode signing, retrieve the data from the remote node @@ -165,22 +176,28 @@ func sign(ctx *cli.Context) error { if err != nil { return err } + num := head.Number.Uint64() if num < ((cindex+1)*params.CheckpointFrequency + params.CheckpointProcessConfirmations) { utils.Fatalf("Invalid future checkpoint") } + _, oracle = newContract(node) + latest, _, h, err := oracle.Contract().GetLatestCheckpoint(nil) if err != nil { return err } + if cindex < latest { utils.Fatalf("Checkpoint is too old") } + if cindex == latest && (latest != 0 || h.Uint64() != 0) { utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest, cindex) } } + var ( signature string signer string @@ -191,11 +208,13 @@ func sign(ctx *cli.Context) error { if err != nil { return err } + for _, s := range signers { if s == addr { return nil } } + return fmt.Errorf("signer %v is not the admin", addr.Hex()) } // Print to the user the data thy are about to sign @@ -210,19 +229,24 @@ func sign(ctx *cli.Context) error { return err } } + clef := newRPCClient(ctx.String(clefURLFlag.Name)) p := make(map[string]string) buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, cindex) + p["address"] = address.Hex() p["message"] = hexutil.Encode(append(buf, chash.Bytes()...)) fmt.Println("Sending signing request to Clef...") + if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil { utils.Fatalf("Failed to sign checkpoint, err %v", err) } + fmt.Printf("Signer => %s\n", signer) fmt.Printf("Signature => %s\n", signature) + return nil } @@ -232,6 +256,7 @@ func sighash(index uint64, oracle common.Address, hash common.Hash) []byte { binary.BigEndian.PutUint64(buf, index) data := append([]byte{0x19, 0x00}, append(oracle[:], append(buf, hash[:]...)...)...) + return crypto.Keccak256(data) } @@ -244,6 +269,7 @@ func ecrecover(sighash []byte, sig []byte) common.Address { if err != nil { utils.Fatalf("Failed to recover sender from signature %x: %v", sig, err) } + return crypto.PubkeyToAddress(*signer) } @@ -256,6 +282,7 @@ func publish(ctx *cli.Context) error { // Gather the signatures from the CLI var sigs [][]byte + for _, sig := range strings.Split(ctx.String(signaturesFlag.Name), ",") { trimmed := strings.TrimPrefix(strings.TrimSpace(sig), "0x") if len(trimmed) != 130 { @@ -271,10 +298,12 @@ func publish(ctx *cli.Context) error { checkpoint = getCheckpoint(ctx, client) sighash = sighash(checkpoint.SectionIndex, addr, checkpoint.Hash()) ) + for i := 0; i < len(sigs); i++ { for j := i + 1; j < len(sigs); j++ { signerA := ecrecover(sighash, sigs[i]) signerB := ecrecover(sighash, sigs[j]) + if bytes.Compare(signerA.Bytes(), signerB.Bytes()) > 0 { sigs[i], sigs[j] = sigs[j], sigs[i] } @@ -288,25 +317,32 @@ func publish(ctx *cli.Context) error { if err != nil { return err } + num := head.Number.Uint64() + recent, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, big.NewInt(int64(num-128))) if err != nil { return err } // Print a summary of the operation that's going to be performed fmt.Printf("Publishing %d => %s:\n\n", checkpoint.SectionIndex, checkpoint.Hash().Hex()) + for i, sig := range sigs { fmt.Printf("Signer %d => %s\n", i+1, ecrecover(sighash, sig).Hex()) } + fmt.Println() fmt.Printf("Sentry number => %d\nSentry hash => %s\n", recent.Number, recent.Hash().Hex()) // Publish the checkpoint into the oracle fmt.Println("Sending publish request to Clef...") + tx, err := oracle.RegisterCheckpoint(newClefSigner(ctx), checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs) if err != nil { utils.Fatalf("Register contract failed %v", err) } + log.Info("Successfully registered checkpoint", "tx", tx.Hash().Hex()) + return nil } diff --git a/cmd/checkpoint-admin/status.go b/cmd/checkpoint-admin/status.go index d723531a4b..4d8b841946 100644 --- a/cmd/checkpoint-admin/status.go +++ b/cmd/checkpoint-admin/status.go @@ -45,9 +45,11 @@ func status(ctx *cli.Context) error { if err != nil { return err } + for i, admin := range admins { fmt.Printf("Admin %d => %s\n", i+1, admin.Hex()) } + fmt.Println() // Retrieve the latest checkpoint @@ -55,6 +57,7 @@ func status(ctx *cli.Context) error { if err != nil { return err } + fmt.Printf("Checkpoint (published at #%d) %d => %s\n", height, index, common.Hash(checkpoint).Hex()) return nil diff --git a/cmd/clef/consolecmd_test.go b/cmd/clef/consolecmd_test.go index b5cc1209c6..498bde959f 100644 --- a/cmd/clef/consolecmd_test.go +++ b/cmd/clef/consolecmd_test.go @@ -37,6 +37,7 @@ func TestImportRaw(t *testing.T) { // Run clef importraw clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath) clef.input("myverylongpassword").input("myverylongpassword") + if out := string(clef.Output()); !strings.Contains(out, "Key imported:\n Address 0x9160DC9105f7De5dC5E7f3d97ef11DA47269BdA6") { t.Logf("Output\n%v", out) @@ -49,6 +50,7 @@ func TestImportRaw(t *testing.T) { // Run clef importraw clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath) clef.input("myverylongpassword1").input("myverylongpassword2").WaitExit() + if have, want := clef.StderrText(), "Passwords do not match\n"; have != want { t.Errorf("have %q, want %q", have, want) } @@ -59,6 +61,7 @@ func TestImportRaw(t *testing.T) { // Run clef importraw clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath) clef.input("shorty").input("shorty").WaitExit() + if have, want := clef.StderrText(), "password requirements not met: password too short (<10 characters)\n"; have != want { t.Errorf("have %q, want %q", have, want) @@ -76,6 +79,7 @@ func TestListAccounts(t *testing.T) { t.Run("no-accounts", func(t *testing.T) { t.Parallel() + clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-accounts") if out := string(clef.Output()); !strings.Contains(out, "The keystore is empty.") { t.Logf("Output\n%v", out) @@ -106,6 +110,7 @@ func TestListWallets(t *testing.T) { t.Run("no-accounts", func(t *testing.T) { t.Parallel() + clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-wallets") if out := string(clef.Output()); !strings.Contains(out, "There are no wallets.") { t.Logf("Output\n%v", out) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 58530be186..4380617573 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -315,26 +315,33 @@ func initializeSecrets(c *cli.Context) error { if err := os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) { return err } + location := filepath.Join(configDir, "masterseed.json") if _, err := os.Stat(location); err == nil { return fmt.Errorf("master key %v already exists, will not overwrite", location) } // Key file does not exist yet, generate a new one and encrypt it masterSeed := make([]byte, 256) + num, err := io.ReadFull(rand.Reader, masterSeed) if err != nil { return err } + if num != len(masterSeed) { return fmt.Errorf("failed to read enough random") } + n, p := keystore.StandardScryptN, keystore.StandardScryptP if c.Bool(utils.LightKDFFlag.Name) { n, p = keystore.LightScryptN, keystore.LightScryptP } + text := "The master seed of clef will be locked with a password.\nPlease specify a password. Do not forget this password!" + var password string + for { password = utils.GetPassPhrase(text, true) if err := core.ValidatePasswordFormat(password); err != nil { @@ -344,6 +351,7 @@ func initializeSecrets(c *cli.Context) error { break } } + cipherSeed, err := encryptSeed(masterSeed, []byte(password), n, p) if err != nil { return fmt.Errorf("failed to encrypt master seed: %v", err) @@ -352,6 +360,7 @@ func initializeSecrets(c *cli.Context) error { if err = os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) { return err } + if _, err := os.Stat(location); err == nil { return fmt.Errorf("master key %v already exists, will not overwrite", location) } @@ -359,6 +368,7 @@ func initializeSecrets(c *cli.Context) error { if err = os.WriteFile(location, cipherSeed, 0400); err != nil { return err } + fmt.Printf("A master seed has been generated into %s\n", location) fmt.Printf(` This is required to be able to store credentials, such as: @@ -371,6 +381,7 @@ You should treat 'masterseed.json' with utmost secrecy and make a backup of it! * The master seed does not contain your accounts, those need to be backed up separately! `) + return nil } @@ -378,6 +389,7 @@ func attestFile(ctx *cli.Context) error { if ctx.NArg() < 1 { utils.Fatalf("This command requires an argument.") } + if err := initialize(ctx); err != nil { return err } @@ -396,6 +408,7 @@ func attestFile(ctx *cli.Context) error { val := ctx.Args().First() configStorage.Put("ruleset_sha256", val) log.Info("Ruleset attestation updated", "sha256", val) + return nil } @@ -422,15 +435,19 @@ func setCredential(ctx *cli.Context) error { if ctx.NArg() < 1 { utils.Fatalf("This command requires an address to be passed as an argument") } + if err := initialize(ctx); err != nil { return err } + addr := ctx.Args().First() if !common.IsHexAddress(addr) { utils.Fatalf("Invalid address specified: %s", addr) } + address := common.HexToAddress(addr) password := utils.GetPassPhrase("Please enter a password to store for this address:", true) + fmt.Println() stretchedKey, err := readMasterKey(ctx, nil) @@ -446,6 +463,7 @@ func setCredential(ctx *cli.Context) error { pwStorage.Put(address.Hex(), password) log.Info("Credential store updated", "set", address) + return nil } @@ -453,13 +471,16 @@ func removeCredential(ctx *cli.Context) error { if ctx.NArg() < 1 { utils.Fatalf("This command requires an address to be passed as an argument") } + if err := initialize(ctx); err != nil { return err } + addr := ctx.Args().First() if !common.IsHexAddress(addr) { utils.Fatalf("Invalid address specified: %s", addr) } + address := common.HexToAddress(addr) stretchedKey, err := readMasterKey(ctx, nil) @@ -475,6 +496,7 @@ func removeCredential(ctx *cli.Context) error { pwStorage.Del(address.Hex()) log.Info("Credential store updated", "unset", address) + return nil } @@ -491,13 +513,17 @@ func initialize(c *cli.Context) error { if !confirm(legalWarning) { return fmt.Errorf("aborted by user") } + fmt.Println() } + usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" output := io.Writer(logOutput) + if usecolor { output = colorable.NewColorable(logOutput) } + log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(output, log.TerminalFormat(usecolor)))) return nil @@ -644,6 +670,7 @@ func ipcEndpoint(ipcPath, datadir string) string { if strings.HasPrefix(ipcPath, `\\.\pipe\`) { return ipcPath } + return `\\.\pipe\` + ipcPath } // Resolve names into the data directory full paths otherwise @@ -651,8 +678,10 @@ func ipcEndpoint(ipcPath, datadir string) string { if datadir == "" { return filepath.Join(os.TempDir(), ipcPath) } + return filepath.Join(datadir, ipcPath) } + return ipcPath } @@ -661,26 +690,32 @@ func signer(c *cli.Context) error { if c.NArg() > 0 { return fmt.Errorf("invalid command: %q", c.Args().First()) } + if err := initialize(c); err != nil { return err } + var ( ui core.UIClientAPI ) if c.Bool(stdiouiFlag.Name) { log.Info("Using stdin/stdout as UI-channel") + ui = core.NewStdIOUI() } else { log.Info("Using CLI as UI-channel") + ui = core.NewCommandlineUI() } // 4bytedb data fourByteLocal := c.String(customDBFlag.Name) + db, err := fourbyte.NewWithFile(fourByteLocal) if err != nil { utils.Fatalf(err.Error()) } + embeds, locals := db.Size() log.Info("Loaded 4byte database", "embeds", embeds, "locals", locals, "local", fourByteLocal) @@ -690,6 +725,7 @@ func signer(c *cli.Context) error { ) configDir := c.String(configdirFlag.Name) + if stretchedKey, err := readMasterKey(c, ui); err != nil { log.Warn("Failed to open master, rules disabled", "err", err) } else { @@ -714,6 +750,7 @@ func signer(c *cli.Context) error { shasum := sha256.Sum256(ruleJS) foundShaSum := hex.EncodeToString(shasum[:]) storedShasum, _ := configStorage.Get("ruleset_sha256") + if storedShasum != foundShaSum { log.Warn("Rule hash not attested, disabling", "hash", foundShaSum, "attested", storedShasum) } else { @@ -722,13 +759,16 @@ func signer(c *cli.Context) error { if err != nil { utils.Fatalf(err.Error()) } + ruleEngine.Init(string(ruleJS)) ui = ruleEngine + log.Info("Rule engine configured", "file", c.String(ruleFlag.Name)) } } } } + var ( chainId = c.Int64(chainIdFlag.Name) ksLoc = c.String(keystoreFlag.Name) @@ -737,8 +777,10 @@ func signer(c *cli.Context) error { nousb = c.Bool(utils.NoUSBFlag.Name) scpath = c.String(utils.SmartCardDaemonPathFlag.Name) ) + log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc, "light-kdf", lightKdf, "advanced", advanced) + am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath) apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage) @@ -753,6 +795,7 @@ func signer(c *cli.Context) error { if err != nil { utils.Fatalf(err.Error()) } + log.Info("Audit logs configured", "file", logfile) } // register signer API with server @@ -760,6 +803,7 @@ func signer(c *cli.Context) error { extapiURL = "n/a" ipcapiURL = "n/a" ) + rpcAPI := []rpc.API{ { Namespace: "account", @@ -772,10 +816,12 @@ func signer(c *cli.Context) error { cors := utils.SplitAndTrim(c.String(utils.HTTPCORSDomainFlag.Name)) srv := rpc.NewServer(0, 0) + err := node.RegisterApis(rpcAPI, []string{"account"}, srv) if err != nil { utils.Fatalf("Could not register API: %w", err) } + handler := node.NewHTTPHandlerStack(srv, cors, vhosts, nil) // set port @@ -783,10 +829,12 @@ func signer(c *cli.Context) error { // start http server httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.HTTPListenAddrFlag.Name), port) + httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler) if err != nil { utils.Fatalf("Could not start RPC api: %v", err) } + extapiURL = fmt.Sprintf("http://%v/", addr) log.Info("HTTP endpoint opened", "url", extapiURL) @@ -800,11 +848,14 @@ func signer(c *cli.Context) error { if !c.Bool(utils.IPCDisabledFlag.Name) { givenPath := c.String(utils.IPCPathFlag.Name) ipcapiURL = ipcEndpoint(filepath.Join(givenPath, "clef.ipc"), configDir) + listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI) if err != nil { utils.Fatalf("Could not start IPC api: %v", err) } + log.Info("IPC endpoint opened", "url", ipcapiURL) + defer func() { listener.Close() log.Info("IPC endpoint closed", "url", ipcapiURL) @@ -813,8 +864,10 @@ func signer(c *cli.Context) error { if c.Bool(testFlag.Name) { log.Info("Performing UI test") + go testExternalUI(apiImpl) } + ui.OnSignerStartup(core.StartupInfo{ Info: map[string]interface{}{ "intapi_version": core.InternalAPIVersion, @@ -845,8 +898,10 @@ func DefaultConfigDir() string { if appdata != "" { return filepath.Join(appdata, "Signer") } + return filepath.Join(home, "AppData", "Roaming", "Signer") } + return filepath.Join(home, ".clef") } // As we cannot guess a stable location, return empty and handle later @@ -864,6 +919,7 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { } else { file = filepath.Join(configDir, "masterseed.json") } + if err := checkFile(file); err != nil { return nil, err } @@ -872,6 +928,7 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { if err != nil { return nil, err } + var password string // If ui is not nil, get the password from ui. if ui != nil { @@ -882,23 +939,28 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { if err != nil { return nil, err } + password = resp.Text } else { password = utils.GetPassPhrase("Decrypt master seed of clef", false) } + masterSeed, err := decryptSeed(cipherKey, password) if err != nil { return nil, fmt.Errorf("failed to decrypt the master seed of clef") } + if len(masterSeed) < 256 { return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed)) } // Create vault location vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10])) + err = os.Mkdir(vaultLocation, 0700) if err != nil && !os.IsExist(err) { return nil, err } + return masterSeed, nil } @@ -916,6 +978,7 @@ func checkFile(filename string) error { 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 } @@ -928,9 +991,11 @@ func confirm(text string) bool { if err != nil { log.Crit("Failed to read user input", "err", err) } + if text := strings.TrimSpace(text); text == "ok" { return true } + return false } @@ -954,6 +1019,7 @@ func testExternalUI(api *core.SignerAPI) { if err != nil { addErr(err.Error()) } + return resp.Text } expectResponse := func(testcase, question, expect string) { @@ -965,6 +1031,7 @@ func testExternalUI(api *core.SignerAPI) { if err == nil || err == accounts.ErrUnknownAccount { return } + addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error())) } expectDeny := func(testcase string, err error) { @@ -972,6 +1039,7 @@ func testExternalUI(api *core.SignerAPI) { addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err)) } } + var delay = 1 * time.Second // Test display of info and error { @@ -985,6 +1053,7 @@ func testExternalUI(api *core.SignerAPI) { { // Sign data test - clique header api.UI.ShowInfo("Please approve the next request for signing a clique header") time.Sleep(delay) + cliqueHeader := types.Header{ ParentHash: common.HexToHash("0000H45H"), UncleHash: common.HexToHash("0000H45H"), @@ -1000,10 +1069,12 @@ func testExternalUI(api *core.SignerAPI) { Extra: []byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"), MixDigest: common.HexToHash("0x0000H45H"), } + cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader) if err != nil { utils.Fatalf("Should not error: %v", err) } + addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") _, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp)) expectApprove("signdata - clique header", err) @@ -1011,10 +1082,12 @@ func testExternalUI(api *core.SignerAPI) { { // Sign data test - typed data api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data") time.Sleep(delay) + addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") data := `{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"test","type":"uint8"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"1","verifyingContract":"0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","test":"3","wallet":"0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","test":"2"},"contents":"Hello, Bob!"}}` //_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data))) var typedData apitypes.TypedData + json.Unmarshal([]byte(data), &typedData) _, err := api.SignTypedData(ctx, *addr, typedData) expectApprove("sign 712 typed data", err) @@ -1022,6 +1095,7 @@ func testExternalUI(api *core.SignerAPI) { { // Sign data test - plain text api.UI.ShowInfo("Please approve the next request for signing text") time.Sleep(delay) + addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) expectApprove("signdata - text", err) @@ -1029,6 +1103,7 @@ func testExternalUI(api *core.SignerAPI) { { // Sign data test - plain text reject api.UI.ShowInfo("Please deny the next request for signing text") time.Sleep(delay) + addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) expectDeny("signdata - text", err) @@ -1036,6 +1111,7 @@ func testExternalUI(api *core.SignerAPI) { { // Sign transaction api.UI.ShowInfo("Please reject next transaction") time.Sleep(delay) + data := hexutil.Bytes([]byte{}) to := common.NewMixedcaseAddress(a) tx := apitypes.SendTxArgs{ @@ -1055,12 +1131,14 @@ func testExternalUI(api *core.SignerAPI) { { // Listing api.UI.ShowInfo("Please reject listing-request") time.Sleep(delay) + _, err := api.List(ctx) expectDeny("list", err) } { // Import api.UI.ShowInfo("Please reject new account-request") time.Sleep(delay) + _, err := api.New(ctx) expectDeny("newaccount", err) } @@ -1074,6 +1152,7 @@ func testExternalUI(api *core.SignerAPI) { for _, e := range errs { log.Error(e) } + result := fmt.Sprintf("Tests completed. %d errors:\n%s\n", len(errs), strings.Join(errs, "\n")) api.UI.ShowInfo(result) } @@ -1091,6 +1170,7 @@ func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) if err != nil { return nil, err } + return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct}) } @@ -1100,13 +1180,16 @@ func decryptSeed(keyjson []byte, auth string) ([]byte, error) { if err := json.Unmarshal(keyjson, &encSeed); err != nil { return nil, err } + if encSeed.Version != 1 { log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version)) } + seed, err := keystore.DecryptDataV3(encSeed.Params, auth) if err != nil { return nil, err } + return seed, err } @@ -1218,7 +1301,9 @@ func GenDoc(ctx *cli.Context) error { "The `OnApproved` method cannot be responded to, it's purely informative" rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed") + var tx types.Transaction + tx.UnmarshalBinary(rlpdata) add("OnApproved - SignTransactionResult", desc, ðapi.SignTransactionResult{Raw: rlpdata, Tx: &tx}) } @@ -1256,6 +1341,7 @@ func GenDoc(ctx *cli.Context) error { fmt.Println(`## UI Client interface These data types are defined in the channel between clef and the UI`) + for _, elem := range output { fmt.Println(elem) } diff --git a/cmd/clef/run_test.go b/cmd/clef/run_test.go index a491260a3d..2bac0bdb6c 100644 --- a/cmd/clef/run_test.go +++ b/cmd/clef/run_test.go @@ -42,6 +42,7 @@ func init() { fmt.Fprintln(os.Stderr, err) os.Exit(1) } + os.Exit(0) }) } diff --git a/cmd/devp2p/crawl.go b/cmd/devp2p/crawl.go index b2ed37fa2d..e13fa1e284 100644 --- a/cmd/devp2p/crawl.go +++ b/cmd/devp2p/crawl.go @@ -67,6 +67,7 @@ func newCrawler(input nodeSet, disc resolver, iters ...enode.Iterator) *crawler for id, n := range input { c.output[id] = n } + return c } @@ -82,8 +83,10 @@ func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet { if nthreads < 1 { nthreads = 1 } + defer timeoutTimer.Stop() defer statusTicker.Stop() + for _, it := range c.iters { go c.runIterator(doneCh, it) } @@ -152,18 +155,22 @@ loop: } close(c.closed) + for _, it := range c.iters { it.Close() } + for ; liveIters > 0; liveIters-- { <-doneCh } wg.Wait() + return c.output } func (c *crawler) runIterator(done chan<- enode.Iterator, it enode.Iterator) { defer func() { done <- it }() + for it.Next() { select { case c.ch <- it.Node(): @@ -195,20 +202,24 @@ func (c *crawler) updateNode(n *enode.Node) int { log.Debug("Skipping node", "id", n.ID()) return nodeSkipIncompat } + node.Score /= 2 } else { node.N = nn node.Seq = nn.Seq() node.Score++ + if node.FirstResponse.IsZero() { node.FirstResponse = node.LastCheck status = nodeAdded } + node.LastResponse = node.LastCheck } // Store/update node in output set. c.mu.Lock() defer c.mu.Unlock() + if node.Score <= 0 { log.Debug("Removing node", "id", n.ID()) delete(c.output, n.ID()) diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 5a25e46b5c..bc78557448 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -143,19 +143,24 @@ var discoveryNodeFlags = []cli.Flag{ func discv4Ping(ctx *cli.Context) error { n := getNodeArg(ctx) + disc := startV4(ctx) defer disc.Close() start := time.Now() + if err := disc.Ping(n); err != nil { return fmt.Errorf("node didn't respond: %v", err) } + fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start)) + return nil } func discv4RequestRecord(ctx *cli.Context) error { n := getNodeArg(ctx) + disc := startV4(ctx) defer disc.Close() @@ -163,16 +168,20 @@ func discv4RequestRecord(ctx *cli.Context) error { if err != nil { return fmt.Errorf("can't retrieve record: %v", err) } + fmt.Println(respN.String()) + return nil } func discv4Resolve(ctx *cli.Context) error { n := getNodeArg(ctx) + disc := startV4(ctx) defer disc.Close() fmt.Println(disc.Resolve(n).String()) + return nil } @@ -180,19 +189,23 @@ func discv4ResolveJSON(ctx *cli.Context) error { if ctx.NArg() < 1 { return fmt.Errorf("need nodes file as argument") } + nodesFile := ctx.Args().Get(0) inputSet := make(nodeSet) + if common.FileExist(nodesFile) { inputSet = loadNodesJSON(nodesFile) } // Add extra nodes from command line arguments. var nodeargs []*enode.Node + for i := 1; i < ctx.NArg(); i++ { n, err := parseNode(ctx.Args().Get(i)) if err != nil { exit(err) } + nodeargs = append(nodeargs, n) } @@ -203,6 +216,7 @@ func discv4ResolveJSON(ctx *cli.Context) error { c.revalidateInterval = 0 output := c.run(0, 1) writeNodesJSON(nodesFile, output) + return nil } @@ -210,8 +224,11 @@ func discv4Crawl(ctx *cli.Context) error { if ctx.NArg() < 1 { return fmt.Errorf("need nodes file as argument") } + nodesFile := ctx.Args().First() + var inputSet nodeSet + if common.FileExist(nodesFile) { inputSet = loadNodesJSON(nodesFile) } @@ -222,6 +239,7 @@ func discv4Crawl(ctx *cli.Context) error { c.revalidateInterval = 10 * time.Minute output := c.run(ctx.Duration(crawlTimeoutFlag.Name), ctx.Int(crawlParallelismFlag.Name)) writeNodesJSON(nodesFile, output) + return nil } @@ -231,9 +249,11 @@ func discv4Test(ctx *cli.Context) error { if !ctx.IsSet(remoteEnodeFlag.Name) { return fmt.Errorf("Missing -%v", remoteEnodeFlag.Name) } + v4test.Remote = ctx.String(remoteEnodeFlag.Name) v4test.Listen1 = ctx.String(testListen1Flag.Name) v4test.Listen2 = ctx.String(testListen2Flag.Name) + return runTests(ctx, v4test.AllTests) } @@ -241,10 +261,12 @@ func discv4Test(ctx *cli.Context) error { func startV4(ctx *cli.Context) *discover.UDPv4 { ln, config := makeDiscoveryConfig(ctx) socket := listen(ctx, ln) + disc, err := discover.ListenV4(socket, ln, config) if err != nil { exit(err) } + return disc } @@ -256,6 +278,7 @@ func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) { if err != nil { exit(fmt.Errorf("-%s: %v", nodekeyFlag.Name, err)) } + cfg.PrivateKey = key } else { cfg.PrivateKey, _ = crypto.GenerateKey() @@ -266,15 +289,19 @@ func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) { if err != nil { exit(err) } + cfg.Bootnodes = bn } dbpath := ctx.String(nodedbFlag.Name) + db, err := enode.OpenDB(dbpath) if err != nil { exit(err) } + ln := enode.NewLocalNode(db, cfg.PrivateKey) + return ln, cfg } @@ -310,6 +337,7 @@ func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn { if addr == "" { addr = "0.0.0.0:0" } + socket, err := net.ListenPacket("udp4", addr) if err != nil { exit(err) @@ -317,12 +345,14 @@ func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn { // Configure UDP endpoint in ENR from listener address. usocket := socket.(*net.UDPConn) + uaddr := socket.LocalAddr().(*net.UDPAddr) if uaddr.IP.IsUnspecified() { ln.SetFallbackIP(net.IP{127, 0, 0, 1}) } else { ln.SetFallbackIP(uaddr.IP) } + ln.SetFallbackUDP(uaddr.Port) // If an ENR endpoint is set explicitly on the command-line, override @@ -347,20 +377,26 @@ func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn { func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { s := params.RinkebyBootnodes + if ctx.IsSet(bootnodesFlag.Name) { input := ctx.String(bootnodesFlag.Name) if input == "" { return nil, nil } + s = strings.Split(input, ",") } + nodes := make([]*enode.Node, len(s)) + var err error + for i, record := range s { nodes[i], err = parseNode(record) if err != nil { return nil, fmt.Errorf("invalid bootstrap node: %v", err) } } + return nodes, nil } diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go index 08792a063a..85557c8368 100644 --- a/cmd/devp2p/discv5cmd.go +++ b/cmd/devp2p/discv5cmd.go @@ -81,19 +81,23 @@ var ( func discv5Ping(ctx *cli.Context) error { n := getNodeArg(ctx) + disc := startV5(ctx) defer disc.Close() fmt.Println(disc.Ping(n)) + return nil } func discv5Resolve(ctx *cli.Context) error { n := getNodeArg(ctx) + disc := startV5(ctx) defer disc.Close() fmt.Println(disc.Resolve(n)) + return nil } @@ -101,8 +105,11 @@ func discv5Crawl(ctx *cli.Context) error { if ctx.NArg() < 1 { return fmt.Errorf("need nodes file as argument") } + nodesFile := ctx.Args().First() + var inputSet nodeSet + if common.FileExist(nodesFile) { inputSet = loadNodesJSON(nodesFile) } @@ -113,6 +120,7 @@ func discv5Crawl(ctx *cli.Context) error { c.revalidateInterval = 10 * time.Minute output := c.run(ctx.Duration(crawlTimeoutFlag.Name), ctx.Int(crawlParallelismFlag.Name)) writeNodesJSON(nodesFile, output) + return nil } @@ -123,6 +131,7 @@ func discv5Test(ctx *cli.Context) error { Listen1: ctx.String(testListen1Flag.Name), Listen2: ctx.String(testListen2Flag.Name), } + return runTests(ctx, suite.AllTests()) } @@ -138,9 +147,11 @@ func discv5Listen(ctx *cli.Context) error { func startV5(ctx *cli.Context) *discover.UDPv5 { ln, config := makeDiscoveryConfig(ctx) socket := listen(ctx, ln) + disc, err := discover.ListenV5(socket, ln, config) if err != nil { exit(err) } + return disc } diff --git a/cmd/devp2p/dns_cloudflare.go b/cmd/devp2p/dns_cloudflare.go index 53e37b1682..1b48f260f2 100644 --- a/cmd/devp2p/dns_cloudflare.go +++ b/cmd/devp2p/dns_cloudflare.go @@ -51,10 +51,12 @@ func newCloudflareClient(ctx *cli.Context) *cloudflareClient { if token == "" { exit(fmt.Errorf("need cloudflare API token to proceed")) } + api, err := cloudflare.NewWithAPIToken(token) if err != nil { exit(fmt.Errorf("can't create Cloudflare client: %v", err)) } + return &cloudflareClient{ API: api, zoneID: ctx.String(cloudflareZoneIDFlag.Name), @@ -66,7 +68,9 @@ func (c *cloudflareClient) deploy(name string, t *dnsdisc.Tree) error { if err := c.checkZone(name); err != nil { return err } + records := t.ToTXT(name) + return c.uploadRecords(name, records) } @@ -74,31 +78,39 @@ func (c *cloudflareClient) deploy(name string, t *dnsdisc.Tree) error { func (c *cloudflareClient) checkZone(name string) error { if c.zoneID == "" { log.Info(fmt.Sprintf("Finding CloudFlare zone ID for %s", name)) + id, err := c.ZoneIDByName(name) if err != nil { return err } + c.zoneID = id } + log.Info(fmt.Sprintf("Checking Permissions on zone %s", c.zoneID)) + zone, err := c.ZoneDetails(context.Background(), c.zoneID) if err != nil { return err } + if !strings.HasSuffix(name, "."+zone.Name) { return fmt.Errorf("CloudFlare zone name %q does not match name %q to be deployed", zone.Name, name) } + needPerms := map[string]bool{"#zone:edit": false, "#zone:read": false} for _, perm := range zone.Permissions { if _, ok := needPerms[perm]; ok { needPerms[perm] = true } } + for _, ok := range needPerms { if !ok { return fmt.Errorf("wrong permissions on zone %s: %v", c.zoneID, needPerms) } } + return nil } @@ -111,18 +123,23 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) for name, r := range records { lrecords[strings.ToLower(name)] = r } + records = lrecords log.Info(fmt.Sprintf("Retrieving existing TXT records on %s", name)) + entries, err := c.DNSRecords(context.Background(), c.zoneID, cloudflare.DNSRecord{Type: "TXT"}) if err != nil { return err } + existing := make(map[string]cloudflare.DNSRecord) + for _, entry := range entries { if !strings.HasSuffix(entry.Name, name) { continue } + existing[strings.ToLower(entry.Name)] = entry } @@ -132,28 +149,35 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) created := 0 updated := 0 skipped := 0 + for path, val := range records { old, exists := existing[path] if !exists { // Entry is unknown, push a new one to Cloudflare. log.Debug(fmt.Sprintf("Creating %s = %q", path, val)) + created++ + ttl := rootTTL if path != name { ttl = treeNodeTTLCloudflare // Max TTL permitted by Cloudflare } + record := cloudflare.DNSRecord{Type: "TXT", Name: path, Content: val, TTL: ttl} _, err = c.CreateDNSRecord(context.Background(), c.zoneID, record) } else if old.Content != val { // Entry already exists, only change its content. log.Info(fmt.Sprintf("Updating %s from %q to %q", path, old.Content, val)) + updated++ old.Content = val err = c.UpdateDNSRecord(context.Background(), c.zoneID, old.ID, old) } else { skipped++ + log.Debug(fmt.Sprintf("Skipping %s = %q", path, val)) } + if err != nil { return fmt.Errorf("failed to publish %s: %v", path, err) } @@ -164,18 +188,22 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) deleted := 0 log.Info("Deleting stale DNS entries") + for path, entry := range existing { if _, ok := records[path]; ok { continue } // Stale entry, nuke it. log.Debug(fmt.Sprintf("Deleting %s = %q", path, entry.Content)) + deleted++ + if err := c.DeleteDNSRecord(context.Background(), c.zoneID, entry.ID); err != nil { return fmt.Errorf("failed to delete %s: %v", path, err) } } log.Info("Deleted stale DNS entries", "count", deleted) + return nil } diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index 42b924ddfe..93135b77a9 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -81,15 +81,20 @@ type recordSet struct { func newRoute53Client(ctx *cli.Context) *route53Client { akey := ctx.String(route53AccessKeyFlag.Name) asec := ctx.String(route53AccessSecretFlag.Name) + if akey == "" || asec == "" { exit(fmt.Errorf("need Route53 Access Key ID and secret to proceed")) } + creds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(akey, asec, "")) + cfg, err := config.LoadDefaultConfig(context.Background(), config.WithCredentialsProvider(creds)) if err != nil { exit(fmt.Errorf("can't initialize AWS configuration: %v", err)) } + cfg.Region = ctx.String(route53RegionFlag.Name) + return &route53Client{ api: route53.NewFromConfig(cfg), zoneID: ctx.String(route53ZoneIDFlag.Name), @@ -107,12 +112,15 @@ func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error { if err != nil { return err } + log.Info(fmt.Sprintf("Found %d TXT records", len(existing))) + records := t.ToTXT(name) changes := c.computeChanges(name, records, existing) // Submit to API. comment := fmt.Sprintf("enrtree update of %s at seq %d", name, t.Seq()) + return c.submitChanges(changes, comment) } @@ -127,11 +135,13 @@ func (c *route53Client) deleteDomain(name string) error { if err != nil { return err } + log.Info(fmt.Sprintf("Found %d TXT records", len(existing))) changes := makeDeletionChanges(existing, nil) // Submit to API. comment := "enrtree delete of " + name + return c.submitChanges(changes, comment) } @@ -143,8 +153,10 @@ func (c *route53Client) submitChanges(changes []types.Change, comment string) er } var err error + batches := splitChanges(changes, route53ChangeSizeLimit, route53ChangeCountLimit) changesToCheck := make([]*route53.ChangeResourceRecordSetsOutput, len(batches)) + for i, changes := range batches { log.Info(fmt.Sprintf("Submitting %d changes to Route53", len(changes))) batch := &types.ChangeBatch{ @@ -152,6 +164,7 @@ func (c *route53Client) submitChanges(changes []types.Change, comment string) er Comment: aws.String(fmt.Sprintf("%s (%d/%d)", comment, i+1, len(batches))), } req := &route53.ChangeResourceRecordSetsInput{HostedZoneId: &c.zoneID, ChangeBatch: batch} + changesToCheck[i], err = c.api.ChangeResourceRecordSets(context.TODO(), req) if err != nil { return err @@ -162,7 +175,9 @@ func (c *route53Client) submitChanges(changes []types.Change, comment string) er for _, change := range changesToCheck { log.Info(fmt.Sprintf("Waiting for change request %s", *change.ChangeInfo.Id)) wreq := &route53.GetChangeInput{Id: change.ChangeInfo.Id} + var count int + for { wresp, err := c.api.GetChange(context.TODO(), wreq) if err != nil { @@ -178,6 +193,7 @@ func (c *route53Client) submitChanges(changes []types.Change, comment string) er time.Sleep(30 * time.Second) } } + return nil } @@ -186,29 +202,36 @@ func (c *route53Client) checkZone(name string) (err error) { if c.zoneID == "" { c.zoneID, err = c.findZoneID(name) } + return err } // findZoneID searches for the Zone ID containing the given domain. func (c *route53Client) findZoneID(name string) (string, error) { log.Info(fmt.Sprintf("Finding Route53 Zone ID for %s", name)) + var req route53.ListHostedZonesByNameInput + for { resp, err := c.api.ListHostedZonesByName(context.TODO(), &req) if err != nil { return "", err } + for _, zone := range resp.HostedZones { if isSubdomain(name, *zone.Name) { return *zone.Id, nil } } + if !resp.IsTruncated { break } + req.DNSName = resp.NextDNSName req.HostedZoneId = resp.NextHostedZoneId } + return "", errors.New("can't find zone ID for " + name) } @@ -220,6 +243,7 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e for name, r := range records { lrecords[strings.ToLower(name)] = r } + records = lrecords var ( @@ -254,6 +278,7 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e upserts++ } else { log.Debug(fmt.Sprintf("Skipping %s = %s", path, newValue)) + skips++ } } @@ -270,12 +295,14 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e "upserts", upserts) // Ensure changes are in the correct order. sortChanges(changes) + return changes } // makeDeletionChanges creates record changes which delete all records not contained in 'keep'. func makeDeletionChanges(records map[string]recordSet, keep map[string]string) []types.Change { var changes []types.Change + for path, set := range records { if _, ok := keep[path]; ok { continue @@ -284,16 +311,19 @@ func makeDeletionChanges(records map[string]recordSet, keep map[string]string) [ log.Debug(fmt.Sprintf("Deleting %s = %s", path, strings.Join(set.values, ""))) changes = append(changes, newTXTChange("DELETE", path, set.ttl, set.values...)) } + return changes } // sortChanges ensures DNS changes are in leaf-added -> root-changed -> leaf-deleted order. func sortChanges(changes []types.Change) { score := map[string]int{"CREATE": 1, "UPSERT": 2, "DELETE": 3} + sort.Slice(changes, func(i, j int) bool { if changes[i].Action == changes[j].Action { return *changes[i].ResourceRecordSet.Name < *changes[j].ResourceRecordSet.Name } + return score[string(changes[i].Action)] < score[string(changes[j].Action)] }) } @@ -306,32 +336,38 @@ func splitChanges(changes []types.Change, sizeLimit, countLimit int) [][]types.C batchSize int batchCount int ) + for _, ch := range changes { // Start new batch if this change pushes the current one over the limit. count := changeCount(ch) size := changeSize(ch) * count overSize := batchSize+size > sizeLimit overCount := batchCount+count > countLimit + if len(batches) == 0 || overSize || overCount { batches = append(batches, nil) batchSize = 0 batchCount = 0 } + batches[len(batches)-1] = append(batches[len(batches)-1], ch) batchSize += size batchCount += count } + return batches } // changeSize returns the RDATA size of a DNS change. func changeSize(ch types.Change) int { size := 0 + for _, rr := range ch.ResourceRecordSet.ResourceRecords { if rr.Value != nil { size += len(*rr.Value) } } + return size } @@ -339,6 +375,7 @@ func changeCount(ch types.Change) int { if ch.Action == types.ChangeActionUpsert { return 2 } + return 1 } @@ -349,20 +386,25 @@ func (c *route53Client) collectRecords(name string) (map[string]recordSet, error existing := make(map[string]recordSet) log.Info("Loading existing TXT records", "name", name, "zone", c.zoneID) + for page := 0; ; page++ { log.Debug("Loading existing TXT records", "name", name, "zone", c.zoneID, "page", page) + resp, err := c.api.ListResourceRecordSets(context.TODO(), &req) if err != nil { return existing, err } + for _, set := range resp.ResourceRecordSets { if !isSubdomain(*set.Name, name) || set.Type != types.RRTypeTxt { continue } + s := recordSet{ttl: *set.TTL} for _, rec := range set.ResourceRecords { s.values = append(s.values, *rec.Value) } + name := strings.TrimSuffix(*set.Name, ".") existing[name] = s } @@ -381,6 +423,7 @@ func (c *route53Client) collectRecords(name string) (map[string]recordSet, error req.StartRecordType = resp.NextRecordType } log.Info("Loaded existing TXT records", "name", name, "zone", c.zoneID, "records", len(existing)) + return existing, nil } @@ -391,7 +434,9 @@ func newTXTChange(action, name string, ttl int64, values ...string) types.Change Name: &name, TTL: &ttl, } + var rrs []types.ResourceRecord + for _, val := range values { var rr types.ResourceRecord rr.Value = aws.String(val) @@ -410,19 +455,23 @@ func newTXTChange(action, name string, ttl int64, values ...string) types.Change func isSubdomain(name, domain string) bool { domain = strings.TrimSuffix(domain, ".") name = strings.TrimSuffix(name, ".") + return strings.HasSuffix("."+name, "."+domain) } // splitTXT splits value into a list of quoted 255-character strings. func splitTXT(value string) string { var result strings.Builder + for len(value) > 0 { rlen := len(value) if rlen > 253 { rlen = 253 } + result.WriteString(strconv.Quote(value[:rlen])) value = value[rlen:] } + return result.String() } diff --git a/cmd/devp2p/dns_route53_test.go b/cmd/devp2p/dns_route53_test.go index e6eb516e6b..758d2f1e0d 100644 --- a/cmd/devp2p/dns_route53_test.go +++ b/cmd/devp2p/dns_route53_test.go @@ -135,6 +135,7 @@ func TestRoute53ChangeSort(t *testing.T) { } var client route53Client + changes := client.computeChanges("n", testTree1, testTree0) if !reflect.DeepEqual(changes, wantChanges) { t.Fatalf("wrong changes (got %d, want %d)", len(changes), len(wantChanges)) @@ -147,6 +148,7 @@ func TestRoute53ChangeSort(t *testing.T) { wantChanges[6:], } split := splitChanges(changes, 600, 4000) + if !reflect.DeepEqual(split, wantSplit) { t.Fatalf("wrong split batches: got %d, want %d", len(split), len(wantSplit)) } @@ -157,6 +159,7 @@ func TestRoute53ChangeSort(t *testing.T) { wantChanges[5:], } split = splitChanges(changes, 10000, 6) + if !reflect.DeepEqual(split, wantSplit) { t.Fatalf("wrong split batches: got %d, want %d", len(split), len(wantSplit)) } @@ -180,6 +183,7 @@ func TestRoute53NoChange(t *testing.T) { } var client route53Client + changes := client.computeChanges("n", testTree1, testTree0) if len(changes) > 0 { t.Fatalf("wrong changes (got %d, want 0)", len(changes)) diff --git a/cmd/devp2p/dnscmd.go b/cmd/devp2p/dnscmd.go index 5caa1d0930..bce271f8ef 100644 --- a/cmd/devp2p/dnscmd.go +++ b/cmd/devp2p/dnscmd.go @@ -127,10 +127,12 @@ func dnsSync(ctx *cli.Context) error { url = ctx.Args().Get(0) outdir = ctx.Args().Get(1) ) + domain, _, err := dnsdisc.ParseURL(url) if err != nil { return err } + if outdir == "" { outdir = domain } @@ -139,10 +141,12 @@ func dnsSync(ctx *cli.Context) error { if err != nil { return err } + def := treeToDefinition(url, t) def.Meta.LastModified = time.Now() writeTreeMetadata(outdir, def) writeTreeNodes(outdir, def) + return nil } @@ -150,33 +154,40 @@ func dnsSign(ctx *cli.Context) error { if ctx.NArg() < 2 { return fmt.Errorf("need tree definition directory and key file as arguments") } + var ( defdir = ctx.Args().Get(0) keyfile = ctx.Args().Get(1) def = loadTreeDefinition(defdir) domain = directoryName(defdir) ) + if def.Meta.URL != "" { d, _, err := dnsdisc.ParseURL(def.Meta.URL) if err != nil { return fmt.Errorf("invalid 'url' field: %v", err) } + domain = d } + if ctx.IsSet(dnsDomainFlag.Name) { domain = ctx.String(dnsDomainFlag.Name) } + if ctx.IsSet(dnsSeqFlag.Name) { def.Meta.Seq = ctx.Uint(dnsSeqFlag.Name) } else { def.Meta.Seq++ // Auto-bump sequence number if not supplied via flag. } + t, err := dnsdisc.MakeTree(def.Meta.Seq, def.Nodes, def.Meta.Links) if err != nil { return err } key := loadSigningKey(keyfile) + url, err := t.Sign(key, domain) if err != nil { return fmt.Errorf("can't sign: %v", err) @@ -185,6 +196,7 @@ func dnsSign(ctx *cli.Context) error { def = treeToDefinition(url, t) def.Meta.LastModified = time.Now() writeTreeMetadata(defdir, def) + return nil } @@ -196,6 +208,7 @@ func directoryName(dir string) string { if err != nil { exit(err) } + return filepath.Base(abs) } @@ -204,15 +217,19 @@ func dnsToTXT(ctx *cli.Context) error { if ctx.NArg() < 1 { return fmt.Errorf("need tree definition directory as argument") } + output := ctx.Args().Get(1) if output == "" { output = "-" // default to stdout } + domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) if err != nil { return err } + writeTXTJSON(output, t.ToTXT(domain)) + return nil } @@ -221,11 +238,14 @@ func dnsToCloudflare(ctx *cli.Context) error { if ctx.NArg() != 1 { return fmt.Errorf("need tree definition directory as argument") } + domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) if err != nil { return err } + client := newCloudflareClient(ctx) + return client.deploy(domain, t) } @@ -234,11 +254,14 @@ func dnsToRoute53(ctx *cli.Context) error { if ctx.NArg() != 1 { return fmt.Errorf("need tree definition directory as argument") } + domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) if err != nil { return err } + client := newRoute53Client(ctx) + return client.deploy(domain, t) } @@ -247,7 +270,9 @@ func dnsNukeRoute53(ctx *cli.Context) error { if ctx.NArg() != 1 { return fmt.Errorf("need domain name as argument") } + client := newRoute53Client(ctx) + return client.deleteDomain(ctx.Args().First()) } @@ -257,11 +282,14 @@ func loadSigningKey(keyfile string) *ecdsa.PrivateKey { if err != nil { exit(fmt.Errorf("failed to read the keyfile at '%s': %v", keyfile, err)) } + password, _ := prompt.Stdin.PromptPassword("Please enter the password for '" + keyfile + "': ") + key, err := keystore.DecryptKey(keyjson, password) if err != nil { exit(fmt.Errorf("error decrypting key: %v", err)) } + return key.PrivateKey } @@ -271,6 +299,7 @@ func dnsClient(ctx *cli.Context) *dnsdisc.Client { if commandHasFlag(ctx, dnsTimeoutFlag) { cfg.Timeout = ctx.Duration(dnsTimeoutFlag.Name) } + return dnsdisc.NewClient(cfg) } @@ -311,17 +340,21 @@ func treeToDefinition(url string, t *dnsdisc.Tree) *dnsDefinition { if meta.Links == nil { meta.Links = []string{} } + return &dnsDefinition{Meta: meta, Nodes: t.Nodes()} } // loadTreeDefinition loads a directory in 'definition' format. func loadTreeDefinition(directory string) *dnsDefinition { metaFile, nodesFile := treeDefinitionFiles(directory) + var def dnsDefinition + err := common.LoadJSON(metaFile, &def.Meta) if err != nil && !os.IsNotExist(err) { exit(err) } + if def.Meta.Links == nil { def.Meta.Links = []string{} } @@ -336,7 +369,9 @@ func loadTreeDefinition(directory string) *dnsDefinition { if err := nodes.verify(); err != nil { exit(err) } + def.Nodes = nodes.nodes() + return &def } @@ -344,19 +379,24 @@ func loadTreeDefinition(directory string) *dnsDefinition { func loadTreeDefinitionForExport(dir string) (domain string, t *dnsdisc.Tree, err error) { metaFile, _ := treeDefinitionFiles(dir) def := loadTreeDefinition(dir) + if def.Meta.URL == "" { return "", nil, fmt.Errorf("missing 'url' field in %v", metaFile) } + domain, pubkey, err := dnsdisc.ParseURL(def.Meta.URL) if err != nil { return "", nil, fmt.Errorf("invalid 'url' field in %v: %v", metaFile, err) } + if t, err = dnsdisc.MakeTree(def.Meta.Seq, def.Nodes, def.Meta.Links); err != nil { return "", nil, err } + if err := ensureValidTreeSignature(t, pubkey, def.Meta.Sig); err != nil { return "", nil, err } + return domain, t, nil } @@ -366,9 +406,11 @@ func ensureValidTreeSignature(t *dnsdisc.Tree, pubkey *ecdsa.PublicKey, sig stri if sig == "" { return fmt.Errorf("missing signature, run 'devp2p dns sign' first") } + if err := t.SetSignature(pubkey, sig); err != nil { return fmt.Errorf("invalid signature on tree, run 'devp2p dns sign' to update it") } + return nil } @@ -378,9 +420,11 @@ func writeTreeMetadata(directory string, def *dnsDefinition) { if err != nil { exit(err) } + if err := os.Mkdir(directory, 0744); err != nil && !os.IsExist(err) { exit(err) } + metaFile, _ := treeDefinitionFiles(directory) if err := os.WriteFile(metaFile, metaJSON, 0644); err != nil { @@ -391,6 +435,7 @@ func writeTreeMetadata(directory string, def *dnsDefinition) { func writeTreeNodes(directory string, def *dnsDefinition) { ns := make(nodeSet, len(def.Nodes)) ns.add(def.Nodes...) + _, nodesFile := treeDefinitionFiles(directory) writeNodesJSON(nodesFile, ns) } @@ -398,6 +443,7 @@ func writeTreeNodes(directory string, def *dnsDefinition) { func treeDefinitionFiles(directory string) (string, string) { meta := filepath.Join(directory, "enrtree-info.json") nodes := filepath.Join(directory, "nodes.json") + return meta, nodes } @@ -407,9 +453,11 @@ func writeTXTJSON(file string, txt map[string]string) { if err != nil { exit(err) } + if file == "-" { os.Stdout.Write(txtJSON) fmt.Println() + return } diff --git a/cmd/devp2p/enrcmd.go b/cmd/devp2p/enrcmd.go index a5b5dc3e57..73aaa83abb 100644 --- a/cmd/devp2p/enrcmd.go +++ b/cmd/devp2p/enrcmd.go @@ -47,20 +47,25 @@ var enrdumpCommand = &cli.Command{ func enrdump(ctx *cli.Context) error { var source string + if file := ctx.String(fileFlag.Name); file != "" { if ctx.NArg() != 0 { return fmt.Errorf("can't dump record from command-line argument in -file mode") } + var b []byte + var err error if file == "-" { b, err = io.ReadAll(os.Stdin) } else { b, err = os.ReadFile(file) } + if err != nil { return err } + source = string(b) } else if ctx.NArg() == 1 { source = ctx.Args().First() @@ -72,7 +77,9 @@ func enrdump(ctx *cli.Context) error { if err != nil { return fmt.Errorf("INVALID: %v", err) } + dumpRecord(os.Stdout, r) + return nil } @@ -85,6 +92,7 @@ func dumpRecord(out io.Writer, r *enr.Record) { fmt.Fprintf(out, "Node ID: %v\n", n.ID()) dumpNodeURL(out, n) } + kv := r.AppendElements(nil)[1:] fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2) fmt.Fprint(out, dumpRecordKV(kv, 2)) @@ -95,13 +103,16 @@ func dumpNodeURL(out io.Writer, n *enode.Node) { if n.Load(&key) != nil { return // no secp256k1 public key } + fmt.Fprintf(out, "URLv4: %s\n", n.URLv4()) } func dumpRecordKV(kv []interface{}, indent int) string { // Determine the longest key name for alignment. var out string + var longestKey = 0 + for i := 0; i < len(kv); i += 2 { key := kv[i].(string) if len(key) > longestKey { @@ -114,10 +125,12 @@ func dumpRecordKV(kv []interface{}, indent int) string { val := kv[i+1].(rlp.RawValue) pad := longestKey - len(key) out += strings.Repeat(" ", indent) + strconv.Quote(key) + strings.Repeat(" ", pad+1) + formatter := attrFormatters[key] if formatter == nil { formatter = formatAttrRaw } + fmtval, ok := formatter(val) if ok { out += fmtval + "\n" @@ -125,6 +138,7 @@ func dumpRecordKV(kv []interface{}, indent int) string { out += hex.EncodeToString(val) + " (!)\n" } } + return out } @@ -133,10 +147,12 @@ func parseNode(source string) (*enode.Node, error) { if strings.HasPrefix(source, "enode://") { return enode.ParseV4(source) } + r, err := parseRecord(source) if err != nil { return nil, err } + return enode.New(enode.ValidSchemes, r) } @@ -148,8 +164,10 @@ func parseRecord(source string) (*enr.Record, error) { } else if d, ok := decodeRecordBase64(bytes.TrimSpace(bin)); ok { bin = d } + var r enr.Record err := rlp.DecodeBytes(bin, &r) + return &r, err } @@ -157,8 +175,10 @@ func decodeRecordHex(b []byte) ([]byte, bool) { if bytes.HasPrefix(b, []byte("0x")) { b = b[2:] } + dec := make([]byte, hex.DecodedLen(len(b))) _, err := hex.Decode(dec, b) + return dec, err == nil } @@ -166,8 +186,10 @@ func decodeRecordBase64(b []byte) ([]byte, bool) { if bytes.HasPrefix(b, []byte("enr:")) { b = b[4:] } + dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) n, err := base64.RawURLEncoding.Decode(dec, b) + return dec[:n], err == nil } @@ -197,6 +219,7 @@ func formatAttrIP(v rlp.RawValue) (string, bool) { if err != nil || len(content) != 4 && len(content) != 6 { return "", false } + return net.IP(content).String(), true } @@ -205,5 +228,6 @@ func formatAttrUint(v rlp.RawValue) (string, bool) { if err := rlp.DecodeBytes(v, &x); err != nil { return "", false } + return strconv.FormatUint(x, 10), true } diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index 65adcfe050..f7462ace36 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -51,6 +51,7 @@ func (c *Chain) TD() *big.Int { for _, block := range c.blocks[:c.Len()] { sum.Add(sum, block.Difficulty()) } + return sum } @@ -61,9 +62,11 @@ func (c *Chain) TotalDifficultyAt(height int) *big.Int { if height >= c.Len() { return sum } + for _, block := range c.blocks[:height+1] { sum.Add(sum, block.Difficulty()) } + return sum } @@ -71,6 +74,7 @@ func (c *Chain) RootAt(height int) common.Hash { if height < c.Len() { return c.blocks[height].Root() } + return common.Hash{} } @@ -85,6 +89,7 @@ func (c *Chain) Shorten(height int) *Chain { copy(blocks, c.blocks[:height]) config := *c.chainConfig + return &Chain{ blocks: blocks, chainConfig: &config, @@ -103,6 +108,7 @@ func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) { } headers := make([]*types.Header, req.Amount) + var blockNumber uint64 // range over blocks to check if our chain has the requested header @@ -112,6 +118,7 @@ func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) { blockNumber = block.Number().Uint64() } } + if headers[0] == nil { return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash) } @@ -149,6 +156,7 @@ func loadChain(chainfile string, genesis string) (*Chain, error) { } c := &Chain{genesis: gen, blocks: blocks, chainConfig: gen.Config} + return c, nil } @@ -157,10 +165,12 @@ func loadGenesis(genesisFile string) (core.Genesis, error) { if err != nil { return core.Genesis{}, err } + var gen core.Genesis if err := json.Unmarshal(chainConfig, &gen); err != nil { return core.Genesis{}, err } + return gen, nil } @@ -170,16 +180,21 @@ func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, erro if err != nil { return nil, err } + defer fh.Close() + var reader io.Reader = fh if strings.HasSuffix(chainfile, ".gz") { if reader, err = gzip.NewReader(reader); err != nil { return nil, err } } + stream := rlp.NewStream(reader, 0) + var blocks = make([]*types.Block, 1) blocks[0] = gblock + for i := 0; ; i++ { var b types.Block if err := stream.Decode(&b); err == io.EOF { @@ -187,10 +202,13 @@ func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, erro } else if err != nil { return nil, fmt.Errorf("at block index %d: %v", i, err) } + if b.NumberU64() != uint64(i+1) { return nil, fmt.Errorf("block at index %d has wrong number %d", i, b.NumberU64()) } + blocks = append(blocks, &b) } + return blocks, nil } diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index 67221923a6..97e0fd5d95 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -129,6 +129,7 @@ func TestChain_GetHeaders(t *testing.T) { if err != nil { t.Fatal(err) } + genesisFile, err := filepath.Abs("./testdata/genesis.json") if err != nil { t.Fatal(err) @@ -196,6 +197,7 @@ func TestChain_GetHeaders(t *testing.T) { if err != nil { t.Fatal(err) } + assert.Equal(t, headers, tt.expected) }) } diff --git a/cmd/devp2p/internal/ethtest/helpers.go b/cmd/devp2p/internal/ethtest/helpers.go index 510bd606cf..7ecf9617ae 100644 --- a/cmd/devp2p/internal/ethtest/helpers.go +++ b/cmd/devp2p/internal/ethtest/helpers.go @@ -51,10 +51,12 @@ func (s *Suite) dial() (*Conn, error) { if err != nil { return nil, err } + conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())} // do encHandshake conn.ourKey, _ = crypto.GenerateKey() _, err = conn.Handshake(conn.ourKey) + if err != nil { conn.Close() return nil, err @@ -66,6 +68,7 @@ func (s *Suite) dial() (*Conn, error) { {Name: "eth", Version: 68}, } conn.ourHighestProtoVersion = 68 + return &conn, nil } @@ -75,8 +78,10 @@ func (s *Suite) dialSnap() (*Conn, error) { if err != nil { return nil, fmt.Errorf("dial failed: %v", err) } + conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1}) conn.ourHighestSnapProtoVersion = 1 + return conn, nil } @@ -86,9 +91,11 @@ func (c *Conn) peer(chain *Chain, status *Status) error { if err := c.handshake(); err != nil { return fmt.Errorf("handshake failed: %v", err) } + if _, err := c.statusExchange(chain, status); err != nil { return fmt.Errorf("status exchange failed: %v", err) } + return nil } @@ -98,6 +105,7 @@ func (c *Conn) handshake() error { c.SetDeadline(time.Now().Add(10 * time.Second)) // write hello to client pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] + ourHandshake := &Hello{ Version: 5, Caps: c.caps, @@ -113,7 +121,9 @@ func (c *Conn) handshake() error { if msg.Version >= 5 { c.SetSnappy(true) } + c.negotiateEthProtocol(msg.Caps) + if c.negotiatedProtoVersion == 0 { return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion) } @@ -121,6 +131,7 @@ func (c *Conn) handshake() error { if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion { return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion) } + return nil default: return fmt.Errorf("bad handshake: %#v", msg) @@ -131,7 +142,9 @@ func (c *Conn) handshake() error { // advertised capability from peer. func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { var highestEthVersion uint + var highestSnapVersion uint + for _, capability := range caps { switch capability.Name { case "eth": @@ -144,6 +157,7 @@ func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { } } } + c.negotiatedProtoVersion = highestEthVersion c.negotiatedSnapProtoVersion = highestSnapVersion } @@ -187,6 +201,7 @@ loop: if c.negotiatedProtoVersion == 0 { return nil, fmt.Errorf("eth protocol version must be set in Conn") } + if status == nil { // default status message status = &Status{ @@ -198,9 +213,11 @@ loop: ForkID: chain.ForkID(), } } + if err := c.Write(status); err != nil { return nil, fmt.Errorf("write to connection failed: %v", err) } + return message, nil } @@ -283,9 +300,11 @@ func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, reqID uint func (c *Conn) snapRequest(msg Message, id uint64, chain *Chain) (Message, error) { defer c.SetReadDeadline(time.Time{}) c.SetReadDeadline(time.Now().Add(5 * time.Second)) + if err := c.Write(msg); err != nil { return nil, fmt.Errorf("could not write to connection: %v", err) } + return c.ReadSnap(id) } @@ -313,11 +332,14 @@ func (s *Suite) sendNextBlock() error { if err != nil { return err } + defer sendConn.Close() defer recvConn.Close() + if err = sendConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } + if err = recvConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } @@ -337,6 +359,7 @@ func (s *Suite) sendNextBlock() error { } // update test suite chain s.chain.blocks = append(s.chain.blocks, nextBlock) + return nil } @@ -346,6 +369,7 @@ func (s *Suite) testAnnounce(sendConn, receiveConn *Conn, blockAnnouncement *New if err := sendConn.Write(blockAnnouncement); err != nil { return fmt.Errorf("could not write to connection: %v", err) } + return s.waitAnnounce(receiveConn, blockAnnouncement) } @@ -358,15 +382,18 @@ func (s *Suite) waitAnnounce(conn *Conn, blockAnnouncement *NewBlock) error { return fmt.Errorf("wrong header in block announcement: \nexpected %v "+ "\ngot %v", blockAnnouncement.Block.Header(), msg.Block.Header()) } + if !reflect.DeepEqual(blockAnnouncement.TD, msg.TD) { return fmt.Errorf("wrong TD in announcement: expected %v, got %v", blockAnnouncement.TD, msg.TD) } + return nil case *NewBlockHashes: hashes := *msg if blockAnnouncement.Block.Hash() != hashes[0].Hash { return fmt.Errorf("wrong block hash in announcement: expected %v, got %v", blockAnnouncement.Block.Hash(), hashes[0].Hash) } + return nil // ignore tx announcements from previous tests @@ -398,6 +425,7 @@ func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block) error { // node imported the block for { requestID := uint64(54) + headers, err := conn.headersRequest(req, s.chain, requestID) if err != nil { return fmt.Errorf("GetBlockHeader request failed: %v", err) @@ -407,9 +435,11 @@ func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block) error { time.Sleep(100 * time.Millisecond) continue } + if !reflect.DeepEqual(block.Header(), headers[0]) { return fmt.Errorf("wrong header returned: wanted %v, got %v", block.Header(), headers[0]) } + return nil } } @@ -419,11 +449,14 @@ func (s *Suite) oldAnnounce() error { if err != nil { return err } + defer sendConn.Close() defer receiveConn.Close() + if err := sendConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } + if err := receiveConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } @@ -458,6 +491,7 @@ func (s *Suite) oldAnnounce() error { default: return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) } + return nil } @@ -470,6 +504,7 @@ func (s *Suite) maliciousHandshakes(t *utesting.T) error { // write hello to client pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] + handshakes := []*Hello{ { Version: 5, @@ -512,6 +547,7 @@ func (s *Suite) maliciousHandshakes(t *utesting.T) error { } for i, handshake := range handshakes { t.Logf("Testing malicious handshake %v\n", i) + if err := conn.Write(handshake); err != nil { return fmt.Errorf("could not write to connection: %v", err) } @@ -533,6 +569,7 @@ func (s *Suite) maliciousHandshakes(t *utesting.T) error { return fmt.Errorf("dial failed: %v", err) } } + return nil } @@ -540,6 +577,7 @@ func (s *Suite) maliciousStatus(conn *Conn) error { if err := conn.handshake(); err != nil { return fmt.Errorf("handshake failed: %v", err) } + status := &Status{ ProtocolVersion: uint32(conn.negotiatedProtoVersion), NetworkID: s.chain.chainConfig.ChainID.Uint64(), @@ -554,6 +592,7 @@ func (s *Suite) maliciousStatus(conn *Conn) error { if err != nil { return fmt.Errorf("status exchange failed: %v", err) } + switch msg := msg.(type) { case *Status: default: @@ -578,11 +617,14 @@ func (s *Suite) hashAnnounce() error { if err != nil { return fmt.Errorf("failed to create connections: %v", err) } + defer sendConn.Close() defer recvConn.Close() + if err := sendConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } + if err := recvConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } @@ -592,8 +634,10 @@ func (s *Suite) hashAnnounce() error { Hash common.Hash // Hash of one particular block being announced Number uint64 // Number of one particular block being announced } + nextBlock := s.fullChain.blocks[s.chain.Len()] announcement := anno{Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()} + newBlockHash := &NewBlockHashes{announcement} if err := sendConn.Write(newBlockHash); err != nil { return fmt.Errorf("failed to write to connection: %v", err) @@ -634,6 +678,7 @@ func (s *Suite) hashAnnounce() error { if len(hashes) != 1 { return fmt.Errorf("unexpected new block hash announcement: wanted 1 announcement, got %d", len(hashes)) } + if nextBlock.Hash() != hashes[0].Hash { return fmt.Errorf("unexpected block hash announcement, wanted %v, got %v", nextBlock.Hash(), hashes[0].Hash) @@ -645,6 +690,7 @@ func (s *Suite) hashAnnounce() error { if len(nextBlockBody.Transactions) != 0 || len(nextBlockBody.Uncles) != 0 { return fmt.Errorf("unexpected non-empty new block propagated: %s", pretty.Sdump(msg)) } + if msg.Block.Hash() != nextBlock.Hash() { return fmt.Errorf("mismatched hash of propagated new block: wanted %v, got %v", nextBlock.Hash(), msg.Block.Hash()) @@ -662,5 +708,6 @@ func (s *Suite) hashAnnounce() error { } // update the chain s.chain.blocks = append(s.chain.blocks, nextBlock) + return nil } diff --git a/cmd/devp2p/internal/ethtest/large.go b/cmd/devp2p/internal/ethtest/large.go index 40626c2068..6de7e886ad 100644 --- a/cmd/devp2p/internal/ethtest/large.go +++ b/cmd/devp2p/internal/ethtest/large.go @@ -29,8 +29,10 @@ import ( func largeNumber(megabytes int) *big.Int { buf := make([]byte, megabytes*1024*1024) rand.Read(buf) + bigint := new(big.Int) bigint.SetBytes(buf) + return bigint } @@ -38,6 +40,7 @@ func largeNumber(megabytes int) *big.Int { func largeBuffer(megabytes int) []byte { buf := make([]byte, megabytes*1024*1024) rand.Read(buf) + return buf } @@ -45,6 +48,7 @@ func largeBuffer(megabytes int) []byte { func largeString(megabytes int) string { buf := make([]byte, megabytes*1024*1024) rand.Read(buf) + return hexutil.Encode(buf) } @@ -55,7 +59,9 @@ func largeBlock() *types.Block { // Returns a random hash func randHash() common.Hash { var h common.Hash + rand.Read(h[:]) + return h } diff --git a/cmd/devp2p/internal/ethtest/snap.go b/cmd/devp2p/internal/ethtest/snap.go index ad80e268d4..d2ad3ec3ac 100644 --- a/cmd/devp2p/internal/ethtest/snap.go +++ b/cmd/devp2p/internal/ethtest/snap.go @@ -37,7 +37,9 @@ func (s *Suite) TestSnapStatus(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -66,6 +68,7 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) { secondKey = common.HexToHash("0x09e47cd5056a689e708f22fe1f932709a320518e444f5f7d8d46a3da523d6606") storageRoot = common.HexToHash("0xbe3d75a1729be157e79c3b77f00206db4d54e3ea14375a015451c88ec067c790") ) + for i, tc := range []accRangeTest{ // Tests decreasing the number of bytes {4000, root, zero, ffHash, 76, firstKey, common.HexToHash("0xd2669dcf3858e7f1eecb8b5fedbf22fbea3e9433848a75035f79d68422c2dcda")}, @@ -130,6 +133,7 @@ func (s *Suite) TestSnapGetStorageRanges(t *utesting.T) { firstKey = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a") secondKey = common.HexToHash("0x09e47cd5056a689e708f22fe1f932709a320518e444f5f7d8d46a3da523d6606") ) + for i, tc := range []stRangesTest{ { root: s.chain.RootAt(999), @@ -316,12 +320,15 @@ func hasTerm(s []byte) bool { func keybytesToHex(str []byte) []byte { l := len(str)*2 + 1 + var nibbles = make([]byte, l) for i, b := range str { nibbles[i*2] = b / 16 nibbles[i*2+1] = b % 16 } + nibbles[l-1] = 16 + return nibbles } @@ -331,14 +338,18 @@ func hexToCompact(hex []byte) []byte { terminator = 1 hex = hex[:len(hex)-1] } + buf := make([]byte, len(hex)/2+1) buf[0] = terminator << 5 // the flag byte + if len(hex)&1 == 1 { buf[0] |= 1 << 4 // odd flag buf[0] |= hex[0] // first nibble is contained in the first byte hex = hex[1:] } + decodeNibbles(hex, buf[1:]) + return buf } @@ -351,9 +362,12 @@ func (s *Suite) TestSnapTrieNodes(t *utesting.T) { hex := keybytesToHex(key)[:length] hex[len(hex)-1] = 0 // remove term flag hKey := hexToCompact(hex) + return snap.TrieNodePathSet{hKey} } + var accPaths []snap.TrieNodePathSet + for i := 1; i <= 65; i++ { accPaths = append(accPaths, pathTo(i)) } @@ -475,7 +489,9 @@ func (s *Suite) snapGetAccountRange(t *utesting.T, tc *accRangeTest) error { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -487,16 +503,20 @@ func (s *Suite) snapGetAccountRange(t *utesting.T, tc *accRangeTest) error { Limit: tc.limit, Bytes: tc.nBytes, } + resp, err := conn.snapRequest(req, req.ID, s.chain) if err != nil { return fmt.Errorf("account range request failed: %v", err) } + var res *snap.AccountRangePacket + if r, ok := resp.(*AccountRange); !ok { return fmt.Errorf("account range response wrong: %T %v", resp, resp) } else { res = (*snap.AccountRangePacket)(r) } + if exp, got := tc.expAccounts, len(res.Accounts); exp != got { return fmt.Errorf("expected %d accounts, got %d", exp, got) } @@ -506,22 +526,27 @@ func (s *Suite) snapGetAccountRange(t *utesting.T, tc *accRangeTest) error { return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:]) } } + var ( hashes []common.Hash accounts [][]byte proof = res.Proof ) + hashes, accounts, err = res.Unpack() if err != nil { return err } + if len(hashes) == 0 && len(accounts) == 0 && len(proof) == 0 { return nil } + if len(hashes) > 0 { if exp, got := tc.expFirst, res.Accounts[0].Hash; exp != got { return fmt.Errorf("expected first account %#x, got %#x", exp, got) } + if exp, got := tc.expLast, res.Accounts[len(res.Accounts)-1].Hash; exp != got { return fmt.Errorf("expected last account %#x, got %#x", exp, got) } @@ -531,17 +556,21 @@ func (s *Suite) snapGetAccountRange(t *utesting.T, tc *accRangeTest) error { 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] } + _, err = trie.VerifyRangeProof(tc.root, tc.origin[:], end, keys, accounts, proofdb) + return err } @@ -550,7 +579,9 @@ func (s *Suite) snapGetStorageRanges(t *utesting.T, tc *stRangesTest) error { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -563,29 +594,36 @@ func (s *Suite) snapGetStorageRanges(t *utesting.T, tc *stRangesTest) error { Limit: tc.limit, Bytes: tc.nBytes, } + resp, err := conn.snapRequest(req, req.ID, s.chain) if err != nil { return fmt.Errorf("account range request failed: %v", err) } + var res *snap.StorageRangesPacket + if r, ok := resp.(*StorageRanges); !ok { return fmt.Errorf("account range response wrong: %T %v", resp, resp) } else { res = (*snap.StorageRangesPacket)(r) } + gotSlots := 0 // Ensure the ranges are monotonically increasing for i, slots := range res.Slots { gotSlots += len(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[:]) } } } + if exp, got := tc.expSlots, gotSlots; exp != got { return fmt.Errorf("expected %d slots, got %d", exp, got) } + return nil } @@ -594,7 +632,9 @@ func (s *Suite) snapGetByteCodes(t *utesting.T, tc *byteCodesTest) error { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -604,20 +644,25 @@ func (s *Suite) snapGetByteCodes(t *utesting.T, tc *byteCodesTest) error { Hashes: tc.hashes, Bytes: tc.nBytes, } + resp, err := conn.snapRequest(req, req.ID, s.chain) if err != nil { return fmt.Errorf("getBytecodes request failed: %v", err) } + var res *snap.ByteCodesPacket + if r, ok := resp.(*ByteCodes); !ok { return fmt.Errorf("bytecodes response wrong: %T %v", resp, resp) } else { res = (*snap.ByteCodesPacket)(r) } + if exp, got := tc.expHashes, len(res.Codes); exp != got { for i, c := range res.Codes { fmt.Printf("%d. %#x\n", i, c) } + return fmt.Errorf("expected %d bytecodes, got %d", exp, got) } // Cross reference the requested bytecodes with the response to find gaps @@ -638,9 +683,11 @@ func (s *Suite) snapGetByteCodes(t *utesting.T, tc *byteCodesTest) error { 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 @@ -655,7 +702,9 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -666,14 +715,18 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error { Paths: tc.paths, Bytes: tc.nBytes, } + resp, err := conn.snapRequest(req, req.ID, s.chain) if err != nil { if tc.expReject { return nil } + return fmt.Errorf("trienodes request failed: %v", err) } + var res *snap.TrieNodesPacket + if r, ok := resp.(*TrieNodes); !ok { return fmt.Errorf("trienodes response wrong: %T %v", resp, resp) } else { @@ -686,18 +739,22 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error { // that the serving node is missing hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hash := make([]byte, 32) + trienodes := res.Nodes if got, want := len(trienodes), len(tc.expHashes); got != want { return fmt.Errorf("wrong trienode count, got %d, want %d\n", got, want) } + for i, trienode := range trienodes { hasher.Reset() hasher.Write(trienode) hasher.Read(hash) + if got, want := hash, tc.expHashes[i]; !bytes.Equal(got, want[:]) { fmt.Printf("hash %d wrong, got %#x, want %#x\n", i, got, want) err = fmt.Errorf("hash %d wrong, got %#x, want %#x", i, got, want) } } + return err } diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 9691411d05..2c3c6dad18 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -42,6 +42,7 @@ func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, e if err != nil { return nil, err } + return &Suite{ Dest: dest, chain: chain.Shorten(1000), @@ -93,7 +94,9 @@ func (s *Suite) TestStatus(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -106,7 +109,9 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -119,6 +124,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { Reverse: false, }, } + headers, err := conn.headersRequest(req, s.chain, 33) if err != nil { t.Fatalf("could not get block headers: %v", err) @@ -128,6 +134,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { if err != nil { t.Fatalf("failed to get headers for given request: %v", err) } + if !headersMatch(expected, headers) { t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) } @@ -143,7 +150,9 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -184,11 +193,14 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) { // wait for responses msg := conn.waitForResponse(s.chain, timeout, req1.RequestId) headers1, ok := msg.(*BlockHeaders) + if !ok { t.Fatalf("unexpected %s", pretty.Sdump(msg)) } + msg = conn.waitForResponse(s.chain, timeout, req2.RequestId) headers2, ok := msg.(*BlockHeaders) + if !ok { t.Fatalf("unexpected %s", pretty.Sdump(msg)) } @@ -221,7 +233,9 @@ func (s *Suite) TestSameRequestID(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -258,11 +272,14 @@ func (s *Suite) TestSameRequestID(t *utesting.T) { // wait for responses msg := conn.waitForResponse(s.chain, timeout, reqID) headers1, ok := msg.(*BlockHeaders) + if !ok { t.Fatalf("unexpected %s", pretty.Sdump(msg)) } + msg = conn.waitForResponse(s.chain, timeout, reqID) headers2, ok := msg.(*BlockHeaders) + if !ok { t.Fatalf("unexpected %s", pretty.Sdump(msg)) } @@ -294,16 +311,20 @@ func (s *Suite) TestZeroRequestID(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } + req := &GetBlockHeaders{ GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ Origin: eth.HashOrNumber{Number: 0}, Amount: 2, }, } + headers, err := conn.headersRequest(req, s.chain, 0) if err != nil { t.Fatalf("failed to get block headers: %v", err) @@ -313,6 +334,7 @@ func (s *Suite) TestZeroRequestID(t *utesting.T) { if err != nil { t.Fatalf("failed to get expected block headers: %v", err) } + if !headersMatch(expected, headers) { t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) } @@ -325,7 +347,9 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -343,6 +367,7 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { // wait for block bodies response msg := conn.waitForResponse(s.chain, timeout, req.RequestId) resp, ok := msg.(*BlockBodies) + if !ok { t.Fatalf("unexpected: %s", pretty.Sdump(msg)) } @@ -389,9 +414,11 @@ func (s *Suite) TestLargeAnnounce(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } + if err := conn.Write(blockAnnouncement); err != nil { t.Fatalf("could not write to connection: %v", err) } @@ -476,6 +503,7 @@ func (s *Suite) TestLargeTxRequest(t *utesting.T) { if err != nil { t.Fatalf("failed to generate transactions: %v", err) } + if err = sendMultipleSuccessfulTxs(t, s, txs); err != nil { t.Fatalf("failed to send multiple txs: %v", err) } @@ -485,7 +513,9 @@ func (s *Suite) TestLargeTxRequest(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -546,7 +576,9 @@ func (s *Suite) TestNewPooledTxs(t *utesting.T) { if err != nil { t.Fatalf("dial failed: %v", err) } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } @@ -571,6 +603,7 @@ func (s *Suite) TestNewPooledTxs(t *utesting.T) { if len(msg.GetPooledTransactionsPacket) != len(hashes) { t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg.GetPooledTransactionsPacket)) } + return // ignore propagated txs from previous tests diff --git a/cmd/devp2p/internal/ethtest/suite_test.go b/cmd/devp2p/internal/ethtest/suite_test.go index 66cabbef19..4d774476a5 100644 --- a/cmd/devp2p/internal/ethtest/suite_test.go +++ b/cmd/devp2p/internal/ethtest/suite_test.go @@ -50,6 +50,7 @@ func TestEthSuite(t *testing.T) { test := test t.Run(test.Name, func(t *testing.T) { t.Parallel() + result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) if result[0].Failed { t.Fatal() @@ -69,6 +70,7 @@ func TestSnapSuite(t *testing.T) { if err != nil { t.Fatalf("could not create new test suite: %v", err) } + for _, test := range suite.SnapTests() { t.Run(test.Name, func(t *testing.T) { result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) @@ -98,10 +100,12 @@ func runGeth() (*node.Node, error) { stack.Close() return nil, err } + if err = stack.Start(); err != nil { stack.Close() return nil, err } + return stack, nil } @@ -127,5 +131,6 @@ func setupGeth(stack *node.Node) error { } _, err = backend.BlockChain().InsertChain(chain.blocks[1:]) + return err } diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go index c99f91c5d8..764cfc589d 100644 --- a/cmd/devp2p/internal/ethtest/transaction.go +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -41,6 +41,7 @@ func (s *Suite) sendSuccessfulTxs(t *utesting.T) error { if tx == nil { return fmt.Errorf("could not find tx to send") } + t.Logf("Testing tx propagation %d: sending tx %v %v %v\n", i, tx.Hash().String(), tx.GasPrice(), tx.Gas()) // get previous tx if exists for reference in case of old tx propagation var prevTx *types.Transaction @@ -52,6 +53,7 @@ func (s *Suite) sendSuccessfulTxs(t *utesting.T) error { return fmt.Errorf("send successful tx test failed: %v", err) } } + return nil } @@ -61,8 +63,10 @@ func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction if err != nil { return err } + defer sendConn.Close() defer recvConn.Close() + if err = sendConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } @@ -89,12 +93,14 @@ func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction continue } } + for _, gotTx := range recTxs { if gotTx.Hash() == tx.Hash() { // Ok return nil } } + return fmt.Errorf("missing transaction: got %v missing %v", recTxs, tx.Hash()) case *NewPooledTransactionHashes66: txHashes := *msg @@ -104,12 +110,14 @@ func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction continue } } + for _, gotHash := range txHashes { if gotHash == tx.Hash() { // Ok return nil } } + return fmt.Errorf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash()) case *NewPooledTransactionHashes: txHashes := msg.Hashes @@ -163,7 +171,9 @@ func (s *Suite) sendMaliciousTxs(t *utesting.T) error { if err != nil { return fmt.Errorf("dial failed: %v", err) } + defer recvConn.Close() + if err = recvConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } @@ -184,7 +194,9 @@ func sendMaliciousTx(s *Suite, tx *types.Transaction) error { if err != nil { return fmt.Errorf("dial failed: %v", err) } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } @@ -193,6 +205,7 @@ func sendMaliciousTx(s *Suite, tx *types.Transaction) error { if err = conn.Write(&Transactions{tx}); err != nil { return fmt.Errorf("failed to write to connection: %v", err) } + return nil } @@ -208,11 +221,14 @@ func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, txs []*types.Transaction if err != nil { return err } + defer sendConn.Close() defer recvConn.Close() + if err = sendConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } + if err = recvConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } @@ -248,6 +264,7 @@ func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, txs []*types.Transaction if len(recvHashes) == 2000 { break } + if len(recvHashes) > 0 { _, missingTxs := compareReceivedTxs(recvHashes, txs) if len(missingTxs) > 0 { @@ -258,13 +275,16 @@ func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, txs []*types.Transaction } } } + _, missingTxs := compareReceivedTxs(recvHashes, txs) if len(missingTxs) > 0 { for _, missing := range missingTxs { t.Logf("missing tx: %v", missing.Hash()) } + return fmt.Errorf("missing %d txs", len(missingTxs)) } + return nil } @@ -279,6 +299,7 @@ func checkMaliciousTxPropagation(s *Suite, txs []*types.Transaction, conn *Conn) for i, recvTx := range *msg { recvTxs[i] = recvTx.Hash() } + badTxs, _ := compareReceivedTxs(recvTxs, txs) if len(badTxs) > 0 { return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs) @@ -299,6 +320,7 @@ func checkMaliciousTxPropagation(s *Suite, txs []*types.Transaction, conn *Conn) default: return fmt.Errorf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg)) } + return nil } @@ -315,6 +337,7 @@ func compareReceivedTxs(recvTxs []common.Hash, txs []*types.Transaction) (presen // collect present txs and missing txs separately present = make([]*types.Transaction, 0) missing = make([]*types.Transaction, 0) + for _, tx := range txs { if _, exists := recvHashes[tx.Hash()]; exists { present = append(present, tx) @@ -322,6 +345,7 @@ func compareReceivedTxs(recvTxs []common.Hash, txs []*types.Transaction) (presen missing = append(missing, tx) } } + return present, missing } @@ -330,11 +354,14 @@ func unknownTx(s *Suite) *types.Transaction { if tx == nil { return nil } + 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(s.chain.chainConfig, txNew) } @@ -346,6 +373,7 @@ func getNextTxFromChain(s *Suite) *types.Transaction { return txs[0] } } + return nil } @@ -357,6 +385,7 @@ func generateTxs(s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Tr if nextTx == nil { return nil, nil, fmt.Errorf("failed to get the next transaction") } + gas := nextTx.Gas() nonce = nonce + 1 @@ -366,16 +395,19 @@ func generateTxs(s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Tr if tx == nil { return nil, nil, fmt.Errorf("failed to get the next transaction") } + txHashMap[tx.Hash()] = tx.Hash() txs[i] = tx nonce = nonce + 1 } + return txHashMap, txs, nil } func generateTx(chainConfig *params.ChainConfig, nonce uint64, gas uint64) *types.Transaction { var to common.Address tx := types.NewTransaction(nonce, to, big.NewInt(1), gas, big.NewInt(1), []byte{}) + return signWithFaucet(chainConfig, tx) } @@ -386,6 +418,7 @@ func getOldTxFromChain(s *Suite) *types.Transaction { return txs[0] } } + return nil } @@ -394,11 +427,14 @@ func invalidNonceTx(s *Suite) *types.Transaction { if tx == nil { return nil } + 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(s.chain.chainConfig, txNew) } @@ -407,12 +443,16 @@ func hugeAmount(s *Suite) *types.Transaction { if tx == nil { return nil } + 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(s.chain.chainConfig, txNew) } @@ -421,12 +461,16 @@ func hugeGasPrice(s *Suite) *types.Transaction { if tx == nil { return nil } + 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(s.chain.chainConfig, txNew) } @@ -435,19 +479,24 @@ func hugeData(s *Suite) *types.Transaction { if tx == nil { return nil } + 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(s.chain.chainConfig, txNew) } func signWithFaucet(chainConfig *params.ChainConfig, tx *types.Transaction) *types.Transaction { signer := types.LatestSigner(chainConfig) + signedTx, err := types.SignTx(tx, signer, faucetKey) if err != nil { return nil } + return signedTx } diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index 6067fc9cd9..f0d2e37ef6 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -167,6 +167,7 @@ func (c *Conn) Read() Message { } var msg Message + switch int(code) { case (Hello{}).Code(): msg = new(Hello) @@ -255,7 +256,9 @@ func (c *Conn) Write(msg Message) error { if err != nil { return err } + _, err = c.Conn.Write(uint64(msg.Code()), payload) + return err } @@ -263,11 +266,13 @@ func (c *Conn) Write(msg Message) error { func (c *Conn) ReadSnap(id uint64) (Message, error) { respId := id + 1 start := time.Now() + for respId != id && time.Since(start) < timeout { code, rawData, _, err := c.Conn.Read() if err != nil { return nil, fmt.Errorf("could not read from connection: %v", err) } + var snpMsg interface{} switch int(code) { case (GetAccountRange{}).Code(): @@ -290,10 +295,13 @@ func (c *Conn) ReadSnap(id uint64) (Message, error) { //return nil, fmt.Errorf("invalid message code: %d", code) continue } + if err := rlp.DecodeBytes(rawData, snpMsg); err != nil { return nil, fmt.Errorf("could not rlp decode message: %v", err) } + return snpMsg.(Message), nil } + return nil, fmt.Errorf("request timed out") } diff --git a/cmd/devp2p/internal/v4test/discv4tests.go b/cmd/devp2p/internal/v4test/discv4tests.go index d799e4194b..88b331433d 100644 --- a/cmd/devp2p/internal/v4test/discv4tests.go +++ b/cmd/devp2p/internal/v4test/discv4tests.go @@ -92,16 +92,19 @@ func (te *testenv) checkPingPong(pingHash []byte) error { pings int pongs int ) + for i := 0; i < 2; i++ { reply, _, err := te.read(te.l1) if err != nil { return err } + switch reply.Kind() { case v4wire.PongPacket: if err := te.checkPong(reply, pingHash); err != nil { return err } + pongs++ case v4wire.PingPacket: pings++ @@ -109,9 +112,11 @@ func (te *testenv) checkPingPong(pingHash []byte) error { return fmt.Errorf("expected PING or PONG, got %v %v", reply.Name(), reply) } } + if pongs == 1 && pings == 1 { return nil } + return fmt.Errorf("expected 1 PING (got %d) and 1 PONG (got %d)", pings, pongs) } @@ -121,19 +126,24 @@ func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error { if reply == nil { return fmt.Errorf("expected PONG reply, got nil") } + if reply.Kind() != v4wire.PongPacket { return fmt.Errorf("expected PONG reply, got %v %v", reply.Name(), reply) } + pong := reply.(*v4wire.Pong) if !bytes.Equal(pong.ReplyTok, pingHash) { return fmt.Errorf("PONG reply token mismatch: got %x, want %x", pong.ReplyTok, pingHash) } + if want := te.localEndpoint(te.l1); !want.IP.Equal(pong.To.IP) || want.UDP != pong.To.UDP { return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, want) } + if v4wire.Expired(pong.Expiration) { return fmt.Errorf("PONG is expired (%v)", pong.Expiration) } + return nil } @@ -143,6 +153,7 @@ func PingWrongTo(t *utesting.T) { defer te.close() wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ Version: 4, From: te.localEndpoint(te.l1), @@ -208,6 +219,7 @@ func PingExtraDataWrongFrom(t *utesting.T) { JunkData1: 42, JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, } + pingHash := te.send(te.l1, &req) if err := te.checkPingPong(pingHash); err != nil { t.Fatal(err) @@ -304,9 +316,11 @@ func FindnodeWithoutEndpointProof(t *utesting.T) { // No response, all good break } + if reply.Kind() == v4wire.PingPacket { continue // A ping is ok, just ignore it } + t.Fatalf("Expected no reply, got %v %v", reply.Name(), reply) } } @@ -326,6 +340,7 @@ func BasicFindnode(t *utesting.T) { if err != nil { t.Fatal("read find nodes", err) } + if reply.Kind() != v4wire.NeighborsPacket { t.Fatalf("Expected neighbors, got %v %v", reply.Name(), reply) } @@ -363,9 +378,11 @@ func UnsolicitedNeighbors(t *utesting.T) { if err != nil { t.Fatal("read find nodes", err) } + if reply.Kind() != v4wire.NeighborsPacket { t.Fatalf("Expected neighbors, got %v %v", reply.Name(), reply) } + nodes := reply.(*v4wire.Neighbors).Nodes if contains(nodes, encFakeKey) { t.Fatal("neighbors response contains node from earlier unsolicited neighbors response") @@ -408,6 +425,7 @@ func bond(t *utesting.T, te *testenv) { if err != nil { t.Fatal(err) } + switch req.(type) { case *v4wire.Ping: te.send(te.l1, &v4wire.Pong{ @@ -415,11 +433,13 @@ func bond(t *utesting.T, te *testenv) { ReplyTok: hash, Expiration: futureExpiration(), }) + gotPing = true case *v4wire.Pong: if err := te.checkPong(req, pingHash); err != nil { t.Fatal(err) } + gotPong = true } } @@ -450,6 +470,7 @@ func FindnodeAmplificationInvalidPongHash(t *utesting.T) { if err != nil { t.Fatal(err) } + switch req.(type) { case *v4wire.Ping: // Send PONG from this node ID, but with invalid ReplyTok. @@ -458,6 +479,7 @@ func FindnodeAmplificationInvalidPongHash(t *utesting.T) { ReplyTok: make([]byte, macSize), Expiration: futureExpiration(), }) + gotPing = true case *v4wire.Pong: gotPong = true diff --git a/cmd/devp2p/internal/v4test/framework.go b/cmd/devp2p/internal/v4test/framework.go index 9286594181..548e449b69 100644 --- a/cmd/devp2p/internal/v4test/framework.go +++ b/cmd/devp2p/internal/v4test/framework.go @@ -41,33 +41,44 @@ func newTestEnv(remote string, listen1, listen2 string) *testenv { if err != nil { panic(err) } + l2, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", listen2)) if err != nil { panic(err) } + key, err := crypto.GenerateKey() if err != nil { panic(err) } + node, err := enode.Parse(enode.ValidSchemes, remote) if err != nil { panic(err) } + if node.IP() == nil || node.UDP() == 0 { var ip net.IP + var tcpPort, udpPort int + if ip = node.IP(); ip == nil { ip = net.ParseIP("127.0.0.1") } + if tcpPort = node.TCP(); tcpPort == 0 { tcpPort = 30303 } + if udpPort = node.TCP(); udpPort == 0 { udpPort = 30303 } + node = enode.NewV4(node.Pubkey(), ip, tcpPort, udpPort) } + addr := &net.UDPAddr{IP: node.IP(), Port: node.UDP()} + return &testenv{l1, l2, key, node, addr} } @@ -81,27 +92,34 @@ func (te *testenv) send(c net.PacketConn, req v4wire.Packet) []byte { if err != nil { panic(fmt.Errorf("can't encode %v packet: %v", req.Name(), err)) } + if _, err := c.WriteTo(packet, te.remoteAddr); err != nil { panic(fmt.Errorf("can't send %v: %v", req.Name(), err)) } + return hash } func (te *testenv) read(c net.PacketConn) (v4wire.Packet, []byte, error) { buf := make([]byte, 2048) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { return nil, nil, err } + n, _, err := c.ReadFrom(buf) if err != nil { return nil, nil, err } + p, _, hash, err := v4wire.Decode(buf[:n]) + return p, hash, err } func (te *testenv) localEndpoint(c net.PacketConn) v4wire.Endpoint { addr := c.LocalAddr().(*net.UDPAddr) + return v4wire.Endpoint{ IP: addr.IP.To4(), UDP: uint16(addr.Port), @@ -119,5 +137,6 @@ func contains(ns []v4wire.Node, key v4wire.Pubkey) bool { return true } } + return false } diff --git a/cmd/devp2p/internal/v5test/discv5tests.go b/cmd/devp2p/internal/v5test/discv5tests.go index 56624a0ca8..8ef7ee2a3b 100644 --- a/cmd/devp2p/internal/v5test/discv5tests.go +++ b/cmd/devp2p/internal/v5test/discv5tests.go @@ -37,12 +37,14 @@ type Suite struct { func (s *Suite) listen1(log logger) (*conn, net.PacketConn) { c := newConn(s.Dest, log) l := c.listen(s.Listen1) + return c, l } func (s *Suite) listen2(log logger) (*conn, net.PacketConn, net.PacketConn) { c := newConn(s.Dest, log) l1, l2 := c.listen(s.Listen1), c.listen(s.Listen2) + return c, l1, l2 } @@ -76,9 +78,11 @@ func checkPong(t *utesting.T, pong *v5wire.Pong, ping *v5wire.Ping, c net.Packet if !bytes.Equal(pong.ReqID, ping.ReqID) { t.Fatalf("wrong request ID %x in PONG, want %x", pong.ReqID, ping.ReqID) } + if !pong.ToIP.Equal(laddr(c).IP) { t.Fatalf("wrong destination IP %v in PONG, want %v", pong.ToIP, laddr(c).IP) } + if int(pong.ToPort) != laddr(c).Port { t.Fatalf("wrong destination port %v in PONG, want %v", pong.ToPort, laddr(c).Port) } @@ -112,20 +116,24 @@ func (s *Suite) TestPingMultiIP(t *utesting.T) { // Create the session on l1. ping := &v5wire.Ping{ReqID: conn.nextReqID()} + resp := conn.reqresp(l1, ping) if resp.Kind() != v5wire.PongMsg { t.Fatal("expected PONG, got", resp) } + checkPong(t, resp.(*v5wire.Pong), ping, l1) // Send on l2. This reuses the session because there is only one codec. ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} conn.write(l2, ping2, nil) + switch resp := conn.read(l2).(type) { case *v5wire.Pong: t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l2).IP, laddr(l1).IP) case *v5wire.Whoareyou: t.Logf("got WHOAREYOU for new session as expected") + resp.Node = s.Dest conn.write(l2, ping2, resp) default: @@ -143,6 +151,7 @@ func (s *Suite) TestPingMultiIP(t *utesting.T) { // Try on l1 again. ping3 := &v5wire.Ping{ReqID: conn.nextReqID()} conn.write(l1, ping3, nil) + switch resp := conn.read(l1).(type) { case *v5wire.Pong: t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l1).IP, laddr(l2).IP) @@ -163,6 +172,7 @@ func (s *Suite) TestPingHandshakeInterrupted(t *utesting.T) { // First PING triggers challenge. ping := &v5wire.Ping{ReqID: conn.nextReqID()} conn.write(l1, ping, nil) + switch resp := conn.read(l1).(type) { case *v5wire.Whoareyou: t.Logf("got WHOAREYOU for PING") @@ -188,11 +198,13 @@ func (s *Suite) TestTalkRequest(t *utesting.T) { // Non-empty request ID. id := conn.nextReqID() resp := conn.reqresp(l1, &v5wire.TalkRequest{ReqID: id, Protocol: "test-protocol"}) + switch resp := resp.(type) { case *v5wire.TalkResponse: if !bytes.Equal(resp.ReqID, id) { t.Fatalf("wrong request ID %x in TALKRESP, want %x", resp.ReqID, id) } + if len(resp.Message) > 0 { t.Fatalf("non-empty message %x in TALKRESP", resp.Message) } @@ -207,6 +219,7 @@ func (s *Suite) TestTalkRequest(t *utesting.T) { if len(resp.ReqID) > 0 { t.Fatalf("wrong request ID %x in TALKRESP, want empty byte array", resp.ReqID) } + if len(resp.Message) > 0 { t.Fatalf("non-empty message %x in TALKRESP", resp.Message) } @@ -224,9 +237,11 @@ func (s *Suite) TestFindnodeZeroDistance(t *utesting.T) { if err != nil { t.Fatal(err) } + if len(nodes) != 1 { t.Fatalf("remote returned more than one node for FINDNODE [0]") } + if nodes[0].ID() != conn.remote.ID() { t.Errorf("ID of response node is %v, want %v", nodes[0].ID(), conn.remote.ID()) } @@ -238,6 +253,7 @@ func (s *Suite) TestFindnodeResults(t *utesting.T) { // Create bystanders. nodes := make([]*bystander, 5) added := make(chan enode.ID, len(nodes)) + for i := range nodes { nodes[i] = newBystander(t, s, added) defer nodes[i].close() @@ -246,14 +262,17 @@ func (s *Suite) TestFindnodeResults(t *utesting.T) { // Get them added to the remote table. timeout := 60 * time.Second timeoutCh := time.After(timeout) + for count := 0; count < len(nodes); { select { case id := <-added: t.Logf("bystander node %v added to remote table", id) + count++ case <-timeoutCh: t.Errorf("remote added %d bystander nodes in %v, need %d to continue", count, timeout, len(nodes)) t.Logf("this can happen if the node has a non-empty table from previous runs") + return } } @@ -261,10 +280,13 @@ func (s *Suite) TestFindnodeResults(t *utesting.T) { // Collect our nodes by distance. var dists []uint + expect := make(map[enode.ID]*enode.Node) + for _, bn := range nodes { n := bn.conn.localNode.Node() expect[n.ID()] = n + d := uint(enode.LogDist(n.ID(), s.Dest.ID())) if !containsUint(dists, d) { dists = append(dists, d) @@ -274,14 +296,18 @@ func (s *Suite) TestFindnodeResults(t *utesting.T) { // Send FINDNODE for all distances. conn, l1 := s.listen1(t) defer conn.close() + foundNodes, err := conn.findnode(l1, dists) if err != nil { t.Fatal(err) } + t.Logf("remote returned %d nodes for distance list %v", len(foundNodes), dists) + for _, n := range foundNodes { delete(expect, n.ID()) } + if len(expect) > 0 { t.Errorf("missing %d nodes in FINDNODE result", len(expect)) t.Logf("this can happen if the test is run multiple times in quick succession") @@ -311,7 +337,9 @@ func newBystander(t *utesting.T, s *Suite, added chan enode.ID) *bystander { addedCh: added, } bn.done.Add(1) + go bn.loop() + return bn } @@ -334,6 +362,7 @@ func (bn *bystander) loop() { lastPing time.Time wasAdded bool ) + for { // Ping the remote node. if !wasAdded && time.Since(lastPing) > 10*time.Second { @@ -341,6 +370,7 @@ func (bn *bystander) loop() { ReqID: bn.conn.nextReqID(), ENRSeq: bn.dest.Seq(), }) + lastPing = time.Now() } // Answer packets. @@ -352,11 +382,15 @@ func (bn *bystander) loop() { ToIP: bn.dest.IP(), ToPort: uint16(bn.dest.UDP()), }, nil) + wasAdded = true + bn.notifyAdded() case *v5wire.Findnode: bn.conn.write(bn.l, &v5wire.Nodes{ReqID: p.ReqID, RespCount: 1}, nil) + wasAdded = true + bn.notifyAdded() case *v5wire.TalkRequest: bn.conn.write(bn.l, &v5wire.TalkResponse{ReqID: p.ReqID}, nil) diff --git a/cmd/devp2p/internal/v5test/framework.go b/cmd/devp2p/internal/v5test/framework.go index 065bb8ae36..34d52451a7 100644 --- a/cmd/devp2p/internal/v5test/framework.go +++ b/cmd/devp2p/internal/v5test/framework.go @@ -77,10 +77,12 @@ func newConn(dest *enode.Node, log logger) *conn { if err != nil { panic(err) } + db, err := enode.OpenDB("") if err != nil { panic(err) } + ln := enode.NewLocalNode(db, key) return &conn{ @@ -103,7 +105,9 @@ func (tc *conn) listen(ip string) net.PacketConn { if err != nil { panic(err) } + tc.listeners = append(tc.listeners, l) + return l } @@ -112,6 +116,7 @@ func (tc *conn) close() { for _, l := range tc.listeners { l.Close() } + tc.localNode.Database().Close() } @@ -120,6 +125,7 @@ func (tc *conn) nextReqID() []byte { id := make([]byte, 4) tc.idCounter++ binary.BigEndian.PutUint32(id, tc.idCounter) + return id } @@ -132,8 +138,10 @@ func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet { if resp.Nonce != reqnonce { return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:]) } + resp.Node = tc.remote tc.write(c, req, resp) + return tc.read(c) default: return resp @@ -149,6 +157,7 @@ func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error) total uint8 results []*enode.Node ) + for n := 1; n > 0; { switch resp := tc.read(c).(type) { case *v5wire.Whoareyou: @@ -182,6 +191,7 @@ func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error) first = false } else { n-- + if resp.RespCount != total { return nil, fmt.Errorf("invalid NODES response count %d (!= %d)", resp.RespCount, total) } @@ -191,11 +201,13 @@ func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error) if err != nil { return nil, fmt.Errorf("invalid node in NODES response: %v", err) } + results = append(results, nodes...) default: return nil, fmt.Errorf("expected NODES, got %v", resp) } } + return results, nil } @@ -205,29 +217,36 @@ func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoar if err != nil { panic(fmt.Errorf("can't encode %v packet: %v", p.Name(), err)) } + if _, err := c.WriteTo(packet, tc.remoteAddr); err != nil { tc.logf("Can't send %s: %v", p.Name(), err) } else { tc.logf(">> %s", p.Name()) } + return nonce } // read waits for an incoming packet on the given connection. func (tc *conn) read(c net.PacketConn) v5wire.Packet { buf := make([]byte, 1280) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { return &readError{err} } + n, fromAddr, err := c.ReadFrom(buf) if err != nil { return &readError{err} } + _, _, p, err := tc.codec.Decode(buf[:n], fromAddr.String()) if err != nil { return &readError{err} } + tc.logf("<< %s", p.Name()) + return p } @@ -244,13 +263,16 @@ func laddr(c net.PacketConn) *net.UDPAddr { func checkRecords(records []*enr.Record) ([]*enode.Node, error) { nodes := make([]*enode.Node, len(records)) + for i := range records { n, err := enode.New(enode.ValidSchemes, records[i]) if err != nil { return nil, err } + nodes[i] = n } + return nodes, nil } @@ -260,5 +282,6 @@ func containsUint(ints []uint, x uint) bool { return true } } + return false } diff --git a/cmd/devp2p/keycmd.go b/cmd/devp2p/keycmd.go index 79d189c5a4..b80b0d0e52 100644 --- a/cmd/devp2p/keycmd.go +++ b/cmd/devp2p/keycmd.go @@ -89,12 +89,14 @@ func genkey(ctx *cli.Context) error { if ctx.NArg() != 1 { return fmt.Errorf("need key file as argument") } + file := ctx.Args().Get(0) key, err := crypto.GenerateKey() if err != nil { return fmt.Errorf("could not generate key: %v", err) } + return crypto.SaveECDSA(file, key) } @@ -142,6 +144,7 @@ func makeRecord(ctx *cli.Context) (*enode.Node, error) { tcp = ctx.Int(tcpPortFlag.Name) udp = ctx.Int(udpPortFlag.Name) ) + key, err := crypto.LoadECDSA(file) if err != nil { return nil, err diff --git a/cmd/devp2p/main.go b/cmd/devp2p/main.go index 58411f0f0b..1a489033b3 100644 --- a/cmd/devp2p/main.go +++ b/cmd/devp2p/main.go @@ -88,6 +88,7 @@ func getNodeArg(ctx *cli.Context) *enode.Node { if err != nil { exit(err) } + return n } @@ -95,6 +96,7 @@ func exit(err interface{}) { if err == nil { os.Exit(0) } + fmt.Fprintln(os.Stderr, err) os.Exit(1) } diff --git a/cmd/devp2p/nodeset.go b/cmd/devp2p/nodeset.go index 6af6baa7ea..0f98315780 100644 --- a/cmd/devp2p/nodeset.go +++ b/cmd/devp2p/nodeset.go @@ -53,6 +53,7 @@ func loadNodesJSON(file string) nodeSet { if err := common.LoadJSON(file, &nodes); err != nil { exit(err) } + return nodes } @@ -61,6 +62,7 @@ func writeNodesJSON(file string, nodes nodeSet) { if err != nil { exit(err) } + if file == "-" { os.Stdout.Write(nodesJSON) return @@ -81,6 +83,7 @@ func (ns nodeSet) nodes() []*enode.Node { sort.Slice(result, func(i, j int) bool { return bytes.Compare(result[i].ID().Bytes(), result[j].ID().Bytes()) < 0 }) + return result } @@ -104,13 +107,16 @@ func (ns nodeSet) topN(n int) nodeSet { for _, v := range ns { byscore = append(byscore, v) } + sort.Slice(byscore, func(i, j int) bool { return byscore[i].Score >= byscore[j].Score }) + result := make(nodeSet, n) for _, v := range byscore[:n] { result[v.N.ID()] = v } + return result } @@ -120,9 +126,11 @@ func (ns nodeSet) verify() error { if n.N.ID() != id { return fmt.Errorf("invalid node %v: ID does not match ID %v in record", id, n.N.ID()) } + if n.N.Seq() != n.Seq { return fmt.Errorf("invalid node %v: 'seq' does not match seq %d from record", id, n.N.Seq()) } } + return nil } diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index 5d6977bcf0..688864f652 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -66,15 +66,19 @@ func nodesetInfo(ctx *cli.Context) error { ns := loadNodesJSON(ctx.Args().First()) fmt.Printf("Set contains %d nodes.\n", len(ns)) showAttributeCounts(ns) + return nil } // showAttributeCounts prints the distribution of ENR attributes in a node set. func showAttributeCounts(ns nodeSet) { attrcount := make(map[string]int) + var attrlist []interface{} + for _, n := range ns { r := n.N.Record() + attrlist = r.AppendElements(attrlist[:0])[1:] for i := 0; i < len(attrlist); i += 2 { key := attrlist[i].(string) @@ -83,15 +87,20 @@ func showAttributeCounts(ns nodeSet) { } var keys []string + var maxlength int + for key := range attrcount { keys = append(keys, key) + if len(key) > maxlength { maxlength = len(key) } } + sort.Strings(keys) fmt.Println("ENR attribute counts:") + for _, key := range keys { fmt.Printf("%s%s: %d\n", strings.Repeat(" ", maxlength-len(key)+1), key, attrcount[key]) } @@ -115,15 +124,19 @@ func nodesetFilter(ctx *cli.Context) error { // Load nodes and apply filters. ns := loadNodesJSON(ctx.Args().First()) result := make(nodeSet) + for id, n := range ns { if filter(n) { result[id] = n } } + if limit >= 0 { result = result.topN(limit) } + writeNodesJSON("-", result) + return nil } @@ -146,39 +159,48 @@ var filterFlags = map[string]nodeFilterC{ // parseFilters parses nodeFilters from args. func parseFilters(args []string) ([]nodeFilter, error) { var filters []nodeFilter + for len(args) > 0 { fc, ok := filterFlags[args[0]] if !ok { return nil, fmt.Errorf("invalid filter %q", args[0]) } + 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 : 1+fc.narg]) if err != nil { return nil, fmt.Errorf("%s: %v", args[0], err) } + filters = append(filters, filter) args = args[1+fc.narg:] } + return filters, nil } // parseFilterLimit parses the -limit option in args. It returns -1 if there is no limit. func parseFilterLimit(args []string) (int, error) { limit := -1 + for i, arg := range args { if arg == "-limit" { if i == len(args)-1 { return -1, errors.New("-limit requires an argument") } + n, err := strconv.Atoi(args[i+1]) if err != nil { return -1, fmt.Errorf("invalid -limit %q", args[i+1]) } + limit = n } } + return limit, nil } @@ -189,14 +211,17 @@ func andFilter(args []string) (nodeFilter, error) { if err != nil { return nil, err } + f := func(n nodeJSON) bool { for _, filter := range checks { if !filter(n) { return false } } + return true } + return f, nil } @@ -209,7 +234,9 @@ func ipFilter(args []string) (nodeFilter, error) { if err != nil { return nil, err } + f := func(n nodeJSON) bool { return cidr.Contains(n.N.IP()) } + return f, nil } @@ -218,15 +245,18 @@ func minAgeFilter(args []string) (nodeFilter, error) { if err != nil { return nil, err } + f := func(n nodeJSON) bool { age := n.LastResponse.Sub(n.FirstResponse) return age >= minage } + return f, nil } func ethFilter(args []string) (nodeFilter, error) { var filter forkid.Filter + switch args[0] { case "mainnet": filter = forkid.NewStaticFilter(params.MainnetChainConfig, params.MainnetGenesisHash) @@ -247,11 +277,14 @@ func ethFilter(args []string) (nodeFilter, error) { ForkID forkid.ID Tail []rlp.RawValue `rlp:"tail"` } + if n.N.Load(enr.WithEntry("eth", ð)) != nil { return false } + return filter(eth.ForkID) == nil } + return f, nil } @@ -260,8 +293,10 @@ func lesFilter(args []string) (nodeFilter, error) { var les struct { Tail []rlp.RawValue `rlp:"tail"` } + return n.N.Load(enr.WithEntry("les", &les)) == nil } + return f, nil } @@ -270,7 +305,9 @@ func snapFilter(args []string) (nodeFilter, error) { var snap struct { Tail []rlp.RawValue `rlp:"tail"` } + return n.N.Load(enr.WithEntry("snap", &snap)) == nil } + return f, nil } diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go index 81fa04c2a4..c60b8622f7 100644 --- a/cmd/devp2p/rlpxcmd.go +++ b/cmd/devp2p/rlpxcmd.go @@ -68,36 +68,44 @@ var ( func rlpxPing(ctx *cli.Context) error { n := getNodeArg(ctx) + fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", n.IP(), n.TCP())) if err != nil { return err } + conn := rlpx.NewConn(fd, n.Pubkey()) ourKey, _ := crypto.GenerateKey() + _, err = conn.Handshake(ourKey) if err != nil { return err } + code, data, _, err := conn.Read() if err != nil { return err } + switch code { case 0: var h ethtest.Hello if err := rlp.DecodeBytes(data, &h); err != nil { return fmt.Errorf("invalid handshake: %v", err) } + fmt.Printf("%+v\n", h) case 1: var msg []p2p.DiscReason if rlp.DecodeBytes(data, &msg); len(msg) == 0 { return fmt.Errorf("invalid disconnect message") } + return fmt.Errorf("received disconnect message: %v", msg[0]) default: return fmt.Errorf("invalid message code %d, expected handshake (code zero)", code) } + return nil } @@ -125,5 +133,6 @@ func rlpxSnapTest(ctx *cli.Context) error { if err != nil { exit(err) } + return runTests(ctx, suite.SnapTests()) } diff --git a/cmd/devp2p/runtest.go b/cmd/devp2p/runtest.go index b1374678aa..fa2a3f885d 100644 --- a/cmd/devp2p/runtest.go +++ b/cmd/devp2p/runtest.go @@ -62,9 +62,11 @@ func runTests(ctx *cli.Context, tests []utesting.Test) error { if ctx.Bool(testTAPFlag.Name) { run = utesting.RunTAP } + results := run(tests, os.Stdout) if utesting.CountFailures(results) > 0 { os.Exit(1) } + return nil } diff --git a/cmd/ethkey/message.go b/cmd/ethkey/message.go index 387fc3873a..5616fce32c 100644 --- a/cmd/ethkey/message.go +++ b/cmd/ethkey/message.go @@ -153,11 +153,13 @@ func getMessage(ctx *cli.Context, msgarg int) []byte { if err != nil { utils.Fatalf("Can't read message file: %v", err) } + return msg } else if ctx.NArg() == msgarg+1 { return []byte(ctx.Args().Get(msgarg)) } utils.Fatalf("Invalid number of arguments: want %d, got %d", msgarg+1, ctx.NArg()) + return nil } diff --git a/cmd/ethkey/message_test.go b/cmd/ethkey/message_test.go index 544a494cfa..9861da5cc8 100644 --- a/cmd/ethkey/message_test.go +++ b/cmd/ethkey/message_test.go @@ -34,8 +34,10 @@ func TestMessageSignVerify(t *testing.T) { Password: {{.InputLine "foobar"}} Repeat password: {{.InputLine "foobar"}} `) + _, matches := generate.ExpectRegexp(`Address: (0x[0-9a-fA-F]{40})\n`) address := matches[1] + generate.ExpectExit() // Sign a message. @@ -44,8 +46,10 @@ Repeat password: {{.InputLine "foobar"}} !! Unsupported terminal, password will be echoed. Password: {{.InputLine "foobar"}} `) + _, matches = sign.ExpectRegexp(`Signature: ([0-9a-f]+)\n`) signature := matches[1] + sign.ExpectExit() // Verify the message. @@ -56,6 +60,7 @@ Recovered public key: [0-9a-f]+ Recovered address: (0x[0-9a-fA-F]{40}) `) recovered := matches[1] + verify.ExpectExit() if recovered != address { diff --git a/cmd/ethkey/run_test.go b/cmd/ethkey/run_test.go index 6006f6b5bb..2528c0f094 100644 --- a/cmd/ethkey/run_test.go +++ b/cmd/ethkey/run_test.go @@ -34,6 +34,7 @@ func runEthkey(t *testing.T, args ...string) *testEthkey { tt := new(testEthkey) tt.TestCmd = cmdtest.NewTestCmd(t, tt) tt.Run("ethkey-test", args...) + return tt } @@ -44,11 +45,13 @@ func TestMain(m *testing.M) { fmt.Fprintln(os.Stderr, err) os.Exit(1) } + os.Exit(0) }) // check if we have been reexec'd if reexec.Init() { return } + os.Exit(m.Run()) } diff --git a/cmd/ethkey/utils.go b/cmd/ethkey/utils.go index aef0403e9a..eec161c23c 100644 --- a/cmd/ethkey/utils.go +++ b/cmd/ethkey/utils.go @@ -39,6 +39,7 @@ func getPassphrase(ctx *cli.Context, confirmation bool) string { utils.Fatalf("Failed to read password file '%s': %v", passphraseFile, err) } + return strings.TrimRight(string(content), "\r\n") } @@ -53,5 +54,6 @@ func mustPrintJSON(jsonObject interface{}) { if err != nil { utils.Fatalf("Failed to marshal JSON object: %v", err) } + fmt.Println(string(str)) } diff --git a/cmd/evm/compiler.go b/cmd/evm/compiler.go index 699d434bb0..7cf22bf8ba 100644 --- a/cmd/evm/compiler.go +++ b/cmd/evm/compiler.go @@ -41,6 +41,7 @@ func compileCmd(ctx *cli.Context) error { } fn := ctx.Args().First() + src, err := os.ReadFile(fn) if err != nil { return err @@ -50,6 +51,8 @@ func compileCmd(ctx *cli.Context) error { if err != nil { return err } + fmt.Println(bin) + return nil } diff --git a/cmd/evm/disasm.go b/cmd/evm/disasm.go index 92897a1637..78e934930c 100644 --- a/cmd/evm/disasm.go +++ b/cmd/evm/disasm.go @@ -36,13 +36,16 @@ var disasmCommand = &cli.Command{ func disasmCmd(ctx *cli.Context) error { var in string + switch { case len(ctx.Args().First()) > 0: fn := ctx.Args().First() + input, err := os.ReadFile(fn) if err != nil { return err } + in = string(input) case ctx.IsSet(InputFlag.Name): in = ctx.String(InputFlag.Name) @@ -52,5 +55,6 @@ func disasmCmd(ctx *cli.Context) error { code := strings.TrimSpace(in) fmt.Printf("%v\n", code) + return asm.PrintDisassembled(code) } diff --git a/cmd/evm/internal/compiler/compiler.go b/cmd/evm/internal/compiler/compiler.go index 54981b6697..1daa0edfac 100644 --- a/cmd/evm/internal/compiler/compiler.go +++ b/cmd/evm/internal/compiler/compiler.go @@ -33,7 +33,9 @@ func Compile(fn string, src []byte, debug bool) (string, error) { for _, err := range compileErrors { fmt.Printf("%s:%v\n", fn, err) } + return "", errors.New("compiling failed") } + return bin, nil } diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go index d6ec6d3f4e..be44b51c25 100644 --- a/cmd/evm/internal/t8ntool/block.go +++ b/cmd/evm/internal/t8ntool/block.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/urfave/cli/v2" "math/big" "os" @@ -96,20 +97,25 @@ func (c *cliqueInput) UnmarshalJSON(input []byte) error { Authorize *bool `json:"authorize"` Vanity common.Hash `json:"vanity"` } + if err := json.Unmarshal(input, &x); err != nil { return err } + if x.Key == nil { return errors.New("missing required field 'secretKey' for cliqueInput") } + if ecdsaKey, err := crypto.ToECDSA(x.Key[:]); err != nil { return err } else { c.Key = ecdsaKey } + c.Voted = x.Voted c.Authorize = x.Authorize c.Vanity = x.Vanity + return nil } @@ -141,18 +147,23 @@ func (i *bbInput) ToBlock() *types.Block { // Calculate the ommer hash if none is provided and there are ommers to hash header.UncleHash = types.CalcUncleHash(i.Ommers) } + if i.Header.Coinbase != nil { header.Coinbase = *i.Header.Coinbase } + if i.Header.TxHash != nil { header.TxHash = *i.Header.TxHash } + if i.Header.ReceiptHash != nil { header.ReceiptHash = *i.Header.ReceiptHash } + if i.Header.Nonce != nil { header.Nonce = *i.Header.Nonce } + if header.Difficulty != nil { header.Difficulty = i.Header.Difficulty } @@ -177,6 +188,7 @@ func (i *bbInput) sealEthash(block *types.Block) (*types.Block, error) { if i.Header.Nonce != nil { return nil, NewError(ErrorConfig, fmt.Errorf("sealing with ethash will overwrite provided nonce")) } + ethashConfig := ethash.Config{ PowMode: i.PowMode, DatasetDir: i.EthashDir, @@ -186,6 +198,7 @@ func (i *bbInput) sealEthash(block *types.Block) (*types.Block, error) { CachesInMem: 2, CachesOnDisk: 3, } + engine := ethash.New(ethashConfig, nil, true) defer engine.Close() // Use a buffered chan for results. @@ -195,7 +208,9 @@ func (i *bbInput) sealEthash(block *types.Block) (*types.Block, error) { if err := engine.Seal(context.Background(), nil, block, results, nil); err != nil { panic(fmt.Sprintf("failed to seal block: %v", err)) } + found := <-results + return block.WithSeal(found.Header()), nil } @@ -206,17 +221,22 @@ func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) { if i.Header.Extra != nil { return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique will overwrite provided extra data")) } + header := block.Header() + if i.Clique.Voted != nil { if i.Header.Coinbase != nil { return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique and voting will overwrite provided coinbase")) } + header.Coinbase = *i.Clique.Voted } + if i.Clique.Authorize != nil { if i.Header.Nonce != nil { return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique and voting will overwrite provided nonce")) } + if *i.Clique.Authorize { header.Nonce = [8]byte{} } else { @@ -229,12 +249,15 @@ func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) { // Sign the seal hash and fill in the rest of the extra data h := clique.SealHash(header) + sighash, err := crypto.Sign(h[:], i.Clique.Key) if err != nil { return nil, err } + copy(header.Extra[32:], sighash) block = block.WithSeal(header) + return block, nil } @@ -249,15 +272,19 @@ func BuildBlock(ctx *cli.Context) error { if err != nil { return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) } + inputData, err := readInput(ctx) if err != nil { return err } + block := inputData.ToBlock() + block, err = inputData.SealBlock(block) if err != nil { return err } + return dispatchBlock(ctx, baseDir, block) } @@ -273,12 +300,15 @@ func readInput(ctx *cli.Context) (*bbInput, error) { ethashMode = ctx.String(SealEthashModeFlag.Name) inputData = &bbInput{} ) + if ethashOn && cliqueStr != "" { return nil, NewError(ErrorConfig, fmt.Errorf("both ethash and clique sealing specified, only one may be chosen")) } + if ethashOn { inputData.Ethash = ethashOn inputData.EthashDir = ethashDir + switch ethashMode { case "normal": inputData.PowMode = ethash.ModeNormal @@ -290,31 +320,38 @@ func readInput(ctx *cli.Context) (*bbInput, error) { return nil, NewError(ErrorConfig, fmt.Errorf("unknown pow mode: %s, supported modes: test, fake, normal", ethashMode)) } } + if headerStr == stdinSelector || ommersStr == stdinSelector || txsStr == stdinSelector || cliqueStr == stdinSelector { decoder := json.NewDecoder(os.Stdin) if err := decoder.Decode(inputData); err != nil { return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err)) } } + if cliqueStr != stdinSelector && cliqueStr != "" { var clique cliqueInput if err := readFile(cliqueStr, "clique", &clique); err != nil { return nil, err } + inputData.Clique = &clique } + if headerStr != stdinSelector { var env header if err := readFile(headerStr, "header", &env); err != nil { return nil, err } + inputData.Header = &env } + if ommersStr != stdinSelector && ommersStr != "" { var ommers []string if err := readFile(ommersStr, "ommers", &ommers); err != nil { return nil, err } + inputData.OmmersRlp = ommers } @@ -326,11 +363,13 @@ func readInput(ctx *cli.Context) (*bbInput, error) { inputData.Withdrawals = withdrawals } + if txsStr != stdinSelector { var txs string if err := readFile(txsStr, "txs", &txs); err != nil { return nil, err } + inputData.TxRlp = txs } // Deserialize rlp txs and ommers @@ -338,24 +377,30 @@ func readInput(ctx *cli.Context) (*bbInput, error) { ommers = []*types.Header{} txs = []*types.Transaction{} ) + if inputData.TxRlp != "" { if err := rlp.DecodeBytes(common.FromHex(inputData.TxRlp), &txs); err != nil { return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode transaction from rlp data: %v", err)) } + inputData.Txs = txs } + for _, str := range inputData.OmmersRlp { type extblock struct { Header *types.Header Txs []*types.Transaction Ommers []*types.Header } + var ommer *extblock if err := rlp.DecodeBytes(common.FromHex(str), &ommer); err != nil { return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode ommer from rlp data: %v", err)) } + ommers = append(ommers, ommer.Header) } + inputData.Ommers = ommers return inputData, nil @@ -365,6 +410,7 @@ func readInput(ctx *cli.Context) (*bbInput, error) { // files func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error { raw, _ := rlp.EncodeToBytes(block) + type blockInfo struct { Rlp hexutil.Bytes `json:"rlp"` Hash common.Hash `json:"hash"` @@ -374,10 +420,12 @@ func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error { Rlp: raw, Hash: block.Hash(), } + b, err := json.MarshalIndent(enc, "", " ") if err != nil { return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) } + switch dest := ctx.String(OutputBlockFlag.Name); dest { case "stdout": os.Stdout.Write(b) @@ -390,5 +438,6 @@ func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error { return err } } + return nil } diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 853143ae4e..a81e26ee0e 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -109,21 +109,24 @@ type rejectedTx struct { func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txs types.Transactions, miningReward int64, getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, error) { - // Capture errors for BLOCKHASH operation, if we haven't been supplied the // required blockhashes var hashError error + getHash := func(num uint64) common.Hash { if pre.Env.BlockHashes == nil { hashError = fmt.Errorf("getHash(%d) invoked, no blockhashes provided", num) return common.Hash{} } + h, ok := pre.Env.BlockHashes[math.HexOrDecimal64(num)] if !ok { hashError = fmt.Errorf("getHash(%d) invoked, blockhash for that block not provided", num) } + return h } + var ( statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number)) @@ -135,6 +138,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, receipts = make(types.Receipts, 0) txIndex = 0 ) + gaspool.AddGas(pre.Env.GasLimit) vmContext := vm.BlockContext{ CanTransfer: core.CanTransfer, @@ -168,12 +172,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, if err != nil { log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", err) rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) + continue } + tracer, err := getTracerFn(txIndex, tx.Hash()) if err != nil { return nil, nil, err } + vmConfig.Tracer = tracer statedb.SetTxContext(tx.Hash(), txIndex) @@ -183,6 +190,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, snapshot = statedb.Snapshot() prevGas = gaspool.Gas() ) + evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) // (ret []byte, usedGas uint64, failed bool, err error) @@ -193,17 +201,22 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) gaspool.SetGas(prevGas) + continue } + includedTxs = append(includedTxs, tx) + if hashError != nil { return nil, nil, NewError(ErrorMissingBlockhash, hashError) } + gasUsed += msgResult.UsedGas // Receipt: { var root []byte + if chainConfig.IsByzantium(vmContext.BlockNumber) { statedb.Finalise(true) } else { @@ -218,6 +231,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } else { receipt.Status = types.ReceiptStatusSuccessful } + receipt.TxHash = tx.Hash() receipt.GasUsed = msgResult.UsedGas @@ -238,6 +252,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txIndex++ } + statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) // Add mining reward? (-1 means rewards are disabled) if miningReward >= 0 { @@ -251,6 +266,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, minerReward = new(big.Int).Set(blockReward) perOmmer = new(big.Int).Div(blockReward, big.NewInt(32)) ) + for _, ommer := range pre.Env.Ommers { // Add 1/32th for each ommer included minerReward.Add(minerReward, perOmmer) @@ -261,6 +277,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, reward.Div(reward, big.NewInt(8)) statedb.AddBalance(ommer.Address, reward) } + statedb.AddBalance(pre.Env.Coinbase, minerReward) } // Apply withdrawals @@ -275,6 +292,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, fmt.Fprintf(os.Stderr, "Could not commit state: %v", err) return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) } + execRs := &ExecutionResult{ StateRoot: root, TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)), @@ -292,16 +310,19 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil)) execRs.WithdrawalsRoot = &h } + return statedb, execRs, nil } func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB { sdb := state.NewDatabaseWithConfig(db, &trie.Config{Preimages: true}) + statedb, _ := state.New(common.Hash{}, sdb, nil) for addr, a := range accounts { statedb.SetCode(addr, a.Code) statedb.SetNonce(addr, a.Nonce) statedb.SetBalance(addr, a.Balance) + for k, v := range a.Storage { statedb.SetState(addr, k, v) } @@ -309,6 +330,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB // Commit and re-open to start with a clean state. root, _ := statedb.Commit(false) statedb, _ = state.New(root, sdb, nil) + return statedb } @@ -316,6 +338,7 @@ func rlpHash(x interface{}) (h common.Hash) { hw := sha3.NewLegacyKeccak256() rlp.Encode(hw, x) hw.Sum(h[:0]) + return h } @@ -329,6 +352,7 @@ func calcDifficulty(config *params.ChainConfig, number, currentTime, parentTime if uncleHash == (common.Hash{}) { uncleHash = types.EmptyUncleHash } + parent := &types.Header{ ParentHash: common.Hash{}, UncleHash: uncleHash, @@ -336,5 +360,6 @@ func calcDifficulty(config *params.ChainConfig, number, currentTime, parentTime Number: new(big.Int).SetUint64(number - 1), Time: parentTime, } + return ethash.CalcDifficulty(config, currentTime, parent) } diff --git a/cmd/evm/internal/t8ntool/gen_header.go b/cmd/evm/internal/t8ntool/gen_header.go index 76228394dc..abd0c5b12a 100644 --- a/cmd/evm/internal/t8ntool/gen_header.go +++ b/cmd/evm/internal/t8ntool/gen_header.go @@ -36,6 +36,7 @@ func (h header) MarshalJSON() ([]byte, error) { BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` } + var enc header enc.ParentHash = h.ParentHash enc.OmmerHash = h.OmmerHash @@ -54,6 +55,7 @@ func (h header) MarshalJSON() ([]byte, error) { enc.Nonce = h.Nonce enc.BaseFee = (*math.HexOrDecimal256)(h.BaseFee) enc.WithdrawalsHash = h.WithdrawalsHash + return json.Marshal(&enc) } @@ -78,64 +80,84 @@ func (h *header) UnmarshalJSON(input []byte) error { BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` } + var dec header if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.ParentHash != nil { h.ParentHash = *dec.ParentHash } + if dec.OmmerHash != nil { h.OmmerHash = dec.OmmerHash } + if dec.Coinbase != nil { h.Coinbase = dec.Coinbase } + if dec.Root == nil { return errors.New("missing required field 'stateRoot' for header") } + h.Root = *dec.Root if dec.TxHash != nil { h.TxHash = dec.TxHash } + if dec.ReceiptHash != nil { h.ReceiptHash = dec.ReceiptHash } + if dec.Bloom != nil { h.Bloom = *dec.Bloom } + if dec.Difficulty != nil { h.Difficulty = (*big.Int)(dec.Difficulty) } + if dec.Number == nil { return errors.New("missing required field 'number' for header") } + h.Number = (*big.Int)(dec.Number) + if dec.GasLimit == nil { return errors.New("missing required field 'gasLimit' for header") } + h.GasLimit = uint64(*dec.GasLimit) if dec.GasUsed != nil { h.GasUsed = uint64(*dec.GasUsed) } + if dec.Time == nil { return errors.New("missing required field 'timestamp' for header") } + h.Time = uint64(*dec.Time) if dec.Extra != nil { h.Extra = *dec.Extra } + if dec.MixDigest != nil { h.MixDigest = *dec.MixDigest } + if dec.Nonce != nil { h.Nonce = dec.Nonce } + if dec.BaseFee != nil { h.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.WithdrawalsHash != nil { h.WithdrawalsHash = dec.WithdrawalsHash } + return nil } diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go index c2cc3a2c8a..1f74c23c95 100644 --- a/cmd/evm/internal/t8ntool/gen_stenv.go +++ b/cmd/evm/internal/t8ntool/gen_stenv.go @@ -34,6 +34,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) { BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` ParentUncleHash common.Hash `json:"parentUncleHash"` } + var enc stEnv enc.Coinbase = common.UnprefixedAddress(s.Coinbase) enc.Difficulty = (*math.HexOrDecimal256)(s.Difficulty) @@ -51,6 +52,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) { enc.Withdrawals = s.Withdrawals enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee) enc.ParentUncleHash = s.ParentUncleHash + return json.Marshal(&enc) } @@ -74,61 +76,81 @@ func (s *stEnv) UnmarshalJSON(input []byte) error { BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` ParentUncleHash *common.Hash `json:"parentUncleHash"` } + var dec stEnv if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Coinbase == nil { return errors.New("missing required field 'currentCoinbase' for stEnv") } + s.Coinbase = common.Address(*dec.Coinbase) if dec.Difficulty != nil { s.Difficulty = (*big.Int)(dec.Difficulty) } + if dec.Random != nil { s.Random = (*big.Int)(dec.Random) } + if dec.ParentDifficulty != nil { s.ParentDifficulty = (*big.Int)(dec.ParentDifficulty) } + if dec.ParentBaseFee != nil { s.ParentBaseFee = (*big.Int)(dec.ParentBaseFee) } + if dec.ParentGasUsed != nil { s.ParentGasUsed = uint64(*dec.ParentGasUsed) } + if dec.ParentGasLimit != nil { s.ParentGasLimit = uint64(*dec.ParentGasLimit) } + if dec.GasLimit == nil { return errors.New("missing required field 'currentGasLimit' for stEnv") } + s.GasLimit = uint64(*dec.GasLimit) + if dec.Number == nil { return errors.New("missing required field 'currentNumber' for stEnv") } + s.Number = uint64(*dec.Number) + if dec.Timestamp == nil { return errors.New("missing required field 'currentTimestamp' for stEnv") } + s.Timestamp = uint64(*dec.Timestamp) if dec.ParentTimestamp != nil { s.ParentTimestamp = uint64(*dec.ParentTimestamp) } + if dec.BlockHashes != nil { s.BlockHashes = dec.BlockHashes } + if dec.Ommers != nil { s.Ommers = dec.Ommers } + if dec.Withdrawals != nil { s.Withdrawals = dec.Withdrawals } + if dec.BaseFee != nil { s.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.ParentUncleHash != nil { s.ParentUncleHash = *dec.ParentUncleHash } + return nil } diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 74a36e4d31..73735da7c2 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/urfave/cli/v2" "math/big" "os" "strings" @@ -49,17 +50,22 @@ func (r *result) MarshalJSON() ([]byte, error) { Hash *common.Hash `json:"hash,omitempty"` IntrinsicGas hexutil.Uint64 `json:"intrinsicGas,omitempty"` } + var out xx if r.Error != nil { out.Error = r.Error.Error() } + if r.Address != (common.Address{}) { out.Address = &r.Address } + if r.Hash != (common.Hash{}) { out.Hash = &r.Hash } + out.IntrinsicGas = hexutil.Uint64(r.IntrinsicGas) + return json.Marshal(out) } @@ -87,7 +93,9 @@ func Transaction(ctx *cli.Context) error { } // Set the chain id chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) + var body hexutil.Bytes + if txStr == stdinSelector { decoder := json.NewDecoder(os.Stdin) if err := decoder.Decode(inputData); err != nil { @@ -101,7 +109,9 @@ func Transaction(ctx *cli.Context) error { if err != nil { return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) } + defer inFile.Close() + decoder := json.NewDecoder(inFile) if strings.HasSuffix(txStr, ".rlp") { if err := decoder.Decode(&body); err != nil { @@ -111,6 +121,7 @@ func Transaction(ctx *cli.Context) error { return NewError(ErrorIO, errors.New("only rlp supported")) } } + signer := types.MakeSigner(chainConfig, new(big.Int)) // We now have the transactions in 'body', which is supposed to be an // rlp list of transactions @@ -118,21 +129,27 @@ func Transaction(ctx *cli.Context) error { if err != nil { return err } + var results []result + for it.Next() { if err := it.Err(); err != nil { return NewError(ErrorIO, err) } + var tx types.Transaction + err := rlp.DecodeBytes(it.Value(), &tx) if err != nil { results = append(results, result{Error: err}) continue } + r := result{Hash: tx.Hash()} if sender, err := types.Sender(signer, &tx); err != nil { r.Error = err results = append(results, r) + continue } else { r.Address = sender @@ -142,12 +159,14 @@ func Transaction(ctx *cli.Context) error { chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(0)); err != nil { r.Error = err results = append(results, r) + continue } else { r.IntrinsicGas = gas if tx.Gas() < gas { r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas) results = append(results, r) + continue } } @@ -175,9 +194,12 @@ func Transaction(ctx *cli.Context) error { if chainConfig.IsShanghai(0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { r.Error = errors.New("max initcode size exceeded") } + results = append(results, r) } + out, err := json.MarshalIndent(results, "", " ") fmt.Println(string(out)) + return err } diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 70cd753122..05dd193581 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/urfave/cli/v2" "math/big" "os" "path" @@ -92,22 +93,27 @@ func Transition(ctx *cli.Context) error { err error tracer vm.EVMLogger ) + var getTracer func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) baseDir, err := createBasedir(ctx) if err != nil { return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) } + if ctx.Bool(TraceFlag.Name) { if ctx.IsSet(TraceDisableMemoryFlag.Name) && ctx.IsSet(TraceEnableMemoryFlag.Name) { return NewError(ErrorConfig, fmt.Errorf("can't use both flags --%s and --%s", TraceDisableMemoryFlag.Name, TraceEnableMemoryFlag.Name)) } + if ctx.IsSet(TraceDisableReturnDataFlag.Name) && ctx.IsSet(TraceEnableReturnDataFlag.Name) { return NewError(ErrorConfig, fmt.Errorf("can't use both flags --%s and --%s", TraceDisableReturnDataFlag.Name, TraceEnableReturnDataFlag.Name)) } + if ctx.IsSet(TraceDisableMemoryFlag.Name) { log.Warn(fmt.Sprintf("--%s has been deprecated in favour of --%s", TraceDisableMemoryFlag.Name, TraceEnableMemoryFlag.Name)) } + if ctx.IsSet(TraceDisableReturnDataFlag.Name) { log.Warn(fmt.Sprintf("--%s has been deprecated in favour of --%s", TraceDisableReturnDataFlag.Name, TraceEnableReturnDataFlag.Name)) } @@ -118,6 +124,7 @@ func Transition(ctx *cli.Context) error { EnableReturnData: !ctx.Bool(TraceDisableReturnDataFlag.Name) || ctx.Bool(TraceEnableReturnDataFlag.Name), Debug: true, } + var prevFile *os.File // This one closes the last file defer func() { @@ -125,15 +132,19 @@ func Transition(ctx *cli.Context) error { prevFile.Close() } }() + getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { if prevFile != nil { prevFile.Close() } + traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String()))) if err != nil { return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) } + prevFile = traceFile + return logger.NewJSONLogger(logConfig, traceFile), nil } } else { @@ -160,11 +171,13 @@ func Transition(ctx *cli.Context) error { return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err)) } } + if allocStr != stdinSelector { if err := readFile(allocStr, "alloc", &inputData.Alloc); err != nil { return err } } + prestate.Pre = inputData.Alloc // Set the block environment @@ -173,8 +186,10 @@ func Transition(ctx *cli.Context) error { if err := readFile(envStr, "env", &env); err != nil { return err } + inputData.Env = &env } + prestate.Env = *inputData.Env vmConfig := vm.Config{ @@ -182,6 +197,7 @@ func Transition(ctx *cli.Context) error { } // Construct the chainconfig var chainConfig *params.ChainConfig + if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { return NewError(ErrorConfig, fmt.Errorf("failed constructing chain configuration: %v", err)) } else { @@ -192,22 +208,27 @@ func Transition(ctx *cli.Context) error { 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 strings.HasSuffix(txStr, ".rlp") { var body hexutil.Bytes if err := decoder.Decode(&body); err != nil { return err } + var txs types.Transactions if err := rlp.DecodeBytes(body, &txs); err != nil { return err } + for _, tx := range txs { txsWithKeys = append(txsWithKeys, &txWithKey{ key: nil, @@ -223,10 +244,13 @@ func Transition(ctx *cli.Context) error { if len(inputData.TxRlp) > 0 { // Decode the body of already signed transactions body := common.FromHex(inputData.TxRlp) + var txs types.Transactions + if err := rlp.DecodeBytes(body, &txs); err != nil { return err } + for _, tx := range txs { txsWithKeys = append(txsWithKeys, &txWithKey{ key: nil, @@ -292,6 +316,7 @@ func Transition(ctx *cli.Context) error { return NewError(ErrorConfig, fmt.Errorf("currentDifficulty cannot be calculated -- currentTime (%d) needs to be after parent time (%d)", env.Timestamp, env.ParentTimestamp)) } + prestate.Env.Difficulty = calcDifficulty(chainConfig, env.Number, env.Timestamp, env.ParentTimestamp, env.ParentDifficulty, env.ParentUncleHash) } @@ -300,10 +325,12 @@ func Transition(ctx *cli.Context) error { if err != nil { return err } + body, _ := rlp.EncodeToBytes(txs) // Dump the excution result collector := make(Alloc) s.DumpToCollector(collector, nil) + return dispatchOutput(ctx, baseDir, result, collector, body) } @@ -321,10 +348,12 @@ func (t *txWithKey) UnmarshalJSON(input []byte) error { Key *common.Hash `json:"secretKey"` Protected *bool `json:"protected"` } + var data txMetadata if err := json.Unmarshal(input, &data); err != nil { return err } + if data.Key != nil { k := data.Key.Hex()[2:] if ecdsaKey, err := crypto.HexToECDSA(k); err != nil { @@ -333,6 +362,7 @@ func (t *txWithKey) UnmarshalJSON(input []byte) error { t.key = ecdsaKey } } + if data.Protected != nil { t.protected = *data.Protected } else { @@ -343,7 +373,9 @@ func (t *txWithKey) UnmarshalJSON(input []byte) error { if err := json.Unmarshal(input, &tx); err != nil { return err } + t.tx = &tx + return nil } @@ -361,30 +393,36 @@ func (t *txWithKey) UnmarshalJSON(input []byte) error { // 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 var ( signed *types.Transaction err error ) + if txWithKey.protected { signed, err = types.SignTx(tx, signer, key) } else { signed, err = types.SignTx(tx, types.FrontierSigner{}, 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 } @@ -394,6 +432,7 @@ func (g Alloc) OnRoot(common.Hash) {} func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) { balance, _ := new(big.Int).SetString(dumpAccount.Balance, 10) + var storage map[common.Hash]common.Hash if dumpAccount.Storage != nil { storage = make(map[common.Hash]common.Hash) @@ -401,6 +440,7 @@ func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) { storage[k] = common.HexToHash(v) } } + genesisAccount := core.GenesisAccount{ Code: dumpAccount.Code, Storage: storage, @@ -416,11 +456,14 @@ func saveFile(baseDir, filename string, data interface{}) error { if err != nil { return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) } + location := path.Join(baseDir, filename) if err = os.WriteFile(location, b, 0644); err != nil { return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err)) } + log.Info("Wrote file", "file", location) + return nil } @@ -429,6 +472,7 @@ func saveFile(baseDir, filename string, data interface{}) 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 { switch fName { case "stdout": @@ -442,32 +486,40 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a return err } } + return nil } if err := dispatch(baseDir, ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil { return err } + 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 { return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) } + os.Stdout.Write(b) os.Stdout.WriteString("\n") } + if len(stdErrObject) > 0 { b, err := json.MarshalIndent(stdErrObject, "", " ") if err != nil { return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) } + os.Stderr.Write(b) os.Stderr.WriteString("\n") } + return nil } diff --git a/cmd/evm/internal/t8ntool/utils.go b/cmd/evm/internal/t8ntool/utils.go index 8ec38c7618..756db4ed97 100644 --- a/cmd/evm/internal/t8ntool/utils.go +++ b/cmd/evm/internal/t8ntool/utils.go @@ -30,25 +30,31 @@ func readFile(path, desc string, dest interface{}) error { if err != nil { return NewError(ErrorIO, fmt.Errorf("failed reading %s file: %v", desc, err)) } + defer inFile.Close() + decoder := json.NewDecoder(inFile) if err := decoder.Decode(dest); err != nil { return NewError(ErrorJson, fmt.Errorf("failed unmarshaling %s file: %v", desc, err)) } + return nil } // createBasedir makes sure the basedir exists, if user specified one. func createBasedir(ctx *cli.Context) (string, error) { baseDir := "" + if ctx.IsSet(OutputBasedir.Name) { if base := ctx.String(OutputBasedir.Name); len(base) > 0 { err := os.MkdirAll(base, 0755) // //rw-r--r-- if err != nil { return "", err } + baseDir = base } } + return baseDir, nil } diff --git a/cmd/evm/main.go b/cmd/evm/main.go index ad40064d51..5f8be278f9 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -233,6 +233,7 @@ func main() { if ec, ok := err.(*t8ntool.NumberedError); ok { code = ec.ExitCode() } + fmt.Fprintln(os.Stderr, err) os.Exit(code) } diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 153a3471eb..c1ebfe9b01 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -61,16 +61,19 @@ func readGenesis(genesisPath string) *core.Genesis { if len(genesisPath) == 0 { utils.Fatalf("Must supply path to genesis JSON file") } + file, err := os.Open(genesisPath) if err != nil { utils.Fatalf("Failed to read genesis file: %v", err) } + defer file.Close() genesis := new(core.Genesis) if err := json.NewDecoder(file).Decode(genesis); err != nil { utils.Fatalf("invalid genesis file: %v", err) } + return genesis } @@ -95,10 +98,13 @@ func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) (output []by stats.bytesAllocated = result.AllocedBytesPerOp() } else { var memStatsBefore, memStatsAfter goruntime.MemStats + goruntime.ReadMemStats(&memStatsBefore) + startTime := time.Now() output, gasLeft, err = execFunc() stats.time = time.Since(startTime) + goruntime.ReadMemStats(&memStatsAfter) stats.allocs = int64(memStatsAfter.Mallocs - memStatsBefore.Mallocs) stats.bytesAllocated = int64(memStatsAfter.TotalAlloc - memStatsBefore.TotalAlloc) @@ -111,6 +117,7 @@ func runCmd(ctx *cli.Context) error { glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name))) log.Root().SetHandler(glogger) + logconfig := &logger.Config{ EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), DisableStack: ctx.Bool(DisableStackFlag.Name), @@ -156,6 +163,7 @@ func runCmd(ctx *cli.Context) error { if ctx.String(SenderFlag.Name) != "" { sender = common.HexToAddress(ctx.String(SenderFlag.Name)) } + statedb.CreateAccount(sender) if ctx.String(ReceiverFlag.Name) != "" { @@ -170,6 +178,7 @@ func runCmd(ctx *cli.Context) error { // The '--code' or '--codefile' flag overrides code in state if codeFileFlag != "" || codeFlag != "" { var hexcode []byte + if codeFileFlag != "" { var err error // If - is specified, it means that code comes from stdin @@ -189,11 +198,13 @@ func runCmd(ctx *cli.Context) error { } else { hexcode = []byte(codeFlag) } + hexcode = bytes.TrimSpace(hexcode) if len(hexcode)%2 != 0 { fmt.Printf("Invalid input length for hex data (%d)\n", len(hexcode)) os.Exit(1) } + code = common.FromHex(string(hexcode)) } else if fn := ctx.Args().First(); len(fn) > 0 { // EASM-file to compile @@ -201,10 +212,12 @@ func runCmd(ctx *cli.Context) error { if err != nil { return err } + bin, err := compiler.Compile(fn, src, false) if err != nil { return err } + code = common.Hex2Bytes(bin) } @@ -212,6 +225,7 @@ func runCmd(ctx *cli.Context) error { if genesisConfig.GasLimit != 0 { initialGas = genesisConfig.GasLimit } + runtimeConfig := runtime.Config{ Origin: sender, State: statedb, @@ -233,10 +247,12 @@ func runCmd(ctx *cli.Context) error { fmt.Println("could not create CPU profile: ", err) os.Exit(1) } + if err := pprof.StartCPUProfile(f); err != nil { fmt.Println("could not start CPU profile: ", err) os.Exit(1) } + defer pprof.StopCPUProfile() } @@ -279,6 +295,7 @@ func runCmd(ctx *cli.Context) error { if len(code) > 0 { statedb.SetCode(receiver, code) } + execFunc = func() ([]byte, uint64, error) { return runtime.Call(receiver, input, &runtimeConfig) } @@ -299,10 +316,12 @@ func runCmd(ctx *cli.Context) error { fmt.Println("could not create memory profile: ", err) os.Exit(1) } + if err := pprof.WriteHeapProfile(f); err != nil { fmt.Println("could not write memory profile: ", err) os.Exit(1) } + f.Close() } @@ -311,6 +330,7 @@ func runCmd(ctx *cli.Context) error { fmt.Fprintln(os.Stderr, "#### TRACE ####") logger.WriteTrace(os.Stderr, debugLogger.StructLogs()) } + fmt.Fprintln(os.Stderr, "#### LOGS ####") logger.WriteLogs(os.Stderr, statedb.Logs()) } @@ -322,8 +342,10 @@ allocations: %d allocated bytes: %d `, initialGas-leftOverGas, stats.time, stats.allocs, stats.bytesAllocated) } + if tracer == nil { fmt.Printf("%#x\n", output) + if err != nil { fmt.Printf(" error: %v\n", err) } diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index a6b0d83e36..c4ac13cfb6 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -66,10 +66,12 @@ func stateTestCmd(ctx *cli.Context) error { DisableStorage: ctx.Bool(DisableStorageFlag.Name), EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), } + var ( tracer vm.EVMLogger debugger *logger.StructLogger ) + switch { case ctx.Bool(MachineFlag.Name): tracer = logger.NewJSONLogger(config, os.Stderr) @@ -86,6 +88,7 @@ func stateTestCmd(ctx *cli.Context) error { if err != nil { return err } + var tests map[string]tests.StateTest if err = json.Unmarshal(src, &tests); err != nil { return err @@ -95,6 +98,7 @@ func stateTestCmd(ctx *cli.Context) error { Tracer: tracer, } results := make([]StatetestResult, 0, len(tests)) + for key, test := range tests { for _, st := range test.Subtests() { // Run the test and aggregate the result @@ -109,6 +113,7 @@ func stateTestCmd(ctx *cli.Context) error { fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root) } } + if err != nil { // Test failed, mark as so and dump any state to aid debugging result.Pass, result.Error = false, err.Error() @@ -130,7 +135,9 @@ func stateTestCmd(ctx *cli.Context) error { } } } + out, _ := json.MarshalIndent(results, "", " ") fmt.Println(string(out)) + return nil } diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go index 37eaa8c19c..cbe2926206 100644 --- a/cmd/evm/t8n_test.go +++ b/cmd/evm/t8n_test.go @@ -36,12 +36,14 @@ func TestMain(m *testing.M) { fmt.Fprintln(os.Stderr, err) os.Exit(1) } + os.Exit(0) }) // check if we have been reexec'd if reexec.Init() { return } + os.Exit(m.Run()) } @@ -63,20 +65,25 @@ func (args *t8nInput) get(base string) []string { out = append(out, "--input.alloc") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if opt := args.inTxs; opt != "" { out = append(out, "--input.txs") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if opt := args.inEnv; opt != "" { out = append(out, "--input.env") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if opt := args.stFork; opt != "" { out = append(out, "--state.fork", opt) } + if opt := args.stReward; opt != "" { out = append(out, "--state.reward", opt) } + return out } @@ -92,22 +99,26 @@ func (args *t8nOutput) get() (out []string) { } else { out = append(out, "--output.body", "") // empty means ignore } + if args.result { out = append(out, "--output.result", "stdout") } else { out = append(out, "--output.result", "") } + if args.alloc { out = append(out, "--output.alloc", "stdout") } else { out = append(out, "--output.alloc", "") } + return out } func TestT8n(t *testing.T) { tt := new(testT8n) tt.TestCmd = cmdtest.NewTestCmd(t, tt) + for i, tc := range []struct { base string input t8nInput @@ -264,7 +275,9 @@ func TestT8n(t *testing.T) { args := []string{"t8n"} args = append(args, tc.output.get()...) args = append(args, tc.input.get(tc.base)...) + var qArgs []string // quoted args for debugging purposes + for _, arg := range args { if len(arg) == 0 { qArgs = append(qArgs, `""`) @@ -272,6 +285,7 @@ func TestT8n(t *testing.T) { qArgs = append(qArgs, arg) } } + tt.Logf("args: %v\n", strings.Join(qArgs, " ")) tt.Run("evm-test", args...) // Compare the expected output, if provided @@ -280,7 +294,9 @@ func TestT8n(t *testing.T) { if err != nil { t.Fatalf("test %d: could not read expected output: %v", i, err) } + have := tt.Output() + ok, err := cmpJson(have, want) switch { case err != nil: @@ -289,7 +305,9 @@ func TestT8n(t *testing.T) { t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) } } + tt.WaitExit() + if have, want := tt.ExitStatus(), tc.expExitCode; have != want { t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) } @@ -307,15 +325,18 @@ func (args *t9nInput) get(base string) []string { out = append(out, "--input.txs") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if opt := args.stFork; opt != "" { out = append(out, "--state.fork", opt) } + return out } func TestT9n(t *testing.T) { tt := new(testT8n) tt.TestCmd = cmdtest.NewTestCmd(t, tt) + for i, tc := range []struct { base string input t9nInput @@ -382,8 +403,10 @@ func TestT9n(t *testing.T) { if err != nil { t.Fatalf("test %d: could not read expected output: %v", i, err) } + have := tt.Output() ok, err := cmpJson(have, want) + switch { case err != nil: t.Logf(string(have)) @@ -392,7 +415,9 @@ func TestT9n(t *testing.T) { t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) } } + tt.WaitExit() + if have, want := tt.ExitStatus(), tc.expExitCode; have != want { t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) } @@ -416,6 +441,7 @@ func (args *b11rInput) get(base string) []string { out = append(out, "--input.header") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if opt := args.inOmmersRlp; opt != "" { out = append(out, "--input.ommers") out = append(out, fmt.Sprintf("%v/%v", base, opt)) @@ -425,33 +451,41 @@ func (args *b11rInput) get(base string) []string { out = append(out, "--input.withdrawals") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if opt := args.inTxsRlp; opt != "" { out = append(out, "--input.txs") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if opt := args.inClique; opt != "" { out = append(out, "--seal.clique") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if args.ethash { out = append(out, "--seal.ethash") } + if opt := args.ethashMode; opt != "" { out = append(out, "--seal.ethash.mode") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if opt := args.ethashDir; opt != "" { out = append(out, "--seal.ethash.dir") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + out = append(out, "--output.block") out = append(out, "stdout") + return out } func TestB11r(t *testing.T) { tt := new(testT8n) tt.TestCmd = cmdtest.NewTestCmd(t, tt) + for i, tc := range []struct { base string input b11rInput @@ -517,8 +551,10 @@ func TestB11r(t *testing.T) { if err != nil { t.Fatalf("test %d: could not read expected output: %v", i, err) } + have := tt.Output() ok, err := cmpJson(have, want) + switch { case err != nil: t.Logf(string(have)) @@ -527,7 +563,9 @@ func TestB11r(t *testing.T) { t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) } } + tt.WaitExit() + if have, want := tt.ExitStatus(), tc.expExitCode; have != want { t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) } @@ -540,8 +578,10 @@ func cmpJson(a, b []byte) (bool, error) { if err := json.Unmarshal(a, &j); err != nil { return false, err } + if err := json.Unmarshal(b, &j2); err != nil { return false, err } + return reflect.DeepEqual(j2, j), nil } diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 735b0dfe1b..62208c5893 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -105,16 +105,19 @@ func main() { // Construct the payout tiers amounts := make([]string, *tiersFlag) periods := make([]string, *tiersFlag) + for i := 0; i < *tiersFlag; i++ { // Calculate the amount for the next tier and format it amount := float64(*payoutFlag) * math.Pow(2.5, float64(i)) amounts[i] = fmt.Sprintf("%s Ethers", strconv.FormatFloat(amount, 'f', -1, 64)) + if amount == 1 { amounts[i] = strings.TrimSuffix(amounts[i], "s") } // Calculate the period for the next tier and format it period := *minutesFlag * int(math.Pow(3, float64(i))) periods[i] = fmt.Sprintf("%d mins", period) + if period%60 == 0 { period /= 60 periods[i] = fmt.Sprintf("%d hours", period) @@ -124,11 +127,14 @@ func main() { periods[i] = fmt.Sprintf("%d days", period) } } + if period == 1 { periods[i] = strings.TrimSuffix(periods[i], "s") } } + website := new(bytes.Buffer) + err := template.Must(template.New("").Parse(websiteTmpl)).Execute(website, map[string]interface{}{ "Network": *netnameFlag, "Amounts": amounts, @@ -146,6 +152,7 @@ func main() { } // Convert the bootnodes to internal enode representations var enodes []*enode.Node + for _, boot := range strings.Split(*bootFlag, ",") { if url, err := enode.Parse(enode.ValidSchemes, boot); err == nil { enodes = append(enodes, url) @@ -165,6 +172,7 @@ func main() { if err != nil { log.Crit("Failed to read account password contents", "file", canonicalPath, "err", err) } + pass := strings.TrimSuffix(string(blob), "\n") ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP) @@ -172,6 +180,7 @@ func main() { if blob, err = os.ReadFile(*accJSONFlag); err != nil { log.Crit("Failed to read account key contents", "file", *accJSONFlag, "err", err) } + acc, err := ks.Import(blob, pass, pass) if err != nil && err != keystore.ErrAccountAlreadyExists { log.Crit("Failed to import faucet signer account", "err", err) @@ -231,6 +240,7 @@ type wsConn struct { 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 git, _ := version.VCS() + stack, err := node.New(&node.Config{ Name: "geth", Version: params.VersionWithCommit(git.Commit, git.Date), @@ -270,6 +280,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network ui if err := stack.Start(); err != nil { return nil, err } + for _, boot := range enodes { old, err := enode.Parse(enode.ValidSchemes, boot.String()) if err == nil { @@ -282,6 +293,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network ui stack.Close() return nil, err } + client := ethclient.NewClient(api) return &faucet{ @@ -308,6 +320,7 @@ func (f *faucet) listenAndServe(port int) error { http.HandleFunc("/", f.webHandler) http.HandleFunc("/api", f.apiHandler) + return http.ListenAndServe(fmt.Sprintf(":%d", port), nil) } @@ -320,6 +333,7 @@ func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) { // apiHandler handles requests for Ether grants and transaction statuses. func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { upgrader := websocket.Upgrader{} + conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return @@ -349,15 +363,18 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { balance *big.Int nonce uint64 ) + for head == nil || balance == nil { // Retrieve the current stats cached by the faucet f.lock.RLock() if f.head != nil { head = types.CopyHeader(f.head) } + if f.balance != nil { balance = new(big.Int).Set(f.balance) } + nonce = f.nonce f.lock.RUnlock() @@ -368,6 +385,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { log.Warn("Failed to send faucet error to client", "err", err) return } + time.Sleep(3 * time.Second) } } @@ -375,6 +393,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { f.lock.RLock() reqs := f.reqs f.lock.RUnlock() + if err = send(wsconn, map[string]interface{}{ "funds": new(big.Int).Div(balance, ether), "funded": nonce, @@ -384,6 +403,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(wsconn, head, 3*time.Second); err != nil { log.Warn("Failed to send initial header to client", "err", err) return @@ -396,24 +416,30 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { Tier uint `json:"tier"` Captcha string `json:"captcha"` } + if err = conn.ReadJSON(&msg); err != nil { return } + if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://twitter.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { 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 } + continue } + if msg.Tier >= uint(*tiersFlag) { //lint:ignore ST1005 This error is to be displayed in the browser if err = sendError(wsconn, errors.New("Invalid funding tier requested")); err != nil { log.Warn("Failed to send tier error to client", "err", err) return } + continue } + log.Info("Faucet funds requested", "url", msg.URL, "tier", msg.Tier) // If captcha verifications are enabled, make sure we're not dealing with a robot @@ -428,21 +454,27 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { log.Warn("Failed to send captcha post error to client", "err", err) return } + continue } + var result struct { Success bool `json:"success"` Errors json.RawMessage `json:"error-codes"` } + err = json.NewDecoder(res.Body).Decode(&result) res.Body.Close() + if err != nil { if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send captcha decode error to client", "err", err) return } + continue } + if !result.Success { log.Warn("Captcha verification failed", "err", string(result.Errors)) //lint:ignore ST1005 it's funny and the robot won't mind @@ -450,6 +482,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { log.Warn("Failed to send captcha failure to client", "err", err) return } + continue } } @@ -460,6 +493,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { avatar string address common.Address ) + switch { case strings.HasPrefix(msg.URL, "https://twitter.com/"): id, username, avatar, address, err = authTwitter(msg.URL, *twitterTokenV1Flag, *twitterTokenFlag) @@ -473,13 +507,16 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { //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") } + if err != nil { if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send prefix error to client", "err", err) return } + continue } + log.Info("Faucet request valid", "url", msg.URL, "tier", msg.Tier, "user", username, "address", address) // Ensure the user didn't request funds too recently @@ -488,6 +525,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { fund bool timeout time.Time ) + 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) @@ -495,24 +533,30 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil)) tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil) + signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID) if err != nil { f.lock.Unlock() + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send transaction creation error to client", "err", err) return } + continue } // 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(wsconn, err); err != nil { log.Warn("Failed to send transaction transmission error to client", "err", err) return } + continue } + f.reqs = append(f.reqs, &request{ Avatar: avatar, Account: address, @@ -533,8 +577,10 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { log.Warn("Failed to send funding error to client", "err", err) return } + continue } + 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 @@ -566,12 +612,15 @@ func (f *faucet) refresh(head *types.Header) error { nonce uint64 price *big.Int ) + if balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number); err != nil { return err } + if nonce, err = f.client.NonceAt(ctx, f.account.Address, head.Number); err != nil { return err } + if price, err = f.client.SuggestGasPrice(ctx); err != nil { return err } @@ -579,6 +628,7 @@ func (f *faucet) refresh(head *types.Header) error { f.lock.Lock() f.head, f.balance = head, balance f.price, f.nonce = price, nonce + for len(f.reqs) > 0 && f.reqs[0].Tx.Nonce() < f.nonce { f.reqs = f.reqs[1:] } @@ -592,10 +642,12 @@ func (f *faucet) refresh(head *types.Header) error { func (f *faucet) loop() { // Wait for chain events and push them to clients heads := make(chan *types.Header, 16) + sub, err := f.client.SubscribeNewHead(context.Background(), heads) if err != nil { log.Crit("Failed to subscribe to head events", "err", err) } + defer sub.Unsubscribe() // Start a goroutine to update the state from head notifications in the background @@ -609,6 +661,7 @@ func (f *faucet) loop() { log.Warn("Skipping faucet refresh, head too old", "number", head.Number, "hash", head.Hash(), "age", common.PrettyAge(timestamp)) continue } + if err := f.refresh(head); err != nil { log.Warn("Failed to update faucet state", "block", head.Number, "hash", head.Hash(), "err", err) continue @@ -629,8 +682,10 @@ func (f *faucet) loop() { }, time.Second); err != nil { log.Warn("Failed to send stats to client", "err", err) conn.conn.Close() + continue } + if err := send(conn, head, time.Second); err != nil { log.Warn("Failed to send header to client", "err", err) conn.conn.Close() @@ -669,9 +724,11 @@ func send(conn *wsConn, value interface{}, timeout time.Duration) error { if timeout == 0 { timeout = 60 * time.Second } + conn.wlock.Lock() defer conn.wlock.Unlock() conn.conn.SetWriteDeadline(time.Now().Add(timeout)) + return conn.conn.WriteJSON(value) } @@ -731,21 +788,25 @@ func authTwitter(url string, tokenV1, tokenV2 string) (string, string, string, c //lint:ignore ST1005 This error is to be displayed in the browser return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") } + username := parts[len(parts)-3] body, err := io.ReadAll(res.Body) if err != nil { 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") } + var avatar string if parts = regexp.MustCompile(`src="([^"]+twimg\.com/profile_images[^"]+)"`).FindStringSubmatch(string(body)); len(parts) == 2 { avatar = parts[1] } + return username + "@twitter", username, avatar, address, nil } @@ -755,15 +816,19 @@ func authTwitter(url string, tokenV1, tokenV2 string) (string, string, string, c 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.NewRequestWithContext(context.Background(), http.MethodGet, 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 { @@ -774,15 +839,18 @@ func authTwitterWithTokenV1(tweetID string, token string) (string, string, strin 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 } @@ -792,15 +860,19 @@ func authTwitterWithTokenV1(tweetID string, token string) (string, string, strin 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", tweetID) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, 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 { @@ -827,6 +899,7 @@ func authTwitterWithTokenV2(tweetID string, token string) (string, string, strin //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].Avatar, address, nil } @@ -838,10 +911,12 @@ func authFacebook(url string) (string, string, common.Address, error) { 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") } + username := parts[len(parts)-3] // Facebook's Graph API isn't really friendly with direct links. Still, we don't @@ -863,15 +938,18 @@ func authFacebook(url string) (string, string, common.Address, error) { if err != nil { 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. Please check the post URL and verify that it can be viewed publicly.") } + var avatar string if parts = regexp.MustCompile(`src="([^"]+fbcdn\.net[^"]+)"`).FindStringSubmatch(string(body)); len(parts) == 2 { avatar = parts[1] } + return username + "@facebook", avatar, address, nil } @@ -884,6 +962,7 @@ func authNoAuth(url string) (string, string, common.Address, error) { //lint:ignore ST1005 This error is to be displayed in the browser return "", "", common.Address{}, errors.New("No Ethereum address found to fund") } + return address.Hex() + "@noauth", "", address, nil } @@ -893,6 +972,7 @@ func getGenesis(genesisFlag string, goerliFlag bool, sepoliaFlag bool, mumbaiFla case genesisFlag != "": var genesis core.Genesis err := common.LoadJSON(genesisFlag, &genesis) + return &genesis, err case goerliFlag: return core.DefaultGoerliGenesisBlock(), nil diff --git a/cmd/faucet/faucet_test.go b/cmd/faucet/faucet_test.go index 58a1f22b54..7a5ad2091c 100644 --- a/cmd/faucet/faucet_test.go +++ b/cmd/faucet/faucet_test.go @@ -25,6 +25,7 @@ import ( func TestFacebook(t *testing.T) { // TODO: Remove facebook auth or implement facebook api, which seems to require an API key t.Skipf("The facebook access is flaky, needs to be reimplemented or removed") + for _, tt := range []struct { url string want common.Address @@ -38,6 +39,7 @@ func TestFacebook(t *testing.T) { if err != nil { t.Fatal(err) } + if gotAddress != tt.want { t.Fatalf("address wrong, have %v want %v", gotAddress, tt.want) } diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go index 5ec0a4d736..196de621e8 100644 --- a/cmd/geth/accountcmd.go +++ b/cmd/geth/accountcmd.go @@ -191,13 +191,17 @@ nodes. func accountList(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) + var index int + for _, wallet := range stack.AccountManager().Wallets() { for _, account := range wallet.Accounts() { fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) + index++ } } + return nil } @@ -207,18 +211,22 @@ func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []str if err != nil { utils.Fatalf("Could not list accounts: %v", err) } + for trials := 0; trials < 3; trials++ { prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) password := utils.GetPassPhraseWithList(prompt, false, i, passwords) + err = ks.Unlock(account, password) if err == nil { log.Info("Unlocked account", "address", account.Address.Hex()) return account, password } + if err, ok := err.(*keystore.AmbiguousAddrError); ok { log.Info("Unlocked account", "address", account.Address.Hex()) return ambiguousAddrRecovery(ks, err, password), password } + if err != keystore.ErrDecrypt { // No need to prompt again if the error is not decryption-related. break @@ -232,10 +240,13 @@ func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []str func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) + for _, a := range err.Matches { fmt.Println(" ", a.URL) } + fmt.Println("Testing your password against all of them...") + var match *accounts.Account for i, a := range err.Matches { @@ -244,17 +255,21 @@ func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrErr break } } + if match == nil { utils.Fatalf("None of the listed files could be unlocked.") return accounts.Account{} } + fmt.Printf("Your password unlocked %s\n", match.URL) fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") + for _, a := range err.Matches { if a != *match { fmt.Println(" ", a.URL) } } + return *match } @@ -267,13 +282,17 @@ func accountCreate(ctx *cli.Context) error { utils.Fatalf("%v", err) } } + utils.SetNodeConfig(ctx, &cfg.Node) + keydir, err := cfg.Node.KeyDirConfig() if err != nil { utils.Fatalf("Failed to read configuration: %v", err) } + scryptN := keystore.StandardScryptN scryptP := keystore.StandardScryptP + if cfg.Node.UseLightweightKDF { scryptN = keystore.LightScryptN scryptP = keystore.LightScryptP @@ -286,6 +305,7 @@ func accountCreate(ctx *cli.Context) error { if err != nil { utils.Fatalf("Failed to create account: %v", err) } + fmt.Printf("\nYour new key was generated\n\n") fmt.Printf("Public address of the key: %s\n", account.Address.Hex()) fmt.Printf("Path of the secret key file: %s\n\n", account.URL.Path) @@ -293,6 +313,7 @@ func accountCreate(ctx *cli.Context) error { fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n") fmt.Printf("- You must BACKUP your key file! Without the key, it's impossible to access account funds!\n") fmt.Printf("- You must REMEMBER your password! Without the password, it's impossible to decrypt the key!\n\n") + return nil } @@ -302,6 +323,7 @@ func accountUpdate(ctx *cli.Context) error { if ctx.Args().Len() == 0 { utils.Fatalf("No accounts specified to update") } + stack, _ := makeConfigNode(ctx) backends := stack.AccountManager().Backends(keystore.KeyStoreType) @@ -314,10 +336,12 @@ func accountUpdate(ctx *cli.Context) error { for _, addr := range ctx.Args().Slice() { account, oldPassword := unlockAccount(ks, addr, 0, nil) newPassword := utils.GetPassPhraseWithList("Please give a new password. Do not forget this password.", true, 0, nil) + if err := ks.Update(account, oldPassword, newPassword); err != nil { utils.Fatalf("Could not update the account: %v", err) } } + return nil } @@ -327,6 +351,7 @@ func importWallet(ctx *cli.Context) error { } keyfile := ctx.Args().First() + keyJSON, err := os.ReadFile(keyfile) if err != nil { utils.Fatalf("Could not read wallet file: %v", err) @@ -341,11 +366,14 @@ func importWallet(ctx *cli.Context) error { } ks := backends[0].(*keystore.KeyStore) + acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) if err != nil { utils.Fatalf("%v", err) } + fmt.Printf("Address: {%x}\n", acct.Address) + return nil } @@ -355,10 +383,12 @@ func accountImport(ctx *cli.Context) error { } keyfile := ctx.Args().First() + key, err := crypto.LoadECDSA(keyfile) if err != nil { utils.Fatalf("Failed to load the private key: %v", err) } + stack, _ := makeConfigNode(ctx) passphrase := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) @@ -368,10 +398,13 @@ func accountImport(ctx *cli.Context) error { } ks := backends[0].(*keystore.KeyStore) + acct, err := ks.ImportECDSA(key, passphrase) if err != nil { utils.Fatalf("Could not create the account: %v", err) } + fmt.Printf("Address: {%x}\n", acct.Address) + return nil } diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 26512974ba..791e3ea27f 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -36,9 +36,11 @@ func tmpDatadirWithKeystore(t *testing.T) string { datadir := t.TempDir() keystore := filepath.Join(datadir, "keystore") source := filepath.Join("..", "..", "accounts", "keystore", "testdata", "keystore") + if err := cp.CopyAll(keystore, source); err != nil { t.Fatal(err) } + return datadir } @@ -55,6 +57,7 @@ Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}/k Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}/keystore/aaa Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/keystore/zzz ` + if runtime.GOOS == "windows" { want = ` Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}\keystore\UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 @@ -143,6 +146,7 @@ func importAccountWithExpect(t *testing.T, key string, expected string) { if err := os.WriteFile(keyfile, []byte(key), 0600); err != nil { t.Error(err) } + passwordFile := filepath.Join(dir, "password.txt") if err := os.WriteFile(passwordFile, []byte("foobar"), 0600); err != nil { @@ -168,6 +172,7 @@ Fatal: Passwords do not match func TestAccountUpdate(t *testing.T) { datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, "account", "update", "--datadir", datadir, "--lightkdf", "f466859ead1932d743d622cb74fc058882e8648a") @@ -306,6 +311,7 @@ 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 := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore", store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index d3caaa82ce..afb18d8346 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -173,14 +173,17 @@ func initGenesis(ctx *cli.Context) error { if ctx.Args().Len() != 1 { utils.Fatalf("need genesis.json file as the only argument") } + genesisPath := ctx.Args().First() if len(genesisPath) == 0 { utils.Fatalf("invalid path to genesis file") } + file, err := os.Open(genesisPath) if err != nil { utils.Fatalf("Failed to read genesis file: %v", err) } + defer file.Close() genesis := new(core.Genesis) @@ -200,13 +203,16 @@ func initGenesis(ctx *cli.Context) error { triedb := trie.NewDatabaseWithConfig(chaindb, &trie.Config{ Preimages: ctx.Bool(utils.CachePreimagesFlag.Name), }) + _, hash, err := core.SetupGenesisBlock(chaindb, triedb, genesis) if err != nil { utils.Fatalf("Failed to write genesis block: %v", err) } + chaindb.Close() log.Info("Successfully wrote genesis state", "database", name, "hash", hash) } + return nil } @@ -253,6 +259,7 @@ func dumpGenesis(ctx *cli.Context) error { } utils.Fatalf("no network preset provided. no exisiting genesis in the default datadir") + return nil } @@ -273,16 +280,21 @@ func importChain(ctx *cli.Context) error { // Start periodically gathering memory profiles var peakMemAlloc, peakMemSys uint64 + go func() { stats := new(runtime.MemStats) + for { runtime.ReadMemStats(stats) + if atomic.LoadUint64(&peakMemAlloc) < stats.Alloc { atomic.StoreUint64(&peakMemAlloc, stats.Alloc) } + if atomic.LoadUint64(&peakMemSys) < stats.Sys { atomic.StoreUint64(&peakMemSys, stats.Sys) } + time.Sleep(5 * time.Second) } }() @@ -304,6 +316,7 @@ func importChain(ctx *cli.Context) error { } } } + chain.Stop() fmt.Printf("Import done in %v.\n\n", time.Since(start)) @@ -325,13 +338,17 @@ 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 { utils.Fatalf("Compaction failed: %v", err) } + fmt.Printf("Compaction done in %v.\n\n", time.Since(start)) showLeveldbStats(db) + return importErr } @@ -347,6 +364,7 @@ func exportChain(ctx *cli.Context) error { start := time.Now() var err error + fp := ctx.Args().First() if ctx.Args().Len() < 3 { @@ -355,22 +373,28 @@ func exportChain(ctx *cli.Context) error { // This can be improved to allow for numbers larger than 9223372036854775807 first, ferr := strconv.ParseInt(ctx.Args().Get(1), 10, 64) last, lerr := strconv.ParseInt(ctx.Args().Get(2), 10, 64) + if ferr != nil || lerr != nil { utils.Fatalf("Export error in parsing parameters: block number not an integer\n") } + if first < 0 || last < 0 { utils.Fatalf("Export error: block number must be greater than 0\n") } + if head := chain.CurrentSnapBlock(); uint64(last) > head.Number.Uint64() { utils.Fatalf("Export error: block number %d larger than head block %d\n", uint64(last), head.Number.Uint64()) } + err = utils.ExportAppendChain(chain, fp, uint64(first), uint64(last)) } if err != nil { utils.Fatalf("Export error: %v\n", err) } + fmt.Printf("Export done in %v\n", time.Since(start)) + return nil } @@ -389,7 +413,9 @@ func importPreimages(ctx *cli.Context) error { if err := utils.ImportPreimages(db, ctx.Args().First()); err != nil { utils.Fatalf("Import error: %v\n", err) } + fmt.Printf("Import done in %v\n", time.Since(start)) + return nil } @@ -398,6 +424,7 @@ func exportPreimages(ctx *cli.Context) error { if ctx.Args().Len() < 1 { utils.Fatalf("This command requires an argument.") } + stack, _ := makeConfigNode(ctx) defer stack.Close() @@ -407,16 +434,21 @@ func exportPreimages(ctx *cli.Context) error { if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil { utils.Fatalf("Export error: %v\n", err) } + fmt.Printf("Export done in %v\n", time.Since(start)) + return nil } func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, ethdb.Database, common.Hash, error) { db := utils.MakeChainDatabase(ctx, stack, true) + var header *types.Header + if ctx.NArg() > 1 { return nil, nil, common.Hash{}, fmt.Errorf("expected 1 argument (number or hash), got %d", ctx.NArg()) } + if ctx.NArg() == 1 { arg := ctx.Args().First() if hashish(arg) { @@ -431,6 +463,7 @@ func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, eth if err != nil { return nil, nil, common.Hash{}, err } + if hash := rawdb.ReadCanonicalHash(db, number); hash != (common.Hash{}) { header = rawdb.ReadHeader(db, hash, number) } else { @@ -441,11 +474,15 @@ func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, eth // Use latest header = rawdb.ReadHeadHeader(db) } + if header == nil { return nil, nil, common.Hash{}, errors.New("no head block found") } + startArg := common.FromHex(ctx.String(utils.StartKeyFlag.Name)) + var start common.Hash + switch len(startArg) { case 0: // common.Hash case 32: @@ -456,6 +493,7 @@ func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, eth default: return nil, nil, common.Hash{}, fmt.Errorf("invalid start argument: %x. 20 or 32 hex-encoded bytes required", startArg) } + var conf = &state.DumpConfig{ SkipCode: ctx.Bool(utils.ExcludeCodeFlag.Name), SkipStorage: ctx.Bool(utils.ExcludeStorageFlag.Name), @@ -463,9 +501,11 @@ func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, eth Start: start.Bytes(), Max: ctx.Uint64(utils.DumpLimitFlag.Name), } + log.Info("State dump configured", "block", header.Number, "hash", header.Hash().Hex(), "skipcode", conf.SkipCode, "skipstorage", conf.SkipStorage, "start", hexutil.Encode(conf.Start), "limit", conf.Max) + return conf, db, header.Root, nil } @@ -481,10 +521,12 @@ func dump(ctx *cli.Context) error { config := &trie.Config{ Preimages: true, // always enable preimage lookup } + state, err := state.New(root, state.NewDatabaseWithConfig(db, config), nil) if err != nil { return err } + if ctx.Bool(utils.IterativeOutputFlag.Name) { state.IterativeDump(conf, json.NewEncoder(os.Stdout)) } else { @@ -493,8 +535,10 @@ func dump(ctx *cli.Context) error { " otherwise the accounts will overwrite each other in the resulting mapping.") return fmt.Errorf("incompatible options") } + fmt.Println(string(state.Dump(conf))) } + return nil } diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 08e24e4fb2..76d3b57220 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -90,6 +90,7 @@ func defaultNodeConfig() node.Config { cfg.HTTPModules = append(cfg.HTTPModules, "eth") cfg.WSModules = append(cfg.WSModules, "eth") cfg.IPCPath = clientIdentifier + ".ipc" + return cfg } @@ -119,6 +120,7 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Apply flags. utils.SetNodeConfig(ctx, &cfg.Node) + stack, err := node.New(&cfg.Node) if err != nil { utils.Fatalf("Failed to create the protocol stack: %v", err) @@ -133,6 +135,7 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if ctx.IsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.String(utils.EthStatsURLFlag.Name) } + applyMetricConfig(ctx, &cfg) // Set Bor config flags @@ -149,6 +152,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { v := ctx.Uint64(utils.OverrideShanghai.Name) cfg.Eth.OverrideShanghai = &v } + backend, eth := utils.RegisterEthService(stack, &cfg.Eth) // Configure log filter RPC API. @@ -168,6 +172,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { if ctx.IsSet(utils.SyncTargetFlag.Name) && cfg.Eth.SyncMode == downloader.FullSync { utils.RegisterFullSyncTester(stack, eth, ctx.Path(utils.SyncTargetFlag.Name)) } + return stack, backend } @@ -189,7 +194,6 @@ func dumpConfig(ctx *cli.Context) error { } func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) { - if ctx.IsSet(utils.MetricsEnabledFlag.Name) { cfg.Metrics.Enabled = ctx.Bool(utils.MetricsEnabledFlag.Name) } @@ -264,6 +268,7 @@ func setAccountManagerBackends(stack *node.Node) error { keydir := stack.KeyStoreDir() scryptN := keystore.StandardScryptN scryptP := keystore.StandardScryptP + if conf.UseLightweightKDF { scryptN = keystore.LightScryptN scryptP = keystore.LightScryptP @@ -272,6 +277,7 @@ func setAccountManagerBackends(stack *node.Node) error { // Assemble the supported backends if len(conf.ExternalSigner) > 0 { log.Info("Using external signer", "url", conf.ExternalSigner) + if extapi, err := external.NewExternalBackend(conf.ExternalSigner); err == nil { am.AddBackend(extapi) return nil @@ -285,6 +291,7 @@ func setAccountManagerBackends(stack *node.Node) 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. am.AddBackend(keystore.NewKeyStore(keydir, scryptN, scryptP)) + if conf.USB { // Start a USB hub for Ledger hardware wallets if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { @@ -305,6 +312,7 @@ func setAccountManagerBackends(stack *node.Node) error { am.AddBackend(trezorhub) } } + if len(conf.SmartCardDaemonPath) > 0 { // Start a smart card hub if schub, err := scwallet.NewHub(conf.SmartCardDaemonPath, scwallet.Scheme, keydir); err != nil { diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 49ea43e955..3a2ed64587 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -76,6 +76,7 @@ func localConsole(ctx *cli.Context) error { prepare(ctx) stack, backend := makeFullNode(ctx) startNode(ctx, stack, backend, true) + defer stack.Close() // Attach to the newly started node and create the JavaScript console. @@ -83,16 +84,19 @@ func localConsole(ctx *cli.Context) error { if err != nil { return fmt.Errorf("failed to attach to the inproc geth: %v", err) } + config := console.Config{ DataDir: utils.MakeDataDir(ctx), DocRoot: ctx.String(utils.JSpathFlag.Name), Client: client, Preload: utils.MakeConsolePreloads(ctx), } + console, err := console.New(config) if err != nil { return fmt.Errorf("failed to start the JavaScript console: %v", err) } + defer console.Stop(false) // If only a short execution was requested, evaluate and return. @@ -111,6 +115,7 @@ func localConsole(ctx *cli.Context) error { // Print the welcome screen and enter interactive mode. console.Welcome() console.Interactive() + return nil } @@ -120,12 +125,14 @@ func remoteConsole(ctx *cli.Context) error { if ctx.Args().Len() > 1 { utils.Fatalf("invalid command-line: too many arguments") } + endpoint := ctx.Args().First() if endpoint == "" { path := node.DefaultDataDir() if ctx.IsSet(utils.DataDirFlag.Name) { path = ctx.String(utils.DataDirFlag.Name) } + if path != "" { if ctx.Bool(utils.GoerliFlag.Name) { path = filepath.Join(path, "goerli") @@ -135,8 +142,8 @@ func remoteConsole(ctx *cli.Context) error { } else if ctx.Bool(utils.SepoliaFlag.Name) { path = filepath.Join(path, "sepolia") } - } + endpoint = fmt.Sprintf("%s/bor.ipc", path) } @@ -144,16 +151,19 @@ func remoteConsole(ctx *cli.Context) error { if err != nil { utils.Fatalf("Unable to attach to remote geth: %v", err) } + config := console.Config{ DataDir: utils.MakeDataDir(ctx), DocRoot: ctx.String(utils.JSpathFlag.Name), Client: client, Preload: utils.MakeConsolePreloads(ctx), } + console, err := console.New(config) if err != nil { utils.Fatalf("Failed to start the JavaScript console: %v", err) } + defer console.Stop(false) if script := ctx.String(utils.ExecFlag.Name); script != "" { @@ -164,6 +174,7 @@ func remoteConsole(ctx *cli.Context) error { // Otherwise print the welcome screen and enter interactive mode console.Welcome() console.Interactive() + return nil } @@ -178,5 +189,6 @@ func ephemeralConsole(ctx *cli.Context) error { utils.Fatalf(`The "js" command is deprecated. Please use the following instead: geth --exec "%s" console`, b.String()) + return nil } diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 46bdf3c90d..ffc894d14c 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -44,6 +44,7 @@ func runMinimalGeth(t *testing.T, args ...string) *testgeth { allArgs := []string{"--goerli", "--networkid", "1337", "--authrpc.port", "0", "--syncmode=full", "--port", "0", "--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64", "--datadir.minfreedisk", "0"} + return runGeth(t, append(allArgs, args...)...) } @@ -102,6 +103,7 @@ func TestAttachWelcome(t *testing.T) { "--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) diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index 0701a2d5a2..2013789e32 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -113,6 +113,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc if err := os.WriteFile(json, []byte(genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", test, err) } + runGeth(t, "--datadir", datadir, "--networkid", "1337", "init", json).WaitExit() } else { // Force chain initialization @@ -121,16 +122,19 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc } // Retrieve the DAO config flag from the database path := filepath.Join(datadir, "geth", "chaindata") + db, err := rawdb.NewLevelDBDatabase(path, 0, 0, "", false) if err != nil { t.Fatalf("test %d: failed to open test database: %v", test, err) } + defer db.Close() genesisHash := common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") if genesis != "" { genesisHash = daoGenesisHash } + config := rawdb.ReadChainConfig(db, genesisHash) if config == nil { t.Errorf("test %d: failed to retrieve chain config: %v", test, err) @@ -146,6 +150,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc } else if config.DAOForkBlock.Cmp(expectBlock) != 0 { t.Errorf("test %d: dao hard-fork block mismatch: have %v, want %v", test, config.DAOForkBlock, expectBlock) } + if config.DAOForkSupport != expectVote { t.Errorf("test %d: dao hard-fork support mismatch: have %v, want %v", test, config.DAOForkSupport, expectVote) } diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 6ef4acc174..29b0009a86 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -207,12 +207,14 @@ func removeDB(ctx *cli.Context) error { } // 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 { @@ -225,6 +227,7 @@ func removeDB(ctx *cli.Context) error { } else { log.Info("Light node database missing", "path", path) } + return nil } @@ -232,6 +235,7 @@ func removeDB(ctx *cli.Context) error { // 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) @@ -239,6 +243,7 @@ func confirmAndRemoveDB(database string, kind string) { 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 { @@ -249,6 +254,7 @@ func confirmAndRemoveDB(database string, kind string) { os.Remove(path) return nil } + return filepath.SkipDir }) log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start))) @@ -260,9 +266,11 @@ func inspect(ctx *cli.Context) error { 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) @@ -270,6 +278,7 @@ func inspect(ctx *cli.Context) error { 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) @@ -277,6 +286,7 @@ func inspect(ctx *cli.Context) error { start = d } } + stack, _ := makeConfigNode(ctx) defer stack.Close() @@ -359,6 +369,7 @@ func showLeveldbStats(db ethdb.KeyValueStater) { } else { fmt.Println(stats) } + if ioStats, err := db.Stat("leveldb.iostats"); err != nil { log.Warn("Failed to read database iostats", "error", err) } else { @@ -374,6 +385,7 @@ func dbStats(ctx *cli.Context) error { defer db.Close() showLeveldbStats(db) + return nil } @@ -388,12 +400,15 @@ func dbCompact(ctx *cli.Context) error { showLeveldbStats(db) log.Info("Triggering compaction") + if err := db.Compact(nil, nil); err != nil { log.Info("Compact err", "error", err) return err } + log.Info("Stats after compaction") showLeveldbStats(db) + return nil } @@ -402,6 +417,7 @@ 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() @@ -419,7 +435,9 @@ func dbGet(ctx *cli.Context) error { log.Info("Get operation failed", "key", fmt.Sprintf("%#x", key), "error", err) return err } + fmt.Printf("key %#x: %#x\n", key, data) + return nil } @@ -428,6 +446,7 @@ 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() @@ -439,14 +458,17 @@ func dbDelete(ctx *cli.Context) error { log.Info("Could not decode the key", "error", err) return err } + data, err := db.Get(key) if err == nil { fmt.Printf("Previous value: %#x\n", data) } + if err = db.Delete(key); err != nil { log.Info("Delete operation returned an error", "key", fmt.Sprintf("%#x", key), "error", err) return err } + return nil } @@ -455,6 +477,7 @@ 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() @@ -473,15 +496,18 @@ func dbPut(ctx *cli.Context) error { 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: %#x\n", data) } + return db.Put(key, value) } @@ -490,6 +516,7 @@ func dbDumpTrie(ctx *cli.Context) error { if ctx.NArg() < 3 { return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) } + stack, _ := makeConfigNode(ctx) defer stack.Close() @@ -535,20 +562,26 @@ func dbDumpTrie(ctx *cli.Context) error { } id := trie.StorageTrieID(common.BytesToHash(state), common.BytesToHash(account), common.BytesToHash(storage)) + theTrie, err := trie.New(id, trie.NewDatabase(db)) if err != nil { return err } + var count int64 + it := trie.NewIterator(theTrie.NodeIterator(start)) for it.Next() { if max > 0 && count == max { fmt.Printf("Exiting after %d values\n", count) break } + fmt.Printf(" %d. key %#x: %#x\n", count, it.Key, it.Value) + count++ } + return it.Err } @@ -575,6 +608,7 @@ func freezerInspect(ctx *cli.Context) error { log.Info("Could not read count param", "err", err) return err } + stack, _ := makeConfigNode(ctx) ancient := stack.ResolveAncient("chaindata", ctx.String(utils.AncientFlag.Name)) stack.Close() @@ -584,6 +618,7 @@ func freezerInspect(ctx *cli.Context) error { func importLDBdata(ctx *cli.Context) error { start := 0 + switch ctx.NArg() { case 1: break @@ -592,27 +627,35 @@ func importLDBdata(ctx *cli.Context) error { if err != nil { return fmt.Errorf("second arg must be an integer: %v", err) } + start = s default: return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) } + var ( fName = ctx.Args().Get(0) stack, _ = makeConfigNode(ctx) interrupt = make(chan os.Signal, 1) stop = make(chan struct{}) ) + defer stack.Close() signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(interrupt) defer close(interrupt) + go func() { if _, ok := <-interrupt; ok { log.Info("Interrupted during ldb import, stopping at next batch") } + close(stop) }() + db := utils.MakeChainDatabase(ctx, stack, false) + return utils.ImportLDBData(db, fName, int64(start), stop) } @@ -627,6 +670,7 @@ func (iter *preimageIterator) Next() (byte, []byte, []byte, bool) { return utils.OpBatchAdd, key, iter.iter.Value(), true } } + return 0, nil, nil, false } @@ -645,18 +689,21 @@ func (iter *snapshotIterator) Next() (byte, []byte, []byte, bool) { iter.init = true return utils.OpBatchDel, rawdb.SnapshotRootKey, nil, true } + for iter.account.Next() { key := iter.account.Key() if bytes.HasPrefix(key, rawdb.SnapshotAccountPrefix) && len(key) == (len(rawdb.SnapshotAccountPrefix)+common.HashLength) { return utils.OpBatchAdd, key, iter.account.Value(), true } } + for iter.storage.Next() { key := iter.storage.Key() if bytes.HasPrefix(key, rawdb.SnapshotStoragePrefix) && len(key) == (len(rawdb.SnapshotStoragePrefix)+2*common.HashLength) { return utils.OpBatchAdd, key, iter.storage.Value(), true } } + return 0, nil, nil, false } @@ -685,30 +732,39 @@ func exportChaindata(ctx *cli.Context) error { // Parse the required chain data type, make sure it's supported. kind := ctx.Args().Get(0) kind = strings.ToLower(strings.Trim(kind, " ")) + exporter, ok := chainExporters[kind] if !ok { var kinds []string for kind := range chainExporters { kinds = append(kinds, kind) } + return fmt.Errorf("invalid data type %s, supported types: %s", kind, strings.Join(kinds, ", ")) } + var ( stack, _ = makeConfigNode(ctx) interrupt = make(chan os.Signal, 1) stop = make(chan struct{}) ) + defer stack.Close() signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(interrupt) defer close(interrupt) + go func() { if _, ok := <-interrupt; ok { log.Info("Interrupted during db export, stopping at next batch") } + close(stop) }() + db := utils.MakeChainDatabase(ctx, stack, true) + return utils.ExportChaindata(ctx.Args().Get(1), kind, exporter(db), stop) } @@ -716,6 +772,7 @@ func showMetaData(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack, true) + ancients, err := db.Ancients() if err != nil { fmt.Fprintf(os.Stderr, "Error accessing ancients: %v", err) @@ -724,19 +781,23 @@ func showMetaData(ctx *cli.Context) error { data := rawdb.ReadChainMetadata(db) data = append(data, []string{"frozen", fmt.Sprintf("%d items", ancients)}) data = append(data, []string{"snapshotGenerator", snapshot.ParseGeneratorStatus(rawdb.ReadSnapshotGenerator(db))}) + if b := rawdb.ReadHeadBlock(db); b != nil { data = append(data, []string{"headBlock.Hash", fmt.Sprintf("%v", b.Hash())}) data = append(data, []string{"headBlock.Root", fmt.Sprintf("%v", b.Root())}) data = append(data, []string{"headBlock.Number", fmt.Sprintf("%d (%#x)", b.Number(), b.Number())}) } + if h := rawdb.ReadHeadHeader(db); h != nil { data = append(data, []string{"headHeader.Hash", fmt.Sprintf("%v", h.Hash())}) data = append(data, []string{"headHeader.Root", fmt.Sprintf("%v", h.Root)}) data = append(data, []string{"headHeader.Number", fmt.Sprintf("%d (%#x)", h.Number, h.Number)}) } + table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"Field", "Value"}) table.AppendBulk(data) table.Render() + return nil } diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index 80468e8e9a..932454fa85 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -73,6 +73,7 @@ var customGenesisTests = []struct { // work properly. func TestCustomGenesis(t *testing.T) { t.Parallel() + for i, tt := range customGenesisTests { // Create a temporary data directory to use and inspect later datadir := t.TempDir() @@ -82,6 +83,7 @@ func TestCustomGenesis(t *testing.T) { if err := os.WriteFile(json, []byte(tt.genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", i, err) } + runGeth(t, "--datadir", datadir, "init", json).WaitExit() // Query the custom genesis block diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index 607b454ead..06cef45654 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -53,12 +53,15 @@ func (g *gethrpc) addPeer(peer *gethrpc) { g.geth.Logf("%v.addPeer(%v)", g.name, peer.name) enode := peer.getNodeInfo().Enode peerCh := make(chan *p2p.PeerEvent) + sub, err := g.rpc.Subscribe(context.Background(), "admin", peerCh, "peerEvents") if err != nil { g.geth.Fatalf("subscribe %v: %v", g.name, err) } + defer sub.Unsubscribe() g.callRPC(nil, "admin_addPeer", enode) + dur := 14 * time.Second timeout := time.After(dur) select { @@ -76,8 +79,10 @@ func (g *gethrpc) getNodeInfo() *p2p.NodeInfo { if g.nodeInfo != nil { return g.nodeInfo } + g.nodeInfo = &p2p.NodeInfo{} g.callRPC(&g.nodeInfo, "admin_nodeInfo") + return g.nodeInfo } @@ -90,6 +95,7 @@ func ipcEndpoint(ipcPath, datadir string) string { if strings.HasPrefix(ipcPath, `\\.\pipe\`) { return ipcPath } + return `\\.\pipe\` + ipcPath } // Resolve names into the data directory full paths otherwise @@ -97,8 +103,10 @@ func ipcEndpoint(ipcPath, datadir string) string { if datadir == "" { return filepath.Join(os.TempDir(), ipcPath) } + return filepath.Join(datadir, ipcPath) } + return ipcPath } @@ -122,13 +130,16 @@ func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { // We can't know exactly how long geth will take to start, so we try 10 // times over a 5 second period. var err error + for i := 0; i < 10; i++ { time.Sleep(500 * time.Millisecond) + if g.rpc, err = rpc.Dial(ipcpath); err == nil { return g } } t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err) + return nil } @@ -138,6 +149,7 @@ func initGeth(t *testing.T) string { g := runGeth(t, args...) datadir := g.Datadir g.WaitExit() + return datadir } @@ -145,8 +157,10 @@ func startLightServer(t *testing.T) *gethrpc { datadir := initGeth(t) t.Logf("Importing keys to geth") runGeth(t, "account", "import", "--datadir", datadir, "--password", "./testdata/password.txt", "--lightkdf", "./testdata/key.prv").WaitExit() + account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--miner.etherbase=0x02f0d131f1f97aef08aec6e3291b957d9efe7105", "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1", "--verbosity=4") + return server } @@ -165,7 +179,9 @@ func TestPriorityClient(t *testing.T) { freeCli.addPeer(lightServer) var peers []*p2p.PeerInfo + freeCli.callRPC(&peers, "admin_peers") + if len(peers) != 1 { t.Errorf("Expected: # of client peers == 1, actual: %v", len(peers)) return @@ -181,6 +197,7 @@ func TestPriorityClient(t *testing.T) { // Check if priority client is actually syncing and the regular client got kicked out prioCli.callRPC(&peers, "admin_peers") + if len(peers) != 1 { t.Errorf("Expected: # of prio peers == 1, actual: %v", len(peers)) } @@ -190,15 +207,19 @@ func TestPriorityClient(t *testing.T) { freeCli.getNodeInfo().ID: freeCli, prioCli.getNodeInfo().ID: prioCli, } + time.Sleep(1 * time.Second) lightServer.callRPC(&peers, "admin_peers") + peersWithNames := make(map[string]string) for _, p := range peers { peersWithNames[nodes[p.ID].name] = p.ID } + if _, freeClientFound := peersWithNames[freeCli.name]; freeClientFound { t.Error("client is still a peer of lightServer", peersWithNames) } + if _, prioClientFound := peersWithNames[prioCli.name]; !prioClientFound { t.Error("prio client is not among lightServer peers", peersWithNames) } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index f2d11b51ac..f807da0c71 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -269,6 +269,7 @@ func init() { app.After = func(ctx *cli.Context) error { debug.Exit() prompt.Stdin.Close() // Resets terminal mode. + return nil } } @@ -361,11 +362,13 @@ func geth(ctx *cli.Context) error { } prepare(ctx) + stack, backend := makeFullNode(ctx) defer stack.Close() startNode(ctx, stack, backend, false) stack.Wait() + return nil } @@ -390,6 +393,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon if err != nil { utils.Fatalf("Failed to attach to self: %v", err) } + ethClient := ethclient.NewClient(rpcClient) go func() { @@ -414,6 +418,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon if event.Wallet.URL().Scheme == "ledger" { derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath) } + derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath) event.Wallet.SelfDerive(derivationPaths, ethClient) @@ -431,15 +436,18 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon go func() { sub := stack.EventMux().Subscribe(downloader.DoneEvent{}) defer sub.Unsubscribe() + for { event := <-sub.Chan() if event == nil { continue } + done, ok := event.Data.(downloader.DoneEvent) if !ok { continue } + if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute { log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(), "age", common.PrettyAge(timestamp)) @@ -455,6 +463,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon if ctx.String(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } + ethBackend, ok := backend.(*eth.EthAPIBackend) if !ok { utils.Fatalf("Ethereum service not running") @@ -499,6 +508,7 @@ func unlockAccounts(ctx *cli.Context, stack *node.Node) { ks := backends[0].(*keystore.KeyStore) passwords := utils.MakePasswordList(ctx) + for i, account := range unlocks { unlockAccount(ks, account, i, passwords) } diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go index 5e7aa7bb12..7adcdb065d 100644 --- a/cmd/geth/misccmd.go +++ b/cmd/geth/misccmd.go @@ -103,10 +103,12 @@ func makecache(ctx *cli.Context) error { if len(args) != 2 { utils.Fatalf(`Usage: geth makecache `) } + block, err := strconv.ParseUint(args[0], 0, 64) if err != nil { utils.Fatalf("Invalid block number: %v", err) } + ethash.MakeCache(block, args[1]) return nil @@ -118,10 +120,12 @@ func makedag(ctx *cli.Context) error { if len(args) != 2 { utils.Fatalf(`Usage: geth makedag `) } + block, err := strconv.ParseUint(args[0], 0, 64) if err != nil { utils.Fatalf("Invalid block number: %v", err) } + ethash.MakeDataset(block, args[1]) return nil @@ -140,11 +144,13 @@ func printVersion(ctx *cli.Context) error { if git.Date != "" { fmt.Println("Git Commit Date:", git.Date) } + fmt.Println("Architecture:", runtime.GOARCH) fmt.Println("Go Version:", runtime.Version()) fmt.Println("Operating System:", runtime.GOOS) fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) fmt.Printf("GOROOT=%s\n", runtime.GOROOT()) + return nil } @@ -161,5 +167,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with geth. If not, see .`) + return nil } diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go index 0588623acb..98114d631c 100644 --- a/cmd/geth/run_test.go +++ b/cmd/geth/run_test.go @@ -43,6 +43,7 @@ func init() { fmt.Fprintln(os.Stderr, err) os.Exit(1) } + os.Exit(0) }) } @@ -52,6 +53,7 @@ func TestMain(m *testing.M) { if reexec.Init() { return } + os.Exit(m.Run()) } @@ -60,6 +62,7 @@ func TestMain(m *testing.M) { func runGeth(t *testing.T, args ...string) *testgeth { tt := &testgeth{} tt.TestCmd = cmdtest.NewTestCmd(t, tt) + for i, arg := range args { switch arg { case "--datadir": @@ -72,6 +75,7 @@ func runGeth(t *testing.T, args ...string) *testgeth { } } } + if tt.Datadir == "" { // The temporary datadir will be removed automatically if something fails below. tt.Datadir = t.TempDir() @@ -90,22 +94,27 @@ func waitForEndpoint(t *testing.T, endpoint string, timeout time.Duration) { probe := func() bool { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() + c, err := rpc.DialContext(ctx, endpoint) if c != nil { _, err = c.SupportedModules() c.Close() } + return err == nil } start := time.Now() + for { if probe() { return } + if time.Since(start) > timeout { t.Fatal("endpoint", endpoint, "did not open within", timeout) } + time.Sleep(200 * time.Millisecond) } } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 61bd79d8d7..bb65297953 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -172,15 +172,18 @@ func pruneState(ctx *cli.Context) error { Cachedir: stack.ResolvePath(config.Eth.TrieCleanCacheJournal), BloomSize: ctx.Uint64(utils.BloomFilterSizeFlag.Name), } + pruner, err := pruner.NewPruner(chaindb, prunerconfig) if err != nil { log.Error("Failed to open snapshot tree", "err", 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().First()) @@ -189,10 +192,12 @@ func pruneState(ctx *cli.Context) error { return err } } + if err = pruner.Prune(targetRoot); err != nil { log.Error("Failed to prune state", "err", err) return err } + return nil } @@ -215,15 +220,18 @@ func verifyState(ctx *cli.Context) error { NoBuild: true, AsyncBuild: false, } + snaptree, err := snapshot.New(snapconfig, chaindb, trie.NewDatabase(chaindb), headBlock.Root()) if err != nil { log.Error("Failed to open snapshot tree", "err", err) return err } + if ctx.NArg() > 1 { log.Error("Too many arguments given") return errors.New("too many arguments") } + var root = headBlock.Root() if ctx.NArg() == 1 { root, err = parseRoot(ctx.Args().First()) @@ -232,10 +240,12 @@ func verifyState(ctx *cli.Context) error { return err } } + if err := snaptree.Verify(root); err != nil { log.Error("Failed to verify state", "root", root, "err", err) return err } + log.Info("Verified the state", "root", root) return snapshot.CheckDanglingStorage(chaindb) @@ -258,36 +268,44 @@ func traverseState(ctx *cli.Context) error { defer stack.Close() chaindb := utils.MakeChainDatabase(ctx, stack, true) + headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") return errors.New("no head block") } + if ctx.NArg() > 1 { log.Error("Too many arguments given") return errors.New("too many arguments") } + var ( root common.Hash err error ) + if ctx.NArg() == 1 { root, err = parseRoot(ctx.Args().First()) if err != nil { log.Error("Failed to resolve state root", "err", err) return err } + log.Info("Start traversing the state", "root", root) } else { root = headBlock.Root() log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } + triedb := trie.NewDatabase(chaindb) + t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) if err != nil { log.Error("Failed to open trie", "root", root, "err", err) return err } + var ( accounts int slots int @@ -295,9 +313,11 @@ func traverseState(ctx *cli.Context) error { lastReport time.Time start = time.Now() ) + accIter := trie.NewIterator(t.NodeIterator(nil)) for accIter.Next() { accounts += 1 + var acc types.StateAccount if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { log.Error("Invalid account encountered during traversal", "err", err) @@ -306,15 +326,18 @@ func traverseState(ctx *cli.Context) error { if acc.Root != types.EmptyRootHash { id := trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root) + storageTrie, err := trie.NewStateTrie(id, triedb) if err != nil { log.Error("Failed to open storage trie", "root", acc.Root, "err", 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, "err", storageIter.Err) return storageIter.Err @@ -326,18 +349,23 @@ func traverseState(ctx *cli.Context) error { 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, "err", accIter.Err) return accIter.Err } + log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + return nil } @@ -350,36 +378,44 @@ func traverseRawState(ctx *cli.Context) error { defer stack.Close() chaindb := utils.MakeChainDatabase(ctx, stack, true) + headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") return errors.New("no head block") } + if ctx.NArg() > 1 { log.Error("Too many arguments given") return errors.New("too many arguments") } + var ( root common.Hash err error ) + if ctx.NArg() == 1 { root, err = parseRoot(ctx.Args().First()) if err != nil { log.Error("Failed to resolve state root", "err", err) return err } + log.Info("Start traversing the state", "root", root) } else { root = headBlock.Root() log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } + triedb := trie.NewDatabase(chaindb) + t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) if err != nil { log.Error("Failed to open trie", "root", root, "err", err) return err } + var ( nodes int accounts int @@ -390,6 +426,7 @@ func traverseRawState(ctx *cli.Context) error { hasher = crypto.NewKeccakState() got = make([]byte, 32) ) + accIter := t.NodeIterator(nil) for accIter.Next(true) { nodes += 1 @@ -417,6 +454,7 @@ func traverseRawState(ctx *cli.Context) error { // dig into the storage trie further. if accIter.Leaf() { accounts += 1 + var acc types.StateAccount if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { log.Error("Invalid account encountered during traversal", "err", err) @@ -425,11 +463,13 @@ func traverseRawState(ctx *cli.Context) error { if acc.Root != types.EmptyRootHash { id := trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root) + storageTrie, err := trie.NewStateTrie(id, triedb) if err != nil { log.Error("Failed to open storage trie", "root", acc.Root, "err", err) return errors.New("missing storage trie") } + storageIter := storageTrie.NodeIterator(nil) for storageIter.Next(true) { nodes += 1 @@ -458,6 +498,7 @@ func traverseRawState(ctx *cli.Context) error { slots += 1 } } + if storageIter.Error() != nil { log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error()) return storageIter.Error() @@ -469,19 +510,24 @@ func traverseRawState(ctx *cli.Context) error { 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, "err", 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 } @@ -490,6 +536,7 @@ func parseRoot(input string) (common.Hash, error) { if err := h.UnmarshalText([]byte(input)); err != nil { return h, err } + return h, nil } @@ -508,31 +555,38 @@ func dumpState(ctx *cli.Context) error { NoBuild: true, AsyncBuild: false, } + snaptree, err := snapshot.New(snapConfig, db, trie.NewDatabase(db), root) if err != nil { return err } + accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start)) if err != nil { return err } + defer accIt.Release() log.Info("Snapshot dumping started", "root", root) + var ( start = time.Now() logged = time.Now() accounts uint64 ) + enc := json.NewEncoder(os.Stdout) enc.Encode(struct { Root common.Hash `json:"root"` }{root}) + for accIt.Next() { account, err := snapshot.FullAccount(accIt.Account()) if err != nil { return err } + da := &state.DumpAccount{ Balance: account.Balance.String(), Nonce: account.Nonce, @@ -544,6 +598,7 @@ func dumpState(ctx *cli.Context) error { if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash)) } + if !conf.SkipStorage { da.Storage = make(map[common.Hash]string) @@ -551,23 +606,29 @@ func dumpState(ctx *cli.Context) error { if err != nil { return err } + for stIt.Next() { da.Storage[stIt.Hash()] = common.Bytes2Hex(stIt.Slot()) } } + enc.Encode(da) + accounts++ if time.Since(logged) > 8*time.Second { log.Info("Snapshot dumping in progress", "at", accIt.Hash(), "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() } + if conf.Max > 0 && accounts >= conf.Max { break } } log.Info("Snapshot dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start))) + return nil } diff --git a/cmd/geth/version_check.go b/cmd/geth/version_check.go index 96ccb0eebc..48746ee547 100644 --- a/cmd/geth/version_check.go +++ b/cmd/geth/version_check.go @@ -59,6 +59,7 @@ 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) } @@ -68,46 +69,60 @@ func checkCurrent(url, current string) error { 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 } @@ -116,15 +131,19 @@ func fetch(url string) ([]byte, error) { if filep := strings.TrimPrefix(url, "file://"); filep != url { return os.ReadFile(filep) } + res, err := http.Get(url) if err != nil { return nil, err } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) if err != nil { return nil, err } + return body, nil } @@ -137,26 +156,33 @@ func verifySignature(pubkeys []string, data, sigdata []byte) error { } // 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 } @@ -167,5 +193,6 @@ func keyID(id [8]byte) string { 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 index bd4d820a79..b629f418ae 100644 --- a/cmd/geth/version_check_test.go +++ b/cmd/geth/version_check_test.go @@ -58,11 +58,13 @@ func testVerification(t *testing.T, pubkey, sigdir string) { if err != nil { t.Fatal(err) } + for _, f := range files { sig, err := os.ReadFile(filepath.Join(sigdir, f.Name())) if err != nil { t.Fatal(err) } + err = verifySignature([]string{pubkey}, data, sig) if err != nil { t.Fatal(err) @@ -76,36 +78,45 @@ func versionUint(v string) int { 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, _ := os.ReadFile("./testdata/vcheck/vulnerabilities.json") + var vulns []vulnJson + if err := json.Unmarshal(data, &vulns); err != nil { t.Fatal(err) } + 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) { @@ -120,6 +131,7 @@ func TestMatching(t *testing.T) { } } } + for major := 1; major < 2; major++ { for minor := 0; minor < 30; minor++ { for patch := 0; patch < 30; patch++ { @@ -143,6 +155,7 @@ func TestKeyID(t *testing.T) { type args struct { id [8]byte } + tests := []struct { name string args args diff --git a/cmd/p2psim/main.go b/cmd/p2psim/main.go index e1cdd857c8..771ba52af3 100644 --- a/cmd/p2psim/main.go +++ b/cmd/p2psim/main.go @@ -197,6 +197,7 @@ func main() { }, }, } + if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -207,14 +208,17 @@ func showNetwork(ctx *cli.Context) error { if ctx.NArg() != 0 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + network, err := client.GetNetwork() if err != nil { return err } + w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0) defer w.Flush() fmt.Fprintf(w, "NODES\t%d\n", len(network.Nodes)) fmt.Fprintf(w, "CONNS\t%d\n", len(network.Conns)) + return nil } @@ -222,7 +226,9 @@ func streamNetwork(ctx *cli.Context) error { if ctx.NArg() != 0 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + events := make(chan *simulations.Event) + sub, err := client.SubscribeNetwork(events, simulations.SubscribeOpts{ Current: ctx.Bool(currentFlag.Name), Filter: ctx.String(filterFlag.Name), @@ -230,8 +236,11 @@ func streamNetwork(ctx *cli.Context) error { if err != nil { return err } + defer sub.Unsubscribe() + enc := json.NewEncoder(ctx.App.Writer) + for { select { case event := <-events: @@ -248,10 +257,12 @@ func createSnapshot(ctx *cli.Context) error { if ctx.NArg() != 0 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + snap, err := client.CreateSnapshot() if err != nil { return err } + return json.NewEncoder(os.Stdout).Encode(snap) } @@ -259,10 +270,12 @@ func loadSnapshot(ctx *cli.Context) error { if ctx.NArg() != 0 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + snap := &simulations.Snapshot{} if err := json.NewDecoder(os.Stdin).Decode(snap); err != nil { return err } + return client.LoadSnapshot(snap) } @@ -270,16 +283,20 @@ func listNodes(ctx *cli.Context) error { if ctx.NArg() != 0 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + nodes, err := client.GetNodes() if err != nil { return err } + w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0) defer w.Flush() fmt.Fprintf(w, "NAME\tPROTOCOLS\tID\n") + for _, node := range nodes { fmt.Fprintf(w, "%s\t%s\t%s\n", node.Name, strings.Join(protocolList(node), ","), node.ID) } + return nil } @@ -288,6 +305,7 @@ func protocolList(node *p2p.NodeInfo) []string { for name := range node.Protocols { protos = append(protos, name) } + return protos } @@ -295,24 +313,31 @@ func createNode(ctx *cli.Context) error { if ctx.NArg() != 0 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + config := adapters.RandomNodeConfig() config.Name = ctx.String(nameFlag.Name) + if key := ctx.String(keyFlag.Name); key != "" { privKey, err := crypto.HexToECDSA(key) if err != nil { return err } + config.ID = enode.PubkeyToIDV4(&privKey.PublicKey) config.PrivateKey = privKey } + if services := ctx.String(servicesFlag.Name); services != "" { config.Lifecycles = strings.Split(services, ",") } + node, err := client.CreateNode(config) if err != nil { return err } + fmt.Fprintln(ctx.App.Writer, "Created", node.Name) + return nil } @@ -320,23 +345,28 @@ func showNode(ctx *cli.Context) error { if ctx.NArg() != 1 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + nodeName := ctx.Args().First() + node, err := client.GetNode(nodeName) if err != nil { return err } + w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0) defer w.Flush() fmt.Fprintf(w, "NAME\t%s\n", node.Name) fmt.Fprintf(w, "PROTOCOLS\t%s\n", strings.Join(protocolList(node), ",")) fmt.Fprintf(w, "ID\t%s\n", node.ID) fmt.Fprintf(w, "ENODE\t%s\n", node.Enode) + for name, proto := range node.Protocols { fmt.Fprintln(w) fmt.Fprintf(w, "--- PROTOCOL INFO: %s\n", name) fmt.Fprintf(w, "%v\n", proto) fmt.Fprintf(w, "---\n") } + return nil } @@ -344,11 +374,14 @@ func startNode(ctx *cli.Context) error { if ctx.NArg() != 1 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + nodeName := ctx.Args().First() if err := client.StartNode(nodeName); err != nil { return err } + fmt.Fprintln(ctx.App.Writer, "Started", nodeName) + return nil } @@ -356,11 +389,14 @@ func stopNode(ctx *cli.Context) error { if ctx.NArg() != 1 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + nodeName := ctx.Args().First() if err := client.StopNode(nodeName); err != nil { return err } + fmt.Fprintln(ctx.App.Writer, "Stopped", nodeName) + return nil } @@ -368,13 +404,17 @@ func connectNode(ctx *cli.Context) error { if ctx.NArg() != 2 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + args := ctx.Args() nodeName := args.Get(0) peerName := args.Get(1) + if err := client.ConnectNode(nodeName, peerName); err != nil { return err } + fmt.Fprintln(ctx.App.Writer, "Connected", nodeName, "to", peerName) + return nil } @@ -383,12 +423,16 @@ func disconnectNode(ctx *cli.Context) error { if args.Len() != 2 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + nodeName := args.Get(0) peerName := args.Get(1) + if err := client.DisconnectNode(nodeName, peerName); err != nil { return err } + fmt.Fprintln(ctx.App.Writer, "Disconnected", nodeName, "from", peerName) + return nil } @@ -397,23 +441,30 @@ func rpcNode(ctx *cli.Context) error { if args.Len() < 2 { return cli.ShowCommandHelp(ctx, ctx.Command.Name) } + nodeName := args.Get(0) method := args.Get(1) + rpcClient, err := client.RPCClient(context.Background(), nodeName) if err != nil { return err } + if ctx.Bool(subscribeFlag.Name) { return rpcSubscribe(rpcClient, ctx.App.Writer, method, args.Slice()[3:]...) } + var result interface{} + params := make([]interface{}, len(args.Slice()[3:])) for i, v := range args.Slice()[3:] { params[i] = v } + if err := rpcClient.Call(&result, method, params...); err != nil { return err } + return json.NewEncoder(ctx.App.Writer).Encode(result) } @@ -424,15 +475,20 @@ func rpcSubscribe(client *rpc.Client, out io.Writer, method string, args ...stri ch := make(chan interface{}) subArgs := make([]interface{}, len(args)+1) subArgs[0] = method + for i, v := range args { subArgs[i+1] = v } + sub, err := client.Subscribe(context.Background(), namespace, ch, subArgs...) if err != nil { return err } + defer sub.Unsubscribe() + enc := json.NewEncoder(out) + for { select { case v := <-ch: diff --git a/cmd/rlpdump/main.go b/cmd/rlpdump/main.go index d57e1d1878..c1b6c86147 100644 --- a/cmd/rlpdump/main.go +++ b/cmd/rlpdump/main.go @@ -53,12 +53,14 @@ func main() { flag.Parse() var r io.Reader + switch { case *hexMode != "": data, err := hex.DecodeString(strings.TrimPrefix(*hexMode, "0x")) if err != nil { die(err) } + r = bytes.NewReader(data) case flag.NArg() == 0: @@ -69,6 +71,7 @@ func main() { if err != nil { die(err) } + defer fd.Close() r = fd @@ -77,7 +80,9 @@ func main() { flag.Usage() os.Exit(2) } + out := os.Stdout + if *reverseMode { data, err := textToRlp(r) if err != nil { @@ -85,6 +90,7 @@ func main() { } fmt.Printf("%#x\n", data) + return } else { err := rlpToText(r, out) @@ -96,18 +102,23 @@ func main() { func rlpToText(r io.Reader, out io.Writer) error { s := rlp.NewStream(r, 0) + for { if err := dump(s, 0, out); err != nil { if err != io.EOF { return err } + break } + fmt.Fprintln(out) + if *single { break } } + return nil } @@ -116,12 +127,14 @@ func dump(s *rlp.Stream, depth int, out io.Writer) error { if err != nil { return err } + switch kind { case rlp.Byte, rlp.String: str, err := s.Bytes() if err != nil { return err } + if len(str) == 0 || !*noASCII && isASCII(str) { fmt.Fprintf(out, "%s%q", ws(depth), str) } else { @@ -130,14 +143,17 @@ func dump(s *rlp.Stream, depth int, out io.Writer) error { case rlp.List: s.List() defer s.ListEnd() + if size == 0 { fmt.Fprintf(out, ws(depth)+"[]") } else { fmt.Fprintln(out, ws(depth)+"[") + for i := 0; ; i++ { if i > 0 { fmt.Fprint(out, ",\n") } + if err := dump(s, depth+1, out); err == rlp.EOL { break } else if err != nil { @@ -147,6 +163,7 @@ func dump(s *rlp.Stream, depth int, out io.Writer) error { fmt.Fprint(out, ws(depth)+"]") } } + return nil } @@ -156,6 +173,7 @@ func isASCII(b []byte) bool { return false } } + return true } @@ -179,11 +197,13 @@ func textToRlp(r io.Reader) ([]byte, error) { obj []interface{} stack = list.New() ) + for scanner.Scan() { t := strings.TrimSpace(scanner.Text()) if len(t) == 0 { continue } + switch t { case "[": // list start stack.PushFront(obj) @@ -200,12 +220,16 @@ func textToRlp(r io.Reader) ([]byte, error) { } else { // hex data data = common.FromHex(string(data)) } + obj = append(obj, data) } } + if err := scanner.Err(); err != nil { return nil, err } + data, err := rlp.EncodeToBytes(obj[0]) + return data, err } diff --git a/cmd/rlpdump/rlpdump_test.go b/cmd/rlpdump/rlpdump_test.go index 534bd87ece..2d5d4a459d 100644 --- a/cmd/rlpdump/rlpdump_test.go +++ b/cmd/rlpdump/rlpdump_test.go @@ -33,11 +33,14 @@ func TestRoundtrip(t *testing.T) { "0xc780c0c1c0825208", } { var out strings.Builder + err := rlpToText(bytes.NewReader(common.FromHex(want)), &out) if err != nil { t.Fatal(err) } + text := out.String() + rlpBytes, err := textToRlp(strings.NewReader(text)) if err != nil { t.Errorf("test %d: error %v", i, err) @@ -56,6 +59,7 @@ func TestTextToRlp(t *testing.T) { text string want string } + cases := []tc{ { text: `[ @@ -75,6 +79,7 @@ func TestTextToRlp(t *testing.T) { t.Errorf("test %d: error %v", i, err) continue } + if hexutil.Encode(have) != tc.want { t.Errorf("test %d:\nhave %v\nwant %v", i, hexutil.Encode(have), tc.want) } diff --git a/cmd/utils/bor_flags.go b/cmd/utils/bor_flags.go index 83c94d82f7..323ec1f885 100644 --- a/cmd/utils/bor_flags.go +++ b/cmd/utils/bor_flags.go @@ -69,16 +69,19 @@ var ( func getGenesis(genesisPath string) (*core.Genesis, error) { log.Info("Reading genesis at ", "file", genesisPath) + file, err := os.Open(genesisPath) if err != nil { return nil, err } + defer file.Close() genesis := new(core.Genesis) if err := json.NewDecoder(file).Decode(genesis); err != nil { return nil, err } + return genesis, nil } @@ -104,6 +107,7 @@ func CreateBorEthereum(cfg *ethconfig.Config) *eth.Ethereum { if err != nil { Fatalf("Failed to create node: %v", err) } + ethereum, err := eth.New(stack, cfg) if err != nil { Fatalf("Failed to register Ethereum protocol: %v", err) @@ -113,6 +117,7 @@ func CreateBorEthereum(cfg *ethconfig.Config) *eth.Ethereum { if err = stack.Start(); err != nil { Fatalf("Failed to start stack: %v", err) } + _, err = stack.Attach() if err != nil { Fatalf("Failed to attach to node: %v", err) diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index e9f2e24b40..662a5fd017 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -22,6 +22,7 @@ import ( "compress/gzip" "errors" "fmt" + "github.com/urfave/cli/v2" "io" "os" "os/signal" @@ -59,10 +60,12 @@ func Fatalf(format string, args ...interface{}) { } else { outf, _ := os.Stdout.Stat() errf, _ := os.Stderr.Stat() + if outf != nil && errf != nil && os.SameFile(outf, errf) { w = os.Stderr } } + fmt.Fprintf(w, "Fatal: "+format+"\n", args...) os.Exit(1) } @@ -71,6 +74,7 @@ func StartNode(ctx *cli.Context, stack *node.Node, isConsole bool) { if err := stack.Start(); err != nil { Fatalf("Error starting protocol stack: %v", err) } + go func() { sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) @@ -82,15 +86,19 @@ func StartNode(ctx *cli.Context, stack *node.Node, isConsole bool) { } else if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheGCFlag.Name) { minFreeDiskSpace = 2 * ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100 } + if minFreeDiskSpace > 0 { go monitorFreeDiskSpace(sigc, stack.InstanceDir(), uint64(minFreeDiskSpace)*1024*1024) } shutdown := func() { log.Info("Got interrupt, shutting down...") + go stack.Close() + for i := 10; i > 0; i-- { <-sigc + if i > 1 { log.Warn("Already shutting down, interrupt more to panic.", "times", i-1) } @@ -123,9 +131,11 @@ func monitorFreeDiskSpace(sigc chan os.Signal, path string, freeDiskSpaceCritica 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), "path", path) 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), "path", path) @@ -140,15 +150,20 @@ func ImportChain(chain *core.BlockChain, fn string) error { // If a signal is received, the import will stop at the next batch. interrupt := make(chan os.Signal, 1) stop := make(chan struct{}) + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(interrupt) defer close(interrupt) + go func() { if _, ok := <-interrupt; ok { log.Info("Interrupted during import, stopping at next batch") } + close(stop) }() + checkInterrupt := func() bool { select { case <-stop: @@ -173,16 +188,19 @@ func ImportChain(chain *core.BlockChain, fn string) error { return err } } + stream := rlp.NewStream(reader, 0) // Run actual the import. blocks := make(types.Blocks, importBatchSize) n := 0 + for batch := 0; ; batch++ { // Load a batch of RLP blocks. if checkInterrupt() { return fmt.Errorf("interrupted") } + i := 0 for ; i < importBatchSize; i++ { var b types.Block @@ -196,9 +214,11 @@ func ImportChain(chain *core.BlockChain, fn string) error { i-- continue } + blocks[i] = &b n++ } + if i == 0 { break } @@ -206,15 +226,18 @@ func ImportChain(chain *core.BlockChain, fn string) error { if checkInterrupt() { return fmt.Errorf("interrupted") } + missing := missingBlocks(chain, blocks[:i]) if len(missing) == 0 { log.Info("Skipping batch as all blocks present", "batch", batch, "first", blocks[0].Hash(), "last", blocks[i-1].Hash()) continue } + if _, err := chain.InsertChain(missing); err != nil { return fmt.Errorf("invalid block %d: %v", n, err) } } + return nil } @@ -226,6 +249,7 @@ func missingBlocks(chain *core.BlockChain, blocks []*types.Block) []*types.Block if !chain.HasBlock(block.Hash(), block.NumberU64()) { return blocks[i:] } + continue } // If we're above the chain head, state availability is a must @@ -233,6 +257,7 @@ func missingBlocks(chain *core.BlockChain, blocks []*types.Block) []*types.Block return blocks[i:] } } + return nil } @@ -257,6 +282,7 @@ func ExportChain(blockchain *core.BlockChain, fn string) error { if err := blockchain.Export(writer); err != nil { return err } + log.Info("Exported blockchain", "file", fn) return nil @@ -283,7 +309,9 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las if err := blockchain.ExportN(writer, first, last); err != nil { return err } + log.Info("Exported blockchain to", "file", fn) + return nil } @@ -305,6 +333,7 @@ func ImportPreimages(db ethdb.Database, fn string) error { return err } } + stream := rlp.NewStream(reader, 0) // Import the preimages in batches to prevent disk thrashing @@ -318,6 +347,7 @@ func ImportPreimages(db ethdb.Database, fn string) error { if err == io.EOF { break } + return err } // Accumulate the preimages and flush when enough ws gathered @@ -331,6 +361,7 @@ func ImportPreimages(db ethdb.Database, fn string) error { if len(preimages) > 0 { rawdb.WritePreimages(db, preimages) } + return nil } @@ -362,6 +393,7 @@ func ExportPreimages(db ethdb.Database, fn string) error { } } log.Info("Exported preimages", "file", fn) + return nil } @@ -400,6 +432,7 @@ func ImportLDBData(db ethdb.Database, f string, startIndex int64, interrupt chan return err } } + stream := rlp.NewStream(reader, 0) // Read the header @@ -407,12 +440,15 @@ func ImportLDBData(db ethdb.Database, f string, startIndex int64, interrupt chan if err := stream.Decode(&header); err != nil { return fmt.Errorf("could not decode header: %v", err) } + if header.Magic != exportMagic { return errors.New("incompatible data, wrong magic") } + if header.Version != 0 { return fmt.Errorf("incompatible version %d, (support only 0)", header.Version) } + log.Info("Importing data", "file", f, "type", header.Kind, "data age", common.PrettyDuration(time.Since(time.Unix(int64(header.UnixTime), 0)))) @@ -423,28 +459,35 @@ func ImportLDBData(db ethdb.Database, f string, startIndex int64, interrupt chan logged = time.Now() batch = db.NewBatch() ) + for { // Read the next entry var ( op byte key, val []byte ) + if err := stream.Decode(&op); err != nil { if err == io.EOF { break } + return err } + if err := stream.Decode(&key); err != nil { return err } + if err := stream.Decode(&val); err != nil { return err } + if count < startIndex { count++ continue } + switch op { case OpBatchDel: batch.Delete(key) @@ -453,10 +496,12 @@ func ImportLDBData(db ethdb.Database, f string, startIndex int64, interrupt chan default: return fmt.Errorf("unknown op %d\n", op) } + if batch.ValueSize() > ethdb.IdealBatchSize { if err := batch.Write(); err != nil { return err } + batch.Reset() } // Check interruption emitted by ctrl+c @@ -466,15 +511,19 @@ func ImportLDBData(db ethdb.Database, f string, startIndex int64, interrupt chan if err := batch.Write(); err != nil { return err } + log.Info("External data import interrupted", "file", f, "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + return nil default: } } + if count%1000 == 0 && time.Since(logged) > 8*time.Second { log.Info("Importing external data", "file", f, "count", count, "elapsed", common.PrettyDuration(time.Since(start))) logged = time.Now() } + count += 1 } // Flush the last batch snapshot data @@ -483,8 +532,10 @@ func ImportLDBData(db ethdb.Database, f string, startIndex int64, interrupt chan return err } } + log.Info("Imported chain data", "file", f, "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + return nil } @@ -504,6 +555,7 @@ type ChainDataIterator interface { // in the file. If the suffix is 'gz', gzip compression is used. func ExportChaindata(fn string, kind string, iter ChainDataIterator, interrupt chan struct{}) error { log.Info("Exporting chain data", "file", fn, "kind", kind) + defer iter.Release() // Open the file handle and potentially wrap with a gzip stream @@ -533,20 +585,25 @@ func ExportChaindata(fn string, kind string, iter ChainDataIterator, interrupt c start = time.Now() logged = time.Now() ) + for { op, key, val, ok := iter.Next() if !ok { break } + if err := rlp.Encode(writer, op); err != nil { return err } + if err := rlp.Encode(writer, key); err != nil { return err } + if err := rlp.Encode(writer, val); err != nil { return err } + if count%1000 == 0 { // Check interruption emitted by ctrl+c select { @@ -556,15 +613,19 @@ func ExportChaindata(fn string, kind string, iter ChainDataIterator, interrupt c return nil default: } + if time.Since(logged) > 8*time.Second { log.Info("Exporting chain data", "file", fn, "kind", kind, "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() } } + count++ } log.Info("Exported chain data", "file", fn, "kind", kind, "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + return nil } diff --git a/cmd/utils/export_test.go b/cmd/utils/export_test.go index 445e3fac37..d013b293e3 100644 --- a/cmd/utils/export_test.go +++ b/cmd/utils/export_test.go @@ -56,10 +56,12 @@ func (iter *testIterator) Next() (byte, []byte, []byte, bool) { if iter.index >= 999 { return 0, nil, nil, false } + iter.index += 1 if iter.index == 42 { iter.index += 1 } + return OpBatchAdd, []byte(fmt.Sprintf("key-%04d", iter.index)), []byte(fmt.Sprintf("value %d", iter.index)), true } @@ -71,7 +73,9 @@ func testExport(t *testing.T, f string) { if err != nil { t.Fatal(err) } + db := rawdb.NewMemoryDatabase() + err = ImportLDBData(db, f, 5, make(chan struct{})) if err != nil { t.Fatal(err) @@ -82,15 +86,18 @@ func testExport(t *testing.T, f string) { if (i < 5 || i == 42) && err == nil { t.Fatalf("expected no element at idx %d, got '%v'", i, string(v)) } + if !(i < 5 || i == 42) { if err != nil { t.Fatalf("expected element idx %d: %v", i, err) } + if have, want := string(v), fmt.Sprintf("value %d", i); have != want { t.Fatalf("have %v, want %v", have, want) } } } + v, err := db.Get([]byte(fmt.Sprintf("key-%04d", 1000))) if err == nil { t.Fatalf("expected no element at idx %d, got '%v'", 1000, string(v)) @@ -128,10 +135,12 @@ func (iter *deletionIterator) Next() (byte, []byte, []byte, bool) { if iter.index >= 999 { return 0, nil, nil, false } + iter.index += 1 if iter.index == 42 { iter.index += 1 } + return OpBatchDel, []byte(fmt.Sprintf("key-%04d", iter.index)), nil, true } @@ -142,24 +151,29 @@ func testDeletion(t *testing.T, f string) { if err != nil { t.Fatal(err) } + db := rawdb.NewMemoryDatabase() for i := 0; i < 1000; i++ { db.Put([]byte(fmt.Sprintf("key-%04d", i)), []byte(fmt.Sprintf("value %d", i))) } + err = ImportLDBData(db, f, 5, make(chan struct{})) if err != nil { t.Fatal(err) } + for i := 0; i < 1000; i++ { v, err := db.Get([]byte(fmt.Sprintf("key-%04d", i))) if i < 5 || i == 42 { if err != nil { t.Fatalf("expected element at idx %d, got '%v'", i, err) } + if have, want := string(v), fmt.Sprintf("value %d", i); have != want { t.Fatalf("have %v, want %v", have, want) } } + if !(i < 5 || i == 42) { if err == nil { t.Fatalf("expected no element idx %d: %v", i, string(v)) @@ -174,11 +188,14 @@ func TestImportFutureFormat(t *testing.T) { defer func() { os.Remove(f) }() + fh, err := os.OpenFile(f, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) if err != nil { t.Fatal(err) } + defer fh.Close() + if err := rlp.Encode(fh, &exportHeader{ Magic: exportMagic, Version: 500, @@ -187,11 +204,14 @@ func TestImportFutureFormat(t *testing.T) { }); err != nil { t.Fatal(err) } + db2 := rawdb.NewMemoryDatabase() + err = ImportLDBData(db2, f, 0, make(chan struct{})) if err == nil { t.Fatal("Expected error, got none") } + if !strings.HasPrefix(err.Error(), "incompatible version") { t.Fatalf("wrong error: %v", err) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 01d062201b..f0b9468188 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1042,9 +1042,12 @@ func MakeDataDir(ctx *cli.Context) string { if ctx.Bool(SepoliaFlag.Name) { return filepath.Join(path, "sepolia") } + return path } + Fatalf("Cannot determine default data directory, please set manually (--datadir)") + return "" } @@ -1058,6 +1061,7 @@ func setNodeKey(ctx *cli.Context, cfg *p2p.Config) { key *ecdsa.PrivateKey err error ) + switch { case file != "" && hex != "": Fatalf("Options %q and %q are mutually exclusive", NodeKeyFileFlag.Name, NodeKeyHexFlag.Name) @@ -1065,11 +1069,13 @@ func setNodeKey(ctx *cli.Context, cfg *p2p.Config) { if key, err = crypto.LoadECDSA(file); err != nil { Fatalf("Option %q: %v", NodeKeyFileFlag.Name, err) } + cfg.PrivateKey = key case hex != "": if key, err = crypto.HexToECDSA(hex); err != nil { Fatalf("Option %q: %v", NodeKeyHexFlag.Name, err) } + cfg.PrivateKey = key } } @@ -1085,6 +1091,7 @@ func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) { // flags, reverting to pre-configured ones if none have been specified. func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { urls := params.MainnetBootnodes + switch { case ctx.IsSet(BootnodesFlag.Name): urls = SplitAndTrim(ctx.String(BootnodesFlag.Name)) @@ -1100,6 +1107,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { } cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls)) + for _, url := range urls { if url != "" { node, err := enode.Parse(enode.ValidSchemes, url) @@ -1107,6 +1115,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { log.Crit("Bootstrap URL invalid", "enode", url, "err", err) continue } + cfg.BootstrapNodes = append(cfg.BootstrapNodes, node) } } @@ -1116,6 +1125,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { // flags, reverting to pre-configured ones if none have been specified. func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { urls := params.V5Bootnodes + switch { case ctx.IsSet(BootnodesFlag.Name): urls = SplitAndTrim(ctx.String(BootnodesFlag.Name)) @@ -1124,6 +1134,7 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { } cfg.BootstrapNodesV5 = make([]*enode.Node, 0, len(urls)) + for _, url := range urls { if url != "" { node, err := enode.Parse(enode.ValidSchemes, url) @@ -1131,6 +1142,7 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { log.Error("Bootstrap URL invalid", "enode", url, "err", err) continue } + cfg.BootstrapNodesV5 = append(cfg.BootstrapNodesV5, node) } } @@ -1155,6 +1167,7 @@ func setNAT(ctx *cli.Context, cfg *p2p.Config) { if err != nil { Fatalf("Option %s: %v", NATFlag.Name, err) } + cfg.NAT = natif } } @@ -1168,6 +1181,7 @@ func SplitAndTrim(input string) (ret []string) { ret = append(ret, r) } } + return ret } @@ -1261,6 +1275,7 @@ func setWS(ctx *cli.Context, cfg *node.Config) { // returning an empty string if IPC was explicitly disabled, or the set path. func setIPC(ctx *cli.Context, cfg *node.Config) { CheckExclusive(ctx, IPCDisabledFlag, IPCPathFlag) + switch { case ctx.Bool(IPCDisabledFlag.Name): cfg.IPCPath = "" @@ -1320,6 +1335,7 @@ func MakeDatabaseHandles(max int) int { if err != nil { Fatalf("Failed to retrieve file descriptor allowance: %v", err) } + switch { case max == 0: // User didn't specify a meaningful value, use system limits @@ -1333,10 +1349,12 @@ func MakeDatabaseHandles(max int) int { // User limit is meaningful and within allowed range, use that limit = max } + raised, err := fdlimit.Raise(uint64(limit)) if err != nil { Fatalf("Failed to raise file descriptor allowance: %v", err) } + return int(raised / 2) // Leave half for networking and other stuff } @@ -1352,6 +1370,7 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error if err != nil || index < 0 { return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account) } + log.Warn("-------------------------------------------------------------------") log.Warn("Referring to accounts by order in the keystore folder is dangerous!") log.Warn("This functionality is deprecated and will be removed in the future!") @@ -1362,6 +1381,7 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error if len(accs) <= index { return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs)) } + return accs[index], nil } @@ -1398,11 +1418,13 @@ func MakePasswordList(ctx *cli.Context) []string { if err != nil { Fatalf("Failed to read password file: %v", err) } + lines := strings.Split(string(text), "\n") // Sanitise DOS line endings. for i := range lines { lines[i] = strings.TrimRight(lines[i], "\r") } + return lines } @@ -1431,17 +1453,21 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { if lightServer { cfg.MaxPeers += lightPeers } + if lightClient && ctx.IsSet(LightMaxPeersFlag.Name) && cfg.MaxPeers < lightPeers { cfg.MaxPeers = lightPeers } } + if !(lightClient || lightServer) { lightPeers = 0 } + ethPeers := cfg.MaxPeers - lightPeers if lightClient { ethPeers = 0 } + log.Info("Maximum peer count", "ETH", ethPeers, "LES", lightPeers, "total", cfg.MaxPeers) if ctx.IsSet(MaxPendingPeersFlag.Name) { @@ -1468,6 +1494,7 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { if err != nil { Fatalf("Option %q: %v", NetrestrictFlag.Name, err) } + cfg.NetRestrict = list } @@ -1736,10 +1763,12 @@ func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) { if len(parts) != 2 { Fatalf("Invalid required block entry: %s", entry) } + number, err := strconv.ParseUint(parts[0], 0, 64) if err != nil { Fatalf("Invalid required block number %s: %v", parts[0], err) } + var hash common.Hash if err = hash.UnmarshalText([]byte(parts[1])); err != nil { Fatalf("Invalid required block hash %s: %v", parts[1], err) @@ -1754,6 +1783,7 @@ func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) { // specialize it further. func CheckExclusive(ctx *cli.Context, args ...interface{}) { set := make([]string, 0, 1) + for i := 0; i < len(args); i++ { // Make sure the next argument is a flag and skip if not set flag, ok := args[i].(cli.Flag) @@ -1773,6 +1803,7 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { } // shift arguments and continue i++ + continue case cli.Flag: @@ -1785,6 +1816,7 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { set = append(set, "--"+name) } } + if len(set) > 1 { Fatalf("Flags %v can't be used at the same time", strings.Join(set, ", ")) } @@ -1796,6 +1828,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { CheckExclusive(ctx, MainnetFlag, DeveloperFlag, GoerliFlag, SepoliaFlag) CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer + if ctx.String(GCModeFlag.Name) == "archive" && ctx.Uint64(TxLookupLimitFlag.Name) != 0 { ctx.Set(TxLookupLimitFlag.Name, "0") log.Warn("Disable transaction unindexing for archive node") @@ -1820,6 +1853,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024) mem.Total = 2 * 1024 * 1024 * 1024 } + allowance := int(mem.Total / 1024 / 1024 / 3) if cache := ctx.Int(CacheFlag.Name); cache > allowance { @@ -1868,6 +1902,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if cfg.NoPruning && !cfg.Preimages { cfg.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") } @@ -1921,6 +1956,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(RPCGlobalGasCapFlag.Name) { cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name) } + if cfg.RPCGasCap != 0 { log.Info("Set global gas cap", "cap", cfg.RPCGasCap) } else { @@ -1952,24 +1988,28 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1 } + cfg.Genesis = core.DefaultGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) case ctx.Bool(SepoliaFlag.Name): if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 11155111 } + cfg.Genesis = core.DefaultSepoliaGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.SepoliaGenesisHash) case ctx.Bool(GoerliFlag.Name): if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 5 } + cfg.Genesis = core.DefaultGoerliGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) case ctx.Bool(DeveloperFlag.Name): if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1337 } + cfg.SyncMode = downloader.FullSync // Create new developer account or reuse existing one var ( @@ -1977,6 +2017,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { passphrase string err error ) + if list := MakePasswordList(ctx); len(list) > 0 { // Just take the first value. Although the function returns a possible multiple values and // some usages iterate through them as attempts, that doesn't make sense in this setting, @@ -2013,6 +2054,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if err := ks.Unlock(developer, passphrase); err != nil { Fatalf("Failed to unlock developer account: %v", err) } + log.Info("Using developer account", "address", developer.Address) // Create a new developer genesis block or reuse existing one @@ -2031,6 +2073,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) { cfg.Genesis = nil // fallback to db content } + chaindb.Close() } @@ -2050,10 +2093,12 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) { 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.EthDiscoveryURLs = []string{url} cfg.SnapDiscoveryURLs = cfg.EthDiscoveryURLs @@ -2069,17 +2114,21 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } + stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) if err := lescatalyst.Register(stack, backend); err != nil { Fatalf("Failed to register the Engine API service: %v", err) } + return backend.ApiBackend, nil } + 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 { @@ -2090,7 +2139,9 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend if err := ethcatalyst.Register(stack, backend); err != nil { Fatalf("Failed to register the Engine API service: %v", err) } + stack.RegisterAPIs(tracers.APIs(backend.APIBackend)) + return backend.APIBackend, backend } @@ -2142,6 +2193,7 @@ func RegisterFullSyncTester(stack *node.Node, eth *eth.Ethereum, path string) { if err := rlp.DecodeBytes(rlpBlob, &block); err != nil { Fatalf("Failed to decode block: %v", err) } + ethcatalyst.RegisterFullSyncTester(stack, eth, &block) log.Info("Registered full-sync tester", "number", block.NumberU64(), "hash", block.Hash()) } @@ -2249,9 +2301,11 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb. default: chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.String(AncientFlag.Name), "", readonly) } + if err != nil { Fatalf("Could not open database: %v", err) } + return chainDb } @@ -2299,6 +2353,7 @@ func DialRPCWithHeaders(endpoint string, headers []string) (*rpc.Client, error) func MakeGenesis(ctx *cli.Context) *core.Genesis { var genesis *core.Genesis + switch { case ctx.Bool(MainnetFlag.Name): genesis = core.DefaultGenesisBlock() @@ -2309,6 +2364,7 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { case ctx.Bool(DeveloperFlag.Name): Fatalf("Developer chains are ephemeral") } + return genesis } @@ -2350,6 +2406,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh if gcmode := ctx.String(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" { Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name) } + cache := &core.CacheConfig{ TrieCleanLimit: ethconfig.Defaults.TrieCleanCache, TrieCleanNoPrefetch: ctx.Bool(CacheNoPrefetchFlag.Name), @@ -2361,6 +2418,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh } if cache.TrieDirtyDisabled && !cache.Preimages { cache.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") } @@ -2387,6 +2445,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh if err != nil { Fatalf("Can't create BlockChain: %v", err) } + return chain, chainDb } @@ -2403,5 +2462,6 @@ func MakeConsolePreloads(ctx *cli.Context) []string { for _, file := range strings.Split(ctx.String(PreloadJSFlag.Name), ",") { preloads = append(preloads, strings.TrimSpace(file)) } + return preloads } diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go index 1b49423569..d8b50565b3 100644 --- a/cmd/utils/flags_legacy.go +++ b/cmd/utils/flags_legacy.go @@ -51,9 +51,11 @@ func showDeprecated(*cli.Context) error { 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()) } + fmt.Println() return nil diff --git a/cmd/utils/prompt.go b/cmd/utils/prompt.go index f513e38188..bc14d38fde 100644 --- a/cmd/utils/prompt.go +++ b/cmd/utils/prompt.go @@ -30,19 +30,23 @@ func GetPassPhrase(text string, confirmation bool) string { if text != "" { fmt.Println(text) } + password, err := prompt.Stdin.PromptPassword("Password: ") if err != nil { Fatalf("Failed to read password: %v", err) } + if confirmation { confirm, err := prompt.Stdin.PromptPassword("Repeat password: ") if err != nil { Fatalf("Failed to read password confirmation: %v", err) } + if password != confirm { Fatalf("Passwords do not match") } } + return password } @@ -54,9 +58,11 @@ func GetPassPhraseWithList(text string, confirmation bool, index int, passwords if index < len(passwords) { return passwords[index] } + return passwords[len(passwords)-1] } // Otherwise prompt the user for the password password := GetPassPhrase(text, confirmation) + return password } diff --git a/cmd/utils/prompt_test.go b/cmd/utils/prompt_test.go index 86ee8b6525..20e3dc4ff6 100644 --- a/cmd/utils/prompt_test.go +++ b/cmd/utils/prompt_test.go @@ -28,6 +28,7 @@ func TestGetPassPhraseWithList(t *testing.T) { index int passwords []string } + tests := []struct { name string args args diff --git a/common/bitutil/bitutil.go b/common/bitutil/bitutil.go index cd3e72169f..dfb0a666f6 100644 --- a/common/bitutil/bitutil.go +++ b/common/bitutil/bitutil.go @@ -21,6 +21,7 @@ func XORBytes(dst, a, b []byte) int { if supportsUnaligned { return fastXORBytes(dst, a, b) } + return safeXORBytes(dst, a, b) } @@ -31,18 +32,22 @@ func fastXORBytes(dst, a, b []byte) int { if len(b) < n { n = len(b) } + w := n / wordSize if w > 0 { dw := *(*[]uintptr)(unsafe.Pointer(&dst)) aw := *(*[]uintptr)(unsafe.Pointer(&a)) bw := *(*[]uintptr)(unsafe.Pointer(&b)) + for i := 0; i < w; i++ { dw[i] = aw[i] ^ bw[i] } } + for i := n - n%wordSize; i < n; i++ { dst[i] = a[i] ^ b[i] } + return n } @@ -53,9 +58,11 @@ func safeXORBytes(dst, a, b []byte) int { if len(b) < n { n = len(b) } + for i := 0; i < n; i++ { dst[i] = a[i] ^ b[i] } + return n } @@ -65,6 +72,7 @@ func ANDBytes(dst, a, b []byte) int { if supportsUnaligned { return fastANDBytes(dst, a, b) } + return safeANDBytes(dst, a, b) } @@ -75,18 +83,22 @@ func fastANDBytes(dst, a, b []byte) int { if len(b) < n { n = len(b) } + w := n / wordSize if w > 0 { dw := *(*[]uintptr)(unsafe.Pointer(&dst)) aw := *(*[]uintptr)(unsafe.Pointer(&a)) bw := *(*[]uintptr)(unsafe.Pointer(&b)) + for i := 0; i < w; i++ { dw[i] = aw[i] & bw[i] } } + for i := n - n%wordSize; i < n; i++ { dst[i] = a[i] & b[i] } + return n } @@ -97,9 +109,11 @@ func safeANDBytes(dst, a, b []byte) int { if len(b) < n { n = len(b) } + for i := 0; i < n; i++ { dst[i] = a[i] & b[i] } + return n } @@ -109,6 +123,7 @@ func ORBytes(dst, a, b []byte) int { if supportsUnaligned { return fastORBytes(dst, a, b) } + return safeORBytes(dst, a, b) } @@ -119,18 +134,22 @@ func fastORBytes(dst, a, b []byte) int { if len(b) < n { n = len(b) } + w := n / wordSize if w > 0 { dw := *(*[]uintptr)(unsafe.Pointer(&dst)) aw := *(*[]uintptr)(unsafe.Pointer(&a)) bw := *(*[]uintptr)(unsafe.Pointer(&b)) + for i := 0; i < w; i++ { dw[i] = aw[i] | bw[i] } } + for i := n - n%wordSize; i < n; i++ { dst[i] = a[i] | b[i] } + return n } @@ -141,9 +160,11 @@ func safeORBytes(dst, a, b []byte) int { if len(b) < n { n = len(b) } + for i := 0; i < n; i++ { dst[i] = a[i] | b[i] } + return n } @@ -152,6 +173,7 @@ func TestBytes(p []byte) bool { if supportsUnaligned { return fastTestBytes(p) } + return safeTestBytes(p) } @@ -159,6 +181,7 @@ func TestBytes(p []byte) bool { // support unaligned read/writes. func fastTestBytes(p []byte) bool { n := len(p) + w := n / wordSize if w > 0 { pw := *(*[]uintptr)(unsafe.Pointer(&p)) @@ -168,11 +191,13 @@ func fastTestBytes(p []byte) bool { } } } + for i := n - n%wordSize; i < n; i++ { if p[i] != 0 { return true } } + return false } @@ -184,5 +209,6 @@ func safeTestBytes(p []byte) bool { return true } } + return false } diff --git a/common/bitutil/bitutil_test.go b/common/bitutil/bitutil_test.go index 307bf731f7..4c31f412e8 100644 --- a/common/bitutil/bitutil_test.go +++ b/common/bitutil/bitutil_test.go @@ -22,14 +22,17 @@ func TestXOR(t *testing.T) { for i := 0; i < len(p); i++ { p[i] = byte(i) } + for i := 0; i < len(q); i++ { q[i] = byte(len(q) - i) } + d1 := make([]byte, 1023+alignD)[alignD:] d2 := make([]byte, 1023+alignD)[alignD:] XORBytes(d1, p, q) safeXORBytes(d2, p, q) + if !bytes.Equal(d1, d2) { t.Error("not equal", d1, d2) } @@ -49,14 +52,17 @@ func TestAND(t *testing.T) { for i := 0; i < len(p); i++ { p[i] = byte(i) } + for i := 0; i < len(q); i++ { q[i] = byte(len(q) - i) } + d1 := make([]byte, 1023+alignD)[alignD:] d2 := make([]byte, 1023+alignD)[alignD:] ANDBytes(d1, p, q) safeANDBytes(d2, p, q) + if !bytes.Equal(d1, d2) { t.Error("not equal") } @@ -76,14 +82,17 @@ func TestOR(t *testing.T) { for i := 0; i < len(p); i++ { p[i] = byte(i) } + for i := 0; i < len(q); i++ { q[i] = byte(len(q) - i) } + d1 := make([]byte, 1023+alignD)[alignD:] d2 := make([]byte, 1023+alignD)[alignD:] ORBytes(d1, p, q) safeORBytes(d2, p, q) + if !bytes.Equal(d1, d2) { t.Error("not equal") } @@ -200,9 +209,11 @@ func BenchmarkFastTest4KB(b *testing.B) { benchmarkFastTest(b, 4096) } func benchmarkFastTest(b *testing.B, size int) { p := make([]byte, size) a := false + for i := 0; i < b.N; i++ { a = a != TestBytes(p) } + GloBool = a // Use of benchmark "result" to prevent total dead code elimination. } @@ -214,8 +225,10 @@ func BenchmarkBaseTest4KB(b *testing.B) { benchmarkBaseTest(b, 4096) } func benchmarkBaseTest(b *testing.B, size int) { p := make([]byte, size) a := false + for i := 0; i < b.N; i++ { a = a != safeTestBytes(p) } + GloBool = a // Use of benchmark "result" to prevent total dead code elimination. } diff --git a/common/bitutil/compress.go b/common/bitutil/compress.go index c057cee4a6..197b7f4b1d 100644 --- a/common/bitutil/compress.go +++ b/common/bitutil/compress.go @@ -61,8 +61,10 @@ func CompressBytes(data []byte) []byte { if out := bitsetEncodeBytes(data); len(out) < len(data) { return out } + cpy := make([]byte, len(data)) copy(cpy, data) + return cpy } @@ -78,6 +80,7 @@ func bitsetEncodeBytes(data []byte) []byte { if data[0] == 0 { return nil } + return data } // Calculate the bitset of set bytes, and gather the non-zero bytes @@ -90,9 +93,11 @@ func bitsetEncodeBytes(data []byte) []byte { nonZeroBitset[i/8] |= 1 << byte(7-i%8) } } + if len(nonZeroBytes) == 0 { return nil } + return append(bitsetEncodeBytes(nonZeroBitset), nonZeroBytes...) } @@ -103,11 +108,14 @@ func DecompressBytes(data []byte, target int) ([]byte, error) { if len(data) > target { return nil, errExceededTarget } + if len(data) == target { cpy := make([]byte, len(data)) copy(cpy, data) + return cpy, nil } + return bitsetDecodeBytes(data, target) } @@ -117,9 +125,11 @@ func bitsetDecodeBytes(data []byte, target int) ([]byte, error) { if err != nil { return nil, err } + if size != len(data) { return nil, errUnreferencedData } + return out, nil } @@ -137,11 +147,13 @@ func bitsetDecodePartialBytes(data []byte, target int) ([]byte, int, error) { if len(data) == 0 { return decomp, 0, nil } + if target == 1 { decomp[0] = data[0] // copy to avoid referencing the input slice if data[0] != 0 { return decomp, 1, nil } + return decomp, 0, nil } // Decompress the bitset of set bytes and distribute the non zero bytes @@ -149,12 +161,14 @@ func bitsetDecodePartialBytes(data []byte, target int) ([]byte, int, error) { if err != nil { return nil, ptr, err } + for i := 0; i < 8*len(nonZeroBitset); i++ { if nonZeroBitset[i/8]&(1<= len(data) { return nil, 0, errMissingData } + if i >= len(decomp) { return nil, 0, errExceededTarget } @@ -162,9 +176,11 @@ func bitsetDecodePartialBytes(data []byte, target int) ([]byte, int, error) { if data[ptr] == 0 { return nil, 0, errZeroContent } + decomp[i] = data[ptr] ptr++ } } + return decomp, ptr, nil } diff --git a/common/bitutil/compress_test.go b/common/bitutil/compress_test.go index 13a13011dc..4030b12619 100644 --- a/common/bitutil/compress_test.go +++ b/common/bitutil/compress_test.go @@ -55,6 +55,7 @@ func TestEncodingCycle(t *testing.T) { t.Errorf("test %d: failed to decompress compressed data: %v", i, err) continue } + if !bytes.Equal(data, proc) { t.Errorf("test %d: compress/decompress mismatch: have %x, want %x", i, proc, data) } @@ -105,9 +106,11 @@ func TestDecodingCycle(t *testing.T) { if err != tt.fail { t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.fail) } + if err != nil { continue } + if comp := bitsetEncodeBytes(orig); !bytes.Equal(comp, data) { t.Errorf("test %d: decompress/compress mismatch: have %x, want %x", i, comp, data) } @@ -124,6 +127,7 @@ func TestCompression(t *testing.T) { if data := CompressBytes(in); !bytes.Equal(data, out) { t.Errorf("encoding mismatch for sparse data: have %x, want %x", data, out) } + if data, err := DecompressBytes(out, len(in)); err != nil || !bytes.Equal(data, in) { t.Errorf("decoding mismatch for sparse data: have %x, want %x, error %v", data, in, err) } @@ -134,6 +138,7 @@ func TestCompression(t *testing.T) { if data := CompressBytes(in); !bytes.Equal(data, out) { t.Errorf("encoding mismatch for dense data: have %x, want %x", data, out) } + if data, err := DecompressBytes(out, len(in)); err != nil || !bytes.Equal(data, in) { t.Errorf("decoding mismatch for dense data: have %x, want %x, error %v", data, in, err) } @@ -175,6 +180,7 @@ func benchmarkEncoding(b *testing.B, bytes int, fill float64) { // Reset the benchmark and measure encoding/decoding b.ResetTimer() b.ReportAllocs() + for i := 0; i < b.N; i++ { bitsetDecodeBytes(bitsetEncodeBytes(data), len(data)) } diff --git a/common/bytes.go b/common/bytes.go index 9ae94f5cef..c3f7db20ba 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -30,9 +30,11 @@ func FromHex(s string) []byte { if has0xPrefix(s) { s = s[2:] } + if len(s)%2 == 1 { s = "0" + s } + return Hex2Bytes(s) } @@ -41,6 +43,7 @@ func CopyBytes(b []byte) (copiedBytes []byte) { if b == nil { return nil } + copiedBytes = make([]byte, len(b)) copy(copiedBytes, b) @@ -62,11 +65,13 @@ func isHex(str string) bool { if len(str)%2 != 0 { return false } + for _, c := range []byte(str) { if !isHexCharacter(c) { return false } } + return true } @@ -87,11 +92,14 @@ func Hex2BytesFixed(str string, flen int) []byte { if len(h) == flen { return h } + if len(h) > flen { return h[len(h)-flen:] } + hh := make([]byte, flen) copy(hh[flen-len(h):flen], h) + return hh } @@ -137,6 +145,7 @@ func TrimLeftZeroes(s []byte) []byte { break } } + return s[idx:] } @@ -148,5 +157,6 @@ func TrimRightZeroes(s []byte) []byte { break } } + return s[:idx] } diff --git a/common/bytes_test.go b/common/bytes_test.go index 0e3ec974ee..f99ddc3d7a 100644 --- a/common/bytes_test.go +++ b/common/bytes_test.go @@ -28,6 +28,7 @@ func TestCopyBytes(t *testing.T) { if !bytes.Equal(v, []byte{1, 2, 3, 4}) { t.Fatal("not equal after copy") } + v[0] = 99 if bytes.Equal(v, input) { t.Fatal("result is not a copy") @@ -41,6 +42,7 @@ func TestLeftPadBytes(t *testing.T) { if r := LeftPadBytes(val, 8); !bytes.Equal(r, padded) { t.Fatalf("LeftPadBytes(%v, 8) == %v", val, r) } + if r := LeftPadBytes(val, 2); !bytes.Equal(r, val) { t.Fatalf("LeftPadBytes(%v, 2) == %v", val, r) } @@ -53,6 +55,7 @@ func TestRightPadBytes(t *testing.T) { if r := RightPadBytes(val, 8); !bytes.Equal(r, padded) { t.Fatalf("RightPadBytes(%v, 8) == %v", val, r) } + if r := RightPadBytes(val, 2); !bytes.Equal(r, val) { t.Fatalf("RightPadBytes(%v, 2) == %v", val, r) } @@ -62,6 +65,7 @@ func TestFromHex(t *testing.T) { input := "0x01" expected := []byte{1} result := FromHex(input) + if !bytes.Equal(expected, result) { t.Errorf("Expected %x got %x", expected, result) } @@ -92,6 +96,7 @@ func TestFromHexOddLength(t *testing.T) { input := "0x1" expected := []byte{1} result := FromHex(input) + if !bytes.Equal(expected, result) { t.Errorf("Expected %x got %x", expected, result) } @@ -101,6 +106,7 @@ func TestNoPrefixShortHexOddLength(t *testing.T) { input := "1" expected := []byte{1} result := FromHex(input) + if !bytes.Equal(expected, result) { t.Errorf("Expected %x got %x", expected, result) } diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go index 232163ed5f..da451d0801 100644 --- a/common/compiler/solidity.go +++ b/common/compiler/solidity.go @@ -64,6 +64,7 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin } // Compilation succeeded, assemble and return the contracts. contracts := make(map[string]*Contract) + for name, info := range output.Contracts { // Parse the individual compilation results. var abi, userdoc, devdoc interface{} @@ -98,6 +99,7 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin }, } } + return contracts, nil } @@ -130,5 +132,6 @@ func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion str }, } } + return contracts, nil } diff --git a/common/fdlimit/fdlimit_darwin.go b/common/fdlimit/fdlimit_darwin.go index 6b26fa00f1..e00a15bd07 100644 --- a/common/fdlimit/fdlimit_darwin.go +++ b/common/fdlimit/fdlimit_darwin.go @@ -35,6 +35,7 @@ func Raise(max uint64) (uint64, error) { if limit.Cur > max { limit.Cur = max } + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { return 0, err } @@ -42,6 +43,7 @@ func Raise(max uint64) (uint64, error) { if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { return 0, err } + return limit.Cur, nil } @@ -52,6 +54,7 @@ func Current() (int, error) { if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { return 0, err } + return int(limit.Cur), nil } @@ -67,5 +70,6 @@ func Maximum() (int, error) { if limit.Max > hardlimit { limit.Max = hardlimit } + return int(limit.Max), nil } diff --git a/common/fdlimit/fdlimit_test.go b/common/fdlimit/fdlimit_test.go index 9fd5e9fc3c..31bf70dcd5 100644 --- a/common/fdlimit/fdlimit_test.go +++ b/common/fdlimit/fdlimit_test.go @@ -24,10 +24,12 @@ import ( // per this process can be retrieved. func TestFileDescriptorLimits(t *testing.T) { target := 4096 + hardlimit, err := Maximum() if err != nil { t.Fatal(err) } + if hardlimit < target { t.Skipf("system limit is less than desired test target: %d < %d", hardlimit, target) } @@ -35,9 +37,11 @@ func TestFileDescriptorLimits(t *testing.T) { if limit, err := Current(); err != nil || limit <= 0 { t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err) } + if _, err := Raise(uint64(target)); err != nil { t.Fatalf("failed to raise file allowance") } + if limit, err := Current(); err != nil || limit < target { t.Fatalf("failed to retrieve raised descriptor limit (have %v, want %v): %v", limit, target, err) } diff --git a/common/format.go b/common/format.go index 7af41f52d5..4253869130 100644 --- a/common/format.go +++ b/common/format.go @@ -36,6 +36,7 @@ func (d PrettyDuration) String() string { if match := prettyDurationRe.FindString(label); len(match) > 4 { label = strings.Replace(label, match, match[:4], 1) } + return label } @@ -78,5 +79,6 @@ func (t PrettyAge) String() string { } } } + return result } diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go index d3201850a8..12a4aa76a0 100644 --- a/common/hexutil/hexutil.go +++ b/common/hexutil/hexutil.go @@ -61,13 +61,16 @@ func Decode(input string) ([]byte, error) { if len(input) == 0 { return nil, ErrEmptyString } + if !has0xPrefix(input) { return nil, ErrMissingPrefix } + b, err := hex.DecodeString(input[2:]) if err != nil { err = mapError(err) } + return b, err } @@ -77,6 +80,7 @@ func MustDecode(input string) []byte { if err != nil { panic(err) } + return dec } @@ -85,6 +89,7 @@ func Encode(b []byte) string { enc := make([]byte, len(b)*2+2) copy(enc, "0x") hex.Encode(enc[2:], b) + return string(enc) } @@ -94,10 +99,12 @@ func DecodeUint64(input string) (uint64, error) { if err != nil { return 0, err } + dec, err := strconv.ParseUint(raw, 16, 64) if err != nil { err = mapError(err) } + return dec, err } @@ -108,6 +115,7 @@ func MustDecodeUint64(input string) uint64 { if err != nil { panic(err) } + return dec } @@ -115,6 +123,7 @@ func MustDecodeUint64(input string) uint64 { func EncodeUint64(i uint64) string { enc := make([]byte, 2, 10) copy(enc, "0x") + return string(strconv.AppendUint(enc, i, 16)) } @@ -141,27 +150,35 @@ func DecodeBig(input string) (*big.Int, error) { if err != nil { return nil, err } + if len(raw) > 64 { return nil, ErrBig256Range } + words := make([]big.Word, len(raw)/bigWordNibbles+1) end := len(raw) + for i := range words { start := end - bigWordNibbles if start < 0 { start = 0 } + for ri := start; ri < end; ri++ { nib := decodeNibble(raw[ri]) if nib == badNibble { return nil, ErrSyntax } + words[i] *= 16 words[i] += big.Word(nib) } + end = start } + dec := new(big.Int).SetBits(words) + return dec, nil } @@ -172,6 +189,7 @@ func MustDecodeBig(input string) *big.Int { if err != nil { panic(err) } + return dec } @@ -194,16 +212,20 @@ func checkNumber(input string) (raw string, err error) { if len(input) == 0 { return "", ErrEmptyString } + if !has0xPrefix(input) { return "", ErrMissingPrefix } + input = input[2:] if len(input) == 0 { return "", ErrEmptyNumber } + if len(input) > 1 && input[0] == '0' { return "", ErrLeadingZero } + return input, nil } @@ -231,11 +253,14 @@ func mapError(err error) error { return ErrSyntax } } + if _, ok := err.(hex.InvalidByteError); ok { return ErrSyntax } + if err == hex.ErrLength { return ErrOddLength } + return err } diff --git a/common/hexutil/hexutil_test.go b/common/hexutil/hexutil_test.go index f2b800d82c..f617fa7827 100644 --- a/common/hexutil/hexutil_test.go +++ b/common/hexutil/hexutil_test.go @@ -151,6 +151,7 @@ func TestDecode(t *testing.T) { if !checkError(t, test.input, err, test.wantErr) { continue } + if !bytes.Equal(test.want.([]byte), dec) { t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) continue @@ -173,6 +174,7 @@ func TestDecodeBig(t *testing.T) { if !checkError(t, test.input, err, test.wantErr) { continue } + if dec.Cmp(test.want.(*big.Int)) != 0 { t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) continue @@ -195,6 +197,7 @@ func TestDecodeUint64(t *testing.T) { if !checkError(t, test.input, err, test.wantErr) { continue } + if dec != test.want.(uint64) { t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) continue @@ -206,6 +209,7 @@ func BenchmarkEncodeBig(b *testing.B) { for _, bench := range encodeBigTests { b.Run(bench.want, func(b *testing.B) { b.ReportAllocs() + bigint := bench.input.(*big.Int) for i := 0; i < b.N; i++ { EncodeBig(bigint) diff --git a/common/hexutil/json.go b/common/hexutil/json.go index 50db208118..e70a928fac 100644 --- a/common/hexutil/json.go +++ b/common/hexutil/json.go @@ -41,6 +41,7 @@ func (b Bytes) MarshalText() ([]byte, error) { result := make([]byte, len(b)*2+2) copy(result, `0x`) hex.Encode(result[2:], b) + return result, nil } @@ -49,6 +50,7 @@ func (b *Bytes) UnmarshalJSON(input []byte) error { if !isString(input) { return errNonString(bytesT) } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bytesT) } @@ -58,12 +60,14 @@ func (b *Bytes) UnmarshalText(input []byte) error { if err != nil { return err } + dec := make([]byte, len(raw)/2) if _, err = hex.Decode(dec, raw); err != nil { err = mapError(err) } else { *b = dec } + return err } @@ -84,10 +88,12 @@ func (b *Bytes) UnmarshalGraphQL(input interface{}) error { if err != nil { return err } + *b = data default: err = fmt.Errorf("unexpected type %T for Bytes", input) } + return err } @@ -98,6 +104,7 @@ func UnmarshalFixedJSON(typ reflect.Type, input, out []byte) error { if !isString(input) { return errNonString(typ) } + return wrapTypeError(UnmarshalFixedText(typ.String(), input[1:len(input)-1], out), typ) } @@ -109,6 +116,7 @@ func UnmarshalFixedText(typname string, input, out []byte) error { if err != nil { return err } + if len(raw)/2 != len(out) { return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname) } @@ -118,7 +126,9 @@ func UnmarshalFixedText(typname string, input, out []byte) error { return ErrSyntax } } + hex.Decode(out, raw) + return nil } @@ -130,6 +140,7 @@ func UnmarshalFixedUnprefixedText(typname string, input, out []byte) error { if err != nil { return err } + if len(raw)/2 != len(out) { return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname) } @@ -139,7 +150,9 @@ func UnmarshalFixedUnprefixedText(typname string, input, out []byte) error { return ErrSyntax } } + hex.Decode(out, raw) + return nil } @@ -161,6 +174,7 @@ func (b *Big) UnmarshalJSON(input []byte) error { if !isString(input) { return errNonString(bigT) } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bigT) } @@ -170,29 +184,38 @@ func (b *Big) UnmarshalText(input []byte) error { if err != nil { return err } + if len(raw) > 64 { return ErrBig256Range } + words := make([]big.Word, len(raw)/bigWordNibbles+1) end := len(raw) + for i := range words { start := end - bigWordNibbles if start < 0 { start = 0 } + for ri := start; ri < end; ri++ { nib := decodeNibble(raw[ri]) if nib == badNibble { return ErrSyntax } + words[i] *= 16 words[i] += big.Word(nib) } + end = start } + var dec big.Int + dec.SetBits(words) *b = (Big)(dec) + return nil } @@ -212,16 +235,19 @@ func (b Big) ImplementsGraphQLType(name string) bool { return name == "BigInt" } // UnmarshalGraphQL unmarshals the provided GraphQL query data. func (b *Big) UnmarshalGraphQL(input interface{}) error { var err error + switch input := input.(type) { case string: return b.UnmarshalText([]byte(input)) case int32: var num big.Int + num.SetInt64(int64(input)) *b = Big(num) default: err = fmt.Errorf("unexpected type %T for BigInt", input) } + return err } @@ -234,6 +260,7 @@ func (b Uint64) MarshalText() ([]byte, error) { buf := make([]byte, 2, 10) copy(buf, `0x`) buf = strconv.AppendUint(buf, uint64(b), 16) + return buf, nil } @@ -242,6 +269,7 @@ func (b *Uint64) UnmarshalJSON(input []byte) error { if !isString(input) { return errNonString(uint64T) } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint64T) } @@ -251,19 +279,25 @@ func (b *Uint64) UnmarshalText(input []byte) error { if err != nil { return err } + if len(raw) > 16 { return ErrUint64Range } + var dec uint64 + for _, byte := range raw { nib := decodeNibble(byte) if nib == badNibble { return ErrSyntax } + dec *= 16 dec += nib } + *b = Uint64(dec) + return nil } @@ -278,6 +312,7 @@ func (b Uint64) ImplementsGraphQLType(name string) bool { return name == "Long" // UnmarshalGraphQL unmarshals the provided GraphQL query data. func (b *Uint64) UnmarshalGraphQL(input interface{}) error { var err error + switch input := input.(type) { case string: return b.UnmarshalText([]byte(input)) @@ -286,6 +321,7 @@ func (b *Uint64) UnmarshalGraphQL(input interface{}) error { default: err = fmt.Errorf("unexpected type %T for Long", input) } + return err } @@ -303,19 +339,23 @@ func (b *Uint) UnmarshalJSON(input []byte) error { if !isString(input) { return errNonString(uintT) } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uintT) } // UnmarshalText implements encoding.TextUnmarshaler. func (b *Uint) UnmarshalText(input []byte) error { var u64 Uint64 + err := u64.UnmarshalText(input) if u64 > Uint64(^uint(0)) || err == ErrUint64Range { return ErrUintRange } else if err != nil { return err } + *b = Uint(u64) + return nil } @@ -336,14 +376,17 @@ func checkText(input []byte, wantPrefix bool) ([]byte, error) { if len(input) == 0 { return nil, nil // empty strings are allowed } + if bytesHave0xPrefix(input) { input = input[2:] } else if wantPrefix { return nil, ErrMissingPrefix } + if len(input)%2 != 0 { return nil, ErrOddLength } + return input, nil } @@ -351,16 +394,20 @@ func checkNumberText(input []byte) (raw []byte, err error) { if len(input) == 0 { return nil, nil // empty strings are allowed } + if !bytesHave0xPrefix(input) { return nil, ErrMissingPrefix } + input = input[2:] if len(input) == 0 { return nil, ErrEmptyNumber } + if len(input) > 1 && input[0] == '0' { return nil, ErrLeadingZero } + return input, nil } @@ -368,6 +415,7 @@ func wrapTypeError(err error, typ reflect.Type) error { if _, ok := err.(*decError); ok { return &json.UnmarshalTypeError{Value: err.Error(), Type: typ} } + return err } diff --git a/common/hexutil/json_example_test.go b/common/hexutil/json_example_test.go index 80180d9186..f4ccb0773a 100644 --- a/common/hexutil/json_example_test.go +++ b/common/hexutil/json_example_test.go @@ -35,6 +35,7 @@ func (v MyType) String() string { func ExampleUnmarshalFixedText() { var v1, v2 MyType + fmt.Println("v1 error:", json.Unmarshal([]byte(`"0x01"`), &v1)) fmt.Println("v2 error:", json.Unmarshal([]byte(`"0x0101010101"`), &v2)) fmt.Println("v2:", v2) diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index ed7d6fad1a..64d17b156d 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -31,13 +31,16 @@ func checkError(t *testing.T, input string, got, want error) bool { t.Errorf("input %s: got no error, want %q", input, want) return false } + return true } + if want == nil { t.Errorf("input %s: unexpected error %q", input, got) } else if got.Error() != want.Error() { t.Errorf("input %s: got error %q, want %q", input, got, want) } + return false } @@ -46,6 +49,7 @@ func referenceBig(s string) *big.Int { if !ok { panic("invalid") } + return b } @@ -54,6 +58,7 @@ func referenceBytes(s string) []byte { if err != nil { panic(err) } + return b } @@ -84,10 +89,12 @@ var unmarshalBytesTests = []unmarshalTest{ func TestUnmarshalBytes(t *testing.T) { for _, test := range unmarshalBytesTests { var v Bytes + err := json.Unmarshal([]byte(test.input), &v) if !checkError(t, test.input, err, test.wantErr) { continue } + if !bytes.Equal(test.want.([]byte), v) { t.Errorf("input %s: value mismatch: got %x, want %x", test.input, &v, test.want) continue @@ -97,6 +104,7 @@ func TestUnmarshalBytes(t *testing.T) { func BenchmarkUnmarshalBytes(b *testing.B) { input := []byte(`"0x123456789abcdef123456789abcdef"`) + for i := 0; i < b.N; i++ { var v Bytes if err := v.UnmarshalJSON(input); err != nil { @@ -109,14 +117,17 @@ func TestMarshalBytes(t *testing.T) { for _, test := range encodeBytesTests { in := test.input.([]byte) out, err := json.Marshal(Bytes(in)) + if err != nil { t.Errorf("%x: %v", in, err) continue } + if want := `"` + test.want + `"`; string(out) != want { t.Errorf("%x: MarshalJSON output mismatch: got %q, want %q", in, out, want) continue } + if out := Bytes(in).String(); out != test.want { t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) continue @@ -165,10 +176,12 @@ var unmarshalBigTests = []unmarshalTest{ func TestUnmarshalBig(t *testing.T) { for _, test := range unmarshalBigTests { var v Big + err := json.Unmarshal([]byte(test.input), &v) if !checkError(t, test.input, err, test.wantErr) { continue } + if test.want != nil && test.want.(*big.Int).Cmp((*big.Int)(&v)) != 0 { t.Errorf("input %s: value mismatch: got %x, want %x", test.input, (*big.Int)(&v), test.want) continue @@ -178,6 +191,7 @@ func TestUnmarshalBig(t *testing.T) { func BenchmarkUnmarshalBig(b *testing.B) { input := []byte(`"0x123456789abcdef123456789abcdef"`) + for i := 0; i < b.N; i++ { var v Big if err := v.UnmarshalJSON(input); err != nil { @@ -190,14 +204,17 @@ func TestMarshalBig(t *testing.T) { for _, test := range encodeBigTests { in := test.input.(*big.Int) out, err := json.Marshal((*Big)(in)) + if err != nil { t.Errorf("%d: %v", in, err) continue } + if want := `"` + test.want + `"`; string(out) != want { t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) continue } + if out := (*Big)(in).String(); out != test.want { t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) continue @@ -231,10 +248,12 @@ var unmarshalUint64Tests = []unmarshalTest{ func TestUnmarshalUint64(t *testing.T) { for _, test := range unmarshalUint64Tests { var v Uint64 + err := json.Unmarshal([]byte(test.input), &v) if !checkError(t, test.input, err, test.wantErr) { continue } + if uint64(v) != test.want.(uint64) { t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) continue @@ -244,8 +263,10 @@ func TestUnmarshalUint64(t *testing.T) { func BenchmarkUnmarshalUint64(b *testing.B) { input := []byte(`"0x123456789abcdf"`) + for i := 0; i < b.N; i++ { var v Uint64 + v.UnmarshalJSON(input) } } @@ -254,14 +275,17 @@ func TestMarshalUint64(t *testing.T) { for _, test := range encodeUint64Tests { in := test.input.(uint64) out, err := json.Marshal(Uint64(in)) + if err != nil { t.Errorf("%d: %v", in, err) continue } + if want := `"` + test.want + `"`; string(out) != want { t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) continue } + if out := (Uint64)(in).String(); out != test.want { t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) continue @@ -273,14 +297,17 @@ func TestMarshalUint(t *testing.T) { for _, test := range encodeUintTests { in := test.input.(uint) out, err := json.Marshal(Uint(in)) + if err != nil { t.Errorf("%d: %v", in, err) continue } + if want := `"` + test.want + `"`; string(out) != want { t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) continue } + if out := (Uint)(in).String(); out != test.want { t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) continue @@ -323,14 +350,17 @@ var unmarshalUintTests = []unmarshalTest{ func TestUnmarshalUint(t *testing.T) { for _, test := range unmarshalUintTests { var v Uint + err := json.Unmarshal([]byte(test.input), &v) if uintBits == 32 && test.wantErr32bit != nil { checkError(t, test.input, err, test.wantErr32bit) continue } + if !checkError(t, test.input, err, test.wantErr) { continue } + if uint(v) != test.want.(uint) { t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) continue @@ -359,6 +389,7 @@ func TestUnmarshalFixedUnprefixedText(t *testing.T) { for _, test := range tests { out := make([]byte, 4) err := UnmarshalFixedUnprefixedText("x", []byte(test.input), out) + switch { case err == nil && test.wantErr != nil: t.Errorf("%q: got no error, expected %q", test.input, test.wantErr) @@ -367,6 +398,7 @@ func TestUnmarshalFixedUnprefixedText(t *testing.T) { case err != nil && err.Error() != test.wantErr.Error(): t.Errorf("%q: error mismatch: got %q, want %q", test.input, err, test.wantErr) } + if test.want != nil && !bytes.Equal(out, test.want) { t.Errorf("%q: output mismatch: got %x, want %x", test.input, out, test.want) } diff --git a/common/lru/basiclru_test.go b/common/lru/basiclru_test.go index a092123ba4..5c2fd34b72 100644 --- a/common/lru/basiclru_test.go +++ b/common/lru/basiclru_test.go @@ -229,14 +229,17 @@ func BenchmarkLRU(b *testing.B) { }) b.Run("Get/BasicLRU", func(b *testing.B) { cache := NewBasicLRU[string, []byte](capacity) + for i := 0; i < capacity; i++ { index := indexes[i] cache.Add(keys[index], values[index]) } b.ResetTimer() + for i := 0; i < b.N; i++ { k := keys[indexes[i%len(indexes)]] + v, ok := cache.Get(k) if ok { sink = v diff --git a/common/lru/blob_lru_test.go b/common/lru/blob_lru_test.go index 30ca368206..4188d25302 100644 --- a/common/lru/blob_lru_test.go +++ b/common/lru/blob_lru_test.go @@ -108,6 +108,7 @@ func TestSizeConstrainedCacheOverflow(t *testing.T) { k := mkKey(i) v := fmt.Sprintf("value-%04d", i) lru.Add(k, []byte(v)) + if have, want := lru.size, uint64(10); have != want { t.Fatalf("size wrong, have %d want %d", have, want) } diff --git a/common/math/big.go b/common/math/big.go index d814eed2ec..e267c27d4d 100644 --- a/common/math/big.go +++ b/common/math/big.go @@ -48,6 +48,7 @@ type HexOrDecimal256 big.Int func NewHexOrDecimal256(x int64) *HexOrDecimal256 { b := big.NewInt(x) h := HexOrDecimal256(*b) + return &h } @@ -69,7 +70,9 @@ func (i *HexOrDecimal256) UnmarshalText(input []byte) error { if !ok { return fmt.Errorf("invalid hex or decimal integer %q", input) } + *i = HexOrDecimal256(*bigint) + return nil } @@ -78,6 +81,7 @@ func (i *HexOrDecimal256) MarshalText() ([]byte, error) { if i == nil { return []byte("0x0"), nil } + return []byte(fmt.Sprintf("%#x", (*big.Int)(i))), nil } @@ -89,6 +93,7 @@ type Decimal256 big.Int func NewDecimal256(x int64) *Decimal256 { b := big.NewInt(x) d := Decimal256(*b) + return &d } @@ -98,7 +103,9 @@ func (i *Decimal256) UnmarshalText(input []byte) error { if !ok { return fmt.Errorf("invalid hex or decimal integer %q", input) } + *i = Decimal256(*bigint) + return nil } @@ -112,6 +119,7 @@ func (i *Decimal256) String() string { if i == nil { return "0" } + return fmt.Sprintf("%#d", (*big.Int)(i)) } @@ -121,16 +129,20 @@ func ParseBig256(s string) (*big.Int, bool) { if s == "" { return new(big.Int), true } + var bigint *big.Int + var ok bool if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { bigint, ok = new(big.Int).SetString(s[2:], 16) } else { bigint, ok = new(big.Int).SetString(s, 10) } + if ok && bigint.BitLen() > 256 { bigint, ok = nil, false } + return bigint, ok } @@ -140,6 +152,7 @@ func MustParseBig256(s string) *big.Int { if !ok { panic("invalid 256 bit integer: " + s) } + return v } @@ -191,6 +204,7 @@ func FirstBitSet(v *big.Int) int { return i } } + return v.BitLen() } @@ -200,8 +214,10 @@ func PaddedBigBytes(bigint *big.Int, n int) []byte { if bigint.BitLen()/8 >= n { return bigint.Bytes() } + ret := make([]byte, n) ReadBits(bigint, ret) + return ret } @@ -215,6 +231,7 @@ func bigEndianByteAt(bigint *big.Int, n int) byte { if i >= len(words) { return byte(0) } + word := words[i] // Offset of the byte shift := 8 * uint(n%wordBytes) @@ -230,6 +247,7 @@ func Byte(bigint *big.Int, padlength, n int) byte { if n >= padlength { return byte(0) } + return bigEndianByteAt(bigint, padlength-1-n) } @@ -268,6 +286,7 @@ func S256(x *big.Int) *big.Int { if x.Cmp(tt255) < 0 { return x } + return new(big.Int).Sub(x, tt256) } @@ -284,9 +303,12 @@ func Exp(base, exponent *big.Int) *big.Int { if word&1 == 1 { U256(result.Mul(result, base)) } + U256(base.Mul(base, base)) + word >>= 1 } } + return result } diff --git a/common/math/big_test.go b/common/math/big_test.go index 803b5e1cc6..daf9ee6e68 100644 --- a/common/math/big_test.go +++ b/common/math/big_test.go @@ -50,11 +50,13 @@ func TestHexOrDecimal256(t *testing.T) { } for _, test := range tests { var num HexOrDecimal256 + err := num.UnmarshalText([]byte(test.input)) if (err == nil) != test.ok { t.Errorf("ParseBig(%q) -> (err == nil) == %t, want %t", test.input, err == nil, test.ok) continue } + if test.num != nil && (*big.Int)(&num).Cmp(test.num) != 0 { t.Errorf("ParseBig(%q) -> %d, want %d", test.input, (*big.Int)(&num), test.num) } @@ -183,6 +185,7 @@ func TestReadBits(t *testing.T) { int, _ := new(big.Int).SetString(input, 16) buf := make([]byte, len(want)) ReadBits(int, buf) + if !bytes.Equal(buf, want) { t.Errorf("have: %x\nwant: %x", buf, want) } @@ -239,6 +242,7 @@ func TestBigEndianByteAt(t *testing.T) { } for _, test := range tests { v := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) + actual := bigEndianByteAt(v, test.y) if actual != test.exp { t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.x, test.y, test.exp, actual) @@ -271,6 +275,7 @@ func TestLittleEndianByteAt(t *testing.T) { } for _, test := range tests { v := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) + actual := Byte(v, 32, test.y) if actual != test.exp { t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.x, test.y, test.exp, actual) diff --git a/common/math/integer.go b/common/math/integer.go index bdb19af146..7b2f7ebc97 100644 --- a/common/math/integer.go +++ b/common/math/integer.go @@ -59,7 +59,9 @@ func (i *HexOrDecimal64) UnmarshalText(input []byte) error { if !ok { return fmt.Errorf("invalid hex or decimal integer %q", input) } + *i = HexOrDecimal64(int) + return nil } @@ -74,11 +76,14 @@ func ParseUint64(s string) (uint64, bool) { if s == "" { return 0, true } + if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { v, err := strconv.ParseUint(s[2:], 16, 64) return v, err == nil } + v, err := strconv.ParseUint(s, 10, 64) + return v, err == nil } @@ -88,6 +93,7 @@ func MustParseUint64(s string) uint64 { if !ok { panic("invalid unsigned 64 bit integer: " + s) } + return v } diff --git a/common/math/integer_test.go b/common/math/integer_test.go index b31c7c26c2..dbf832b91e 100644 --- a/common/math/integer_test.go +++ b/common/math/integer_test.go @@ -50,6 +50,7 @@ func TestOverflow(t *testing.T) { {MaxUint64, 1, false, mul}, } { var overflows bool + switch test.op { case sub: _, overflows = SafeSub(test.x, test.y) @@ -89,11 +90,13 @@ func TestHexOrDecimal64(t *testing.T) { } for _, test := range tests { var num HexOrDecimal64 + err := num.UnmarshalText([]byte(test.input)) if (err == nil) != test.ok { t.Errorf("ParseUint64(%q) -> (err == nil) = %t, want %t", test.input, err == nil, test.ok) continue } + if err == nil && uint64(num) != test.num { t.Errorf("ParseUint64(%q) -> %d, want %d", test.input, num, test.num) } diff --git a/common/mclock/mclock.go b/common/mclock/mclock.go index c05738cf2b..2a945c7863 100644 --- a/common/mclock/mclock.go +++ b/common/mclock/mclock.go @@ -98,13 +98,16 @@ func (c System) NewTimer(d time.Duration) ChanTimer { default: } }) + return &systemTimer{t, ch} } // After returns a channel which receives the current time after d has elapsed. func (c System) After(d time.Duration) <-chan AbsTime { ch := make(chan AbsTime, 1) + time.AfterFunc(d, func() { ch <- c.Now() }) + return ch } diff --git a/common/mclock/simclock.go b/common/mclock/simclock.go index f5ad3f8bc0..4b78f3c925 100644 --- a/common/mclock/simclock.go +++ b/common/mclock/simclock.go @@ -59,11 +59,14 @@ func (s *Simulated) Run(d time.Duration) { s.init() end := s.now.Add(d) + var do []func() + for len(s.scheduled) > 0 && s.scheduled[0].at <= end { ev := heap.Pop(&s.scheduled).(*simTimer) do = append(do, ev.do) } + s.now = end s.mu.Unlock() @@ -110,9 +113,11 @@ func (s *Simulated) NewTimer(d time.Duration) ChanTimer { defer s.mu.Unlock() ch := make(chan AbsTime, 1) + var timer *simTimer timer = s.schedule(d, func() { ch <- timer.at }) timer.ch = ch + return timer } @@ -138,6 +143,7 @@ func (s *Simulated) schedule(d time.Duration, fn func()) *simTimer { ev := &simTimer{do: fn, at: at, s: s} heap.Push(&s.scheduled, ev) s.cond.Broadcast() + return ev } @@ -148,9 +154,11 @@ func (ev *simTimer) Stop() bool { if ev.index < 0 { return false } + heap.Remove(&ev.s.scheduled, ev.index) ev.s.cond.Broadcast() ev.index = -1 + return true } @@ -161,12 +169,14 @@ func (ev *simTimer) Reset(d time.Duration) { ev.s.mu.Lock() defer ev.s.mu.Unlock() + ev.at = ev.s.now.Add(d) if ev.index < 0 { heap.Push(&ev.s.scheduled, ev) // already expired } else { heap.Fix(&ev.s.scheduled, ev.index) // hasn't fired yet, reschedule } + ev.s.cond.Broadcast() } @@ -174,6 +184,7 @@ func (ev *simTimer) C() <-chan AbsTime { if ev.ch == nil { panic("mclock: C() on timer created by AfterFunc") } + return ev.ch } @@ -205,5 +216,6 @@ func (h *simTimerHeap) Pop() interface{} { t.index = -1 (*h)[end] = nil *h = (*h)[:end] + return t } diff --git a/common/mclock/simclock_test.go b/common/mclock/simclock_test.go index 582bc31dcd..7f5ddceb5c 100644 --- a/common/mclock/simclock_test.go +++ b/common/mclock/simclock_test.go @@ -31,10 +31,12 @@ func TestSimulatedAfter(t *testing.T) { adv = 11 * time.Minute c Simulated ) + c.Run(offset) end := c.Now().Add(timeout) ch := c.After(timeout) + for c.Now() < end.Add(-adv) { c.Run(adv) select { @@ -61,18 +63,23 @@ func TestSimulatedAfterFunc(t *testing.T) { called1 := false timer1 := c.AfterFunc(100*time.Millisecond, func() { called1 = true }) + if c.ActiveTimers() != 1 { t.Fatalf("%d active timers, want one", c.ActiveTimers()) } + if fired := timer1.Stop(); !fired { t.Fatal("Stop returned false even though timer didn't fire") } + if c.ActiveTimers() != 0 { t.Fatalf("%d active timers, want zero", c.ActiveTimers()) } + if called1 { t.Fatal("timer 1 called") } + if fired := timer1.Stop(); fired { t.Fatal("Stop returned true after timer was already stopped") } @@ -80,13 +87,17 @@ func TestSimulatedAfterFunc(t *testing.T) { called2 := false timer2 := c.AfterFunc(100*time.Millisecond, func() { called2 = true }) c.Run(50 * time.Millisecond) + if called2 { t.Fatal("timer 2 called") } + c.Run(51 * time.Millisecond) + if !called2 { t.Fatal("timer 2 not called") } + if fired := timer2.Stop(); fired { t.Fatal("Stop returned true after timer has fired") } @@ -98,6 +109,7 @@ func TestSimulatedSleep(t *testing.T) { timeout = 1 * time.Hour done = make(chan AbsTime, 1) ) + go func() { c.Sleep(timeout) done <- c.Now() @@ -121,6 +133,7 @@ func TestSimulatedTimerReset(t *testing.T) { c Simulated timeout = 1 * time.Hour ) + timer := c.NewTimer(timeout) c.Run(2 * timeout) select { @@ -149,8 +162,10 @@ func TestSimulatedTimerStop(t *testing.T) { c Simulated timeout = 1 * time.Hour ) + timer := c.NewTimer(timeout) c.Run(2 * timeout) + if timer.Stop() { t.Errorf("Stop returned true for fired timer") } diff --git a/common/path.go b/common/path.go index 8ee646d310..565c6ececc 100644 --- a/common/path.go +++ b/common/path.go @@ -37,6 +37,7 @@ func AbsolutePath(datadir string, filename string) string { if filepath.IsAbs(filename) { return filename } + return filepath.Join(datadir, filename) } diff --git a/common/prque/lazyqueue.go b/common/prque/lazyqueue.go index 51ab3c1bfd..1047b51e8e 100644 --- a/common/prque/lazyqueue.go +++ b/common/prque/lazyqueue.go @@ -69,6 +69,7 @@ func NewLazyQueue[P constraints.Ordered, V any](setIndex SetIndexCallback[V], pr } q.Reset() q.refresh(clock.Now()) + return q } @@ -94,6 +95,7 @@ func (q *LazyQueue[P, V]) refresh(now mclock.AbsTime) { for q.queue[0].Len() != 0 { q.Push(heap.Pop(q.queue[0]).(*item[P, V]).value) } + q.queue[0], q.queue[1] = q.queue[1], q.queue[0] q.indexOffset = 1 - q.indexOffset q.maxUntil = q.maxUntil.Add(q.period) @@ -115,11 +117,14 @@ func (q *LazyQueue[P, V]) Pop() (V, P) { resData V resPri P ) + q.MultiPop(func(data V, priority P) bool { resData = data resPri = priority + return false }) + return resData, resPri } @@ -130,11 +135,14 @@ func (q *LazyQueue[P, V]) peekIndex() int { if q.queue[1].Len() != 0 && q.queue[1].blocks[0][0].priority > q.queue[0].blocks[0][0].priority { return 1 } + return 0 } + if q.queue[1].Len() != 0 { return 1 } + return -1 } @@ -146,6 +154,7 @@ func (q *LazyQueue[P, V]) MultiPop(callback func(data V, priority P) bool) { for nextIndex != -1 { data := heap.Pop(q.queue[nextIndex]).(*item[P, V]).value heap.Push(q.popQueue, &item[P, V]{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[P, V]) @@ -153,8 +162,10 @@ func (q *LazyQueue[P, V]) MultiPop(callback func(data V, priority P) bool) { for q.popQueue.Len() != 0 { q.Push(heap.Pop(q.popQueue).(*item[P, V]).value) } + return } + nextIndex = q.peekIndex() // re-check because callback is allowed to push items back } } diff --git a/common/prque/lazyqueue_test.go b/common/prque/lazyqueue_test.go index ffb7e5e9e3..c7a514a993 100644 --- a/common/prque/lazyqueue_test.go +++ b/common/prque/lazyqueue_test.go @@ -48,6 +48,7 @@ func testMaxPriority(a interface{}, until mclock.AbsTime) int64 { i := a.(*lazyItem) dt := until - i.last i.maxp = i.p + int64(float64(dt)*testAvgRate) + return i.maxp } @@ -69,6 +70,7 @@ func TestLazyQueue(t *testing.T) { if items[i].p > maxPri { maxPri = items[i].p } + items[i].index = -1 q.Push(&items[i]) } @@ -78,10 +80,13 @@ func TestLazyQueue(t *testing.T) { wg sync.WaitGroup stopCh = make(chan chan struct{}) ) + defer wg.Wait() wg.Add(1) + go func() { defer wg.Done() + for { select { case <-clock.After(testQueueRefresh): @@ -96,15 +101,19 @@ func TestLazyQueue(t *testing.T) { for c := 0; c < testSteps; c++ { i := rand.Intn(testItems) + lock.Lock() + items[i].p += rand.Int63n(testPriorityStep*2-1) + 1 if items[i].p > maxPri { maxPri = items[i].p } + items[i].last = clock.Now() if items[i].p > items[i].maxp { q.Update(items[i].index) } + if rand.Intn(100) == 0 { p := q.PopItem().(*lazyItem) if p.p != maxPri { @@ -112,6 +121,7 @@ func TestLazyQueue(t *testing.T) { close(stopCh) t.Fatalf("incorrect item (best known priority %d, popped %d)", maxPri, p.p) } + q.Push(p) } lock.Unlock() diff --git a/common/prque/prque_test.go b/common/prque/prque_test.go index c4910f205a..d3e72d2927 100644 --- a/common/prque/prque_test.go +++ b/common/prque/prque_test.go @@ -17,16 +17,19 @@ 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[int, int](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], prio[i]) + if queue.Size() != i+1 { t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) } @@ -39,15 +42,19 @@ func TestPrque(t *testing.T) { // Pop out the elements in priority order and verify them prevPrio := 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) } } @@ -57,16 +64,19 @@ 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[int, int](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], prio[i]) + if queue.Size() != i+1 { t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) } @@ -78,19 +88,24 @@ func TestReset(t *testing.T) { } // Pop out half the elements in priority order and verify them prevPrio := 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) } @@ -101,12 +116,14 @@ 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[int64, int](nil) for i := 0; i < len(data); i++ { queue.Push(data[i], prio[i]) @@ -117,16 +134,19 @@ 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[int64, int](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.go b/common/prque/sstack.go index 5dcd1d9dd0..5f5df03cbe 100755 --- a/common/prque/sstack.go +++ b/common/prque/sstack.go @@ -46,6 +46,7 @@ func newSstack[P constraints.Ordered, V any](setIndex SetIndexCallback[V]) *ssta result.active = make([]*item[P, V], blockSize) result.blocks = [][]*item[P, V]{result.active} result.capacity = blockSize + return result } @@ -61,9 +62,11 @@ func (s *sstack[P, V]) Push(data any) { s.active = s.blocks[s.size/blockSize] s.offset = 0 } + if s.setIndex != nil { s.setIndex(data.(*item[P, V]).value, s.size) } + s.active[s.offset] = data.(*item[P, V]) s.offset++ s.size++ @@ -73,15 +76,18 @@ func (s *sstack[P, V]) Push(data any) { // Required by heap.Interface. func (s *sstack[P, V]) Pop() (res any) { s.size-- + s.offset-- if s.offset < 0 { s.offset = blockSize - 1 s.active = s.blocks[s.size/blockSize] } + res, s.active[s.offset] = s.active[s.offset], nil if s.setIndex != nil { s.setIndex(res.(*item[P, V]).value, -1) } + return } @@ -99,11 +105,13 @@ func (s *sstack[P, V]) Less(i, j int) bool { // Swaps two elements in the stack. Required by sort.Interface. func (s *sstack[P, V]) Swap(i, j int) { ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize + a, b := s.blocks[jb][jo], s.blocks[ib][io] if s.setIndex != nil { s.setIndex(a.value, i) s.setIndex(b.value, j) } + s.blocks[ib][io], s.blocks[jb][jo] = a, b } diff --git a/common/prque/sstack_test.go b/common/prque/sstack_test.go index edc99955e8..e050655a6b 100644 --- a/common/prque/sstack_test.go +++ b/common/prque/sstack_test.go @@ -18,19 +18,25 @@ func TestSstack(t *testing.T) { // Create some initial data size := 16 * blockSize data := make([]*item[int64, int], size) + for i := 0; i < size; i++ { data[i] = &item[int64, int]{rand.Int(), rand.Int63()} } + stack := newSstack[int64, int](nil) + for rep := 0; rep < 2; rep++ { // Push all the data into the stack, pop out every second secs := []*item[int64, int]{} + for i := 0; i < size; i++ { stack.Push(data[i]) + if i%2 == 0 { secs = append(secs, stack.Pop().(*item[int64, int])) } } + rest := []*item[int64, int]{} for stack.Len() > 0 { rest = append(rest, stack.Pop().(*item[int64, int])) @@ -40,6 +46,7 @@ func TestSstack(t *testing.T) { 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]) } @@ -51,6 +58,7 @@ func TestSstackSort(t *testing.T) { // Create some initial data size := 16 * blockSize data := make([]*item[int64, int], size) + for i := 0; i < size; i++ { data[i] = &item[int64, int]{rand.Int(), int64(i)} } @@ -61,6 +69,7 @@ func TestSstackSort(t *testing.T) { } // Sort and pop the stack contents (should reverse the order) sort.Sort(stack) + for _, val := range data { out := stack.Pop() if out != val { @@ -73,24 +82,31 @@ func TestSstackReset(t *testing.T) { // Create some initial data size := 16 * blockSize data := make([]*item[int64, int], size) + for i := 0; i < size; i++ { data[i] = &item[int64, int]{rand.Int(), rand.Int63()} } + stack := newSstack[int64, int](nil) + for rep := 0; rep < 2; rep++ { // Push all the data into the stack, pop out every second secs := []*item[int64, int]{} + for i := 0; i < size; i++ { stack.Push(data[i]) + if i%2 == 0 { secs = append(secs, stack.Pop().(*item[int64, int])) } } // 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]) diff --git a/common/test_utils.go b/common/test_utils.go index 7a175412f4..59ad98971f 100644 --- a/common/test_utils.go +++ b/common/test_utils.go @@ -28,26 +28,32 @@ func LoadJSON(file string, val interface{}) error { if err != nil { return err } + if err := json.Unmarshal(content, val); err != nil { if syntaxerr, ok := err.(*json.SyntaxError); ok { line := findLine(content, syntaxerr.Offset) return fmt.Errorf("JSON syntax error at %v:%v: %v", file, line, err) } + return fmt.Errorf("JSON unmarshal error in %v: %v", file, err) } + return nil } // findLine returns the line number for the given offset into data. func findLine(data []byte, offset int64) (line int) { line = 1 + for i, r := range string(data) { if int64(i) >= offset { return } + if r == '\n' { line++ } } + return } diff --git a/common/types.go b/common/types.go index 0b68a19dd3..c0b903aca3 100644 --- a/common/types.go +++ b/common/types.go @@ -52,7 +52,9 @@ type Hash [HashLength]byte // If b is larger than len(h), b will be cropped from the left. func BytesToHash(b []byte) Hash { var h Hash + h.SetBytes(b) + return h } @@ -97,9 +99,11 @@ func (h Hash) Format(s fmt.State, c rune) { if !s.Flag('#') { hexb = hexb[2:] } + if c == 'X' { hexb = bytes.ToUpper(hexb) } + fallthrough case 'v', 's': s.Write(hexb) @@ -146,6 +150,7 @@ func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { for i := len(h) - 1; i > m; i-- { h[i] = byte(rand.Uint32()) } + return reflect.ValueOf(h) } @@ -155,10 +160,13 @@ func (h *Hash) Scan(src interface{}) error { if !ok { return fmt.Errorf("can't scan %T into Hash", src) } + if len(srcB) != HashLength { return fmt.Errorf("can't scan []byte of len %d into Hash, want %d", len(srcB), HashLength) } + copy(h[:], srcB) + return nil } @@ -179,6 +187,7 @@ func (h *Hash) UnmarshalGraphQL(input interface{}) error { default: err = fmt.Errorf("unexpected type %T for Hash", input) } + return err } @@ -204,7 +213,9 @@ type Address [AddressLength]byte // If b is larger than len(h), b will be cropped from the left. func BytesToAddress(b []byte) Address { var a Address + a.SetBytes(b) + return a } @@ -222,6 +233,7 @@ func IsHexAddress(s string) bool { if has0xPrefix(s) { s = s[2:] } + return len(s) == 2*AddressLength && isHex(s) } @@ -250,6 +262,7 @@ func (a *Address) checksumHex() []byte { // compute checksum sha := sha3.NewLegacyKeccak256() sha.Write(buf[2:]) + hash := sha.Sum(nil) for i := 2; i < len(buf); i++ { hashByte := hash[(i-2)/2] @@ -258,17 +271,21 @@ func (a *Address) checksumHex() []byte { } else { hashByte &= 0xf } + if buf[i] > '9' && hashByte > 7 { buf[i] -= 32 } } + return buf[:] } func (a Address) hex() []byte { var buf [len(a)*2 + 2]byte + copy(buf[:2], "0x") hex.Encode(buf[2:], a[:]) + return buf[:] } @@ -289,9 +306,11 @@ func (a Address) Format(s fmt.State, c rune) { 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)) @@ -306,6 +325,7 @@ func (a *Address) SetBytes(b []byte) { if len(b) > len(a) { b = b[len(b)-AddressLength:] } + copy(a[AddressLength-len(b):], b) } @@ -330,10 +350,13 @@ func (a *Address) Scan(src interface{}) error { if !ok { return fmt.Errorf("can't scan %T into Address", src) } + if len(srcB) != AddressLength { return fmt.Errorf("can't scan []byte of len %d into Address, want %d", len(srcB), AddressLength) } + copy(a[:], srcB) + return nil } @@ -354,6 +377,7 @@ func (a *Address) UnmarshalGraphQL(input interface{}) error { default: err = fmt.Errorf("unexpected type %T for Address", input) } + return err } @@ -387,7 +411,9 @@ func NewMixedcaseAddressFromString(hexaddr string) (*MixedcaseAddress, error) { if !IsHexAddress(hexaddr) { return nil, errors.New("invalid address") } + a := FromHex(hexaddr) + return &MixedcaseAddress{addr: BytesToAddress(a), original: hexaddr}, nil } @@ -396,6 +422,7 @@ func (ma *MixedcaseAddress) UnmarshalJSON(input []byte) error { if err := hexutil.UnmarshalFixedJSON(addressT, input, ma.addr[:]); err != nil { return err } + return json.Unmarshal(input, &ma.original) } @@ -404,6 +431,7 @@ func (ma MixedcaseAddress) MarshalJSON() ([]byte, error) { if strings.HasPrefix(ma.original, "0x") || strings.HasPrefix(ma.original, "0X") { return json.Marshal(fmt.Sprintf("0x%s", ma.original[2:])) } + return json.Marshal(fmt.Sprintf("0x%s", ma.original)) } @@ -417,6 +445,7 @@ func (ma *MixedcaseAddress) String() string { if ma.ValidChecksum() { return fmt.Sprintf("%s [chksum ok]", ma.original) } + return fmt.Sprintf("%s [chksum INVALID]", ma.original) } diff --git a/common/types_test.go b/common/types_test.go index 1e29f1998d..76a70b7e4a 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -76,9 +76,12 @@ func TestHashJsonValidation(t *testing.T) { {"0x", 64, ""}, {"0X", 64, ""}, } + for _, test := range tests { input := `"` + test.Prefix + strings.Repeat("0", test.Size) + `"` + var v Hash + err := json.Unmarshal([]byte(input), &v) if err == nil { if test.Error != "" { @@ -106,16 +109,20 @@ func TestAddressUnmarshalJSON(t *testing.T) { {`"0x0000000000000000000000000000000000000000"`, false, big.NewInt(0)}, {`"0x0000000000000000000000000000000000000010"`, false, big.NewInt(16)}, } + for i, test := range tests { var v Address + err := json.Unmarshal([]byte(test.Input), &v) if err != nil && !test.ShouldErr { t.Errorf("test #%d: unexpected error: %v", i, err) } + if err == nil { if test.ShouldErr { t.Errorf("test #%d: expected error, got none", i) } + if got := new(big.Int).SetBytes(v.Bytes()); got.Cmp(test.Output) != 0 { t.Errorf("test #%d: address mismatch: have %v, want %v", i, got, test.Output) } @@ -139,6 +146,7 @@ func TestAddressHexChecksum(t *testing.T) { {"0x00a", "0x000000000000000000000000000000000000000A"}, {"0x000000000000000000000000000000000000000a", "0x000000000000000000000000000000000000000A"}, } + for i, test := range tests { output := HexToAddress(test.Input).Hex() if output != test.Output { @@ -189,6 +197,7 @@ func TestMixedcaseAddressMarshal(t *testing.T) { func TestMixedcaseAccount_Address(t *testing.T) { t.Parallel() + var res []struct { A MixedcaseAddress Valid bool @@ -232,6 +241,7 @@ func TestHash_Scan(t *testing.T) { type args struct { src interface{} } + tests := []struct { name string args args @@ -290,8 +300,11 @@ func TestHash_Value(t *testing.T) { 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, 0x10, 0x00, } + var usedH Hash + usedH.SetBytes(b) + tests := []struct { name string h Hash @@ -312,6 +325,7 @@ func TestHash_Value(t *testing.T) { t.Errorf("Hash.Value() error = %v, wantErr %v", err, tt.wantErr) return } + if !reflect.DeepEqual(got, tt.want) { t.Errorf("Hash.Value() = %v, want %v", got, tt.want) } @@ -323,6 +337,7 @@ func TestAddress_Scan(t *testing.T) { type args struct { src interface{} } + tests := []struct { name string args args @@ -376,8 +391,11 @@ func TestAddress_Value(t *testing.T) { 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, } + var usedA Address + usedA.SetBytes(b) + tests := []struct { name string a Address @@ -398,6 +416,7 @@ func TestAddress_Value(t *testing.T) { t.Errorf("Address.Value() error = %v, wantErr %v", err, tt.wantErr) return } + if !reflect.DeepEqual(got, tt.want) { t.Errorf("Address.Value() = %v, want %v", got, tt.want) } @@ -410,7 +429,9 @@ func TestAddress_Format(t *testing.T) { 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 { @@ -486,6 +507,7 @@ func TestAddress_Format(t *testing.T) { 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, diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 97d0b3627a..dd09723800 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -66,6 +66,7 @@ func New(ethone consensus.Engine) *Beacon { if _, ok := ethone.(*Beacon); ok { panic("nested consensus engine") } + return &Beacon{ethone: ethone} } @@ -74,6 +75,7 @@ func (beacon *Beacon) Author(header *types.Header) (common.Address, error) { if !beacon.IsPoSHeader(header) { return beacon.ethone.Author(header) } + return header.Coinbase, nil } @@ -84,6 +86,7 @@ func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *ty if err != nil { return err } + if !reached { return beacon.ethone.VerifyHeader(chain, header, seal) } @@ -127,6 +130,7 @@ func (beacon *Beacon) splitHeaders(chain consensus.ChainHeaderReader, headers [] if ptd.Cmp(ttd) >= 0 { return nil, headers, nil } + var ( preHeaders = headers postHeaders []*types.Header @@ -138,6 +142,7 @@ func (beacon *Beacon) splitHeaders(chain consensus.ChainHeaderReader, headers [] if tdPassed { preHeaders = headers[:i] postHeaders = headers[i:] + break } @@ -166,6 +171,7 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ if len(postHeaders) == 0 { return beacon.ethone.VerifyHeaders(chain, headers, seals) } + if len(preHeaders) == 0 { return beacon.verifyHeaders(chain, headers, nil) } @@ -175,6 +181,7 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ abort = make(chan struct{}) results = make(chan error, len(headers)) ) + go func() { var ( old, new, out = 0, len(preHeaders), 0 @@ -187,6 +194,7 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ for { for ; done[out]; out++ { results <- errors[out] + if out == len(headers)-1 { return } @@ -196,6 +204,7 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ if !done[old] { // skip TTD-verified failures errors[old], done[old] = err, true } + old++ case err := <-newResult: errors[new], done[new] = err, true @@ -203,10 +212,12 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ case <-abort: close(oldDone) close(newDone) + return } } }() + return abort, results } @@ -220,6 +231,7 @@ func (beacon *Beacon) VerifyUncles(chain consensus.ChainReader, block *types.Blo if len(block.Uncles()) > 0 { return errTooManyUncles } + return nil } @@ -242,6 +254,7 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa if header.Nonce != beaconNonce { return errInvalidNonce } + if header.UncleHash != types.EmptyUncleHash { return errInvalidUncleHash } @@ -301,9 +314,11 @@ func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers [ abort = make(chan struct{}) results = make(chan error, len(headers)) ) + go func() { for i, header := range headers { var parent *types.Header + if i == 0 { if ancestor != nil { parent = ancestor @@ -313,14 +328,17 @@ func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers [ } else if headers[i-1].Hash() == headers[i].ParentHash { parent = headers[i-1] } + if parent == nil { select { case <-abort: return case results <- consensus.ErrUnknownAncestor: } + continue } + err := beacon.verifyHeader(chain, header, parent) select { case <-abort: @@ -329,6 +347,7 @@ func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers [ } } }() + return abort, results } @@ -340,10 +359,13 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H if err != nil { return err } + if !reached { return beacon.ethone.Prepare(chain, header) } + header.Difficulty = beaconDifficulty + return nil } @@ -420,6 +442,7 @@ func (beacon *Beacon) CalcDifficulty(chain consensus.ChainHeaderReader, time uin if reached, _ := IsTTDReached(chain, parent.Hash(), parent.Number.Uint64()); !reached { return beacon.ethone.CalcDifficulty(chain, time, parent) } + return beaconDifficulty } @@ -440,6 +463,7 @@ func (beacon *Beacon) IsPoSHeader(header *types.Header) bool { if header.Difficulty == nil { panic("IsPoSHeader called with invalid difficulty") } + return header.Difficulty.Cmp(beaconDifficulty) == 0 } @@ -454,6 +478,7 @@ func (beacon *Beacon) SetThreads(threads int) { type threaded interface { SetThreads(threads int) } + if th, ok := beacon.ethone.(threaded); ok { th.SetThreads(threads) } @@ -471,5 +496,6 @@ func IsTTDReached(chain consensus.ChainHeaderReader, parentHash common.Hash, par if td == nil { return false, consensus.ErrUnknownAncestor } + return td.Cmp(chain.Config().TerminalTotalDifficulty) >= 0, nil } diff --git a/consensus/bor/api/caller_mock.go b/consensus/bor/api/caller_mock.go index e734d6c899..fb8fa3b3c6 100644 --- a/consensus/bor/api/caller_mock.go +++ b/consensus/bor/api/caller_mock.go @@ -30,6 +30,7 @@ type MockCallerMockRecorder struct { func NewMockCaller(ctrl *gomock.Controller) *MockCaller { mock := &MockCaller{ctrl: ctrl} mock.recorder = &MockCallerMockRecorder{mock} + return mock } @@ -44,6 +45,7 @@ func (m *MockCaller) Call(arg0 context.Context, arg1 ethapi.TransactionArgs, arg ret := m.ctrl.Call(m, "Call", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(hexutil.Bytes) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -59,6 +61,7 @@ func (m *MockCaller) CallWithState(arg0 context.Context, arg1 ethapi.Transaction ret := m.ctrl.Call(m, "CallWithState", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(hexutil.Bytes) ret1, _ := ret[1].(error) + return ret0, ret1 } diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index aa35dc2fbc..93b3a0b093 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -513,7 +513,6 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainHeaderReader, header *t // nolint: gocognit func (c *Bor) snapshot(chain consensus.ChainHeaderReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) { // Search for a snapshot in memory or on disk for checkpoints - signer := common.BytesToAddress(c.authorizedSigner.Load().signer.Bytes()) if c.devFakeAuthor && signer.String() != "0x0000000000000000000000000000000000000000" { log.Info("👨‍💻Using DevFakeAuthor", "signer", signer) diff --git a/consensus/bor/genesis_contract_mock.go b/consensus/bor/genesis_contract_mock.go index 0296cbe905..7ba2427e2c 100644 --- a/consensus/bor/genesis_contract_mock.go +++ b/consensus/bor/genesis_contract_mock.go @@ -31,6 +31,7 @@ type MockGenesisContractMockRecorder struct { func NewMockGenesisContract(ctrl *gomock.Controller) *MockGenesisContract { mock := &MockGenesisContract{ctrl: ctrl} mock.recorder = &MockGenesisContractMockRecorder{mock} + return mock } @@ -45,6 +46,7 @@ func (m *MockGenesisContract) CommitState(arg0 *clerk.EventRecordWithTime, arg1 ret := m.ctrl.Call(m, "CommitState", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -60,6 +62,7 @@ func (m *MockGenesisContract) LastStateId(arg0 *state.StateDB, arg1 uint64, arg2 ret := m.ctrl.Call(m, "LastStateId", arg0, arg1, arg2) ret0, _ := ret[0].(*big.Int) ret1, _ := ret[1].(error) + return ret0, ret1 } diff --git a/consensus/bor/snapshot_test.go b/consensus/bor/snapshot_test.go index 68a9ba042f..d7c1a7f498 100644 --- a/consensus/bor/snapshot_test.go +++ b/consensus/bor/snapshot_test.go @@ -29,6 +29,7 @@ func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) { // proposer is signer signerTest := validatorSet.Proposer.Address + successionNumber, err := snap.GetSignerSuccessionNumber(signerTest) if err != nil { t.Fatalf("%s", err) @@ -55,6 +56,7 @@ func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) { // choose a signer at an index greater than proposer index signerTest := snap.ValidatorSet.Validators[signerIndex].Address + successionNumber, err := snap.GetSignerSuccessionNumber(signerTest) if err != nil { t.Fatalf("%s", err) @@ -77,6 +79,7 @@ func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) { // choose a signer at an index greater than proposer index signerTest := snap.ValidatorSet.Validators[signerIndex].Address + successionNumber, err := snap.GetSignerSuccessionNumber(signerTest) if err != nil { t.Fatalf("%s", err) diff --git a/consensus/bor/span_mock.go b/consensus/bor/span_mock.go index 099807161c..04935199c7 100644 --- a/consensus/bor/span_mock.go +++ b/consensus/bor/span_mock.go @@ -33,6 +33,7 @@ type MockSpannerMockRecorder struct { func NewMockSpanner(ctrl *gomock.Controller) *MockSpanner { mock := &MockSpanner{ctrl: ctrl} mock.recorder = &MockSpannerMockRecorder{mock} + return mock } @@ -46,6 +47,7 @@ func (m *MockSpanner) CommitSpan(arg0 context.Context, arg1 span.HeimdallSpan, a m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CommitSpan", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(error) + return ret0 } @@ -61,6 +63,7 @@ func (m *MockSpanner) GetCurrentSpan(arg0 context.Context, arg1 common.Hash) (*s ret := m.ctrl.Call(m, "GetCurrentSpan", arg0, arg1) ret0, _ := ret[0].(*span.Span) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -76,6 +79,7 @@ func (m *MockSpanner) GetCurrentValidatorsByBlockNrOrHash(arg0 context.Context, ret := m.ctrl.Call(m, "GetCurrentValidatorsByBlockNrOrHash", arg0, arg1, arg2) ret0, _ := ret[0].([]*valset.Validator) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -91,6 +95,7 @@ func (m *MockSpanner) GetCurrentValidatorsByHash(arg0 context.Context, arg1 comm ret := m.ctrl.Call(m, "GetCurrentValidatorsByHash", arg0, arg1, arg2) ret0, _ := ret[0].([]*valset.Validator) ret1, _ := ret[1].(error) + return ret0, ret1 } diff --git a/consensus/bor/valset/validator_set.go b/consensus/bor/valset/validator_set.go index 7ed9f4572e..da187ddf9d 100644 --- a/consensus/bor/valset/validator_set.go +++ b/consensus/bor/valset/validator_set.go @@ -499,6 +499,7 @@ func (vals *ValidatorSet) applyUpdates(updates []*Validator) { } else { // Apply add or update. merged[i] = updates[0] + if existing[0].Address == updates[0].Address { // Validator is present in both, advance existing. existing = existing[1:] @@ -506,6 +507,7 @@ func (vals *ValidatorSet) applyUpdates(updates []*Validator) { updates = updates[1:] } + i++ } diff --git a/consensus/clique/api.go b/consensus/clique/api.go index cb270d321d..259e11902e 100644 --- a/consensus/clique/api.go +++ b/consensus/clique/api.go @@ -48,6 +48,7 @@ func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) { if header == nil { return nil, errUnknownBlock } + return api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) } @@ -57,6 +58,7 @@ func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) { if header == nil { return nil, errUnknownBlock } + return api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) } @@ -73,10 +75,12 @@ func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) { if header == nil { return nil, errUnknownBlock } + snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) if err != nil { return nil, err } + return snap.signers(), nil } @@ -86,10 +90,12 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) { if header == nil { return nil, errUnknownBlock } + snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) if err != nil { return nil, err } + return snap.signers(), nil } @@ -102,6 +108,7 @@ func (api *API) Proposals() map[common.Address]bool { for address, auth := range api.clique.proposals { proposals[address] = auth } + return proposals } @@ -140,38 +147,48 @@ func (api *API) Status() (*status, error) { diff = uint64(0) optimals = 0 ) + snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) if err != nil { return nil, err } + var ( signers = snap.signers() end = header.Number.Uint64() start = end - numBlocks ) + if numBlocks > end { start = 1 numBlocks = end - start } + signStatus := make(map[common.Address]int) for _, s := range signers { signStatus[s] = 0 } + for n := start; n < end; n++ { h := api.chain.GetHeaderByNumber(n) if h == nil { return nil, fmt.Errorf("missing block %d", n) } + if h.Difficulty.Cmp(diffInTurn) == 0 { optimals++ } + diff += h.Difficulty.Uint64() + sealer, err := api.clique.Author(h) if err != nil { return nil, err } + signStatus[sealer]++ } + return &status{ InturnPercent: float64(100*optimals) / float64(numBlocks), SigningStatus: signStatus, @@ -196,11 +213,14 @@ func (sb *blockNumberOrHashOrRLP) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &input); err != nil { return err } + blob, err := hexutil.Decode(input) if err != nil { return err } + sb.RLP = blob + return nil } @@ -210,7 +230,9 @@ func (sb *blockNumberOrHashOrRLP) UnmarshalJSON(data []byte) error { func (api *API) GetSigner(rlpOrBlockNr *blockNumberOrHashOrRLP) (common.Address, error) { if len(rlpOrBlockNr.RLP) == 0 { blockNrOrHash := rlpOrBlockNr.BlockNumberOrHash + var header *types.Header + if blockNrOrHash == nil { header = api.chain.CurrentHeader() } else if hash, ok := blockNrOrHash.Hash(); ok { @@ -218,18 +240,23 @@ func (api *API) GetSigner(rlpOrBlockNr *blockNumberOrHashOrRLP) (common.Address, } else if number, ok := blockNrOrHash.Number(); ok { header = api.chain.GetHeaderByNumber(uint64(number.Int64())) } + if header == nil { return common.Address{}, fmt.Errorf("missing block %v", blockNrOrHash.String()) } + return api.clique.Author(header) } + block := new(types.Block) if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, block); err == nil { return api.clique.Author(block.Header()) } + header := new(types.Header) if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, header); err != nil { return common.Address{}, err } + return api.clique.Author(header) } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 8edf8632b5..175c5ff804 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -154,6 +154,7 @@ func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) { if len(header.Extra) < extraSeal { return common.Address{}, errMissingSignature } + signature := header.Extra[len(header.Extra)-extraSeal:] // Recover the public key and the Ethereum address @@ -161,10 +162,13 @@ func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) { if err != nil { return common.Address{}, err } + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) sigcache.Add(hash, signer) + return signer, nil } @@ -237,6 +241,7 @@ func (c *Clique) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*typ } } }() + return abort, results } @@ -248,6 +253,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if header.Number == nil { return errUnknownBlock } + number := header.Number.Uint64() // Don't waste time checking blocks from the future @@ -263,6 +269,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if !bytes.Equal(header.Nonce[:], nonceAuthVote) && !bytes.Equal(header.Nonce[:], nonceDropVote) { return errInvalidVote } + if checkpoint && !bytes.Equal(header.Nonce[:], nonceDropVote) { return errInvalidCheckpointVote } @@ -270,6 +277,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if len(header.Extra) < extraVanity { return errMissingVanity } + if len(header.Extra) < extraVanity+extraSeal { return errMissingSignature } @@ -278,6 +286,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if !checkpoint && signersBytes != 0 { return errExtraSigners } + if checkpoint && signersBytes%common.AddressLength != 0 { return errInvalidCheckpointSigners } @@ -303,6 +312,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if chain.Config().IsShanghai(header.Time) { return fmt.Errorf("clique does not support shanghai fork") } + if chain.Config().IsCancun(header.Time) { return fmt.Errorf("clique does not support cancun fork") } @@ -327,9 +337,11 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header } else { parent = chain.GetHeader(header.ParentHash, number-1) } + if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { return consensus.ErrUnknownAncestor } + if parent.Time+c.config.Period > header.Time { return errInvalidTimestamp } @@ -337,11 +349,13 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header if header.GasUsed > header.GasLimit { return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) } + if !chain.Config().IsLondon(header.Number) { // Verify BaseFee not present before EIP-1559 fork. if header.BaseFee != nil { return fmt.Errorf("invalid baseFee before fork: have %d, want ", header.BaseFee) } + if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { return err } @@ -360,6 +374,7 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header for i, signer := range snap.signers() { copy(signers[i*common.AddressLength:], signer[:]) } + extraSuffix := len(header.Extra) - extraSeal if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) { return errMismatchingCheckpointSigners @@ -376,6 +391,7 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash headers []*types.Header snap *Snapshot ) + for snap == nil { // If an in-memory snapshot was found, use that if s, ok := c.recents.Get(hash); ok { @@ -386,7 +402,9 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash if number%checkpointInterval == 0 { if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { log.Trace("Loaded voting snapshot from disk", "number", number, "hash", hash) + snap = s + break } } @@ -403,11 +421,14 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash for i := 0; i < len(signers); i++ { copy(signers[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:]) } + snap = newSnapshot(c.config, c.signatures, number, hash, signers) if err := snap.store(c.db); err != nil { return nil, err } + log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash) + break } } @@ -419,6 +440,7 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash if header.Hash() != hash || header.Number.Uint64() != number { return nil, consensus.ErrUnknownAncestor } + parents = parents[:len(parents)-1] } else { // No explicit parents (or no more left), reach out to the database @@ -427,6 +449,7 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash return nil, consensus.ErrUnknownAncestor } } + headers = append(headers, header) number, hash = number-1, header.ParentHash } @@ -434,10 +457,12 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash for i := 0; i < len(headers)/2; i++ { headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i] } + snap, err := snap.apply(headers) if err != nil { return nil, err } + c.recents.Add(snap.Hash, snap) // If we've generated a new checkpoint snapshot, save to disk @@ -445,8 +470,10 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash if err = snap.store(c.db); err != nil { return nil, err } + log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash) } + return snap, err } @@ -456,6 +483,7 @@ func (c *Clique) VerifyUncles(chain consensus.ChainReader, block *types.Block) e if len(block.Uncles()) > 0 { return errors.New("uncles not allowed") } + return nil } @@ -474,9 +502,11 @@ func (c *Clique) verifySeal(snap *Snapshot, header *types.Header, parents []*typ if err != nil { return err } + if _, ok := snap.Signers[signer]; !ok { return errUnauthorizedSigner } + for seen, recent := range snap.Recents { if recent == signer { // Signer is among recents, only fail if the current block doesn't shift it out @@ -491,10 +521,12 @@ func (c *Clique) verifySeal(snap *Snapshot, header *types.Header, parents []*typ if inturn && header.Difficulty.Cmp(diffInTurn) != 0 { return errWrongDifficulty } + if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 { return errWrongDifficulty } } + return nil } @@ -511,10 +543,12 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header if err != nil { return err } + c.lock.RLock() if number%c.config.Epoch != 0 { // Gather all the proposals that make sense voting on addresses := make([]common.Address, 0, len(c.proposals)) + for address, authorize := range c.proposals { if snap.validVote(address, authorize) { addresses = append(addresses, address) @@ -542,6 +576,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header if len(header.Extra) < extraVanity { header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) } + header.Extra = header.Extra[:extraVanity] if number%c.config.Epoch == 0 { @@ -549,6 +584,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header header.Extra = append(header.Extra, signer[:]...) } } + header.Extra = append(header.Extra, make([]byte, extraSeal)...) // Mix digest is reserved for now, set to empty @@ -559,10 +595,12 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header if parent == nil { return consensus.ErrUnknownAncestor } + header.Time = parent.Time + c.config.Period if header.Time < uint64(time.Now().Unix()) { header.Time = uint64(time.Now().Unix()) } + return nil } @@ -622,6 +660,7 @@ func (c *Clique) Seal(ctx context.Context, chain consensus.ChainHeaderReader, bl if err != nil { return err } + if _, authorized := snap.Signers[signer]; !authorized { return errUnauthorizedSigner } @@ -636,6 +675,7 @@ func (c *Clique) Seal(ctx context.Context, chain consensus.ChainHeaderReader, bl } // Sweet, the protocol permits us to sign the block, wait for our time delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple + if header.Difficulty.Cmp(diffNoTurn) == 0 { // It's not our turn explicitly to sign, delay it a bit wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime @@ -648,9 +688,11 @@ func (c *Clique) Seal(ctx context.Context, chain consensus.ChainHeaderReader, bl if err != nil { return err } + copy(header.Extra[len(header.Extra)-extraSeal:], sighash) // Wait until sealing is terminated or delay timeout. log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) + go func() { select { case <-stop: @@ -677,9 +719,11 @@ func (c *Clique) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, if err != nil { return nil } + c.lock.RLock() signer := c.signer c.lock.RUnlock() + return calcDifficulty(snap, signer) } @@ -687,6 +731,7 @@ func calcDifficulty(snap *Snapshot, signer common.Address) *big.Int { if snap.inturn(snap.Number+1, signer) { return new(big.Int).Set(diffInTurn) } + return new(big.Int).Set(diffNoTurn) } @@ -714,6 +759,7 @@ func SealHash(header *types.Header) (hash common.Hash) { hasher := sha3.NewLegacyKeccak256() encodeSigHeader(hasher, header) hasher.(crypto.KeccakState).Read(hash[:]) + return hash } @@ -727,6 +773,7 @@ func SealHash(header *types.Header) (hash common.Hash) { func CliqueRLP(header *types.Header) []byte { b := new(bytes.Buffer) encodeSigHeader(b, header) + return b.Bytes() } @@ -751,9 +798,11 @@ func encodeSigHeader(w io.Writer, header *types.Header) { if header.BaseFee != nil { enc = append(enc, header.BaseFee) } + if header.WithdrawalsHash != nil { panic("unexpected withdrawal hash value in clique") } + if err := rlp.Encode(w, enc); err != nil { panic("can't encode: " + err.Error()) } diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index 990c78f9f7..ae380b80fa 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -44,6 +44,7 @@ func TestReimportMirroredState(t *testing.T) { engine = New(params.AllCliqueProtocolChanges.Clique, db) signer = new(types.HomesteadSigner) ) + genspec := &core.Genesis{ Config: params.AllCliqueProtocolChanges, ExtraData: make([]byte, extraVanity+common.AddressLength+extraSeal), @@ -70,6 +71,7 @@ func TestReimportMirroredState(t *testing.T) { if err != nil { panic(err) } + block.AddTxWithChain(chain, tx) } }) @@ -78,6 +80,7 @@ func TestReimportMirroredState(t *testing.T) { if i > 0 { header.ParentHash = blocks[i-1].Hash() } + header.Extra = make([]byte, extraVanity+extraSeal) header.Difficulty = diffInTurn @@ -87,12 +90,14 @@ func TestReimportMirroredState(t *testing.T) { } // Insert the first two blocks and make sure the chain is valid db = rawdb.NewMemoryDatabase() + chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil, nil, nil) defer chain.Stop() if _, err := chain.InsertChain(blocks[:2]); err != nil { t.Fatalf("failed to insert initial blocks: %v", err) } + if head := chain.CurrentBlock().Number.Uint64(); head != 2 { t.Fatalf("chain head mismatch: have %d, want %d", head, 2) } @@ -106,6 +111,7 @@ func TestReimportMirroredState(t *testing.T) { if _, err := chain.InsertChain(blocks[2:]); err != nil { t.Fatalf("failed to insert final block: %v", err) } + if head := chain.CurrentBlock().Number.Uint64(); head != 3 { t.Fatalf("chain head mismatch: have %d, want %d", head, 3) } @@ -119,6 +125,7 @@ func TestSealHash(t *testing.T) { BaseFee: new(big.Int), }) want := common.HexToHash("0xbd3d1fa43fbc4c5bfcc91b179ec92e2861df3654de60468beb908ff805359e8f") + if have != want { t.Errorf("have %x, want %x", have, want) } diff --git a/consensus/clique/snapshot.go b/consensus/clique/snapshot.go index e5efa5108f..b5fbe72e36 100644 --- a/consensus/clique/snapshot.go +++ b/consensus/clique/snapshot.go @@ -85,6 +85,7 @@ func newSnapshot(config *params.CliqueConfig, sigcache *sigLRU, number uint64, h for _, signer := range signers { snap.Signers[signer] = struct{}{} } + return snap } @@ -94,10 +95,12 @@ func loadSnapshot(config *params.CliqueConfig, sigcache *sigLRU, db ethdb.Databa if err != nil { return nil, err } + snap := new(Snapshot) if err := json.Unmarshal(blob, snap); err != nil { return nil, err } + snap.config = config snap.sigcache = sigcache @@ -110,6 +113,7 @@ func (s *Snapshot) store(db ethdb.Database) error { if err != nil { return err } + return db.Put(append(rawdb.CliqueSnapshotPrefix, s.Hash[:]...), blob) } @@ -128,12 +132,15 @@ func (s *Snapshot) copy() *Snapshot { for signer := range s.Signers { cpy.Signers[signer] = struct{}{} } + for block, signer := range s.Recents { cpy.Recents[block] = signer } + for address, tally := range s.Tally { cpy.Tally[address] = tally } + copy(cpy.Votes, s.Votes) return cpy @@ -159,6 +166,7 @@ func (s *Snapshot) cast(address common.Address, authorize bool) bool { } else { s.Tally[address] = Tally{Authorize: authorize, Votes: 1} } + return true } @@ -180,6 +188,7 @@ func (s *Snapshot) uncast(address common.Address, authorize bool) bool { } else { delete(s.Tally, address) } + return true } @@ -196,6 +205,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { return nil, errInvalidVotingChain } } + if headers[0].Number.Uint64() != s.Number+1 { return nil, errInvalidVotingChain } @@ -206,6 +216,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { start = time.Now() logged = time.Now() ) + for i, header := range headers { // Remove any votes on checkpoint blocks number := header.Number.Uint64() @@ -222,14 +233,17 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { if err != nil { return nil, err } + if _, ok := snap.Signers[signer]; !ok { return nil, errUnauthorizedSigner } + for _, recent := range snap.Recents { if recent == signer { return nil, errRecentlySigned } } + snap.Recents[number] = signer // Header authorized, discard any previous votes from the signer @@ -240,11 +254,13 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { // Uncast the vote from the chronological list snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + break // only one vote allowed } } // Tally up the new vote from the signer var authorize bool + switch { case bytes.Equal(header.Nonce[:], nonceAuthVote): authorize = true @@ -253,6 +269,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { default: return nil, errInvalidVote } + if snap.cast(header.Coinbase, authorize) { snap.Votes = append(snap.Votes, &Vote{ Signer: signer, @@ -300,9 +317,11 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { logged = time.Now() } } + if time.Since(start) > 8*time.Second { log.Info("Reconstructed voting history", "processed", len(headers), "elapsed", common.PrettyDuration(time.Since(start))) } + snap.Number += uint64(len(headers)) snap.Hash = headers[len(headers)-1].Hash() @@ -315,7 +334,9 @@ func (s *Snapshot) signers() []common.Address { for sig := range s.Signers { sigs = append(sigs, sig) } + sort.Sort(signersAscending(sigs)) + return sigs } @@ -325,5 +346,6 @@ func (s *Snapshot) inturn(number uint64, signer common.Address) bool { for offset < len(signers) && signers[offset] != signer { offset++ } + return (number % uint64(len(signers))) == uint64(offset) } diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index 27ee92f839..0736ca94f5 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -53,7 +53,9 @@ func (ap *testerAccountPool) checkpoint(header *types.Header, signers []string) for i, signer := range signers { auths[i] = ap.address(signer) } + sort.Sort(signersAscending(auths)) + for i, auth := range auths { copy(header.Extra[extraVanity+i*common.AddressLength:], auth.Bytes()) } @@ -395,6 +397,7 @@ func (tt *cliqueTest) run(t *testing.T) { for j, signer := range tt.signers { signers[j] = accounts.address(signer) } + for j := 0; j < len(signers); j++ { for k := j + 1; k < len(signers); k++ { if bytes.Compare(signers[j][:], signers[k][:]) > 0 { @@ -425,8 +428,10 @@ func (tt *cliqueTest) run(t *testing.T) { _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, len(tt.votes), func(j int, gen *core.BlockGen) { // Cast the vote contained in this block gen.SetCoinbase(accounts.address(tt.votes[j].voted)) + if tt.votes[j].auth { var nonce types.BlockNonce + copy(nonce[:], nonceAuthVote) gen.SetNonce(nonce) } @@ -438,11 +443,13 @@ func (tt *cliqueTest) run(t *testing.T) { if j > 0 { header.ParentHash = blocks[j-1].Hash() } + header.Extra = make([]byte, extraVanity+extraSeal) if auths := tt.votes[j].checkpoint; auths != nil { header.Extra = make([]byte, extraVanity+len(auths)*common.AddressLength+extraSeal) accounts.checkpoint(header, auths) } + header.Difficulty = diffInTurn // Ignored, we just need a valid number // Generate the signature, embed it into the header and the block @@ -451,10 +458,12 @@ func (tt *cliqueTest) run(t *testing.T) { } // Split the blocks up into individual import batches (cornercase testing) batches := [][]*types.Block{nil} + for j, block := range blocks { if tt.votes[j].newbatch { batches = append(batches, nil) } + batches[len(batches)-1] = append(batches[len(batches)-1], block) } // Pass all the headers through clique and ensure tallying succeeds @@ -470,9 +479,11 @@ func (tt *cliqueTest) run(t *testing.T) { break } } + if _, err = chain.InsertChain(batches[len(batches)-1]); err != tt.failure { t.Errorf("failure mismatch: have %v, want %v", err, tt.failure) } + if tt.failure != nil { return } @@ -489,6 +500,7 @@ func (tt *cliqueTest) run(t *testing.T) { for j, signer := range tt.results { signers[j] = accounts.address(signer) } + for j := 0; j < len(signers); j++ { for k := j + 1; k < len(signers); k++ { if bytes.Compare(signers[j][:], signers[k][:]) > 0 { @@ -496,10 +508,12 @@ func (tt *cliqueTest) run(t *testing.T) { } } } + result := snap.signers() if len(result) != len(signers) { t.Fatalf("signers mismatch: have %x, want %x", result, signers) } + for j := 0; j < len(result); j++ { if !bytes.Equal(result[j][:], signers[j][:]) { t.Fatalf("signer %d: signer mismatch: have %x, want %x", j, result[j], signers[j]) diff --git a/consensus/ethash/algorithm.go b/consensus/ethash/algorithm.go index a2a6081f55..f950730fab 100644 --- a/consensus/ethash/algorithm.go +++ b/consensus/ethash/algorithm.go @@ -55,6 +55,7 @@ func cacheSize(block uint64) uint64 { if epoch < maxEpoch { return cacheSizes[epoch] } + return calcCacheSize(epoch) } @@ -66,6 +67,7 @@ func calcCacheSize(epoch int) uint64 { for !new(big.Int).SetUint64(size / hashBytes).ProbablyPrime(1) { // Always accurate for n < 2^64 size -= 2 * hashBytes } + return size } @@ -76,6 +78,7 @@ func datasetSize(block uint64) uint64 { if epoch < maxEpoch { return datasetSizes[epoch] } + return calcDatasetSize(epoch) } @@ -87,6 +90,7 @@ func calcDatasetSize(epoch int) uint64 { for !new(big.Int).SetUint64(size / mixBytes).ProbablyPrime(1) { // Always accurate for n < 2^64 size -= 2 * mixBytes } + return size } @@ -104,11 +108,14 @@ func makeHasher(h hash.Hash) hasher { hash.Hash Read([]byte) (int, error) } + rh, ok := h.(readerHash) if !ok { panic("can't find Read method on hash") } + outputLen := rh.Size() + return func(dest []byte, data []byte) { rh.Reset() rh.Write(data) @@ -123,10 +130,12 @@ func seedHash(block uint64) []byte { if block < epochLength { return seed } + keccak256 := makeHasher(sha3.NewLegacyKeccak256()) for i := 0; i < int(block/epochLength); i++ { keccak256(seed, seed) } + return seed } @@ -148,6 +157,7 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) { if elapsed > 3*time.Second { logFn = logger.Info } + logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed)) }() // Convert our destination slice to a byte buffer @@ -183,6 +193,7 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) { // Sequentially produce the initial dataset keccak512(cache, seed) + for offset := uint64(hashBytes); offset < size; offset += hashBytes { keccak512(cache[offset:], cache[offset-hashBytes:offset]) progress.Add(1) @@ -197,6 +208,7 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) { dstOff = j * hashBytes xorOff = (binary.LittleEndian.Uint32(cache[dstOff:]) % uint32(rows)) * hashBytes ) + bitutil.XORBytes(temp, cache[srcOff:srcOff+hashBytes], cache[xorOff:xorOff+hashBytes]) keccak512(cache[dstOff:], temp) @@ -241,6 +253,7 @@ func generateDatasetItem(cache []uint32, index uint32, keccak512 hasher) []byte mix := make([]byte, hashBytes) binary.LittleEndian.PutUint32(mix, cache[(index%rows)*hashWords]^index) + for i := 1; i < hashWords; i++ { binary.LittleEndian.PutUint32(mix[i*4:], cache[(index%rows)*hashWords+uint32(i)]) } @@ -260,7 +273,9 @@ func generateDatasetItem(cache []uint32, index uint32, keccak512 hasher) []byte for i, val := range intMix { binary.LittleEndian.PutUint32(mix[i*4:], val) } + keccak512(mix, mix) + return mix } @@ -278,6 +293,7 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) { if elapsed > 3*time.Second { logFn = logger.Info } + logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed)) }() @@ -297,9 +313,11 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) { size := uint64(len(dataset)) var pend sync.WaitGroup + pend.Add(threads) var progress atomic.Uint64 + for i := 0; i < threads; i++ { go func(id int) { defer pend.Done() @@ -310,17 +328,20 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) { // Calculate the data segment this thread should generate batch := (size + hashBytes*uint64(threads) - 1) / (hashBytes * uint64(threads)) first := uint64(id) * batch + limit := first + batch if limit > size/hashBytes { limit = size / hashBytes } // Calculate the dataset segment percent := size / hashBytes / 100 + for index := first; index < limit; index++ { item := generateDatasetItem(cache, uint32(index), keccak512) if swapped { swap(item) } + copy(dataset[index*hashBytes:], item) if status := progress.Add(1); status%percent == 0 { @@ -366,12 +387,14 @@ func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) for i := 0; i < len(mix); i += 4 { mix[i/4] = fnv(fnv(fnv(mix[i], mix[i+1]), mix[i+2]), mix[i+3]) } + mix = mix[:len(mix)/4] digest := make([]byte, common.HashLength) for i, val := range mix { binary.LittleEndian.PutUint32(digest[i*4:], val) } + return digest, crypto.Keccak256(append(seed, digest...)) } @@ -388,8 +411,10 @@ func hashimotoLight(size uint64, cache []uint32, hash []byte, nonce uint64) ([]b for i := 0; i < len(data); i++ { data[i] = binary.LittleEndian.Uint32(rawData[i*4:]) } + return data } + return hashimoto(hash, nonce, size, lookup) } @@ -401,6 +426,7 @@ func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) offset := index * hashWords return dataset[offset : offset+hashWords] } + return hashimoto(hash, nonce, uint64(len(dataset))*4, lookup) } diff --git a/consensus/ethash/algorithm_test.go b/consensus/ethash/algorithm_test.go index 3ecd730681..37db42f307 100644 --- a/consensus/ethash/algorithm_test.go +++ b/consensus/ethash/algorithm_test.go @@ -48,6 +48,7 @@ func TestSizeCalculations(t *testing.T) { t.Errorf("cache %d: cache size mismatch: have %d, want %d", epoch, size, want) } } + for epoch, want := range datasetSizes { if size := calcDatasetSize(epoch); size != want { t.Errorf("dataset %d: dataset size mismatch: have %d, want %d", epoch, size, want) @@ -682,13 +683,16 @@ func TestHashimoto(t *testing.T) { if !bytes.Equal(digest, wantDigest) { t.Errorf("light hashimoto digest mismatch: have %x, want %x", digest, wantDigest) } + if !bytes.Equal(result, wantResult) { t.Errorf("light hashimoto result mismatch: have %x, want %x", result, wantResult) } + digest, result = hashimotoFull(dataset, hash, nonce) if !bytes.Equal(digest, wantDigest) { t.Errorf("full hashimoto digest mismatch: have %x, want %x", digest, wantDigest) } + if !bytes.Equal(result, wantResult) { t.Errorf("full hashimoto result mismatch: have %x, want %x", result, wantResult) } @@ -727,6 +731,7 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) { for i := 0; i < 3; i++ { pend.Add(1) + go func(idx int) { defer pend.Done() @@ -734,8 +739,10 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) { CacheDir: cachedir, CachesOnDisk: 1, } + ethash := New(config, nil, false) defer ethash.Close() + if err := ethash.verifySeal(nil, block.Header(), false); err != nil { t.Errorf("proc %d: block verification failed: %v", idx, err) } @@ -758,6 +765,7 @@ func BenchmarkSmallDatasetGeneration(b *testing.B) { generateCache(cache, 0, make([]byte, 32)) b.ResetTimer() + for i := 0; i < b.N; i++ { dataset := make([]uint32, 32*65536/4) generateDataset(dataset, 0, cache) @@ -772,6 +780,7 @@ func BenchmarkHashimotoLight(b *testing.B) { hash := hexutil.MustDecode("0xc9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f") b.ResetTimer() + for i := 0; i < b.N; i++ { hashimotoLight(datasetSize(1), cache, hash, 0) } @@ -788,6 +797,7 @@ func BenchmarkHashimotoFullSmall(b *testing.B) { hash := hexutil.MustDecode("0xc9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f") b.ResetTimer() + for i := 0; i < b.N; i++ { hashimotoFull(dataset, hash, 0) } @@ -799,8 +809,11 @@ func benchmarkHashimotoFullMmap(b *testing.B, name string, lock bool) { d := &dataset{epoch: 0} d.generate(tmpdir, 1, lock, false) + var hash [common.HashLength]byte + b.ResetTimer() + for i := 0; i < b.N; i++ { binary.PutVarint(hash[:], int64(i)) hashimotoFull(d.dataset, hash[:], 0) diff --git a/consensus/ethash/api.go b/consensus/ethash/api.go index da3b0751be..5ac9600443 100644 --- a/consensus/ethash/api.go +++ b/consensus/ethash/api.go @@ -80,7 +80,9 @@ func (api *API) SubmitWork(nonce types.BlockNonce, hash, digest common.Hash) boo case <-api.ethash.remote.exitCh: return false } + err := <-errc + return err == nil } @@ -104,6 +106,7 @@ func (api *API) SubmitHashrate(rate hexutil.Uint64, id common.Hash) bool { // Block until hash rate submitted successfully. <-done + return true } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index f58c0f9287..3a1b65984a 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -114,6 +114,7 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *ty if chain.GetHeader(header.Hash(), number) != nil { return nil } + parent := chain.GetHeader(header.ParentHash, number-1) if parent == nil { return consensus.ErrUnknownAncestor @@ -132,6 +133,7 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ for i := 0; i < len(headers); i++ { results <- nil } + return abort, results } @@ -149,6 +151,7 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ abort = make(chan struct{}) unixNow = time.Now().Unix() ) + for i := 0; i < workers; i++ { go func() { for index := range inputs { @@ -159,13 +162,16 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ } errorsOut := make(chan error, len(headers)) + go func() { defer close(inputs) + var ( in, out = 0, 0 checked = make([]bool, len(headers)) inputs = inputs ) + for { select { case inputs <- in: @@ -176,6 +182,7 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ case index := <-done: for checked[index] = true; checked[out]; out++ { errorsOut <- errors[out] + if out == len(headers)-1 { return } @@ -185,6 +192,7 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ } } }() + return abort, errorsOut } @@ -195,9 +203,11 @@ func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainHeaderReader, head } else if headers[index-1].Hash() == headers[index].ParentHash { parent = headers[index-1] } + if parent == nil { return consensus.ErrUnknownAncestor } + return ethash.verifyHeader(chain, headers[index], parent, false, seals[index], unixNow) } @@ -212,6 +222,7 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo if len(block.Uncles()) > maxUncles { return errTooManyUncles } + if len(block.Uncles()) == 0 { return nil } @@ -224,6 +235,7 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo if ancestorHeader == nil { break } + ancestors[parent] = ancestorHeader // If the ancestor doesn't have any uncles, we don't have to iterate them if ancestorHeader.UncleHash != types.EmptyUncleHash { @@ -232,12 +244,15 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo if ancestor == nil { break } + for _, uncle := range ancestor.Uncles() { uncles.Add(uncle.Hash()) } } + parent, number = ancestorHeader.ParentHash, number-1 } + ancestors[block.Hash()] = block.Header() uncles.Add(block.Hash()) @@ -248,19 +263,23 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo if uncles.Contains(hash) { return errDuplicateUncle } + uncles.Add(hash) // Make sure the uncle has a valid ancestry if ancestors[hash] != nil { return errUncleIsAncestor } + if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() { return errDanglingUncle } + if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true, time.Now().Unix()); err != nil { return err } } + return nil } @@ -278,6 +297,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa return consensus.ErrFutureBlock } } + if header.Time <= parent.Time { return errOlderBlockTime } @@ -301,6 +321,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa if header.BaseFee != nil { return fmt.Errorf("invalid baseFee before fork: have %d, expected 'nil'", header.BaseFee) } + if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { return err } @@ -330,6 +351,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil { return err } + return nil } @@ -345,6 +367,7 @@ func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time uin // given the parent block's time and difficulty. func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { next := new(big.Int).Add(parent.Number, big1) + switch { case config.IsGrayGlacier(next): return calcDifficultyEip5133(time, parent) @@ -382,13 +405,13 @@ func makeDifficultyCalculator(bombDelay *big.Int) func(time uint64, parent *type // 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 := new(big.Int).Sub(bombDelay, big1) + return func(time uint64, parent *types.Header) *big.Int { // https://github.com/ethereum/EIPs/issues/100. // algorithm: // diff = (parent_diff + // (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)) // ) + 2^(periodCount - 2) - bigTime := new(big.Int).SetUint64(time) bigParentTime := new(big.Int).SetUint64(parent.Time) @@ -399,6 +422,7 @@ func makeDifficultyCalculator(bombDelay *big.Int) func(time uint64, parent *type // (2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9 x.Sub(bigTime, bigParentTime) x.Div(x, big9) + if parent.UncleHash == types.EmptyUncleHash { x.Sub(big1, x) } else { @@ -434,6 +458,7 @@ func makeDifficultyCalculator(bombDelay *big.Int) func(time uint64, parent *type y.Exp(big2, y, nil) x.Add(x, y) } + return x } } @@ -447,7 +472,6 @@ func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int { // diff = (parent_diff + // (parent_diff / 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99)) // ) + 2^(periodCount - 2) - bigTime := new(big.Int).SetUint64(time) bigParentTime := new(big.Int).SetUint64(parent.Time) @@ -484,6 +508,7 @@ func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int { y.Exp(big2, y, nil) x.Add(x, y) } + return x } @@ -504,12 +529,14 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int { } else { diff.Sub(parent.Difficulty, adjust) } + if diff.Cmp(params.MinimumDifficulty) < 0 { diff.Set(params.MinimumDifficulty) } periodCount := new(big.Int).Add(parent.Number, big1) periodCount.Div(periodCount, expDiffPeriod) + if periodCount.Cmp(big1) > 0 { // diff = diff + 2^(periodCount - 2) expDiff := periodCount.Sub(periodCount, big2) @@ -517,6 +544,7 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int { diff.Add(diff, expDiff) diff = math.BigMax(diff, params.MinimumDifficulty) } + return diff } @@ -532,9 +560,11 @@ func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *type // If we're running a fake PoW, accept any seal as valid if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake { time.Sleep(ethash.fakeDelay) + if ethash.fakeFail == header.Number.Uint64() { return errInvalidPoW } + return nil } // If we're running a shared PoW, delegate verification to it @@ -574,6 +604,7 @@ func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *type if ethash.config.PowMode == ModeTest { size = 32 * 1024 } + digest, result = hashimotoLight(size, cache.cache, ethash.SealHash(header).Bytes(), header.Nonce.Uint64()) // Caches are unmapped in a finalizer. Ensure that the cache stays alive @@ -584,10 +615,12 @@ func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *type if !bytes.Equal(header.MixDigest[:], digest) { return errInvalidMixDigest } + target := new(big.Int).Div(two256, header.Difficulty) if new(big.Int).SetBytes(result).Cmp(target) > 0 { return errInvalidPoW } + return nil } @@ -598,7 +631,9 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H if parent == nil { return consensus.ErrUnknownAncestor } + header.Difficulty = ethash.CalcDifficulty(chain, header.Time, parent) + return nil } @@ -650,8 +685,10 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { if header.WithdrawalsHash != nil { panic("withdrawal hash set on ethash") } + rlp.Encode(hasher, enc) hasher.Sum(hash[:0]) + return hash } @@ -670,11 +707,13 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header if config.IsByzantium(header.Number) { blockReward = ByzantiumBlockReward } + if config.IsConstantinople(header.Number) { blockReward = ConstantinopleBlockReward } // Accumulate the rewards for the miner and any included uncles reward := new(big.Int).Set(blockReward) + r := new(big.Int) for _, uncle := range uncles { r.Add(uncle.Number, big8) @@ -686,5 +725,6 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header r.Div(blockReward, big32) reward.Add(reward, r) } + state.AddBalance(header.Coinbase, reward) } diff --git a/consensus/ethash/consensus_test.go b/consensus/ethash/consensus_test.go index 59db41c49b..2188ad52e4 100644 --- a/consensus/ethash/consensus_test.go +++ b/consensus/ethash/consensus_test.go @@ -48,6 +48,7 @@ func (d *diffTest) UnmarshalJSON(b []byte) (err error) { CurrentBlocknumber string CurrentDifficulty string } + if err := json.Unmarshal(b, &ext); err != nil { return err } @@ -69,6 +70,7 @@ func TestCalcDifficulty(t *testing.T) { defer file.Close() tests := make(map[string]diffTest) + err = json.NewDecoder(file).Decode(&tests) if err != nil { t.Fatal(err) @@ -78,6 +80,7 @@ func TestCalcDifficulty(t *testing.T) { for name, test := range tests { number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1)) + diff := CalcDifficulty(config, test.CurrentTimestamp, &types.Header{ Number: number, Time: test.ParentTimestamp, @@ -91,11 +94,12 @@ func TestCalcDifficulty(t *testing.T) { func randSlice(min, max uint32) []byte { var b = make([]byte, 4) - _ , _ = crand.Read(b) + _, _ = crand.Read(b) a := binary.LittleEndian.Uint32(b) size := min + a%(max-min) out := make([]byte, size) - _ , _ = crand.Read(out) + _, _ = crand.Read(out) + return out } @@ -117,6 +121,7 @@ func TestDifficultyCalculators(t *testing.T) { 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 @@ -129,9 +134,11 @@ func TestDifficultyCalculators(t *testing.T) { 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) @@ -150,38 +157,45 @@ func BenchmarkDifficultyCalculator(b *testing.B) { 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 index 66a18059c6..b7c09b8fd4 100644 --- a/consensus/ethash/difficulty.go +++ b/consensus/ethash/difficulty.go @@ -51,7 +51,6 @@ func CalcDifficultyFrontierU256(time uint64, parent *types.Header) *big.Int { - time = block.timestamp - num = block.number */ - pDiff, _ := uint256.FromBig(parent.Difficulty) // pDiff: pdiff adjust := pDiff.Clone() adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048 @@ -61,6 +60,7 @@ func CalcDifficultyFrontierU256(time uint64, parent *types.Header) *big.Int { } else { pDiff.Sub(pDiff, adjust) } + if pDiff.LtUint64(minimumDifficulty) { pDiff.SetUint64(minimumDifficulty) } @@ -73,6 +73,7 @@ func CalcDifficultyFrontierU256(time uint64, parent *types.Header) *big.Int { expDiff.Lsh(expDiff, uint(periodCount-2)) // expdiff: 2 ^ (periodCount -2) pDiff.Add(pDiff, expDiff) } + return pDiff.ToBig() } @@ -94,13 +95,14 @@ func CalcDifficultyHomesteadU256(time uint64, parent *types.Header) *big.Int { - time = block.timestamp - num = block.number */ - pDiff, _ := uint256.FromBig(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 @@ -109,13 +111,16 @@ func CalcDifficultyHomesteadU256(time uint64, parent *types.Header) *big.Int { } 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) } @@ -125,6 +130,7 @@ func CalcDifficultyHomesteadU256(time uint64, parent *types.Header) *big.Int { expFactor := adjust.Lsh(adjust.SetOne(), uint(periodCount-2)) pDiff.Add(pDiff, expFactor) } + return pDiff.ToBig() } @@ -135,6 +141,7 @@ func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent * // 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 @@ -145,10 +152,12 @@ func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent * child_diff = max(a,b ) */ x := (time - parent.Time) / 9 // (block_timestamp - parent_timestamp) // 9 - c := uint64(1) // if parent.unclehash == emptyUncleHashHash + + 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 @@ -156,6 +165,7 @@ func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent * } else { x = c - x // (2 or 1) - (t-p)/9 } + if x > 99 { x = 99 // max(x, 99) } @@ -164,8 +174,9 @@ func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent * 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) + + 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 @@ -186,6 +197,7 @@ func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent * y.Add(z, y) } } + return y.ToBig() } } diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index 0ad235bee1..ebc22fe2c3 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -79,25 +79,31 @@ func memoryMap(path string, lock bool) (*os.File, mmap.MMap, []uint32, error) { if err != nil { return nil, nil, nil, err } + mem, buffer, err := memoryMapFile(file, false) if err != nil { file.Close() return nil, nil, nil, err } + for i, magic := range dumpMagic { if buffer[i] != magic { mem.Unmap() file.Close() + return nil, nil, nil, ErrInvalidDumpMagic } } + if lock { if err := mem.Lock(); err != nil { mem.Unmap() file.Close() + return nil, nil, nil, err } } + return file, mem, buffer[len(dumpMagic):], err } @@ -108,6 +114,7 @@ func memoryMapFile(file *os.File, write bool) (mmap.MMap, []uint32, error) { if write { flag = mmap.RDWR } + mem, err := mmap.Map(file, flag, 0) if err != nil { return nil, nil, err @@ -118,6 +125,7 @@ func memoryMapFile(file *os.File, write bool) (mmap.MMap, []uint32, error) { header.Data = (*reflect.SliceHeader)(unsafe.Pointer(&mem)).Data header.Cap = len(mem) / 4 header.Len = header.Cap + return mem, view, nil } @@ -136,9 +144,11 @@ func memoryMapAndGenerate(path string, size uint64, lock bool, generator func(bu if err != nil { return nil, nil, nil, err } + if err = ensureSize(dump, int64(len(dumpMagic))*4+int64(size)); err != nil { dump.Close() os.Remove(temp) + return nil, nil, nil, err } // Memory map the file for writing and fill it with the generator @@ -146,8 +156,10 @@ func memoryMapAndGenerate(path string, size uint64, lock bool, generator func(bu if err != nil { dump.Close() os.Remove(temp) + return nil, nil, nil, err } + copy(buffer, dumpMagic) data := buffer[len(dumpMagic):] @@ -156,12 +168,15 @@ func memoryMapAndGenerate(path string, size uint64, lock bool, generator func(bu if err := mem.Unmap(); err != nil { return nil, nil, nil, err } + if err := dump.Close(); err != nil { return nil, nil, nil, err } + if err := os.Rename(temp, path); err != nil { return nil, nil, nil, err } + return memoryMap(path, lock) } @@ -217,6 +232,7 @@ func (lru *lru[T]) get(epoch uint64) (item, future T) { log.Trace("Requiring new ethash "+lru.what, "epoch", epoch) item = lru.new(epoch) } + lru.cache.Add(epoch, item) } // Update the 'future item' if epoch is larger than previously seen. @@ -226,6 +242,7 @@ func (lru *lru[T]) get(epoch uint64) (item, future T) { lru.future = epoch + 1 lru.futureItem = future } + return item, future } @@ -248,6 +265,7 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) { c.once.Do(func() { size := cacheSize(c.epoch*epochLength + 1) seed := seedHash(c.epoch*epochLength + 1) + if test { size = 1024 } @@ -255,6 +273,7 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) { if dir == "" { c.cache = make([]uint32, size/4) generateCache(c.cache, c.epoch, seed) + return } // Disk storage is needed, this will get fancy @@ -262,6 +281,7 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) { if !isLittleEndian() { endian = ".be" } + path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian)) logger := log.New("epoch", c.epoch) @@ -271,11 +291,13 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) { // Try to load the file from disk and memory map it var err error + c.dump, c.mmap, c.cache, err = memoryMap(path, lock) if err == nil { logger.Debug("Loaded old ethash cache from disk") return } + logger.Debug("Failed to load old ethash cache", "err", err) // No previous cache available, create a new cache file to fill @@ -290,6 +312,7 @@ func (c *cache) generate(dir string, limit int, lock bool, test bool) { for ep := int(c.epoch) - limit; ep >= 0; ep-- { seed := seedHash(uint64(ep)*epochLength + 1) path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s*", algorithmRevision, seed[:8], endian)) + files, _ := filepath.Glob(path) // find also the temp files that are generated. for _, file := range files { os.Remove(file) @@ -332,6 +355,7 @@ func (d *dataset) generate(dir string, limit int, lock bool, test bool) { csize := cacheSize(d.epoch*epochLength + 1) dsize := datasetSize(d.epoch*epochLength + 1) seed := seedHash(d.epoch*epochLength + 1) + if test { csize = 1024 dsize = 32 * 1024 @@ -351,6 +375,7 @@ func (d *dataset) generate(dir string, limit int, lock bool, test bool) { if !isLittleEndian() { endian = ".be" } + path := filepath.Join(dir, fmt.Sprintf("full-R%d-%x%s", algorithmRevision, seed[:8], endian)) logger := log.New("epoch", d.epoch) @@ -360,11 +385,13 @@ func (d *dataset) generate(dir string, limit int, lock bool, test bool) { // Try to load the file from disk and memory map it var err error + d.dump, d.mmap, d.dataset, err = memoryMap(path, lock) if err == nil { logger.Debug("Loaded old ethash dataset from disk") return } + logger.Debug("Failed to load old ethash dataset", "err", err) // No previous dataset available, create a new dataset file to fill @@ -476,16 +503,20 @@ func New(config Config, notify []string, noverify bool) *Ethash { if config.Log == nil { config.Log = log.Root() } + if config.CachesInMem <= 0 { config.Log.Warn("One ethash cache must always be in memory", "requested", config.CachesInMem) config.CachesInMem = 1 } + if config.CacheDir != "" && config.CachesOnDisk > 0 { config.Log.Info("Disk storage enabled for ethash caches", "dir", config.CacheDir, "count", config.CachesOnDisk) } + if config.DatasetDir != "" && config.DatasetsOnDisk > 0 { config.Log.Info("Disk storage enabled for ethash DAGs", "dir", config.DatasetDir, "count", config.DatasetsOnDisk) } + ethash := &Ethash{ config: config, caches: newlru(config.CachesInMem, newCache), @@ -496,7 +527,9 @@ func New(config Config, notify []string, noverify bool) *Ethash { if config.PowMode == ModeShared { ethash.shared = sharedEthash } + ethash.remote = startRemoteSealer(ethash, notify, noverify) + return ethash } @@ -573,9 +606,11 @@ func (ethash *Ethash) StopRemoteSealer() error { if ethash.remote == nil { return } + close(ethash.remote.requestExit) <-ethash.remote.exitCh }) + return nil } @@ -593,6 +628,7 @@ func (ethash *Ethash) cache(block uint64) *cache { if future != nil { go future.generate(ethash.config.CacheDir, ethash.config.CachesOnDisk, ethash.config.CachesLockMmap, ethash.config.PowMode == ModeTest) } + return current } @@ -619,10 +655,12 @@ func (ethash *Ethash) dataset(block uint64, async bool) *dataset { } else { // Either blocking generation was requested, or already done current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) + if future != nil { go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) } } + return current } @@ -666,6 +704,7 @@ func (ethash *Ethash) Hashrate() float64 { if ethash.config.PowMode != ModeNormal && ethash.config.PowMode != ModeTest { return ethash.hashrate.Rate1() } + var res = make(chan uint64, 1) select { diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go index 92de17a3a7..339295321c 100644 --- a/consensus/ethash/ethash_test.go +++ b/consensus/ethash/ethash_test.go @@ -38,6 +38,7 @@ func TestTestMode(t *testing.T) { defer ethash.Close() results := make(chan *types.Block) + err := ethash.Seal(context.Background(), nil, types.NewBlockWithHeader(header), results, nil) if err != nil { t.Fatalf("failed to seal block: %v", err) @@ -46,6 +47,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, false); err != nil { t.Fatalf("unexpected verification error: %v", err) } @@ -71,13 +73,17 @@ func TestCacheFileEvict(t *testing.T) { CacheDir: tmpdir, PowMode: ModeTest, } + e := New(config, nil, false) defer e.Close() workers := 8 epochs := 100 + var wg sync.WaitGroup + wg.Add(workers) + for i := 0; i < workers; i++ { go verifyTest(&wg, e, i, epochs) } @@ -88,12 +94,14 @@ func verifyTest(wg *sync.WaitGroup, e *Ethash, workerIndex, epochs int) { defer wg.Done() const wiggle = 4 * epochLength + r := rand.New(rand.NewSource(int64(workerIndex))) for epoch := 0; epoch < epochs; epoch++ { block := int64(epoch)*epochLength - wiggle/2 + r.Int63n(wiggle) if block < 0 { block = 0 } + header := &types.Header{Number: big.NewInt(block), Difficulty: big.NewInt(100)} e.verifySeal(nil, header, false) } @@ -107,6 +115,7 @@ func TestRemoteSealer(t *testing.T) { if _, err := api.GetWork(); err != errNoMiningWork { t.Error("expect to return an error indicate there is no mining work") } + header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)} block := types.NewBlockWithHeader(header) sealhash := ethash.SealHash(header) @@ -148,6 +157,7 @@ func TestHashrate(t *testing.T) { expect uint64 ids = []common.Hash{common.HexToHash("a"), common.HexToHash("b"), common.HexToHash("c")} ) + ethash := NewTester(nil, false) defer ethash.Close() @@ -160,8 +170,10 @@ func TestHashrate(t *testing.T) { if res := api.SubmitHashrate(hashrate[i], ids[i]); !res { t.Error("remote miner submit hashrate failed") } + expect += uint64(hashrate[i]) } + if tot := ethash.Hashrate(); tot != float64(expect) { t.Error("expect total hashrate should be same") } @@ -169,6 +181,7 @@ func TestHashrate(t *testing.T) { func TestClosedRemoteSealer(t *testing.T) { ethash := NewTester(nil, false) + time.Sleep(1 * time.Second) // ensure exit channel is listening ethash.Close() diff --git a/consensus/ethash/sealer.go b/consensus/ethash/sealer.go index 4e2780640b..3e5c7403d6 100644 --- a/consensus/ethash/sealer.go +++ b/consensus/ethash/sealer.go @@ -58,6 +58,7 @@ func (ethash *Ethash) Seal(ctx context.Context, chain consensus.ChainHeaderReade default: ethash.config.Log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ethash.SealHash(block.Header())) } + return nil } // If we're running a shared PoW, delegate sealing to it @@ -69,18 +70,22 @@ func (ethash *Ethash) Seal(ctx context.Context, chain consensus.ChainHeaderReade ethash.lock.Lock() threads := ethash.threads + if ethash.rand == nil { seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) if err != nil { ethash.lock.Unlock() return err } + ethash.rand = rand.New(rand.NewSource(seed.Int64())) } ethash.lock.Unlock() + if threads == 0 { threads = runtime.NumCPU() } + if threads < 0 { threads = 0 // Allows disabling local mining without extra logic around local/remote } @@ -88,12 +93,15 @@ func (ethash *Ethash) Seal(ctx context.Context, chain consensus.ChainHeaderReade if ethash.remote != nil { ethash.remote.workCh <- &sealTask{block: block, results: results} } + var ( pend sync.WaitGroup locals = make(chan *types.Block) ) + for i := 0; i < threads; i++ { pend.Add(1) + go func(id int, nonce uint64) { defer pend.Done() ethash.mine(block, id, nonce, abort, locals) @@ -125,6 +133,7 @@ func (ethash *Ethash) Seal(ctx context.Context, chain consensus.ChainHeaderReade // Wait for all miners to terminate and return the block pend.Wait() }() + return nil } @@ -145,6 +154,7 @@ func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan s nonce = seed powBuffer = new(big.Int) ) + logger := ethash.config.Log.New("miner", id) logger.Trace("Started ethash search for new nonces", "seed", seed) search: @@ -245,6 +255,7 @@ type sealWork struct { func startRemoteSealer(ethash *Ethash, urls []string, noverify bool) *remoteSealer { ctx, cancel := context.WithCancel(context.Background()) + s := &remoteSealer{ ethash: ethash, noverify: noverify, @@ -262,6 +273,7 @@ func startRemoteSealer(ethash *Ethash, urls []string, noverify bool) *remoteSeal exitCh: make(chan struct{}), } go s.loop() + return s } @@ -372,6 +384,7 @@ func (s *remoteSealer) notifyWork() { } s.reqWG.Add(len(s.notifyURLs)) + for _, url := range s.notifyURLs { go s.sendNotification(s.notifyCtx, url, blob, work) } @@ -385,8 +398,10 @@ func (s *remoteSealer) sendNotification(ctx context.Context, url string, json [] s.ethash.config.Log.Warn("Can't create remote miner notification", "err", err) return } + ctx, cancel := context.WithTimeout(ctx, remoteSealerTimeout) defer cancel() + req = req.WithContext(ctx) req.Header.Set("Content-Type", "application/json") @@ -419,6 +434,7 @@ func (s *remoteSealer) submitWork(nonce types.BlockNonce, mixDigest common.Hash, header.MixDigest = mixDigest start := time.Now() + if !s.noverify { if err := s.ethash.verifySeal(nil, header, true); err != nil { s.ethash.config.Log.Warn("Invalid proof-of-work submitted", "sealhash", sealhash, "elapsed", common.PrettyDuration(time.Since(start)), "err", err) @@ -430,6 +446,7 @@ func (s *remoteSealer) submitWork(nonce types.BlockNonce, mixDigest common.Hash, s.ethash.config.Log.Warn("Ethash result channel is empty, submitted mining result is rejected") return false } + s.ethash.config.Log.Trace("Verified correct proof-of-work", "sealhash", sealhash, "elapsed", common.PrettyDuration(time.Since(start))) // Solutions seems to be valid, return to the miner and notify acceptance. @@ -448,5 +465,6 @@ func (s *remoteSealer) submitWork(nonce types.BlockNonce, mixDigest common.Hash, } // The submitted block is too old to accept, drop it. s.ethash.config.Log.Warn("Work submitted is too old", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash()) + return false } diff --git a/consensus/ethash/sealer_test.go b/consensus/ethash/sealer_test.go index b3c3c5de75..0f0d38d820 100644 --- a/consensus/ethash/sealer_test.go +++ b/consensus/ethash/sealer_test.go @@ -37,11 +37,13 @@ import ( func TestRemoteNotify(t *testing.T) { // Start a simple web server to capture notifications. sink := make(chan [3]string) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { blob, err := io.ReadAll(req.Body) if err != nil { t.Errorf("failed to read miner notification: %v", err) } + var work [3]string if err := json.Unmarshal(blob, &work); err != nil { t.Errorf("failed to unmarshal miner notification: %v", err) @@ -68,9 +70,11 @@ func TestRemoteNotify(t *testing.T) { if want := ethash.SealHash(header).Hex(); work[0] != want { t.Errorf("work packet hash mismatch: have %s, want %s", work[0], want) } + if want := common.BytesToHash(SeedHash(header.Number.Uint64())).Hex(); work[1] != want { t.Errorf("work packet seed mismatch: have %s, want %s", work[1], want) } + target := new(big.Int).Div(new(big.Int).Lsh(big.NewInt(1), 256), header.Difficulty) if want := common.BytesToHash(target.Bytes()).Hex(); work[2] != want { t.Errorf("work packet target mismatch: have %s, want %s", work[2], want) @@ -84,11 +88,13 @@ func TestRemoteNotify(t *testing.T) { func TestRemoteNotifyFull(t *testing.T) { // Start a simple web server to capture notifications. sink := make(chan map[string]interface{}) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { blob, err := io.ReadAll(req.Body) if err != nil { t.Errorf("failed to read miner notification: %v", err) } + var work map[string]interface{} if err := json.Unmarshal(blob, &work); err != nil { t.Errorf("failed to unmarshal miner notification: %v", err) @@ -103,6 +109,7 @@ func TestRemoteNotifyFull(t *testing.T) { NotifyFull: true, Log: testlog.Logger(t, log.LvlWarn), } + ethash := New(config, []string{server.URL}, false) defer ethash.Close() @@ -120,6 +127,7 @@ func TestRemoteNotifyFull(t *testing.T) { if want := "0x" + strconv.FormatUint(header.Number.Uint64(), 16); work["number"] != want { t.Errorf("pending block number mismatch: have %v, want %v", work["number"], want) } + if want := "0x" + header.Difficulty.Text(16); work["difficulty"] != want { t.Errorf("pending block difficulty mismatch: have %s, want %s", work["difficulty"], want) } @@ -133,11 +141,13 @@ func TestRemoteNotifyFull(t *testing.T) { func TestRemoteMultiNotify(t *testing.T) { // Start a simple web server to capture notifications. sink := make(chan [3]string, 64) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { blob, err := io.ReadAll(req.Body) if err != nil { t.Errorf("failed to read miner notification: %v", err) } + var work [3]string if err := json.Unmarshal(blob, &work); err != nil { t.Errorf("failed to unmarshal miner notification: %v", err) @@ -187,6 +197,7 @@ func TestRemoteMultiNotifyFull(t *testing.T) { if err != nil { t.Errorf("failed to read miner notification: %v", err) } + var work map[string]interface{} if err := json.Unmarshal(blob, &work); err != nil { t.Errorf("failed to unmarshal miner notification: %v", err) @@ -196,6 +207,7 @@ func TestRemoteMultiNotifyFull(t *testing.T) { // Allowing the server to start listening. time.Sleep(2 * time.Second) + defer server.Close() // Create the custom ethash engine. @@ -204,6 +216,7 @@ func TestRemoteMultiNotifyFull(t *testing.T) { NotifyFull: true, Log: testlog.Logger(t, log.LvlWarn), } + ethash := New(config, []string{server.URL}, false) defer ethash.Close() @@ -292,9 +305,11 @@ func TestStaleSubmission(t *testing.T) { t.Error("error in sealing block") } } + if res := api.SubmitWork(fakeNonce, ethash.SealHash(c.headers[c.submitIndex]), fakeDigest); res != c.submitRes { t.Errorf("case %d submit result mismatch, want %t, get %t", id+1, c.submitRes, res) } + if !c.submitRes { continue } @@ -303,15 +318,19 @@ func TestStaleSubmission(t *testing.T) { if res.Header().Nonce != fakeNonce { t.Errorf("case %d block nonce mismatch, want %x, get %x", id+1, fakeNonce, res.Header().Nonce) } + if res.Header().MixDigest != fakeDigest { t.Errorf("case %d block digest mismatch, want %x, get %x", id+1, fakeDigest, res.Header().MixDigest) } + if res.Header().Difficulty.Uint64() != c.headers[c.submitIndex].Difficulty.Uint64() { t.Errorf("case %d block difficulty mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Difficulty, res.Header().Difficulty) } + if res.Header().Number.Uint64() != c.headers[c.submitIndex].Number.Uint64() { t.Errorf("case %d block number mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Number.Uint64(), res.Header().Number.Uint64()) } + if res.Header().ParentHash != c.headers[c.submitIndex].ParentHash { t.Errorf("case %d block parent hash mismatch, want %s, get %s", id+1, c.headers[c.submitIndex].ParentHash.Hex(), res.Header().ParentHash.Hex()) } diff --git a/consensus/merger.go b/consensus/merger.go index ffbcbf2b85..d6a5dff76e 100644 --- a/consensus/merger.go +++ b/consensus/merger.go @@ -45,12 +45,14 @@ type Merger struct { // NewMerger creates a new Merger which stores its transition status in the provided db. func NewMerger(db ethdb.KeyValueStore) *Merger { var status transitionStatus + blob := rawdb.ReadTransitionStatus(db) if len(blob) != 0 { if err := rlp.DecodeBytes(blob, &status); err != nil { log.Crit("Failed to decode the transition status", "err", err) } } + return &Merger{ db: db, status: status, @@ -66,11 +68,14 @@ func (m *Merger) ReachTTD() { if m.status.LeftPoW { return } + m.status = transitionStatus{LeftPoW: true} + blob, err := rlp.EncodeToBytes(m.status) if err != nil { panic(fmt.Sprintf("Failed to encode the transition status: %v", err)) } + rawdb.WriteTransitionStatus(m.db, blob) log.Info("Left PoW stage") } @@ -84,11 +89,14 @@ func (m *Merger) FinalizePoS() { if m.status.EnteredPoS { return } + m.status = transitionStatus{LeftPoW: true, EnteredPoS: true} + blob, err := rlp.EncodeToBytes(m.status) if err != nil { panic(fmt.Sprintf("Failed to encode the transition status: %v", err)) } + rawdb.WriteTransitionStatus(m.db, blob) log.Info("Entered PoS stage") } diff --git a/consensus/misc/eip1559.go b/consensus/misc/eip1559.go index 4679efa2f1..5e0dfc1460 100644 --- a/consensus/misc/eip1559.go +++ b/consensus/misc/eip1559.go @@ -37,6 +37,7 @@ func VerifyEip1559Header(config *params.ChainConfig, parent, header *types.Heade if !config.IsLondon(parent.Number) { parentGasLimit = parent.GasLimit * config.ElasticityMultiplier() } + if err := VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { return err } @@ -50,6 +51,7 @@ func VerifyEip1559Header(config *params.ChainConfig, parent, header *types.Heade return fmt.Errorf("invalid baseFee: have %s, want %s, parentBaseFee %s, parentGasUsed %d", header.BaseFee, expectedBaseFee, parent.BaseFee, parent.GasUsed) } + return nil } diff --git a/consensus/misc/eip1559_test.go b/consensus/misc/eip1559_test.go index 1bc6a23a06..0ba0aec3be 100644 --- a/consensus/misc/eip1559_test.go +++ b/consensus/misc/eip1559_test.go @@ -53,6 +53,7 @@ func config() *params.ChainConfig { config := copyConfig(params.TestChainConfig) config.LondonBlock = big.NewInt(5) config.Bor.DelhiBlock = big.NewInt(8) + return config } @@ -96,10 +97,12 @@ func TestBlockGasLimits(t *testing.T) { BaseFee: initial, Number: big.NewInt(tc.pNum + 1), } + err := VerifyEip1559Header(config(), parent, header) if tc.ok && err != nil { t.Errorf("test %d: Expected valid header: %s", i, err) } + if !tc.ok && err == nil { t.Errorf("test %d: Expected invalid header", i) } diff --git a/consensus/misc/gaslimit.go b/consensus/misc/gaslimit.go index 25f35300b9..4fa47c1f36 100644 --- a/consensus/misc/gaslimit.go +++ b/consensus/misc/gaslimit.go @@ -31,12 +31,15 @@ func VerifyGaslimit(parentGasLimit, headerGasLimit uint64) error { if diff < 0 { diff *= -1 } + limit := parentGasLimit / params.GasLimitBoundDivisor if uint64(diff) >= limit { return fmt.Errorf("invalid gas limit: have %d, want %d +-= %d", headerGasLimit, parentGasLimit, limit-1) } + if headerGasLimit < params.MinGasLimit { return errors.New("invalid gas limit below 5000") } + return nil } diff --git a/console/bridge.go b/console/bridge.go index 21ef0e8e7b..ff0cac3a15 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -55,6 +55,7 @@ func getJeth(vm *goja.Runtime) *goja.Object { if jeth == nil { panic(vm.ToValue("jeth object does not exist")) } + return jeth.ToObject(vm) } @@ -67,15 +68,18 @@ func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) { confirm string err error ) + switch { // No password was specified, prompt the user for it case len(call.Arguments) == 0: if password, err = b.prompter.PromptPassword("Passphrase: "); err != nil { return nil, err } + if confirm, err = b.prompter.PromptPassword("Repeat passphrase: "); err != nil { return nil, err } + if password != confirm { return nil, fmt.Errorf("passwords don't match!") } @@ -90,10 +94,12 @@ func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) { if !callable { return nil, fmt.Errorf("jeth.newAccount is not callable") } + ret, err := newAccount(goja.Null(), call.VM.ToValue(password)) if err != nil { return nil, err } + return ret, nil } @@ -104,6 +110,7 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { if call.Argument(0).ToObject(call.VM).ClassName() != "String" { return nil, fmt.Errorf("first argument must be the wallet URL to open") } + wallet := call.Argument(0) var passwd goja.Value @@ -117,6 +124,7 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { if !callable { return nil, fmt.Errorf("jeth.openWallet is not callable") } + val, err := openWallet(goja.Null(), wallet, passwd) if err == nil { return val, nil @@ -129,6 +137,7 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { if err == nil { return val, nil } + val, err = b.readPassphraseAndReopenWallet(call) if err != nil { return nil, err @@ -140,6 +149,7 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { if err != nil { return nil, err } + passwd = call.VM.ToValue(input) if val, err = openWallet(goja.Null(), wallet, passwd); err != nil { if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) { @@ -150,6 +160,7 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { if err != nil { return nil, err } + if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { return nil, err } @@ -158,15 +169,19 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { case strings.HasSuffix(err.Error(), scwallet.ErrPINUnblockNeeded.Error()): // PIN unblock requested, fetch PUK and new PIN from the user var pukpin string + input, err := b.prompter.PromptPassword("Please enter current PUK: ") if err != nil { return nil, err } + pukpin = input + input, err = b.prompter.PromptPassword("Please enter new PIN: ") if err != nil { return nil, err } + pukpin += input if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(pukpin)); err != nil { @@ -179,6 +194,7 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { if err != nil { return nil, err } + if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { return nil, err } @@ -187,19 +203,23 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { // Unknown error occurred, drop to the user return nil, err } + return val, nil } func (b *bridge) readPassphraseAndReopenWallet(call jsre.Call) (goja.Value, error) { wallet := call.Argument(0) + input, err := b.prompter.PromptPassword("Please enter your passphrase: ") if err != nil { return nil, err } + openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) if !callable { return nil, fmt.Errorf("jeth.openWallet is not callable") } + return openWallet(goja.Null(), wallet, call.VM.ToValue(input)) } @@ -217,10 +237,12 @@ func (b *bridge) readPinAndReopenWallet(call jsre.Call) (goja.Value, error) { if err != nil { return nil, err } + openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) if !callable { return nil, fmt.Errorf("jeth.openWallet is not callable") } + return openWallet(goja.Null(), wallet, call.VM.ToValue(input)) } @@ -241,26 +263,32 @@ func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) { // If password is not given or is the null value, prompt the user for it. var passwd goja.Value + if goja.IsUndefined(call.Argument(1)) || goja.IsNull(call.Argument(1)) { fmt.Fprintf(b.printer, "Unlock account %s\n", account) + input, err := b.prompter.PromptPassword("Passphrase: ") if err != nil { return nil, err } + passwd = call.VM.ToValue(input) } else { if call.Argument(1).ExportType().Kind() != reflect.String { return nil, fmt.Errorf("password must be a string") } + passwd = call.Argument(1) } // Third argument is the duration how long the account should be unlocked. duration := goja.Null() + if !goja.IsUndefined(call.Argument(2)) && !goja.IsNull(call.Argument(2)) { if !isNumber(call.Argument(2)) { return nil, fmt.Errorf("unlock duration must be a number") } + duration = call.Argument(2) } @@ -269,6 +297,7 @@ func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) { if !callable { return nil, fmt.Errorf("jeth.unlockAccount is not callable") } + return unlockAccount(goja.Null(), account, passwd, duration) } @@ -279,6 +308,7 @@ func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { if nArgs := len(call.Arguments); nArgs < 2 { return nil, fmt.Errorf("usage: sign(message, account, [ password ])") } + var ( message = call.Argument(0) account = call.Argument(1) @@ -288,6 +318,7 @@ func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { if goja.IsUndefined(message) || message.ExportType().Kind() != reflect.String { return nil, fmt.Errorf("first argument must be the message to sign") } + if goja.IsUndefined(account) || account.ExportType().Kind() != reflect.String { return nil, fmt.Errorf("second argument must be the account to sign with") } @@ -295,10 +326,12 @@ func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { // if the password is not given or null ask the user and ensure password is a string if goja.IsUndefined(passwd) || goja.IsNull(passwd) { fmt.Fprintf(b.printer, "Give password for account %s\n", account) + input, err := b.prompter.PromptPassword("Password: ") if err != nil { return nil, err } + passwd = call.VM.ToValue(input) } else if passwd.ExportType().Kind() != reflect.String { return nil, fmt.Errorf("third argument must be the password to unlock the account") @@ -309,6 +342,7 @@ func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { if !callable { return nil, fmt.Errorf("jeth.sign is not callable") } + return sign(goja.Null(), message, account, passwd) } @@ -317,12 +351,15 @@ func (b *bridge) Sleep(call jsre.Call) (goja.Value, error) { if nArgs := len(call.Arguments); nArgs < 1 { return nil, fmt.Errorf("usage: sleep()") } + sleepObj := call.Argument(0) if goja.IsUndefined(sleepObj) || goja.IsNull(sleepObj) || !isNumber(sleepObj) { return nil, fmt.Errorf("usage: sleep()") } + sleep := sleepObj.ToFloat() time.Sleep(time.Duration(sleep * float64(time.Second))) + return call.VM.ToValue(true), nil } @@ -334,43 +371,54 @@ func (b *bridge) SleepBlocks(call jsre.Call) (goja.Value, error) { blocks = int64(0) sleep = int64(9999999999999999) // indefinitely ) + nArgs := len(call.Arguments) if nArgs == 0 { return nil, fmt.Errorf("usage: sleepBlocks([, max sleep in seconds])") } + if nArgs >= 1 { if goja.IsNull(call.Argument(0)) || goja.IsUndefined(call.Argument(0)) || !isNumber(call.Argument(0)) { return nil, fmt.Errorf("expected number as first argument") } + blocks = call.Argument(0).ToInteger() } + if nArgs >= 2 { if goja.IsNull(call.Argument(1)) || goja.IsUndefined(call.Argument(1)) || !isNumber(call.Argument(1)) { return nil, fmt.Errorf("expected number as second argument") } + sleep = call.Argument(1).ToInteger() } // Poll the current block number until either it or a timeout is reached. deadline := time.Now().Add(time.Duration(sleep) * time.Second) + var lastNumber hexutil.Uint64 if err := b.client.Call(&lastNumber, "eth_blockNumber"); err != nil { return nil, err } + for time.Now().Before(deadline) { var number hexutil.Uint64 if err := b.client.Call(&number, "eth_blockNumber"); err != nil { return nil, err } + if number != lastNumber { lastNumber = number blocks-- } + if blocks <= 0 { break } + time.Sleep(time.Second) } + return call.VM.ToValue(true), nil } @@ -394,9 +442,12 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) { reqs []jsonrpcCall batch bool ) + dec.UseNumber() // avoid float64s + if rawReq[0] == '[' { batch = true + dec.Decode(&reqs) } else { batch = false @@ -406,6 +457,7 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) { // Execute the requests. var resps []*goja.Object + for _, req := range reqs { resp := call.VM.NewObject() resp.Set("jsonrpc", "2.0") @@ -419,10 +471,12 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) { resp.Set("result", goja.Null()) } else { JSON := call.VM.Get("JSON").ToObject(call.VM) + parse, callable := goja.AssertFunction(JSON.Get("parse")) if !callable { return nil, fmt.Errorf("JSON.parse is not a function") } + resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result))) if err != nil { setError(resp, -32603, err.Error(), nil) @@ -432,15 +486,20 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) { } } else { code := -32603 + var data interface{} + if err, ok := err.(rpc.Error); ok { code = err.ErrorCode() } + if err, ok := err.(rpc.DataError); ok { data = err.ErrorData() } + setError(resp, code, err.Error(), data) } + resps = append(resps, resp) } // Return the responses either to the callback (if supplied) @@ -451,10 +510,12 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) { } else { result = resps[0] } + if fn, isFunc := goja.AssertFunction(call.Argument(1)); isFunc { fn(goja.Null(), goja.Null(), result) return goja.Undefined(), nil } + return result, nil } @@ -462,9 +523,11 @@ func setError(resp *goja.Object, code int, msg string, data interface{}) { err := make(map[string]interface{}) err["code"] = code err["message"] = msg + if data != nil { err["data"] = data } + resp.Set("error", err) } @@ -479,5 +542,6 @@ func getObject(vm *goja.Runtime, name string) *goja.Object { if v == nil { return nil } + return v.ToObject(vm) } diff --git a/console/console.go b/console/console.go index 80950bd60a..b07ea2b8f9 100644 --- a/console/console.go +++ b/console/console.go @@ -92,9 +92,11 @@ func New(config Config) (*Console, error) { if config.Prompter == nil { config.Prompter = prompt.Stdin } + if config.Prompt == "" { config.Prompt = DefaultPrompt } + if config.Printer == nil { config.Printer = colorable.NewColorableStdout() } @@ -112,9 +114,11 @@ func New(config Config) (*Console, error) { signalReceived: make(chan struct{}, 1), stopped: make(chan struct{}), } + if err := os.MkdirAll(config.DataDir, 0700); err != nil { return nil, err } + if err := console.init(config.Preload); err != nil { return nil, err } @@ -135,6 +139,7 @@ func (c *Console) init(preload []string) error { if err := c.initWeb3(bridge); err != nil { return err } + if err := c.initExtensions(); err != nil { return err } @@ -152,6 +157,7 @@ func (c *Console) init(preload []string) error { if gojaErr, ok := err.(*goja.Exception); ok { failure = gojaErr.String() } + return fmt.Errorf("%s: %v", path, failure) } } @@ -164,8 +170,10 @@ func (c *Console) init(preload []string) error { c.history = strings.Split(string(content), "\n") c.prompter.SetHistory(c.history) } + c.prompter.SetWordCompleter(c.AutoCompleteInput) } + return nil } @@ -186,10 +194,13 @@ func (c *Console) initWeb3(bridge *bridge) error { if err := c.jsre.Compile("web3.js", deps.Web3JS); err != nil { return fmt.Errorf("web3.js: %v", err) } + if _, err := c.jsre.Run("var Web3 = require('web3');"); err != nil { return fmt.Errorf("web3 require: %v", err) } + var err error + c.jsre.Do(func(vm *goja.Runtime) { transport := vm.NewObject() transport.Set("send", jsre.MakeCallback(vm, bridge.Send)) @@ -197,6 +208,7 @@ func (c *Console) initWeb3(bridge *bridge) error { vm.Set("_consoleWeb3Transport", transport) _, err = vm.RunString("var web3 = new Web3(_consoleWeb3Transport)") }) + return err } @@ -205,6 +217,7 @@ var defaultAPIs = map[string]string{"eth": "1.0", "net": "1.0", "debug": "1.0"} // initExtensions loads and registers web3.js extensions. func (c *Console) initExtensions() error { const methodNotFound = -32601 + apis, err := c.client.SupportedModules() if err != nil { if rpcErr, ok := err.(rpc.Error); ok && rpcErr.ErrorCode() == methodNotFound { @@ -218,11 +231,14 @@ func (c *Console) initExtensions() error { // Compute aliases from server-provided modules. aliases := map[string]struct{}{"eth": {}} + for api := range apis { if api == "web3" { continue } + aliases[api] = struct{}{} + if file, ok := web3ext.Modules[api]; ok { if err = c.jsre.Compile(api+".js", file); err != nil { return fmt.Errorf("%s.js: %v", api, err) @@ -239,6 +255,7 @@ func (c *Console) initExtensions() error { } } }) + return nil } @@ -264,6 +281,7 @@ func (c *Console) initPersonal(vm *goja.Runtime, bridge *bridge) { } log.Warn("Enabling deprecated personal namespace") + jeth := vm.NewObject() vm.Set("jeth", jeth) jeth.Set("openWallet", personal.Get("openWallet")) @@ -279,6 +297,7 @@ func (c *Console) initPersonal(vm *goja.Runtime, bridge *bridge) { func (c *Console) clearHistory() { c.history = nil c.prompter.ClearHistory() + if err := os.Remove(c.histPath); err != nil { fmt.Fprintln(c.printer, "can't delete history file:", err) } else { @@ -293,7 +312,9 @@ func (c *Console) consoleOutput(call goja.FunctionCall) goja.Value { for _, argument := range call.Arguments { output = append(output, fmt.Sprintf("%v", argument)) } + fmt.Fprintln(c.printer, strings.Join(output, " ")) + return goja.Null() } @@ -315,8 +336,10 @@ func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, str } // We've hit an unexpected character, autocomplete form here start++ + break } + return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:] } @@ -345,9 +368,11 @@ func (c *Console) Welcome() { for api, version := range apis { modules = append(modules, fmt.Sprintf("%s:%s", api, version)) } + sort.Strings(modules) message += " modules: " + strings.Join(modules, " ") + "\n" } + message += "\nTo exit, press ctrl-d or type exit" fmt.Fprintln(c.printer, message) } @@ -460,6 +485,7 @@ func (c *Console) Interactive() { prompt, indents, input = c.prompt, 0, "" continue } + return case line := <-inputLine: @@ -467,11 +493,13 @@ func (c *Console) Interactive() { if indents <= 0 && exit.MatchString(line) { return } + if onlyWhitespace.MatchString(line) { continue } // Append the line to the input and check for multi-line interpretation. input += line + "\n" + indents = countIndents(input) if indents <= 0 { prompt = c.prompt @@ -488,6 +516,7 @@ func (c *Console) Interactive() { } } } + c.Evaluate(input) input = "" } @@ -531,16 +560,19 @@ func countIndents(input string) int { inString = true strOpenChar = c } + charEscaped = false case '{', '(': if !inString { // ignore brackets when in string, allow var str = "a{"; without indenting indents++ } + charEscaped = false case '}', ')': if !inString { indents-- } + charEscaped = false default: charEscaped = false @@ -559,6 +591,7 @@ func (c *Console) Stop(graceful bool) error { }) c.jsre.Stop(graceful) + return nil } @@ -566,5 +599,6 @@ func (c *Console) writeHistory() error { if err := os.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil { return err } + return os.Chmod(c.histPath, 0600) // Force 0600, even if it was different previously } diff --git a/console/console_test.go b/console/console_test.go index 35341fcba0..4252f2e3cb 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -94,6 +94,7 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { if err != nil { t.Fatalf("failed to create node: %v", err) } + ethConf := ðconfig.Config{ Genesis: core.DeveloperGenesisBlock(15, 11_500_000, common.Address{}), Miner: miner.Config{ @@ -106,6 +107,7 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { if confOverride != nil { confOverride(ethConf) } + ethBackend, err := eth.New(stack, ethConf) if err != nil { t.Fatalf("failed to register Ethereum protocol: %v", err) @@ -114,10 +116,12 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { if err = stack.Start(); err != nil { t.Fatalf("failed to start test stack: %v", err) } + client, err := stack.Attach() if err != nil { t.Fatalf("failed to attach to node: %v", err) } + prompter := &hookedPrompter{scheduler: make(chan string)} printer := new(bytes.Buffer) @@ -148,9 +152,11 @@ func (env *tester) Close(t *testing.T) { if err := env.console.Stop(false); err != nil { t.Errorf("failed to stop embedded console: %v", err) } + if err := env.stack.Close(); err != nil { t.Errorf("failed to tear down embedded node: %v", err) } + os.RemoveAll(env.workspace) } @@ -167,15 +173,19 @@ func TestWelcome(t *testing.T) { if want := "Welcome"; !strings.Contains(output, want) { t.Fatalf("console output missing welcome message: have\n%s\nwant also %s", output, want) } + if want := fmt.Sprintf("instance: %s", testInstance); !strings.Contains(output, want) { t.Fatalf("console output missing instance: have\n%s\nwant also %s", output, want) } + if want := fmt.Sprintf("coinbase: %s", testAddress); !strings.Contains(output, want) { t.Fatalf("console output missing coinbase: have\n%s\nwant also %s", output, want) } + if want := "at block: 0"; !strings.Contains(output, want) { t.Fatalf("console output missing sync status: have\n%s\nwant also %s", output, want) } + if want := fmt.Sprintf("datadir: %s", tester.workspace); !strings.Contains(output, want) { t.Fatalf("console output missing coinbase: have\n%s\nwant also %s", output, want) } @@ -187,6 +197,7 @@ func TestEvaluate(t *testing.T) { defer tester.Close(t) tester.console.Evaluate("2 + 2") + if output := tester.output.String(); !strings.Contains(output, "4") { t.Fatalf("statement evaluation failed: have %s, want %s", output, "4") } @@ -217,6 +228,7 @@ func TestInteractive(t *testing.T) { case <-time.After(time.Second): t.Fatalf("secondary prompt timeout") } + if output := tester.output.String(); !strings.Contains(output, "4") { t.Fatalf("statement evaluation failed: have %s, want %s", output, "4") } @@ -229,6 +241,7 @@ func TestPreload(t *testing.T) { defer tester.Close(t) tester.console.Evaluate("preloaded") + if output := tester.output.String(); !strings.Contains(output, "some-preloaded-string") { t.Fatalf("preloaded variable missing: have %s, want %s", output, "some-preloaded-string") } diff --git a/console/prompt/prompter.go b/console/prompt/prompter.go index 2a20b6906a..4ec465e80b 100644 --- a/console/prompt/prompter.go +++ b/console/prompt/prompter.go @@ -87,6 +87,7 @@ func newTerminalPrompter() *terminalPrompter { // Turn on liner. It switches to raw mode. p.State = liner.NewLiner() rawMode, err := liner.TerminalMode() + if err != nil || !liner.TerminalSupported() { p.supported = false } else { @@ -96,9 +97,11 @@ func newTerminalPrompter() *terminalPrompter { // Switch back to normal mode while we're not prompting. normalMode.ApplyMode() } + p.SetCtrlCAborts(true) p.SetTabCompletionStyle(liner.TabPrints) p.SetMultiLineMode(true) + return p } @@ -116,6 +119,7 @@ func (p *terminalPrompter) PromptInput(prompt string) (string, error) { prompt = "" defer fmt.Println() } + return p.State.Prompt(prompt) } @@ -126,16 +130,22 @@ func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err err if p.supported { p.rawMode.ApplyMode() defer p.normalMode.ApplyMode() + return p.State.PasswordPrompt(prompt) } + if !p.warned { fmt.Println("!! Unsupported terminal, password will be echoed.") + p.warned = true } // Just as in Prompt, handle printing the prompt here instead of relying on liner. fmt.Print(prompt) + passwd, err = p.State.Prompt("") + fmt.Println() + return passwd, err } @@ -146,6 +156,7 @@ func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) { if len(input) > 0 && strings.EqualFold(input[:1], "y") { return true, nil } + return false, err } diff --git a/contracts/checkpointoracle/contract/oracle.go b/contracts/checkpointoracle/contract/oracle.go index a4a308f5c5..1cfae94dbb 100644 --- a/contracts/checkpointoracle/contract/oracle.go +++ b/contracts/checkpointoracle/contract/oracle.go @@ -50,6 +50,7 @@ func DeployCheckpointOracle(auth *bind.TransactOpts, backend bind.ContractBacken if err != nil { return common.Address{}, nil, nil, err } + return address, tx, &CheckpointOracle{CheckpointOracleCaller: CheckpointOracleCaller{contract: contract}, CheckpointOracleTransactor: CheckpointOracleTransactor{contract: contract}, CheckpointOracleFilterer: CheckpointOracleFilterer{contract: contract}}, nil } @@ -118,6 +119,7 @@ func NewCheckpointOracle(address common.Address, backend bind.ContractBackend) ( if err != nil { return nil, err } + return &CheckpointOracle{CheckpointOracleCaller: CheckpointOracleCaller{contract: contract}, CheckpointOracleTransactor: CheckpointOracleTransactor{contract: contract}, CheckpointOracleFilterer: CheckpointOracleFilterer{contract: contract}}, nil } @@ -127,6 +129,7 @@ func NewCheckpointOracleCaller(address common.Address, caller bind.ContractCalle if err != nil { return nil, err } + return &CheckpointOracleCaller{contract: contract}, nil } @@ -136,6 +139,7 @@ func NewCheckpointOracleTransactor(address common.Address, transactor bind.Contr if err != nil { return nil, err } + return &CheckpointOracleTransactor{contract: contract}, nil } @@ -145,6 +149,7 @@ func NewCheckpointOracleFilterer(address common.Address, filterer bind.ContractF if err != nil { return nil, err } + return &CheckpointOracleFilterer{contract: contract}, nil } @@ -154,6 +159,7 @@ func bindCheckpointOracle(address common.Address, caller bind.ContractCaller, tr if err != nil { return nil, err } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil } @@ -209,7 +215,6 @@ func (_CheckpointOracle *CheckpointOracleCaller) GetAllAdmin(opts *bind.CallOpts out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) return out0, err - } // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. @@ -242,7 +247,6 @@ func (_CheckpointOracle *CheckpointOracleCaller) GetLatestCheckpoint(opts *bind. out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) return out0, out1, out2, err - } // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. @@ -310,7 +314,9 @@ func (it *CheckpointOracleNewCheckpointVoteIterator) Next() bool { it.fail = err return false } + it.Event.Raw = log + return true default: @@ -325,12 +331,15 @@ func (it *CheckpointOracleNewCheckpointVoteIterator) Next() bool { it.fail = err return false } + it.Event.Raw = log + return true case err := <-it.sub.Err(): it.done = true it.fail = err + return it.Next() } } @@ -361,7 +370,6 @@ type CheckpointOracleNewCheckpointVote struct { // // Solidity: event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s) func (_CheckpointOracle *CheckpointOracleFilterer) FilterNewCheckpointVote(opts *bind.FilterOpts, index []uint64) (*CheckpointOracleNewCheckpointVoteIterator, error) { - var indexRule []interface{} for _, indexItem := range index { indexRule = append(indexRule, indexItem) @@ -371,6 +379,7 @@ func (_CheckpointOracle *CheckpointOracleFilterer) FilterNewCheckpointVote(opts if err != nil { return nil, err } + return &CheckpointOracleNewCheckpointVoteIterator{contract: _CheckpointOracle.contract, event: "NewCheckpointVote", logs: logs, sub: sub}, nil } @@ -378,7 +387,6 @@ func (_CheckpointOracle *CheckpointOracleFilterer) FilterNewCheckpointVote(opts // // Solidity: event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s) func (_CheckpointOracle *CheckpointOracleFilterer) WatchNewCheckpointVote(opts *bind.WatchOpts, sink chan<- *CheckpointOracleNewCheckpointVote, index []uint64) (event.Subscription, error) { - var indexRule []interface{} for _, indexItem := range index { indexRule = append(indexRule, indexItem) @@ -388,8 +396,10 @@ func (_CheckpointOracle *CheckpointOracleFilterer) WatchNewCheckpointVote(opts * if err != nil { return nil, err } + return event.NewSubscription(func(quit <-chan struct{}) error { defer sub.Unsubscribe() + for { select { case log := <-logs: @@ -398,6 +408,7 @@ func (_CheckpointOracle *CheckpointOracleFilterer) WatchNewCheckpointVote(opts * if err := _CheckpointOracle.contract.UnpackLog(event, "NewCheckpointVote", log); err != nil { return err } + event.Raw = log select { @@ -424,5 +435,6 @@ func (_CheckpointOracle *CheckpointOracleFilterer) ParseNewCheckpointVote(log ty if err := _CheckpointOracle.contract.UnpackLog(event, "NewCheckpointVote", log); err != nil { return nil, err } + return event, nil } diff --git a/contracts/checkpointoracle/oracle.go b/contracts/checkpointoracle/oracle.go index dec01db244..319b679e9f 100644 --- a/contracts/checkpointoracle/oracle.go +++ b/contracts/checkpointoracle/oracle.go @@ -42,6 +42,7 @@ func NewCheckpointOracle(contractAddr common.Address, backend bind.ContractBacke if err != nil { return nil, err } + return &CheckpointOracle{address: contractAddr, contract: c}, nil } @@ -66,11 +67,13 @@ func (oracle *CheckpointOracle) LookupCheckpointEvents(blockLogs [][]*types.Log, if err != nil { continue } + if event.Index == section && event.CheckpointHash == hash { votes = append(votes, event) } } } + return votes } @@ -85,13 +88,16 @@ func (oracle *CheckpointOracle) RegisterCheckpoint(opts *bind.TransactOpts, inde s [][32]byte v []uint8 ) + for i := 0; i < len(sigs); i++ { if len(sigs[i]) != 65 { return nil, errors.New("invalid signature") } + r = append(r, common.BytesToHash(sigs[i][:32])) s = append(s, common.BytesToHash(sigs[i][32:64])) v = append(v, sigs[i][64]) } + return oracle.contract.SetCheckpoint(opts, rnum, rhash, common.BytesToHash(hash), index, v, r, s) } diff --git a/contracts/checkpointoracle/oracle_test.go b/contracts/checkpointoracle/oracle_test.go index 61dd8aec79..d1e3324a02 100644 --- a/contracts/checkpointoracle/oracle_test.go +++ b/contracts/checkpointoracle/oracle_test.go @@ -76,6 +76,7 @@ func validateOperation(t *testing.T, c *contract.CheckpointOracle, backend *back sink = make(chan *contract.CheckpointOracleNewCheckpointVote) sub, _ = c.WatchNewCheckpointVote(nil, sink, nil) ) + defer func() { // Close all subscribers sub.Unsubscribe() @@ -84,6 +85,7 @@ func validateOperation(t *testing.T, c *contract.CheckpointOracle, backend *back // flush pending block backend.Commit() + if err := assert(sink); err != nil { t.Errorf("operation {%s} failed, err %s", opName, err) } @@ -93,26 +95,35 @@ func validateOperation(t *testing.T, c *contract.CheckpointOracle, backend *back // fired by contract backend. func validateEvents(target int, sink interface{}) (bool, []reflect.Value) { chanval := reflect.ValueOf(sink) + chantyp := chanval.Type() if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.RecvDir == 0 { return false, nil } + count := 0 + var recv []reflect.Value + timeout := time.After(1 * time.Second) cases := []reflect.SelectCase{{Chan: chanval, Dir: reflect.SelectRecv}, {Chan: reflect.ValueOf(timeout), Dir: reflect.SelectRecv}} + for { chose, v, _ := reflect.Select(cases) if chose == 1 { // Not enough event received return false, nil } + count += 1 + recv = append(recv, v) + if count == target { break } } + done := time.After(50 * time.Millisecond) cases = cases[:1] cases = append(cases, reflect.SelectCase{Chan: reflect.ValueOf(done), Dir: reflect.SelectRecv}) @@ -137,6 +148,7 @@ func signCheckpoint(addr common.Address, privateKey *ecdsa.PrivateKey, index uin data := append([]byte{0x19, 0x00}, append(addr.Bytes(), append(buf, hash.Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), privateKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + return sig } @@ -145,12 +157,16 @@ func assertSignature(addr common.Address, index uint64, hash [32]byte, r, s [32] buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, index) data := append([]byte{0x19, 0x00}, append(addr.Bytes(), append(buf, hash[:]...)...)...) + pubkey, err := crypto.Ecrecover(crypto.Keccak256(data), append(r[:], append(s[:], v-27)...)) if err != nil { return false } + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + return bytes.Equal(signer.Bytes(), expect.Bytes()) } @@ -167,6 +183,7 @@ func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), func TestCheckpointRegister(t *testing.T) { // Initialize test accounts var accounts Accounts + for i := 0; i < 3; i++ { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -191,12 +208,14 @@ func TestCheckpointRegister(t *testing.T) { if err != nil { t.Error("Failed to deploy registrar contract", err) } + contractBackend.Commit() // getRecent returns block height and hash of the head parent. getRecent := func() (*big.Int, common.Hash) { parentNumber := new(big.Int).Sub(contractBackend.Blockchain().CurrentHeader().Number, big.NewInt(1)) parentHash := contractBackend.Blockchain().CurrentHeader().ParentHash + return parentNumber, parentHash } // collectSig generates specified number signatures. @@ -206,10 +225,12 @@ func TestCheckpointRegister(t *testing.T) { if unauthorized != nil { sig = signCheckpoint(contractAddr, unauthorized, index, hash) } + r = append(r, common.BytesToHash(sig[:32])) s = append(s, common.BytesToHash(sig[32:64])) v = append(v, sig[64]) } + return v, r, s } // insertEmptyBlocks inserts a batch of empty blocks to blockchain. @@ -225,15 +246,19 @@ func TestCheckpointRegister(t *testing.T) { if err != nil { return err } + if lindex != index { return errors.New("latest checkpoint index mismatch") } + if !bytes.Equal(lhash[:], hash[:]) { return errors.New("latest checkpoint hash mismatch") } + if lheight.Cmp(height) != 0 { return errors.New("latest checkpoint height mismatch") } + return nil } @@ -293,7 +318,9 @@ func TestCheckpointRegister(t *testing.T) { } } } + number, _ := getRecent() + return assert(0, checkpoint0.Hash(), number.Add(number, big.NewInt(1))) }, "test valid checkpoint registration") @@ -316,7 +343,9 @@ func TestCheckpointRegister(t *testing.T) { } } } + number, _ := getRecent() + return assert(2, checkpoint2.Hash(), number.Add(number, big.NewInt(1))) }, "test uncontinuous checkpoint registration") diff --git a/core/asm/asm.go b/core/asm/asm.go index 7c1e14ec01..29ff8dd55f 100644 --- a/core/asm/asm.go +++ b/core/asm/asm.go @@ -38,6 +38,7 @@ type instructionIterator struct { func NewInstructionIterator(code []byte) *instructionIterator { it := new(instructionIterator) it.code = code + return it } @@ -53,6 +54,7 @@ func (it *instructionIterator) Next() bool { if it.arg != nil { it.pc += uint64(len(it.arg)) } + it.pc++ } else { // We start the iteration from the first instruction. @@ -67,15 +69,18 @@ func (it *instructionIterator) Next() bool { it.op = vm.OpCode(it.code[it.pc]) if it.op.IsPush() { a := uint64(it.op) - uint64(vm.PUSH1) + 1 + u := it.pc + 1 + a if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u { it.error = fmt.Errorf("incomplete push instruction at %v", it.pc) return false } + it.arg = it.code[it.pc+1 : u] } else { it.arg = nil } + return true } @@ -114,6 +119,7 @@ func PrintDisassembled(code string) error { fmt.Printf("%05x: %v\n", it.PC(), it.Op()) } } + return it.Error() } @@ -129,8 +135,10 @@ func Disassemble(script []byte) ([]string, error) { instrs = append(instrs, fmt.Sprintf("%05x: %v\n", it.PC(), it.Op())) } } + if err := it.Error(); err != nil { return nil, err } + return instrs, nil } diff --git a/core/asm/asm_test.go b/core/asm/asm_test.go index 92b26b67a5..a5e94f61ca 100644 --- a/core/asm/asm_test.go +++ b/core/asm/asm_test.go @@ -35,6 +35,7 @@ func TestInstructionIteratorValid(t *testing.T) { if err := it.Error(); err != nil { t.Errorf("Expected 2, but encountered error %v instead.", err) } + if cnt != 2 { t.Errorf("Expected 2, but got %v instead.", cnt) } @@ -68,6 +69,7 @@ func TestInstructionIteratorEmpty(t *testing.T) { if err := it.Error(); err != nil { t.Errorf("Expected 0, but encountered error %v instead.", err) } + if cnt != 0 { t.Errorf("Expected 0, but got %v instead.", cnt) } diff --git a/core/asm/compiler.go b/core/asm/compiler.go index 050dc6233f..75ad8efb1e 100644 --- a/core/asm/compiler.go +++ b/core/asm/compiler.go @@ -58,6 +58,7 @@ func NewCompiler(debug bool) *Compiler { // position. func (c *Compiler) Feed(ch <-chan token) { var prev token + for i := range ch { switch i.typ { case number: @@ -65,6 +66,7 @@ func (c *Compiler) Feed(ch <-chan token) { if len(num) == 0 { num = []byte{0} } + c.pc += len(num) case stringValue: c.pc += len(i.text) - 2 @@ -83,6 +85,7 @@ func (c *Compiler) Feed(ch <-chan token) { c.tokens = append(c.tokens, i) prev = i } + if c.debug { fmt.Fprintln(os.Stderr, "found", len(c.labels), "labels") } @@ -106,6 +109,7 @@ func (c *Compiler) Compile() (string, []error) { // turn the binary to hex var bin strings.Builder + for _, v := range c.binary { switch v := v.(type) { case vm.OpCode: @@ -123,6 +127,7 @@ func (c *Compiler) Compile() (string, []error) { func (c *Compiler) next() token { token := c.tokens[c.pos] c.pos++ + return token } @@ -163,7 +168,9 @@ func (c *Compiler) compileNumber(element token) (int, error) { if len(num) == 0 { num = []byte{0} } + c.pushBin(num) + return len(num), nil } @@ -194,6 +201,7 @@ func (c *Compiler) compileElement(element token) error { } // push the operation c.pushBin(toBinary(element.text)) + return nil } else if isPush(element.text) { // handle pushes. pushes are read from left to right. @@ -238,6 +246,7 @@ func (c *Compiler) pushBin(v interface{}) { if c.debug { fmt.Printf("%d: %v\n", len(c.binary), v) } + c.binary = append(c.binary, v) } diff --git a/core/asm/compiler_test.go b/core/asm/compiler_test.go index ce9df436bd..fa939b650a 100644 --- a/core/asm/compiler_test.go +++ b/core/asm/compiler_test.go @@ -59,11 +59,13 @@ func TestCompiler(t *testing.T) { ch := Lex([]byte(test.input), false) c := NewCompiler(false) c.Feed(ch) + output, err := c.Compile() if len(err) != 0 { t.Errorf("compile error: %v\ninput: %s", err, test.input) continue } + if output != test.output { t.Errorf("incorrect output\ninput: %sgot: %s\nwant: %s\n", test.input, output, test.output) } diff --git a/core/asm/lex_test.go b/core/asm/lex_test.go index 53e05fbbba..54e2a86255 100644 --- a/core/asm/lex_test.go +++ b/core/asm/lex_test.go @@ -28,6 +28,7 @@ func lexAll(src string) []token { for i := range ch { tokens = append(tokens, i) } + return tokens } diff --git a/core/asm/lexer.go b/core/asm/lexer.go index d1b79a1fb9..305bd9834c 100644 --- a/core/asm/lexer.go +++ b/core/asm/lexer.go @@ -63,6 +63,7 @@ func (it tokenType) String() string { if int(it) > len(stringtokenTypes) { return "invalid" } + return stringtokenTypes[it] } @@ -103,8 +104,10 @@ func Lex(source []byte, debug bool) <-chan token { state: lexLine, debug: debug, } + go func() { l.emit(lineStart) + for l.state != nil { l.state = l.state(l) } @@ -121,8 +124,10 @@ func (l *lexer) next() (rune rune) { l.width = 0 return 0 } + rune, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) l.pos += l.width + return rune } @@ -135,6 +140,7 @@ func (l *lexer) backup() { func (l *lexer) peek() rune { r := l.next() l.backup() + return r } @@ -199,6 +205,7 @@ func lexLine(l *lexer) stateFn { case r == '\n': l.emit(lineEnd) l.ignore() + l.lineno++ l.emit(lineStart) @@ -257,6 +264,7 @@ func lexNumber(l *lexer) stateFn { if l.accept("xX") { acceptance = HexadecimalNumbers } + l.acceptRun(acceptance) l.emit(number) @@ -275,6 +283,7 @@ func lexElement(l *lexer) stateFn { } else { l.emit(element) } + return lexLine } diff --git a/core/beacon/gen_blockparams.go b/core/beacon/gen_blockparams.go index 0e2ea4bb13..0b721e8b8c 100644 --- a/core/beacon/gen_blockparams.go +++ b/core/beacon/gen_blockparams.go @@ -19,10 +19,12 @@ func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) { Random common.Hash `json:"prevRandao" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` } + var enc PayloadAttributesV1 enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Random = p.Random enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient + return json.Marshal(&enc) } @@ -33,21 +35,29 @@ func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error { Random *common.Hash `json:"prevRandao" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` } + var dec PayloadAttributesV1 if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Timestamp == nil { return errors.New("missing required field 'timestamp' for PayloadAttributesV1") } + p.Timestamp = uint64(*dec.Timestamp) + if dec.Random == nil { return errors.New("missing required field 'prevRandao' for PayloadAttributesV1") } + p.Random = *dec.Random + if dec.SuggestedFeeRecipient == nil { return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributesV1") } + p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient + return nil } diff --git a/core/beacon/gen_ed.go b/core/beacon/gen_ed.go index dcee3bf18c..c8dd815b7e 100644 --- a/core/beacon/gen_ed.go +++ b/core/beacon/gen_ed.go @@ -31,6 +31,7 @@ func (e ExecutableDataV1) MarshalJSON() ([]byte, error) { BlockHash common.Hash `json:"blockHash" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` } + var enc ExecutableDataV1 enc.ParentHash = e.ParentHash enc.FeeRecipient = e.FeeRecipient @@ -45,12 +46,14 @@ func (e ExecutableDataV1) MarshalJSON() ([]byte, error) { enc.ExtraData = e.ExtraData enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas) enc.BlockHash = e.BlockHash + if e.Transactions != nil { enc.Transactions = make([]hexutil.Bytes, len(e.Transactions)) for k, v := range e.Transactions { enc.Transactions[k] = v } } + return json.Marshal(&enc) } @@ -72,68 +75,98 @@ func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error { BlockHash *common.Hash `json:"blockHash" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` } + var dec ExecutableDataV1 if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.ParentHash == nil { return errors.New("missing required field 'parentHash' for ExecutableDataV1") } + e.ParentHash = *dec.ParentHash + if dec.FeeRecipient == nil { return errors.New("missing required field 'feeRecipient' for ExecutableDataV1") } + e.FeeRecipient = *dec.FeeRecipient + if dec.StateRoot == nil { return errors.New("missing required field 'stateRoot' for ExecutableDataV1") } + e.StateRoot = *dec.StateRoot + if dec.ReceiptsRoot == nil { return errors.New("missing required field 'receiptsRoot' for ExecutableDataV1") } + e.ReceiptsRoot = *dec.ReceiptsRoot + if dec.LogsBloom == nil { return errors.New("missing required field 'logsBloom' for ExecutableDataV1") } + e.LogsBloom = *dec.LogsBloom + if dec.Random == nil { return errors.New("missing required field 'prevRandao' for ExecutableDataV1") } + e.Random = *dec.Random + if dec.Number == nil { return errors.New("missing required field 'blockNumber' for ExecutableDataV1") } + e.Number = uint64(*dec.Number) + if dec.GasLimit == nil { return errors.New("missing required field 'gasLimit' for ExecutableDataV1") } + e.GasLimit = uint64(*dec.GasLimit) + if dec.GasUsed == nil { return errors.New("missing required field 'gasUsed' for ExecutableDataV1") } + e.GasUsed = uint64(*dec.GasUsed) + if dec.Timestamp == nil { return errors.New("missing required field 'timestamp' for ExecutableDataV1") } + e.Timestamp = uint64(*dec.Timestamp) + if dec.ExtraData == nil { return errors.New("missing required field 'extraData' for ExecutableDataV1") } + e.ExtraData = *dec.ExtraData + if dec.BaseFeePerGas == nil { return errors.New("missing required field 'baseFeePerGas' for ExecutableDataV1") } + e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas) + if dec.BlockHash == nil { return errors.New("missing required field 'blockHash' for ExecutableDataV1") } + e.BlockHash = *dec.BlockHash + if dec.Transactions == nil { return errors.New("missing required field 'transactions' for ExecutableDataV1") } + e.Transactions = make([][]byte, len(dec.Transactions)) for k, v := range dec.Transactions { e.Transactions[k] = v } + return nil } diff --git a/core/beacon/types.go b/core/beacon/types.go index 18d5d2ab78..30647f9d7f 100644 --- a/core/beacon/types.go +++ b/core/beacon/types.go @@ -100,6 +100,7 @@ func (b *PayloadID) UnmarshalText(input []byte) error { if err != nil { return fmt.Errorf("invalid payload id %q: %w", input, err) } + return nil } @@ -119,35 +120,43 @@ func encodeTransactions(txs []*types.Transaction) [][]byte { for i, tx := range txs { enc[i], _ = tx.MarshalBinary() } + return enc } func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { var txs = make([]*types.Transaction, len(enc)) + for i, encTx := range enc { var tx types.Transaction if err := tx.UnmarshalBinary(encTx); err != nil { return nil, fmt.Errorf("invalid transaction %d: %v", i, err) } + txs[i] = &tx } + return txs, nil } // ExecutableDataToBlock constructs a block from executable data. // It verifies that the following fields: -// len(extraData) <= 32 -// uncleHash = emptyUncleHash -// difficulty = 0 +// +// len(extraData) <= 32 +// uncleHash = emptyUncleHash +// difficulty = 0 +// // and that the blockhash of the constructed block matches the parameters. func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { txs, err := decodeTransactions(params.Transactions) if err != nil { return nil, err } + if len(params.ExtraData) > 32 { return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) } + header := &types.Header{ ParentHash: params.ParentHash, UncleHash: types.EmptyUncleHash, @@ -165,10 +174,12 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { Extra: params.ExtraData, MixDigest: params.Random, } + block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) if block.Hash() != params.BlockHash { return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) } + return block, nil } diff --git a/core/bench_test.go b/core/bench_test.go index 7b6b56222b..1f9e0f91e6 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -85,10 +85,12 @@ func genValueTx(nbytes int) func(int, *BlockGen) { data := make([]byte, nbytes) gas, _ := IntrinsicGas(data, nil, false, false, false, false) signer := types.MakeSigner(gen.config, big.NewInt(int64(i))) + gasPrice := big.NewInt(0) if gen.header.BaseFee != nil { gasPrice = gen.header.BaseFee } + tx, _ := types.SignNewTx(benchRootKey, signer, &types.LegacyTx{ Nonce: gen.TxNonce(benchRootAddr), To: &toaddr, @@ -109,6 +111,7 @@ var ( func init() { ringKeys[0] = benchRootKey ringAddrs[0] = benchRootAddr + for i := 1; i < len(ringKeys); i++ { ringKeys[i], _ = crypto.GenerateKey() ringAddrs[i] = crypto.PubkeyToAddress(ringKeys[i].PublicKey) @@ -121,26 +124,33 @@ func init() { func genTxRing(naccounts int) func(int, *BlockGen) { from := 0 availableFunds := new(big.Int).Set(benchRootFunds) + return func(i int, gen *BlockGen) { block := gen.PrevBlock(i - 1) gas := block.GasLimit() + gasPrice := big.NewInt(0) if gen.header.BaseFee != nil { gasPrice = gen.header.BaseFee } + signer := types.MakeSigner(gen.config, big.NewInt(int64(i))) + for { gas -= params.TxGas if gas < params.TxGas { break } + to := (from + 1) % naccounts burn := new(big.Int).SetUint64(params.TxGas) burn.Mul(burn, gen.header.BaseFee) availableFunds.Sub(availableFunds, burn) + if availableFunds.Cmp(big.NewInt(1)) < 0 { panic("not enough funds") } + tx, err := types.SignNewTx(ringKeys[from], signer, &types.LegacyTx{ Nonce: gen.TxNonce(ringAddrs[from]), @@ -152,7 +162,9 @@ func genTxRing(naccounts int) func(int, *BlockGen) { if err != nil { panic(err) } + gen.AddTx(tx) + from = to } } @@ -175,14 +187,17 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { var db ethdb.Database var err error + if !disk { db = rawdb.NewMemoryDatabase() } else { dir := b.TempDir() + db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "", false) if err != nil { b.Fatalf("cannot create temporary database: %v", err) } + defer db.Close() } @@ -200,6 +215,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { defer chainman.Stop() b.ReportAllocs() b.ResetTimer() + if i, err := chainman.InsertChain(chain); err != nil { b.Fatalf("insert error (block %d): %v\n", i, err) } @@ -281,9 +297,11 @@ func benchWriteChain(b *testing.B, full bool, count uint64) { for i := 0; i < b.N; i++ { dir := b.TempDir() db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) + if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } + makeChainForBench(db, full, count) db.Close() } @@ -296,8 +314,10 @@ func benchReadChain(b *testing.B, full bool, count uint64) { if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } + makeChainForBench(db, full, count) db.Close() + cacheConfig := *DefaultCacheConfig cacheConfig.TrieDirtyDisabled = true diff --git a/core/block_validator.go b/core/block_validator.go index bc5fc0b6c5..f445b3bb7c 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -43,6 +43,7 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin engine: engine, bc: blockchain, } + return validator } @@ -58,12 +59,15 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // Header validity is known at this point. Here we verify that uncles, transactions // and withdrawals given in the block body match the header. header := block.Header() + if err := v.engine.VerifyUncles(v.bc, block); err != nil { return err } + if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash { return fmt.Errorf("uncle root hash mismatch (header value %x, calculated %x)", header.UncleHash, hash) } + if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash { return fmt.Errorf("transaction root hash mismatch (header value %x, calculated %x)", header.TxHash, hash) } @@ -86,8 +90,10 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { return consensus.ErrUnknownAncestor } + return consensus.ErrPrunedAncestor } + return nil } @@ -95,6 +101,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // such as amount of used gas, the receipt roots and the state root itself. func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error { header := block.Header() + if block.GasUsed() != usedGas { return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) } @@ -114,6 +121,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) } + return nil } @@ -123,6 +131,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD func CalcGasLimit(parentGasLimit, desiredLimit uint64) uint64 { delta := parentGasLimit/params.GasLimitBoundDivisor - 1 limit := parentGasLimit + if desiredLimit < params.MinGasLimit { desiredLimit = params.MinGasLimit } @@ -132,13 +141,16 @@ func CalcGasLimit(parentGasLimit, desiredLimit uint64) uint64 { if limit > desiredLimit { limit = desiredLimit } + return limit } + if limit > desiredLimit { limit = parentGasLimit - delta if limit < desiredLimit { limit = desiredLimit } } + return limit } diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 0da8bc1966..ab35a43eee 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -41,6 +41,7 @@ func TestHeaderVerification(t *testing.T) { gspec = &Genesis{Config: params.TestChainConfig} _, blocks, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 8, nil) ) + headers := make([]*types.Header, len(blocks)) for i, block := range blocks { headers[i] = block.Header() @@ -76,6 +77,7 @@ func TestHeaderVerification(t *testing.T) { case <-time.After(25 * time.Millisecond): } } + chain.InsertChain(blocks[i : i+1]) } } @@ -92,6 +94,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { engine consensus.Engine merger = consensus.NewMerger(rawdb.NewMemoryDatabase()) ) + if isClique { var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -119,6 +122,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { if i > 0 { header.ParentHash = blocks[i-1].Hash() } + header.Extra = make([]byte, 32+crypto.SignatureLength) header.Difficulty = big.NewInt(2) @@ -139,10 +143,12 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { engine = beacon.New(ethash.NewFaker()) td := int(params.GenesisDifficulty.Uint64()) genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 8, nil) + for _, block := range blocks { // calculate td td += int(block.Difficulty().Uint64()) } + preBlocks = blocks gspec.Config.TerminalTotalDifficulty = big.NewInt(int64(td)) t.Logf("Set ttd to %v\n", gspec.Config.TerminalTotalDifficulty) @@ -156,6 +162,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { preHeaders[i] = block.Header() t.Logf("Pre-merge header: %d", block.NumberU64()) } + postHeaders := make([]*types.Header, len(postBlocks)) for i, block := range postBlocks { postHeaders[i] = block.Header() @@ -216,16 +223,19 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { headers []*types.Header seals []bool ) + for _, block := range preBlocks { headers = append(headers, block.Header()) seals = append(seals, true) } + for _, block := range postBlocks { headers = append(headers, block.Header()) seals = append(seals, true) } _, results := engine.VerifyHeaders(chain, headers, seals) + for i := 0; i < len(headers); i++ { select { case result := <-results: @@ -255,6 +265,7 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) { gspec = &Genesis{Config: params.TestChainConfig} _, blocks, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 8, nil) ) + headers := make([]*types.Header, len(blocks)) seals := make([]bool, len(blocks)) @@ -282,6 +293,7 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) { } // Wait for all the verification results checks := make(map[int]error) + for j := 0; j < len(blocks); j++ { select { case result := <-results: @@ -297,6 +309,7 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) { if (checks[j] == nil) != want { t.Errorf("test %d.%d: validity mismatch: have %v, want %v", i, j, checks[j], want) } + if !want { // A few blocks after the first error may pass verification due to concurrent // workers. We don't care about those in this test, just that the correct block @@ -325,6 +338,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { gspec = &Genesis{Config: params.TestChainConfig} _, blocks, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 1024, nil) ) + headers := make([]*types.Header, len(blocks)) seals := make([]bool, len(blocks)) @@ -345,12 +359,14 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { // Deplete the results channel verified := 0 + for depleted := false; !depleted; { select { case result := <-results: if result != nil { t.Errorf("header %d: validation failed: %v", verified, result) } + verified++ case <-time.After(50 * time.Millisecond): depleted = true diff --git a/core/blockchain.go b/core/blockchain.go index e4ac7d7a46..1f9c7f49e2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -312,10 +312,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis bc.processor = NewStateProcessor(chainConfig, bc, engine) var err error + bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped) if err != nil { return nil, err } + bc.genesisBlock = bc.GetBlockByNumber(0) if bc.genesisBlock == nil { return nil, ErrNoGenesis @@ -346,6 +348,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if bc.cacheConfig.SnapshotLimit > 0 { diskRoot = rawdb.ReadSnapshotRoot(bc.db) } + if diskRoot != (common.Hash{}) { log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash(), "snaproot", diskRoot) @@ -359,6 +362,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis } } else { log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash()) + if _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil { return nil, err } @@ -389,8 +393,10 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis low = snapBlock.Number.Uint64() } } + if needRewind { log.Error("Truncating ancient chain", "from", bc.CurrentHeader().Number.Uint64(), "to", low) + if err := bc.SetHead(low); err != nil { return nil, err } @@ -409,9 +415,11 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis // make sure the headerByNumber (if present) is in our current canonical chain if headerByNumber != nil && headerByNumber.Hash() == header.Hash() { log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash) + if err := bc.SetHead(header.Number.Uint64() - 1); err != nil { return nil, err } + log.Error("Chain rewind was successful, resuming normal operation") } } @@ -428,6 +436,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis head := bc.CurrentBlock() if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer >= head.Number.Uint64() { log.Warn("Enabling snapshot recovery", "chainhead", head.Number, "diskbase", *layer) + recover = true } @@ -450,7 +459,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis log.Warn("Sanitizing invalid trie cache journal time", "provided", bc.cacheConfig.TrieCleanRejournal, "updated", time.Minute) bc.cacheConfig.TrieCleanRejournal = time.Minute } + bc.wg.Add(1) + go func() { defer bc.wg.Done() bc.triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit) @@ -590,6 +601,7 @@ func (bc *BlockChain) empty() bool { return false } } + return true } @@ -616,6 +628,7 @@ func (bc *BlockChain) loadLastState() error { // Restore the last known head header headHeader := headBlock.Header() + if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) { if header := bc.GetHeaderByHash(head); header != nil { headHeader = header @@ -670,9 +683,11 @@ func (bc *BlockChain) loadLastState() error { finalTd := bc.GetTd(currentFinalBlock.Hash(), currentFinalBlock.Number.Uint64()) log.Info("Loaded most recent local finalized block", "number", currentFinalBlock.Number, "hash", currentFinalBlock.Hash(), "td", finalTd, "age", common.PrettyAge(time.Unix(int64(currentFinalBlock.Time), 0))) } + if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil { log.Info("Loaded last fast-sync pivot marker", "number", *pivot) } + return nil } @@ -783,6 +798,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha newHeadBlock := bc.GetBlock(header.Hash(), header.Number.Uint64()) if newHeadBlock == nil { log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash()) + newHeadBlock = bc.genesisBlock } else { // Block exists, keep rewinding until we find one with state, @@ -795,14 +811,17 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha if root != (common.Hash{}) && !beyondRoot && newHeadBlock.Root() == root { beyondRoot, rootNumber = true, newHeadBlock.NumberU64() } + if !bc.HasState(newHeadBlock.Root()) { log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) + if pivot == nil || newHeadBlock.NumberU64() > *pivot { 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 { @@ -810,6 +829,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha newHeadBlock = bc.genesisBlock } } + if beyondRoot || newHeadBlock.NumberU64() == 0 { if newHeadBlock.NumberU64() == 0 { // Recommit the genesis state into disk in case the rewinding destination @@ -821,16 +841,21 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha if err := CommitGenesisState(bc.db, bc.triedb, bc.genesisBlock.Hash()); err != nil { log.Crit("Failed to commit genesis state", "err", err) } + log.Debug("Recommitted genesis state to disk") } } + log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) + break } + log.Debug("Skipping block with threshold state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "root", newHeadBlock.Root()) newHeadBlock = bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) // Keep rewinding } } + rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash()) // Degrade the chain markers if they are explicitly reverted. @@ -931,6 +956,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha log.Error("SetHead invalidated finalized block") bc.SetFinalized(nil) } + return rootNumber, bc.loadLastState() } @@ -962,7 +988,9 @@ func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error { if bc.snaps != nil { bc.snaps.Rebuild(root) } + log.Info("Committed new head block", "number", block.Number(), "hash", hash) + return nil } @@ -978,6 +1006,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { if err := bc.SetHead(0); err != nil { return err } + if !bc.chainmu.TryLock() { return errChainStopped } @@ -987,9 +1016,11 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { batch := bc.db.NewBatch() rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) rawdb.WriteBlock(batch, genesis) + if err := batch.Write(); err != nil { log.Crit("Failed to write genesis block", "err", err) } + bc.writeHeadBlock(genesis) // Last update all in-memory chain markers @@ -1000,6 +1031,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { bc.hc.SetCurrentHeader(bc.genesisBlock.Header()) bc.currentSnapBlock.Store(bc.genesisBlock.Header()) headFastBlockGauge.Update(int64(bc.genesisBlock.NumberU64())) + return nil } @@ -1013,6 +1045,7 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error { if first > last { return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last) } + log.Info("Exporting batch of blocks", "count", last-first+1) var ( @@ -1020,6 +1053,7 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error { start = time.Now() reported = time.Now() ) + for nr := first; nr <= last; nr++ { block := bc.GetBlockByNumber(nr) if block == nil { @@ -1031,14 +1065,17 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error { } parentHash = block.Hash() + if err := block.EncodeRLP(w); err != nil { return err } + if time.Since(reported) >= statsReportLimit { log.Info("Exporting blocks", "exported", block.NumberU64()-first, "elapsed", common.PrettyDuration(time.Since(start))) reported = time.Now() } } + return nil } @@ -1106,6 +1143,7 @@ func (bc *BlockChain) Stop() { // Ensure that the entirety of the state snapshot is journalled to disk. var snapBase common.Hash + if bc.snaps != nil { var err error if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root); err != nil { @@ -1132,6 +1170,7 @@ func (bc *BlockChain) Stop() { } } } + if snapBase != (common.Hash{}) { log.Info("Writing snapshot state to disk", "root", snapBase) @@ -1139,9 +1178,11 @@ func (bc *BlockChain) Stop() { log.Error("Failed to commit recent state trie", "err", err) } } + for !bc.triegc.Empty() { triedb.Dereference(bc.triegc.PopItem()) } + if size, _ := triedb.Size(); size != 0 { log.Error("Dangling trie nodes after full cleanup") } @@ -1155,6 +1196,7 @@ func (bc *BlockChain) Stop() { if bc.cacheConfig.TrieCleanJournal != "" { bc.triedb.SaveCache(bc.cacheConfig.TrieCleanJournal) } + log.Info("Blockchain stopped") } @@ -1172,11 +1214,13 @@ func (bc *BlockChain) insertStopped() bool { func (bc *BlockChain) procFutureBlocks() { blocks := make([]*types.Block, 0, bc.futureBlocks.Len()) + for _, hash := range bc.futureBlocks.Keys() { if block, exist := bc.futureBlocks.Peek(hash); exist { blocks = append(blocks, block) } } + if len(blocks) > 0 { sort.Slice(blocks, func(i, j int) bool { return blocks[i].NumberU64() < blocks[j].NumberU64() @@ -1215,10 +1259,12 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if blockChain[i].NumberU64() != blockChain[i-1].NumberU64()+1 || blockChain[i].ParentHash() != blockChain[i-1].Hash() { log.Error("Non contiguous receipt insert", "number", blockChain[i].Number(), "hash", blockChain[i].Hash(), "parent", blockChain[i].ParentHash(), "prevnumber", blockChain[i-1].Number(), "prevhash", blockChain[i-1].Hash()) + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, blockChain[i-1].NumberU64(), blockChain[i-1].Hash().Bytes()[:4], i, blockChain[i].NumberU64(), blockChain[i].Hash().Bytes()[:4], blockChain[i].ParentHash().Bytes()[:4]) } } + if blockChain[i].NumberU64() <= ancientLimit { ancientBlocks, ancientReceipts = append(ancientBlocks, blockChain[i]), append(ancientReceipts, receiptChain[i]) } else { @@ -1261,8 +1307,10 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ rawdb.WriteHeadFastBlockHash(bc.db, head.Hash()) bc.currentSnapBlock.Store(head.Header()) headFastBlockGauge.Update(int64(head.NumberU64())) + return true } + return false } // writeAncient writes blockchain and corresponding receipt chain into ancient store. @@ -1280,10 +1328,12 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ td := bc.genesisBlock.Difficulty() writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{b}, []types.Receipts{nil}, []types.Receipts{nil}, td) size += writeSize + if err != nil { log.Error("Error writing genesis to ancients", "err", err) return 0, err } + log.Info("Wrote genesis to ancients") } } @@ -1299,6 +1349,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ borReceipts := []types.Receipts{} var headers []*types.Header + for _, block := range blockChain { borReceipts = append(borReceipts, []*types.Receipt{bc.GetBorReceiptByHash(block.Hash())}) headers = append(headers, block.Header()) @@ -1308,6 +1359,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ td := bc.GetTd(first.Hash(), first.NumberU64()) writeSize, err := rawdb.WriteAncientBlocks(bc.db, blockChain, receiptChain, borReceipts, td) size += writeSize + if err != nil { log.Error("Error importing chain data to ancients", "err", err) return 0, err @@ -1325,23 +1377,28 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ // range. In this case, all tx indices of newly imported blocks should be // generated. var batch = bc.db.NewBatch() + for i, block := range blockChain { if bc.txLookupLimit == 0 || ancientLimit <= bc.txLookupLimit || block.NumberU64() >= ancientLimit-bc.txLookupLimit { rawdb.WriteTxLookupEntriesByBlock(batch, block) } else if rawdb.ReadTxIndexTail(bc.db) != nil { rawdb.WriteTxLookupEntriesByBlock(batch, block) } + stats.processed++ if batch.ValueSize() > ethdb.IdealBatchSize || i == len(blockChain)-1 { size += int64(batch.ValueSize()) + if err = batch.Write(); err != nil { snapBlock := bc.CurrentSnapBlock().Number.Uint64() if err := bc.db.TruncateHead(snapBlock + 1); err != nil { log.Error("Can't truncate ancient store after failed insert", "err", err) } + return 0, err } + batch.Reset() } } @@ -1358,17 +1415,21 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if err := bc.db.TruncateHead(previousSnapBlock + 1); err != nil { log.Error("Can't truncate ancient store after failed insert", "err", err) } + return 0, errSideChainReceipts } // Delete block data from the main database. batch.Reset() + canonHashes := make(map[common.Hash]struct{}) for _, block := range blockChain { canonHashes[block.Hash()] = struct{}{} + if block.NumberU64() == 0 { continue } + rawdb.DeleteCanonicalHash(batch, block.NumberU64()) rawdb.DeleteBlockWithoutNumber(batch, block.Hash(), block.NumberU64()) } @@ -1378,9 +1439,11 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ rawdb.DeleteHeader(batch, nh.Hash, nh.Number) } } + if err := batch.Write(); err != nil { return 0, err } + return 0, nil } @@ -1388,6 +1451,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ writeLive := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { skipPresenceCheck := false batch := bc.db.NewBatch() + headers := make([]*types.Header, 0, len(blockChain)) for i, block := range blockChain { // Update the headers for bor specific reorg check @@ -1401,6 +1465,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if !bc.HasHeader(block.Hash(), block.NumberU64()) { return i, fmt.Errorf("containing header #%d [%x..] unknown", block.Number(), block.Hash().Bytes()[:4]) } + if !skipPresenceCheck { // Ignore if the entire data is already known if bc.HasBlock(block.Hash(), block.NumberU64()) { @@ -1425,9 +1490,11 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if err := batch.Write(); err != nil { return 0, err } + size += int64(batch.ValueSize()) batch.Reset() } + stats.processed++ } // Write everything belongs to the blocks into the database. So that @@ -1435,12 +1502,14 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ // tx indexes) if batch.ValueSize() > 0 { size += int64(batch.ValueSize()) + if err := batch.Write(); err != nil { return 0, err } } updateHead(blockChain[len(blockChain)-1], headers) + return 0, nil } @@ -1450,6 +1519,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if err == errInsertionInterrupted { return 0, nil } + return n, err } } @@ -1466,16 +1536,19 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ } } } + if len(liveBlocks) > 0 { if n, err := writeLive(liveBlocks, liveReceipts); err != nil { if err == errInsertionInterrupted { return 0, nil } + return n, err } } head := blockChain[len(blockChain)-1] + context := []interface{}{ "count", stats.processed, "elapsed", common.PrettyDuration(time.Since(start)), "number", head.Number(), "hash", head.Hash(), "age", common.PrettyAge(time.Unix(int64(head.Time()), 0)), @@ -1501,9 +1574,11 @@ func (bc *BlockChain) writeBlockWithoutState(block *types.Block, td *big.Int) (e batch := bc.db.NewBatch() rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), td) rawdb.WriteBlock(batch, block) + if err := batch.Write(); err != nil { log.Crit("Failed to write block into disk", "err", err) } + return nil } @@ -1516,7 +1591,9 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error { return err } } + bc.writeHeadBlock(block) + return nil } @@ -1549,7 +1626,9 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // // block logs = receipt logs + state sync logs = `state.Logs()` blockLogs := state.Logs() + var stateSyncLogs []*types.Log + if len(blockLogs) > 0 { sort.SliceStable(blockLogs, func(i, j int) bool { return blockLogs[i].Index < blockLogs[j].Index @@ -1574,6 +1653,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. } rawdb.WritePreimages(blockBatch, state.Preimages()) + if err := blockBatch.Write(); err != nil { log.Crit("Failed to write block into disk", "err", err) } @@ -1636,6 +1716,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. bc.triedb.Dereference(root) } + return stateSyncLogs, nil } @@ -1672,6 +1753,7 @@ func (bc *BlockChain) writeBlockAndSetHead(ctx context.Context, block *types.Blo } currentBlock := bc.CurrentBlock() + var reorg bool tracing.Exec(writeBlockAndSetHeadCtx, "", "blockchain.ReorgNeeded", func(_ context.Context, span trace.Span) { @@ -1684,6 +1766,7 @@ func (bc *BlockChain) writeBlockAndSetHead(ctx context.Context, block *types.Blo attribute.Bool("error", err != nil), ) }) + if err != nil { return NonStatTy, err } @@ -1696,6 +1779,7 @@ func (bc *BlockChain) writeBlockAndSetHead(ctx context.Context, block *types.Blo status = NonStatTy } } + status = CanonStatTy } else { status = SideStatTy @@ -1725,6 +1809,7 @@ func (bc *BlockChain) writeBlockAndSetHead(ctx context.Context, block *types.Blo if status == CanonStatTy { bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) + if len(logs) > 0 { bc.logsFeed.Send(logs) } @@ -1755,6 +1840,7 @@ func (bc *BlockChain) writeBlockAndSetHead(ctx context.Context, block *types.Blo NewChain: []*types.Block{block}, }) } + return status, nil } @@ -1769,11 +1855,14 @@ func (bc *BlockChain) addFutureBlock(block *types.Block) error { if block.Time() > max { return fmt.Errorf("future block timestamp %v > allowed %v", block.Time(), max) } + if block.Difficulty().Cmp(common.Big0) == 0 { // Never add PoS blocks into the future queue return nil } + bc.futureBlocks.Add(block.Hash(), block) + return nil } @@ -1786,7 +1875,9 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { if len(chain) == 0 { return 0, nil } + bc.blockProcFeed.Send(true) + defer bc.blockProcFeed.Send(false) // Do a sanity check that the provided chain is actually ordered and linked. @@ -1800,6 +1891,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { "prevnumber", prev.Number(), "prevhash", prev.Hash(), ) + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, prev.NumberU64(), prev.Hash().Bytes()[:4], i, block.NumberU64(), block.Hash().Bytes()[:4], block.ParentHash().Bytes()[:4]) } @@ -1809,6 +1901,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { return 0, errChainStopped } defer bc.chainmu.Unlock() + return bc.insertChain(chain, true, true) } @@ -1847,6 +1940,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) headers[i] = block.Header() seals[i] = verifySeals } + abort, results := bc.engine.VerifyHeaders(bc, headers, seals) defer close(abort) @@ -1882,11 +1976,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) reorg bool current = bc.CurrentBlock() ) + for block != nil && bc.skipBlock(err, it) { reorg, err = bc.forker.ReorgNeeded(current, block.Header()) if err != nil { return it.index, err } + if reorg { // Switch to import mode if the forker says the reorg is necessary // and also the block is not on the canonical chain. @@ -1897,7 +1993,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) break } } + log.Debug("Ignoring already known block", "number", block.Number(), "hash", block.Hash()) + stats.ignored++ block, err = it.next() @@ -1912,15 +2010,18 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) // head full block(new pivot point). for block != nil && bc.skipBlock(err, it) { log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash()) + if err := bc.writeKnownBlock(block); err != nil { return it.index, err } + lastCanon = block block, err = it.next() } // Falls through to the block import } + switch { // First block is pruned case errors.Is(err, consensus.ErrPrunedAncestor): @@ -1932,17 +2033,21 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) // We're post-merge and the parent is pruned, try to recover the parent state log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash()) _, err := bc.recoverAncestors(block) + return it.index, err } // First block is future, shove it (and all children) to the future queue (unknown ancestor) case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())): for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) { log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash()) + if err := bc.addFutureBlock(block); err != nil { return it.index, err } + block, err = it.next() } + stats.queued += it.processed() stats.ignored += it.remaining() @@ -1954,8 +2059,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) // still need re-execution to generate snapshots that are missing case err != nil && !errors.Is(err, ErrKnownBlock): bc.futureBlocks.Remove(block.Hash()) + stats.ignored += len(it.chain) + bc.reportBlock(block, nil, err) + return it.index, err } // No validation errors for the first block (or chain prefix skipped) @@ -1979,10 +2087,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) // avoid reporting events for large sync events return } + bc.chain2HeadFeed.Send(Chain2HeadEvent{ Type: Chain2HeadCanonicalEvent, NewChain: canonAccum, }) + canonAccum = canonAccum[:0] } @@ -2008,6 +2118,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) if bc.chainConfig.Clique == nil { logger = log.Warn } + logger("Inserted known block", "number", block.Number(), "hash", block.Hash(), "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), "root", block.Root()) @@ -2026,23 +2137,28 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) log.Error("Please file an issue, skip known block execution without receipt", "hash", block.Hash(), "number", block.NumberU64()) } + if err := bc.writeKnownBlock(block); err != nil { return it.index, err } + stats.processed++ // We can assume that logs are empty here, since the only way for consecutive // Clique blocks to have the same state is if there are no transactions. lastCanon = block + continue } // Retrieve the parent block and it's state to execute on top start := time.Now() + parent := it.previous() if parent == nil { parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } + statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) if err != nil { return it.index, err @@ -2055,6 +2171,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) // 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 atomic.Bool + if !bc.cacheConfig.TrieCleanNoPrefetch { if followup, err := it.peek(); followup != nil && err == nil { throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) @@ -2075,9 +2192,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) pstart := time.Now() receipts, logs, usedGas, statedb, err := bc.ProcessBlock(block, parent) activeState = statedb + if err != nil { bc.reportBlock(block, receipts, err) followupInterrupt.Store(true) + return it.index, err } @@ -2089,9 +2208,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) ptime := time.Since(pstart) vstart := time.Now() + if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { bc.reportBlock(block, receipts, err) followupInterrupt.Store(true) + return it.index, err } @@ -2119,6 +2240,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) wstart = time.Now() status WriteStatus ) + if !setHead { // Don't set the head, only insert the block _, err = bc.writeBlockWithState(block, receipts, logs, statedb) @@ -2127,6 +2249,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) } followupInterrupt.Store(true) + if err != nil { return it.index, err } @@ -2199,15 +2322,18 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) if err := bc.addFutureBlock(block); err != nil { return it.index, err } + block, err = it.next() for ; block != nil && errors.Is(err, consensus.ErrUnknownAncestor); block, err = it.next() { if err := bc.addFutureBlock(block); err != nil { return it.index, err } + stats.queued++ } } + stats.ignored += it.remaining() return it.index, err @@ -2239,13 +2365,13 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i canonical := bc.GetBlockByNumber(number) if canonical != nil && canonical.Hash() == block.Hash() { // Not a sidechain block, this is a re-import of a canon block which has it's state pruned - // Collect the TD of the block. Since we know it's a canon one, // we can get it directly, and not (like further below) use // the parent and then add the block on top externTd = bc.GetTd(block.Hash(), block.NumberU64()) continue } + if canonical != nil && canonical.Root() == block.Root() { // This is most likely a shadow-state attack. When a fork is imported into the // database, and it eventually reaches a block height which is not pruned, we @@ -2262,21 +2388,26 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i return it.index, errors.New("sidechain ghost-state attack") } } + if externTd == nil { externTd = bc.GetTd(block.ParentHash(), block.NumberU64()-1) } + externTd = new(big.Int).Add(externTd, block.Difficulty()) if !bc.HasBlock(block.Hash(), block.NumberU64()) { start := time.Now() + if err := bc.writeBlockWithoutState(block, externTd); err != nil { return it.index, err } + log.Debug("Injected sidechain block", "number", block.Number(), "hash", block.Hash(), "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), "root", block.Root()) } + lastBlock = block } // At this point, we've written all sidechain blocks to database. Loop ended @@ -2298,6 +2429,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i if !reorg || !isValid { localTd := bc.GetTd(current.Hash(), current.Number.Uint64()) log.Info("Sidechain written to disk", "start", it.first().NumberU64(), "end", it.previous().Number, "sidetd", externTd, "localtd", localTd) + return it.index, err } // Gather all the sidechain hashes (full blocks may be memory heavy) @@ -2305,6 +2437,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i hashes []common.Hash numbers []uint64 ) + parent := it.previous() for parent != nil && !bc.HasState(parent.Root) { hashes = append(hashes, parent.Hash()) @@ -2312,6 +2445,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1) } + if parent == nil { return it.index, errors.New("missing parent") } @@ -2320,6 +2454,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i blocks []*types.Block memory uint64 ) + for i := len(hashes) - 1; i >= 0; i-- { // Append the next block to our batch block := bc.GetBlock(hashes[i], numbers[i]) @@ -2332,9 +2467,11 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i // memory here. if len(blocks) >= 2048 || memory > 64*1024*1024 { log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64()) + if _, err := bc.insertChain(blocks, false, true); err != nil { return 0, err } + blocks, memory = blocks[:0], 0 // If the chain is terminating, stop processing blocks @@ -2344,10 +2481,12 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i } } } + if len(blocks) > 0 { log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64()) return bc.insertChain(blocks, false, true) } + return 0, nil } @@ -2362,6 +2501,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) numbers []uint64 parent = block ) + for parent != nil && !bc.HasState(parent.Root()) { hashes = append(hashes, parent.Hash()) numbers = append(numbers, parent.NumberU64()) @@ -2373,6 +2513,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) return common.Hash{}, errInsertionInterrupted } } + if parent == nil { return common.Hash{}, errors.New("missing parent") } @@ -2383,12 +2524,14 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) log.Debug("Abort during blocks processing") return common.Hash{}, errInsertionInterrupted } + var b *types.Block if i == 0 { b = block } else { b = bc.GetBlock(hashes[i], numbers[i]) } + if _, err := bc.insertChain(types.Blocks{b}, false, false); err != nil { return b.ParentHash(), err } @@ -2411,15 +2554,18 @@ func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.BaseFee(), b.Transactions()) var logs []*types.Log + for _, receipt := range receipts { for _, log := range receipt.Logs { l := *log if removed { l.Removed = true } + logs = append(logs, &l) } } + return logs } @@ -2462,9 +2608,11 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { newChain = append(newChain, newBlock) } } + if oldBlock == nil { return errors.New("invalid old chain") } + if newBlock == nil { return errors.New("invalid new chain") } @@ -2482,6 +2630,7 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { for _, tx := range oldBlock.Transactions() { deletedTxs = append(deletedTxs, tx.Hash()) } + newChain = append(newChain, newBlock) // Step back with both chains @@ -2489,6 +2638,7 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { if oldBlock == nil { return fmt.Errorf("invalid old chain") } + newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1) if newBlock == nil { return fmt.Errorf("invalid new chain") @@ -2497,7 +2647,6 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { // Ensure the user sees large reorgs if len(oldChain) > 0 && len(newChain) > 0 { - bc.chain2HeadFeed.Send(Chain2HeadEvent{ Type: Chain2HeadReorgEvent, NewChain: newChain, @@ -2505,11 +2654,13 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { }) logFn := log.Info + msg := "Chain reorg detected" if len(oldChain) > 63 { msg = "Large chain reorg detected" logFn = log.Warn } + logFn(msg, "number", commonBlock.Number(), "hash", commonBlock.Hash(), "drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash()) blockReorgAddMeter.Mark(int64(len(newChain))) @@ -2523,11 +2674,11 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { } else { // len(newChain) == 0 && len(oldChain) > 0 // rewind the canonical chain to a lower point. - home, err := os.UserHomeDir() if err != nil { fmt.Println("Impossible reorg : Unable to get user home dir", "Error", err) } + outPath := filepath.Join(home, "impossible-reorgs", fmt.Sprintf("%v-impossibleReorg", time.Now().Format(time.RFC3339))) if _, err := os.Stat(outPath); errors.Is(err, os.ErrNotExist) { @@ -2580,13 +2731,16 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { if len(newChain) > 1 { number = newChain[1].NumberU64() } + for i := number + 1; ; i++ { hash := rawdb.ReadCanonicalHash(bc.db, i) if hash == (common.Hash{}) { break } + rawdb.DeleteCanonicalHash(indexesBatch, i) } + if err := indexesBatch.Write(); err != nil { log.Crit("Failed to delete useless indexes", "err", err) } @@ -2634,6 +2788,7 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { if len(rebirthLogs) > 0 { bc.logsFeed.Send(rebirthLogs) } + return nil } @@ -2687,6 +2842,7 @@ func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error { defer bc.chainmu.Unlock() _, err := bc.insertChain(types.Blocks{block}, true, false) + return err } @@ -2709,19 +2865,23 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) { } // Run the reorg if necessary and set the given block as new head. start := time.Now() + if head.ParentHash() != bc.CurrentBlock().Hash() { if err := bc.reorg(bc.CurrentBlock(), head); err != nil { return common.Hash{}, err } } + bc.writeHeadBlock(head) // Emit events logs := bc.collectLogs(head, false) bc.chainFeed.Send(ChainEvent{Block: head, Hash: head.Hash(), Logs: logs}) + if len(logs) > 0 { bc.logsFeed.Send(logs) } + bc.chainHeadFeed.Send(ChainHeadEvent{Block: head}) context := []interface{}{ @@ -2733,6 +2893,7 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) { if timestamp := time.Unix(int64(head.Time()), 0); time.Since(timestamp) > time.Minute { context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) } + log.Info("Chain head was updated", context...) return head.Hash(), nil @@ -2742,6 +2903,7 @@ func (bc *BlockChain) updateFutureBlocks() { futureTimer := time.NewTicker(5 * time.Second) defer futureTimer.Stop() defer bc.wg.Done() + for { select { case <-futureTimer.C: @@ -2766,6 +2928,7 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool { if bc.snaps == nil { return true } + var ( header = it.current() // header can't be nil parentRoot common.Hash @@ -2783,6 +2946,7 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool { } else if parent = bc.GetHeaderByHash(header.ParentHash); parent != nil { parentRoot = parent.Root } + if parentRoot == (common.Hash{}) { return false // Theoretically impossible case } @@ -2790,6 +2954,7 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool { if bc.snaps.Snapshot(parentRoot) == nil { return true } + return false } @@ -2853,10 +3018,12 @@ func (bc *BlockChain) maintainTxIndex() { done chan struct{} // Non-nil if background unindexing or reindexing routine is active. headCh = make(chan ChainHeadEvent, 1) // Buffered to avoid locking up the event feed ) + sub := bc.SubscribeChainHeadEvent(headCh) if sub == nil { return } + defer sub.Unsubscribe() for { @@ -2873,6 +3040,7 @@ func (bc *BlockChain) maintainTxIndex() { log.Info("Waiting background transaction indexer to exit") <-done } + return } } @@ -2924,7 +3092,9 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i if len(chain) == 0 { return 0, nil } + start := time.Now() + if i, err := bc.hc.ValidateHeaderChain(chain, checkFreq); err != nil { return i, err } @@ -2934,6 +3104,7 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i } defer bc.chainmu.Unlock() _, err := bc.hc.InsertHeaderChain(chain, start, bc.forker) + return 0, err } diff --git a/core/blockchain_bor_test.go b/core/blockchain_bor_test.go index 9063a49d0c..6719f4f149 100644 --- a/core/blockchain_bor_test.go +++ b/core/blockchain_bor_test.go @@ -40,12 +40,15 @@ func TestChain2HeadEvent(t *testing.T) { replacementBlocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, gen *BlockGen) { tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, nil), signer, key1) + if i == 2 { gen.OffsetTime(-9) } + if err != nil { t.Fatalf("failed to create tx: %v", err) } + gen.AddTx(tx) }) @@ -69,6 +72,7 @@ func TestChain2HeadEvent(t *testing.T) { if len(ev.NewChain) != len(expect.Added) { t.Fatal("Newchain and Added Array Size don't match") } + if len(ev.OldChain) != len(expect.Removed) { t.Fatal("Oldchain and Removed Array Size don't match") } @@ -78,6 +82,7 @@ func TestChain2HeadEvent(t *testing.T) { t.Fatal("Oldchain hashes Do Not Match") } } + for j := 0; j < len(ev.NewChain); j++ { if ev.NewChain[j].Hash() != expect.Added[j] { t.Fatalf("Newchain hashes Do Not Match %s %s", ev.NewChain[j].Hash(), expect.Added[j]) @@ -134,5 +139,4 @@ func TestChain2HeadEvent(t *testing.T) { replacementBlocks[2].Hash(), replacementBlocks[3].Hash(), }}) - } diff --git a/core/blockchain_insert.go b/core/blockchain_insert.go index 8f496e182c..9428371382 100644 --- a/core/blockchain_insert.go +++ b/core/blockchain_insert.go @@ -52,6 +52,7 @@ func (st *insertStats) report(chain []*types.Block, index int, dirty common.Stor for _, block := range chain[st.lastIndex : index+1] { txs += len(block.Transactions()) } + end := chain[index] // Assemble the log context and send it to the logger @@ -63,14 +64,17 @@ func (st *insertStats) report(chain []*types.Block, index int, dirty common.Stor if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute { context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) } + context = append(context, []interface{}{"dirty", dirty}...) if st.queued > 0 { context = append(context, []interface{}{"queued", st.queued}...) } + if st.ignored > 0 { context = append(context, []interface{}{"ignored", st.ignored}...) } + if setHead { log.Info("Imported new chain segment", context...) } else { @@ -117,6 +121,7 @@ func (it *insertIterator) next() (*types.Block, error) { if len(it.errors) <= it.index { it.errors = append(it.errors, <-it.results) } + if it.errors[it.index] != nil { return it.chain[it.index], it.errors[it.index] } @@ -138,6 +143,7 @@ func (it *insertIterator) peek() (*types.Block, error) { if len(it.errors) <= it.index+1 { it.errors = append(it.errors, <-it.results) } + if it.errors[it.index+1] != nil { return it.chain[it.index+1], it.errors[it.index+1] } @@ -150,6 +156,7 @@ func (it *insertIterator) previous() *types.Header { if it.index < 1 { return nil } + return it.chain[it.index-1].Header() } @@ -158,6 +165,7 @@ func (it *insertIterator) current() *types.Header { if it.index == -1 || it.index >= len(it.chain) { return nil } + return it.chain[it.index].Header() } diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index b15664415e..41ccf26934 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -17,6 +17,7 @@ package core import ( + "github.com/ethereum/go-ethereum/ethdb" "math/big" "github.com/ethereum/go-ethereum/common" @@ -99,16 +100,19 @@ func (bc *BlockChain) GetBody(hash common.Hash) *types.Body { if cached, ok := bc.bodyCache.Get(hash); ok { return cached } + number := bc.hc.GetBlockNumber(hash) if number == nil { return nil } + body := rawdb.ReadBody(bc.db, hash, *number) if body == nil { return nil } // Cache the found body for next time and return bc.bodyCache.Add(hash, body) + return body } @@ -119,16 +123,19 @@ func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue { if cached, ok := bc.bodyRLPCache.Get(hash); ok { return cached } + number := bc.hc.GetBlockNumber(hash) if number == nil { return nil } + body := rawdb.ReadBodyRLP(bc.db, hash, *number) if len(body) == 0 { return nil } // Cache the found body for next time and return bc.bodyRLPCache.Add(hash, body) + return body } @@ -141,6 +148,7 @@ func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool { if !bc.HasHeader(hash, number) { return false } + return rawdb.HasBody(bc.db, hash, number) } @@ -149,9 +157,11 @@ func (bc *BlockChain) HasFastBlock(hash common.Hash, number uint64) bool { if !bc.HasBlock(hash, number) { return false } + if bc.receiptsCache.Contains(hash) { return true } + return rawdb.HasReceipts(bc.db, hash, number) } @@ -162,12 +172,14 @@ func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { if block, ok := bc.blockCache.Get(hash); ok { return block } + block := rawdb.ReadBlock(bc.db, hash, number) if block == nil { return nil } // Cache the found block for next time and return bc.blockCache.Add(block.Hash(), block) + return block } @@ -177,6 +189,7 @@ func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block { if number == nil { return nil } + return bc.GetBlock(hash, *number) } @@ -187,6 +200,7 @@ func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block { if hash == (common.Hash{}) { return nil } + return bc.GetBlock(hash, number) } @@ -197,15 +211,18 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type if number == nil { return nil } + for i := 0; i < n; i++ { block := bc.GetBlock(hash, *number) if block == nil { break } + blocks = append(blocks, block) hash = block.ParentHash() *number-- } + return } @@ -214,15 +231,19 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { if receipts, ok := bc.receiptsCache.Get(hash); ok { return receipts } + number := rawdb.ReadHeaderNumber(bc.db, hash) if number == nil { return nil } + receipts := rawdb.ReadReceipts(bc.db, hash, *number, bc.chainConfig) if receipts == nil { return nil } + bc.receiptsCache.Add(hash, receipts) + return receipts } @@ -234,6 +255,7 @@ func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types. uncles = append(uncles, block.Uncles()...) block = bc.GetBlock(block.ParentHash(), block.NumberU64()-1) } + return uncles } @@ -258,12 +280,15 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) *rawdb.LegacyTxLook if lookup, exist := bc.txLookupCache.Get(hash); exist { return lookup } + tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash) if tx == nil { return nil } + lookup := &rawdb.LegacyTxLookupEntry{BlockHash: blockHash, BlockIndex: blockNumber, Index: txIndex} bc.txLookupCache.Add(hash, lookup) + return lookup } @@ -287,6 +312,7 @@ func (bc *BlockChain) HasBlockAndState(hash common.Hash, number uint64) bool { if block == nil { return false } + return bc.HasState(block.Root()) } @@ -311,6 +337,7 @@ func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) { type codeReader interface { ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) } + return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Hash{}, hash) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index b45379df10..c43c94a029 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -65,6 +65,7 @@ func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *G if n == 0 { return rawdb.NewMemoryDatabase(), genesis, blockchain, nil } + if full { // Full block-chain requested genDb, blocks := makeBlockChainWithGenesis(genesis, n, engine, canonicalSeed) @@ -101,6 +102,7 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara hash1 = blockchain.GetHeaderByNumber(uint64(i)).Hash() hash2 = blockchain2.GetHeaderByNumber(uint64(i)).Hash() } + if hash1 != hash2 { t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1) } @@ -109,6 +111,7 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara blockChainB []*types.Block headerChainB []*types.Header ) + if full { blockChainB = makeBlockChain(blockchain2.chainConfig, blockchain2.GetBlockByHash(blockchain2.CurrentBlock().Hash()), n, ethash.NewFaker(), genDb, forkSeed) if _, err := blockchain2.InsertChain(blockChainB); err != nil { @@ -126,17 +129,21 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara if full { cur := blockchain.CurrentBlock() tdPre = blockchain.GetTd(cur.Hash(), cur.Number.Uint64()) + if err := testBlockChainImport(blockChainB, blockchain); err != nil { t.Fatalf("failed to import forked block chain: %v", err) } + last := blockChainB[len(blockChainB)-1] tdPost = blockchain.GetTd(last.Hash(), last.NumberU64()) } else { cur := blockchain.CurrentHeader() tdPre = blockchain.GetTd(cur.Hash(), cur.Number.Uint64()) + if err := testHeaderChainImport(headerChainB, blockchain); err != nil { t.Fatalf("failed to import forked header chain: %v", err) } + last := headerChainB[len(headerChainB)-1] tdPost = blockchain.GetTd(last.Hash(), last.Number.Uint64()) } @@ -153,10 +160,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { if err == nil { err = blockchain.validator.ValidateBody(block) } + if err != nil { if err == ErrKnownBlock { continue } + return err } @@ -166,6 +175,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { blockchain.reportBlock(block, receipts, err) return err } + err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas) if err != nil { blockchain.reportBlock(block, receipts, err) @@ -178,6 +188,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { statedb.Commit(false) blockchain.chainmu.Unlock() } + return nil } @@ -215,6 +226,7 @@ func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error rawdb.WriteHeader(blockchain.db, header) blockchain.chainmu.Unlock() } + return nil } @@ -229,6 +241,7 @@ func TestLastBlock(t *testing.T) { if _, err := blockchain.InsertChain(blocks); err != nil { t.Fatalf("Failed to insert block: %v", err) } + if blocks[len(blocks)-1].Hash() != rawdb.ReadHeadBlockHash(blockchain.db) { t.Fatalf("Write/Get HeadBlockHash failed") } @@ -253,6 +266,7 @@ func testInsertAfterMerge(t *testing.T, blockchain *BlockChain, i, n int, full b hash1 = blockchain.GetHeaderByNumber(uint64(i)).Hash() hash2 = blockchain2.GetHeaderByNumber(uint64(i)).Hash() } + if hash1 != hash2 { t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1) } @@ -267,6 +281,7 @@ func testInsertAfterMerge(t *testing.T, blockchain *BlockChain, i, n int, full b if blockchain2.CurrentBlock().Number.Uint64() != blockChainB[len(blockChainB)-1].NumberU64() { t.Fatalf("failed to reorg to the given chain") } + if blockchain2.CurrentBlock().Hash() != blockChainB[len(blockChainB)-1].Hash() { t.Fatalf("failed to reorg to the given chain") } @@ -275,9 +290,11 @@ func testInsertAfterMerge(t *testing.T, blockchain *BlockChain, i, n int, full b if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil { t.Fatalf("failed to insert forking chain: %v", err) } + if blockchain2.CurrentHeader().Number.Uint64() != headerChainB[len(headerChainB)-1].Number.Uint64() { t.Fatalf("failed to reorg to the given chain") } + if blockchain2.CurrentHeader().Hash() != headerChainB[len(headerChainB)-1].Hash() { t.Fatalf("failed to reorg to the given chain") } @@ -531,6 +548,7 @@ func testReorgShort(t *testing.T, full bool) { for i := 0; i < len(easy); i++ { easy[i] = 60 } + diff := make([]int64, len(easy)-1) for i := 0; i < len(diff); i++ { diff[i] = -9 @@ -553,10 +571,12 @@ func testReorg(t *testing.T, first, second []int64, td int64, full bool) { diffBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), ethash.NewFaker(), genDb, len(second), func(i int, b *BlockGen) { b.OffsetTime(second[i]) }) + if full { if _, err := blockchain.InsertChain(easyBlocks); err != nil { t.Fatalf("failed to insert easy chain: %v", err) } + if _, err := blockchain.InsertChain(diffBlocks); err != nil { t.Fatalf("failed to insert difficult chain: %v", err) } @@ -565,13 +585,16 @@ func testReorg(t *testing.T, first, second []int64, td int64, full bool) { for i, block := range easyBlocks { easyHeaders[i] = block.Header() } + diffHeaders := make([]*types.Header, len(diffBlocks)) for i, block := range diffBlocks { diffHeaders[i] = block.Header() } + if _, err := blockchain.InsertHeaderChain(easyHeaders, 1); err != nil { t.Fatalf("failed to insert easy chain: %v", err) } + if _, err := blockchain.InsertHeaderChain(diffHeaders, 1); err != nil { t.Fatalf("failed to insert difficult chain: %v", err) } @@ -594,6 +617,7 @@ func testReorg(t *testing.T, first, second []int64, td int64, full bool) { } // Make sure the chain total difficulty is the correct one want := new(big.Int).Add(blockchain.genesisBlock.Difficulty(), big.NewInt(td)) + if full { cur := blockchain.CurrentBlock() if have := blockchain.GetTd(cur.Hash(), cur.Number.Uint64()); have.Cmp(want) != 0 { @@ -635,6 +659,7 @@ func testBadHashes(t *testing.T, full bool) { _, err = blockchain.InsertHeaderChain(headers, 1) } + if !errors.Is(err, ErrBannedHash) { t.Errorf("error mismatch: have: %v, want: %v", err, ErrBannedHash) } @@ -659,21 +684,28 @@ func testReorgBadHashes(t *testing.T, full bool) { if _, err = blockchain.InsertChain(blocks); err != nil { t.Errorf("failed to import blocks: %v", err) } + if blockchain.CurrentBlock().Hash() != blocks[3].Hash() { t.Errorf("last block hash mismatch: have: %x, want %x", blockchain.CurrentBlock().Hash(), blocks[3].Header().Hash()) } + BadHashes[blocks[3].Header().Hash()] = true + defer func() { delete(BadHashes, blocks[3].Header().Hash()) }() } else { if _, err = blockchain.InsertHeaderChain(headers, 1); err != nil { t.Errorf("failed to import headers: %v", err) } + if blockchain.CurrentHeader().Hash() != headers[3].Hash() { t.Errorf("last header hash mismatch: have: %x, want %x", blockchain.CurrentHeader().Hash(), headers[3].Hash()) } + BadHashes[headers[3].Hash()] = true + defer func() { delete(BadHashes, headers[3].Hash()) }() } + blockchain.Stop() // Create a new BlockChain and check that it rolled back the state. @@ -681,10 +713,12 @@ func testReorgBadHashes(t *testing.T, full bool) { if err != nil { t.Fatalf("failed to create new chain manager: %v", err) } + if full { if ncm.CurrentBlock().Hash() != blocks[2].Header().Hash() { t.Errorf("last block hash mismatch: have: %x, want %x", ncm.CurrentBlock().Hash(), blocks[2].Header().Hash()) } + if blocks[2].Header().GasLimit != ncm.GasLimit() { t.Errorf("last block gasLimit mismatch: have: %d, want %d", ncm.GasLimit(), blocks[2].Header().GasLimit) } @@ -693,6 +727,7 @@ func testReorgBadHashes(t *testing.T, full bool) { t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash()) } } + ncm.Stop() } @@ -715,6 +750,7 @@ func testInsertNonceError(t *testing.T, full bool) { failRes int failNum uint64 ) + if full { blocks := makeBlockChain(blockchain.chainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), i, ethash.NewFaker(), genDb, 0) @@ -781,6 +817,7 @@ func TestFastVsFullChains(t *testing.T) { if err != nil { panic(err) } + block.AddTx(tx) } } @@ -791,6 +828,7 @@ func TestFastVsFullChains(t *testing.T) { }) // Import the chain as an archive node for the comparison baseline archiveDb := rawdb.NewMemoryDatabase() + archive, _ := NewBlockChain(archiveDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer archive.Stop() @@ -799,6 +837,7 @@ func TestFastVsFullChains(t *testing.T) { } // Fast import the chain as a non-archive node to test fastDb := rawdb.NewMemoryDatabase() + fast, _ := NewBlockChain(fastDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer fast.Stop() @@ -806,9 +845,11 @@ func TestFastVsFullChains(t *testing.T) { for i, block := range blocks { headers[i] = block.Header() } + if n, err := fast.InsertHeaderChain(headers, 1); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } + if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil { t.Fatalf("failed to insert receipt %d: %v", n, err) } @@ -817,13 +858,16 @@ func TestFastVsFullChains(t *testing.T) { if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } + defer ancientDb.Close() + ancient, _ := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers, 1); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } + if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(len(blocks)/2)); err != nil { t.Fatalf("failed to insert receipt %d: %v", n, err) } @@ -835,15 +879,19 @@ func TestFastVsFullChains(t *testing.T) { if ftd, atd := fast.GetTd(hash, num), archive.GetTd(hash, num); ftd.Cmp(atd) != 0 { t.Errorf("block #%d [%x]: td mismatch: fastdb %v, archivedb %v", num, hash, ftd, atd) } + if antd, artd := ancient.GetTd(hash, num), archive.GetTd(hash, num); antd.Cmp(artd) != 0 { t.Errorf("block #%d [%x]: td mismatch: ancientdb %v, archivedb %v", num, hash, antd, artd) } + if fheader, aheader := fast.GetHeaderByHash(hash), archive.GetHeaderByHash(hash); fheader.Hash() != aheader.Hash() { t.Errorf("block #%d [%x]: header mismatch: fastdb %v, archivedb %v", num, hash, fheader, aheader) } + if anheader, arheader := ancient.GetHeaderByHash(hash), archive.GetHeaderByHash(hash); anheader.Hash() != arheader.Hash() { t.Errorf("block #%d [%x]: header mismatch: ancientdb %v, archivedb %v", num, hash, anheader, arheader) } + 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(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) || types.DeriveSha(anblock.Transactions(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) { @@ -856,6 +904,7 @@ func TestFastVsFullChains(t *testing.T) { freceipts := rawdb.ReadReceipts(fastDb, hash, num, fast.Config()) anreceipts := rawdb.ReadReceipts(ancientDb, hash, num, fast.Config()) areceipts := rawdb.ReadReceipts(archiveDb, hash, num, fast.Config()) + if 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) } @@ -864,9 +913,11 @@ func TestFastVsFullChains(t *testing.T) { if m := rawdb.ReadHeaderNumber(fastDb, hash); m == nil || *m != num { t.Errorf("block #%d [%x]: wrong hash-to-number mapping in fastdb: %v", num, hash, m) } + if m := rawdb.ReadHeaderNumber(ancientDb, hash); m == nil || *m != num { t.Errorf("block #%d [%x]: wrong hash-to-number mapping in ancientdb: %v", num, hash, m) } + if m := rawdb.ReadHeaderNumber(archiveDb, hash); m == nil || *m != num { t.Errorf("block #%d [%x]: wrong hash-to-number mapping in archivedb: %v", num, hash, m) } @@ -877,6 +928,7 @@ func TestFastVsFullChains(t *testing.T) { if fhash, ahash := rawdb.ReadCanonicalHash(fastDb, uint64(i)), rawdb.ReadCanonicalHash(archiveDb, uint64(i)); fhash != ahash { t.Errorf("block #%d: canonical hash mismatch: fastdb %v, archivedb %v", i, fhash, ahash) } + if anhash, arhash := rawdb.ReadCanonicalHash(ancientDb, uint64(i)), rawdb.ReadCanonicalHash(archiveDb, uint64(i)); anhash != arhash { t.Errorf("block #%d: canonical hash mismatch: ancientdb %v, archivedb %v", i, anhash, arhash) } @@ -897,6 +949,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), } ) + height := uint64(1024) _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil) @@ -923,6 +976,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { if num := chain.CurrentSnapBlock().Number.Uint64(); num != fast { t.Errorf("%s head snap-block mismatch: have #%v, want #%v", kind, num, fast) } + if num := chain.CurrentHeader().Number.Uint64(); num != header { t.Errorf("%s head header mismatch: have #%v, want #%v", kind, num, header) } @@ -947,6 +1001,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a non-archive node and ensure all pointers are updated fastDb := makeDb() defer fastDb.Close() + fast, _ := NewBlockChain(fastDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer fast.Stop() @@ -954,12 +1009,15 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { for i, block := range blocks { headers[i] = block.Header() } + if n, err := fast.InsertHeaderChain(headers, 1); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } + if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil { t.Fatalf("failed to insert receipt %d: %v", n, err) } + assert(t, "fast", fast, height, height, 0) fast.SetHead(remove - 1) assert(t, "fast", fast, height/2, height/2, 0) @@ -967,15 +1025,18 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a ancient-first node and ensure all pointers are updated ancientDb := makeDb() defer ancientDb.Close() + ancient, _ := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers, 1); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } + if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil { t.Fatalf("failed to insert receipt %d: %v", n, err) } + assert(t, "ancient", ancient, height, height, 0) ancient.SetHead(remove - 1) assert(t, "ancient", ancient, 0, 0, 0) @@ -986,10 +1047,12 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a light node and ensure all pointers are updated lightDb := makeDb() defer lightDb.Close() + light, _ := NewBlockChain(lightDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if n, err := light.InsertHeaderChain(headers, 1); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } + defer light.Stop() assert(t, "light", light, height, 0, 0) @@ -1054,10 +1117,12 @@ func TestChainTxReorgs(t *testing.T) { }) // Import the chain. This runs all block validation rules. db := rawdb.NewMemoryDatabase() + blockchain, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if i, err := blockchain.InsertChain(chain); err != nil { t.Fatalf("failed to insert original chain[%d]: %v", i, err) } + defer blockchain.Stop() // overwrite the old chain @@ -1088,6 +1153,7 @@ func TestChainTxReorgs(t *testing.T) { if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn != nil { t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn) } + if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil { t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt) } @@ -1097,6 +1163,7 @@ func TestChainTxReorgs(t *testing.T) { if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { t.Errorf("add %d: expected tx to be found", i) } + if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { t.Errorf("add %d: expected receipt to be found", i) } @@ -1106,6 +1173,7 @@ func TestChainTxReorgs(t *testing.T) { if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { t.Errorf("share %d: expected tx to be found", i) } + if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { t.Errorf("share %d: expected receipt to be found", i) } @@ -1135,6 +1203,7 @@ func TestLogReorgs(t *testing.T) { if err != nil { t.Fatalf("failed to create tx: %v", err) } + gen.AddTx(tx) } }) @@ -1144,16 +1213,20 @@ func TestLogReorgs(t *testing.T) { _, chain, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 3, func(i int, gen *BlockGen) {}) done := make(chan struct{}) + go func() { ev := <-rmLogsCh if len(ev.Logs) == 0 { t.Error("expected logs") } + close(done) }() + if _, err := blockchain.InsertChain(chain); err != nil { t.Fatalf("failed to insert forked chain: %v", err) } + timeout := time.NewTimer(1 * time.Second) defer timeout.Stop() select { @@ -1177,11 +1250,13 @@ func TestLogRebirth(t *testing.T) { engine = ethash.NewFaker() blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil, nil) ) + defer blockchain.Stop() // The event channels. newLogCh := make(chan []*types.Log, 10) rmLogsCh := make(chan RemovedLogsEvent, 10) + blockchain.SubscribeLogsEvent(newLogCh) blockchain.SubscribeRemovedLogsEvent(rmLogsCh) @@ -1198,6 +1273,7 @@ func TestLogRebirth(t *testing.T) { if err != nil { t.Fatalf("failed to create tx: %v", err) } + gen.AddTx(tx) } } @@ -1215,6 +1291,7 @@ func TestLogRebirth(t *testing.T) { // The last (head) block is not part of the reorg-chain, we can ignore it return } + for ii := 0; ii < 5; ii++ { tx, err := types.SignNewTx(key1, signer, &types.LegacyTx{ Nonce: gen.TxNonce(addr1), @@ -1225,6 +1302,7 @@ func TestLogRebirth(t *testing.T) { if err != nil { t.Fatalf("failed to create tx: %v", err) } + gen.AddTx(tx) } gen.OffsetTime(-9) // higher block difficulty @@ -1256,10 +1334,12 @@ func TestSideLogRebirth(t *testing.T) { signer = types.LatestSigner(gspec.Config) blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) ) + defer blockchain.Stop() newLogCh := make(chan []*types.Log, 10) rmLogsCh := make(chan RemovedLogsEvent, 10) + blockchain.SubscribeLogsEvent(newLogCh) blockchain.SubscribeRemovedLogsEvent(rmLogsCh) @@ -1271,6 +1351,7 @@ func TestSideLogRebirth(t *testing.T) { if _, err := blockchain.InsertChain(chain); err != nil { t.Fatalf("failed to insert forked chain: %v", err) } + checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) // Generate side chain with lower difficulty @@ -1280,12 +1361,14 @@ func TestSideLogRebirth(t *testing.T) { if err != nil { t.Fatalf("failed to create tx: %v", err) } + gen.AddTx(tx) } }) if _, err := blockchain.InsertChain(sideChain); err != nil { t.Fatalf("failed to insert forked chain: %v", err) } + checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) // Generate a new block based on side chain. @@ -1293,6 +1376,7 @@ func TestSideLogRebirth(t *testing.T) { if _, err := blockchain.InsertChain(newBlocks); err != nil { t.Fatalf("failed to insert forked chain: %v", err) } + checkLogEvents(t, newLogCh, rmLogsCh, 1, 0) } @@ -1367,16 +1451,20 @@ func TestReorgSideEvent(t *testing.T) { _, replacementBlocks, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, func(i int, gen *BlockGen) { tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, nil), signer, key1) + if i == 2 { gen.OffsetTime(-9) } + if err != nil { t.Fatalf("failed to create tx: %v", err) } + gen.AddTx(tx) }) chainSideCh := make(chan ChainSideEvent, 64) blockchain.SubscribeChainSideEvent(chainSideCh) + if _, err := blockchain.InsertChain(replacementBlocks); err != nil { t.Fatalf("failed to insert chain: %v", err) } @@ -1437,6 +1525,7 @@ func TestCanonicalBlockRetrieval(t *testing.T) { _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 10, func(i int, gen *BlockGen) {}) var pend sync.WaitGroup + pend.Add(len(chain)) for i := range chain { @@ -1449,19 +1538,23 @@ func TestCanonicalBlockRetrieval(t *testing.T) { if ch == (common.Hash{}) { continue // busy wait for canonical hash to be written } + if ch != block.Hash() { t.Errorf("unknown canonical hash, want %s, got %s", block.Hash().Hex(), ch.Hex()) return } + fb := rawdb.ReadBlock(blockchain.db, ch, block.NumberU64()) if fb == nil { t.Errorf("unable to retrieve block %d for canonical hash: %s", block.NumberU64(), ch.Hex()) return } + if fb.Hash() != block.Hash() { t.Errorf("invalid block hash for block %d, want %s, got %s", block.NumberU64(), block.Hash().Hex(), fb.Hash().Hex()) return } + return } }(chain[i]) @@ -1470,6 +1563,7 @@ func TestCanonicalBlockRetrieval(t *testing.T) { t.Fatalf("failed to insert block %d: %v", i, err) } } + pend.Wait() } @@ -1499,36 +1593,42 @@ func TestEIP155Transition(t *testing.T) { return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key) } ) + switch i { case 0: tx, err = basicTx(types.HomesteadSigner{}) if err != nil { t.Fatal(err) } + block.AddTx(tx) case 2: tx, err = basicTx(types.HomesteadSigner{}) if err != nil { t.Fatal(err) } + block.AddTx(tx) tx, err = basicTx(types.LatestSigner(gspec.Config)) if err != nil { t.Fatal(err) } + block.AddTx(tx) case 3: tx, err = basicTx(types.HomesteadSigner{}) if err != nil { t.Fatal(err) } + block.AddTx(tx) tx, err = basicTx(types.LatestSigner(gspec.Config)) if err != nil { t.Fatal(err) } + block.AddTx(tx) } }) @@ -1539,6 +1639,7 @@ func TestEIP155Transition(t *testing.T) { if _, err := blockchain.InsertChain(blocks); err != nil { t.Fatal(err) } + block := blockchain.GetBlockByNumber(1) if block.Transactions()[0].Protected() { t.Error("Expected block[0].txs[0] to not be replay protected") @@ -1548,9 +1649,11 @@ func TestEIP155Transition(t *testing.T) { if block.Transactions()[0].Protected() { t.Error("Expected block[3].txs[0] to not be replay protected") } + if !block.Transactions()[1].Protected() { t.Error("Expected block[3].txs[1] to be replay protected") } + if _, err := blockchain.InsertChain(blocks[4:]); err != nil { t.Fatal(err) } @@ -1570,14 +1673,17 @@ func TestEIP155Transition(t *testing.T) { return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key) } ) + if i == 0 { tx, err = basicTx(types.LatestSigner(config)) if err != nil { t.Fatal(err) } + block.AddTx(tx) } }) + _, err := blockchain.InsertChain(blocks) if have, want := err, types.ErrInvalidChainId; !errors.Is(have, want) { t.Errorf("have %v, want %v", have, want) @@ -1609,6 +1715,7 @@ func TestEIP161AccountRemoval(t *testing.T) { err error signer = types.LatestSigner(gspec.Config) ) + switch i { case 0: tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil), signer, key) @@ -1617,9 +1724,11 @@ func TestEIP161AccountRemoval(t *testing.T) { case 2: tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil), signer, key) } + if err != nil { t.Fatal(err) } + block.AddTx(tx) }) // account must exist pre eip 161 @@ -1629,6 +1738,7 @@ func TestEIP161AccountRemoval(t *testing.T) { if _, err := blockchain.InsertChain(types.Blocks{blocks[0]}); err != nil { t.Fatal(err) } + if st, _ := blockchain.State(); !st.Exist(theAddr) { t.Error("expected account to exist") } @@ -1637,6 +1747,7 @@ func TestEIP161AccountRemoval(t *testing.T) { if _, err := blockchain.InsertChain(types.Blocks{blocks[1]}); err != nil { t.Fatal(err) } + if st, _ := blockchain.State(); st.Exist(theAddr) { t.Error("account should not exist") } @@ -1645,6 +1756,7 @@ func TestEIP161AccountRemoval(t *testing.T) { if _, err := blockchain.InsertChain(types.Blocks{blocks[2]}); err != nil { t.Fatal(err) } + if st, _ := blockchain.State(); st.Exist(theAddr) { t.Error("account should not exist") } @@ -1688,12 +1800,15 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { if _, err := chain.InsertChain(blocks[i : i+1]); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", i, err) } + if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() { t.Errorf("block %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number, chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) } + if _, err := chain.InsertChain(forks[i : i+1]); err != nil { t.Fatalf(" fork %d: failed to insert into chain: %v", i, err) } + if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() { t.Errorf(" fork %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number, chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) } @@ -1734,6 +1849,7 @@ func TestTrieForkGC(t *testing.T) { if _, err := chain.InsertChain(blocks[i : i+1]); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", i, err) } + if _, err := chain.InsertChain(forks[i : i+1]); err != nil { t.Fatalf("fork %d: failed to insert into chain: %v", i, err) } @@ -1743,6 +1859,7 @@ func TestTrieForkGC(t *testing.T) { chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root()) chain.stateCache.TrieDB().Dereference(forks[len(blocks)-1-i].Root()) } + if len(chain.stateCache.TrieDB().Nodes()) > 0 { t.Fatalf("stale tries still alive after garbase collection") } @@ -1772,6 +1889,7 @@ func TestLargeReorgTrieGC(t *testing.T) { if _, err := chain.InsertChain(shared); err != nil { t.Fatalf("failed to insert shared chain: %v", err) } + if _, err := chain.InsertChain(original); err != nil { t.Fatalf("failed to insert original chain: %v", err) } @@ -1784,6 +1902,7 @@ func TestLargeReorgTrieGC(t *testing.T) { if _, err := chain.InsertChain(competitor[:len(competitor)-2]); err != nil { t.Fatalf("failed to insert competitor chain: %v", err) } + for i, block := range competitor[:len(competitor)-2] { if node, _ := chain.stateCache.TrieDB().Node(block.Root()); node != nil { t.Fatalf("competitor %d: low TD chain became processed", i) @@ -1794,6 +1913,7 @@ func TestLargeReorgTrieGC(t *testing.T) { if _, err := chain.InsertChain(competitor[len(competitor)-2:]); err != nil { t.Fatalf("failed to finalize competitor chain: %v", err) } + for i, block := range competitor[:len(competitor)-int(chain.cacheConfig.TriesInMemory)] { if node, _ := chain.stateCache.TrieDB().Node(block.Root()); node != nil { t.Fatalf("competitor %d: competing chain state missing", i) @@ -1809,6 +1929,7 @@ func TestBlockchainRecovery(t *testing.T) { funds = big.NewInt(1000000000) gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} ) + height := uint64(1024) _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil) @@ -1817,6 +1938,7 @@ func TestBlockchainRecovery(t *testing.T) { if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } + defer ancientDb.Close() ancient, _ := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) @@ -1824,12 +1946,15 @@ func TestBlockchainRecovery(t *testing.T) { for i, block := range blocks { headers[i] = block.Header() } + if n, err := ancient.InsertHeaderChain(headers, 1); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } + if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil { t.Fatalf("failed to insert receipt %d: %v", n, err) } + rawdb.WriteLastPivotNumber(ancientDb, blocks[len(blocks)-1].NumberU64()) // Force fast sync behavior ancient.Stop() @@ -1848,6 +1973,7 @@ func TestBlockchainRecovery(t *testing.T) { if num := ancient.CurrentSnapBlock().Number.Uint64(); num != midBlock.NumberU64() { t.Errorf("head snap-block mismatch: have #%v, want #%v", num, midBlock.NumberU64()) } + if num := ancient.CurrentHeader().Number.Uint64(); num != midBlock.NumberU64() { t.Errorf("head header mismatch: have #%v, want #%v", num, midBlock.NumberU64()) } @@ -1867,6 +1993,7 @@ func TestInsertReceiptChainRollback(t *testing.T) { } t.Log("sidechain head:", tmpChain.CurrentBlock().Number, tmpChain.CurrentBlock().Hash()) + sidechainReceipts := make([]types.Receipts, len(sideblocks)) for i, block := range sideblocks { sidechainReceipts[i] = tmpChain.GetReceiptsByHash(block.Hash()) @@ -1877,6 +2004,7 @@ func TestInsertReceiptChainRollback(t *testing.T) { } t.Log("canon head:", tmpChain.CurrentBlock().Number, tmpChain.CurrentBlock().Hash()) + canonReceipts := make([]types.Receipts, len(canonblocks)) for i, block := range canonblocks { canonReceipts[i] = tmpChain.GetReceiptsByHash(block.Hash()) @@ -1897,6 +2025,7 @@ func TestInsertReceiptChainRollback(t *testing.T) { for i, block := range canonblocks { canonHeaders[i] = block.Header() } + if _, err = ancientChain.InsertHeaderChain(canonHeaders, 1); err != nil { t.Fatal("can't import canon headers:", err) } @@ -1910,6 +2039,7 @@ func TestInsertReceiptChainRollback(t *testing.T) { if ancientChain.CurrentSnapBlock().Number.Uint64() != 0 { t.Fatalf("failed to rollback ancient data, want %d, have %d", 0, ancientChain.CurrentSnapBlock().Number) } + if frozen, err := ancientChain.db.Ancients(); err != nil || frozen != 1 { t.Fatalf("failed to truncate ancient data, frozen index is %d", frozen) } @@ -1923,6 +2053,7 @@ func TestInsertReceiptChainRollback(t *testing.T) { if ancientChain.CurrentSnapBlock().Number.Uint64() != canonblocks[len(canonblocks)-1].NumberU64() { t.Fatalf("failed to insert ancient recept chain after rollback") } + if frozen, _ := ancientChain.db.Ancients(); frozen != uint64(len(canonblocks))+1 { t.Fatalf("wrong ancients count %d", frozen) } @@ -1970,6 +2101,7 @@ func TestLowDiffLongChain(t *testing.T) { if i, err := chain.InsertChain(fork); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", i, err) } + head := chain.CurrentBlock() if got := fork[len(fork)-1].Hash(); got != head.Hash() { t.Fatalf("head wrong, expected %x got %x", head.Hash(), got) @@ -1980,6 +2112,7 @@ func TestLowDiffLongChain(t *testing.T) { if hash := chain.GetHeaderByNumber(number).Hash(); hash != header.Hash() { t.Fatalf("header %d: canonical hash mismatch: have %x, want %x", number, hash, header.Hash()) } + header = chain.GetHeader(header.ParentHash, number-1) } } @@ -1997,6 +2130,7 @@ func TestLowDiffLongChain(t *testing.T) { func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommonAncestorAndPruneblock int, mergePoint int) { // Generate a canonical chain to act as the main dataset chainConfig := *params.TestChainConfig + var ( merger = consensus.NewMerger(rawdb.NewMemoryDatabase()) engine = beacon.New(ethash.NewFaker()) @@ -2023,6 +2157,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon // Activate the transition since genesis if required if mergePoint == 0 { mergeBlock = 0 + merger.ReachTTD() merger.FinalizePoS() @@ -2035,10 +2170,13 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon if err != nil { t.Fatalf("failed to create tx: %v", err) } + gen.AddTx(tx) + if int(gen.header.Number.Uint64()) >= mergeBlock { gen.SetPoS() } + nonce++ }) if n, err := chain.InsertChain(blocks); err != nil { @@ -2078,6 +2216,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon parent := blocks[parentIndex] fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*int(DefaultCacheConfig.TriesInMemory), func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) + if int(b.header.Number.Uint64()) >= mergeBlock { b.SetPoS() } @@ -2087,11 +2226,14 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon for i := numCanonBlocksInSidechain; i > 0; i-- { sidechain = append(sidechain, blocks[parentIndex+1-i]) } + sidechain = append(sidechain, fork...) n, err := chain.InsertChain(sidechain) + if err != nil { t.Errorf("Got error, %v number %d - %d", err, sidechain[n].NumberU64(), n) } + head := chain.CurrentBlock() if got := fork[len(fork)-1].Hash(); got != head.Hash() { t.Fatalf("head wrong, expected %x got %x", head.Hash(), got) @@ -2175,13 +2317,16 @@ func testInsertKnownChainData(t *testing.T, typ string) { inserter func(blocks []*types.Block, receipts []types.Receipts) error asserter func(t *testing.T, block *types.Block) ) + if typ == "headers" { inserter = func(blocks []*types.Block, receipts []types.Receipts) error { headers := make([]*types.Header, 0, len(blocks)) for _, block := range blocks { headers = append(headers, block.Header()) } + _, err := chain.InsertHeaderChain(headers, 1) + return err } asserter = func(t *testing.T, block *types.Block) { @@ -2195,11 +2340,14 @@ func testInsertKnownChainData(t *testing.T, typ string) { for _, block := range blocks { headers = append(headers, block.Header()) } + _, err := chain.InsertHeaderChain(headers, 1) if err != nil { return err } + _, err = chain.InsertReceiptChain(blocks, receipts, 0) + return err } asserter = func(t *testing.T, block *types.Block) { @@ -2228,21 +2376,25 @@ func testInsertKnownChainData(t *testing.T, typ string) { if err := inserter(blocks, receipts); err != nil { t.Fatalf("failed to insert chain data: %v", err) } + asserter(t, blocks[len(blocks)-1]) // Import a long canonical chain with some known data as prefix. rollback := blocks[len(blocks)/2].NumberU64() chain.SetHead(rollback - 1) + if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { t.Fatalf("failed to insert chain data: %v", err) } + asserter(t, blocks2[len(blocks2)-1]) // Import a heavier shorter but higher total difficulty chain with some known data as prefix. if err := inserter(append(blocks, blocks3...), append(receipts, receipts3...)); err != nil { t.Fatalf("failed to insert chain data: %v", err) } + asserter(t, blocks3[len(blocks3)-1]) // Import a longer but lower total difficulty chain with some known data as prefix. @@ -2254,9 +2406,11 @@ func testInsertKnownChainData(t *testing.T, typ string) { // Rollback the heavier chain and re-insert the longer chain again chain.SetHead(rollback - 1) + if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { t.Fatalf("failed to insert chain data: %v", err) } + asserter(t, blocks2[len(blocks2)-1]) } @@ -2285,6 +2439,7 @@ func TestInsertKnownBlocksAfterMerging(t *testing.T) { func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight int) { // Copy the TestChainConfig so we can modify it during tests chainConfig := *params.TestChainConfig + var ( genesis = &Genesis{ BaseFee: big.NewInt(params.InitialBaseFee), @@ -2304,6 +2459,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i if b.header.Number.Uint64() >= mergeBlock { b.SetPoS() } + b.SetCoinbase(common.Address{1}) }) @@ -2318,6 +2474,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i // Longer chain and shorter chain blocks2, receipts2 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 65, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) + if b.header.Number.Uint64() >= mergeBlock { b.SetPoS() } @@ -2325,6 +2482,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i blocks3, receipts3 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) b.OffsetTime(-9) // Time shifted, difficulty shouldn't be changed + if b.header.Number.Uint64() >= mergeBlock { b.SetPoS() } @@ -2347,6 +2505,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i inserter func(blocks []*types.Block, receipts []types.Receipts) error asserter func(t *testing.T, block *types.Block) ) + if typ == "headers" { inserter = func(blocks []*types.Block, receipts []types.Receipts) error { headers := make([]*types.Header, 0, len(blocks)) @@ -2359,6 +2518,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i if err != nil { return fmt.Errorf("index %d, number %d: %w", i, headers[i].Number, err) } + return err } asserter = func(t *testing.T, block *types.Block) { @@ -2372,11 +2532,14 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i for _, block := range blocks { headers = append(headers, block.Header()) } + i, err := chain.InsertHeaderChain(headers, 1) if err != nil { return fmt.Errorf("index %d: %w", i, err) } + _, err = chain.InsertReceiptChain(blocks, receipts, 0) + return err } asserter = func(t *testing.T, block *types.Block) { @@ -2390,6 +2553,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i if err != nil { return fmt.Errorf("index %d: %w", i, err) } + return nil } asserter = func(t *testing.T, block *types.Block) { @@ -2398,6 +2562,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i } } } + if err := inserter(blocks, receipts); err != nil { t.Fatalf("failed to insert chain data: %v", err) } @@ -2407,20 +2572,24 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i if err := inserter(blocks, receipts); err != nil { t.Fatalf("failed to insert chain data: %v", err) } + asserter(t, blocks[len(blocks)-1]) // Import a long canonical chain with some known data as prefix. rollback := blocks[len(blocks)/2].NumberU64() chain.SetHead(rollback - 1) + if err := inserter(blocks, receipts); err != nil { t.Fatalf("failed to insert chain data: %v", err) } + asserter(t, blocks[len(blocks)-1]) // Import a longer chain with some known data as prefix. if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { t.Fatalf("failed to insert chain data: %v", err) } + asserter(t, blocks2[len(blocks2)-1]) // Import a shorter chain with some known data as prefix. @@ -2434,9 +2603,11 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i // Reimport the longer chain again, the reorg is still expected chain.SetHead(rollback - 1) + if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { t.Fatalf("failed to insert chain data: %v", err) } + asserter(t, blocks2[len(blocks2)-1]) } @@ -2453,6 +2624,7 @@ func getLongAndShortChains() (*BlockChain, []*types.Block, []*types.Block, *Gene genDb, longChain, _ := GenerateChainWithGenesis(genesis, engine, 80, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil, nil) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to create tester chain: %v", err) @@ -2474,20 +2646,26 @@ func getLongAndShortChains() (*BlockChain, []*types.Block, []*types.Block, *Gene longerTd = new(big.Int) shorterTd = new(big.Int) ) + for index, b := range longChain { longerTd.Add(longerTd, b.Difficulty()) + if index <= parentIndex { shorterTd.Add(shorterTd, b.Difficulty()) } } + for _, b := range heavyChain { shorterTd.Add(shorterTd, b.Difficulty()) } + if shorterTd.Cmp(longerTd) <= 0 { return nil, nil, nil, nil, fmt.Errorf("test is moot, heavyChain td (%v) must be larger than canon td (%v)", shorterTd, longerTd) } + longerNum := longChain[len(longChain)-1].NumberU64() shorterNum := heavyChain[len(heavyChain)-1].NumberU64() + if shorterNum >= longerNum { return nil, nil, nil, nil, fmt.Errorf("test is moot, heavyChain num (%v) must be lower than canon num (%v)", shorterNum, longerNum) } @@ -2514,10 +2692,12 @@ func TestReorgToShorterRemovesCanonMapping(t *testing.T) { canonNum := chain.CurrentBlock().Number.Uint64() canonHash := chain.CurrentBlock().Hash() + _, err = chain.InsertChain(sideblocks) if err != nil { t.Errorf("Got error, %v", err) } + head := chain.CurrentBlock() if got := sideblocks[len(sideblocks)-1].Hash(); got != head.Hash() { t.Fatalf("head wrong, expected %x got %x", head.Hash(), got) @@ -2526,12 +2706,15 @@ func TestReorgToShorterRemovesCanonMapping(t *testing.T) { if blockByNum := chain.GetBlockByNumber(canonNum); blockByNum != nil { t.Errorf("expected block to be gone: %v", blockByNum.NumberU64()) } + if headerByNum := chain.GetHeaderByNumber(canonNum); headerByNum != nil { t.Errorf("expected header to be gone: %v", headerByNum.Number) } + if blockByHash := chain.GetBlockByHash(canonHash); blockByHash == nil { t.Errorf("expected block to be present: %x", blockByHash.Hash()) } + if headerByHash := chain.GetHeaderByHash(canonHash); headerByHash == nil { t.Errorf("expected header to be present: %x", headerByHash.Hash()) } @@ -2553,18 +2736,23 @@ func TestReorgToShorterRemovesCanonMappingHeaderChain(t *testing.T) { for i, block := range canonblocks { canonHeaders[i] = block.Header() } + if n, err := chain.InsertHeaderChain(canonHeaders, 0); err != nil { t.Fatalf("header %d: failed to insert into chain: %v", n, err) } + canonNum := chain.CurrentHeader().Number.Uint64() canonHash := chain.CurrentBlock().Hash() + sideHeaders := make([]*types.Header, len(sideblocks)) for i, block := range sideblocks { sideHeaders[i] = block.Header() } + if n, err := chain.InsertHeaderChain(sideHeaders, 0); err != nil { t.Fatalf("header %d: failed to insert into chain: %v", n, err) } + head := chain.CurrentHeader() if got := sideblocks[len(sideblocks)-1].Hash(); got != head.Hash() { t.Fatalf("head wrong, expected %x got %x", head.Hash(), got) @@ -2573,12 +2761,15 @@ func TestReorgToShorterRemovesCanonMappingHeaderChain(t *testing.T) { if blockByNum := chain.GetBlockByNumber(canonNum); blockByNum != nil { t.Errorf("expected block to be gone: %v", blockByNum.NumberU64()) } + if headerByNum := chain.GetHeaderByNumber(canonNum); headerByNum != nil { t.Errorf("expected header to be gone: %v", headerByNum.Number.Uint64()) } + if blockByHash := chain.GetBlockByHash(canonHash); blockByHash == nil { t.Errorf("expected block to be present: %x", blockByHash.Hash()) } + if headerByHash := chain.GetHeaderByHash(canonHash); headerByHash == nil { t.Errorf("expected header to be present: %x", headerByHash.Hash()) } @@ -2603,6 +2794,7 @@ func TestTransactionIndices(t *testing.T) { if err != nil { panic(err) } + block.AddTx(tx) }) @@ -2611,26 +2803,31 @@ func TestTransactionIndices(t *testing.T) { if tail == nil && stored != nil { t.Fatalf("Oldest indexded block mismatch, want nil, have %d", *stored) } + if tail != nil && *stored != *tail { t.Fatalf("Oldest indexded block mismatch, want %d, have %d", *tail, *stored) } + if tail != nil { for i := *tail; i <= chain.CurrentBlock().Number.Uint64(); i++ { block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) if block.Transactions().Len() == 0 { continue } + for _, tx := range block.Transactions() { if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index == nil { t.Fatalf("Miss transaction indice, number %d hash %s", i, tx.Hash().Hex()) } } } + for i := uint64(0); i < *tail; i++ { block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) if block.Transactions().Len() == 0 { continue } + for _, tx := range block.Transactions() { if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index != nil { t.Fatalf("Transaction indice should be deleted, number %d hash %s", i, tx.Hash().Hex()) @@ -2647,6 +2844,7 @@ func TestTransactionIndices(t *testing.T) { rawdb.WriteAncientBlocks(ancientDb, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), []types.Receipts{nil}, big.NewInt(0)) l := l + chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) @@ -2658,6 +2856,7 @@ func TestTransactionIndices(t *testing.T) { if l != 0 { tail = uint64(128) - l + 1 } + check(&tail, chain) chain.Stop() ancientDb.Close() @@ -2669,10 +2868,12 @@ func TestTransactionIndices(t *testing.T) { defer ancientDb.Close() rawdb.WriteAncientBlocks(ancientDb, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), []types.Receipts{nil}, big.NewInt(0)) + limit = []uint64{0, 64 /* drop stale */, 32 /* shorten history */, 64 /* extend history */, 0 /* restore all */} for _, l := range limit { l := l + chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) @@ -2705,6 +2906,7 @@ func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { if err != nil { panic(err) } + block.AddTx(tx) }) @@ -2713,26 +2915,31 @@ func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { if tail == nil && stored != nil { t.Fatalf("Oldest indexded block mismatch, want nil, have %d", *stored) } + if tail != nil && *stored != *tail { t.Fatalf("Oldest indexded block mismatch, want %d, have %d", *tail, *stored) } + if tail != nil { for i := *tail; i <= chain.CurrentBlock().Number.Uint64(); i++ { block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) if block.Transactions().Len() == 0 { continue } + for _, tx := range block.Transactions() { if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index == nil { t.Fatalf("Miss transaction indice, number %d hash %s", i, tx.Hash().Hex()) } } } + for i := uint64(0); i < *tail; i++ { block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) if block.Transactions().Len() == 0 { continue } + for _, tx := range block.Transactions() { if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index != nil { t.Fatalf("Transaction indice should be deleted, number %d hash %s", i, tx.Hash().Hex()) @@ -2750,6 +2957,7 @@ func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { // Import all blocks into ancient db, only HEAD-32 indices are kept. l := uint64(32) + chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) @@ -2761,6 +2969,7 @@ func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { for i, block := range blocks { headers[i] = block.Header() } + if n, err := chain.InsertHeaderChain(headers, 0); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } @@ -2768,6 +2977,7 @@ func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { if n, err := chain.InsertReceiptChain(blocks, receipts, 64); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } + tail := uint64(32) check(&tail, chain) } @@ -2796,30 +3006,38 @@ func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks in blockGenerator := func(i int, block *BlockGen) { block.SetCoinbase(common.Address{1}) + for txi := 0; txi < numTxs; txi++ { uniq := uint64(i*numTxs + txi) recipient := recipientFn(uniq) + tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, block.header.BaseFee, nil), signer, testBankKey) if err != nil { b.Error(err) } + block.AddTx(tx) } } _, shared, _ := GenerateChainWithGenesis(gspec, engine, numBlocks, blockGenerator) + b.StopTimer() b.ResetTimer() + for i := 0; i < b.N; i++ { // Import the shared chain and the original canonical one chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil, nil) if err != nil { b.Fatalf("failed to create tester chain: %v", err) } + b.StartTimer() + if _, err := chain.InsertChain(shared); err != nil { b.Fatalf("failed to insert shared chain: %v", err) } + b.StopTimer() block := chain.GetBlockByHash(chain.CurrentBlock().Hash()) @@ -2835,6 +3053,7 @@ func BenchmarkBlockChain_1x1000ValueTransferToNonexisting(b *testing.B) { numTxs = 1000 numBlocks = 1 ) + recipientFn := func(nonce uint64) common.Address { return common.BigToAddress(new(big.Int).SetUint64(1337 + nonce)) } @@ -2849,6 +3068,7 @@ func BenchmarkBlockChain_1x1000ValueTransferToExisting(b *testing.B) { numTxs = 1000 numBlocks = 1 ) + b.StopTimer() b.ResetTimer() @@ -2866,6 +3086,7 @@ func BenchmarkBlockChain_1x1000Executions(b *testing.B) { numTxs = 1000 numBlocks = 1 ) + b.StopTimer() b.ResetTimer() @@ -2922,6 +3143,7 @@ func TestSideImportPrunedBlocks(t *testing.T) { } // Now re-import some old blocks blockToReimport := blocks[5:8] + _, err = chain.InsertChain(blockToReimport) if err != nil { t.Errorf("Got error, %v", err) @@ -3048,6 +3270,7 @@ func TestDeleteRecreateSlots(t *testing.T) { if l := len(initCode); l > 32 { t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") } + bbCode := []byte{ // Push initcode onto stack byte(vm.PUSH1) + byte(len(initCode)-1)} @@ -3109,12 +3332,14 @@ func TestDeleteRecreateSlots(t *testing.T) { if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } + statedb, _ := chain.State() // If all is correct, then slot 1 and 2 are zero if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { t.Errorf("got %x exp %x", got, exp) } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { t.Errorf("got %x exp %x", got, exp) } @@ -3122,6 +3347,7 @@ func TestDeleteRecreateSlots(t *testing.T) { if got, exp := statedb.GetState(aa, common.HexToHash("03")), common.HexToHash("03"); got != exp { t.Fatalf("got %x exp %x", got, exp) } + if got, exp := statedb.GetState(aa, common.HexToHash("04")), common.HexToHash("04"); got != exp { t.Fatalf("got %x exp %x", got, exp) } @@ -3187,12 +3413,14 @@ func TestDeleteRecreateAccount(t *testing.T) { if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } + statedb, _ := chain.State() // If all is correct, then both slots are zero if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { t.Errorf("got %x exp %x", got, exp) } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { t.Errorf("got %x exp %x", got, exp) } @@ -3248,6 +3476,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { if l := len(initCode); l > 32 { t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") } + bbCode := []byte{ // Push initcode onto stack byte(vm.PUSH1) + byte(len(initCode)-1)} @@ -3284,6 +3513,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { }, }, } + var nonce uint64 type expectation struct { @@ -3291,12 +3521,15 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { blocknum int values map[int]int } + var current = &expectation{ exist: true, // exists in genesis blocknum: 0, values: map[int]int{1: 1, 2: 2}, } + var expectations []*expectation + var newDestruct = func(e *expectation, b *BlockGen) *types.Transaction { tx, _ := types.SignTx(types.NewTransaction(nonce, aa, big.NewInt(0), 50000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) @@ -3308,6 +3541,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { //t.Logf("block %d; adding destruct\n", e.blocknum) return tx } + var newResurrect = func(e *expectation, b *BlockGen) *types.Transaction { tx, _ := types.SignTx(types.NewTransaction(nonce, bb, big.NewInt(0), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) @@ -3324,24 +3558,31 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { var exp = new(expectation) exp.blocknum = i + 1 exp.values = make(map[int]int) + for k, v := range current.values { exp.values[k] = v } + exp.exist = current.exist b.SetCoinbase(common.Address{1}) + if i%2 == 0 { b.AddTx(newDestruct(exp, b)) } + if i%3 == 0 { b.AddTx(newResurrect(exp, b)) } + if i%5 == 0 { b.AddTx(newDestruct(exp, b)) } + if i%7 == 0 { b.AddTx(newResurrect(exp, b)) } + expectations = append(expectations, exp) current = exp }) @@ -3359,24 +3600,30 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { var asHash = func(num int) common.Hash { return common.BytesToHash([]byte{byte(num)}) } + for i, block := range blocks { blockNum := i + 1 + if n, err := chain.InsertChain([]*types.Block{block}); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } + statedb, _ := chain.State() // If all is correct, then slot 1 and 2 are zero if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { t.Errorf("block %d, got %x exp %x", blockNum, got, exp) } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { t.Errorf("block %d, got %x exp %x", blockNum, got, exp) } + exp := expectations[i] if exp.exist { if !statedb.Exist(aa) { t.Fatalf("block %d, expected %v to exist, it did not", blockNum, aa) } + for slot, val := range exp.values { if gotValue, expValue := statedb.GetState(aa, asHash(slot)), asHash(val); gotValue != expValue { t.Fatalf("block %d, slot %d, got %x exp %x", blockNum, slot, gotValue, expValue) @@ -3436,6 +3683,7 @@ func TestInitThenFailCreateContract(t *testing.T) { if l := len(initCode); l > 32 { t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") } + bbCode := []byte{ // Push initcode onto stack byte(vm.PUSH1) + byte(len(initCode)-1)} @@ -3474,6 +3722,7 @@ func TestInitThenFailCreateContract(t *testing.T) { tx, _ := types.SignTx(types.NewTransaction(nonce, bb, big.NewInt(0), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) b.AddTx(tx) + nonce++ }) @@ -3498,6 +3747,7 @@ func TestInitThenFailCreateContract(t *testing.T) { if _, err := chain.InsertChain([]*types.Block{blocks[0]}); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", block.NumberU64(), err) } + statedb, _ = chain.State() if got, exp := statedb.GetBalance(aa), big.NewInt(100000); got.Cmp(exp) != 0 { t.Fatalf("block %d: got %v exp %v", block.NumberU64(), got, exp) @@ -3653,6 +3903,7 @@ func TestEIP1559Transition(t *testing.T) { b.AddTx(tx) }) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) @@ -3681,6 +3932,7 @@ func TestEIP1559Transition(t *testing.T) { new(big.Int).SetUint64(block.GasUsed()*block.Transactions()[0].GasTipCap().Uint64()), ethash.ConstantinopleBlockReward, ) + if actual.Cmp(expected) != 0 { t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) } @@ -3688,6 +3940,7 @@ func TestEIP1559Transition(t *testing.T) { // 4: Ensure the tx sender paid for the gasUsed * (tip + block baseFee). actual = new(big.Int).Sub(funds, state.GetBalance(addr1)) expected = new(big.Int).SetUint64(block.GasUsed() * (block.Transactions()[0].GasTipCap().Uint64() + block.BaseFee().Uint64())) + if actual.Cmp(expected) != 0 { t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) } @@ -3721,6 +3974,7 @@ func TestEIP1559Transition(t *testing.T) { new(big.Int).SetUint64(block.GasUsed()*effectiveTip), ethash.ConstantinopleBlockReward, ) + if actual.Cmp(expected) != 0 { t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) } @@ -3728,6 +3982,7 @@ func TestEIP1559Transition(t *testing.T) { // 4: Ensure the tx sender paid for the gasUsed * (effectiveTip + block baseFee). actual = new(big.Int).Sub(funds, state.GetBalance(addr2)) expected = new(big.Int).SetUint64(block.GasUsed() * (effectiveTip + block.BaseFee().Uint64())) + if actual.Cmp(expected) != 0 { t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) } @@ -3737,6 +3992,7 @@ func TestEIP1559Transition(t *testing.T) { // It expects the state is recovered and all relevant chain markers are set correctly. func TestSetCanonical(t *testing.T) { t.Parallel() + var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") address = crypto.PubkeyToAddress(key.PublicKey) @@ -3758,6 +4014,7 @@ func TestSetCanonical(t *testing.T) { if err != nil { panic(err) } + gen.AddTx(tx) }) @@ -3778,6 +4035,7 @@ func TestSetCanonical(t *testing.T) { if err != nil { panic(err) } + gen.AddTx(tx) }) @@ -3813,6 +4071,7 @@ func TestSetCanonical(t *testing.T) { t.Fatalf("Lost block state %v %x", head.Number(), head.Hash()) } } + chain.SetCanonical(side[len(side)-1]) verify(side[len(side)-1]) @@ -3958,6 +4217,7 @@ func TestTxIndexer(t *testing.T) { _, blocks, receipts := GenerateChainWithGenesis(gspec, engine, 128, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey) gen.AddTx(tx) + nonce += 1 }) @@ -4217,7 +4477,9 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { if b.header.BaseFee != nil { fee = b.header.BaseFee } + b.SetCoinbase(common.Address{1}) + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ Nonce: nonce, GasPrice: new(big.Int).Set(fee), @@ -4225,6 +4487,7 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { Data: initCode, }) nonce++ + b.AddTx(tx) tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{ Nonce: nonce, @@ -4233,6 +4496,7 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { To: &destAddress, }) b.AddTx(tx) + nonce++ }) // Import the canonical chain @@ -4307,7 +4571,9 @@ func TestTransientStorageReset(t *testing.T) { if b.header.BaseFee != nil { fee = b.header.BaseFee } + b.SetCoinbase(common.Address{1}) + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ Nonce: nonce, GasPrice: new(big.Int).Set(fee), @@ -4315,6 +4581,7 @@ func TestTransientStorageReset(t *testing.T) { Data: initCode, }) nonce++ + b.AddTxWithVMConfig(tx, vmConfig) tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{ @@ -4324,6 +4591,7 @@ func TestTransientStorageReset(t *testing.T) { To: &destAddress, }) b.AddTxWithVMConfig(tx, vmConfig) + nonce++ }) diff --git a/core/blockstm/executor.go b/core/blockstm/executor.go index 7ce99f9492..6a73e102d1 100644 --- a/core/blockstm/executor.go +++ b/core/blockstm/executor.go @@ -448,11 +448,14 @@ func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResul if len(pe.estimateDeps[tx]) > 0 { estimate = pe.estimateDeps[tx][len(pe.estimateDeps[tx])-1] } + addedDependencies = pe.execTasks.addDependencies(estimate, tx) + newEstimate := estimate + (estimate+tx)/2 if newEstimate >= tx { newEstimate = tx - 1 } + pe.estimateDeps[tx] = append(pe.estimateDeps[tx], newEstimate) } @@ -461,6 +464,7 @@ func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResul if !addedDependencies { pe.execTasks.pushPending(tx) } + pe.txIncarnations[tx]++ pe.diagExecAbort[tx]++ pe.cntAbort++ @@ -497,6 +501,7 @@ func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResul pe.validateTasks.pushPending(tx) pe.execTasks.markComplete(tx) + pe.diagExecSuccess[tx]++ pe.cntSuccess++ @@ -521,6 +526,7 @@ func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResul pe.validateTasks.markComplete(tx) } else { pe.cntValidationFail++ + pe.diagExecAbort[tx]++ for _, v := range pe.lastTxIO.AllWriteSet(tx) { pe.mvh.MarkEstimate(v.Path, tx) diff --git a/core/blockstm/executor_test.go b/core/blockstm/executor_test.go index 511d9c774a..a1ad2bd946 100644 --- a/core/blockstm/executor_test.go +++ b/core/blockstm/executor_test.go @@ -286,6 +286,7 @@ func testExecutorComb(t *testing.T, totalTxs []int, numReads []int, numWrites [] if execDuration < expectedSerialDuration { improved++ } + total++ performance := greenTick @@ -331,6 +332,7 @@ func testExecutorCombWithMetadata(t *testing.T, totalTxs []int, numReads []int, if execDuration < expectedSerialDuration { improved++ } + total++ performance := greenTick diff --git a/core/blockstm/mvhashmap.go b/core/blockstm/mvhashmap.go index 2a517bcc84..3739609304 100644 --- a/core/blockstm/mvhashmap.go +++ b/core/blockstm/mvhashmap.go @@ -118,6 +118,7 @@ func (mv *MVHashMap) Write(k Key, v Version, data interface{}) { cells = n val, _ := mv.m.LoadOrStore(kenc, n) cells = val.(*TxnIndexCells) + return }) diff --git a/core/blockstm/status.go b/core/blockstm/status.go index 3025cf6c3e..29bb1461bb 100644 --- a/core/blockstm/status.go +++ b/core/blockstm/status.go @@ -38,8 +38,10 @@ func insertInList(l []int, v int) []int { // already in list return l } + a := append(l[:x+1], l[x:]...) a[x] = v + return a } } diff --git a/core/bloom_indexer.go b/core/bloom_indexer.go index 68a35d811e..0839637ec6 100644 --- a/core/bloom_indexer.go +++ b/core/bloom_indexer.go @@ -61,6 +61,7 @@ func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *ChainIndexer { 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 } @@ -69,6 +70,7 @@ func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHea 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 } @@ -76,13 +78,16 @@ func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error // writing it out into the database. func (b *BloomIndexer) Commit() error { batch := b.db.NewBatchWithSize((int(b.size) / 8) * types.BloomBitLength) + 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() } diff --git a/core/bloombits/generator.go b/core/bloombits/generator.go index 646151db0b..b21849025a 100644 --- a/core/bloombits/generator.go +++ b/core/bloombits/generator.go @@ -46,10 +46,12 @@ func NewGenerator(sections uint) (*Generator, error) { if sections%8 != 0 { return nil, errors.New("section count not multiple of 8") } + b := &Generator{sections: sections} for i := 0; i < types.BloomBitLength; i++ { b.blooms[i] = make([]byte, sections/8) } + return b, nil } @@ -60,17 +62,20 @@ func (b *Generator) AddBloom(index uint, bloom types.Bloom) error { if b.nextSec >= b.sections { return errSectionOutOfBounds } + if b.nextSec != index { return errors.New("bloom filter with unexpected index") } // Rotate the bloom and insert into our collection byteIndex := b.nextSec / 8 bitIndex := byte(7 - b.nextSec%8) + for byt := 0; byt < types.BloomByteLength; byt++ { bloomByte := bloom[types.BloomByteLength-1-byt] if bloomByte == 0 { continue } + base := 8 * byt b.blooms[base+7][byteIndex] |= ((bloomByte >> 7) & 1) << bitIndex b.blooms[base+6][byteIndex] |= ((bloomByte >> 6) & 1) << bitIndex @@ -81,7 +86,9 @@ func (b *Generator) AddBloom(index uint, bloom types.Bloom) error { b.blooms[base+1][byteIndex] |= ((bloomByte >> 1) & 1) << bitIndex b.blooms[base][byteIndex] |= (bloomByte & 1) << bitIndex } + b.nextSec++ + return nil } @@ -91,8 +98,10 @@ func (b *Generator) Bitset(idx uint) ([]byte, error) { if b.nextSec != b.sections { return nil, errors.New("bloom not fully generated yet") } + if idx >= types.BloomBitLength { return nil, errBloomBitOutOfBounds } + return b.blooms[idx], nil } diff --git a/core/bloombits/generator_test.go b/core/bloombits/generator_test.go index 4739d4d5c1..c59ed9a9f0 100644 --- a/core/bloombits/generator_test.go +++ b/core/bloombits/generator_test.go @@ -44,16 +44,19 @@ func TestGenerator(t *testing.T) { if err != nil { t.Fatalf("failed to create bloombit generator: %v", err) } + for i, bloom := range input { if err := gen.AddBloom(uint(i), bloom); err != nil { t.Fatalf("bloom %d: failed to add: %v", i, err) } } + for i, want := range output { have, err := gen.Bitset(uint(i)) if err != nil { t.Fatalf("output %d: failed to retrieve bits: %v", i, err) } + if !bytes.Equal(have, want[:]) { t.Errorf("output %d: bit vector mismatch have %x, want %x", i, have, want) } @@ -62,15 +65,18 @@ func TestGenerator(t *testing.T) { func BenchmarkGenerator(b *testing.B) { var input [types.BloomBitLength][types.BloomByteLength]byte + b.Run("empty", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() + for i := 0; i < b.N; i++ { // Crunch the input through the generator and verify the result gen, err := NewGenerator(types.BloomBitLength) if err != nil { b.Fatalf("failed to create bloombit generator: %v", err) } + for j, bloom := range &input { if err := gen.AddBloom(uint(j), bloom); err != nil { b.Fatalf("bloom %d: failed to add: %v", i, err) @@ -78,18 +84,21 @@ func BenchmarkGenerator(b *testing.B) { } } }) + for i := 0; i < types.BloomBitLength; i++ { _, _ = crand.Read(input[i][:]) } b.Run("random", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() + for i := 0; i < b.N; i++ { // Crunch the input through the generator and verify the result gen, err := NewGenerator(types.BloomBitLength) if err != nil { b.Fatalf("failed to create bloombit generator: %v", err) } + for j, bloom := range &input { if err := gen.AddBloom(uint(j), bloom); err != nil { b.Fatalf("bloom %d: failed to add: %v", i, err) diff --git a/core/bloombits/matcher.go b/core/bloombits/matcher.go index d8f932041b..7368944d09 100644 --- a/core/bloombits/matcher.go +++ b/core/bloombits/matcher.go @@ -42,6 +42,7 @@ func calcBloomIndexes(b []byte) bloomIndexes { for i := 0; i < len(idxs); i++ { idxs[i] = (uint(b[2*i])<<8)&2047 + uint(b[2*i+1]) } + return idxs } @@ -107,12 +108,15 @@ func NewMatcher(sectionSize uint64, filters [][][]byte) *Matcher { if len(filter) == 0 { continue } + bloomBits := make([]bloomIndexes, len(filter)) + for i, clause := range filter { if clause == nil { bloomBits = nil break } + bloomBits[i] = calcBloomIndexes(clause) } // Accumulate the filter rules if no nil rule was within @@ -128,6 +132,7 @@ func NewMatcher(sectionSize uint64, filters [][][]byte) *Matcher { } } } + return m } @@ -138,6 +143,7 @@ func (m *Matcher) addScheduler(idx uint) { if _, ok := m.schedulers[idx]; ok { return } + m.schedulers[idx] = newScheduler(idx) } @@ -157,13 +163,16 @@ func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uin quit: make(chan struct{}), ctx: ctx, } + for _, scheduler := range m.schedulers { scheduler.reset() } + sink := m.run(begin, end, cap(results), session) // Read the output from the result sink and deliver to the user session.pend.Add(1) + go func() { defer session.pend.Done() defer close(results) @@ -185,6 +194,7 @@ func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uin if begin > first { first = begin } + last := sectionStart + m.sectionSize - 1 if end < last { last = end @@ -197,6 +207,7 @@ func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uin if i%8 == 0 { i += 7 } + continue } // Some bit it set, do the actual submatching @@ -211,6 +222,7 @@ func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uin } } }() + return session, nil } @@ -226,6 +238,7 @@ func (m *Matcher) run(begin, end uint64, buffer int, session *MatcherSession) ch source := make(chan *partialMatches, buffer) session.pend.Add(1) + go func() { defer session.pend.Done() defer close(source) @@ -247,6 +260,7 @@ func (m *Matcher) run(begin, end uint64, buffer int, session *MatcherSession) ch } // Start the request distribution session.pend.Add(1) + go m.distributor(dist, session) return next @@ -260,6 +274,7 @@ func (m *Matcher) subMatch(source chan *partialMatches, dist chan *request, bloo // Start the concurrent schedulers for each bit required by the bloom filter sectionSources := make([][3]chan uint64, len(bloom)) sectionSinks := make([][3]chan []byte, len(bloom)) + for i, bits := range bloom { for j, bit := range bits { sectionSources[i][j] = make(chan uint64, cap(source)) @@ -273,6 +288,7 @@ func (m *Matcher) subMatch(source chan *partialMatches, dist chan *request, bloo results := make(chan *partialMatches, cap(source)) session.pend.Add(2) + go func() { // Tear down the goroutine and terminate all source channels defer session.pend.Done() @@ -334,8 +350,10 @@ func (m *Matcher) subMatch(source chan *partialMatches, dist chan *request, bloo } // Gather all the sub-results and merge them together var orVector []byte + for _, bloomSinks := range sectionSinks { var andVector []byte + for _, bitSink := range bloomSinks { var data []byte select { @@ -343,6 +361,7 @@ func (m *Matcher) subMatch(source chan *partialMatches, dist chan *request, bloo return case data = <-bitSink: } + if andVector == nil { andVector = make([]byte, int(m.sectionSize/8)) copy(andVector, data) @@ -350,6 +369,7 @@ func (m *Matcher) subMatch(source chan *partialMatches, dist chan *request, bloo bitutil.ANDBytes(andVector, andVector, data) } } + if orVector == nil { orVector = andVector } else { @@ -360,9 +380,11 @@ func (m *Matcher) subMatch(source chan *partialMatches, dist chan *request, bloo if orVector == nil { orVector = make([]byte, int(m.sectionSize/8)) } + if subres.bitset != nil { bitutil.ANDBytes(orVector, orVector, subres.bitset) } + if bitutil.TestBytes(orVector) { select { case <-session.quit: @@ -373,6 +395,7 @@ func (m *Matcher) subMatch(source chan *partialMatches, dist chan *request, bloo } } }() + return results } @@ -409,6 +432,7 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) { // Shutdown requested. No more retrievers can be allocated, // but we still need to wait until all pending requests have returned. shutdown = nil + if allocs == 0 { return } @@ -434,9 +458,11 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) { } // Stop tracking this bit (and alloc notifications if no more work is available) delete(unallocs, bit) + if len(unallocs) == 0 { retrievers = nil } + allocs++ fetcher <- bit @@ -469,15 +495,19 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) { bitsets = make([][]byte, 0, len(result.Bitsets)) missing = make([]uint64, 0, len(result.Sections)) ) + for i, bitset := range result.Bitsets { if len(bitset) == 0 { missing = append(missing, result.Sections[i]) continue } + sections = append(sections, result.Sections[i]) bitsets = append(bitsets, bitset) } + m.schedulers[result.Bit].deliver(sections, bitsets) + allocs-- // Reschedule missing sections and allocate bit if newly available @@ -487,6 +517,7 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) { index := sort.Search(len(queue), func(i int) bool { return queue[i] >= section }) queue = append(queue[:index], append([]uint64{section}, queue[index:]...)...) } + requests[result.Bit] = queue if len(queue) == len(missing) { @@ -579,6 +610,7 @@ func (s *MatcherSession) allocateSections(bit uint, count int) []uint64 { Sections: make([]uint64, count), } fetcher <- task + return (<-fetcher).Sections } } @@ -609,6 +641,7 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan // Session terminating, we can't meaningfully service, abort s.allocateSections(bit, 0) s.deliverSections(bit, []uint64{}, [][]byte{}) + return case <-time.After(wait): @@ -636,6 +669,7 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan s.errLock.Unlock() s.Close() } + s.deliverSections(result.Bit, result.Sections, result.Bitsets) } } diff --git a/core/bloombits/matcher_test.go b/core/bloombits/matcher_test.go index c0e3a76a1c..11d670b104 100644 --- a/core/bloombits/matcher_test.go +++ b/core/bloombits/matcher_test.go @@ -31,6 +31,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 @@ -44,12 +45,15 @@ func TestMatcherWildcards(t *testing.T) { if len(matcher.filters) != 3 { t.Fatalf("filter system size mismatch: have %d, want %d", len(matcher.filters), 3) } + if len(matcher.filters[0]) != 2 { t.Fatalf("address clause size mismatch: have %d, want %d", len(matcher.filters[0]), 2) } + if len(matcher.filters[1]) != 2 { t.Fatalf("combo topic clause size mismatch: have %d, want %d", len(matcher.filters[1]), 2) } + if len(matcher.filters[2]) != 1 { t.Fatalf("singletone topic clause size mismatch: have %d, want %d", len(matcher.filters[2]), 1) } @@ -75,6 +79,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) @@ -119,6 +124,7 @@ func makeRandomIndexes(lengths []int, max int) [][]bloomIndexes { } } } + return res } @@ -170,6 +176,7 @@ func testMatcher(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, in if err != nil { t.Fatalf("failed to stat matcher session: %v", err) } + startRetrievers(session, quit, &requested, maxReqCount) // Iterate over all the blocks and verify that the pipeline produces the correct matches @@ -180,6 +187,7 @@ func testMatcher(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, in t.Errorf("filter = %v blocks = %v intermittent = %v: expected #%v, results channel closed", filter, blocks, intermittent, i) return 0 } + if match != i { t.Errorf("filter = %v blocks = %v intermittent = %v: expected #%v, got #%v", filter, blocks, intermittent, i, match) } @@ -195,6 +203,7 @@ func testMatcher(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, in if err != nil { t.Fatalf("failed to stat matcher session: %v", err) } + startRetrievers(session, quit, &requested, maxReqCount) } } @@ -258,11 +267,13 @@ func generateBitset(bit uint, section uint64) []byte { for b := 0; b < 8; b++ { blockIdx := section*testSectionSize + uint64(i*8+b) bitset[i] += bitset[i] + if (blockIdx % uint64(bit)) == 0 { bitset[i]++ } } } + return bitset } @@ -272,6 +283,7 @@ func expMatch1(filter bloomIndexes, i uint64) bool { return false } } + return true } @@ -281,6 +293,7 @@ func expMatch2(filter []bloomIndexes, i uint64) bool { return true } } + return false } @@ -290,5 +303,6 @@ func expMatch3(filter [][]bloomIndexes, i uint64) bool { return false } } + return true } diff --git a/core/bloombits/scheduler.go b/core/bloombits/scheduler.go index 6449c7465a..062a16227a 100644 --- a/core/bloombits/scheduler.go +++ b/core/bloombits/scheduler.go @@ -63,6 +63,7 @@ func (s *scheduler) run(sections chan uint64, dist chan *request, done chan []by // Start the pipeline schedulers to forward between user -> distributor -> user wg.Add(2) + go s.scheduleRequests(sections, dist, pend, quit, wg) go s.scheduleDeliveries(pend, done, quit, wg) } diff --git a/core/bloombits/scheduler_test.go b/core/bloombits/scheduler_test.go index dcaaa91525..0fdd743df4 100644 --- a/core/bloombits/scheduler_test.go +++ b/core/bloombits/scheduler_test.go @@ -34,11 +34,13 @@ func TestSchedulerMultiClientMultiFetcher(t *testing.T) { testScheduler(t, 10, 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 // deliver them to the scheduler. var fetchPend sync.WaitGroup + fetchPend.Add(fetchers) defer fetchPend.Wait() @@ -46,6 +48,7 @@ func testScheduler(t *testing.T, clients int, fetchers int, requests int) { defer close(fetch) var delivered atomic.Uint32 + for i := 0; i < fetchers; i++ { go func() { defer fetchPend.Done() @@ -69,6 +72,7 @@ func testScheduler(t *testing.T, clients int, fetchers int, requests int) { quit := make(chan struct{}) var pend sync.WaitGroup + pend.Add(clients) for i := 0; i < clients; i++ { @@ -86,7 +90,9 @@ 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 := b.SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) { diff --git a/core/bor_blockchain.go b/core/bor_blockchain.go index 80a4b3ce28..d6a0226b02 100644 --- a/core/bor_blockchain.go +++ b/core/bor_blockchain.go @@ -26,5 +26,6 @@ func (bc *BlockChain) GetBorReceiptByHash(hash common.Hash) *types.Receipt { // add into bor receipt cache bc.borReceiptsCache.Add(hash, receipt) + return receipt } diff --git a/core/chain_indexer.go b/core/chain_indexer.go index 23ab23ef0f..46d532078d 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -135,6 +135,7 @@ func (c *ChainIndexer) AddCheckpoint(section uint64, shead common.Hash) { if c.checkpointSections >= section+1 || section < c.storedSections { return } + c.checkpointSections = section + 1 c.checkpointHead = shead @@ -162,12 +163,14 @@ func (c *ChainIndexer) Close() error { // Tear down the primary update loop errc := make(chan error) c.quit <- errc + if err := <-errc; err != nil { errs = append(errs, err) } // If needed, tear down the secondary event loop if c.active.Load() { c.quit <- errc + if err := <-errc; err != nil { errs = append(errs, err) } @@ -207,6 +210,7 @@ func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainH prevHeader = currentHeader prevHash = currentHeader.Hash() ) + for { select { case errc := <-c.quit: @@ -219,19 +223,21 @@ func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainH if !ok { errc := <-c.quit errc <- nil + return } + header := ev.Block.Header() if header.ParentHash != prevHash { // Reorg to the common ancestor if needed (might not exist in light sync mode, skip reorg then) // TODO(karalabe, zsfelfoldi): This seems a bit brittle, can we detect this case explicitly? - if rawdb.ReadCanonicalHash(c.chainDb, prevHeader.Number.Uint64()) != prevHash { if h := rawdb.FindCommonAncestor(c.chainDb, prevHeader, header); h != nil { c.newHead(h.Number.Uint64(), true) } } } + c.newHead(header.Number.Uint64(), false) prevHeader, prevHash = header, header.Hash() @@ -249,12 +255,15 @@ func (c *ChainIndexer) newHead(head uint64, reorg bool) { // Revert the known section number to the reorg point known := (head + 1) / c.sectionSize stored := known + if known < c.checkpointSections { known = 0 } + if stored < c.checkpointSections { stored = c.checkpointSections } + if known < c.knownSections { c.knownSections = known } @@ -271,6 +280,7 @@ func (c *ChainIndexer) newHead(head uint64, reorg bool) { child.newHead(c.cascadedHead, true) } } + return } // No reorg, calculate the number of newly known sections and update if high enough @@ -280,6 +290,7 @@ func (c *ChainIndexer) newHead(head uint64, reorg bool) { if sections < c.checkpointSections { sections = 0 } + if sections > c.knownSections { if c.knownSections < c.checkpointSections { // syncing reached the checkpoint, verify section head @@ -289,6 +300,7 @@ func (c *ChainIndexer) newHead(head uint64, reorg bool) { return } } + c.knownSections = sections select { @@ -322,19 +334,24 @@ func (c *ChainIndexer) updateLoop() { if time.Since(updated) > 8*time.Second { if c.knownSections > c.storedSections+1 { updating = true + c.log.Info("Upgrading chain index", "percentage", c.storedSections*100/c.knownSections) } + updated = time.Now() } // Cache the current section count and head to allow unlocking the mutex c.verifyLastHead() section := c.storedSections + var oldHead common.Hash + if section > 0 { oldHead = c.SectionHead(section - 1) } // Process the newly defined section in the background c.lock.Unlock() + newHead, err := c.processSection(section, oldHead) if err != nil { select { @@ -345,16 +362,20 @@ func (c *ChainIndexer) updateLoop() { } c.log.Error("Section processing failed", "error", err) } + c.lock.Lock() // If processing succeeded and no reorgs occurred, mark the section completed if err == nil && (section == 0 || oldHead == c.SectionHead(section-1)) { c.setSectionHead(section, newHead) c.setValidSections(section + 1) + if c.storedSections == c.knownSections && updating { updating = false + c.log.Info("Finished upgrading chain index") } + c.cascadedHead = c.storedSections*c.sectionSize - 1 for _, child := range c.children { c.log.Trace("Cascading chain index update", "head", c.cascadedHead) @@ -399,20 +420,25 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com if hash == (common.Hash{}) { return common.Hash{}, fmt.Errorf("canonical block #%d unknown", number) } + header := rawdb.ReadHeader(c.chainDb, hash, number) if header == nil { return common.Hash{}, fmt.Errorf("block #%d [%x..] not found", number, hash[:4]) } else if header.ParentHash != lastHead { return common.Hash{}, fmt.Errorf("chain reorged during section processing") } + if err := c.backend.Process(c.ctx, header); err != nil { return common.Hash{}, err } + lastHead = header.Hash() } + if err := c.backend.Commit(); err != nil { return common.Hash{}, err } + return lastHead, nil } @@ -424,6 +450,7 @@ func (c *ChainIndexer) verifyLastHead() { if c.SectionHead(c.storedSections-1) == rawdb.ReadCanonicalHash(c.chainDb, c.storedSections*c.sectionSize-1) { return } + c.setValidSections(c.storedSections - 1) } } @@ -436,6 +463,7 @@ func (c *ChainIndexer) Sections() (uint64, uint64, common.Hash) { defer c.lock.Unlock() c.verifyLastHead() + return c.storedSections, c.storedSections*c.sectionSize - 1, c.SectionHead(c.storedSections - 1) } @@ -444,6 +472,7 @@ func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) { if indexer == c { panic("can't add indexer as a child of itself") } + c.lock.Lock() defer c.lock.Unlock() @@ -456,6 +485,7 @@ func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) { // available chain data so we should not cascade it yet sections = c.knownSections } + if sections > 0 { indexer.newHead(sections*c.sectionSize-1, false) } @@ -479,6 +509,7 @@ func (c *ChainIndexer) loadValidSections() { func (c *ChainIndexer) setValidSections(sections uint64) { // Set the current number of valid sections in the database var data [8]byte + binary.BigEndian.PutUint64(data[:], sections) c.indexDb.Put([]byte("count"), data[:]) @@ -487,6 +518,7 @@ func (c *ChainIndexer) setValidSections(sections uint64) { c.storedSections-- c.removeSectionHead(c.storedSections) } + c.storedSections = sections // needed if new > old } @@ -494,12 +526,14 @@ func (c *ChainIndexer) setValidSections(sections uint64) { // index database. func (c *ChainIndexer) SectionHead(section uint64) common.Hash { var data [8]byte + binary.BigEndian.PutUint64(data[:], section) hash, _ := c.indexDb.Get(append([]byte("shead"), data[:]...)) if len(hash) == len(common.Hash{}) { return common.BytesToHash(hash) } + return common.Hash{} } @@ -507,6 +541,7 @@ func (c *ChainIndexer) SectionHead(section uint64) common.Hash { // database. func (c *ChainIndexer) setSectionHead(section uint64, hash common.Hash) { var data [8]byte + binary.BigEndian.PutUint64(data[:], section) c.indexDb.Put(append([]byte("shead"), data[:]...), hash.Bytes()) @@ -516,6 +551,7 @@ func (c *ChainIndexer) setSectionHead(section uint64, hash common.Hash) { // database. func (c *ChainIndexer) removeSectionHead(section uint64) { var data [8]byte + binary.BigEndian.PutUint64(data[:], section) c.indexDb.Delete(append([]byte("shead"), data[:]...)) diff --git a/core/chain_indexer_test.go b/core/chain_indexer_test.go index f099609015..e39a89e3e8 100644 --- a/core/chain_indexer_test.go +++ b/core/chain_indexer_test.go @@ -54,17 +54,20 @@ func testChainIndexer(t *testing.T, count int) { // Create a chain of indexers and ensure they all report empty backends := make([]*testChainIndexBackend, count) + for i := 0; i < count; i++ { var ( sectionSize = uint64(rand.Intn(100) + 1) confirmsReq = uint64(rand.Intn(10)) ) + backends[i] = &testChainIndexBackend{t: t, processCh: make(chan uint64)} backends[i].indexer = NewChainIndexer(db, rawdb.NewTable(db, string([]byte{byte(i)})), backends[i], sectionSize, confirmsReq, 0, fmt.Sprintf("indexer-%d", i)) if sections, _, _ := backends[i].indexer.Sections(); sections != 0 { t.Fatalf("Canonical section count mismatch: have %v, want %v", sections, 0) } + if i > 0 { backends[i-1].indexer.AddChildIndexer(backends[i].indexer) } @@ -74,19 +77,23 @@ func testChainIndexer(t *testing.T, count int) { // processed blocks if a section is processable notify := func(headNum, failNum uint64, reorg bool) { backends[0].indexer.newHead(headNum, reorg) + if reorg { for _, backend := range backends { headNum = backend.reorg(headNum) backend.assertSections() } + return } + var cascade bool for _, backend := range backends { headNum, cascade = backend.assertBlocks(headNum, failNum) if !cascade { break } + backend.assertSections() } } @@ -96,6 +103,7 @@ func testChainIndexer(t *testing.T, count int) { if number > 0 { header.ParentHash = rawdb.ReadCanonicalHash(db, number-1) } + rawdb.WriteHeader(db, header) rawdb.WriteCanonicalHash(db, header.Hash(), number) } @@ -118,6 +126,7 @@ func testChainIndexer(t *testing.T, count int) { inject(i) notify(i, i, false) } + for i := uint64(1001); i <= 1500; i++ { inject(i) } @@ -151,6 +160,7 @@ func (b *testChainIndexBackend) assertSections() { if sections == b.stored { return } + time.Sleep(10 * time.Millisecond) } b.t.Fatalf("Canonical section count mismatch: have %v, want %v", sections, b.stored) @@ -170,19 +180,25 @@ func (b *testChainIndexBackend) assertBlocks(headNum, failNum uint64) (uint64, b // rolled back after processing started, no more process calls expected // wait until updating is done to make sure that processing actually fails var updating bool + for i := 0; i < 300; i++ { b.indexer.lock.Lock() updating = b.indexer.knownSections > b.indexer.storedSections b.indexer.lock.Unlock() + if !updating { break } + time.Sleep(10 * time.Millisecond) } + if updating { b.t.Fatalf("update did not finish") } + sections = expectd / b.indexer.sectionSize + break } select { @@ -194,12 +210,15 @@ func (b *testChainIndexBackend) assertBlocks(headNum, failNum uint64) (uint64, b } } } + b.stored = sections } } + if b.stored == 0 { return 0, false } + return b.stored*b.indexer.sectionSize - 1, true } @@ -208,12 +227,14 @@ func (b *testChainIndexBackend) reorg(headNum uint64) uint64 { if firstChanged < b.stored { b.stored = firstChanged } + return b.stored * b.indexer.sectionSize } func (b *testChainIndexBackend) Reset(ctx context.Context, section uint64, prevHead common.Hash) error { b.section = section b.headerCnt = 0 + return nil } @@ -231,6 +252,7 @@ func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Heade return errors.New("Unexpected call to Process") case b.processCh <- header.Number.Uint64(): } + return nil } @@ -238,6 +260,7 @@ func (b *testChainIndexBackend) Commit() error { if b.headerCnt != b.indexer.sectionSize { b.t.Error("Not enough headers processed") } + return nil } diff --git a/core/chain_makers.go b/core/chain_makers.go index 277d79d404..ab35f5f67f 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -60,8 +60,10 @@ func (b *BlockGen) SetCoinbase(addr common.Address) { if len(b.txs) > 0 { panic("coinbase must be set before adding transactions") } + panic("coinbase can only be set once") } + b.header.Coinbase = addr b.gasPool = new(GasPool).AddGas(b.header.GasLimit) } @@ -134,10 +136,12 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { b.addTx(bc, vm.Config{}, tx) b.statedb.SetTxContext(tx.Hash(), len(b.txs)) + receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}, nil) if err != nil { panic(err) } + b.txs = append(b.txs, tx) b.receipts = append(b.receipts, receipt) } @@ -193,6 +197,7 @@ func (b *BlockGen) TxNonce(addr common.Address) uint64 { if !b.statedb.Exist(addr) { panic("account does not exist") } + return b.statedb.GetNonce(addr) } @@ -202,12 +207,14 @@ func (b *BlockGen) AddUncle(h *types.Header) { h.Time = b.header.Time var parent *types.Header + for i := b.i - 1; i >= 0; i-- { if b.chain[i].Hash() == h.ParentHash { parent = b.chain[i].Header() break } } + chainreader := &fakeChainReader{config: b.config} h.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, parent) @@ -215,11 +222,13 @@ func (b *BlockGen) AddUncle(h *types.Header) { h.GasLimit = parent.GasLimit if b.config.IsLondon(h.Number) { h.BaseFee = misc.CalcBaseFee(b.config, parent) + if !b.config.IsLondon(parent.Number) { parentGasLimit := parent.GasLimit * b.config.ElasticityMultiplier() h.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) } } + b.uncles = append(b.uncles, h) } @@ -262,9 +271,11 @@ func (b *BlockGen) PrevBlock(index int) *types.Block { if index >= b.i { panic(fmt.Errorf("block index %d out of range (%d,%d)", index, -1, b.i)) } + if index == -1 { return b.parent } + return b.chain[index] } @@ -276,6 +287,7 @@ func (b *BlockGen) OffsetTime(seconds int64) { if b.header.Time <= b.parent.Header().Time { panic("block time out of range") } + chainreader := &fakeChainReader{config: b.config} b.header.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, b.parent.Header()) } @@ -296,6 +308,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if config == nil { config = params.TestChainConfig } + blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n) chainreader := &fakeChainReader{config: config} genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) { @@ -323,6 +336,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } } } + if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 { misc.ApplyDAOHardFork(statedb) } @@ -330,6 +344,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if gen != nil { gen(i, b) } + if b.engine != nil { block, err := b.engine.FinalizeAndAssemble(context.Background(), chainreader, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals) if err != nil { @@ -345,20 +360,25 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if err := statedb.Database().TrieDB().Commit(root, false); err != nil { panic(fmt.Sprintf("trie write error: %v", err)) } + return block, b.receipts } + return nil, nil } + for i := 0; i < n; i++ { statedb, err := state.New(parent.Root(), state.NewDatabase(db), nil) if err != nil { panic(err) } + block, receipt := genblock(i, parent, statedb) blocks[i] = block receipts[i] = receipt parent = block } + return blocks, receipts } @@ -385,6 +405,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S } else { time = parent.Time() + 10 // block time is fixed at 10 seconds } + header := &types.Header{ Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())), ParentHash: parent.Hash(), @@ -401,11 +422,13 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S } if chain.Config().IsLondon(header.Number) { header.BaseFee = misc.CalcBaseFee(chain.Config(), parent.Header()) + if !chain.Config().IsLondon(parent.Number()) { parentGasLimit := parent.GasLimit() * chain.Config().ElasticityMultiplier() header.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) } } + return header } @@ -413,9 +436,11 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S func makeHeaderChain(chainConfig *params.ChainConfig, parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header { blocks := makeBlockChain(chainConfig, types.NewBlockWithHeader(parent), n, engine, db, seed) headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { headers[i] = block.Header() } + return headers } @@ -454,6 +479,7 @@ func makeFakeNonEmptyBlockChain(parent *types.Block, n int, engine consensus.Eng blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) { addr := common.Address{0: byte(seed), 19: byte(i)} b.SetCoinbase(addr) + for j := 0; j < numTx; j++ { b.txs = append(b.txs, types.NewTransaction(0, addr, big.NewInt(1000), params.TxGas, nil, nil)) } diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 98a8cb4ec4..cdbe86cca6 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -82,6 +82,7 @@ func TestGenerateWithdrawalChain(t *testing.T) { chain, _ := GenerateChain(gspec.Config, genesis, beacon.NewFaker(), gendb, 4, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(address), address, big.NewInt(1000), params.TxGas, new(big.Int).Add(gen.BaseFee(), common.Big1), nil), signer, key) gen.AddTx(tx) + if i == 1 { gen.AddWithdrawal(&types.Withdrawal{ Validator: 42, @@ -94,6 +95,7 @@ func TestGenerateWithdrawalChain(t *testing.T) { Amount: 1, }) } + if i == 3 { gen.AddWithdrawal(&types.Withdrawal{ Validator: 42, diff --git a/core/dao_test.go b/core/dao_test.go index e15742eb91..491f3453b7 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -50,6 +50,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), Config: &proConf, } + proBc, _ := NewBlockChain(proDb, nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer proBc.Stop() @@ -62,12 +63,14 @@ func TestDAOForkRangeExtradata(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), Config: &conConf, } + conBc, _ := NewBlockChain(conDb, nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer conBc.Stop() if _, err := proBc.InsertChain(prefix); err != nil { t.Fatalf("pro-fork: failed to import chain prefix: %v", err) } + if _, err := conBc.InsertChain(prefix); err != nil { t.Fatalf("con-fork: failed to import chain prefix: %v", err) } @@ -80,6 +83,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { for j := 0; j < len(blocks)/2; j++ { blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j] } + if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import contra-fork chain for expansion: %v", err) } @@ -106,6 +110,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { for j := 0; j < len(blocks)/2; j++ { blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j] } + if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import pro-fork chain for expansion: %v", err) } @@ -134,6 +139,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { for j := 0; j < len(blocks)/2; j++ { blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j] } + if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import contra-fork chain for expansion: %v", err) } diff --git a/core/evm.go b/core/evm.go index 979bcf1716..c067f88a74 100644 --- a/core/evm.go +++ b/core/evm.go @@ -50,12 +50,15 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common } else { beneficiary = *author } + if header.BaseFee != nil { baseFee = new(big.Int).Set(header.BaseFee) } + if header.Difficulty.Cmp(common.Big0) == 0 { random = &header.MixDigest } + return vm.BlockContext{ CanTransfer: CanTransfer, Transfer: Transfer, @@ -100,6 +103,7 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash if len(cache) == 0 { cache = append(cache, ref.ParentHash) } + if idx := ref.Number.Uint64() - n - 1; idx < uint64(len(cache)) { return cache[idx] } @@ -112,13 +116,16 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash if header == nil { break } + cache = append(cache, header.ParentHash) lastKnownHash = header.ParentHash lastKnownNumber = header.Number.Uint64() - 1 + if n == lastKnownNumber { return lastKnownHash } } + return common.Hash{} } } diff --git a/core/forkchoice.go b/core/forkchoice.go index c9c38fb95f..2fb3994dac 100644 --- a/core/forkchoice.go +++ b/core/forkchoice.go @@ -64,6 +64,7 @@ type Floater interface { func NewForkChoice(chainReader ChainReader, preserve func(header *types.Header) bool, validator ethereum.ChainValidator) *ForkChoice { // Seed a fast but crypto originating random generator r := crand.NewRand() + return &ForkChoice{ chain: chainReader, rand: r, @@ -82,6 +83,7 @@ func (f *ForkChoice) ReorgNeeded(current *types.Header, extern *types.Header) (b localTD = f.chain.GetTd(current.Hash(), current.Number.Uint64()) externTd = f.chain.GetTd(extern.Hash(), extern.Number.Uint64()) ) + if localTD == nil || externTd == nil { return false, errors.New("missing td") } @@ -111,8 +113,10 @@ func (f *ForkChoice) ReorgNeeded(current *types.Header, extern *types.Header) (b if f.preserve != nil { currentPreserve, externPreserve = f.preserve(current), f.preserve(extern) } + reorg = !currentPreserve && (externPreserve || f.rand.Float64() < 0.5) } + return reorg, nil } diff --git a/core/forkchoice_test.go b/core/forkchoice_test.go index f05893ca92..c288d28b17 100644 --- a/core/forkchoice_test.go +++ b/core/forkchoice_test.go @@ -39,6 +39,7 @@ func TestPastChainInsert(t *testing.T) { ) gspec.Commit(db, trie.NewDatabase(db)) + hc, err := NewHeaderChain(db, gspec.Config, ethash.NewFaker(), func() bool { return false }) if err != nil { t.Fatal(err) @@ -110,6 +111,7 @@ func TestFutureChainInsert(t *testing.T) { ) gspec.Commit(db, trie.NewDatabase(db)) + hc, err := NewHeaderChain(db, gspec.Config, ethash.NewFaker(), func() bool { return false }) if err != nil { t.Fatal(err) @@ -169,6 +171,7 @@ func TestOverlappingChainInsert(t *testing.T) { ) gspec.Commit(db, trie.NewDatabase(db)) + hc, err := NewHeaderChain(db, gspec.Config, ethash.NewFaker(), func() bool { return false }) if err != nil { t.Fatal(err) diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 4d48299c7f..29c9c84d0a 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -144,8 +144,10 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u forks = append(append([]uint64{}, forksByBlock...), forksByTime...) sums = make([][4]byte, len(forks)+1) // 0th is the genesis ) + hash := crc32.ChecksumIEEE(genesis[:]) sums[0] = checksumToBytes(hash) + for i, fork := range forks { hash = checksumUpdate(hash, fork) sums[i+1] = checksumToBytes(hash) @@ -210,6 +212,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u if forks[j] != id.Next { return ErrRemoteStale } + return nil } } @@ -224,7 +227,9 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u // No exact, subset or superset match. We are on differing chains, reject. return ErrLocalIncompatibleOrStale } + log.Error("Impossible fork ID validation", "id", id) + return nil // Something's very wrong, accept rather than reject } } @@ -233,14 +238,18 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u // one and a fork block number (equivalent to CRC32(original-blob || fork)). func checksumUpdate(hash uint32, fork uint64) uint32 { var blob [8]byte + binary.BigEndian.PutUint64(blob[:], fork) + return crc32.Update(hash, crc32.IEEETable, blob[:]) } // checksumToBytes converts a uint32 checksum into a [4]byte array. func checksumToBytes(hash uint32) [4]byte { var blob [4]byte + binary.BigEndian.PutUint32(blob[:], hash) + return blob } @@ -256,6 +265,7 @@ func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) { forksByBlock []uint64 forksByTime []uint64 ) + for i := 0; i < kind.NumField(); i++ { // Fetch the next field and skip non-fork rules field := kind.Field(i) diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index 1dc65aec36..2c2fbb2910 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -34,6 +34,7 @@ func TestCreation(t *testing.T) { time uint64 want ID } + tests := []struct { config *params.ChainConfig genesis common.Hash @@ -377,6 +378,7 @@ func TestEncoding(t *testing.T) { t.Errorf("test %d: failed to encode forkid: %v", i, err) continue } + if !bytes.Equal(have, tt.want) { t.Errorf("test %d: RLP mismatch: have %x, want %x", i, have, tt.want) } diff --git a/core/gaspool.go b/core/gaspool.go index 767222674f..564f059016 100644 --- a/core/gaspool.go +++ b/core/gaspool.go @@ -30,7 +30,9 @@ func (gp *GasPool) AddGas(amount uint64) *GasPool { if uint64(*gp) > math.MaxUint64-amount { panic("gas pool pushed above uint64") } + *(*uint64)(gp) += amount + return gp } @@ -40,7 +42,9 @@ func (gp *GasPool) SubGas(amount uint64) error { if uint64(*gp) < amount { return ErrGasLimitReached } + *(*uint64)(gp) -= amount + return nil } diff --git a/core/gen_genesis.go b/core/gen_genesis.go index 4e0844e889..f86024b06a 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -32,6 +32,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) { ParentHash common.Hash `json:"parentHash"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` } + var enc Genesis enc.Config = g.Config enc.Nonce = math.HexOrDecimal64(g.Nonce) @@ -41,16 +42,19 @@ func (g Genesis) MarshalJSON() ([]byte, error) { enc.Difficulty = (*math.HexOrDecimal256)(g.Difficulty) enc.Mixhash = g.Mixhash enc.Coinbase = g.Coinbase + if g.Alloc != nil { enc.Alloc = make(map[common.UnprefixedAddress]GenesisAccount, len(g.Alloc)) for k, v := range g.Alloc { enc.Alloc[common.UnprefixedAddress(k)] = v } } + enc.Number = math.HexOrDecimal64(g.Number) enc.GasUsed = math.HexOrDecimal64(g.GasUsed) enc.ParentHash = g.ParentHash enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee) + return json.Marshal(&enc) } @@ -71,54 +75,71 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { ParentHash *common.Hash `json:"parentHash"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` } + var dec Genesis if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Config != nil { g.Config = dec.Config } + if dec.Nonce != nil { g.Nonce = uint64(*dec.Nonce) } + if dec.Timestamp != nil { g.Timestamp = uint64(*dec.Timestamp) } + if dec.ExtraData != nil { g.ExtraData = *dec.ExtraData } + if dec.GasLimit == nil { return errors.New("missing required field 'gasLimit' for Genesis") } + g.GasLimit = uint64(*dec.GasLimit) + if dec.Difficulty == nil { return errors.New("missing required field 'difficulty' for Genesis") } + g.Difficulty = (*big.Int)(dec.Difficulty) if dec.Mixhash != nil { g.Mixhash = *dec.Mixhash } + if dec.Coinbase != nil { g.Coinbase = *dec.Coinbase } + if dec.Alloc == nil { return errors.New("missing required field 'alloc' for Genesis") } + g.Alloc = make(GenesisAlloc, len(dec.Alloc)) for k, v := range dec.Alloc { g.Alloc[common.Address(k)] = v } + if dec.Number != nil { g.Number = uint64(*dec.Number) } + if dec.GasUsed != nil { g.GasUsed = uint64(*dec.GasUsed) } + if dec.ParentHash != nil { g.ParentHash = *dec.ParentHash } + if dec.BaseFee != nil { g.BaseFee = (*big.Int)(dec.BaseFee) } + return nil } diff --git a/core/gen_genesis_account.go b/core/gen_genesis_account.go index a9d47e6ba3..d21280e257 100644 --- a/core/gen_genesis_account.go +++ b/core/gen_genesis_account.go @@ -23,7 +23,9 @@ func (g GenesisAccount) MarshalJSON() ([]byte, error) { Nonce math.HexOrDecimal64 `json:"nonce,omitempty"` PrivateKey hexutil.Bytes `json:"secretKey,omitempty"` } + var enc GenesisAccount + enc.Code = g.Code if g.Storage != nil { enc.Storage = make(map[storageJSON]storageJSON, len(g.Storage)) @@ -31,9 +33,11 @@ func (g GenesisAccount) MarshalJSON() ([]byte, error) { enc.Storage[storageJSON(k)] = storageJSON(v) } } + enc.Balance = (*math.HexOrDecimal256)(g.Balance) enc.Nonce = math.HexOrDecimal64(g.Nonce) enc.PrivateKey = g.PrivateKey + return json.Marshal(&enc) } @@ -46,28 +50,35 @@ func (g *GenesisAccount) UnmarshalJSON(input []byte) error { Nonce *math.HexOrDecimal64 `json:"nonce,omitempty"` PrivateKey *hexutil.Bytes `json:"secretKey,omitempty"` } + var dec GenesisAccount if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Code != nil { g.Code = *dec.Code } + if dec.Storage != nil { g.Storage = make(map[common.Hash]common.Hash, len(dec.Storage)) for k, v := range dec.Storage { g.Storage[common.Hash(k)] = common.Hash(v) } } + if dec.Balance == nil { return errors.New("missing required field 'balance' for GenesisAccount") } + g.Balance = (*big.Int)(dec.Balance) if dec.Nonce != nil { g.Nonce = uint64(*dec.Nonce) } + if dec.PrivateKey != nil { g.PrivateKey = *dec.PrivateKey } + return nil } diff --git a/core/genesis.go b/core/genesis.go index 5780d82954..37ccb9e43a 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -122,10 +122,12 @@ func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &m); err != nil { return err } + *ga = make(GenesisAlloc) for addr, a := range m { (*ga)[common.Address(addr)] = a } + return nil } @@ -134,14 +136,17 @@ func (ga *GenesisAlloc) deriveHash() (common.Hash, error) { // Create an ephemeral in-memory database for computing hash, // all the derived states will be discarded to not pollute disk. db := state.NewDatabase(rawdb.NewMemoryDatabase()) + statedb, err := state.New(common.Hash{}, db, nil) if err != nil { return common.Hash{}, err } + for addr, account := range *ga { statedb.AddBalance(addr, account.Balance) statedb.SetCode(addr, account.Code) statedb.SetNonce(addr, account.Nonce) + for key, value := range account.Storage { statedb.SetState(addr, key, value) } @@ -186,6 +191,7 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas } rawdb.WriteGenesisStateSpec(db, blockhash, blob) + return nil } @@ -206,6 +212,7 @@ func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash comm // - supported networks(mainnet, testnets), recover with defined allocations // - private network, can't recover var genesis *Genesis + switch blockhash { case params.MainnetGenesisHash: genesis = DefaultGenesisBlock() @@ -216,6 +223,7 @@ func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash comm case params.SepoliaGenesisHash: genesis = DefaultSepoliaGenesisBlock() } + if genesis != nil { alloc = genesis.Alloc } else { @@ -265,10 +273,12 @@ func (h *storageJSON) UnmarshalText(text []byte) error { if len(text) > 64 { return fmt.Errorf("too many hex characters in storage key/value %q", text) } + offset := len(h) - len(text)/2 // pad on the left if _, err := hex.Decode(h[offset:], text); err != nil { return fmt.Errorf("invalid hex storage key/value %q", text) } + return nil } @@ -326,6 +336,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen if (stored == common.Hash{}) { if genesis == nil { log.Info("Writing default main-net genesis block") + genesis = DefaultGenesisBlock() } else { log.Info("Writing custom genesis block") @@ -337,6 +348,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen } applyOverrides(genesis.Config) + return genesis.Config, block.Hash(), nil } // We have the genesis block in database(perhaps in ancient database) @@ -358,6 +370,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen } applyOverrides(genesis.Config) + return genesis.Config, block.Hash(), nil } // Check whether the genesis block is already written. @@ -370,13 +383,16 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen // Get the existing chain configuration. newcfg := genesis.configOrDefault(stored) applyOverrides(newcfg) + if err := newcfg.CheckConfigForkOrder(); err != nil { return newcfg, common.Hash{}, err } + storedcfg := rawdb.ReadChainConfig(db, stored) if storedcfg == nil { log.Warn("Found genesis block without chain config") rawdb.WriteChainConfig(db, stored, newcfg) + return newcfg, stored, nil } @@ -408,6 +424,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen if newData, _ := json.Marshal(newcfg); !bytes.Equal(storedData, newData) { rawdb.WriteChainConfig(db, stored, newcfg) } + return newcfg, stored, nil } @@ -478,6 +495,7 @@ func (g *Genesis) ToBlock() *types.Block { if err != nil { panic(err) } + head := &types.Header{ Number: new(big.Int).SetUint64(g.Number), Nonce: types.EncodeNonce(g.Nonce), @@ -495,9 +513,11 @@ func (g *Genesis) ToBlock() *types.Block { if g.GasLimit == 0 { head.GasLimit = params.GenesisGasLimit } + if g.Difficulty == nil && g.Mixhash == (common.Hash{}) { head.Difficulty = params.GenesisDifficulty } + if g.Config != nil && g.Config.IsLondon(common.Big0) { if g.BaseFee != nil { head.BaseFee = g.BaseFee @@ -523,10 +543,12 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block if block.Number().Sign() != 0 { return nil, errors.New("can't commit genesis block with number > 0") } + config := g.Config if config == nil { config = params.AllEthashProtocolChanges } + if err := config.CheckConfigForkOrder(); err != nil { return nil, err } @@ -540,6 +562,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block if err := g.Alloc.flush(db, triedb, block.Hash()); err != nil { return nil, err } + rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty()) rawdb.WriteBlock(db, block) rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil) @@ -548,6 +571,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block rawdb.WriteHeadFastBlockHash(db, block.Hash()) rawdb.WriteHeadHeaderHash(db, block.Hash()) rawdb.WriteChainConfig(db, block.Hash(), config) + return block, nil } @@ -560,6 +584,7 @@ func (g *Genesis) MustCommit(db ethdb.Database) *types.Block { if err != nil { panic(err) } + return block } @@ -643,9 +668,11 @@ func DefaultBorMainnetGenesisBlock() *Genesis { func DefaultKilnGenesisBlock() *Genesis { g := new(Genesis) reader := strings.NewReader(KilnAllocData) + if err := json.NewDecoder(reader).Decode(g); err != nil { panic(err) } + return g } @@ -685,10 +712,12 @@ func decodePrealloc(data string) GenesisAlloc { if err := rlp.NewStream(strings.NewReader(data), 0).Decode(&p); err != nil { panic(err) } + ga := make(GenesisAlloc, len(p)) for _, account := range p { ga[common.BigToAddress(account.Addr)] = GenesisAccount{Balance: account.Balance} } + return ga } @@ -697,12 +726,15 @@ func readPrealloc(filename string) GenesisAlloc { if err != nil { panic(fmt.Sprintf("Could not open genesis preallocation for %s: %v", filename, err)) } + defer f.Close() decoder := json.NewDecoder(f) ga := make(GenesisAlloc) + err = decoder.Decode(&ga) if err != nil { panic(fmt.Sprintf("Could not parse genesis preallocation for %s: %v", filename, err)) } + return ga } diff --git a/core/genesis_test.go b/core/genesis_test.go index b0f4525112..d0a11a99e5 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -53,6 +53,7 @@ func TestSetupGenesis(t *testing.T) { } oldcustomg = customg ) + oldcustomg.Config = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(2)} tests := []struct { name string @@ -149,9 +150,11 @@ func TestSetupGenesis(t *testing.T) { spew := spew.ConfigState{DisablePointerAddresses: true, DisableCapacities: true} t.Errorf("%s: returned error %#v, want %#v", test.name, spew.NewFormatter(err), spew.NewFormatter(test.wantErr)) } + if !reflect.DeepEqual(config, test.wantConfig) { t.Errorf("%s:\nreturned %v\nwant %v", test.name, config, test.wantConfig) } + if hash != test.wantHash { t.Errorf("%s: returned hash %s, want %s", test.name, hash.Hex(), test.wantHash.Hex()) } else if err == nil { @@ -229,18 +232,22 @@ func TestReadWriteGenesisAlloc(t *testing.T) { rawdb.WriteGenesisStateSpec(db, hash, blob) var reload GenesisAlloc + err := reload.UnmarshalJSON(rawdb.ReadGenesisStateSpec(db, hash)) if err != nil { t.Fatalf("Failed to load genesis state %v", err) } + if len(reload) != len(*alloc) { t.Fatal("Unexpected genesis allocation") } + for addr, account := range reload { want, ok := (*alloc)[addr] if !ok { t.Fatal("Account is not found") } + if !reflect.DeepEqual(want, account) { t.Fatal("Unexpected account") } diff --git a/core/headerchain.go b/core/headerchain.go index 92e805795c..74a3a6ff72 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -82,6 +82,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c if err != nil { return nil, err } + hc := &HeaderChain{ config: config, chainDb: chainDb, @@ -93,17 +94,22 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c engine: engine, } hc.genesisHeader = hc.GetHeaderByNumber(0) + if hc.genesisHeader == nil { return nil, ErrNoGenesis } + hc.currentHeader.Store(hc.genesisHeader) + if head := rawdb.ReadHeadBlockHash(chainDb); head != (common.Hash{}) { if chead := hc.GetHeaderByHash(head); chead != nil { hc.currentHeader.Store(chead) } } + hc.currentHeaderHash = hc.CurrentHeader().Hash() headHeaderGauge.Update(hc.CurrentHeader().Number.Int64()) + return hc, nil } @@ -113,10 +119,12 @@ func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 { if cached, ok := hc.numberCache.Get(hash); ok { return &cached } + number := rawdb.ReadHeaderNumber(hc.chainDb, hash) if number != nil { hc.numberCache.Add(hash, *number) } + return number } @@ -145,6 +153,7 @@ func (hc *HeaderChain) Reorg(headers []*types.Header) error { last = headers[len(headers)-1] batch = hc.chainDb.NewBatch() ) + if first.ParentHash != hc.currentHeaderHash { // Delete any canonical number assignments above the new head for i := last.Number.Uint64() + 1; ; i++ { @@ -152,6 +161,7 @@ func (hc *HeaderChain) Reorg(headers []*types.Header) error { if hash == (common.Hash{}) { break } + rawdb.DeleteCanonicalHash(batch, i) } // Overwrite any stale canonical number assignments, going @@ -162,13 +172,17 @@ func (hc *HeaderChain) Reorg(headers []*types.Header) error { headNumber = header.Number.Uint64() headHash = header.Hash() ) + for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash { rawdb.WriteCanonicalHash(batch, headHash, headNumber) + if headNumber == 0 { break // It shouldn't be reached } + headHash, headNumber = header.ParentHash, header.Number.Uint64()-1 header = hc.GetHeader(headHash, headNumber) + if header == nil { return fmt.Errorf("missing parent %d %x", headNumber, headHash) } @@ -194,6 +208,7 @@ func (hc *HeaderChain) Reorg(headers []*types.Header) error { hc.currentHeaderHash = last.Hash() hc.currentHeader.Store(types.CopyHeader(last)) headHeaderGauge.Update(last.Number.Int64()) + return nil } @@ -205,16 +220,19 @@ func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) { if len(headers) == 0 { return 0, nil } + ptd := hc.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1) if ptd == nil { return 0, consensus.ErrUnknownAncestor } + var ( newTD = new(big.Int).Set(ptd) // Total difficulty of inserted chain inserted []rawdb.NumberHash // Ephemeral lookup of number/hash for the chain parentKnown = true // Set to true to force hc.HasHeader check the first iteration 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 @@ -225,6 +243,7 @@ func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) { } else { hash = header.Hash() } + number := header.Number.Uint64() newTD.Add(newTD, header.Difficulty) @@ -237,10 +256,12 @@ func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) { hc.tdCache.Add(hash, new(big.Int).Set(newTD)) rawdb.WriteHeader(batch, header) + inserted = append(inserted, rawdb.NumberHash{Number: number, Hash: hash}) hc.headerCache.Add(hash, header) hc.numberCache.Add(hash, number) } + parentKnown = alreadyKnown } // Skip the slow disk write of all headers if interrupted. @@ -252,6 +273,7 @@ func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) { if err := batch.Write(); err != nil { log.Crit("Failed to write headers", "error", err) } + return len(inserted), nil } @@ -267,6 +289,7 @@ func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *F if err != nil { return nil, err } + var ( lastHeader = headers[len(headers)-1] lastHash = headers[len(headers)-1].Hash() @@ -285,6 +308,7 @@ func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *F if inserted != 0 { result.status = SideStatTy } + return result, nil } @@ -295,6 +319,7 @@ func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *F if inserted != 0 { result.status = SideStatTy } + return result, nil } @@ -307,7 +332,9 @@ func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *F if err := hc.Reorg(headers); err != nil { return nil, err } + result.status = CanonStatTy + return result, nil } @@ -343,6 +370,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) if index >= len(seals) { index = len(seals) - 1 } + seals[index] = true } // Last should always be verified to avoid junk. @@ -382,6 +410,7 @@ func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time, if hc.procInterrupt() { return 0, errors.New("aborted") } + res, err := hc.writeHeadersAndSetHead(chain, forker) if err != nil { return 0, err @@ -397,11 +426,13 @@ func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time, context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) } } + if res.ignored > 0 { context = append(context, []interface{}{"ignored", res.ignored}...) } log.Debug("Imported new block headers", context...) + return res.status, err } @@ -414,33 +445,42 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma if ancestor > number { return common.Hash{}, 0 } + if ancestor == 1 { // in this case it is cheaper to just read the header if header := hc.GetHeader(hash, number); header != nil { return header.ParentHash, number - 1 } + return common.Hash{}, 0 } + for ancestor != 0 { if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash { ancestorHash := rawdb.ReadCanonicalHash(hc.chainDb, number-ancestor) + if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash { number -= ancestor return ancestorHash, number } } + if *maxNonCanonical == 0 { return common.Hash{}, 0 } + *maxNonCanonical-- ancestor-- + header := hc.GetHeader(hash, number) if header == nil { return common.Hash{}, 0 } + hash = header.ParentHash number-- } + return hash, number } @@ -451,12 +491,14 @@ func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int { if cached, ok := hc.tdCache.Get(hash); ok { return cached } + td := rawdb.ReadTd(hc.chainDb, hash, number) if td == nil { return nil } // Cache the found body for next time and return hc.tdCache.Add(hash, td) + return td } @@ -467,12 +509,14 @@ func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header if header, ok := hc.headerCache.Get(hash); ok { return header } + header := rawdb.ReadHeader(hc.chainDb, hash, number) if header == nil { return nil } // Cache the found header for next time and return hc.headerCache.Add(hash, header) + return header } @@ -483,6 +527,7 @@ func (hc *HeaderChain) GetHeaderByHash(hash common.Hash) *types.Header { if number == nil { return nil } + return hc.GetHeader(hash, *number) } @@ -493,6 +538,7 @@ func (hc *HeaderChain) HasHeader(hash common.Hash, number uint64) bool { if hc.numberCache.Contains(hash) || hc.headerCache.Contains(hash) { return true } + return rawdb.HasHeader(hc.chainDb, hash, number) } @@ -503,6 +549,7 @@ func (hc *HeaderChain) GetHeaderByNumber(number uint64) *types.Header { if hash == (common.Hash{}) { return nil } + return hc.GetHeader(hash, number) } @@ -521,12 +568,14 @@ func (hc *HeaderChain) GetHeadersFrom(number, count uint64) []rlp.RawValue { return nil } } + var headers []rlp.RawValue // If we have some of the headers in cache already, use that before going to db. hash := rawdb.ReadCanonicalHash(hc.chainDb, number) if hash == (common.Hash{}) { return nil } + for count > 0 { header, ok := hc.headerCache.Get(hash) if !ok { @@ -543,6 +592,7 @@ func (hc *HeaderChain) GetHeadersFrom(number, count uint64) []rlp.RawValue { if count > 0 { headers = append(headers, rawdb.ReadHeaderRange(hc.chainDb, number, count)...) } + return headers } @@ -604,6 +654,7 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat // startup, so failing hard there is ok. log.Crit("Rejecting genesis rewind via timestamp", "target", headTime, "genesis", hc.genesisHeader.Time) } + var ( parentHash common.Hash batch = hc.chainDb.NewBatch() @@ -626,6 +677,7 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat if parent == nil { parent = hc.genesisHeader } + parentHash = parent.Hash() // Notably, since geth has the possibility for setting the head to a low @@ -645,22 +697,28 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat } // Update head header then. rawdb.WriteHeadHeaderHash(markerBatch, parentHash) + if err := markerBatch.Write(); err != nil { log.Crit("Failed to update chain markers", "error", err) } + hc.currentHeader.Store(parent) hc.currentHeaderHash = parentHash + headHeaderGauge.Update(parent.Number.Int64()) // If this is the first iteration, wipe any leftover data upwards too so // we don't end up with dangling daps in the database var nums []uint64 + if origin { for n := num + 1; len(rawdb.ReadAllHashes(hc.chainDb, n)) > 0; n++ { nums = append([]uint64{n}, nums...) // suboptimal, but we don't really expect this path } + origin = false } + nums = append(nums, num) // Remove the related data from the database on all sidechains @@ -671,13 +729,16 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat // No hashes in the database whatsoever, probably frozen already hashes = append(hashes, hdr.Hash()) } + for _, hash := range hashes { if delFn != nil { delFn(batch, hash, num) } + rawdb.DeleteHeader(batch, hash, num) rawdb.DeleteTd(batch, hash, num) } + rawdb.DeleteCanonicalHash(batch, num) } } diff --git a/core/headerchain_test.go b/core/headerchain_test.go index 2f9b1988c2..8a1ecb9905 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -33,6 +33,7 @@ import ( func verifyUnbrokenCanonchain(hc *HeaderChain) error { h := hc.CurrentHeader() + for { canonHash := rawdb.ReadCanonicalHash(hc.chainDb, h.Number.Uint64()) if exp := h.Hash(); canonHash != exp { @@ -43,11 +44,14 @@ func verifyUnbrokenCanonchain(hc *HeaderChain) error { 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 } @@ -62,6 +66,7 @@ func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus if err := verifyUnbrokenCanonchain(hc); err != nil { t.Fatal(err) } + if !errors.Is(err, wantErr) { t.Fatalf("unexpected error from InsertHeaderChain: %v", err) } @@ -73,7 +78,9 @@ func TestHeaderInsertion(t *testing.T) { db = rawdb.NewMemoryDatabase() gspec = &Genesis{BaseFee: big.NewInt(params.InitialBaseFee), Config: params.AllEthashProtocolChanges} ) + gspec.Commit(db, trie.NewDatabase(db)) + hc, err := NewHeaderChain(db, gspec.Config, ethash.NewFaker(), func() bool { return false }) if err != nil { t.Fatal(err) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 463eb9d4f0..6f49f7ede4 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -36,14 +36,17 @@ import ( // ReadCanonicalHash retrieves the hash assigned to a canonical block number. func ReadCanonicalHash(db ethdb.Reader, number uint64) common.Hash { var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { data, _ = reader.Ancient(ChainFreezerHashTable, number) if len(data) == 0 { // Get it by hash from leveldb data, _ = db.Get(headerHashKey(number)) } + return nil }) + return common.BytesToHash(data) } @@ -67,6 +70,7 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash { prefix := headerKeyPrefix(number) hashes := make([]common.Hash, 0, 1) + it := db.NewIterator(prefix, nil) defer it.Release() @@ -75,6 +79,7 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash { hashes = append(hashes, common.BytesToHash(key[len(key)-32:])) } } + return hashes } @@ -93,19 +98,24 @@ func ReadAllHashesInRange(db ethdb.Iteratee, first, last uint64) []*NumberHash { hashes = make([]*NumberHash, 0, 1+last-first) it = db.NewIterator(headerPrefix, start) ) + defer it.Release() + for it.Next() { key := it.Key() if len(key) != keyLength { continue } + num := binary.BigEndian.Uint64(key[len(headerPrefix) : len(headerPrefix)+8]) if num > last { break } + hash := common.BytesToHash(key[len(key)-32:]) hashes = append(hashes, &NumberHash{num, hash}) } + return hashes } @@ -117,12 +127,14 @@ func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int if limit == 0 { return nil, nil } + var ( numbers []uint64 hashes []common.Hash ) // Construct the key prefix of start point. start, end := headerHashKey(from), headerHashKey(to) + it := db.NewIterator(nil, start) defer it.Release() @@ -130,6 +142,7 @@ func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int if bytes.Compare(it.Key(), end) >= 0 { break } + if key := it.Key(); len(key) == len(headerPrefix)+8+1 && bytes.Equal(key[len(key)-1:], headerHashSuffix) { numbers = append(numbers, binary.BigEndian.Uint64(key[len(headerPrefix):len(headerPrefix)+8])) hashes = append(hashes, common.BytesToHash(it.Value())) @@ -139,6 +152,7 @@ func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int } } } + return numbers, hashes } @@ -148,7 +162,9 @@ func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 { if len(data) != 8 { return nil } + number := binary.BigEndian.Uint64(data) + return &number } @@ -156,6 +172,7 @@ func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 { func WriteHeaderNumber(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { key := headerNumberKey(hash) enc := encodeBlockNumber(number) + if err := db.Put(key, enc); err != nil { log.Crit("Failed to store hash to number mapping", "err", err) } @@ -174,6 +191,7 @@ func ReadHeadHeaderHash(db ethdb.KeyValueReader) common.Hash { if len(data) == 0 { return common.Hash{} } + return common.BytesToHash(data) } @@ -190,6 +208,7 @@ func ReadHeadBlockHash(db ethdb.KeyValueReader) common.Hash { if len(data) == 0 { return common.Hash{} } + return common.BytesToHash(data) } @@ -206,6 +225,7 @@ func ReadHeadFastBlockHash(db ethdb.KeyValueReader) common.Hash { if len(data) == 0 { return common.Hash{} } + return common.BytesToHash(data) } @@ -222,6 +242,7 @@ func ReadFinalizedBlockHash(db ethdb.KeyValueReader) common.Hash { if len(data) == 0 { return common.Hash{} } + return common.BytesToHash(data) } @@ -239,11 +260,13 @@ func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 { if len(data) == 0 { return nil } + var pivot uint64 if err := rlp.DecodeBytes(data, &pivot); err != nil { log.Error("Invalid pivot block number in database", "err", err) return nil } + return &pivot } @@ -253,6 +276,7 @@ func WriteLastPivotNumber(db ethdb.KeyValueWriter, pivot uint64) { if err != nil { log.Crit("Failed to encode pivot block number", "err", err) } + if err := db.Put(lastPivotKey, enc); err != nil { log.Crit("Failed to store pivot block number", "err", err) } @@ -265,7 +289,9 @@ func ReadTxIndexTail(db ethdb.KeyValueReader) *uint64 { if len(data) != 8 { return nil } + number := binary.BigEndian.Uint64(data) + return &number } @@ -283,7 +309,9 @@ func ReadFastTxLookupLimit(db ethdb.KeyValueReader) *uint64 { if len(data) != 8 { return nil } + number := binary.BigEndian.Uint64(data) + return &number } @@ -308,11 +336,14 @@ func ReadHeaderRange(db ethdb.Reader, number uint64, count uint64) []rlp.RawValu if count == 0 { return rlpHeaders } + i := number + if count-1 > number { // It's ok to request block 0, 1 item count = number + 1 } + limit, _ := db.Ancients() // First read live blocks if i >= limit { @@ -326,14 +357,17 @@ func ReadHeaderRange(db ethdb.Reader, number uint64, count uint64) []rlp.RawValu } else { break // Maybe got moved to ancients } + count-- } } + if count == 0 { return rlpHeaders } // read remaining from ancients max := count * 700 + data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, max) if err == nil && uint64(len(data)) == count { // the data is on the order [h, h+1, .., n] -- reordering needed @@ -341,12 +375,14 @@ func ReadHeaderRange(db ethdb.Reader, number uint64, count uint64) []rlp.RawValu rlpHeaders = append(rlpHeaders, data[len(data)-1-i]) } } + return rlpHeaders } // ReadHeaderRLP retrieves a block header in its raw RLP database encoding. func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { // First try to look up the data in ancient database. Extra hash // comparison is necessary since ancient database only maintains @@ -357,8 +393,10 @@ func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValu } // If not, try reading from leveldb data, _ = db.Get(headerKey(number, hash)) + return nil }) + return data } @@ -367,9 +405,11 @@ func HasHeader(db ethdb.Reader, hash common.Hash, number uint64) bool { if isCanon(db, number, hash) { return true } + if has, err := db.Has(headerKey(number, hash)); !has || err != nil { return false } + return true } @@ -379,11 +419,13 @@ func ReadHeader(db ethdb.Reader, hash common.Hash, number uint64) *types.Header if len(data) == 0 { return nil } + header := new(types.Header) if err := rlp.Decode(bytes.NewReader(data), header); err != nil { log.Error("Invalid block header RLP", "hash", hash, "err", err) return nil } + return header } @@ -402,6 +444,7 @@ func WriteHeader(db ethdb.KeyValueWriter, header *types.Header) { if err != nil { log.Crit("Failed to RLP encode header", "err", err) } + key := headerKey(number, hash) if err := db.Put(key, data); err != nil { log.Crit("Failed to store header", "err", err) @@ -411,6 +454,7 @@ func WriteHeader(db ethdb.KeyValueWriter, header *types.Header) { // DeleteHeader removes all block header data associated with a hash. func DeleteHeader(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { deleteHeaderWithoutNumber(db, hash, number) + if err := db.Delete(headerNumberKey(hash)); err != nil { log.Crit("Failed to delete hash to number mapping", "err", err) } @@ -431,6 +475,7 @@ func isCanon(reader ethdb.AncientReaderOp, number uint64, hash common.Hash) bool if err != nil { return false } + return bytes.Equal(h, hash[:]) } @@ -440,6 +485,7 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue // comparison is necessary since ancient database only maintains // the canonical data. var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { // Check if the data is in ancients if isCanon(reader, number, hash) { @@ -448,8 +494,10 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue } // If not, try reading from leveldb data, _ = db.Get(blockBodyKey(number, hash)) + return nil }) + return data } @@ -457,6 +505,7 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue // block at number, in RLP encoding. func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue { var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { data, _ = reader.Ancient(ChainFreezerBodiesTable, number) if len(data) > 0 { @@ -467,8 +516,10 @@ func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue { // calls ReadAncients internally. hash, _ := db.Get(headerHashKey(number)) data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hash))) + return nil }) + return data } @@ -484,9 +535,11 @@ func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool { if isCanon(db, number, hash) { return true } + if has, err := db.Has(blockBodyKey(number, hash)); !has || err != nil { return false } + return true } @@ -496,11 +549,13 @@ func ReadBody(db ethdb.Reader, hash common.Hash, number uint64) *types.Body { if len(data) == 0 { return nil } + body := new(types.Body) if err := rlp.Decode(bytes.NewReader(data), body); err != nil { log.Error("Invalid block body RLP", "hash", hash, "err", err) return nil } + return body } @@ -510,6 +565,7 @@ func WriteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64, body *t if err != nil { log.Crit("Failed to RLP encode body", "err", err) } + WriteBodyRLP(db, hash, number, data) } @@ -523,6 +579,7 @@ func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { // ReadTdRLP retrieves a block's total difficulty corresponding to the hash in RLP encoding. func ReadTdRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { // Check if the data is in ancients if isCanon(reader, number, hash) { @@ -531,8 +588,10 @@ func ReadTdRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { } // If not, try reading from leveldb data, _ = db.Get(headerTDKey(number, hash)) + return nil }) + return data } @@ -542,11 +601,13 @@ func ReadTd(db ethdb.Reader, hash common.Hash, number uint64) *big.Int { if len(data) == 0 { return nil } + td := new(big.Int) if err := rlp.Decode(bytes.NewReader(data), td); err != nil { log.Error("Invalid block total difficulty RLP", "hash", hash, "err", err) return nil } + return td } @@ -556,6 +617,7 @@ func WriteTd(db ethdb.KeyValueWriter, hash common.Hash, number uint64, td *big.I if err != nil { log.Crit("Failed to RLP encode block total difficulty", "err", err) } + if err := db.Put(headerTDKey(number, hash), data); err != nil { log.Crit("Failed to store block total difficulty", "err", err) } @@ -574,15 +636,18 @@ func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool { if isCanon(db, number, hash) { return true } + if has, err := db.Has(blockReceiptsKey(number, hash)); !has || err != nil { return false } + return true } // ReadReceiptsRLP retrieves all the transaction receipts belonging to a block in RLP encoding. func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { // Check if the data is in ancients if isCanon(reader, number, hash) { @@ -591,8 +656,10 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa } // If not, try reading from leveldb data, _ = db.Get(blockReceiptsKey(number, hash)) + return nil }) + return data } @@ -611,10 +678,12 @@ func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Rec log.Error("Invalid receipt array RLP", "hash", hash, "err", err) return nil } + receipts := make(types.Receipts, len(storageReceipts)) for i, storageReceipt := range storageReceipts { receipts[i] = (*types.Receipt)(storageReceipt) } + return receipts } @@ -631,22 +700,28 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, config *para if receipts == nil { return nil } + body := ReadBody(db, hash, number) if body == nil { log.Error("Missing body but have receipt", "hash", hash, "number", number) return nil } + header := ReadHeader(db, hash, number) + var baseFee *big.Int + if header == nil { baseFee = big.NewInt(0) } else { baseFee = header.BaseFee } + if err := receipts.DeriveFields(config, hash, number, baseFee, body.Transactions); err != nil { log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err) return nil } + return receipts } @@ -657,6 +732,7 @@ func WriteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rec for i, receipt := range receipts { storageReceipts[i] = (*types.ReceiptForStorage)(receipt) } + bytes, err := rlp.EncodeToBytes(storageReceipts) if err != nil { log.Crit("Failed to encode block receipts", "err", err) @@ -696,16 +772,20 @@ func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error { if err := s.Decode(&stored); err != nil { return err } + r.Logs = stored.Logs + return nil } // DeriveLogFields fills the logs in receiptLogs with information such as block number, txhash, etc. func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, txs types.Transactions) error { logIndex := uint(0) + if len(txs) != len(receipts) { return errors.New("transaction and receipt count mismatch") } + for i := 0; i < len(receipts); i++ { txHash := txs[i].Hash() // The derived log fields can simply be set from the block and transaction @@ -718,6 +798,7 @@ func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, t logIndex++ } } + return nil } @@ -730,6 +811,7 @@ func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.C if len(data) == 0 { return nil } + receipts := []*receiptLogs{} if err := rlp.DecodeBytes(data, &receipts); err != nil { // Receipts might be in the legacy format, try decoding that. @@ -737,7 +819,9 @@ func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.C if logs := readLegacyLogs(db, hash, number, config); logs != nil { return logs } + log.Error("Invalid receipt array RLP", "hash", hash, "err", err) + return nil } @@ -745,6 +829,7 @@ func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.C for i, receipt := range receipts { logs[i] = receipt.Logs } + return logs } @@ -756,10 +841,12 @@ func readLegacyLogs(db ethdb.Reader, hash common.Hash, number uint64, config *pa if receipts == nil { return nil } + logs := make([][]*types.Log, len(receipts)) for i, receipt := range receipts { logs[i] = receipt.Logs } + return logs } @@ -774,10 +861,12 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block { if header == nil { return nil } + body := ReadBody(db, hash, number) if body == nil { return nil } + return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals) } @@ -794,6 +883,7 @@ func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts stReceipts []*types.ReceiptForStorage borStReceipts []*types.ReceiptForStorage ) + return db.ModifyAncients(func(op ethdb.AncientWriteOp) error { for i, block := range blocks { // Convert receipts to storage format and sum up total difficulty. @@ -812,10 +902,12 @@ func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts if i > 0 { tdSum.Add(tdSum, header.Difficulty) } + if err := writeAncientBlock(op, block, header, stReceipts, borStReceipts, tdSum); err != nil { return err } } + return nil }) } @@ -825,21 +917,27 @@ func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *type if err := op.AppendRaw(ChainFreezerHashTable, num, block.Hash().Bytes()); err != nil { return fmt.Errorf("can't add block %d hash: %v", num, err) } + if err := op.Append(ChainFreezerHeaderTable, num, header); err != nil { return fmt.Errorf("can't append block header %d: %v", num, err) } + if err := op.Append(ChainFreezerBodiesTable, num, block.Body()); err != nil { return fmt.Errorf("can't append block body %d: %v", num, err) } + if err := op.Append(ChainFreezerReceiptTable, num, receipts); err != nil { return fmt.Errorf("can't append block %d receipts: %v", num, err) } + if err := op.Append(ChainFreezerDifficultyTable, num, td); err != nil { return fmt.Errorf("can't append block %d total difficulty: %v", num, err) } + if err := op.Append(freezerBorReceiptTable, num, borReceipts); err != nil { return fmt.Errorf("can't append block %d borReceipts: %v", num, err) } + return nil } @@ -889,15 +987,18 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block { 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).WithWithdrawals(bad.Body.Withdrawals) } } + return nil } @@ -908,14 +1009,17 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block { 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).WithWithdrawals(bad.Body.Withdrawals)) } + return blocks } @@ -926,30 +1030,36 @@ func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) { 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) } @@ -970,22 +1080,26 @@ func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header { return nil } } + for an := a.Number.Uint64(); an < b.Number.Uint64(); { b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1) if b == nil { return nil } } + for a.Hash() != b.Hash() { a = ReadHeader(db, a.ParentHash, a.Number.Uint64()-1) if a == nil { return nil } + b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1) if b == nil { return nil } } + return a } @@ -995,10 +1109,12 @@ func ReadHeadHeader(db ethdb.Reader) *types.Header { if headHeaderHash == (common.Hash{}) { return nil } + headHeaderNumber := ReadHeaderNumber(db, headHeaderHash) if headHeaderNumber == nil { return nil } + return ReadHeader(db, headHeaderHash, *headHeaderNumber) } @@ -1008,9 +1124,11 @@ func ReadHeadBlock(db ethdb.Reader) *types.Block { if headBlockHash == (common.Hash{}) { return nil } + headBlockNumber := ReadHeaderNumber(db, headBlockHash) if headBlockNumber == nil { return nil } + return ReadBlock(db, headBlockHash, *headBlockNumber) } diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index e1d7a119d7..b26f2bf522 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -45,11 +45,13 @@ func TestHeaderStorage(t *testing.T) { } // Write and verify the header in the database WriteHeader(db, header) + if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry == nil { t.Fatalf("Stored header not found") } else if entry.Hash() != header.Hash() { t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, header) } + if entry := ReadHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil { t.Fatalf("Stored header RLP not found") } else { @@ -62,6 +64,7 @@ func TestHeaderStorage(t *testing.T) { } // Delete the header and verify the execution DeleteHeader(db, header.Hash(), header.Number.Uint64()) + if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry != nil { t.Fatalf("Deleted header returned: %v", entry) } @@ -83,11 +86,13 @@ func TestBodyStorage(t *testing.T) { } // Write and verify the body in the database WriteBody(db, hash, 0, body) + if entry := ReadBody(db, hash, 0); entry == nil { t.Fatalf("Stored body not found") } else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) { t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, body) } + if entry := ReadBodyRLP(db, hash, 0); entry == nil { t.Fatalf("Stored body RLP not found") } else { @@ -100,6 +105,7 @@ func TestBodyStorage(t *testing.T) { } // Delete the body and verify the execution DeleteBody(db, hash, 0) + if entry := ReadBody(db, hash, 0); entry != nil { t.Fatalf("Deleted body returned: %v", entry) } @@ -119,24 +125,29 @@ func TestBlockStorage(t *testing.T) { if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent block returned: %v", entry) } + if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent header returned: %v", entry) } + if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent body returned: %v", entry) } // Write and verify the block in the database WriteBlock(db, block) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); 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) } + if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry == nil { t.Fatalf("Stored header not found") } else if entry.Hash() != block.Header().Hash() { t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, block.Header()) } + if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry == nil { t.Fatalf("Stored body not found") } else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(block.Transactions(), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) { @@ -144,12 +155,15 @@ func TestBlockStorage(t *testing.T) { } // Delete the block and verify the execution DeleteBlock(db, block.Hash(), block.NumberU64()) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Deleted block returned: %v", entry) } + if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Deleted header returned: %v", entry) } + if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Deleted body returned: %v", entry) } @@ -166,16 +180,20 @@ func TestPartialBlockStorage(t *testing.T) { }) // Store a header and check that it's not recognized as a block WriteHeader(db, block.Header()) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent block returned: %v", entry) } + DeleteHeader(db, block.Hash(), block.NumberU64()) // Store a body and check that it's not recognized as a block WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent block returned: %v", entry) } + DeleteBody(db, block.Hash(), block.NumberU64()) // Store a header and a body separately and check reassembly @@ -206,6 +224,7 @@ func TestBadBlockStorage(t *testing.T) { } // 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() { @@ -223,6 +242,7 @@ func TestBadBlockStorage(t *testing.T) { // 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") @@ -240,10 +260,12 @@ func TestBadBlockStorage(t *testing.T) { }) 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()) @@ -252,6 +274,7 @@ func TestBadBlockStorage(t *testing.T) { // Delete all bad blocks DeleteBadBlocks(db) + badBlocks = ReadAllBadBlocks(db) if len(badBlocks) != 0 { t.Fatalf("Failed to delete bad blocks") @@ -269,6 +292,7 @@ func TestTdStorage(t *testing.T) { } // Write and verify the TD in the database WriteTd(db, hash, 0, td) + if entry := ReadTd(db, hash, 0); entry == nil { t.Fatalf("Stored TD not found") } else if entry.Cmp(td) != 0 { @@ -276,6 +300,7 @@ func TestTdStorage(t *testing.T) { } // Delete the TD and verify the execution DeleteTd(db, hash, 0) + if entry := ReadTd(db, hash, 0); entry != nil { t.Fatalf("Deleted TD returned: %v", entry) } @@ -292,6 +317,7 @@ func TestCanonicalMappingStorage(t *testing.T) { } // Write and verify the TD in the database WriteCanonicalHash(db, hash, number) + if entry := ReadCanonicalHash(db, number); entry == (common.Hash{}) { t.Fatalf("Stored canonical mapping not found") } else if entry != hash { @@ -299,6 +325,7 @@ func TestCanonicalMappingStorage(t *testing.T) { } // Delete the TD and verify the execution DeleteCanonicalHash(db, number) + if entry := ReadCanonicalHash(db, number); entry != (common.Hash{}) { t.Fatalf("Deleted canonical mapping returned: %v", entry) } @@ -316,9 +343,11 @@ func TestHeadStorage(t *testing.T) { if entry := ReadHeadHeaderHash(db); entry != (common.Hash{}) { t.Fatalf("Non head header entry returned: %v", entry) } + if entry := ReadHeadBlockHash(db); entry != (common.Hash{}) { t.Fatalf("Non head block entry returned: %v", entry) } + if entry := ReadHeadFastBlockHash(db); entry != (common.Hash{}) { t.Fatalf("Non fast head block entry returned: %v", entry) } @@ -331,9 +360,11 @@ func TestHeadStorage(t *testing.T) { if entry := ReadHeadHeaderHash(db); entry != blockHead.Hash() { t.Fatalf("Head header hash mismatch: have %v, want %v", entry, blockHead.Hash()) } + if entry := ReadHeadBlockHash(db); entry != blockFull.Hash() { t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFull.Hash()) } + if entry := ReadHeadFastBlockHash(db); entry != blockFast.Hash() { t.Fatalf("Fast head block hash mismatch: have %v, want %v", entry, blockFast.Hash()) } @@ -387,6 +418,7 @@ func TestBlockReceiptStorage(t *testing.T) { // Insert the receipt slice into the database and check presence WriteReceipts(db, hash, 0, receipts) + if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) == 0 { t.Fatalf("no receipts returned") } else { @@ -396,6 +428,7 @@ func TestBlockReceiptStorage(t *testing.T) { } // Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed) DeleteBody(db, hash, 0) + if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); rs != nil { t.Fatalf("receipts returned when body was deleted: %v", rs) } @@ -407,6 +440,7 @@ func TestBlockReceiptStorage(t *testing.T) { WriteBody(db, hash, 0, body) DeleteReceipts(db, hash, 0) + if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 { t.Fatalf("deleted receipts returned: %v", rs) } @@ -416,19 +450,23 @@ func checkReceiptsRLP(have, want types.Receipts) error { if len(have) != len(want) { return fmt.Errorf("receipts sizes mismatch: have %d, want %d", len(have), len(want)) } + for i := 0; i < len(want); i++ { rlpHave, err := rlp.EncodeToBytes(have[i]) if err != nil { return err } + rlpWant, err := rlp.EncodeToBytes(want[i]) if err != nil { return err } + if !bytes.Equal(rlpHave, rlpWant) { return fmt.Errorf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant)) } } + return nil } @@ -454,12 +492,15 @@ func TestAncientStorage(t *testing.T) { if blob := ReadHeaderRLP(db, hash, number); len(blob) > 0 { t.Fatalf("non existent header returned") } + if blob := ReadBodyRLP(db, hash, number); len(blob) > 0 { t.Fatalf("non existent body returned") } + if blob := ReadReceiptsRLP(db, hash, number); len(blob) > 0 { t.Fatalf("non existent receipts returned") } + if blob := ReadTdRLP(db, hash, number); len(blob) > 0 { t.Fatalf("non existent td returned") } @@ -470,12 +511,15 @@ func TestAncientStorage(t *testing.T) { if blob := ReadHeaderRLP(db, hash, number); len(blob) == 0 { t.Fatalf("no header returned") } + if blob := ReadBodyRLP(db, hash, number); len(blob) == 0 { t.Fatalf("no body returned") } + if blob := ReadReceiptsRLP(db, hash, number); len(blob) == 0 { t.Fatalf("no receipts returned") } + if blob := ReadTdRLP(db, hash, number); len(blob) == 0 { t.Fatalf("no td returned") } @@ -485,12 +529,15 @@ func TestAncientStorage(t *testing.T) { if blob := ReadHeaderRLP(db, fakeHash, number); len(blob) != 0 { t.Fatalf("invalid header returned") } + if blob := ReadBodyRLP(db, fakeHash, number); len(blob) != 0 { t.Fatalf("invalid body returned") } + if blob := ReadReceiptsRLP(db, fakeHash, number); len(blob) != 0 { t.Fatalf("invalid receipts returned") } + if blob := ReadTdRLP(db, fakeHash, number); len(blob) != 0 { t.Fatalf("invalid td returned") } @@ -511,6 +558,7 @@ func TestCanonicalHashIteration(t *testing.T) { } // Test empty db iteration db := NewMemoryDatabase() + numbers, _ := ReadAllCanonicalHashes(db, 0, 10, 10) if len(numbers) != 0 { t.Fatalf("No entry should be returned to iterate an empty db") @@ -520,6 +568,7 @@ func TestCanonicalHashIteration(t *testing.T) { WriteCanonicalHash(db, common.Hash{}, i) WriteTd(db, common.Hash{}, i, big.NewInt(10)) // Write some interferential data } + for i, c := range cases { numbers, _ := ReadAllCanonicalHashes(db, c.from, c.to, c.limit) if !reflect.DeepEqual(numbers, c.expect) { @@ -535,35 +584,45 @@ func TestHashesInRange(t *testing.T) { Number: big.NewInt(int64(number)), GasLimit: uint64(seq), } + return &h } db := NewMemoryDatabase() // For each number, write N versions of that particular number total := 0 + for i := 0; i < 15; i++ { for ii := 0; ii < i; ii++ { WriteHeader(db, mkHeader(i, ii)) + total++ } } + if have, want := len(ReadAllHashesInRange(db, 10, 10)), 10; have != want { t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) } + if have, want := len(ReadAllHashesInRange(db, 10, 9)), 0; have != want { t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) } + if have, want := len(ReadAllHashesInRange(db, 0, 100)), total; have != want { t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) } + if have, want := len(ReadAllHashesInRange(db, 9, 10)), 9+10; have != want { t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) } + if have, want := len(ReadAllHashes(db, 10)), 10; have != want { t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) } + if have, want := len(ReadAllHashes(db, 16)), 0; have != want { t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) } + if have, want := len(ReadAllHashes(db, 1)), 1; have != want { t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) } @@ -573,26 +632,32 @@ func TestHashesInRange(t *testing.T) { func BenchmarkWriteAncientBlocks(b *testing.B) { // Open freezer database. frdir := b.TempDir() + db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false) if err != nil { b.Fatalf("failed to create database with ancient backend") } + defer db.Close() // Create the data to insert. The blocks must have consecutive numbers, so we create // all of them ahead of time. However, there is no need to create receipts // individually for each block, just make one batch here and reuse it for all writes. const batchSize = 128 + const blockTxs = 20 allBlocks := makeTestBlocks(b.N, blockTxs) batchReceipts := makeTestReceipts(batchSize, blockTxs) + b.ResetTimer() // The benchmark loop writes batches of blocks, but note that the total block count is // b.N. This means the resulting ns/op measurement is the time it takes to write a // single block and its associated data. var td = big.NewInt(55) + var totalSize int64 + for i := 0; i < b.N; i += batchSize { length := batchSize if i+batchSize > b.N { @@ -601,10 +666,12 @@ func BenchmarkWriteAncientBlocks(b *testing.B) { blocks := allBlocks[i : i+length] receipts := batchReceipts[:length] + writeSize, err := WriteAncientBlocks(db, blocks, receipts, []types.Receipts{nil}, td) if err != nil { b.Fatal(err) } + totalSize += writeSize } @@ -621,7 +688,9 @@ func makeTestBlocks(nblock int, txsPerBlock int) []*types.Block { txs := make([]*types.Transaction, txsPerBlock) for i := 0; i < len(txs); i++ { var err error + to := common.Address{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + txs[i], err = types.SignNewTx(key, signer, &types.LegacyTx{ Nonce: 2, GasPrice: big.NewInt(30000), @@ -635,6 +704,7 @@ func makeTestBlocks(nblock int, txsPerBlock int) []*types.Block { // Create the blocks. blocks := make([]*types.Block, nblock) + for i := 0; i < nblock; i++ { header := &types.Header{ Number: big.NewInt(int64(i)), @@ -643,6 +713,7 @@ func makeTestBlocks(nblock int, txsPerBlock int) []*types.Block { blocks[i] = types.NewBlockWithHeader(header).WithBody(txs, nil) blocks[i].Hash() // pre-cache the block hash } + return blocks } @@ -656,10 +727,12 @@ func makeTestReceipts(n int, nPerBlock int) []types.Receipts { Logs: make([]*types.Log, 5), } } + allReceipts := make([]types.Receipts, n) for i := 0; i < n; i++ { allReceipts[i] = receipts } + return allReceipts } @@ -740,12 +813,15 @@ func TestReadLogs(t *testing.T) { if len(logs) == 0 { t.Fatalf("no logs returned") } + if have, want := len(logs), 2; have != want { t.Fatalf("unexpected number of logs returned, have %d want %d", have, want) } + if have, want := len(logs[0]), 2; have != want { t.Fatalf("unexpected number of logs[0] returned, have %d want %d", have, want) } + if have, want := len(logs[1]), 2; have != want { t.Fatalf("unexpected number of logs[1] returned, have %d want %d", have, want) } @@ -756,10 +832,12 @@ func TestReadLogs(t *testing.T) { if err != nil { t.Fatal(err) } + rlpWant, err := rlp.EncodeToBytes(newFullLogRLP(pl)) if err != nil { t.Fatal(err) } + if !bytes.Equal(rlpHave, rlpWant) { t.Fatalf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant)) } @@ -818,29 +896,36 @@ func TestDeriveLogFields(t *testing.T) { // Derive log metadata fields number := big.NewInt(1) hash := common.BytesToHash([]byte{0x03, 0x14}) + if err := deriveLogFields(receipts, hash, number.Uint64(), txs); err != nil { t.Fatal(err) } // Iterate over all the computed fields and check that they're correct logIndex := uint(0) + for i := range receipts { for j := range receipts[i].Logs { if receipts[i].Logs[j].BlockNumber != number.Uint64() { t.Errorf("receipts[%d].Logs[%d].BlockNumber = %d, want %d", i, j, receipts[i].Logs[j].BlockNumber, number.Uint64()) } + if receipts[i].Logs[j].BlockHash != hash { t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String()) } + if receipts[i].Logs[j].TxHash != txs[i].Hash() { t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String()) } + if receipts[i].Logs[j].TxIndex != uint(i) { t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i) } + if receipts[i].Logs[j].Index != logIndex { t.Errorf("receipts[%d].Logs[%d].Index = %d, want %d", i, j, receipts[i].Logs[j].Index, logIndex) } + logIndex++ } } @@ -852,8 +937,10 @@ func BenchmarkDecodeRLPLogs(b *testing.B) { if err != nil { b.Fatal(err) } + b.Run("ReceiptForStorage", func(b *testing.B) { b.ReportAllocs() + var r []*types.ReceiptForStorage for i := 0; i < b.N; i++ { if err := rlp.DecodeBytes(buf, &r); err != nil { @@ -863,6 +950,7 @@ func BenchmarkDecodeRLPLogs(b *testing.B) { }) b.Run("rlpLogs", func(b *testing.B) { b.ReportAllocs() + var r []*receiptLogs for i := 0; i < b.N; i++ { if err := rlp.DecodeBytes(buf, &r); err != nil { @@ -883,6 +971,7 @@ func TestHeadersRLPStorage(t *testing.T) { defer db.Close() // Create blocks var chain []*types.Block + var pHash common.Hash for i := 0; i < 100; i++ { block := types.NewBlockWithHeader(&types.Header{ @@ -896,7 +985,9 @@ func TestHeadersRLPStorage(t *testing.T) { chain = append(chain, block) pHash = block.Hash() } + var receipts []types.Receipts = make([]types.Receipts, 100) + var borReceipts []types.Receipts = make([]types.Receipts, 100) // Write first half to ancients @@ -906,16 +997,19 @@ func TestHeadersRLPStorage(t *testing.T) { WriteCanonicalHash(db, chain[i].Hash(), chain[i].NumberU64()) WriteBlock(db, chain[i]) } + checkSequence := func(from, amount int) { headersRlp := ReadHeaderRange(db, uint64(from), uint64(amount)) if have, want := len(headersRlp), amount; have != want { t.Fatalf("have %d headers, want %d", have, want) } + for i, headerRlp := range headersRlp { var header types.Header if err := rlp.DecodeBytes(headerRlp, &header); err != nil { t.Fatal(err) } + if have, want := header.Number.Uint64(), uint64(from-i); have != want { t.Fatalf("wrong number, have %d want %d", have, want) } diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index 25a44354bf..036ae66a35 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -50,6 +50,7 @@ func ReadTxLookupEntry(db ethdb.Reader, hash common.Hash) *uint64 { log.Error("Invalid transaction lookup entry RLP", "hash", hash, "blob", data, "err", err) return nil } + return &entry.BlockIndex } @@ -100,21 +101,26 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com if blockNumber == nil { return nil, common.Hash{}, 0, 0 } + blockHash := ReadCanonicalHash(db, *blockNumber) if blockHash == (common.Hash{}) { return nil, common.Hash{}, 0, 0 } + body := ReadBody(db, blockHash, *blockNumber) if body == nil { log.Error("Transaction referenced missing", "number", *blockNumber, "hash", blockHash) return nil, common.Hash{}, 0, 0 } + for txIndex, tx := range body.Transactions { if tx.Hash() == hash { return tx, blockHash, *blockNumber, uint64(txIndex) } } + log.Error("Transaction not found", "number", *blockNumber, "hash", blockHash, "txhash", hash) + return nil, common.Hash{}, 0, 0 } @@ -126,6 +132,7 @@ func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) if blockNumber == nil { return nil, common.Hash{}, 0, 0 } + blockHash := ReadCanonicalHash(db, *blockNumber) if blockHash == (common.Hash{}) { return nil, common.Hash{}, 0, 0 @@ -137,7 +144,9 @@ func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) return receipt, blockHash, *blockNumber, uint64(receiptIndex) } } + log.Error("Receipt not found", "number", *blockNumber, "hash", blockHash, "txhash", hash) + return nil, common.Hash{}, 0, 0 } @@ -159,6 +168,7 @@ func WriteBloomBits(db ethdb.KeyValueWriter, bit uint, section uint64, head comm // given section range and bit index. func DeleteBloombits(db ethdb.Database, bit uint, from uint64, to uint64) { start, end := bloomBitsKey(bit, from, common.Hash{}), bloomBitsKey(bit, to, common.Hash{}) + it := db.NewIterator(nil, start) defer it.Release() @@ -166,11 +176,14 @@ func DeleteBloombits(db ethdb.Database, bit uint, from uint64, to uint64) { if bytes.Compare(it.Key(), end) >= 0 { break } + if len(it.Key()) != len(bloomBitsPrefix)+2+8+32 { continue } + db.Delete(it.Key()) } + if it.Error() != nil { log.Crit("Failed to delete bloom bits", "err", it.Error()) } diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index c5447b16d2..3e82f42633 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -48,6 +48,7 @@ func (h *testHasher) Reset() { func (h *testHasher) Update(key, val []byte) error { h.hasher.Write(key) h.hasher.Write(val) + return nil } @@ -120,6 +121,7 @@ func TestLookupStorage(t *testing.T) { if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) { t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i) } + if tx.Hash() != txn.Hash() { t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx) } @@ -128,6 +130,7 @@ func TestLookupStorage(t *testing.T) { // Delete the transactions and check purge for i, tx := range txs { DeleteTxLookupEntry(db, tx.Hash()) + if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn) } @@ -139,17 +142,20 @@ func TestLookupStorage(t *testing.T) { func TestDeleteBloomBits(t *testing.T) { // Prepare testing data db := NewMemoryDatabase() + for i := uint(0); i < 2; i++ { for s := uint64(0); s < 2; s++ { WriteBloomBits(db, i, s, params.MainnetGenesisHash, []byte{0x01, 0x02}) WriteBloomBits(db, i, s, params.RinkebyGenesisHash, []byte{0x01, 0x02}) } } + check := func(bit uint, section uint64, head common.Hash, exist bool) { bits, _ := ReadBloomBits(db, bit, section, head) if exist && !bytes.Equal(bits, []byte{0x01, 0x02}) { t.Fatalf("Bloombits mismatch") } + if !exist && len(bits) > 0 { t.Fatalf("Bloombits should be removed") } diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 2ff29d1add..27aa88036f 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -35,6 +35,7 @@ func ReadDatabaseVersion(db ethdb.KeyValueReader) *uint64 { if len(enc) == 0 { return nil } + if err := rlp.DecodeBytes(enc, &version); err != nil { return nil } @@ -48,6 +49,7 @@ func WriteDatabaseVersion(db ethdb.KeyValueWriter, version uint64) { if err != nil { log.Crit("Failed to encode database version", "err", err) } + if err = db.Put(databaseVersionKey, enc); err != nil { log.Crit("Failed to store the database version", "err", err) } @@ -59,11 +61,13 @@ func ReadChainConfig(db ethdb.KeyValueReader, hash common.Hash) *params.ChainCon if len(data) == 0 { return nil } + var config params.ChainConfig if err := json.Unmarshal(data, &config); err != nil { log.Error("Invalid chain config JSON", "hash", hash, "err", err) return nil } + return &config } @@ -72,10 +76,12 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha if cfg == nil { return } + data, err := json.Marshal(cfg) if err != nil { log.Crit("Failed to JSON encode chain config", "err", err) } + if err := db.Put(configKey(hash), data); err != nil { log.Crit("Failed to store chain config", "err", err) } @@ -116,8 +122,11 @@ func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) } 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())) @@ -132,6 +141,7 @@ func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) log.Warn("Failed to write unclean-shutdown marker", "err", err) return nil, 0, err } + return previous, discarded, nil } @@ -144,9 +154,11 @@ func PopUncleanShutdownMarker(db ethdb.KeyValueStore) { } 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) @@ -168,7 +180,9 @@ func UpdateUncleanShutdownMarker(db ethdb.KeyValueStore) { log.Warn("No unclean shutdown marker to update") return } + uncleanShutdowns.Recent[count-1] = uint64(time.Now().Unix()) + data, _ := rlp.EncodeToBytes(uncleanShutdowns) if err := db.Put(uncleanShutdownKey, data); err != nil { log.Warn("Failed to write unclean-shutdown marker", "err", err) diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 3c82b3f731..794a74238f 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -51,6 +51,7 @@ func ReadSnapshotRoot(db ethdb.KeyValueReader) common.Hash { if len(data) != common.HashLength { return common.Hash{} } + return common.BytesToHash(data) } @@ -171,10 +172,13 @@ func ReadSnapshotRecoveryNumber(db ethdb.KeyValueReader) *uint64 { if len(data) == 0 { return nil } + if len(data) != 8 { return nil } + number := binary.BigEndian.Uint64(data) + return &number } @@ -182,7 +186,9 @@ func ReadSnapshotRecoveryNumber(db ethdb.KeyValueReader) *uint64 { // snapshot layer. func WriteSnapshotRecoveryNumber(db ethdb.KeyValueWriter, number uint64) { var buf [8]byte + binary.BigEndian.PutUint64(buf[:], number) + if err := db.Put(snapshotRecoveryKey, buf[:]); err != nil { log.Crit("Failed to store snapshot recovery number", "err", err) } diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 39900df23e..c4fe56a8b3 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -35,6 +35,7 @@ func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) { log.Crit("Failed to store trie preimage", "err", err) } } + preimageCounter.Inc(int64(len(preimages))) preimageHitCounter.Inc(int64(len(preimages))) } @@ -47,7 +48,9 @@ func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte { if len(data) != 0 { return data } + data, _ = db.Get(hash.Bytes()) + return data } @@ -67,7 +70,9 @@ func HasCode(db ethdb.KeyValueReader, hash common.Hash) bool { if ok := HasCodeWithPrefix(db, hash); ok { return true } + ok, _ := db.Has(hash.Bytes()) + return ok } diff --git a/core/rawdb/accessors_sync.go b/core/rawdb/accessors_sync.go index e87ad43c36..db1bba38e1 100644 --- a/core/rawdb/accessors_sync.go +++ b/core/rawdb/accessors_sync.go @@ -52,11 +52,13 @@ func ReadSkeletonHeader(db ethdb.KeyValueReader, number uint64) *types.Header { if len(data) == 0 { return nil } + header := new(types.Header) if err := rlp.Decode(bytes.NewReader(data), header); err != nil { log.Error("Invalid skeleton header RLP", "number", number, "err", err) return nil } + return header } @@ -66,6 +68,7 @@ func WriteSkeletonHeader(db ethdb.KeyValueWriter, header *types.Header) { if err != nil { log.Crit("Failed to RLP encode header", "err", err) } + key := skeletonHeaderKey(header.Number.Uint64()) if err := db.Put(key, data); err != nil { log.Crit("Failed to store skeleton header", "err", err) diff --git a/core/rawdb/accessors_trie.go b/core/rawdb/accessors_trie.go index cbc8579ede..1d7f8d4b3a 100644 --- a/core/rawdb/accessors_trie.go +++ b/core/rawdb/accessors_trie.go @@ -61,6 +61,7 @@ func (h *nodeHasher) hashData(data []byte) (n common.Hash) { h.sha.Reset() h.sha.Write(data) h.sha.Read(n[:]) + return n } @@ -71,8 +72,10 @@ func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.H if err != nil { return nil, common.Hash{} } + hasher := newNodeHasher() defer returnHasherToPool(hasher) + return data, hasher.hashData(data) } @@ -83,8 +86,10 @@ func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash) if err != nil { return false } + hasher := newNodeHasher() defer returnHasherToPool(hasher) + return hasher.hashData(data) == hash } @@ -109,8 +114,10 @@ func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path if err != nil { return nil, common.Hash{} } + hasher := newNodeHasher() defer returnHasherToPool(hasher) + return data, hasher.hashData(data) } @@ -121,8 +128,10 @@ func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path [ if err != nil { return false } + hasher := newNodeHasher() defer returnHasherToPool(hasher) + return hasher.hashData(data) == hash } @@ -147,6 +156,7 @@ func ReadLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte { if err != nil { return nil } + return data } @@ -180,6 +190,7 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c if owner == (common.Hash{}) { return HasAccountTrieNode(db, path, hash) } + return HasStorageTrieNode(db, owner, path, hash) default: panic(fmt.Sprintf("Unknown scheme %v", scheme)) @@ -203,14 +214,17 @@ func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash blob []byte nHash common.Hash ) + if owner == (common.Hash{}) { blob, nHash = ReadAccountTrieNode(db, path) } else { blob, nHash = ReadStorageTrieNode(db, owner, path) } + if nHash != hash { return nil } + return blob default: panic(fmt.Sprintf("Unknown scheme %v", scheme)) diff --git a/core/rawdb/ancient_utils.go b/core/rawdb/ancient_utils.go index 363a911aee..45b60ce6d5 100644 --- a/core/rawdb/ancient_utils.go +++ b/core/rawdb/ancient_utils.go @@ -47,12 +47,14 @@ func (info *freezerInfo) size() common.StorageSize { for _, table := range info.sizes { total += table.size } + return total } // inspectFreezers inspects all freezers registered in the system. func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) { var infos []freezerInfo + for _, freezer := range freezers { switch freezer { case chainFreezerName: @@ -65,6 +67,7 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) { if err != nil { return nil, err } + info.sizes = append(info.sizes, tableSize{name: table, size: common.StorageSize(size)}) } // Retrieve the number of last stored item @@ -72,6 +75,7 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) { if err != nil { return nil, err } + info.head = ancients - 1 // Retrieve the number of first stored item @@ -79,6 +83,7 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) { if err != nil { return nil, err } + info.tail = tail infos = append(infos, info) @@ -86,6 +91,7 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) { return nil, fmt.Errorf("unknown freezer, supported ones: %v", freezers) } } + return infos, nil } @@ -98,24 +104,30 @@ func InspectFreezerTable(ancient string, freezerName string, tableName string, s path string tables map[string]bool ) + switch freezerName { case chainFreezerName: path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy default: return fmt.Errorf("unknown freezer, supported ones: %v", freezers) } + noSnappy, exist := tables[tableName] if !exist { var names []string for name := range tables { names = append(names, name) } + return fmt.Errorf("unknown table, supported ones: %v", names) } + table, err := newFreezerTable(path, tableName, noSnappy, true) if err != nil { return err } + table.dumpIndexStdout(start, end) + return nil } diff --git a/core/rawdb/bor_receipt.go b/core/rawdb/bor_receipt.go index df57ce7abc..f3242b51ce 100644 --- a/core/rawdb/bor_receipt.go +++ b/core/rawdb/bor_receipt.go @@ -105,6 +105,7 @@ func ReadBorReceipt(db ethdb.Reader, hash common.Hash, number uint64, config *pa log.Error("Failed to derive bor receipt fields", "hash", hash, "number", number, "err", err) return nil } + return borReceipt } @@ -185,6 +186,7 @@ func ReadBorTxLookupEntry(db ethdb.Reader, txHash common.Hash) *uint64 { } number := new(big.Int).SetBytes(data).Uint64() + return &number } diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index 5a9badcd09..104af6213b 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -57,12 +57,14 @@ func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFre if err != nil { return nil, err } + cf := chainFreezer{ Freezer: freezer, quit: make(chan struct{}), trigger: make(chan chan struct{}), } cf.threshold.Store(params.FullImmutabilityThreshold) + return &cf, nil } @@ -74,6 +76,7 @@ func (f *chainFreezer) Close() error { close(f.quit) } f.wg.Wait() + return f.Freezer.Close() } @@ -89,6 +92,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { triggered chan struct{} // Used in tests nfdb = &nofreezedb{KeyValueStore: db} ) + timer := time.NewTimer(freezerRecheckInterval) defer timer.Stop() @@ -99,6 +103,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { return default: } + if backoff { // If we were doing a manual trigger, notify it if triggered != nil { @@ -108,6 +113,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { select { case <-timer.C: backoff = false + timer.Reset(freezerRecheckInterval) case triggered = <-f.trigger: backoff = false @@ -119,32 +125,45 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { hash := ReadHeadBlockHash(nfdb) if hash == (common.Hash{}) { log.Debug("Current full block hash unavailable") // new chain, empty database + backoff = true + continue } + number := ReadHeaderNumber(nfdb, hash) threshold := f.threshold.Load() frozen := f.frozen.Load() + switch { case number == nil: log.Error("Current full block number unavailable", "hash", hash) + backoff = true + continue case *number < threshold: log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold) + backoff = true + continue case *number-threshold <= frozen: log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", frozen) + backoff = true + continue } + head := ReadHeader(nfdb, hash, *number) if head == nil { log.Error("Current full block unavailable", "number", *number, "hash", hash) + backoff = true + continue } @@ -154,13 +173,17 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { first, _ = f.Ancients() limit = *number - threshold ) + if limit-first > freezerBatchLimit { limit = first + freezerBatchLimit } + ancients, err := f.freezeRange(nfdb, first, limit) if err != nil { log.Error("Error in block freeze operation", "err", err) + backoff = true + continue } @@ -171,6 +194,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { // Wipe out all data from the active database batch := db.NewBatch() + for i := 0; i < len(ancients); i++ { // Always keep the genesis block in active database if first+uint64(i) != 0 { @@ -178,13 +202,16 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { DeleteCanonicalHash(batch, first+uint64(i)) } } + if err := batch.Write(); err != nil { log.Crit("Failed to delete frozen canonical blocks", "err", err) } + batch.Reset() // Wipe out side chains also and track dangling side chains var dangling []common.Hash + frozen = f.frozen.Load() // Needs reload after during freezeRange for number := first; number < frozen; number++ { // Always keep the genesis block in active database @@ -196,20 +223,26 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { } } } + if err := batch.Write(); err != nil { log.Crit("Failed to delete frozen side blocks", "err", err) } + batch.Reset() // Step into the future and delete and dangling side chains if frozen > 0 { tip := frozen + for len(dangling) > 0 { drop := make(map[common.Hash]struct{}) + for _, hash := range dangling { log.Debug("Dangling parent from Freezer", "number", tip-1, "hash", hash) + drop[hash] = struct{}{} } + children := ReadAllHashes(db, tip) for i := 0; i < len(children); i++ { // Dig up the child and ensure it's dangling @@ -218,18 +251,22 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { log.Error("Missing dangling header", "number", tip, "hash", children[i]) continue } + if _, ok := drop[child.ParentHash]; !ok { children = append(children[:i], children[i+1:]...) i-- + continue } // Delete all block data associated with the child log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash) DeleteBlock(batch, children[i], tip) } + dangling = children tip++ } + if err := batch.Write(); err != nil { log.Crit("Failed to delete dangling side blocks", "err", err) } @@ -242,6 +279,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { if n := len(ancients); n > 0 { context = append(context, []interface{}{"hash", ancients[n-1]}...) } + log.Debug("Deep froze chain segment", context...) // Avoid database thrashing with tiny writes @@ -262,18 +300,22 @@ func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hash if hash == (common.Hash{}) { return fmt.Errorf("canonical hash missing, can't freeze block %d", number) } + header := ReadHeaderRLP(nfdb, hash, number) if len(header) == 0 { return fmt.Errorf("block header missing, can't freeze block %d", number) } + body := ReadBodyRLP(nfdb, hash, number) if len(body) == 0 { return fmt.Errorf("block body missing, can't freeze block %d", number) } + receipts := ReadReceiptsRLP(nfdb, hash, number) if len(receipts) == 0 { return fmt.Errorf("block receipts missing, can't freeze block %d", number) } + td := ReadTdRLP(nfdb, hash, number) if len(td) == 0 { return fmt.Errorf("total difficulty missing, can't freeze block %d", number) @@ -283,21 +325,26 @@ func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hash if err := op.AppendRaw(ChainFreezerHashTable, number, hash[:]); err != nil { return fmt.Errorf("can't write hash to Freezer: %v", err) } + if err := op.AppendRaw(ChainFreezerHeaderTable, number, header); err != nil { return fmt.Errorf("can't write header to Freezer: %v", err) } + if err := op.AppendRaw(ChainFreezerBodiesTable, number, body); err != nil { return fmt.Errorf("can't write body to Freezer: %v", err) } + if err := op.AppendRaw(ChainFreezerReceiptTable, number, receipts); err != nil { return fmt.Errorf("can't write receipts to Freezer: %v", err) } + if err := op.AppendRaw(ChainFreezerDifficultyTable, number, td); err != nil { return fmt.Errorf("can't write td to Freezer: %v", err) } hashes = append(hashes, hash) } + return nil }) diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 56bb15b718..c59b2dae77 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -38,22 +38,26 @@ func InitDatabaseFromFreezer(db ethdb.Database) { if err != nil || frozen == 0 { return } + var ( batch = db.NewBatch() start = time.Now() logged = start.Add(-7 * time.Second) // Unindex during import is fast, don't double log hash common.Hash ) + for i := uint64(0); i < frozen; { // We read 100K hashes at a time, for a total of 3.2M count := uint64(100_000) if i+count > frozen { count = frozen - i } + data, err := db.AncientRange(ChainFreezerHashTable, i, count, 32*count) if err != nil { log.Crit("Failed to init database from freezer", "err", err) } + for j, h := range data { number := i + uint64(j) hash = common.BytesToHash(h) @@ -63,9 +67,11 @@ func InitDatabaseFromFreezer(db ethdb.Database) { if err := batch.Write(); err != nil { log.Crit("Failed to write data to db", "err", err) } + batch.Reset() } } + i += uint64(len(data)) // If we've spent too much time already, notify the user of what we're doing if time.Since(logged) > 8*time.Second { @@ -73,9 +79,11 @@ func InitDatabaseFromFreezer(db ethdb.Database) { logged = time.Now() } } + if err := batch.Write(); err != nil { log.Crit("Failed to write data to db", "err", err) } + batch.Reset() WriteHeadHeaderHash(db, hash) @@ -98,13 +106,16 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool number uint64 rlp rlp.RawValue } + if to == from { return nil } + threads := to - from if cpus := runtime.NumCPU(); threads > uint64(cpus) { threads = uint64(cpus) } + var ( rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh @@ -115,7 +126,9 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool if reverse { n, end = to-1, from-1 } + defer close(rlpCh) + for n != end { data := ReadCanonicalBodyRLP(db, n) // Feed the block to the aggregator, or abort on interrupt @@ -124,6 +137,7 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool case <-interrupt: return } + if reverse { n-- } else { @@ -133,7 +147,9 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool } // process runs in parallel var nThreadsAlive atomic.Int32 + nThreadsAlive.Store(int32(threads)) + process := func() { defer func() { // Last processor closes the result channel @@ -141,16 +157,19 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool close(hashesCh) } }() + for data := range rlpCh { var body types.Body if err := rlp.DecodeBytes(data.rlp, &body); err != nil { log.Warn("Failed to decode block body", "block", data.number, "error", err) return } + var hashes []common.Hash for _, tx := range body.Transactions { hashes = append(hashes, tx.Hash()) } + result := &blockTxHashes{ hashes: hashes, number: data.number, @@ -163,10 +182,13 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool } } } + go lookup() // start the sequential db accessor + for i := 0; i < int(threads); i++ { go process() } + return hashesCh } @@ -183,6 +205,7 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan if from >= to { return } + var ( hashesCh = iterateTransactions(db, from, to, true, interrupt) batch = db.NewBatch() @@ -196,11 +219,13 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan // for stats reporting blocks, txs = 0, 0 ) + for chanDelivery := range hashesCh { // Push the delivery into the queue and process contiguous ranges. // Since we iterate in reverse, so lower numbers have lower prio, and // we can use the number directly as prio marker queue.Push(chanDelivery, int64(chanDelivery.number)) + for !queue.Empty() { // If the next available item is gapped, return if _, priority := queue.Peek(); priority != int64(lastNum-1) { @@ -214,15 +239,18 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan delivery := queue.PopItem() lastNum = delivery.number WriteTxLookupEntries(batch, delivery.number, delivery.hashes) + blocks++ txs += len(delivery.hashes) // If enough data was accumulated in memory or we're at the last block, dump to disk if batch.ValueSize() > ethdb.IdealBatchSize { WriteTxIndexTail(batch, lastNum) // Also write the tail here + if err := batch.Write(); err != nil { log.Crit("Failed writing batch to db", "error", err) return } + batch.Reset() } // If we've spent too much time already, notify the user of what we're doing @@ -236,6 +264,7 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan // 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 @@ -275,6 +304,7 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch if from >= to { return } + var ( hashesCh = iterateTransactions(db, from, to, false, interrupt) batch = db.NewBatch() @@ -291,6 +321,7 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch for delivery := range hashesCh { // Push the delivery into the queue and process contiguous ranges. queue.Push(delivery, -int64(delivery.number)) + for !queue.Empty() { // If the next available item is gapped, return if _, priority := queue.Peek(); -priority != int64(nextNum) { @@ -300,6 +331,7 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch if hook != nil && !hook(nextNum) { break } + delivery := queue.PopItem() nextNum = delivery.number + 1 DeleteTxLookupEntries(batch, delivery.hashes) @@ -311,10 +343,12 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch // often than that. if blocks%1000 == 0 { WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { log.Crit("Failed writing batch to db", "error", err) return } + batch.Reset() } // If we've spent too much time already, notify the user of what we're doing @@ -328,6 +362,7 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch // 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 diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go index e1f5159753..6673ca2669 100644 --- a/core/rawdb/chain_iterator_test.go +++ b/core/rawdb/chain_iterator_test.go @@ -32,11 +32,14 @@ func TestChainIterator(t *testing.T) { chainDb := NewMemoryDatabase() var block *types.Block + var txs []*types.Transaction + to := common.BytesToAddress([]byte{0x11}) block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) // Empty genesis block WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + for i := uint64(1); i <= 10; i++ { var tx *types.Transaction if i%2 == 0 { @@ -59,6 +62,7 @@ func TestChainIterator(t *testing.T) { Data: []byte{0x11, 0x11, 0x11}, }) } + txs = append(txs, tx) block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) WriteBlock(chainDb, block) @@ -78,12 +82,15 @@ func TestChainIterator(t *testing.T) { {0, 0, false, nil}, {10, 11, false, []int{10}}, } + for i, c := range cases { var numbers []int + hashCh := iterateTransactions(chainDb, c.from, c.to, c.reverse, nil) if hashCh != nil { for h := range hashCh { numbers = append(numbers, int(h.number)) + if len(h.hashes) > 0 { if got, exp := h.hashes[0], txs[h.number-1].Hash(); got != exp { t.Fatalf("block %d: hash wrong, got %x exp %x", h.number, got, exp) @@ -91,11 +98,13 @@ func TestChainIterator(t *testing.T) { } } } + if !c.reverse { sort.Ints(numbers) } else { sort.Sort(sort.Reverse(sort.IntSlice(numbers))) } + if !reflect.DeepEqual(numbers, c.expect) { t.Fatalf("Case %d failed, visit element mismatch, want %v, got %v", i, c.expect, numbers) } @@ -107,7 +116,9 @@ func TestIndexTransactions(t *testing.T) { chainDb := NewMemoryDatabase() var block *types.Block + var txs []*types.Transaction + to := common.BytesToAddress([]byte{0x11}) // Write empty genesis block @@ -137,6 +148,7 @@ func TestIndexTransactions(t *testing.T) { Data: []byte{0x11, 0x11, 0x11}, }) } + txs = append(txs, tx) block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) WriteBlock(chainDb, block) @@ -149,19 +161,23 @@ func TestIndexTransactions(t *testing.T) { if i == 0 { continue } + number := ReadTxLookupEntry(chainDb, txs[i-1].Hash()) if exist && number == nil { t.Fatalf("Transaction index %d missing", i) } + if !exist && number != nil { t.Fatalf("Transaction index %d is not deleted", i) } } + number := ReadTxIndexTail(chainDb) if number == nil || *number != tail { t.Fatalf("Transaction tail mismatch") } } + IndexTransactions(chainDb, 5, 11, nil) verify(5, 11, true, 5) verify(0, 5, false, 5) @@ -178,14 +194,18 @@ func TestIndexTransactions(t *testing.T) { // Testing corner cases signal := make(chan struct{}) + var once sync.Once + indexTransactionsForTesting(chainDb, 5, 11, signal, func(n uint64) bool { if n <= 8 { once.Do(func() { close(signal) }) + return false } + return true }) verify(9, 11, true, 9) @@ -193,14 +213,18 @@ func TestIndexTransactions(t *testing.T) { IndexTransactions(chainDb, 0, 9, nil) signal = make(chan struct{}) + var once2 sync.Once + unindexTransactionsForTesting(chainDb, 0, 11, signal, func(n uint64) bool { if n >= 8 { once2.Do(func() { close(signal) }) + return false } + return true }) verify(8, 11, true, 8) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 5f1322062e..5cc13d0632 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -53,12 +53,15 @@ func (frdb *freezerdb) Close() error { if err := frdb.AncientStore.Close(); err != nil { errs = append(errs, err) } + if err := frdb.KeyValueStore.Close(); err != nil { errs = append(errs, err) } + if len(errs) != 0 { return fmt.Errorf("%v", errs) } + return nil } @@ -79,6 +82,7 @@ func (frdb *freezerdb) Freeze(threshold uint64) error { trigger := make(chan struct{}, 1) frdb.AncientStore.(*chainFreezer).trigger <- trigger <-trigger + return nil } @@ -190,6 +194,7 @@ func resolveChainFreezerDir(ancient string) string { log.Info("Found legacy ancient chain path", "location", ancient) } } + return freezer } @@ -256,6 +261,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st } // We are about to exit on error. Print database metdata beore exiting printChainMetadata(db) + return nil, fmt.Errorf("gap in the chain between ancients [0 - #%d] and leveldb [#%d - #%d] ", frozen-1, number, head) } @@ -286,11 +292,13 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st // Freezer is consistent with the key-value database, permit combining the two if !frdb.readonly { frdb.wg.Add(1) + go func() { frdb.freeze(db) frdb.wg.Done() }() } + return &freezerdb{ ancientRoot: ancient, KeyValueStore: db, @@ -318,7 +326,9 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string, r if err != nil { return nil, err } + log.Info("Using LevelDB as the backing database") + return NewDatabase(db), nil } @@ -334,12 +344,15 @@ func hasPreexistingDb(path string) string { if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil { return "" // No pre-existing db } + if matches, err := filepath.Glob(filepath.Join(path, "OPTIONS*")); len(matches) > 0 || err != nil { if err != nil { panic(err) // only possible if the pattern is malformed } + return dbPebble } + return dbLeveldb } @@ -366,6 +379,7 @@ func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) { if len(existingDb) != 0 && len(o.Type) != 0 && o.Type != existingDb { return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb) } + if o.Type == dbPebble || existingDb == dbPebble { if PebbleEnabled { log.Info("Using pebble as the backing database") @@ -374,9 +388,11 @@ func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) { return nil, errors.New("db.engine 'pebble' not supported on this platform") } } + if len(o.Type) != 0 && o.Type != dbLeveldb { return nil, fmt.Errorf("unknown db.engine %v", o.Type) } + log.Info("Using leveldb as the backing database") // Use leveldb, either as default (no explicit choice), or pre-existing, or chosen explicitly return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) @@ -392,14 +408,17 @@ func Open(o OpenOptions) (ethdb.Database, error) { if err != nil { return nil, err } + if len(o.AncientsDirectory) == 0 { return kvdb, nil } + frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly) if err != nil { kvdb.Close() return nil, err } + return frdb, nil } @@ -478,7 +497,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { key = it.Key() size = common.StorageSize(len(key) + len(it.Value())) ) + total += size + switch { case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength): headers.Add(size) @@ -526,6 +547,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { bloomTrieNodes.Add(size) default: var accounted bool + for _, meta := range [][]byte{ databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, headFinalizedBlockKey, lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey, @@ -534,14 +556,18 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { } { if bytes.Equal(key, meta) { metadata.Add(size) + accounted = true + break } } + if !accounted { unaccounted.Add(size) } } + count++ if count%1000 == 0 && time.Since(logged) > 8*time.Second { log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) @@ -574,6 +600,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { if err != nil { return err } + for _, ancient := range ancients { for _, table := range ancient.sizes { stats = append(stats, []string{ @@ -586,8 +613,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { fmt.Sprintf("%d", ancient.count()), }) } + total += ancient.size() } + table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"Database", "Category", "Size", "Items"}) table.SetFooter([]string{"", "Total", total.String(), " "}) @@ -597,15 +626,18 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { if unaccounted.size > 0 { log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count) } + return nil } // printChainMetadata prints out chain metadata to stderr. func printChainMetadata(db ethdb.KeyValueStore) { fmt.Fprintf(os.Stderr, "Chain metadata\n") + for _, v := range ReadChainMetadata(db) { fmt.Fprintf(os.Stderr, " %s\n", strings.Join(v, ": ")) } + fmt.Fprintf(os.Stderr, "\n\n") } @@ -617,8 +649,10 @@ func ReadChainMetadata(db ethdb.KeyValueStore) [][]string { if val == nil { return "" } + return fmt.Sprintf("%d (%#x)", *val, *val) } + data := [][]string{ {"databaseVersion", pp(ReadDatabaseVersion(db))}, {"headBlockHash", fmt.Sprintf("%v", ReadHeadBlockHash(db))}, @@ -636,5 +670,6 @@ func ReadChainMetadata(db ethdb.KeyValueStore) [][]string { if b := ReadSkeletonSyncStatus(db); b != nil { data = append(data, []string{"SkeletonSyncStatus", string(b)}) } + return data } diff --git a/core/rawdb/databases_64bit.go b/core/rawdb/databases_64bit.go index 73bfeb2083..ae2ba7f2a0 100644 --- a/core/rawdb/databases_64bit.go +++ b/core/rawdb/databases_64bit.go @@ -33,5 +33,6 @@ func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, if err != nil { return nil, err } + return NewDatabase(db), nil } diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 909327a22e..b24fe8b0e6 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -102,6 +102,7 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui return nil, errSymlinkDatadir } } + flockFile := filepath.Join(datadir, "FLOCK") if err := os.MkdirAll(filepath.Dir(flockFile), 0755); err != nil { return nil, err @@ -129,8 +130,10 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui table.Close() } lock.Unlock() + return nil, err } + freezer.tables[name] = table } @@ -147,6 +150,7 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui return nil, err } } + var err error if freezer.readonly { // In readonly mode only validate, don't truncate. @@ -156,11 +160,13 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui // Truncate all tables to common length. err = freezer.repair() } + if err != nil { for _, table := range freezer.tables { table.Close() } lock.Unlock() + return nil, err } @@ -168,6 +174,7 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui freezer.writeBatch = newFreezerBatch(freezer) log.Info("Opened ancient database", "database", datadir, "readonly", readonly) + return freezer, nil } @@ -177,6 +184,7 @@ func (f *Freezer) Close() error { defer f.writeLock.Unlock() var errs []error + f.closeOnce.Do(func() { for _, table := range f.tables { if err := table.Close(); err != nil { @@ -187,9 +195,11 @@ func (f *Freezer) Close() error { errs = append(errs, err) } }) + if errs != nil { return fmt.Errorf("%v", errs) } + return nil } @@ -199,6 +209,7 @@ func (f *Freezer) HasAncient(kind string, number uint64) (bool, error) { if table := f.tables[kind]; table != nil { return table.has(number), nil } + return false, nil } @@ -207,6 +218,7 @@ func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) { if table := f.tables[kind]; table != nil { return table.Retrieve(number) } + return nil, errUnknownTable } @@ -219,6 +231,7 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][] if table := f.tables[kind]; table != nil { return table.RetrieveItems(start, count, maxBytes) } + return nil, errUnknownTable } @@ -242,6 +255,7 @@ func (f *Freezer) AncientSize(kind string) (uint64, error) { if table := f.tables[kind]; table != nil { return table.size() } + return 0, errUnknownTable } @@ -259,11 +273,13 @@ func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize if f.readonly { return 0, errReadOnly } + f.writeLock.Lock() defer f.writeLock.Unlock() // Roll back all tables to the starting position in case of error. prevItem := f.frozen.Load() + defer func() { if err != nil { // The write operation has failed. Go back to the previous item position. @@ -277,14 +293,18 @@ func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize }() f.writeBatch.reset() + if err := fn(f.writeBatch); err != nil { return 0, err } + item, writeSize, err := f.writeBatch.commit() if err != nil { return 0, err } + f.frozen.Store(item) + return writeSize, nil } @@ -293,18 +313,22 @@ func (f *Freezer) TruncateHead(items uint64) error { if f.readonly { return errReadOnly } + f.writeLock.Lock() defer f.writeLock.Unlock() if f.frozen.Load() <= items { return nil } + for _, table := range f.tables { if err := table.truncateHead(items); err != nil { return err } } + f.frozen.Store(items) + return nil } @@ -313,32 +337,39 @@ func (f *Freezer) TruncateTail(tail uint64) error { if f.readonly { return errReadOnly } + f.writeLock.Lock() defer f.writeLock.Unlock() if f.tail.Load() >= tail { return nil } + for _, table := range f.tables { if err := table.truncateTail(tail); err != nil { return err } } + f.tail.Store(tail) + return nil } // Sync flushes all data tables to disk. func (f *Freezer) Sync() error { var errs []error + for _, table := range f.tables { if err := table.Sync(); err != nil { errs = append(errs, err) } } + if errs != nil { return fmt.Errorf("%v", errs) } + return nil } @@ -348,6 +379,7 @@ func (f *Freezer) validate() error { if len(f.tables) == 0 { return nil } + var ( head uint64 tail uint64 @@ -358,6 +390,7 @@ func (f *Freezer) validate() error { head = table.items.Load() tail = table.itemHidden.Load() name = kind + break } // Now check every table against those boundaries. @@ -365,12 +398,15 @@ func (f *Freezer) validate() error { if head != table.items.Load() { return fmt.Errorf("freezer tables %s and %s have differing head: %d != %d", kind, name, table.items.Load(), head) } + if tail != table.itemHidden.Load() { return fmt.Errorf("freezer tables %s and %s have differing tail: %d != %d", kind, name, table.itemHidden.Load(), tail) } } + f.frozen.Store(head) f.tail.Store(tail) + return nil } @@ -380,26 +416,32 @@ func (f *Freezer) repair() error { head = uint64(math.MaxUint64) tail = uint64(0) ) + for _, table := range f.tables { items := table.items.Load() if head > items { head = items } + hidden := table.itemHidden.Load() if hidden > tail { tail = hidden } } + for _, table := range f.tables { if err := table.truncateHead(head); err != nil { return err } + if err := table.truncateTail(tail); err != nil { return err } } + f.frozen.Store(head) f.tail.Store(tail) + return nil } @@ -414,6 +456,7 @@ func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { if f.readonly { return errReadOnly } + f.writeLock.Lock() defer f.writeLock.Unlock() @@ -430,21 +473,26 @@ func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { batchSize = uint64(1024) maxBytes = uint64(1024 * 1024) ) + for i := offset; i < items; { if i+batchSize > items { batchSize = items - i } + data, err := t.RetrieveItems(i, batchSize, maxBytes) if err != nil { return err } + for j, item := range data { if err := fn(i+uint64(j), item); err != nil { return err } } + i += uint64(len(data)) } + return nil } // TODO(s1na): This is a sanity-check since as of now no process does tail-deletion. But the migration @@ -452,14 +500,17 @@ func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { if table.itemOffset.Load() > 0 || table.itemHidden.Load() > 0 { return fmt.Errorf("migration not supported for tail-deleted freezers") } + ancientsPath := filepath.Dir(table.index.Name()) // Set up new dir for the migrated table, the content of which // we'll at the end move over to the ancients dir. migrationPath := filepath.Join(ancientsPath, "migration") + newTable, err := newFreezerTable(migrationPath, kind, table.noCompression, false) if err != nil { return err } + var ( batch = newTable.newBatch() out []byte @@ -467,6 +518,7 @@ func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { logged = time.Now() offset = newTable.items.Load() ) + if offset > 0 { log.Info("found previous migration attempt", "migrated", offset) } @@ -488,9 +540,11 @@ func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { }); err != nil { return err } + if err := batch.commit(); err != nil { return err } + log.Info("Replacing old table files with migrated ones", "elapsed", common.PrettyDuration(time.Since(start))) // Release and delete old table files. Note this won't // delete the index file. @@ -499,6 +553,7 @@ func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { if err := newTable.Close(); err != nil { return err } + files, err := os.ReadDir(migrationPath) if err != nil { return err @@ -514,5 +569,6 @@ func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { if err := os.Remove(migrationPath); err != nil { return err } + return nil } diff --git a/core/rawdb/freezer_batch.go b/core/rawdb/freezer_batch.go index 3cc7d84f4e..3889c13526 100644 --- a/core/rawdb/freezer_batch.go +++ b/core/rawdb/freezer_batch.go @@ -38,6 +38,7 @@ func newFreezerBatch(f *Freezer) *freezerBatch { for kind, table := range f.tables { batch.tables[kind] = table.newBatch() } + return batch } @@ -67,6 +68,7 @@ func (batch *freezerBatch) commit() (item uint64, writeSize int64, err error) { if item < math.MaxUint64 && tb.curItem != item { return 0, 0, fmt.Errorf("table %s is at item %d, want %d", name, tb.curItem, item) } + item = tb.curItem } @@ -75,8 +77,10 @@ func (batch *freezerBatch) commit() (item uint64, writeSize int64, err error) { if err := tb.commit(); err != nil { return 0, 0, err } + writeSize += tb.totalBytes } + return item, writeSize, nil } @@ -98,7 +102,9 @@ func (t *freezerTable) newBatch() *freezerTableBatch { if !t.noCompression { batch.sb = new(snappyBuffer) } + batch.reset() + return batch } @@ -120,13 +126,16 @@ func (batch *freezerTableBatch) Append(item uint64, data interface{}) error { // Encode the item. batch.encBuffer.Reset() + if err := rlp.Encode(&batch.encBuffer, data); err != nil { return err } + encItem := batch.encBuffer.data if batch.sb != nil { encItem = batch.sb.compress(encItem) } + return batch.appendItem(encItem) } @@ -142,6 +151,7 @@ func (batch *freezerTableBatch) AppendRaw(item uint64, blob []byte) error { if batch.sb != nil { encItem = batch.sb.compress(blob) } + return batch.appendItem(encItem) } @@ -149,14 +159,17 @@ func (batch *freezerTableBatch) appendItem(data []byte) error { // Check if item fits into current data file. itemSize := int64(len(data)) itemOffset := batch.t.headBytes + int64(len(batch.dataBuffer)) + if itemOffset+itemSize > int64(batch.t.maxFileSize) { // It doesn't fit, go to next file first. if err := batch.commit(); err != nil { return err } + if err := batch.t.advanceHead(); err != nil { return err } + itemOffset = 0 } @@ -177,6 +190,7 @@ func (batch *freezerTableBatch) maybeCommit() error { if len(batch.dataBuffer) > freezerBatchBufferLimit { return batch.commit() } + return nil } @@ -187,6 +201,7 @@ func (batch *freezerTableBatch) commit() error { if err != nil { return err } + dataSize := int64(len(batch.dataBuffer)) batch.dataBuffer = batch.dataBuffer[:0] @@ -195,6 +210,7 @@ func (batch *freezerTableBatch) commit() error { if err != nil { return err } + indexSize := int64(len(batch.indexBuffer)) batch.indexBuffer = batch.indexBuffer[:0] @@ -205,6 +221,7 @@ func (batch *freezerTableBatch) commit() error { // Update metrics. batch.t.sizeGauge.Inc(dataSize + indexSize) batch.t.writeMeter.Mark(dataSize + indexSize) + return nil } @@ -225,10 +242,12 @@ func (s *snappyBuffer) compress(data []byte) []byte { if cap(s.dst) < n { s.dst = make([]byte, n) } + s.dst = s.dst[:n] } s.dst = snappy.Encode(s.dst, data) + return s.dst } diff --git a/core/rawdb/freezer_meta.go b/core/rawdb/freezer_meta.go index 9eef9df351..f2d9f46514 100644 --- a/core/rawdb/freezer_meta.go +++ b/core/rawdb/freezer_meta.go @@ -53,10 +53,12 @@ func readMetadata(file *os.File) (*freezerTableMeta, error) { if err != nil { return nil, err } + var meta freezerTableMeta if err := rlp.Decode(file, &meta); err != nil { return nil, err } + return &meta, nil } @@ -67,6 +69,7 @@ func writeMetadata(file *os.File, meta *freezerTableMeta) error { if err != nil { return err } + return rlp.Encode(file, meta) } @@ -89,8 +92,10 @@ func loadMetadata(file *os.File, tail uint64) (*freezerTableMeta, error) { if err := writeMetadata(file, m); err != nil { return nil, err } + return m, nil } + m, err := readMetadata(file) if err != nil { return nil, err @@ -100,10 +105,12 @@ func loadMetadata(file *os.File, tail uint64) (*freezerTableMeta, error) { // a warning here. if m.VirtualTail < tail { log.Warn("Updated virtual tail", "have", m.VirtualTail, "now", tail) + m.VirtualTail = tail if err := writeMetadata(file, m); err != nil { return nil, err } } + return m, nil } diff --git a/core/rawdb/freezer_meta_test.go b/core/rawdb/freezer_meta_test.go index ba1a95e453..d959eb648d 100644 --- a/core/rawdb/freezer_meta_test.go +++ b/core/rawdb/freezer_meta_test.go @@ -26,17 +26,21 @@ func TestReadWriteFreezerTableMeta(t *testing.T) { if err != nil { t.Fatalf("Failed to create file %v", err) } + err = writeMetadata(f, newMetadata(100)) if err != nil { t.Fatalf("Failed to write metadata %v", err) } + meta, err := readMetadata(f) if err != nil { t.Fatalf("Failed to read metadata %v", err) } + if meta.Version != freezerVersion { t.Fatalf("Unexpected version field") } + if meta.VirtualTail != uint64(100) { t.Fatalf("Unexpected virtual tail field") } @@ -47,13 +51,16 @@ func TestInitializeFreezerTableMeta(t *testing.T) { if err != nil { t.Fatalf("Failed to create file %v", err) } + meta, err := loadMetadata(f, uint64(100)) if err != nil { t.Fatalf("Failed to read metadata %v", err) } + if meta.Version != freezerVersion { t.Fatalf("Unexpected version field") } + if meta.VirtualTail != uint64(100) { t.Fatalf("Unexpected virtual tail field") } diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go index f9a56c6de5..d866a082ea 100644 --- a/core/rawdb/freezer_resettable.go +++ b/core/rawdb/freezer_resettable.go @@ -51,13 +51,16 @@ func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTa if err := cleanup(datadir); err != nil { return nil, err } + opener := func() (*Freezer, error) { return NewFreezer(datadir, namespace, readonly, maxTableSize, tables) } + freezer, err := opener() if err != nil { return nil, err } + return &ResettableFreezer{ freezer: freezer, opener: opener, @@ -76,18 +79,23 @@ func (f *ResettableFreezer) Reset() error { if err := f.freezer.Close(); err != nil { return err } + tmp := tmpName(f.datadir) if err := os.Rename(f.datadir, tmp); err != nil { return err } + if err := os.RemoveAll(tmp); err != nil { return err } + freezer, err := f.opener() if err != nil { return err } + f.freezer = freezer + return nil } @@ -209,22 +217,27 @@ func cleanup(path string) error { if _, err := os.Lstat(parent); os.IsNotExist(err) { return nil } + dir, err := os.Open(parent) if err != nil { return err } + names, err := dir.Readdirnames(0) if err != nil { return err } + if cerr := dir.Close(); cerr != nil { return cerr } + for _, name := range names { if name == filepath.Base(path)+tmpSuffix { return os.RemoveAll(filepath.Join(parent, name)) } } + return nil } diff --git a/core/rawdb/freezer_resettable_test.go b/core/rawdb/freezer_resettable_test.go index d1331442ff..c0cfd2cd4f 100644 --- a/core/rawdb/freezer_resettable_test.go +++ b/core/rawdb/freezer_resettable_test.go @@ -35,6 +35,7 @@ func TestResetFreezer(t *testing.T) { {1, bytes.Repeat([]byte{1}, 2048)}, {2, bytes.Repeat([]byte{2}, 2048)}, } + f, _ := NewResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef) defer f.Close() @@ -42,8 +43,10 @@ func TestResetFreezer(t *testing.T) { for _, item := range items { op.AppendRaw("test", item.id, item.blob) } + return nil }) + for _, item := range items { blob, _ := f.Ancient("test", item.id) if !bytes.Equal(blob, item.blob) { @@ -53,10 +56,12 @@ func TestResetFreezer(t *testing.T) { // Reset freezer f.Reset() + count, _ := f.Ancients() if count != 0 { t.Fatal("Failed to reset freezer") } + for _, item := range items { blob, _ := f.Ancient("test", item.id) if len(blob) != 0 { @@ -69,8 +74,10 @@ func TestResetFreezer(t *testing.T) { for _, item := range items { op.AppendRaw("test", item.id, item.blob) } + return nil }) + for _, item := range items { blob, _ := f.Ancient("test", item.id) if !bytes.Equal(blob, item.blob) { @@ -96,6 +103,7 @@ func TestFreezerCleanup(t *testing.T) { for _, item := range items { op.AppendRaw("test", item.id, item.blob) } + return nil }) f.Close() diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index fadacd9739..aa79f71b35 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -68,6 +68,7 @@ func (i *indexEntry) append(b []byte) []byte { out := append(b, make([]byte, indexEntrySize)...) binary.BigEndian.PutUint16(out[offset:], uint16(i.filenum)) binary.BigEndian.PutUint32(out[offset+2:], i.offset) + return out } @@ -81,6 +82,7 @@ func (i *indexEntry) bounds(end *indexEntry) (startOffset, endOffset, fileId uin // We return a zero-indexEntry for the second file as start return 0, end.offset, end.filenum } + return i.offset, end.offset, end.filenum } @@ -133,23 +135,27 @@ func newTable(path string, name string, readMeter metrics.Meter, writeMeter metr if err := os.MkdirAll(path, 0755); err != nil { return nil, err } + var idxName string if noCompression { idxName = fmt.Sprintf("%s.ridx", name) // raw index file } else { idxName = fmt.Sprintf("%s.cidx", name) // compressed index file } + var ( err error index *os.File meta *os.File ) + if readonly { // Will fail if table index file or meta file is not existent index, err = openFreezerFileForReadOnly(filepath.Join(path, idxName)) if err != nil { return nil, err } + meta, err = openFreezerFileForReadOnly(filepath.Join(path, fmt.Sprintf("%s.meta", name))) if err != nil { return nil, err @@ -159,6 +165,7 @@ func newTable(path string, name string, readMeter metrics.Meter, writeMeter metr if err != nil { return nil, err } + meta, err = openFreezerFileForAppend(filepath.Join(path, fmt.Sprintf("%s.meta", name))) if err != nil { return nil, err @@ -189,6 +196,7 @@ func newTable(path string, name string, readMeter metrics.Meter, writeMeter metr tab.Close() return nil, err } + tab.sizeGauge.Inc(int64(size)) return tab, nil @@ -205,6 +213,7 @@ func (t *freezerTable) repair() error { if err != nil { return err } + if stat.Size() == 0 { if _, err := t.index.Write(buffer); err != nil { return err @@ -218,6 +227,7 @@ func (t *freezerTable) repair() error { if stat, err = t.index.Stat(); err != nil { return err } + offsetsSize := stat.Size() // Open the head file @@ -245,6 +255,7 @@ func (t *freezerTable) repair() error { if err != nil { return err } + t.itemHidden.Store(meta.VirtualTail) // Read the last index, use the default value in case the freezer is empty @@ -254,17 +265,21 @@ func (t *freezerTable) repair() error { t.index.ReadAt(buffer, offsetsSize-indexEntrySize) lastIndex.unmarshalBinary(buffer) } + if t.readonly { t.head, err = t.openFile(lastIndex.filenum, openFreezerFileForReadOnly) } else { t.head, err = t.openFile(lastIndex.filenum, openFreezerFileForAppend) } + if err != nil { return err } + if stat, err = t.head.Stat(); err != nil { return err } + contentSize = stat.Size() // Keep truncating both files until they come in sync @@ -273,17 +288,21 @@ func (t *freezerTable) repair() error { // Truncate the head file to the last offset pointer if contentExp < contentSize { t.logger.Warn("Truncating dangling head", "indexed", contentExp, "stored", contentSize) + if err := truncateFreezerFile(t.head, contentExp); err != nil { return err } + contentSize = contentExp } // Truncate the index to point within the head file if contentExp > contentSize { t.logger.Warn("Truncating dangling indexes", "indexes", offsetsSize/indexEntrySize, "indexed", contentExp, "stored", contentSize) + if err := truncateFreezerFile(t.index, offsetsSize-indexEntrySize); err != nil { return err } + offsetsSize -= indexEntrySize // Read the new head index, use the default value in case @@ -299,16 +318,20 @@ func (t *freezerTable) repair() error { if newLastIndex.filenum != lastIndex.filenum { // Release earlier opened file t.releaseFile(lastIndex.filenum) + if t.head, err = t.openFile(newLastIndex.filenum, openFreezerFileForAppend); err != nil { return err } + if stat, err = t.head.Stat(); err != nil { // TODO, anything more we can do here? // A data file has gone missing... return err } + contentSize = stat.Size() } + lastIndex = newLastIndex contentExp = int64(lastIndex.offset) } @@ -319,9 +342,11 @@ func (t *freezerTable) repair() error { if err := t.index.Sync(); err != nil { return err } + if err := t.head.Sync(); err != nil { return err } + if err := t.meta.Sync(); err != nil { return err } @@ -341,11 +366,13 @@ func (t *freezerTable) repair() error { if err := t.preopen(); err != nil { return err } + if verbose { t.logger.Info("Chain freezer table opened", "items", t.items.Load(), "size", t.headBytes) } else { t.logger.Debug("Chain freezer table opened", "items", t.items.Load(), "size", common.StorageSize(t.headBytes)) } + return nil } @@ -363,12 +390,14 @@ func (t *freezerTable) preopen() (err error) { return err } } + if t.readonly { t.head, err = t.openFile(t.headId, openFreezerFileForReadOnly) } else { // Open head in read/write t.head, err = t.openFile(t.headId, openFreezerFileForAppend) } + return err } @@ -382,6 +411,7 @@ func (t *freezerTable) truncateHead(items uint64) error { if existing <= items { return nil } + if items < t.itemHidden.Load() { return errors.New("truncation below tail") } @@ -395,6 +425,7 @@ func (t *freezerTable) truncateHead(items uint64) error { if existing > items+1 { log = t.logger.Warn // Only loud warn if we delete multiple items } + log("Truncating freezer table", "items", existing, "limit", items) // Truncate the index file first, the tail position is also considered @@ -412,12 +443,14 @@ func (t *freezerTable) truncateHead(items uint64) error { if _, err := t.index.ReadAt(buffer, int64(length*indexEntrySize)); err != nil { return err } + expected.unmarshalBinary(buffer) } // We might need to truncate back to older files if expected.filenum != t.headId { // If already open for reading, force-reopen for writing t.releaseFile(expected.filenum) + newHead, err := t.openFile(expected.filenum, openFreezerFileForAppend) if err != nil { return err @@ -429,6 +462,7 @@ func (t *freezerTable) truncateHead(items uint64) error { t.head = newHead t.headId = expected.filenum } + if err := truncateFreezerFile(t.head, int64(expected.offset)); err != nil { return err } @@ -441,7 +475,9 @@ func (t *freezerTable) truncateHead(items uint64) error { if err != nil { return err } + t.sizeGauge.Dec(int64(oldSize - newSize)) + return nil } @@ -454,6 +490,7 @@ func (t *freezerTable) truncateTail(items uint64) error { if t.itemHidden.Load() >= items { return nil } + if t.items.Load() < items { return errors.New("truncation above head") } @@ -462,6 +499,7 @@ func (t *freezerTable) truncateTail(items uint64) error { newTailId uint32 buffer = make([]byte, indexEntrySize) ) + if t.items.Load() == items { newTailId = t.headId } else { @@ -469,12 +507,15 @@ func (t *freezerTable) truncateTail(items uint64) error { if _, err := t.index.ReadAt(buffer, int64((offset+1)*indexEntrySize)); err != nil { return err } + var newTail indexEntry + newTail.unmarshalBinary(buffer) newTailId = newTail.filenum } // Update the virtual tail marker and hidden these entries in table. t.itemHidden.Store(items) + if err := writeMetadata(t.meta, newMetadata(items)); err != nil { return err } @@ -499,15 +540,20 @@ func (t *freezerTable) truncateTail(items uint64) error { newDeleted = items deleted = t.itemOffset.Load() ) + for current := items - 1; current >= deleted; current -= 1 { if _, err := t.index.ReadAt(buffer, int64((current-deleted+1)*indexEntrySize)); err != nil { return err } + var pre indexEntry + pre.unmarshalBinary(buffer) + if pre.filenum != newTailId { break } + newDeleted = current } // Commit the changes of metadata file first before manipulating @@ -522,6 +568,7 @@ func (t *freezerTable) truncateTail(items uint64) error { offset: uint32(newDeleted), } _, err := f.Write(tailIndex.append(nil)) + return err }) if err != nil { @@ -531,6 +578,7 @@ func (t *freezerTable) truncateTail(items uint64) error { if err := t.index.Close(); err != nil { return err } + t.index, err = openFreezerFileForAppend(t.index.Name()) if err != nil { return err @@ -545,7 +593,9 @@ func (t *freezerTable) truncateTail(items uint64) error { if err != nil { return err } + t.sizeGauge.Dec(int64(oldSize - newSize)) + return nil } @@ -555,12 +605,14 @@ func (t *freezerTable) Close() error { defer t.lock.Unlock() var errs []error + doClose := func(f *os.File, sync bool, closed bool) { if sync && !t.readonly { if err := f.Sync(); err != nil { errs = append(errs, err) } } + if closed { if err := f.Close(); err != nil { errs = append(errs, err) @@ -575,9 +627,11 @@ func (t *freezerTable) Close() error { // The head is opened in rw-mode, so we sync it here - but since it's also // part of t.files, it will be closed in the loop below. doClose(t.head, true, false) // sync but do not close + for _, f := range t.files { doClose(f, false, true) // close but do not sync } + t.index = nil t.meta = nil t.head = nil @@ -585,6 +639,7 @@ func (t *freezerTable) Close() error { if errs != nil { return fmt.Errorf("%v", errs) } + return nil } @@ -598,12 +653,15 @@ func (t *freezerTable) openFile(num uint32, opener func(string) (*os.File, error } else { name = fmt.Sprintf("%s.%04d.cdat", t.name, num) } + f, err = opener(filepath.Join(t.path, name)) if err != nil { return nil, err } + t.files[num] = f } + return f, err } @@ -622,6 +680,7 @@ func (t *freezerTable) releaseFilesAfter(num uint32, remove bool) { if fnum > num { delete(t.files, fnum) f.Close() + if remove { os.Remove(f.Name()) } @@ -635,6 +694,7 @@ func (t *freezerTable) releaseFilesBefore(num uint32, remove bool) { if fnum < num { delete(t.files, fnum) f.Close() + if remove { os.Remove(f.Name()) } @@ -656,16 +716,20 @@ func (t *freezerTable) getIndices(from, count uint64) ([]*indexEntry, error) { if _, err := t.index.ReadAt(buffer, int64(from*indexEntrySize)); err != nil { return nil, err } + var ( indices []*indexEntry offset int ) + for i := from; i <= from+count; i++ { index := new(indexEntry) index.unmarshalBinary(buffer[offset:]) offset += indexEntrySize + indices = append(indices, index) } + if from == 0 { // Special case if we're reading the first item in the freezer. We assume that // the first item always start from zero(regarding the deletion, we @@ -675,6 +739,7 @@ func (t *freezerTable) getIndices(from, count uint64) ([]*indexEntry, error) { indices[0].offset = 0 indices[0].filenum = indices[1].filenum } + return indices, nil } @@ -685,6 +750,7 @@ func (t *freezerTable) Retrieve(item uint64) ([]byte, error) { if err != nil { return nil, err } + return items[0], nil } @@ -698,6 +764,7 @@ func (t *freezerTable) RetrieveItems(start, count, maxBytes uint64) ([][]byte, e if err != nil { return nil, err } + var ( output = make([][]byte, 0, count) offset int // offset for reading @@ -707,24 +774,30 @@ func (t *freezerTable) RetrieveItems(start, count, maxBytes uint64) ([][]byte, e for i, diskSize := range sizes { item := diskData[offset : offset+diskSize] offset += diskSize + decompressedSize := diskSize if !t.noCompression { decompressedSize, _ = snappy.DecodedLen(item) } + if i > 0 && uint64(outputSize+decompressedSize) > maxBytes { break } + if !t.noCompression { data, err := snappy.Decode(nil, item) if err != nil { return nil, err } + output = append(output, data) } else { output = append(output, item) } + outputSize += decompressedSize } + return output, nil } @@ -739,6 +812,7 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i if t.index == nil || t.head == nil || t.meta == nil { return nil, nil, errClosed } + var ( items = t.items.Load() // the total items(head + 1) hidden = t.itemHidden.Load() // the number of hidden items @@ -748,9 +822,11 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i if items <= start || hidden > start || count == 0 { return nil, nil, errOutOfBounds } + if start+count > items { count = items - start } + var ( output = make([]byte, maxBytes) // Buffer to read data into outputSize int // Used size of that buffer @@ -762,14 +838,18 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i if len(output) < length { output = make([]byte, length) } + dataFile, exist := t.files[fileId] if !exist { return fmt.Errorf("missing data file %d", fileId) } + if _, err := dataFile.ReadAt(output[outputSize:outputSize+length], int64(start)); err != nil { return err } + outputSize += length + return nil } // Read all the indexes in one go @@ -777,6 +857,7 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i if err != nil { return nil, nil, err } + var ( sizes []int // The sizes for each element totalSize = 0 // The total size of all data read so far @@ -796,10 +877,13 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i if err := readData(firstIndex.filenum, readStart, unreadSize); err != nil { return nil, nil, err } + unreadSize = 0 } + readStart = 0 } + if i > 0 && uint64(totalSize+size) > maxBytes { // About to break out due to byte limit being exceeded. We don't // read this last item, but we need to do the deferred reads now. @@ -808,23 +892,27 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i return nil, nil, err } } + break } // Defer the read for later unreadSize += size totalSize += size sizes = append(sizes, size) + if i == len(indices)-2 || uint64(totalSize) > maxBytes { // Last item, need to do the read now if err := readData(secondIndex.filenum, readStart, unreadSize); err != nil { return nil, nil, err } + break } } // Update metrics. t.readMeter.Mark(int64(totalSize)) + return output[:outputSize], sizes, nil } @@ -849,7 +937,9 @@ func (t *freezerTable) sizeNolock() (uint64, error) { if err != nil { return 0, err } + total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size()) + return total, nil } @@ -863,6 +953,7 @@ func (t *freezerTable) advanceHead() error { // We open the next file in truncated mode -- if this file already // exists, we need to start over from scratch on it. nextID := t.headId + 1 + newHead, err := t.openFile(nextID, openFreezerFileTruncated) if err != nil { return err @@ -872,6 +963,7 @@ func (t *freezerTable) advanceHead() error { if err := t.head.Sync(); err != nil { return err } + t.releaseFile(t.headId) t.openFile(t.headId, openFreezerFileForReadOnly) @@ -879,6 +971,7 @@ func (t *freezerTable) advanceHead() error { t.head = newHead t.headBytes = 0 t.headId = nextID + return nil } @@ -887,10 +980,13 @@ func (t *freezerTable) advanceHead() error { func (t *freezerTable) Sync() error { t.lock.Lock() defer t.lock.Unlock() + if t.index == nil || t.head == nil || t.meta == nil { return errClosed } + var err error + trackError := func(e error) { if e != nil && err == nil { err = e @@ -900,6 +996,7 @@ func (t *freezerTable) Sync() error { trackError(t.index.Sync()) trackError(t.meta.Sync()) trackError(t.head.Sync()) + return err } @@ -909,8 +1006,10 @@ func (t *freezerTable) dumpIndexStdout(start, stop int64) { func (t *freezerTable) dumpIndexString(start, stop int64) string { var out bytes.Buffer + out.WriteString("\n") t.dumpIndex(&out, start, stop) + return out.String() } @@ -920,6 +1019,7 @@ func (t *freezerTable) dumpIndex(w io.Writer, start, stop int64) { fmt.Fprintf(w, "Failed to decode freezer table %v\n", err) return } + fmt.Fprintf(w, "Version %d count %d, deleted %d, hidden %d\n", meta.Version, t.items.Load(), t.itemOffset.Load(), t.itemHidden.Load()) @@ -932,9 +1032,12 @@ func (t *freezerTable) dumpIndex(w io.Writer, start, stop int64) { if _, err := t.index.ReadAt(buf, int64((i+1)*indexEntrySize)); err != nil { break } + var entry indexEntry + entry.unmarshalBinary(buf) fmt.Fprintf(w, "| %03d | %03d | %03d | \n", i, entry.filenum, entry.offset) + if stop > 0 && i >= uint64(stop) { break } @@ -951,6 +1054,7 @@ func (t *freezerTable) Fill(number uint64) error { if t.items.Load() < number { b := t.newBatch() log.Info("Filling all data into freezer for backward compatablity", "name", t.name, "items", t.items, "number", number) + for t.items.Load() < number { if err := b.Append(t.items.Load(), nil); err != nil { log.Error("Failed to fill data into freezer", "name", t.name, "items", t.items, "number", number, "err", err) @@ -959,5 +1063,6 @@ func (t *freezerTable) Fill(number uint64) error { } b.commit() } + return nil } diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index 5c4cc40edd..aea63879e6 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -58,10 +58,12 @@ func TestFreezerBasics(t *testing.T) { for y := 0; y < 255; y++ { exp := getChunk(15, y) + got, err := f.Retrieve(uint64(y)) if err != nil { t.Fatalf("reading item %d: %v", y, err) } + if !bytes.Equal(got, exp) { t.Fatalf("test %d, got \n%x != \n%x", y, got, exp) } @@ -84,6 +86,7 @@ func TestFreezerBasicsClosing(t *testing.T) { f *freezerTable err error ) + f, err = newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) if err != nil { t.Fatal(err) @@ -103,18 +106,23 @@ func TestFreezerBasicsClosing(t *testing.T) { t.Fatal(err) } } + defer f.Close() for y := 0; y < 255; y++ { exp := getChunk(15, y) + got, err := f.Retrieve(uint64(y)) if err != nil { t.Fatal(err) } + if !bytes.Equal(got, exp) { t.Fatalf("test %d, got \n%x != \n%x", y, got, exp) } + f.Close() + f, err = newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) if err != nil { t.Fatal(err) @@ -125,6 +133,7 @@ func TestFreezerBasicsClosing(t *testing.T) { // TestFreezerRepairDanglingHead tests that we can recover if index entries are removed func TestFreezerRepairDanglingHead(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64()) @@ -141,6 +150,7 @@ func TestFreezerRepairDanglingHead(t *testing.T) { if _, err = f.Retrieve(0xfe); err != nil { t.Fatal(err) } + f.Close() } @@ -154,6 +164,7 @@ func TestFreezerRepairDanglingHead(t *testing.T) { if err != nil { t.Fatalf("Failed to stat index file: %v", err) } + idxFile.Truncate(stat.Size() - 4) idxFile.Close() @@ -177,6 +188,7 @@ func TestFreezerRepairDanglingHead(t *testing.T) { // TestFreezerRepairDanglingHeadLarge tests that we can recover if very many index entries are removed func TestFreezerRepairDanglingHeadLarge(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64()) @@ -193,6 +205,7 @@ func TestFreezerRepairDanglingHeadLarge(t *testing.T) { if _, err = f.Retrieve(f.items.Load() - 1); err != nil { t.Fatal(err) } + f.Close() } @@ -232,12 +245,15 @@ func TestFreezerRepairDanglingHeadLarge(t *testing.T) { // And if we open it, we should now be able to read all of them (new values) { f, _ := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + for y := 1; y < 255; y++ { exp := getChunk(15, ^y) + got, err := f.Retrieve(uint64(y)) if err != nil { t.Fatal(err) } + if !bytes.Equal(got, exp) { t.Fatalf("test %d, got \n%x != \n%x", y, got, exp) } @@ -248,6 +264,7 @@ func TestFreezerRepairDanglingHeadLarge(t *testing.T) { // TestSnappyDetection tests that we fail to open a snappy database and vice versa func TestSnappyDetection(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("snappytest-%d", rand.Uint64()) @@ -268,6 +285,7 @@ func TestSnappyDetection(t *testing.T) { if err != nil { t.Fatal(err) } + if _, err = f.Retrieve(0); err == nil { f.Close() t.Fatalf("expected empty table") @@ -293,9 +311,11 @@ func assertFileSize(f string, size int64) error { if err != nil { return err } + if stat.Size() != size { return fmt.Errorf("error, expected size %d, got %d", size, stat.Size()) } + return nil } @@ -303,6 +323,7 @@ func assertFileSize(f string, size int64) error { // the index is repaired func TestFreezerRepairDanglingIndex(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("dangling_indextest-%d", rand.Uint64()) @@ -320,6 +341,7 @@ func TestFreezerRepairDanglingIndex(t *testing.T) { f.Close() t.Fatal(err) } + f.Close() // File sizes should be 45, 45, 45 : items[3, 3, 3) } @@ -331,10 +353,12 @@ func TestFreezerRepairDanglingIndex(t *testing.T) { if err := assertFileSize(fileToCrop, 45); err != nil { t.Fatal(err) } + file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644) if err != nil { t.Fatal(err) } + file.Truncate(20) file.Close() } @@ -348,10 +372,13 @@ func TestFreezerRepairDanglingIndex(t *testing.T) { if err != nil { t.Fatal(err) } + defer f.Close() + if f.items.Load() != 7 { t.Fatalf("expected %d items, got %d", 7, f.items.Load()) } + if err := assertFileSize(fileToCrop, 15); err != nil { t.Fatal(err) } @@ -360,6 +387,7 @@ func TestFreezerRepairDanglingIndex(t *testing.T) { func TestFreezerTruncate(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("truncation-%d", rand.Uint64()) @@ -376,6 +404,7 @@ func TestFreezerTruncate(t *testing.T) { if _, err = f.Retrieve(f.items.Load() - 1); err != nil { t.Fatal(err) } + f.Close() } @@ -385,8 +414,10 @@ func TestFreezerTruncate(t *testing.T) { if err != nil { t.Fatal(err) } + defer f.Close() f.truncateHead(10) // 150 bytes + if f.items.Load() != 10 { t.Fatalf("expected %d items, got %d", 10, f.items.Load()) } @@ -401,6 +432,7 @@ func TestFreezerTruncate(t *testing.T) { // That will rewind the index, and _should_ truncate the head file func TestFreezerRepairFirstFile(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("truncationfirst-%d", rand.Uint64()) @@ -420,6 +452,7 @@ func TestFreezerRepairFirstFile(t *testing.T) { if _, err = f.Retrieve(1); err != nil { t.Fatal(err) } + f.Close() } @@ -429,10 +462,12 @@ func TestFreezerRepairFirstFile(t *testing.T) { if err := assertFileSize(fileToCrop, 40); err != nil { t.Fatal(err) } + file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644) if err != nil { t.Fatal(err) } + file.Truncate(20) file.Close() } @@ -443,6 +478,7 @@ func TestFreezerRepairFirstFile(t *testing.T) { if err != nil { t.Fatal(err) } + if f.items.Load() != 1 { f.Close() t.Fatalf("expected %d items, got %d", 0, f.items.Load()) @@ -469,6 +505,7 @@ func TestFreezerRepairFirstFile(t *testing.T) { // - check that we did not keep the rdonly file descriptors func TestFreezerReadAndTruncate(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("read_truncate-%d", rand.Uint64()) @@ -485,6 +522,7 @@ func TestFreezerReadAndTruncate(t *testing.T) { if _, err = f.Retrieve(f.items.Load() - 1); err != nil { t.Fatal(err) } + f.Close() } @@ -494,10 +532,12 @@ func TestFreezerReadAndTruncate(t *testing.T) { if err != nil { t.Fatal(err) } + if f.items.Load() != 30 { f.Close() t.Fatalf("expected %d items, got %d", 0, f.items.Load()) } + for y := byte(0); y < 30; y++ { f.Retrieve(uint64(y)) } @@ -517,6 +557,7 @@ func TestFreezerReadAndTruncate(t *testing.T) { func TestFreezerOffset(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("offset-%d", rand.Uint64()) @@ -554,10 +595,12 @@ func TestFreezerOffset(t *testing.T) { } // Read the index file p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname)) + indexFile, err := os.OpenFile(p, os.O_RDWR, 0644) if err != nil { t.Fatal(err) } + indexBuf := make([]byte, 7*indexEntrySize) indexFile.Read(indexBuf) @@ -588,6 +631,7 @@ func TestFreezerOffset(t *testing.T) { if err != nil { t.Fatal(err) } + defer f.Close() t.Log(f.dumpIndexString(0, 100)) @@ -613,10 +657,12 @@ func TestFreezerOffset(t *testing.T) { { // Read the index file p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname)) + indexFile, err := os.OpenFile(p, os.O_RDWR, 0644) if err != nil { t.Fatal(err) } + indexBuf := make([]byte, 3*indexEntrySize) indexFile.Read(indexBuf) @@ -641,6 +687,7 @@ func TestFreezerOffset(t *testing.T) { if err != nil { t.Fatal(err) } + defer f.Close() t.Log(f.dumpIndexString(0, 100)) @@ -660,6 +707,7 @@ func TestFreezerOffset(t *testing.T) { func TestTruncateTail(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("truncate-tail-%d", rand.Uint64()) @@ -710,10 +758,12 @@ func TestTruncateTail(t *testing.T) { // Reopen the table, the deletion information should be persisted as well f.Close() + f, err = newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) if err != nil { t.Fatal(err) } + checkRetrieveError(t, f, map[uint64]error{ 0: errOutOfBounds, }) @@ -742,10 +792,12 @@ func TestTruncateTail(t *testing.T) { // Reopen the table, the above testing should still pass f.Close() + f, err = newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) if err != nil { t.Fatal(err) } + defer f.Close() checkRetrieveError(t, f, map[uint64]error{ @@ -775,6 +827,7 @@ func TestTruncateTail(t *testing.T) { func TestTruncateHead(t *testing.T) { t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() fname := fmt.Sprintf("truncate-head-blow-tail-%d", rand.Uint64()) @@ -831,6 +884,7 @@ func checkRetrieve(t *testing.T, f *freezerTable, items map[uint64][]byte) { if err != nil { t.Fatalf("can't get expected item %d: %v", item, err) } + if !bytes.Equal(value, wantBytes) { t.Fatalf("item %d has wrong value %x (want %x)", item, value, wantBytes) } @@ -845,6 +899,7 @@ func checkRetrieveError(t *testing.T, f *freezerTable, items map[uint64]error) { if err == nil { t.Fatalf("unexpected value %x for item %d, want error %v", item, value, wantError) } + if err != wantError { t.Fatalf("wrong error for item %d: %v", item, err) } @@ -857,6 +912,7 @@ func getChunk(size int, b int) []byte { for i := range data { data[i] = byte(b) } + return data } @@ -880,6 +936,7 @@ func writeChunks(t *testing.T, ft *freezerTable, n int, length int) { t.Fatalf("AppendRaw(%d, ...) returned error: %v", i, err) } } + if err := batch.commit(); err != nil { t.Fatalf("Commit returned error: %v", err) } @@ -904,19 +961,23 @@ func TestSequentialRead(t *testing.T) { if err != nil { t.Fatal(err) } + items, err := f.RetrieveItems(0, 10000, 100000) if err != nil { t.Fatal(err) } + if have, want := len(items), 30; have != want { t.Fatalf("want %d items, have %d ", want, have) } + for i, have := range items { want := getChunk(15, i) if !bytes.Equal(want, have) { t.Fatalf("data corruption: have\n%x\n, want \n%x\n", have, want) } } + f.Close() } { // Open it, iterate, verify byte limit. The byte limit is less than item @@ -925,19 +986,23 @@ func TestSequentialRead(t *testing.T) { if err != nil { t.Fatal(err) } + items, err := f.RetrieveItems(0, 10000, 10) if err != nil { t.Fatal(err) } + if have, want := len(items), 1; have != want { t.Fatalf("want %d items, have %d ", want, have) } + for i, have := range items { want := getChunk(15, i) if !bytes.Equal(want, have) { t.Fatalf("data corruption: have\n%x\n, want \n%x\n", have, want) } } + f.Close() } } @@ -959,6 +1024,7 @@ func TestSequentialReadByteLimit(t *testing.T) { writeChunks(t, f, 30, 10) f.Close() } + for i, tc := range []struct { items uint64 limit uint64 @@ -976,19 +1042,23 @@ func TestSequentialReadByteLimit(t *testing.T) { if err != nil { t.Fatal(err) } + items, err := f.RetrieveItems(0, tc.items, tc.limit) if err != nil { t.Fatal(err) } + if have, want := len(items), tc.want; have != want { t.Fatalf("test %d: want %d items, have %d ", i, want, have) } + for ii, have := range items { want := getChunk(10, ii) if !bytes.Equal(want, have) { t.Fatalf("test %d: data corruption item %d: have\n%x\n, want \n%x\n", i, ii, have, want) } } + f.Close() } } @@ -1006,6 +1076,7 @@ func TestFreezerReadonly(t *testing.T) { // Case 2: Check that it fails on invalid index length. fname := fmt.Sprintf("readonlytest-%d", rand.Uint64()) + idxFile, err := openFreezerFileForAppend(filepath.Join(tmpdir, fmt.Sprintf("%s.ridx", fname))) if err != nil { t.Errorf("Failed to open index file: %v\n", err) @@ -1013,6 +1084,7 @@ func TestFreezerReadonly(t *testing.T) { // size should not be a multiple of indexEntrySize. idxFile.Write(make([]byte, 17)) idxFile.Close() + _, err = newTable(tmpdir, fname, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, true) if err == nil { @@ -1023,19 +1095,23 @@ func TestFreezerReadonly(t *testing.T) { // Then corrupt the head file and make sure opening the table // again in readonly triggers an error. fname = fmt.Sprintf("readonlytest-%d", rand.Uint64()) + f, err := newTable(tmpdir, fname, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, false) if err != nil { t.Fatalf("failed to instantiate table: %v", err) } + writeChunks(t, f, 8, 32) // Corrupt table file if _, err := f.head.Write([]byte{1, 1}); err != nil { t.Fatal(err) } + if err := f.Close(); err != nil { t.Fatal(err) } + _, err = newTable(tmpdir, fname, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, true) if err == nil { @@ -1045,24 +1121,30 @@ func TestFreezerReadonly(t *testing.T) { // Case 4: Write some data to a table and later re-open it as readonly. // Should be successful. fname = fmt.Sprintf("readonlytest-%d", rand.Uint64()) + f, err = newTable(tmpdir, fname, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, false) if err != nil { t.Fatalf("failed to instantiate table: %v\n", err) } + writeChunks(t, f, 32, 128) + if err := f.Close(); err != nil { t.Fatal(err) } + f, err = newTable(tmpdir, fname, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, true) if err != nil { t.Fatal(err) } + v, err := f.Retrieve(10) if err != nil { t.Fatal(err) } + exp := getChunk(128, 10) if !bytes.Equal(v, exp) { t.Errorf("retrieved value is incorrect") @@ -1072,9 +1154,11 @@ func TestFreezerReadonly(t *testing.T) { // This should fail either during AppendRaw or Commit batch := f.newBatch() writeErr := batch.AppendRaw(32, make([]byte, 1)) + if writeErr == nil { writeErr = batch.commit() } + if writeErr == nil { t.Fatalf("Writing to readonly table should fail") } @@ -1106,11 +1190,13 @@ const ( func getVals(first uint64, n int) [][]byte { var ret [][]byte + for i := 0; i < n; i++ { val := make([]byte, 8) binary.BigEndian.PutUint64(val, first+uint64(i)) ret = append(ret, val) } + return ret } @@ -1149,12 +1235,14 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value { ) var steps randTest + for i := 0; i < size; i++ { step := randTestStep{op: r.Intn(opMax)} switch step.op { case opReload, opCheckAll: case opAppend: num := r.Intn(3) + step.items = addItems(num) if len(step.items) == 0 { step.blobs = nil @@ -1188,22 +1276,28 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value { items = items[:0] deleted = step.target } + steps = append(steps, step) } + return reflect.ValueOf(steps) } func runRandTest(rt randTest) bool { fname := fmt.Sprintf("randtest-%d", rand.Uint64()) + f, err := newTable(os.TempDir(), fname, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, false) if err != nil { panic("failed to initialize table") } + var values [][]byte + for i, step := range rt { switch step.op { case opReload: f.Close() + f, err = newTable(os.TempDir(), fname, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, false) if err != nil { rt[i].err = fmt.Errorf("failed to reload table %v", err) @@ -1215,6 +1309,7 @@ func runRandTest(rt randTest) bool { if tail == head { continue } + got, err := f.RetrieveItems(f.itemHidden.Load(), head-tail, 100000) if err != nil { rt[i].err = err @@ -1230,17 +1325,21 @@ func runRandTest(rt randTest) bool { batch.AppendRaw(step.items[i], step.blobs[i]) } batch.commit() + values = append(values, step.blobs...) case opRetrieve: var blobs [][]byte + if len(step.items) == 0 { continue } + tail := f.itemHidden.Load() for i := 0; i < len(step.items); i++ { blobs = append(blobs, values[step.items[i]-tail]) } + got, err := f.RetrieveItems(step.items[0], uint64(len(step.items)), 100000) if err != nil { rt[i].err = err @@ -1258,6 +1357,7 @@ func runRandTest(rt randTest) bool { case opTruncateHeadAll: f.truncateHead(step.target) + values = nil case opTruncateTail: @@ -1269,6 +1369,7 @@ func runRandTest(rt randTest) bool { case opTruncateTailAll: f.truncateTail(step.target) + values = nil } // Abort the test on error. @@ -1276,7 +1377,9 @@ func runRandTest(rt randTest) bool { return false } } + f.Close() + return true } @@ -1285,6 +1388,7 @@ func TestRandom(t *testing.T) { if cerr, ok := err.(*quick.CheckError); ok { t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In)) } + t.Fatal(err) } } diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go index 630c9029b0..de05374e86 100644 --- a/core/rawdb/freezer_test.go +++ b/core/rawdb/freezer_test.go @@ -39,7 +39,9 @@ func TestFreezerModify(t *testing.T) { // Create test data. var valuesRaw [][]byte + var valuesRLP []*big.Int + for x := 0; x < 100; x++ { v := getChunk(256, x) valuesRaw = append(valuesRaw, v) @@ -49,6 +51,7 @@ func TestFreezerModify(t *testing.T) { } tables := map[string]bool{"raw": true, "rlp": false} + f, _ := newFreezerForTesting(t, tables) defer f.Close() @@ -58,10 +61,12 @@ func TestFreezerModify(t *testing.T) { if err := op.AppendRaw("raw", uint64(i), valuesRaw[i]); err != nil { return err } + if err := op.Append("rlp", uint64(i), valuesRLP[i]); err != nil { return err } } + return nil }) if err != nil { @@ -76,13 +81,16 @@ func TestFreezerModify(t *testing.T) { // Read back test data. checkAncientCount(t, f, "raw", uint64(len(valuesRaw))) checkAncientCount(t, f, "rlp", uint64(len(valuesRLP))) + for i := range valuesRaw { v, _ := f.Ancient("raw", uint64(i)) if !bytes.Equal(v, valuesRaw[i]) { t.Fatalf("wrong raw value at %d: %x", i, v) } + ivEnc, _ := f.Ancient("rlp", uint64(i)) want, _ := rlp.EncodeToBytes(valuesRLP[i]) + if !bytes.Equal(ivEnc, want) { t.Fatalf("wrong RLP value at %d: %x", i, ivEnc) } @@ -103,20 +111,25 @@ func TestFreezerModifyRollback(t *testing.T) { require.NoError(t, op.AppendRaw("test", 0, make([]byte, 2048))) require.NoError(t, op.AppendRaw("test", 1, make([]byte, 2048))) require.NoError(t, op.AppendRaw("test", 2, make([]byte, 2048))) + return theError }) + if err != theError { t.Errorf("ModifyAncients returned wrong error %q", err) } + checkAncientCount(t, f, "test", 0) f.Close() // Reopen and check that the rolled-back data doesn't reappear. tables := map[string]bool{"test": true} + f2, err := NewFreezer(dir, "", false, 2049, tables) if err != nil { t.Fatalf("can't reopen freezer after failed ModifyAncients: %v", err) } + defer f2.Close() checkAncientCount(t, f2, "test", 0) } @@ -134,26 +147,31 @@ func TestFreezerConcurrentModifyRetrieve(t *testing.T) { written = make(chan uint64, numReaders*6) wg sync.WaitGroup ) + wg.Add(numReaders + 1) // Launch the writer. It appends 10000 items in batches. go func() { defer wg.Done() defer close(written) + for item := uint64(0); item < 10000; item += writeBatchSize { _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { for i := uint64(0); i < writeBatchSize; i++ { item := item + i value := getChunk(32, int(item)) + if err := op.AppendRaw("test", item, value); err != nil { return err } } + return nil }) if err != nil { panic(err) } + for i := 0; i < numReaders; i++ { written <- item + writeBatchSize } @@ -165,13 +183,16 @@ func TestFreezerConcurrentModifyRetrieve(t *testing.T) { for i := 0; i < numReaders; i++ { go func() { defer wg.Done() + for frozen := range written { for rc := 0; rc < 80; rc++ { num := uint64(rand.Intn(int(frozen))) value, err := f.Ancient("test", num) + if err != nil { panic(fmt.Errorf("error reading %d (frozen %d): %v", num, frozen, err)) } + if !bytes.Equal(value, getChunk(32, int(num))) { panic(fmt.Errorf("wrong value at %d", num)) } @@ -195,17 +216,20 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) { if err := f.TruncateHead(0); err != nil { t.Fatal("truncate failed:", err) } + _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { for i := uint64(0); i < 100; i++ { if err := op.AppendRaw("test", i, item); err != nil { return err } } + return nil }) if err != nil { t.Fatal("modify failed:", err) } + checkAncientCount(t, f, "test", 100) // Now append 100 more items and truncate concurrently. @@ -214,7 +238,9 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) { truncateErr error modifyErr error ) + wg.Add(3) + go func() { _, modifyErr = f.ModifyAncients(func(op ethdb.AncientWriteOp) error { for i := uint64(100); i < 200; i++ { @@ -222,12 +248,15 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) { return err } } + return nil }) + wg.Done() }() go func() { truncateErr = f.TruncateHead(10) + wg.Done() }() go func() { @@ -242,9 +271,11 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) { if truncateErr != nil { t.Fatal("concurrent truncate failed:", err) } + if !(errors.Is(modifyErr, nil) || errors.Is(modifyErr, errOutOrderInsertion)) { t.Fatal("wrong error from concurrent modify:", modifyErr) } + checkAncientCount(t, f, "test", 10) } } @@ -258,21 +289,27 @@ func TestFreezerReadonlyValidate(t *testing.T) { if err != nil { t.Fatal("can't open freezer", err) } + var item = make([]byte, 1024) + aBatch := f.tables["a"].newBatch() require.NoError(t, aBatch.AppendRaw(0, item)) require.NoError(t, aBatch.AppendRaw(1, item)) require.NoError(t, aBatch.AppendRaw(2, item)) require.NoError(t, aBatch.commit()) + bBatch := f.tables["b"].newBatch() require.NoError(t, bBatch.AppendRaw(0, item)) require.NoError(t, bBatch.commit()) + if f.tables["a"].items.Load() != 3 { t.Fatalf("unexpected number of items in table") } + if f.tables["b"].items.Load() != 1 { t.Fatalf("unexpected number of items in table") } + require.NoError(t, f.Close()) // Re-openening as readonly should fail when validating @@ -293,6 +330,7 @@ func newFreezerForTesting(t *testing.T, tables map[string]bool) (*Freezer, strin if err != nil { t.Fatal("can't open freezer", err) } + return f, dir } @@ -310,6 +348,7 @@ func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) { if ok, _ := f.HasAncient(kind, index); !ok { t.Errorf("HasAncient(%q, %d) returned false unexpectedly", kind, index) } + if _, err := f.Ancient(kind, index); err != nil { t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err) } @@ -320,6 +359,7 @@ func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) { if ok, _ := f.HasAncient(kind, index); ok { t.Errorf("HasAncient(%q, %d) returned true unexpectedly", kind, index) } + if _, err := f.Ancient(kind, index); err == nil { t.Errorf("Ancient(%q, %d) didn't return expected error", kind, index) } else if err != errOutOfBounds { @@ -346,35 +386,45 @@ func TestRenameWindows(t *testing.T) { if err != nil { t.Fatal(err) } + f2, err := os.Create(path.Join(dir1, fname2)) if err != nil { t.Fatal(err) } + f3, err := os.Create(path.Join(dir2, fname2)) if err != nil { t.Fatal(err) } + if _, err := f.Write(data); err != nil { t.Fatal(err) } + if _, err := f2.Write(data2); err != nil { t.Fatal(err) } + if _, err := f3.Write(data3); err != nil { t.Fatal(err) } + if err := f.Close(); err != nil { t.Fatal(err) } + if err := f2.Close(); err != nil { t.Fatal(err) } + if err := f3.Close(); err != nil { t.Fatal(err) } + if err := os.Rename(f.Name(), path.Join(dir2, fname)); err != nil { t.Fatal(err) } + if err := os.Rename(f2.Name(), path.Join(dir2, fname2)); err != nil { t.Fatal(err) } @@ -384,12 +434,15 @@ func TestRenameWindows(t *testing.T) { if err != nil { t.Fatal(err) } + defer f.Close() defer os.Remove(f.Name()) + buf := make([]byte, dataLen) if _, err := f.Read(buf); err != nil { t.Fatal(err) } + if !bytes.Equal(buf, data) { t.Errorf("unexpected file contents. Got %v\n", buf) } @@ -398,11 +451,14 @@ func TestRenameWindows(t *testing.T) { if err != nil { t.Fatal(err) } + defer f.Close() defer os.Remove(f.Name()) + if _, err := f.Read(buf); err != nil { t.Fatal(err) } + if !bytes.Equal(buf, data2) { t.Errorf("unexpected file contents. Got %v\n", buf) } @@ -410,6 +466,7 @@ func TestRenameWindows(t *testing.T) { func TestFreezerCloseSync(t *testing.T) { t.Parallel() + f, _ := newFreezerForTesting(t, map[string]bool{"a": true, "b": true}) defer f.Close() @@ -423,6 +480,7 @@ func TestFreezerCloseSync(t *testing.T) { if err := f.Close(); err != nil { t.Fatal(err) } + if err := f.Sync(); err == nil { t.Fatalf("want error, have nil") } else if have, want := err.Error(), "[closed closed]"; have != want { diff --git a/core/rawdb/freezer_utils.go b/core/rawdb/freezer_utils.go index e7cce2920d..2bcdf0ec1f 100644 --- a/core/rawdb/freezer_utils.go +++ b/core/rawdb/freezer_utils.go @@ -33,6 +33,7 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e if err != nil { return err } + fname := f.Name() // Clean up the leftover file @@ -40,6 +41,7 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e if f != nil { f.Close() } + os.Remove(fname) }() // Apply the given function if it's not nil before we copy @@ -54,6 +56,7 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e if err != nil { return err } + if _, err = src.Seek(int64(offset), 0); err != nil { src.Close() return err @@ -72,11 +75,13 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e if err := f.Close(); err != nil { return err } + f = nil if err := os.Rename(fname, destPath); err != nil { return err } + return nil } @@ -93,6 +98,7 @@ func openFreezerFileForAppend(filename string) (*os.File, error) { if _, err = file.Seek(0, io.SeekEnd); err != nil { return nil, err } + return file, nil } @@ -115,5 +121,6 @@ func truncateFreezerFile(file *os.File, size int64) error { if _, err := file.Seek(0, io.SeekEnd); err != nil { return err } + return nil } diff --git a/core/rawdb/freezer_utils_test.go b/core/rawdb/freezer_utils_test.go index 829cbfb4f3..3408b4aabe 100644 --- a/core/rawdb/freezer_utils_test.go +++ b/core/rawdb/freezer_utils_test.go @@ -27,6 +27,7 @@ func TestCopyFrom(t *testing.T) { content = []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8} prefix = []byte{0x9, 0xa, 0xb, 0xc, 0xd, 0xf} ) + var cases = []struct { src, dest string offset uint64 @@ -42,6 +43,7 @@ func TestCopyFrom(t *testing.T) { {"foo", "bar", 1, true}, {"foo", "bar", 8, true}, } + for _, c := range cases { os.WriteFile(c.src, content, 0600) @@ -62,13 +64,16 @@ func TestCopyFrom(t *testing.T) { os.Remove(c.dest) t.Fatalf("Failed to read %v", err) } + want := content[c.offset:] if c.writePrefix { want = append(prefix, want...) } + if !bytes.Equal(blob, want) { t.Fatal("Unexpected value") } + os.Remove(c.src) os.Remove(c.dest) } diff --git a/core/rawdb/key_length_iterator_test.go b/core/rawdb/key_length_iterator_test.go index 654efc5b55..047bd20b9e 100644 --- a/core/rawdb/key_length_iterator_test.go +++ b/core/rawdb/key_length_iterator_test.go @@ -26,16 +26,20 @@ func TestKeyLengthIterator(t *testing.T) { keyLen := 8 expectedKeys := make(map[string]struct{}) + for i := 0; i < 100; i++ { key := make([]byte, keyLen) binary.BigEndian.PutUint64(key, uint64(i)) + if err := db.Put(key, []byte{0x1}); err != nil { t.Fatal(err) } + expectedKeys[string(key)] = struct{}{} longerKey := make([]byte, keyLen*2) binary.BigEndian.PutUint64(longerKey, uint64(i)) + if err := db.Put(longerKey, []byte{0x1}); err != nil { t.Fatal(err) } @@ -45,10 +49,13 @@ func TestKeyLengthIterator(t *testing.T) { for it.Next() { key := it.Key() _, exists := expectedKeys[string(key)] + if !exists { t.Fatalf("Found unexpected key %d", binary.BigEndian.Uint64(key)) } + delete(expectedKeys, string(key)) + if len(key) != keyLen { t.Fatalf("Found unexpected key in key length iterator with length %d", len(key)) } diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index fd5ab1ad4c..f95386a4c3 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -137,6 +137,7 @@ type LegacyTxLookupEntry struct { func encodeBlockNumber(number uint64) []byte { enc := make([]byte, 8) binary.BigEndian.PutUint64(enc, number) + return enc } @@ -226,6 +227,7 @@ func IsCodeKey(key []byte) (bool, []byte) { if bytes.HasPrefix(key, CodePrefix) && len(key) == common.HashLength+len(CodePrefix) { return true, key[len(CodePrefix):] } + return false, nil } diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 6d6fa0555d..581d1c9aa3 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -141,6 +141,7 @@ func (t *table) Delete(key []byte) error { func (t *table) NewIterator(prefix []byte, start []byte) ethdb.Iterator { innerPrefix := append([]byte(t.prefix), prefix...) iter := t.db.NewIterator(innerPrefix, start) + return &tableIterator{ iter: iter, prefix: t.prefix, @@ -290,6 +291,7 @@ func (iter *tableIterator) Key() []byte { if key == nil { return nil } + return key[len(iter.prefix):] } diff --git a/core/rawdb/table_test.go b/core/rawdb/table_test.go index aa6adf3e72..d33d66582e 100644 --- a/core/rawdb/table_test.go +++ b/core/rawdb/table_test.go @@ -61,11 +61,13 @@ func testTableDatabase(t *testing.T, prefix string) { for _, entry := range entries { db.Put(entry.key, entry.value) } + for _, entry := range entries { got, err := db.Get(entry.key) if err != nil { t.Fatalf("Failed to get value: %v", err) } + if !bytes.Equal(got, entry.value) { t.Fatalf("Value mismatch: want=%v, got=%v", entry.value, got) } @@ -73,16 +75,20 @@ func testTableDatabase(t *testing.T, prefix string) { // Test batch operation db = NewTable(NewMemoryDatabase(), prefix) + batch := db.NewBatch() for _, entry := range entries { batch.Put(entry.key, entry.value) } + batch.Write() + for _, entry := range entries { got, err := db.Get(entry.key) if err != nil { t.Fatalf("Failed to get value: %v", err) } + if !bytes.Equal(got, entry.value) { t.Fatalf("Value mismatch: want=%v, got=%v", entry.value, got) } @@ -91,6 +97,7 @@ func testTableDatabase(t *testing.T, prefix string) { // Test batch replayer r := &testReplayer{} batch.Replay(r) + for index, entry := range entries { got := r.puts[index] if !bytes.Equal(got, entry.key) { @@ -100,20 +107,25 @@ func testTableDatabase(t *testing.T, prefix string) { check := func(iter ethdb.Iterator, expCount, index int) { count := 0 + for iter.Next() { key, value := iter.Key(), iter.Value() if !bytes.Equal(key, entries[index].key) { t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key) } + if !bytes.Equal(value, entries[index].value) { t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value) } + index += 1 count++ } + if count != expCount { t.Fatalf("Wrong number of elems, exp %d got %d", expCount, count) } + iter.Release() } // Test iterators diff --git a/core/rlp_test.go b/core/rlp_test.go index a2fb4937f8..06a1dcbc98 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -54,12 +54,14 @@ func getBlock(transactions int, uncles int, dataSize int) *types.Block { big.NewInt(0), 50000, b.header.BaseFee, make([]byte, dataSize)), types.HomesteadSigner{}, key) b.AddTx(tx) } + for i := 0; i < uncles; i++ { b.AddUncle(&types.Header{ParentHash: b.PrevBlock(n - 1 - i).Hash(), Number: big.NewInt(int64(n - i))}) } } }) block := blocks[len(blocks)-1] + return block } @@ -84,6 +86,7 @@ func TestRlpIterator(t *testing.T) { func testRlpIterator(t *testing.T, txs, uncles, datasize int) { desc := fmt.Sprintf("%d txs [%d datasize] and %d uncles", txs, datasize, uncles) bodyRlp, _ := rlp.EncodeToBytes(getBlock(txs, uncles, datasize).Body()) + it, err := rlp.NewListIterator(bodyRlp) if err != nil { t.Fatal(err) @@ -92,6 +95,7 @@ func testRlpIterator(t *testing.T, txs, uncles, datasize int) { if !it.Next() { t.Fatal("expected two elems, got zero") } + txdata := it.Value() // Check that uncles exist if !it.Next() { @@ -101,24 +105,31 @@ func testRlpIterator(t *testing.T, txs, uncles, datasize int) { if it.Next() { t.Fatal("expected only two elems, got more") } + txIt, err := rlp.NewListIterator(txdata) if err != nil { t.Fatal(err) } + var gotHashes []common.Hash + var expHashes []common.Hash + for txIt.Next() { gotHashes = append(gotHashes, crypto.Keccak256Hash(txIt.Value())) } var expBody types.Body + err = rlp.DecodeBytes(bodyRlp, &expBody) if err != nil { t.Fatal(err) } + for _, tx := range expBody.Transactions { expHashes = append(expHashes, tx.Hash()) } + if gotLen, expLen := len(gotHashes), len(expHashes); gotLen != expLen { t.Fatalf("testcase %v: length wrong, got %d exp %d", desc, gotLen, expLen) } @@ -126,6 +137,7 @@ func testRlpIterator(t *testing.T, txs, uncles, datasize int) { if gotLen := len(gotHashes); gotLen != txs { t.Fatalf("testcase %v: length wrong, got %d exp %d", desc, gotLen, txs) } + for i, got := range gotHashes { if exp := expHashes[i]; got != exp { t.Errorf("testcase %v: hash wrong, got %x, exp %x", desc, got, exp) @@ -146,22 +158,30 @@ func BenchmarkHashing(b *testing.B) { bodyRlp, _ = rlp.EncodeToBytes(block.Body()) blockRlp, _ = rlp.EncodeToBytes(block) } + var got common.Hash + var hasher = sha3.NewLegacyKeccak256() + b.Run("iteratorhashing", func(b *testing.B) { b.ResetTimer() + for i := 0; i < b.N; i++ { var hash common.Hash + it, err := rlp.NewListIterator(bodyRlp) if err != nil { b.Fatal(err) } + it.Next() txs := it.Value() + txIt, err := rlp.NewListIterator(txs) if err != nil { b.Fatal(err) } + for txIt.Next() { hasher.Reset() hasher.Write(txIt.Value()) @@ -170,12 +190,17 @@ func BenchmarkHashing(b *testing.B) { } } }) + var exp common.Hash + b.Run("fullbodyhashing", func(b *testing.B) { b.ResetTimer() + for i := 0; i < b.N; i++ { var body types.Body + rlp.DecodeBytes(bodyRlp, &body) + for _, tx := range body.Transactions { exp = tx.Hash() } @@ -183,14 +208,18 @@ func BenchmarkHashing(b *testing.B) { }) b.Run("fullblockhashing", func(b *testing.B) { b.ResetTimer() + for i := 0; i < b.N; i++ { var block types.Block + rlp.DecodeBytes(blockRlp, &block) + for _, tx := range block.Transactions() { tx.Hash() } } }) + if got != exp { b.Fatalf("hash wrong, got %x exp %x", got, exp) } diff --git a/core/sender_cacher.go b/core/sender_cacher.go index 8909db1598..273bfec8fb 100644 --- a/core/sender_cacher.go +++ b/core/sender_cacher.go @@ -54,6 +54,7 @@ func newTxSenderCacher(threads int) *txSenderCacher { for i := 0; i < threads; i++ { go cacher.cache() } + return cacher } @@ -80,6 +81,7 @@ func (cacher *txSenderCacher) Recover(signer types.Signer, txs []*types.Transact if len(txs) < tasks*4 { tasks = (len(txs) + 3) / 4 } + for i := 0; i < tasks; i++ { cacher.tasks <- &txSenderCacherRequest{ signer: signer, @@ -97,6 +99,7 @@ func (cacher *txSenderCacher) RecoverFromBlocks(signer types.Signer, blocks []*t for _, block := range blocks { count += len(block.Transactions()) } + txs := make([]*types.Transaction, 0, count) for _, block := range blocks { txs = append(txs, block.Transactions()...) diff --git a/core/state/access_list.go b/core/state/access_list.go index 4194691345..f946056706 100644 --- a/core/state/access_list.go +++ b/core/state/access_list.go @@ -39,11 +39,14 @@ func (al *accessList) Contains(address common.Address, slot common.Hash) (addres // no such address (and hence zero slots) return false, false } + if idx == -1 { // address yes, but no slots return true, false } + _, slotPresent = al.slots[idx][slot] + return true, slotPresent } @@ -60,14 +63,18 @@ func (a *accessList) Copy() *accessList { for k, v := range a.addresses { cp.addresses[k] = v } + cp.slots = make([]map[common.Hash]struct{}, len(a.slots)) + for i, slotMap := range a.slots { newSlotmap := make(map[common.Hash]struct{}, len(slotMap)) for k := range slotMap { newSlotmap[k] = struct{}{} } + cp.slots[i] = newSlotmap } + return cp } @@ -77,7 +84,9 @@ func (al *accessList) AddAddress(address common.Address) bool { if _, present := al.addresses[address]; present { return false } + al.addresses[address] = -1 + return true } @@ -93,6 +102,7 @@ func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrCha al.addresses[address] = len(al.slots) slotmap := map[common.Hash]struct{}{slot: {}} al.slots = append(al.slots, slotmap) + return !addrPresent, true } // There is already an (address,slot) mapping @@ -116,6 +126,7 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { if !addrOk { panic("reverting slot change, address not present in list") } + slotmap := al.slots[idx] delete(slotmap, slot) // If that was the last (first) slot, remove it diff --git a/core/state/database.go b/core/state/database.go index 3e9cb77690..1c57ca9e4f 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -167,6 +167,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { if err != nil { return nil, err } + return tr, nil } @@ -176,6 +177,7 @@ func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, addrHash, root commo if err != nil { return nil, err } + return tr, nil } @@ -201,8 +203,10 @@ func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error if len(code) > 0 { db.codeCache.Add(codeHash, code) db.codeSizeCache.Add(codeHash, len(code)) + return code, nil } + return nil, errors.New("not found") } @@ -220,8 +224,10 @@ func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]b if len(code) > 0 { db.codeCache.Add(codeHash, code) db.codeSizeCache.Add(codeHash, len(code)) + return code, nil } + return nil, errors.New("not found") } @@ -230,7 +236,9 @@ func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, erro if cached, ok := db.codeSizeCache.Get(codeHash); ok { return cached, nil } + code, err := db.ContractCode(addrHash, codeHash) + return len(code), err } diff --git a/core/state/dump.go b/core/state/dump.go index 7e7af0c9f8..8f8c3b4f55 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -113,6 +113,7 @@ func (d iterativeDump) OnAccount(addr common.Address, account DumpAccount) { if addr != (common.Address{}) { dumpAccount.Address = &addr } + d.Encode(dumpAccount) } @@ -130,12 +131,14 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] if conf == nil { conf = new(DumpConfig) } + var ( missingPreimages int accounts uint64 start = time.Now() logged = time.Now() ) + log.Info("Trie dumping started", "root", s.trie.Hash()) c.OnRoot(s.trie.Hash()) @@ -145,6 +148,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] if err := rlp.DecodeBytes(it.Value, &data); err != nil { panic(err) } + account := DumpAccount{ Balance: data.Balance.String(), Nonce: data.Nonce, @@ -152,20 +156,26 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] CodeHash: data.CodeHash, SecureKey: it.Key, } + addrBytes := s.trie.GetKey(it.Key) if addrBytes == nil { // Preimage missing missingPreimages++ + if conf.OnlyWithAddresses { continue } + account.SecureKey = it.Key } + addr := common.BytesToAddress(addrBytes) + obj := newObject(s, addr, data) if !conf.SkipCode { account.Code = obj.Code(s.db) } + if !conf.SkipStorage { account.Storage = make(map[common.Hash]string) tr, err := obj.getTrie(s.db) @@ -182,26 +192,34 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] log.Error("Failed to decode the value returned by iterator", "error", err) continue } + account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content) } } + c.OnAccount(addr, account) + accounts++ if time.Since(logged) > 8*time.Second { log.Info("Trie dumping in progress", "at", it.Key, "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() } + if conf.Max > 0 && accounts >= conf.Max { if it.Next() { nextKey = it.Key } + break } } + if missingPreimages > 0 { log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages) } + log.Info("Trie dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start))) @@ -214,16 +232,19 @@ func (s *StateDB) RawDump(opts *DumpConfig) Dump { Accounts: make(map[common.Address]DumpAccount), } s.DumpToCollector(dump, opts) + return *dump } // Dump returns a JSON string representing the entire state as a single json-object func (s *StateDB) Dump(opts *DumpConfig) []byte { dump := s.RawDump(opts) + json, err := json.MarshalIndent(dump, "", " ") if err != nil { fmt.Println("Dump err", err) } + return json } @@ -238,5 +259,6 @@ func (s *StateDB) IteratorDump(opts *DumpConfig) IteratorDump { Accounts: make(map[common.Address]DumpAccount), } iterator.Next = s.DumpToCollector(iterator, opts) + return *iterator } diff --git a/core/state/iterator.go b/core/state/iterator.go index fefad890c7..c760649a6e 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -64,6 +64,7 @@ func (it *NodeIterator) Next() bool { it.Error = err return false } + return it.retrieve() } @@ -83,8 +84,10 @@ func (it *NodeIterator) step() error { if it.dataIt.Error() != nil { return it.dataIt.Error() } + it.dataIt = nil } + return nil } // If we had source code previously, discard that @@ -97,7 +100,9 @@ func (it *NodeIterator) step() error { if it.stateIt.Error() != nil { return it.stateIt.Error() } + it.state, it.stateIt = nil, nil + return nil } // If the state trie node is an internal entry, leave as is @@ -115,6 +120,7 @@ func (it *NodeIterator) step() error { if err != nil { return err } + it.dataIt = dataTrie.NodeIterator(nil) if !it.dataIt.Next(true) { it.dataIt = nil @@ -123,12 +129,15 @@ func (it *NodeIterator) step() error { if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { it.codeHash = common.BytesToHash(account.CodeHash) addrHash := common.BytesToHash(it.stateIt.LeafKey()) + it.code, err = it.state.db.ContractCode(addrHash, common.BytesToHash(account.CodeHash)) if err != nil { return fmt.Errorf("code %x: %v", account.CodeHash, err) } } + it.accountHash = it.stateIt.Parent() + return nil } @@ -154,5 +163,6 @@ func (it *NodeIterator) retrieve() bool { case it.stateIt != nil: it.Hash, it.Parent = it.stateIt.Hash(), it.stateIt.Parent() } + return true } diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go index 7465073ad8..114d5bf664 100644 --- a/core/state/iterator_test.go +++ b/core/state/iterator_test.go @@ -35,6 +35,7 @@ func TestNodeIteratorCoverage(t *testing.T) { } // Gather all the node hashes found by the iterator hashes := make(map[common.Hash]struct{}) + for it := NewNodeIterator(state); it.Next(); { if it.Hash != (common.Hash{}) { hashes[it.Hash] = struct{}{} diff --git a/core/state/journal.go b/core/state/journal.go index e6fe58ac8b..7d5648153c 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -70,6 +70,7 @@ func (j *journal) revert(statedb *StateDB, snapshot int) { } } } + j.entries = j.entries[:snapshot] } @@ -248,6 +249,7 @@ func (ch addLogChange) revert(s *StateDB) { } else { s.logs[ch.txhash] = logs[:len(logs)-1] } + s.logSize-- } diff --git a/core/state/pruner/bloom.go b/core/state/pruner/bloom.go index 72315db720..d4b657b0a8 100644 --- a/core/state/pruner/bloom.go +++ b/core/state/pruner/bloom.go @@ -67,7 +67,9 @@ func newStateBloomWithSize(size uint64) (*stateBloom, error) { if err != nil { return nil, err } + log.Info("Initialized state bloom", "size", common.StorageSize(float64(bloom.M()/8))) + return &stateBloom{bloom: bloom}, nil } @@ -78,6 +80,7 @@ func NewStateBloomFromDisk(filename string) (*stateBloom, error) { if err != nil { return nil, err } + return &stateBloom{bloom: bloom}, nil } @@ -94,10 +97,12 @@ func (bloom *stateBloom) Commit(filename, tempname string) error { if err != nil { return err } + if err := f.Sync(); err != nil { f.Close() return err } + f.Close() // Move the temporary file into it's final location @@ -113,10 +118,14 @@ func (bloom *stateBloom) Put(key []byte, value []byte) error { if !isCode { return errors.New("invalid entry") } + bloom.bloom.Add(stateBloomHasher(codeKey)) + return nil } + bloom.bloom.Add(stateBloomHasher(key)) + return nil } diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index f654bea3f8..6a3602f8c3 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -93,6 +93,7 @@ func NewPruner(db ethdb.Database, config Config) (*Pruner, error) { NoBuild: true, AsyncBuild: false, } + snaptree, err := snapshot.New(snapconfig, db, trie.NewDatabase(db), headBlock.Root()) if err != nil { return nil, err // The relevant snapshot(s) might not exist @@ -107,6 +108,7 @@ func NewPruner(db ethdb.Database, config Config) (*Pruner, error) { if err != nil { return nil, err } + return &Pruner{ config: config, chainHeader: headBlock.Header(), @@ -132,6 +134,7 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta batch = maindb.NewBatch() iter = maindb.NewIterator(nil, nil) ) + for iter.Next() { key := iter.Key() @@ -145,6 +148,7 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta if isCode { checkKey = codeKey } + if _, exist := middleStateRoots[common.BytesToHash(checkKey)]; exist { log.Debug("Forcibly delete the middle state roots", "hash", common.BytesToHash(checkKey)) } else { @@ -154,21 +158,26 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta 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(pstart)/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 @@ -182,10 +191,12 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta } } } + 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))) @@ -212,15 +223,19 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta // Note for small pruning, the compaction is skipped. if count >= rangeCompactionThreshold { cstart := time.Now() + for b := 0x00; b <= 0xf0; b += 0x10 { var ( start = []byte{byte(b)} end = []byte{byte(b + 0x10)} ) + 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 @@ -228,7 +243,9 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta } 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 } @@ -245,6 +262,7 @@ func (p *Pruner) Prune(root common.Hash) error { if err != nil { return err } + if stateBloomRoot != (common.Hash{}) { return RecoverPruning(p.config.Datadir, p.db, p.config.Cachedir) } @@ -283,18 +301,23 @@ func (p *Pruner) Prune(root common.Hash) error { // 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 rawdb.HasLegacyTrieNode(p.db, layers[i].Root()) { 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 { @@ -313,15 +336,18 @@ func (p *Pruner) Prune(root common.Hash) error { // 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 } @@ -334,10 +360,13 @@ func (p *Pruner) Prune(root common.Hash) error { filterName := bloomFilterName(p.config.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) + return prune(p.snaptree, root, p.db, p.stateBloom, filterName, middleRoots, start) } @@ -353,9 +382,11 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err if err != nil { return err } + if stateBloomPath == "" { return nil // nothing to recover } + headBlock := rawdb.ReadHeadBlock(db) if headBlock == nil { return errors.New("failed to load head block") @@ -374,14 +405,17 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err NoBuild: true, AsyncBuild: false, } + snaptree, err := snapshot.New(snapconfig, db, trie.NewDatabase(db), headBlock.Root()) 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. @@ -397,17 +431,21 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err layers = snaptree.Snapshots(headBlock.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") } + return prune(snaptree, stateBloomRoot, db, stateBloom, stateBloomPath, middleRoots, time.Now()) } @@ -418,6 +456,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { 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") @@ -427,6 +466,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { if err != nil { return err } + accIter := t.NodeIterator(nil) for accIter.Next(true) { hash := accIter.Hash() @@ -445,10 +485,12 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { if acc.Root != types.EmptyRootHash { id := trie.StorageTrieID(genesis.Root(), common.BytesToHash(accIter.LeafKey()), acc.Root) + storageTrie, err := trie.NewStateTrie(id, trie.NewDatabase(db)) if err != nil { return err } + storageIter := storageTrie.NodeIterator(nil) for storageIter.Next(true) { hash := storageIter.Hash() @@ -456,6 +498,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { stateBloom.Put(hash.Bytes(), nil) } } + if storageIter.Error() != nil { return storageIter.Error() } @@ -466,6 +509,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { } } } + return accIter.Error() } @@ -478,6 +522,7 @@ func isBloomFilter(filename string) (bool, common.Hash) { 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{} } @@ -486,6 +531,7 @@ func findBloomFilter(datadir string) (string, common.Hash, error) { 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) @@ -498,6 +544,7 @@ func findBloomFilter(datadir string) (string, common.Hash, error) { }); err != nil { return "", common.Hash{}, err } + return stateBloomPath, stateBloomRoot, nil } @@ -517,6 +564,7 @@ func deleteCleanTrieCache(path string) { log.Warn(warningLog) return } + os.RemoveAll(path) log.Info("Deleted trie clean cache", "path", path) } diff --git a/core/state/snapshot/account.go b/core/state/snapshot/account.go index 713b83a51f..5a05f2eaf5 100644 --- a/core/state/snapshot/account.go +++ b/core/state/snapshot/account.go @@ -49,6 +49,7 @@ func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []by if !bytes.Equal(codehash, types.EmptyCodeHash[:]) { slim.CodeHash = codehash } + return slim } @@ -59,6 +60,7 @@ func SlimAccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash [ if err != nil { panic(err) } + return data } @@ -69,12 +71,15 @@ func FullAccount(data []byte) (Account, error) { if err := rlp.DecodeBytes(data, &account); err != nil { return Account{}, err } + if len(account.Root) == 0 { account.Root = types.EmptyRootHash[:] } + if len(account.CodeHash) == 0 { account.CodeHash = types.EmptyCodeHash[:] } + return account, nil } @@ -84,5 +89,6 @@ func FullAccountRLP(data []byte) ([]byte, error) { if err != nil { return nil, err } + return rlp.EncodeToBytes(account) } diff --git a/core/state/snapshot/context.go b/core/state/snapshot/context.go index 8693c82413..1de39dcd23 100644 --- a/core/state/snapshot/context.go +++ b/core/state/snapshot/context.go @@ -188,7 +188,9 @@ func (ctx *generatorContext) removeStorageBefore(account common.Hash) { iter.Hold() break } + count++ + ctx.batch.Delete(key) if ctx.batch.ValueSize() > ethdb.IdealBatchSize { @@ -225,7 +227,9 @@ func (ctx *generatorContext) removeStorageAt(account common.Hash) error { iter.Hold() break } + count++ + ctx.batch.Delete(key) if ctx.batch.ValueSize() > ethdb.IdealBatchSize { @@ -250,6 +254,7 @@ func (ctx *generatorContext) removeStorageLeft() { for iter.Next() { count++ + ctx.batch.Delete(iter.Key()) if ctx.batch.ValueSize() > ethdb.IdealBatchSize { diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 91bcb176c0..39d4c1b7a2 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -80,6 +80,7 @@ func GenerateTrie(snaptree *Tree, root common.Hash, src ethdb.Database, dst ethd 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. @@ -93,15 +94,18 @@ func GenerateTrie(snaptree *Tree, root common.Hash, src ethdb.Database, dst ethd 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 } @@ -153,6 +157,7 @@ func (stat *generateStats) progressContract(account common.Hash, slot common.Has stat.slots += done stat.slotsHead[account] = slot + if _, ok := stat.slotsStart[account]; !ok { stat.slotsStart[account] = time.Now() } @@ -178,6 +183,7 @@ func (stat *generateStats) report() { "slots", stat.slots, "elapsed", common.PrettyDuration(time.Since(stat.start)), } + 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 { @@ -189,6 +195,7 @@ func (stat *generateStats) report() { // 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]) @@ -200,11 +207,13 @@ func (stat *generateStats) report() { } } } + ctx = append(ctx, []interface{}{ "eta", common.PrettyDuration(eta), }...) } } + log.Info("Iterating state snapshot", ctx...) } @@ -214,10 +223,12 @@ func (stat *generateStats) reportDone() { defer stat.lock.RUnlock() var ctx []interface{} + ctx = append(ctx, []interface{}{"accounts", stat.accounts}...) if stat.slots != 0 { ctx = append(ctx, []interface{}{"slots", stat.slots}...) } + ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) log.Info("Iterated snapshot", ctx...) } @@ -236,6 +247,7 @@ func runReport(stats *generateStats, stop chan bool) { if success { stats.reportDone() } + return } } @@ -254,6 +266,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou ) // Spin up a go-routine for trie hash re-generation wg.Add(1) + go func() { defer wg.Done() generatorFn(db, scheme, account, in, out) @@ -261,6 +274,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou // Spin up a go-routine for progress logging if report && stats != nil { wg.Add(1) + go func() { defer wg.Done() runReport(stats, stoplog) @@ -271,6 +285,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou // processing and gathering results. threads := runtime.NumCPU() results := make(chan error, threads) + for i := 0; i < threads; i++ { results <- nil // fill the semaphore } @@ -278,7 +293,9 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou // and return the re-generated trie hash. stop := func(fail error) (common.Hash, error) { close(in) + result := <-out + for i := 0; i < threads; i++ { if err := <-results; err != nil && fail == nil { fail = err @@ -287,8 +304,10 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou stoplog <- fail == nil wg.Wait() + return result, fail } + var ( logged = time.Now() processed = uint64(0) @@ -301,6 +320,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou err error fullData []byte ) + if leafCallback == nil { fullData, err = FullAccountRLP(it.(AccountIterator).Account()) if err != nil { @@ -318,23 +338,27 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou if err != nil { 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(path %x), want %x, have %x", hash, account.Root, subroot) return } results <- nil }(it.Hash()) + fullData, err = rlp.EncodeToBytes(account) if err != nil { return stop(err) } } + leaf = trieKV{it.Hash(), fullData} } else { leaf = trieKV{it.Hash(), common.CopyBytes(it.(StorageIterator).Slot())} @@ -343,12 +367,14 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou // Accumulate the generation statistic if it's required. processed++ + if time.Since(logged) > 3*time.Second && stats != nil { if account == (common.Hash{}) { stats.progressAccounts(it.Hash(), processed) } else { stats.progressContract(account, it.Hash(), processed) } + logged, processed = time.Now(), 0 } } @@ -360,6 +386,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou stats.finishContract(account, processed) } } + return stop(nil) } @@ -375,6 +402,7 @@ func stackTrieGenerate(db ethdb.KeyValueWriter, scheme string, owner common.Hash for leaf := range in { t.Update(leaf.key[:], leaf.value) } + var root common.Hash if db == nil { root = t.Hash() diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 4701acccd3..398c61f844 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -195,6 +195,7 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s 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)) @@ -205,7 +206,9 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s snapshotDirtyStorageWriteMeter.Mark(int64(len(data))) } } + dl.memory += uint64(len(destructs) * common.HashLength) + return dl } @@ -234,9 +237,11 @@ func (dl *diffLayer) rebloom(origin *diskLayer) { for hash := range dl.destructSet { dl.diffed.Add(destructBloomHasher(hash)) } + for hash := range dl.accountData { dl.diffed.Add(accountBloomHasher(hash)) } + for accountHash, slots := range dl.storageData { for storageHash := range slots { dl.diffed.Add(storageBloomHasher{accountHash, storageHash}) @@ -277,13 +282,16 @@ func (dl *diffLayer) Account(hash common.Hash) (*Account, error) { if err != nil { return nil, err } + if len(data) == 0 { // can be both nil and []byte{} return nil, nil } + account := new(Account) if err := rlp.DecodeBytes(data, account); err != nil { panic(err) } + return account, nil } @@ -295,10 +303,12 @@ func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { // Check the bloom filter first whether there's even a point in reaching into // all the maps in all the layers below dl.lock.RLock() + hit := dl.diffed.Contains(accountBloomHasher(hash)) if !hit { hit = dl.diffed.Contains(destructBloomHasher(hash)) } + var origin *diskLayer if !hit { origin = dl.origin // extract origin while holding the lock @@ -333,6 +343,7 @@ func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) { snapshotDirtyAccountHitDepthHist.Update(int64(depth)) snapshotDirtyAccountReadMeter.Mark(int64(len(data))) snapshotBloomAccountTrueHitMeter.Mark(1) + return data, nil } // If the account is known locally, but deleted, return it @@ -341,6 +352,7 @@ func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) { snapshotDirtyAccountHitDepthHist.Update(int64(depth)) snapshotDirtyAccountInexMeter.Mark(1) snapshotBloomAccountTrueHitMeter.Mark(1) + return nil, nil } // Account unknown to this diff, resolve from parent @@ -349,6 +361,7 @@ func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) { } // Failed to resolve through diff layers, mark a bloom error and use the disk snapshotBloomAccountFalseHitMeter.Mark(1) + return dl.parent.AccountRLP(hash) } @@ -361,10 +374,12 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro // Check the bloom filter first whether there's even a point in reaching into // all the maps in all the layers below dl.lock.RLock() + hit := dl.diffed.Contains(storageBloomHasher{accountHash, storageHash}) if !hit { hit = dl.diffed.Contains(destructBloomHasher(accountHash)) } + var origin *diskLayer if !hit { origin = dl.origin // extract origin while holding the lock @@ -398,12 +413,15 @@ func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([ if data, ok := storage[storageHash]; ok { snapshotDirtyStorageHitMeter.Mark(1) snapshotDirtyStorageHitDepthHist.Update(int64(depth)) + if n := len(data); n > 0 { snapshotDirtyStorageReadMeter.Mark(int64(n)) } else { snapshotDirtyStorageInexMeter.Mark(1) } + snapshotBloomStorageTrueHitMeter.Mark(1) + return data, nil } } @@ -413,6 +431,7 @@ func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([ snapshotDirtyStorageHitDepthHist.Update(int64(depth)) snapshotDirtyStorageInexMeter.Mark(1) snapshotBloomStorageTrueHitMeter.Mark(1) + return nil, nil } // Storage slot unknown to this diff, resolve from parent @@ -421,6 +440,7 @@ func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([ } // Failed to resolve through diff layers, mark a bloom error and use the disk snapshotBloomStorageFalseHitMeter.Mark(1) + return dl.parent.Storage(accountHash, storageHash) } @@ -458,6 +478,7 @@ func (dl *diffLayer) flatten() snapshot { delete(parent.accountData, hash) delete(parent.storageData, hash) } + for hash, data := range dl.accountData { parent.accountData[hash] = data } @@ -509,13 +530,16 @@ func (dl *diffLayer) AccountList() []common.Hash { for hash := range dl.accountData { dl.accountList = append(dl.accountList, hash) } + for hash := range dl.destructSet { if _, ok := dl.accountData[hash]; !ok { dl.accountList = append(dl.accountList, hash) } } + sort.Sort(hashes(dl.accountList)) dl.memory += uint64(len(dl.accountList) * common.HashLength) + return dl.accountList } @@ -531,6 +555,7 @@ func (dl *diffLayer) AccountList() []common.Hash { func (dl *diffLayer) StorageList(accountHash common.Hash) ([]common.Hash, bool) { dl.lock.RLock() _, destructed := dl.destructSet[accountHash] + if _, ok := dl.storageData[accountHash]; !ok { // Account not tracked by this layer dl.lock.RUnlock() @@ -549,11 +574,14 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) ([]common.Hash, bool) storageMap := dl.storageData[accountHash] storageList := make([]common.Hash, 0, len(storageMap)) + for k := range storageMap { storageList = append(storageList, k) } + sort.Sort(hashes(storageList)) dl.storageList[accountHash] = storageList dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength) + return storageList, destructed } diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 674a031b16..692cb8a68a 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -33,6 +33,7 @@ func copyDestructs(destructs map[common.Hash]struct{}) map[common.Hash]struct{} for hash := range destructs { copy[hash] = struct{}{} } + return copy } @@ -41,6 +42,7 @@ func copyAccounts(accounts map[common.Hash][]byte) map[common.Hash][]byte { for hash, blob := range accounts { copy[hash] = blob } + return copy } @@ -52,6 +54,7 @@ func copyStorage(storage map[common.Hash]map[common.Hash][]byte) map[common.Hash copy[accHash][slotHash] = blob } } + return copy } @@ -68,9 +71,11 @@ func TestMergeBasics(t *testing.T) { data := randomAccount() accounts[h] = data + if rand.Intn(4) == 0 { destructs[h] = struct{}{} } + if rand.Intn(2) == 0 { accStorage := make(map[common.Hash][]byte) value := make([]byte, 32) @@ -92,9 +97,11 @@ func TestMergeBasics(t *testing.T) { if have, want := len(merged.accountList), 0; have != want { t.Errorf("accountList wrong: have %v, want %v", have, want) } + if have, want := len(merged.AccountList()), len(accounts); have != want { t.Errorf("AccountList() wrong: have %v, want %v", have, want) } + if have, want := len(merged.accountList), len(accounts); have != want { t.Errorf("accountList [2] wrong: have %v, want %v", have, want) } @@ -110,13 +117,16 @@ func TestMergeBasics(t *testing.T) { if have, want := len(merged.storageList), i; have != want { t.Errorf("[1] storageList wrong: have %v, want %v", have, want) } + list, _ := merged.StorageList(aHash) if have, want := len(list), len(sMap); have != want { t.Errorf("[2] StorageList() wrong: have %v, want %v", have, want) } + if have, want := len(merged.storageList[aHash]), len(sMap); have != want { t.Errorf("storageList wrong: have %v, want %v", have, want) } + i++ } } @@ -163,12 +173,15 @@ func TestMergeDelete(t *testing.T) { if data, _ := child.Account(h1); data == nil { t.Errorf("last diff layer: expected %x account to be non-nil", h1) } + if data, _ := child.Account(h2); data != nil { t.Errorf("last diff layer: expected %x account to be nil", h2) } + if _, ok := child.destructSet[h1]; ok { t.Errorf("last diff layer: expected %x drop to be missing", h1) } + if _, ok := child.destructSet[h2]; !ok { t.Errorf("last diff layer: expected %x drop to be present", h1) } @@ -178,12 +191,15 @@ func TestMergeDelete(t *testing.T) { if data, _ := merged.Account(h1); data == nil { t.Errorf("merged layer: expected %x account to be non-nil", h1) } + if data, _ := merged.Account(h2); data != nil { t.Errorf("merged layer: expected %x account to be nil", h2) } + if _, ok := merged.destructSet[h1]; !ok { // Note, drops stay alive until persisted to disk! t.Errorf("merged diff layer: expected %x drop to be present", h1) } + if _, ok := merged.destructSet[h2]; !ok { // Note, drops stay alive until persisted to disk! t.Errorf("merged diff layer: expected %x drop to be present", h1) } @@ -210,6 +226,7 @@ func TestInsertAndMerge(t *testing.T) { accounts = make(map[common.Hash][]byte) storage = make(map[common.Hash]map[common.Hash][]byte) ) + parent = newDiffLayer(emptyLayer(), common.Hash{}, destructs, accounts, storage) } { @@ -218,6 +235,7 @@ func TestInsertAndMerge(t *testing.T) { accounts = make(map[common.Hash][]byte) storage = make(map[common.Hash]map[common.Hash][]byte) ) + accounts[acc] = randomAccount() storage[acc] = make(map[common.Hash][]byte) storage[acc][slot] = []byte{0x01} @@ -254,18 +272,25 @@ func BenchmarkSearch(b *testing.B) { accounts = make(map[common.Hash][]byte) storage = make(map[common.Hash]map[common.Hash][]byte) ) + for i := 0; i < 10000; i++ { accounts[randomHash()] = randomAccount() } + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) } + var layer snapshot + layer = emptyLayer() for i := 0; i < 128; i++ { layer = fill(layer) } + key := crypto.Keccak256Hash([]byte{0x13, 0x38}) + b.ResetTimer() + for i := 0; i < b.N; i++ { layer.AccountRLP(key) } @@ -290,23 +315,29 @@ func BenchmarkSearchSlot(b *testing.B) { accounts = make(map[common.Hash][]byte) storage = make(map[common.Hash]map[common.Hash][]byte) ) + accounts[accountKey] = accountRLP accStorage := make(map[common.Hash][]byte) + for i := 0; i < 5; i++ { value := make([]byte, 32) crand.Read(value) accStorage[randomHash()] = value storage[accountKey] = accStorage } + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) } + var layer snapshot + layer = emptyLayer() for i := 0; i < 128; i++ { layer = fill(layer) } b.ResetTimer() + for i := 0; i < b.N; i++ { layer.Storage(accountKey, storageKey) } @@ -324,24 +355,32 @@ func BenchmarkFlatten(b *testing.B) { accounts = make(map[common.Hash][]byte) storage = make(map[common.Hash]map[common.Hash][]byte) ) + for i := 0; i < 100; i++ { accountKey := randomHash() accounts[accountKey] = randomAccount() accStorage := make(map[common.Hash][]byte) + for i := 0; i < 20; i++ { value := make([]byte, 32) crand.Read(value) accStorage[randomHash()] = value } + storage[accountKey] = accStorage } + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) } + b.ResetTimer() + for i := 0; i < b.N; i++ { b.StopTimer() + var layer snapshot + layer = emptyLayer() for i := 1; i < 128; i++ { layer = fill(layer) @@ -353,6 +392,7 @@ func BenchmarkFlatten(b *testing.B) { if !ok { break } + layer = dl.flatten() } b.StopTimer() @@ -373,21 +413,26 @@ func BenchmarkJournal(b *testing.B) { accounts = make(map[common.Hash][]byte) storage = make(map[common.Hash]map[common.Hash][]byte) ) + for i := 0; i < 200; i++ { accountKey := randomHash() accounts[accountKey] = randomAccount() accStorage := make(map[common.Hash][]byte) + for i := 0; i < 200; i++ { value := make([]byte, 32) crand.Read(value) accStorage[randomHash()] = value } + storage[accountKey] = accStorage } + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) } layer := snapshot(emptyLayer()) + for i := 1; i < 128; i++ { layer = fill(layer) } diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 7cbf6e293d..f585a74f88 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -70,13 +70,16 @@ func (dl *diskLayer) Account(hash common.Hash) (*Account, error) { if err != nil { return nil, err } + if len(data) == 0 { // can be both nil and []byte{} return nil, nil } + account := new(Account) if err := rlp.DecodeBytes(data, account); err != nil { panic(err) } + return account, nil } @@ -103,6 +106,7 @@ func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) { if blob, found := dl.cache.HasGet(nil, hash[:]); found { snapshotCleanAccountHitMeter.Mark(1) snapshotCleanAccountReadMeter.Mark(int64(len(blob))) + return blob, nil } // Cache doesn't contain account, pull from disk and cache for later @@ -110,11 +114,13 @@ func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) { dl.cache.Set(hash[:], blob) snapshotCleanAccountMissMeter.Mark(1) + if n := len(blob); n > 0 { snapshotCleanAccountWriteMeter.Mark(int64(n)) } else { snapshotCleanAccountInexMeter.Mark(1) } + return blob, nil } @@ -129,6 +135,7 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro if dl.stale { return nil, ErrSnapshotStale } + key := append(accountHash[:], storageHash[:]...) // If the layer is being generated, ensure the requested hash has already been @@ -143,6 +150,7 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro if blob, found := dl.cache.HasGet(nil, key); found { snapshotCleanStorageHitMeter.Mark(1) snapshotCleanStorageReadMeter.Mark(int64(len(blob))) + return blob, nil } // Cache doesn't contain storage slot, pull from disk and cache for later @@ -150,11 +158,13 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro dl.cache.Set(key, blob) snapshotCleanStorageMissMeter.Mark(1) + if n := len(blob); n > 0 { snapshotCleanStorageWriteMeter.Mark(int64(n)) } else { snapshotCleanStorageInexMeter.Mark(1) } + return blob, nil } diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index f95b798515..29e7e37a85 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -34,6 +34,7 @@ func reverse(blob []byte) []byte { for i, b := range blob { res[len(blob)-1-i] = b } + return res } @@ -133,6 +134,7 @@ func TestDiskMerge(t *testing.T) { }); err != nil { t.Fatalf("failed to update snapshot tree: %v", err) } + if err := snaps.Cap(diffRoot, 0); err != nil { t.Fatalf("failed to flatten snapshot tree: %v", err) } @@ -145,6 +147,7 @@ func TestDiskMerge(t *testing.T) { // assertAccount ensures that an account matches the given blob. assertAccount := func(account common.Hash, data []byte) { t.Helper() + blob, err := base.AccountRLP(account) if err != nil { t.Errorf("account access (%x) failed: %v", account, err) @@ -162,6 +165,7 @@ func TestDiskMerge(t *testing.T) { // assertStorage ensures that a storage slot matches the given blob. assertStorage := func(account common.Hash, slot common.Hash, data []byte) { t.Helper() + blob, err := base.Storage(account, slot) if err != nil { t.Errorf("storage access (%x:%x) failed: %v", account, slot, err) @@ -183,6 +187,7 @@ func TestDiskMerge(t *testing.T) { // assertDatabaseAccount ensures that an account from the database matches the given blob. assertDatabaseAccount := func(account common.Hash, data []byte) { t.Helper() + if blob := rawdb.ReadAccountSnapshot(db, account); !bytes.Equal(blob, data) { t.Errorf("account database access (%x) mismatch: have %x, want %x", account, blob, data) } @@ -197,6 +202,7 @@ func TestDiskMerge(t *testing.T) { // assertDatabaseStorage ensures that a storage slot from the database matches the given blob. assertDatabaseStorage := func(account common.Hash, slot common.Hash, data []byte) { t.Helper() + if blob := rawdb.ReadStorageSnapshot(db, account, slot); !bytes.Equal(blob, data) { t.Errorf("storage database access (%x:%x) mismatch: have %x, want %x", account, slot, blob, data) } @@ -272,6 +278,7 @@ func TestDiskPartialMerge(t *testing.T) { rawdb.WriteStorageSnapshot(db, account, slot, data[:]) } } + insertAccount(conNoModNoCache, conNoModNoCache[:]) insertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) insertAccount(conNoModCache, conNoModCache[:]) @@ -310,10 +317,12 @@ func TestDiskPartialMerge(t *testing.T) { // already covered by the disk snapshot, and errors out otherwise. assertAccount := func(account common.Hash, data []byte) { t.Helper() + blob, err := base.AccountRLP(account) if bytes.Compare(account[:], genMarker) > 0 && err != ErrNotCoveredYet { t.Fatalf("test %d: post-marker (%x) account access (%x) succeeded: %x", i, genMarker, account, blob) } + if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { t.Fatalf("test %d: pre-marker (%x) account access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) } @@ -326,10 +335,12 @@ func TestDiskPartialMerge(t *testing.T) { // it's already covered by the disk snapshot, and errors out otherwise. assertStorage := func(account common.Hash, slot common.Hash, data []byte) { t.Helper() + blob, err := base.Storage(account, slot) if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && err != ErrNotCoveredYet { t.Fatalf("test %d: post-marker (%x) storage access (%x:%x) succeeded: %x", i, genMarker, account, slot, blob) } + if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { t.Fatalf("test %d: pre-marker (%x) storage access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) } @@ -356,6 +367,7 @@ func TestDiskPartialMerge(t *testing.T) { }); err != nil { t.Fatalf("test %d: failed to update snapshot tree: %v", i, err) } + if err := snaps.Cap(diffRoot, 0); err != nil { t.Fatalf("test %d: failed to flatten snapshot tree: %v", i, err) } @@ -364,6 +376,7 @@ func TestDiskPartialMerge(t *testing.T) { if _, ok := base.(*diskLayer); !ok { t.Fatalf("test %d: update not flattend into the disk layer", i) } + assertAccount(accNoModNoCache, accNoModNoCache[:]) assertAccount(accNoModCache, accNoModCache[:]) assertAccount(accModNoCache, reverse(accModNoCache[:])) @@ -387,10 +400,12 @@ func TestDiskPartialMerge(t *testing.T) { // exist otherwise. assertDatabaseAccount := func(account common.Hash, data []byte) { t.Helper() + blob := rawdb.ReadAccountSnapshot(db, account) if bytes.Compare(account[:], genMarker) > 0 && blob != nil { t.Fatalf("test %d: post-marker (%x) account database access (%x) succeeded: %x", i, genMarker, account, blob) } + if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { t.Fatalf("test %d: pre-marker (%x) account database access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) } @@ -407,10 +422,12 @@ func TestDiskPartialMerge(t *testing.T) { // and does not exist otherwise. assertDatabaseStorage := func(account common.Hash, slot common.Hash, data []byte) { t.Helper() + blob := rawdb.ReadStorageSnapshot(db, account, slot) if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && blob != nil { t.Fatalf("test %d: post-marker (%x) storage database access (%x:%x) succeeded: %x", i, genMarker, account, slot, blob) } + if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { t.Fatalf("test %d: pre-marker (%x) storage database access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) } @@ -467,14 +484,19 @@ func TestDiskGeneratorPersistence(t *testing.T) { }, nil); err != nil { t.Fatalf("failed to update snapshot tree: %v", err) } + if err := snaps.Cap(diffRoot, 0); err != nil { t.Fatalf("failed to flatten snapshot tree: %v", err) } + blob := rawdb.ReadSnapshotGenerator(db) + var generator journalGenerator + if err := rlp.DecodeBytes(blob, &generator); err != nil { t.Fatalf("Failed to decode snapshot generator %v", err) } + if !bytes.Equal(generator.Marker, genMarker) { t.Fatalf("Generator marker is not matched") } @@ -487,15 +509,19 @@ func TestDiskGeneratorPersistence(t *testing.T) { }); err != nil { t.Fatalf("failed to update snapshot tree: %v", err) } + diskLayer := snaps.layers[snaps.diskRoot()].(*diskLayer) diskLayer.genMarker = nil // Construction finished + if err := snaps.Cap(diffTwoRoot, 0); err != nil { t.Fatalf("failed to flatten snapshot tree: %v", err) } + blob = rawdb.ReadSnapshotGenerator(db) if err := rlp.DecodeBytes(blob, &generator); err != nil { t.Fatalf("Failed to decode snapshot generator %v", err) } + if len(generator.Marker) != 0 { t.Fatalf("Failed to update snapshot generator") } @@ -543,6 +569,7 @@ func TestDiskSeek(t *testing.T) { pos byte expkey byte } + var cases = []testcase{ {0xff, 0x55}, // this should exit immediately without checking key {0x01, 0x02}, @@ -550,12 +577,15 @@ func TestDiskSeek(t *testing.T) { {0xfd, 0xfe}, {0x00, 0x00}, } + for i, tc := range cases { it, err := snaps.AccountIterator(baseRoot, common.Hash{tc.pos}) if err != nil { t.Fatalf("case %d, error: %v", i, err) } + count := 0 + for it.Next() { k, v, err := it.Hash()[0], it.Account()[0], it.Error() if err != nil { @@ -565,6 +595,7 @@ func TestDiskSeek(t *testing.T) { if count == 0 && k != tc.expkey { t.Fatalf("test %d, item %d, got %v exp %v", i, count, k, tc.expkey) } + count++ if v != k { t.Fatalf("test %d, item %d, value wrong, got %v exp %v", i, count, v, k) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index ef6cefaf04..c25c79437c 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -62,11 +62,14 @@ func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache i 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", "err", err) } + base := &diskLayer{ diskdb: diskdb, triedb: triedb, @@ -78,6 +81,7 @@ func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache i } go base.generate(stats) log.Debug("Start snapshot generation", "root", root) + return base } @@ -95,10 +99,12 @@ func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorSta entry.Slots = stats.slots entry.Storage = uint64(stats.storage) } + blob, err := rlp.EncodeToBytes(entry) if err != nil { panic(err) // Cannot happen, here to catch dev errors } + var logstr string switch { case marker == nil: @@ -110,6 +116,7 @@ func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorSta default: logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:]) } + log.Debug("Journalled generator progress", "progress", logstr) rawdb.WriteSnapshotGenerator(db, blob) } @@ -137,6 +144,7 @@ func (result *proofResult) last() []byte { if len(result.keys) > 0 { last = result.keys[len(result.keys)-1] } + return last } @@ -149,6 +157,7 @@ func (result *proofResult) forEach(callback func(key []byte, val []byte) error) return err } } + return nil } @@ -170,6 +179,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ start = time.Now() min = append(prefix, origin...) ) + for iter.Next() { // Ensure the iterated item is always equal or larger than the given origin. key := iter.Key() @@ -189,9 +199,12 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ // extra element out. if len(keys) == max { iter.Hold() + diskMore = true + break } + keys = append(keys, common.CopyBytes(key[len(prefix):])) if valueConvertFn == nil { @@ -206,6 +219,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ // Here append the original value to ensure that the number of key and // value are aligned. vals = append(vals, common.CopyBytes(iter.Value())) + log.Error("Failed to convert account state data", "err", err) } else { vals = append(vals, val) @@ -218,6 +232,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ } else { snapAccountSnapReadCounter.Inc(time.Since(start).Nanoseconds()) } + defer func(start time.Time) { if kind == "storage" { snapStorageProveCounter.Inc(time.Since(start).Nanoseconds()) @@ -228,11 +243,13 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ // The snap state is exhausted, pass the entire key/val set for verification root := trieId.Root + if origin == nil && !diskMore { stackTr := trie.NewStackTrie(nil) for i, key := range keys { stackTr.Update(key, vals[i]) } + if gotRoot := stackTr.Hash(); gotRoot != root { return &proofResult{ keys: keys, @@ -240,6 +257,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ proofErr: fmt.Errorf("wrong root: have %#x want %#x", gotRoot, root), }, nil } + return &proofResult{keys: keys, vals: vals}, nil } // Snap state is chunked, generate edge proofs for verification. @@ -257,8 +275,10 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ if origin == nil { origin = common.Hash{}.Bytes() } + if err := tr.Prove(origin, 0, proof); err != nil { log.Debug("Failed to prove range", "kind", kind, "origin", origin, "err", err) + return &proofResult{ keys: keys, vals: vals, @@ -267,9 +287,11 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ tr: tr, }, nil } + if last != nil { if err := tr.Prove(last, 0, proof); err != nil { log.Debug("Failed to prove range", "kind", kind, "last", last, "err", err) + return &proofResult{ keys: keys, vals: vals, @@ -282,6 +304,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ // Verify the snapshot segment with range prover, ensure that all flat states // in this range correspond to merkle trie. cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof) + return &proofResult{ keys: keys, vals: vals, @@ -315,6 +338,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi if err != nil { return false, nil, err } + last := result.last() // Construct contextual logger @@ -322,6 +346,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi if len(origin) > 0 { logCtx = append(logCtx, "origin", hexutil.Encode(origin)) } + logger := log.New(logCtx...) // The range prover says the range is correct, skip trie iteration @@ -338,6 +363,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi // Only abort the iteration when both database and trie are exhausted return !result.diskMore && !result.trieMore, last, nil } + logger.Trace("Detected outdated state range", "last", hexutil.Encode(last), "err", result.proofErr) snapFailedRangeProofMeter.Mark(1) @@ -351,14 +377,17 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi if kind == snapStorage { meter = snapMissallStorageMeter } + meter.Mark(1) } // We use the snap data to build up a cache which can be used by the // main account trie as a primary lookup when resolving hashes var resolver trie.NodeResolver + if len(result.keys) > 0 { mdb := rawdb.NewMemoryDatabase() tdb := trie.NewDatabase(mdb) + snapTrie := trie.NewEmpty(tdb) for i, key := range result.keys { snapTrie.Update(key, result.vals[i]) @@ -385,6 +414,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi return false, nil, errMissingTrie } } + var ( trieMore bool nodeIt = tr.NodeIterator(origin) @@ -410,51 +440,66 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi trieMore = true break } + count++ write := true created++ + for len(kvkeys) > 0 { if cmp := bytes.Compare(kvkeys[0], iter.Key); cmp < 0 { // delete the key istart := time.Now() + if err := onState(kvkeys[0], nil, false, true); err != nil { return false, nil, err } + kvkeys = kvkeys[1:] kvvals = kvvals[1:] deleted++ internal += time.Since(istart) + continue } else if cmp == 0 { // the snapshot key can be overwritten created-- + if write = !bytes.Equal(kvvals[0], iter.Value); write { updated++ } else { untouched++ } + kvkeys = kvkeys[1:] kvvals = kvvals[1:] } + break } + istart := time.Now() + if err := onState(iter.Key, iter.Value, write, false); err != nil { return false, nil, err } + internal += time.Since(istart) } + if iter.Err != nil { return false, nil, iter.Err } // Delete all stale snapshot states remaining istart := time.Now() + for _, key := range kvkeys { if err := onState(key, nil, false, true); err != nil { return false, nil, err } + deleted += 1 } + internal += time.Since(istart) // Update metrics for counting trie iteration @@ -547,6 +592,7 @@ func generateStorages(ctx *generatorContext, dl *diskLayer, stateRoot common.Has if err := dl.checkAndFlush(ctx, append(account[:], key...)); err != nil { return err } + return nil } // Loop for re-generating the missing storage slots. @@ -583,12 +629,14 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er ctx.removeStorageBefore(account) start := time.Now() + if delete { rawdb.DeleteAccountSnapshot(ctx.batch, account) snapWipedAccountMeter.Mark(1) snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) ctx.removeStorageAt(account) + return nil } // Retrieve the current account and flatten it into the internal format @@ -598,12 +646,14 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er Root common.Hash CodeHash []byte } + if err := rlp.DecodeBytes(val, &acc); err != nil { log.Crit("Invalid account encountered during snapshot creation", "err", err) } // If the account is not yet in-progress, write it out if accMarker == nil || !bytes.Equal(account[:], accMarker) { dataLen := len(val) // Approximate size, saves us a round of RLP-encoding + if !write { if bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) { dataLen -= 32 @@ -612,6 +662,7 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er if acc.Root == types.EmptyRootHash { dataLen -= 32 } + snapRecoveredAccountMeter.Mark(1) } else { data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) @@ -645,12 +696,14 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er if accMarker != nil && bytes.Equal(account[:], accMarker) && len(dl.genMarker) > common.HashLength { storeMarker = dl.genMarker[common.HashLength:] } + if err := generateStorages(ctx, dl, dl.root, account, acc.Root, storeMarker); err != nil { return err } } // Some account processed, unmark the marker accMarker = nil + return nil } // Always reset the initial account range as 1 whenever recover from the @@ -661,8 +714,10 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er } origin := common.CopyBytes(accMarker) + for { id := trie.StateTrieID(dl.root) + exhausted, last, err := dl.generateRange(ctx, id, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountRange, onAccount, FullAccountRLP) if err != nil { return err // The procedure it aborted, either by external signal or internal error. @@ -676,6 +731,7 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er ctx.removeStorageLeft() break } + accountRange = accountCheckRange } @@ -732,6 +788,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { abort = <-dl.genAbort abort <- stats + return } @@ -759,6 +816,7 @@ func increaseKey(key []byte) []byte { return key } } + return nil } diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 41c14aed35..edacc9ab40 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -130,11 +130,13 @@ func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) { if err != nil { return common.Hash{}, err } + return hash, nil }, newGenerateStats(), true) if err != nil { t.Fatal(err) } + if snapRoot != trieRoot { t.Fatalf("snaproot: %#x != trieroot #%x", snapRoot, trieRoot) } @@ -155,6 +157,7 @@ func newHelper() *testHelper { diskdb := rawdb.NewMemoryDatabase() triedb := trie.NewDatabase(diskdb) accTrie, _ := trie.NewStateTrie(trie.StateTrieID(common.Hash{}), triedb) + return &testHelper{ diskdb: diskdb, triedb: triedb, @@ -188,6 +191,7 @@ func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string) func (t *testHelper) makeStorageTrie(stateRoot, owner common.Hash, keys []string, vals []string, commit bool) []byte { id := trie.StorageTrieID(stateRoot, owner, common.Hash{}) + stTrie, _ := trie.NewStateTrie(id, t.triedb) for i, k := range keys { stTrie.MustUpdate([]byte(k), []byte(vals[i])) @@ -202,6 +206,7 @@ func (t *testHelper) makeStorageTrie(stateRoot, owner common.Hash, keys []string if nodes != nil { _ = t.nodes.Merge(nodes) } + return root.Bytes() } @@ -210,6 +215,7 @@ func (t *testHelper) Commit() common.Hash { if nodes != nil { _ = t.nodes.Merge(nodes) } + _ = t.triedb.Update(t.nodes) _ = t.triedb.Commit(root, false) @@ -219,6 +225,7 @@ func (t *testHelper) Commit() common.Hash { func (t *testHelper) CommitAndGenerate() (common.Hash, *diskLayer) { root := t.Commit() snap := generateSnapshot(t.diskdb, t.triedb, 16, root) + return root, snap } @@ -618,6 +625,7 @@ func TestGenerateWithManyExtraAccounts(t *testing.T) { // But in the database, we still have the stale storage slots 0x04, 0x05. They are not iterated yet, but the procedure is finished. func TestGenerateWithExtraBeforeAndAfter(t *testing.T) { accountCheckRange = 3 + if false { enableLogging() } @@ -657,6 +665,7 @@ func TestGenerateWithExtraBeforeAndAfter(t *testing.T) { // in the snapshot database, which cannot be parsed back to an account func TestGenerateWithMalformedSnapdata(t *testing.T) { accountCheckRange = 3 + if false { enableLogging() } @@ -742,8 +751,11 @@ func TestGenerateWithIncompleteStorage(t *testing.T) { accKey := fmt.Sprintf("acc-%d", i) stRoot := helper.makeStorageTrie(common.Hash{}, hashData([]byte(accKey)), stKeys, stVals, true) helper.addAccount(accKey, &Account{Balance: big.NewInt(int64(i)), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + var moddedKeys []string + var moddedVals []string + for ii := 0; ii < 8; ii++ { if ii != i { moddedKeys = append(moddedKeys, stKeys[ii]) diff --git a/core/state/snapshot/holdable_iterator_test.go b/core/state/snapshot/holdable_iterator_test.go index ff6830d71a..11cc97d451 100644 --- a/core/state/snapshot/holdable_iterator_test.go +++ b/core/state/snapshot/holdable_iterator_test.go @@ -84,6 +84,7 @@ func TestIteratorHold(t *testing.T) { if !bytes.Equal(it.Value(), []byte(content[order[idx]])) { t.Errorf("item %d: value mismatch: have %s, want %s", idx, string(it.Value()), content[order[idx]]) } + idx++ } @@ -143,6 +144,7 @@ func TestReopenIterator(t *testing.T) { checkVal(ctx.account, idx) ctx.reopenIterator(snapAccount) + idx++ ctx.account.Next() @@ -151,6 +153,7 @@ func TestReopenIterator(t *testing.T) { // reopen twice ctx.reopenIterator(snapAccount) ctx.reopenIterator(snapAccount) + idx++ ctx.account.Next() @@ -160,6 +163,7 @@ func TestReopenIterator(t *testing.T) { ctx.account.Next() ctx.account.Hold() ctx.reopenIterator(snapAccount) + idx++ ctx.account.Next() @@ -170,6 +174,7 @@ func TestReopenIterator(t *testing.T) { ctx.account.Hold() ctx.reopenIterator(snapAccount) ctx.reopenIterator(snapAccount) + idx++ ctx.account.Next() diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index c1a196c7ff..a4ec56d2af 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -109,6 +109,7 @@ func (it *diffAccountIterator) Next() bool { if len(it.keys) == 0 { return false } + if it.layer.Stale() { it.fail, it.keys = ErrSnapshotStale, nil return false @@ -117,6 +118,7 @@ func (it *diffAccountIterator) Next() bool { it.curHash = it.keys[0] // key cached, shift the iterator and notify the user of success it.keys = it.keys[1:] + return true } @@ -141,18 +143,22 @@ func (it *diffAccountIterator) Hash() common.Hash { // Note the returned account is not a copy, please don't modify it. func (it *diffAccountIterator) Account() []byte { it.layer.lock.RLock() + blob, ok := it.layer.accountData[it.curHash] if !ok { if _, ok := it.layer.destructSet[it.curHash]; ok { it.layer.lock.RUnlock() return nil } + panic(fmt.Sprintf("iterator referenced non-existent account: %x", it.curHash)) } it.layer.lock.RUnlock() + if it.layer.Stale() { it.fail, it.keys = ErrSnapshotStale, nil } + return blob } @@ -169,6 +175,7 @@ type diskAccountIterator struct { // AccountIterator creates an account iterator over a disk layer. func (dl *diskLayer) AccountIterator(seek common.Hash) AccountIterator { pos := common.TrimRightZeroes(seek[:]) + return &diskAccountIterator{ layer: dl, it: dl.diskdb.NewIterator(rawdb.SnapshotAccountPrefix, pos), @@ -186,12 +193,15 @@ func (it *diskAccountIterator) Next() bool { if !it.it.Next() { it.it.Release() it.it = nil + return false } + if len(it.it.Key()) == len(rawdb.SnapshotAccountPrefix)+common.HashLength { break } } + return true } @@ -204,6 +214,7 @@ func (it *diskAccountIterator) Error() error { if it.it == nil { return nil // Iterator is exhausted and released } + return it.it.Error() } @@ -276,6 +287,7 @@ func (it *diffStorageIterator) Next() bool { if len(it.keys) == 0 { return false } + if it.layer.Stale() { it.fail, it.keys = ErrSnapshotStale, nil return false @@ -284,6 +296,7 @@ func (it *diffStorageIterator) Next() bool { it.curHash = it.keys[0] // key cached, shift the iterator and notify the user of success it.keys = it.keys[1:] + return true } @@ -308,6 +321,7 @@ func (it *diffStorageIterator) Hash() common.Hash { // Note the returned slot is not a copy, please don't modify it. func (it *diffStorageIterator) Slot() []byte { it.layer.lock.RLock() + storage, ok := it.layer.storageData[it.account] if !ok { panic(fmt.Sprintf("iterator referenced non-existent account storage: %x", it.account)) @@ -318,9 +332,11 @@ func (it *diffStorageIterator) Slot() []byte { panic(fmt.Sprintf("iterator referenced non-existent storage slot: %x", it.curHash)) } it.layer.lock.RUnlock() + if it.layer.Stale() { it.fail, it.keys = ErrSnapshotStale, nil } + return blob } @@ -341,6 +357,7 @@ type diskStorageIterator struct { // is always false. func (dl *diskLayer) StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) { pos := common.TrimRightZeroes(seek[:]) + return &diskStorageIterator{ layer: dl, account: account, @@ -359,12 +376,15 @@ func (it *diskStorageIterator) Next() bool { if !it.it.Next() { it.it.Release() it.it = nil + return false } + if len(it.it.Key()) == len(rawdb.SnapshotStoragePrefix)+common.HashLength+common.HashLength { break } } + return true } @@ -377,6 +397,7 @@ func (it *diskStorageIterator) Error() error { if it.it == nil { return nil // Iterator is exhausted and released } + return it.it.Error() } diff --git a/core/state/snapshot/iterator_binary.go b/core/state/snapshot/iterator_binary.go index 22184b2545..f720b72542 100644 --- a/core/state/snapshot/iterator_binary.go +++ b/core/state/snapshot/iterator_binary.go @@ -49,8 +49,10 @@ func (dl *diffLayer) initBinaryAccountIterator() Iterator { } l.aDone = !l.a.Next() l.bDone = !l.b.Next() + return l } + l := &binaryIterator{ a: dl.AccountIterator(common.Hash{}), b: parent.initBinaryAccountIterator(), @@ -58,6 +60,7 @@ func (dl *diffLayer) initBinaryAccountIterator() Iterator { } l.aDone = !l.a.Next() l.bDone = !l.b.Next() + return l } @@ -77,6 +80,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator { } l.aDone = !l.a.Next() l.bDone = true + return l } // The parent is disk layer, don't need to take care "destructed" @@ -89,6 +93,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator { } l.aDone = !l.a.Next() l.bDone = !l.b.Next() + return l } // If the storage in this layer is already destructed, discard all @@ -101,8 +106,10 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator { } l.aDone = !l.a.Next() l.bDone = true + return l } + l := &binaryIterator{ a: a, b: parent.initBinaryStorageIterator(account), @@ -110,6 +117,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator { } l.aDone = !l.a.Next() l.bDone = !l.b.Next() + return l } @@ -126,23 +134,29 @@ first: it.bDone = !it.b.Next() return true } + if it.bDone { it.k = it.a.Hash() it.aDone = !it.a.Next() + return true } + nextA, nextB := it.a.Hash(), it.b.Hash() if diff := bytes.Compare(nextA[:], nextB[:]); diff < 0 { it.aDone = !it.a.Next() it.k = nextA + return true } else if diff == 0 { // Now we need to advance one of them it.aDone = !it.a.Next() goto first } + it.bDone = !it.b.Next() it.k = nextB + return true } @@ -172,6 +186,7 @@ func (it *binaryIterator) Account() []byte { it.fail = err return nil } + return blob } @@ -184,11 +199,13 @@ func (it *binaryIterator) Slot() []byte { if it.accountIterator { return nil } + blob, err := it.a.(*diffStorageIterator).layer.Storage(it.account, it.k) if err != nil { it.fail = err return nil } + return blob } diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index 1a042c7cd3..0fb2466b2d 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -83,11 +83,13 @@ func newFastIterator(tree *Tree, root common.Hash, account common.Hash, seek com if snap == nil { return nil, fmt.Errorf("unknown snapshot: %x", root) } + fi := &fastIterator{ tree: tree, root: root, account: accountIterator, } + current := snap.(snapshot) for depth := 0; current != nil; depth++ { if accountIterator { @@ -105,13 +107,16 @@ func newFastIterator(tree *Tree, root common.Hash, account common.Hash, seek com it: it, priority: depth, }) + if destructed { break } } + current = current.Parent() } fi.init() + return fi, nil } @@ -127,10 +132,12 @@ func (fi *fastIterator) init() { // advance either the current one or the old one. Repeat until nothing is // clashing any more. it := fi.iterators[i] + for { // If the iterator is exhausted, drop it off the end if !it.it.Next() { it.it.Release() + last := len(fi.iterators) - 1 fi.iterators[i] = fi.iterators[last] @@ -138,6 +145,7 @@ func (fi *fastIterator) init() { fi.iterators = fi.iterators[:last] i-- + break } // The iterator is still alive, check for collisions with previous ones @@ -161,6 +169,7 @@ func (fi *fastIterator) init() { // The 'other' should be progressed, swap them it = fi.iterators[other] fi.iterators[other], fi.iterators[i] = fi.iterators[i], fi.iterators[other] + continue } } @@ -176,6 +185,7 @@ func (fi *fastIterator) Next() bool { if len(fi.iterators) == 0 { return false } + if !fi.initiated { // Don't forward first time -- we had to 'Next' once in order to // do the sorting already @@ -185,10 +195,12 @@ func (fi *fastIterator) Next() bool { } else { fi.curSlot = fi.iterators[0].it.(StorageIterator).Slot() } + if innerErr := fi.iterators[0].it.Error(); innerErr != nil { fi.fail = innerErr return false } + if fi.curAccount != nil || fi.curSlot != nil { return true } @@ -206,19 +218,23 @@ func (fi *fastIterator) Next() bool { if !fi.next(0) { return false // exhausted } + if fi.account { fi.curAccount = fi.iterators[0].it.(AccountIterator).Account() } else { fi.curSlot = fi.iterators[0].it.(StorageIterator).Slot() } + if innerErr := fi.iterators[0].it.Error(); innerErr != nil { fi.fail = innerErr return false // error } + if fi.curAccount != nil || fi.curSlot != nil { break // non-nil value found } } + return true } @@ -236,6 +252,7 @@ func (fi *fastIterator) next(idx int) bool { it.Release() fi.iterators = append(fi.iterators[:idx], fi.iterators[idx+1:]...) + return len(fi.iterators) > 0 } // If there's no one left to cascade into, return @@ -247,6 +264,7 @@ func (fi *fastIterator) next(idx int) bool { cur, next = fi.iterators[idx], fi.iterators[idx+1] curHash, nextHash = cur.it.Hash(), next.it.Hash() ) + if diff := bytes.Compare(curHash[:], nextHash[:]); diff < 0 { // It is still in correct place return true @@ -265,10 +283,12 @@ func (fi *fastIterator) next(idx int) bool { if n < idx { return false } + if n == len(fi.iterators)-1 { // Can always place an elem last return true } + nextHash := fi.iterators[n+1].it.Hash() if diff := bytes.Compare(curHash[:], nextHash[:]); diff < 0 { return true @@ -282,9 +302,11 @@ func (fi *fastIterator) next(idx int) bool { return cur.priority < fi.iterators[n+1].priority }) fi.move(idx, index) + if clash != -1 { fi.next(clash) } + return true } @@ -324,6 +346,7 @@ func (fi *fastIterator) Release() { for _, it := range fi.iterators { it.it.Release() } + fi.iterators = nil } @@ -332,6 +355,7 @@ func (fi *fastIterator) Debug() { for _, it := range fi.iterators { fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Hash()[0]) } + fmt.Println() } diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 35bb0ee3e0..d3a9ac34bf 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -42,13 +42,15 @@ func TestAccountIteratorBasics(t *testing.T) { data := randomAccount() accounts[h] = data + if rand.Intn(4) == 0 { destructs[h] = struct{}{} } + if rand.Intn(2) == 0 { accStorage := make(map[common.Hash][]byte) value := make([]byte, 32) - _ , _ = crand.Read(value) + _, _ = crand.Read(value) accStorage[randomHash()] = value storage[h] = accStorage } @@ -79,8 +81,10 @@ func TestStorageIteratorBasics(t *testing.T) { value := make([]byte, 32) var nilstorage int + for i := 0; i < 100; i++ { - _ , _ = crand.Read(value) + _, _ = crand.Read(value) + if rand.Intn(2) == 0 { accStorage[randomHash()] = common.CopyBytes(value) } else { @@ -88,6 +92,7 @@ func TestStorageIteratorBasics(t *testing.T) { nilstorage += 1 } } + storage[h] = accStorage nilStorage[h] = nilstorage } @@ -145,6 +150,7 @@ func TestFastIteratorBasics(t *testing.T) { lists [][]byte expKeys []byte } + for i, tc := range []testCase{ {lists: [][]byte{{0, 1, 8}, {1, 2, 8}, {2, 9}, {4}, {7, 14, 15}, {9, 13, 15, 16}}, @@ -154,19 +160,23 @@ func TestFastIteratorBasics(t *testing.T) { expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}}, } { var iterators []*weightedIterator + for i, data := range tc.lists { it := newTestIterator(data...) iterators = append(iterators, &weightedIterator{it, i}) } + fi := &fastIterator{ iterators: iterators, initiated: false, } count := 0 + for fi.Next() { if got, exp := fi.Hash()[31], tc.expKeys[count]; exp != got { t.Errorf("tc %d, [%d]: got %d exp %d", i, count, got, exp) } + count++ } } @@ -187,22 +197,28 @@ func verifyIterator(t *testing.T, expCount int, it Iterator, verify verifyConten count = 0 last = common.Hash{} ) + for it.Next() { hash := it.Hash() if bytes.Compare(last[:], hash[:]) >= 0 { t.Errorf("wrong order: %x >= %x", last, hash) } + count++ + if verify == verifyAccount && len(it.(AccountIterator).Account()) == 0 { t.Errorf("iterator returned nil-value for hash %x", hash) } else if verify == verifyStorage && len(it.(StorageIterator).Slot()) == 0 { t.Errorf("iterator returned nil-value for hash %x", hash) } + last = hash } + if count != expCount { t.Errorf("iterator count mismatch: have %d, want %d", count, expCount) } + if err := it.Error(); err != nil { t.Errorf("iterator failed: %v", err) } @@ -247,7 +263,9 @@ func TestAccountIteratorTraversal(t *testing.T) { defer func() { aggregatorMemoryLimit = limit }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x04"), 2) verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) @@ -295,7 +313,9 @@ func TestStorageIteratorTraversal(t *testing.T) { defer func() { aggregatorMemoryLimit = limit }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x04"), 2) verifyIterator(t, 6, head.(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage) @@ -329,26 +349,34 @@ func TestAccountIteratorTraversalValues(t *testing.T) { g = make(map[common.Hash][]byte) h = make(map[common.Hash][]byte) ) + for i := byte(2); i < 0xff; i++ { a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) + if i > 20 && i%2 == 0 { b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) } + if i%4 == 0 { c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) } + if i%7 == 0 { d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) } + if i%8 == 0 { e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) } + if i > 50 || i < 85 { f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) } + if i%64 == 0 { g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) } + if i%128 == 0 { h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) } @@ -365,12 +393,15 @@ func TestAccountIteratorTraversalValues(t *testing.T) { it, _ := snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) head := snaps.Snapshot(common.HexToHash("0x09")) + for it.Next() { hash := it.Hash() + want, err := head.AccountRLP(hash) if err != nil { t.Fatalf("failed to retrieve expected account: %v", err) } + if have := it.Account(); !bytes.Equal(want, have) { t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want) } @@ -383,16 +414,20 @@ func TestAccountIteratorTraversalValues(t *testing.T) { defer func() { aggregatorMemoryLimit = limit }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x09"), 2) it, _ = snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) for it.Next() { hash := it.Hash() + want, err := head.AccountRLP(hash) if err != nil { t.Fatalf("failed to retrieve expected account: %v", err) } + if have := it.Account(); !bytes.Equal(want, have) { t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want) } @@ -428,26 +463,34 @@ func TestStorageIteratorTraversalValues(t *testing.T) { g = make(map[common.Hash][]byte) h = make(map[common.Hash][]byte) ) + for i := byte(2); i < 0xff; i++ { a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) + if i > 20 && i%2 == 0 { b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) } + if i%4 == 0 { c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) } + if i%7 == 0 { d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) } + if i%8 == 0 { e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) } + if i > 50 || i < 85 { f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) } + if i%64 == 0 { g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) } + if i%128 == 0 { h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) } @@ -464,12 +507,15 @@ func TestStorageIteratorTraversalValues(t *testing.T) { it, _ := snaps.StorageIterator(common.HexToHash("0x09"), common.HexToHash("0xaa"), common.Hash{}) head := snaps.Snapshot(common.HexToHash("0x09")) + for it.Next() { hash := it.Hash() + want, err := head.Storage(common.HexToHash("0xaa"), hash) if err != nil { t.Fatalf("failed to retrieve expected storage slot: %v", err) } + if have := it.Slot(); !bytes.Equal(want, have) { t.Fatalf("hash %x: slot mismatch: have %x, want %x", hash, have, want) } @@ -482,16 +528,20 @@ func TestStorageIteratorTraversalValues(t *testing.T) { defer func() { aggregatorMemoryLimit = limit }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x09"), 2) it, _ = snaps.StorageIterator(common.HexToHash("0x09"), common.HexToHash("0xaa"), common.Hash{}) for it.Next() { hash := it.Hash() + want, err := head.Storage(common.HexToHash("0xaa"), hash) if err != nil { t.Fatalf("failed to retrieve expected slot: %v", err) } + if have := it.Slot(); !bytes.Equal(want, have) { t.Fatalf("hash %x: slot mismatch: have %x, want %x", hash, have, want) } @@ -504,11 +554,14 @@ func TestAccountIteratorLargeTraversal(t *testing.T) { // Create a custom account factory to recreate the same addresses makeAccounts := func(num int) map[common.Hash][]byte { accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { h := common.Hash{} binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() } + return accounts } // Build up a large stack of snapshots @@ -517,6 +570,7 @@ func TestAccountIteratorLargeTraversal(t *testing.T) { root: common.HexToHash("0x01"), cache: fastcache.New(1024 * 500), } + snaps := &Tree{ layers: map[common.Hash]snapshot{ base.root: base, @@ -540,7 +594,9 @@ func TestAccountIteratorLargeTraversal(t *testing.T) { defer func() { aggregatorMemoryLimit = limit }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x80"), 2) verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) @@ -747,11 +803,13 @@ func TestAccountIteratorDeletions(t *testing.T) { // And a more detailed verification that we indeed do not see '0x22' it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) defer it.Release() + for it.Next() { hash := it.Hash() if it.Account() == nil { t.Errorf("iterator returned nil-value for hash %x", hash) } + if hash == deleted { t.Errorf("expected deleted elem %x to not be returned by iterator", deleted) } @@ -830,11 +888,14 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { // Create a custom account factory to recreate the same addresses makeAccounts := func(num int) map[common.Hash][]byte { accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { h := common.Hash{} binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() } + return accounts } // Build up a large stack of snapshots @@ -843,6 +904,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { root: common.HexToHash("0x01"), cache: fastcache.New(1024 * 500), } + snaps := &Tree{ layers: map[common.Hash]snapshot{ base.root: base, @@ -860,9 +922,11 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 it := head.(*diffLayer).newBinaryAccountIterator() + for it.Next() { got++ } + if exp := 200; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) } @@ -872,10 +936,13 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 it := head.(*diffLayer).newBinaryAccountIterator() + for it.Next() { got++ + head.(*diffLayer).accountRLP(it.Hash(), 0) } + if exp := 200; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) } @@ -890,6 +957,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { for it.Next() { got++ } + if exp := 200; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) } @@ -903,8 +971,10 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { got := 0 for it.Next() { got++ + it.Account() } + if exp := 200; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) } @@ -926,11 +996,14 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { // Create a custom account factory to recreate the same addresses makeAccounts := func(num int) map[common.Hash][]byte { accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { h := common.Hash{} binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() } + return accounts } // Build up a large stack of snapshots @@ -945,6 +1018,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { }, } snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, makeAccounts(2000), nil) + for i := 2; i <= 100; i++ { snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(20), nil) } @@ -957,9 +1031,11 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 it := head.(*diffLayer).newBinaryAccountIterator() + for it.Next() { got++ } + if exp := 2000; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) } @@ -969,11 +1045,13 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 it := head.(*diffLayer).newBinaryAccountIterator() + for it.Next() { got++ v := it.Hash() head.(*diffLayer).accountRLP(v, 0) } + if exp := 2000; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) } @@ -988,6 +1066,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { for it.Next() { got++ } + if exp := 2000; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) } @@ -999,10 +1078,13 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { defer it.Release() got := 0 + for it.Next() { it.Account() + got++ } + if exp := 2000; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) } diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index 7ebcf7df0f..199d7ce226 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -70,6 +70,7 @@ func ParseGeneratorStatus(generatorBlob []byte) string { if len(generatorBlob) == 0 { return "" } + var generator journalGenerator if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { log.Warn("failed to decode snapshot generator", "err", err) @@ -77,6 +78,7 @@ func ParseGeneratorStatus(generatorBlob []byte) string { } // Figure out whether we're after or within an account var m string + switch marker := generator.Marker; len(marker) { case common.HashLength: m = fmt.Sprintf("at %#x", marker) @@ -85,6 +87,7 @@ func ParseGeneratorStatus(generatorBlob []byte) string { default: m = fmt.Sprintf("%#x", marker) } + return fmt.Sprintf(`Done: %v, Accounts: %d, Slots: %d, Storage: %d, Marker: %s`, generator.Done, generator.Accounts, generator.Slots, generator.Storage, m) } @@ -98,6 +101,7 @@ func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, jou if len(generatorBlob) == 0 { return nil, journalGenerator{}, errors.New("missing snapshot generator") } + var generator journalGenerator if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err) @@ -134,12 +138,14 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, root common if baseRoot == (common.Hash{}) { return nil, false, errors.New("missing or corrupted snapshot") } + base := &diskLayer{ diskdb: diskdb, triedb: triedb, cache: fastcache.New(cache * 1024 * 1024), root: baseRoot, } + snapshot, generator, err := loadAndParseJournal(diskdb, base) if err != nil { log.Warn("Failed to load journal", "error", err) @@ -183,6 +189,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, root common if len(generator.Marker) >= 8 { origin = binary.BigEndian.Uint64(generator.Marker) } + go base.generate(&generatorStats{ origin: origin, start: time.Now(), @@ -191,6 +198,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, root common storage: common.StorageSize(generator.Storage), }) } + return snapshot, false, nil } @@ -199,6 +207,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, root common func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { // If the snapshot is currently being generated, abort it var stats *generatorStats + if dl.genAbort != nil { abort := make(chan *generatorStats) dl.genAbort <- abort @@ -218,6 +227,7 @@ func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { journalProgress(dl.diskdb, dl.genMarker, stats) log.Debug("Journalled disk layer", "root", dl.root) + return dl.root, nil } @@ -240,34 +250,45 @@ func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { if err := rlp.Encode(buffer, dl.root); err != nil { return common.Hash{}, err } + destructs := make([]journalDestruct, 0, len(dl.destructSet)) for hash := range dl.destructSet { destructs = append(destructs, journalDestruct{Hash: hash}) } + if err := rlp.Encode(buffer, destructs); err != nil { return common.Hash{}, err } + accounts := make([]journalAccount, 0, len(dl.accountData)) for hash, blob := range dl.accountData { accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) } + if err := rlp.Encode(buffer, accounts); err != nil { return common.Hash{}, err } + storage := make([]journalStorage, 0, len(dl.storageData)) + for hash, slots := range dl.storageData { keys := make([]common.Hash, 0, len(slots)) vals := make([][]byte, 0, len(slots)) + for key, val := range slots { keys = append(keys, key) vals = append(vals, val) } + storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) } + if err := rlp.Encode(buffer, storage); err != nil { return common.Hash{}, err } + log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) + return base, nil } diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index f719847189..52432d0abb 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -209,6 +209,7 @@ func New(config Config, diskdb ethdb.KeyValueStore, triedb *trie.Database, root if !config.NoBuild && !config.AsyncBuild { defer snap.waitBuild() } + if err != nil { log.Warn("Failed to load snapshot", "err", err) @@ -216,6 +217,7 @@ func New(config Config, diskdb ethdb.KeyValueStore, triedb *trie.Database, root snap.Rebuild(root) return snap, nil } + return nil, err // Bail out the error, don't rebuild automatically. } // Existing snapshot loaded, seed all the layers @@ -223,6 +225,7 @@ func New(config Config, diskdb ethdb.KeyValueStore, triedb *trie.Database, root snap.layers[head.Root()] = head head = head.Parent() } + return snap, nil } @@ -279,6 +282,7 @@ func (t *Tree) Disable() { panic(fmt.Sprintf("unknown layer type: %T", layer)) } } + t.layers = map[common.Hash]snapshot{} // Delete all snapshot liveness information from the database @@ -315,26 +319,34 @@ func (t *Tree) Snapshots(root common.Hash, limits int, nodisk bool) []Snapshot { 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 } @@ -355,6 +367,7 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m if parent == nil { return fmt.Errorf("parent [%#x] snapshot missing", parentRoot) } + snap := parent.(snapshot).Update(blockRoot, destructs, accounts, storage) // Save the new snapshot for later @@ -362,6 +375,7 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m defer t.lock.Unlock() t.layers[snap.root] = snap + return nil } @@ -380,6 +394,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error { if snap == nil { return fmt.Errorf("snapshot [%#x] missing", root) } + diff, ok := snap.(*diffLayer) if !ok { return fmt.Errorf("snapshot [%#x] is disk layer", root) @@ -406,26 +421,33 @@ 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 } + 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 { if diff, ok := snap.(*diffLayer); ok { parent := diff.parent.Root() children[parent] = append(children[parent], root) } } + var remove func(root common.Hash) remove = func(root common.Hash) { delete(t.layers, root) + for _, child := range children[root] { remove(child) } + delete(children, root) } + for root, snap := range t.layers { if snap.Stale() { remove(root) @@ -438,12 +460,14 @@ func (t *Tree) Cap(root common.Hash, layers int) error { if diff, ok := t.layers[root].(*diffLayer); ok { diff.rebloom(persisted) } + for _, child := range children[root] { rebloom(child) } } rebloom(persisted.root) } + return nil } @@ -491,7 +515,9 @@ func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { if t.onFlatten != nil { t.onFlatten() } + diff.parent = flattened + if flattened.memory < aggregatorMemoryLimit { // Accumulator layer is smaller than the limit, so we can abort, unless // there's a snapshot being generated currently. In that case, the trie @@ -513,6 +539,7 @@ func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { t.layers[base.root] = base diff.parent = base + return base } @@ -541,6 +568,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if base.stale { panic("parent disk layer is stale") // we've committed into the same base from two children, boo } + base.stale = true base.lock.Unlock() @@ -568,6 +596,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if err := batch.Write(); err != nil { log.Crit("Failed to write storage deletions", "err", err) } + batch.Reset() } } @@ -594,6 +623,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if err := batch.Write(); err != nil { log.Crit("Failed to write storage deletions", "err", err) } + batch.Reset() } } @@ -611,6 +641,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if midAccount && bytes.Compare(storageHash[:], base.genMarker[common.HashLength:]) > 0 { continue } + if len(data) > 0 { rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) base.cache.Set(append(accountHash[:], storageHash[:]...), data) @@ -619,6 +650,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash) base.cache.Set(append(accountHash[:], storageHash[:]...), nil) } + snapshotFlushStorageItemMeter.Mark(1) snapshotFlushStorageSizeMeter.Mark(int64(len(data))) } @@ -634,6 +666,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if err := batch.Write(); err != nil { log.Crit("Failed to write leftover snapshot", "err", err) } + log.Debug("Journalled disk layer", "root", bottom.root, "complete", base.genMarker == nil) res := &diskLayer{ root: bottom.root, @@ -651,8 +684,10 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if base.genMarker != nil && base.genAbort != nil { res.genMarker = base.genMarker res.genAbort = make(chan chan *generatorStats) + go res.generate(stats) } + return res } @@ -677,6 +712,7 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) { if err := rlp.Encode(journal, journalVersion); err != nil { return common.Hash{}, err } + diskroot := t.diskRoot() if diskroot == (common.Hash{}) { return common.Hash{}, errors.New("invalid disk root") @@ -693,6 +729,7 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) { } // Store the journal into the database and return rawdb.WriteSnapshotJournal(t.diskdb, journal.Bytes()) + return base, nil } @@ -736,6 +773,7 @@ func (t *Tree) Rebuild(root common.Hash) { // 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{ root: generateSnapshot(t.diskdb, t.triedb, t.config.CacheSize, root), } @@ -748,9 +786,11 @@ func (t *Tree) AccountIterator(root common.Hash, seek common.Hash) (AccountItera if err != nil { return nil, err } + if ok { return nil, ErrNotConstructed } + return newFastAccountIterator(t, root, seek) } @@ -761,9 +801,11 @@ func (t *Tree) StorageIterator(root common.Hash, account common.Hash, seek commo if err != nil { return nil, err } + if ok { return nil, ErrNotConstructed } + return newFastStorageIterator(t, root, account, seek) } @@ -787,15 +829,18 @@ func (t *Tree) Verify(root common.Hash) error { 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 } @@ -807,9 +852,11 @@ func (t *Tree) disklayer() *diskLayer { snap = s break } + if snap == nil { return nil } + switch layer := snap.(type) { case *diskLayer: return layer @@ -827,6 +874,7 @@ func (t *Tree) diskRoot() common.Hash { if disklayer == nil { return common.Hash{} } + return disklayer.Root() } @@ -840,8 +888,10 @@ func (t *Tree) generating() (bool, error) { if layer == nil { return false, errors.New("disk layer is missing") } + layer.lock.RLock() defer layer.lock.RUnlock() + return layer.genMarker != nil, nil } diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 249da10aa6..4c744fddeb 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -38,6 +38,7 @@ func randomHash() common.Hash { if n, err := crand.Read(hash[:]); n != common.HashLength || err != nil { panic(err) } + return hash } @@ -51,6 +52,7 @@ func randomAccount() []byte { CodeHash: types.EmptyCodeHash[:], } data, _ := rlp.EncodeToBytes(a) + return data } @@ -61,6 +63,7 @@ func randomAccountSet(hashes ...string) map[common.Hash][]byte { for _, hash := range hashes { accounts[common.HexToHash(hash)] = randomAccount() } + return accounts } @@ -77,6 +80,7 @@ func randomStorageSet(accounts []string, hashes [][]string, nilStorage [][]strin storages[common.HexToHash(account)][common.HexToHash(hash)] = randomHash().Bytes() } } + if index < len(nilStorage) { nils := nilStorage[index] for _, hash := range nils { @@ -84,6 +88,7 @@ func randomStorageSet(accounts []string, hashes [][]string, nilStorage [][]strin } } } + return storages } @@ -111,6 +116,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { 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 n := len(snaps.layers); n != 2 { t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2) } @@ -122,9 +128,11 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { 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 != 1 { t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 1) fmt.Println(snaps.layers) @@ -155,9 +163,11 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { 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) } @@ -172,9 +182,11 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { 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) @@ -203,22 +215,28 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { 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 err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } + if n := len(snaps.layers); n != 4 { t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 4) } + ref := snaps.Snapshot(common.HexToHash("0x02")) // Doing a Cap operation with many allowed layers should be a no-op exp := len(snaps.layers) + if err := snaps.Cap(common.HexToHash("0x04"), 2000); err != nil { t.Fatalf("failed to flatten diff layer into accumulator: %v", err) } + if got := len(snaps.layers); got != exp { t.Errorf("layers modified, got %d exp %d", got, exp) } @@ -230,9 +248,11 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { 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 != 3 { t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 3) fmt.Println(snaps.layers) @@ -272,6 +292,7 @@ func TestPostCapBasicDataAccess(t *testing.T) { if data, _ := layer.Account(common.HexToHash(key)); data == nil { return fmt.Errorf("expected %x to exist, got nil", common.HexToHash(key)) } + return nil } // shouldErr checks that an account access errors as expected @@ -279,6 +300,7 @@ func TestPostCapBasicDataAccess(t *testing.T) { if data, err := layer.Account(common.HexToHash(key)); err == nil { return fmt.Errorf("expected error, got data %x", data) } + return nil } // check basics @@ -287,9 +309,11 @@ func TestPostCapBasicDataAccess(t *testing.T) { if err := checkExist(snap, "0xa1"); err != nil { t.Error(err) } + if err := checkExist(snap, "0xb2"); err != nil { t.Error(err) } + if err := checkExist(snap, "0xb3"); err != nil { t.Error(err) } @@ -307,6 +331,7 @@ func TestPostCapBasicDataAccess(t *testing.T) { if err := checkExist(snap, "0xb2"); err != nil { t.Error(err) } + if err := checkExist(snap, "0xb3"); err != nil { t.Error(err) } @@ -314,9 +339,11 @@ func TestPostCapBasicDataAccess(t *testing.T) { if err := shouldErr(snap, "0xa1"); err != nil { t.Error(err) } + if err := shouldErr(snap, "0xa2"); err != nil { t.Error(err) } + if err := shouldErr(snap, "0xa3"); err != nil { t.Error(err) } @@ -339,7 +366,9 @@ func TestSnaphots(t *testing.T) { } 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 @@ -358,12 +387,14 @@ func TestSnaphots(t *testing.T) { last = common.HexToHash("0x01") head common.Hash ) + 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) // 130 layers (128 diffs + 1 accumulator + 1 disk) } + var cases = []struct { headRoot common.Hash limit int @@ -377,14 +408,17 @@ func TestSnaphots(t *testing.T) { {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()) @@ -417,9 +451,11 @@ func TestSnaphots(t *testing.T) { if len(layers) != c.expected { 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.Errorf("overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root()) @@ -462,6 +498,7 @@ func TestReadStateDuringFlattening(t *testing.T) { // Register the testing hook to access the state after flattening var result = make(chan *Account) + snaps.onFlatten = func() { // Spin up a thread to read the account from the pre-created // snapshot handler. It's expected to be blocked. diff --git a/core/state/snapshot/utils.go b/core/state/snapshot/utils.go index c962de2f62..7bd88db834 100644 --- a/core/state/snapshot/utils.go +++ b/core/state/snapshot/utils.go @@ -91,6 +91,7 @@ func checkDanglingMemStorage(db ethdb.KeyValueStore) error { log.Error("Dangling storage - missing account", "account", fmt.Sprintf("%#x", accHash), "root", root) } } + return nil }) @@ -126,7 +127,9 @@ func CheckJournalAccount(db ethdb.KeyValueStore, hash common.Hash) error { // Check storage { it := rawdb.NewKeyLengthIterator(db.NewIterator(append(rawdb.SnapshotStoragePrefix, hash.Bytes()...), nil), 1+2*common.HashLength) + fmt.Printf("\tStorage:\n") + for it.Next() { slot := it.Key()[33:] fmt.Printf("\t\t%x: %x\n", slot, it.Value()) @@ -141,29 +144,37 @@ func CheckJournalAccount(db ethdb.KeyValueStore, hash common.Hash) error { _, b := destructs[hash] _, c := storage[hash] depth++ + if !a && !b && !c { return nil } + fmt.Printf("Disklayer+%d: Root: %x, parent %x\n", depth, root, pRoot) + if data, ok := accounts[hash]; ok { account := new(Account) if err := rlp.DecodeBytes(data, account); err != nil { panic(err) } + fmt.Printf("\taccount.nonce: %d\n", account.Nonce) fmt.Printf("\taccount.balance: %x\n", account.Balance) fmt.Printf("\taccount.root: %x\n", account.Root) fmt.Printf("\taccount.codehash: %x\n", account.CodeHash) } + if _, ok := destructs[hash]; ok { fmt.Printf("\t Destructed!") } + if data, ok := storage[hash]; ok { fmt.Printf("\tStorage\n") + for k, v := range data { fmt.Printf("\t\t%x: %x\n", k, v) } } + return nil }) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 89d9a2c18b..37ebc06cf2 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -43,6 +43,7 @@ func (s Storage) String() (str string) { for key, value := range s { str += fmt.Sprintf("%X : %X\n", key, value) } + return } @@ -51,6 +52,7 @@ func (s Storage) Copy() Storage { for key, value := range s { cpy[key] = value } + return cpy } @@ -92,12 +94,15 @@ func newObject(db *StateDB, address common.Address, data types.StateAccount) *st if data.Balance == nil { data.Balance = new(big.Int) } + if data.CodeHash == nil { data.CodeHash = types.EmptyCodeHash.Bytes() } + if data.Root == (common.Hash{}) { data.Root = types.EmptyRootHash } + return &stateObject{ db: db, address: address, @@ -122,6 +127,7 @@ func (s *stateObject) touch() { s.db.journal.append(touchChange{ account: &s.address, }) + if s.address == ripemd { // Explicitly put it in the dirty-cache, which is otherwise generated from // flattened journals. @@ -141,6 +147,7 @@ func (s *stateObject) getTrie(db Database) (Trie, error) { // prefetcher s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root) } + if s.trie == nil { tr, err := db.OpenStorageTrie(s.db.originalRoot, s.addrHash, s.data.Root) if err != nil { @@ -171,6 +178,7 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has if value, pending := s.pendingStorage[key]; pending { return value } + if value, cached := s.originStorage[key]; cached { return value } @@ -188,9 +196,11 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has enc []byte err error ) + if s.db.snap != nil { start := time.Now() enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes())) + if metrics.EnabledExpensive { s.db.SnapshotStorageReads += time.Since(start) } @@ -206,23 +216,30 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has } enc, err = tr.GetStorage(s.address, key.Bytes()) + if metrics.EnabledExpensive { s.db.StorageReads += time.Since(start) } + if err != nil { s.db.setError(err) return common.Hash{} } } + var value common.Hash + if len(enc) > 0 { _, content, _, err := rlp.Split(enc) if err != nil { s.db.setError(err) } + value.SetBytes(content) } + s.originStorage[key] = value + return value } @@ -250,6 +267,7 @@ func (s *stateObject) setState(key, value common.Hash) { // committed later. It is invoked at the end of every transaction. func (s *stateObject) finalise(prefetch bool) { slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage)) + for key, value := range s.dirtyStorage { s.pendingStorage[key] = value if value != s.originStorage[key] { @@ -260,6 +278,7 @@ func (s *stateObject) finalise(prefetch bool) { if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch) } + if len(s.dirtyStorage) > 0 { s.dirtyStorage = make(Storage) } @@ -271,6 +290,7 @@ func (s *stateObject) finalise(prefetch bool) { func (s *stateObject) updateTrie(db Database) (Trie, error) { // Make sure all dirty slots are finalized into the pending storage area s.finalise(false) // Don't prefetch anymore, pull directly if need be + if len(s.pendingStorage) == 0 { return s.trie, nil } @@ -292,19 +312,23 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) { } // Insert all the pending updates into the trie usedStorage := make([][]byte, 0, len(s.pendingStorage)) + for key, value := range s.pendingStorage { // Skip noop changes, persist actual changes if value == s.originStorage[key] { continue } + s.originStorage[key] = value var v []byte + if (value == common.Hash{}) { if err := tr.DeleteStorage(s.address, key[:]); err != nil { s.db.setError(err) return nil, err } + s.db.StorageDeleted += 1 } else { // Encoding []byte cannot fail, ok to ignore the error. @@ -313,6 +337,7 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) { s.db.setError(err) return nil, err } + s.db.StorageUpdated += 1 } // If state snapshotting is active, cache the data til commit @@ -324,13 +349,17 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) { s.db.snapStorage[s.addrHash] = storage } } + storage[crypto.HashData(hasher, key[:])] = v // v will be nil if it's deleted } + usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure } + if s.db.prefetcher != nil { s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage) } + if len(s.pendingStorage) > 0 { s.pendingStorage = make(Storage) } @@ -389,8 +418,10 @@ func (s *stateObject) AddBalance(amount *big.Int) { if s.empty() { s.touch() } + return } + s.SetBalance(new(big.Int).Add(s.Balance(), amount)) } @@ -400,6 +431,7 @@ func (s *stateObject) SubBalance(amount *big.Int) { if amount.Sign() == 0 { return } + s.SetBalance(new(big.Int).Sub(s.Balance(), amount)) } @@ -420,6 +452,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { if s.trie != nil { stateObject.trie = db.db.CopyTrie(s.trie) } + stateObject.code = s.code stateObject.dirtyStorage = s.dirtyStorage.Copy() stateObject.originStorage = s.originStorage.Copy() @@ -427,6 +460,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { stateObject.suicided = s.suicided stateObject.dirtyCode = s.dirtyCode stateObject.deleted = s.deleted + return stateObject } @@ -448,11 +482,14 @@ func (s *stateObject) Code(db Database) []byte { if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { return nil } + code, err := db.ContractCode(s.addrHash, common.BytesToHash(s.CodeHash())) if err != nil { s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err)) } + s.code = code + return code } @@ -467,10 +504,12 @@ func (s *stateObject) CodeSize(db Database) int { if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { return 0 } + size, err := db.ContractCodeSize(s.addrHash, common.BytesToHash(s.CodeHash())) if err != nil { s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err)) } + return size } diff --git a/core/state/state_object_test.go b/core/state/state_object_test.go index 42fd778025..3c7b43a3d5 100644 --- a/core/state/state_object_test.go +++ b/core/state/state_object_test.go @@ -33,6 +33,7 @@ func BenchmarkCutOriginal(b *testing.B) { func BenchmarkCutsetterFn(b *testing.B) { value := common.HexToHash("0x01") cutSetFn := func(r rune) bool { return r == 0 } + for i := 0; i < b.N; i++ { bytes.TrimLeftFunc(value[:], cutSetFn) } diff --git a/core/state/state_test.go b/core/state/state_test.go index b6b46e446f..a345d27719 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -36,6 +36,7 @@ type stateTest struct { func newStateTest() *stateTest { db := rawdb.NewMemoryDatabase() sdb, _ := New(common.Hash{}, NewDatabase(db), nil) + return &stateTest{db: db, state: sdb} } @@ -47,8 +48,10 @@ func TestDump(t *testing.T) { // generate a few entries obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) obj1.AddBalance(big.NewInt(22)) + obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) + obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02})) obj3.SetBalance(big.NewInt(44)) @@ -86,6 +89,7 @@ func TestDump(t *testing.T) { } } }` + if got != want { t.Errorf("DumpToCollector mismatch:\ngot: %s\nwant: %s\n", got, want) } @@ -104,6 +108,7 @@ func TestNull(t *testing.T) { if value := s.state.GetState(address, common.Hash{}); value != (common.Hash{}) { t.Errorf("expected empty current value, got %x", value) } + if value := s.state.GetCommittedState(address, common.Hash{}); value != (common.Hash{}) { t.Errorf("expected empty committed value, got %x", value) } @@ -111,7 +116,9 @@ func TestNull(t *testing.T) { func TestSnapshot(t *testing.T) { stateobjaddr := common.BytesToAddress([]byte("aa")) + var storageaddr common.Hash + data1 := common.BytesToHash([]byte{42}) data2 := common.BytesToHash([]byte{43}) s := newStateTest() @@ -130,15 +137,18 @@ func TestSnapshot(t *testing.T) { if v := s.state.GetState(stateobjaddr, storageaddr); v != data1 { t.Errorf("wrong storage value %v, want %v", v, data1) } + if v := s.state.GetCommittedState(stateobjaddr, storageaddr); v != (common.Hash{}) { t.Errorf("wrong committed storage value %v, want %v", v, common.Hash{}) } // revert up to the genesis state and ensure correct content s.state.RevertToSnapshot(genesis) + if v := s.state.GetState(stateobjaddr, storageaddr); v != (common.Hash{}) { t.Errorf("wrong storage value %v, want %v", v, common.Hash{}) } + if v := s.state.GetCommittedState(stateobjaddr, storageaddr); v != (common.Hash{}) { t.Errorf("wrong committed storage value %v, want %v", v, common.Hash{}) } @@ -154,6 +164,7 @@ func TestSnapshot2(t *testing.T) { stateobjaddr0 := common.BytesToAddress([]byte("so0")) stateobjaddr1 := common.BytesToAddress([]byte("so1")) + var storageaddr common.Hash data0 := common.BytesToHash([]byte{17}) @@ -209,18 +220,23 @@ func compareStateObjects(so0, so1 *stateObject, t *testing.T) { if so0.Address() != so1.Address() { t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address) } + if so0.Balance().Cmp(so1.Balance()) != 0 { t.Fatalf("Balance mismatch: have %v, want %v", so0.Balance(), so1.Balance()) } + if so0.Nonce() != so1.Nonce() { t.Fatalf("Nonce mismatch: have %v, want %v", so0.Nonce(), so1.Nonce()) } + if so0.data.Root != so1.data.Root { t.Errorf("Root mismatch: have %x, want %x", so0.data.Root[:], so1.data.Root[:]) } + if !bytes.Equal(so0.CodeHash(), so1.CodeHash()) { t.Fatalf("CodeHash mismatch: have %v, want %v", so0.CodeHash(), so1.CodeHash()) } + if !bytes.Equal(so0.code, so1.code) { t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code) } @@ -228,24 +244,29 @@ func compareStateObjects(so0, so1 *stateObject, t *testing.T) { if len(so1.dirtyStorage) != len(so0.dirtyStorage) { t.Errorf("Dirty storage size mismatch: have %d, want %d", len(so1.dirtyStorage), len(so0.dirtyStorage)) } + for k, v := range so1.dirtyStorage { if so0.dirtyStorage[k] != v { t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, so0.dirtyStorage[k], v) } } + for k, v := range so0.dirtyStorage { if so1.dirtyStorage[k] != v { t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v) } } + if len(so1.originStorage) != len(so0.originStorage) { t.Errorf("Origin storage size mismatch: have %d, want %d", len(so1.originStorage), len(so0.originStorage)) } + for k, v := range so1.originStorage { if so0.originStorage[k] != v { t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[k], v) } } + for k, v := range so0.originStorage { if so1.originStorage[k] != v { t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v) diff --git a/core/state/statedb.go b/core/state/statedb.go index df439e7a84..d304ccb6b6 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -144,6 +144,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) if err != nil { return nil, err } + sdb := &StateDB{ db: db, trie: tr, @@ -167,6 +168,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte) } } + return sdb, nil } @@ -176,6 +178,7 @@ func NewWithMVHashmap(root common.Hash, db Database, snaps *snapshot.Tree, mvhm } else { sdb.mvHashmap = mvhm sdb.dep = -1 + return sdb, nil } } @@ -371,6 +374,7 @@ func (s *StateDB) ApplyMVWriteSet(writes []blockstm.WriteDescriptor) { continue } else { addr := path.GetAddress() + switch path.GetSubpath() { case BalancePath: s.SetBalance(addr, sr.GetBalance(addr)) @@ -453,6 +457,7 @@ func (s *StateDB) StartPrefetcher(namespace string) { s.prefetcher.close() s.prefetcher = nil } + if s.snap != nil { s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace) } @@ -497,6 +502,7 @@ func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common l.BlockNumber = blockNumber l.BlockHash = blockHash } + return logs } @@ -505,6 +511,7 @@ func (s *StateDB) Logs() []*types.Log { for _, lgs := range s.logs { logs = append(logs, lgs...) } + return logs } @@ -512,6 +519,7 @@ func (s *StateDB) Logs() []*types.Log { func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) { if _, ok := s.preimages[hash]; !ok { s.journal.append(addPreimageChange{hash: hash}) + pi := make([]byte, len(preimage)) copy(pi, preimage) s.preimages[hash] = pi @@ -533,9 +541,11 @@ func (s *StateDB) AddRefund(gas uint64) { // This method will panic if the refund counter goes below zero func (s *StateDB) SubRefund(gas uint64) { s.journal.append(refundChange{prev: s.refund}) + if gas > s.refund { panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund)) } + s.refund -= gas } @@ -606,6 +616,7 @@ func (s *StateDB) GetCode(addr common.Address) []byte { if stateObject != nil { return stateObject.Code(s.db) } + return nil }) } @@ -616,6 +627,7 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { if stateObject != nil { return stateObject.CodeSize(s.db) } + return 0 }) } @@ -626,6 +638,7 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { if stateObject == nil { return common.Hash{} } + return common.BytesToHash(stateObject.CodeHash()) }) } @@ -637,6 +650,7 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { if stateObject != nil { return stateObject.GetState(s.db, hash) } + return common.Hash{} }) } @@ -650,6 +664,7 @@ func (s *StateDB) GetProof(addr common.Address) ([][]byte, error) { func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) { var proof proofList err := s.trie.Prove(addrHash[:], 0, &proof) + return proof, err } @@ -659,6 +674,7 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, if err != nil { return nil, err } + if trie == nil { return nil, errors.New("storage trie for requested address does not exist") } @@ -680,6 +696,7 @@ func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) commo if stateObject != nil { return stateObject.GetCommittedState(s.db, hash) } + return common.Hash{} }) } @@ -698,11 +715,13 @@ func (s *StateDB) StorageTrie(addr common.Address) (Trie, error) { //nolint:nilnil return nil, nil } + cpy := stateObject.deepCopy(s) if _, err := cpy.updateTrie(s.db); err != nil { return nil, err } + return cpy.getTrie(s.db) } @@ -712,6 +731,7 @@ func (s *StateDB) HasSuicided(addr common.Address) bool { if stateObject != nil { return stateObject.suicided } + return false }) } @@ -903,6 +923,7 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { return obj } + return nil } @@ -918,16 +939,20 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { } // If no live objects are available, attempt to use snapshots var data *types.StateAccount + if s.snap != nil { // nolint start := time.Now() acc, err := s.snap.Account(crypto.HashData(crypto.NewKeccakState(), addr.Bytes())) + if metrics.EnabledExpensive { s.SnapshotAccountReads += time.Since(start) } + if err == nil { if acc == nil { return nil } + data = &types.StateAccount{ Nonce: acc.Nonce, Balance: acc.Balance, @@ -937,6 +962,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { if len(data.CodeHash) == 0 { data.CodeHash = types.EmptyCodeHash.Bytes() } + if data.Root == (common.Hash{}) { data.Root = types.EmptyRootHash } @@ -945,15 +971,19 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { // If snapshot unavailable or reading from it failed, load from the database if data == nil { start := time.Now() + var err error data, err = s.trie.GetAccount(addr) + if metrics.EnabledExpensive { s.AccountReads += time.Since(start) } + if err != nil { s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err)) return nil } + if data == nil { return nil } @@ -961,6 +991,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { // Insert into the live set obj := newObject(s, addr, *data) s.setStateObject(obj) + return obj }) } @@ -975,6 +1006,7 @@ func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject { if stateObject == nil { stateObject, _ = s.createObject(addr) } + return stateObject } @@ -1012,18 +1044,23 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) s.stateObjectsDestruct[prev.address] = struct{}{} } } + newobj = newObject(s, addr, types.StateAccount{}) + if prev == nil { s.journal.append(createObjectChange{account: &addr}) } else { s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) } + s.setStateObject(newobj) MVWrite(s, blockstm.NewAddressKey(addr)) + if prev != nil && !prev.deleted { return newobj, prev } + return newobj, nil } @@ -1065,6 +1102,7 @@ func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common. if !cb(key, value) { return nil } + continue } @@ -1073,11 +1111,13 @@ func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common. if err != nil { return err } + if !cb(key, common.BytesToHash(content)) { return nil } } } + return nil } @@ -1125,26 +1165,32 @@ func (s *StateDB) Copy() *StateDB { if _, exist := state.stateObjects[addr]; !exist { state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state) } + state.stateObjectsPending[addr] = struct{}{} } + for addr := range s.stateObjectsDirty { if _, exist := state.stateObjects[addr]; !exist { state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state) } + state.stateObjectsDirty[addr] = struct{}{} } // Deep copy the destruction flag. for addr := range s.stateObjectsDestruct { state.stateObjectsDestruct[addr] = struct{}{} } + for hash, logs := range s.logs { cpy := make([]*types.Log, len(logs)) for i, l := range logs { cpy[i] = new(types.Log) *cpy[i] = *l } + state.logs[hash] = cpy } + for hash, preimage := range s.preimages { state.preimages[hash] = preimage } @@ -1163,6 +1209,7 @@ 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 as well. @@ -1176,12 +1223,15 @@ func (s *StateDB) Copy() *StateDB { 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 } } @@ -1189,6 +1239,7 @@ func (s *StateDB) Copy() *StateDB { if s.mvHashmap != nil { state.mvHashmap = s.mvHashmap } + return state } @@ -1197,6 +1248,7 @@ func (s *StateDB) Snapshot() int { id := s.nextRevisionId s.nextRevisionId++ s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()}) + return id } @@ -1209,6 +1261,7 @@ func (s *StateDB) RevertToSnapshot(revid int) { if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid { panic(fmt.Errorf("revision id %v cannot be reverted", revid)) } + snapshot := s.validRevisions[idx].journalIndex // Replay the journal to undo changes and remove invalidated snapshots @@ -1226,6 +1279,7 @@ func (s *StateDB) GetRefund() uint64 { // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) + for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] if !exist { @@ -1237,6 +1291,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // Thus, we can safely ignore it here continue } + if obj.suicided || (deleteEmptyObjects && obj.empty()) { obj.deleted = true @@ -1255,6 +1310,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { } else { obj.finalise(true) // Prefetch slots in the background } + s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} @@ -1263,6 +1319,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // the commit-phase will be a lot faster addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure } + if s.prefetcher != nil && len(addressesToPrefetch) > 0 { s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch) } @@ -1309,7 +1366,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.trie = trie } } + usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) + for addr := range s.stateObjectsPending { if obj := s.stateObjects[addr]; obj.deleted { s.deleteStateObject(obj) @@ -1318,11 +1377,14 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.updateStateObject(obj) s.AccountUpdated += 1 } + usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure } + if prefetcher != nil { prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs) } + if len(s.stateObjectsPending) > 0 { s.stateObjectsPending = make(map[common.Address]struct{}) } @@ -1330,6 +1392,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if metrics.EnabledExpensive { defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) } + return s.trie.Hash() } @@ -1368,6 +1431,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { nodes = trie.NewMergedNodeSet() codeWriter = s.db.DiskDB().NewBatch() ) + for addr := range s.stateObjectsDirty { if obj := s.stateObjects[addr]; !obj.deleted { // Write any contract code associated with the state object @@ -1398,9 +1462,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { // and in path-based-scheme some technical challenges are still unsolved. // Although it won't affect the correctness but please fix it TODO(rjl493456442). } + if len(s.stateObjectsDirty) > 0 { s.stateObjectsDirty = make(map[common.Address]struct{}) } + if codeWriter.ValueSize() > 0 { if err := codeWriter.Write(); err != nil { log.Crit("Failed to commit dirty codes", "error", err) @@ -1421,6 +1487,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size() } + if metrics.EnabledExpensive { s.AccountCommits += time.Since(start) @@ -1432,6 +1499,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted)) storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated)) storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted)) + s.AccountUpdated, s.AccountDeleted = 0, 0 s.StorageUpdated, s.StorageDeleted = 0, 0 } @@ -1553,6 +1621,7 @@ func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { // Better safe than sorry, though s.journal.append(accessListAddAccountChange{&addr}) } + if slotMod { s.journal.append(accessListAddSlotChange{ address: &addr, diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index eca3110511..3823939231 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -49,9 +49,11 @@ func TestUpdateLeaks(t *testing.T) { addr := common.BytesToAddress([]byte{i}) state.AddBalance(addr, big.NewInt(int64(11*i))) state.SetNonce(addr, uint64(42*i)) + if i%2 == 0 { state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) } + if i%3 == 0 { state.SetCode(addr, []byte{i, i, i, i, i}) } @@ -82,10 +84,12 @@ func TestIntermediateLeaks(t *testing.T) { modify := func(state *StateDB, addr common.Address, i, tweak byte) { state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak))) state.SetNonce(addr, uint64(42*i+tweak)) + if i%2 == 0 { state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{}) state.SetState(addr, common.Hash{i, i, i, tweak}, common.Hash{i, i, i, i, tweak}) } + if i%3 == 0 { state.SetCode(addr, []byte{i, i, i, i, i, tweak}) } @@ -127,9 +131,11 @@ func TestIntermediateLeaks(t *testing.T) { for it.Next() { key, fvalue := it.Key(), it.Value() tvalue, err := transDb.Get(key) + if err != nil { t.Errorf("entry missing from the transition database: %x -> %x", key, fvalue) } + if !bytes.Equal(fvalue, tvalue) { t.Errorf("value mismatch at key %x: %x in transition database, %x in final database", key, tvalue, fvalue) } @@ -140,9 +146,11 @@ func TestIntermediateLeaks(t *testing.T) { for it.Next() { key, tvalue := it.Key(), it.Value() fvalue, err := finalDb.Get(key) + if err != nil { t.Errorf("extra entry in the transition database: %x -> %x", key, it.Value()) } + if !bytes.Equal(fvalue, tvalue) { t.Errorf("value mismatch at key %x: %x in transition database, %x in final database", key, tvalue, fvalue) } @@ -191,7 +199,9 @@ func TestCopy(t *testing.T) { } var wg sync.WaitGroup + wg.Add(3) + go finalise(&wg, orig) go finalise(&wg, copy) go finalise(&wg, ccopy) @@ -206,9 +216,11 @@ func TestCopy(t *testing.T) { if want := big.NewInt(3 * int64(i)); origObj.Balance().Cmp(want) != 0 { t.Errorf("orig obj %d: balance mismatch: have %v, want %v", i, origObj.Balance(), want) } + if want := big.NewInt(4 * int64(i)); copyObj.Balance().Cmp(want) != 0 { t.Errorf("copy obj %d: balance mismatch: have %v, want %v", i, copyObj.Balance(), want) } + if want := big.NewInt(5 * int64(i)); ccopyObj.Balance().Cmp(want) != 0 { t.Errorf("copy obj %d: balance mismatch: have %v, want %v", i, ccopyObj.Balance(), want) } @@ -217,6 +229,7 @@ func TestCopy(t *testing.T) { func TestSnapshotRandom(t *testing.T) { config := &quick.Config{MaxCount: 1000} + err := quick.Check((*snapshotTest).run, config) if cerr, ok := err.(*quick.CheckError); ok { test := cerr.In[0].(*snapshotTest) @@ -359,15 +372,20 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction { }, } action := actions[r.Intn(len(actions))] + var nameargs []string + if !action.noAddr { nameargs = append(nameargs, addr.Hex()) } + for i := range action.args { action.args[i] = rand.Int63n(100) nameargs = append(nameargs, fmt.Sprint(action.args[i])) } + action.name += strings.Join(nameargs, ", ") + return action } @@ -379,6 +397,7 @@ func (*snapshotTest) Generate(r *rand.Rand, size int) reflect.Value { for i := range addrs { addrs[i][0] = byte(i) } + actions := make([]testAction, size) for i := range actions { addr := addrs[r.Intn(len(addrs))] @@ -389,25 +408,32 @@ func (*snapshotTest) Generate(r *rand.Rand, size int) reflect.Value { if size > 0 && nsnapshots == 0 { nsnapshots = 1 } + snapshots := make([]int, nsnapshots) snaplen := len(actions) / nsnapshots + for i := range snapshots { // Try to place the snapshots some number of actions apart from each other. snapshots[i] = (i * snaplen) + r.Intn(snaplen) } + return reflect.ValueOf(&snapshotTest{addrs, actions, snapshots, nil}) } func (test *snapshotTest) String() string { out := new(bytes.Buffer) + sindex := 0 for i, action := range test.actions { if len(test.snapshots) > sindex && i == test.snapshots[sindex] { fmt.Fprintf(out, "---- snapshot %d ----\n", sindex) + sindex++ } + fmt.Fprintf(out, "%4d: %s\n", i, action.name) } + return out.String() } @@ -418,11 +444,13 @@ func (test *snapshotTest) run() bool { snapshotRevs = make([]int, len(test.snapshots)) sindex = 0 ) + for i, action := range test.actions { if len(test.snapshots) > sindex && i == test.snapshots[sindex] { snapshotRevs[sindex] = state.Snapshot() sindex++ } + action.fn(action, state) } // Revert all snapshots in reverse order. Each revert must yield a state @@ -432,12 +460,15 @@ func (test *snapshotTest) run() bool { for _, action := range test.actions[:test.snapshots[sindex]] { action.fn(action, checkstate) } + state.RevertToSnapshot(snapshotRevs[sindex]) + if err := test.checkEqual(state, checkstate); err != nil { test.err = fmt.Errorf("state mismatch after revert to snapshot %d\n%v", sindex, err) return false } } + return true } @@ -445,11 +476,13 @@ func (test *snapshotTest) run() bool { func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { for _, addr := range test.addrs { var err error + checkeq := func(op string, a, b interface{}) bool { if err == nil && !reflect.DeepEqual(a, b) { err = fmt.Errorf("got %s(%s) == %v, want %v", op, addr.Hex(), a, b) return false } + return true } // Check basic accessor methods. @@ -469,6 +502,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value) }) } + if err != nil { return err } @@ -483,6 +517,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v", state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{})) } + return nil } @@ -498,7 +533,9 @@ func TestTouchDelete(t *testing.T) { if len(s.state.journal.dirties) != 1 { t.Fatal("expected one dirty state object") } + s.state.RevertToSnapshot(snapshot) + if len(s.state.journal.dirties) != 0 { t.Fatal("expected no dirty state object") } @@ -965,6 +1002,7 @@ func TestCopyOfCopy(t *testing.T) { if got := state.Copy().GetBalance(addr).Uint64(); got != 42 { t.Fatalf("1st copy fail, expected 42, got %v", got) } + if got := state.Copy().Copy().GetBalance(addr).Uint64(); got != 42 { t.Fatalf("2nd copy fail, expected 42, got %v", got) } @@ -989,12 +1027,15 @@ func TestCopyCommitCopy(t *testing.T) { if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) } + if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) } + if val := state.GetState(addr, skey); val != sval { t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval) } + if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } @@ -1003,26 +1044,33 @@ func TestCopyCommitCopy(t *testing.T) { if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("first copy pre-commit balance mismatch: have %v, want %v", balance, 42) } + if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("first copy pre-commit code mismatch: have %x, want %x", code, []byte("hello")) } + if val := copyOne.GetState(addr, skey); val != sval { t.Fatalf("first copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval) } + if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } copyOne.Commit(false) + if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("first copy post-commit balance mismatch: have %v, want %v", balance, 42) } + if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("first copy post-commit code mismatch: have %x, want %x", code, []byte("hello")) } + if val := copyOne.GetState(addr, skey); val != sval { t.Fatalf("first copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval) } + if val := copyOne.GetCommittedState(addr, skey); val != sval { t.Fatalf("first copy post-commit committed storage slot mismatch: have %x, want %x", val, sval) } @@ -1031,12 +1079,15 @@ func TestCopyCommitCopy(t *testing.T) { if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("second copy balance mismatch: have %v, want %v", balance, 42) } + if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("second copy code mismatch: have %x, want %x", code, []byte("hello")) } + if val := copyTwo.GetState(addr, skey); val != sval { t.Fatalf("second copy non-committed storage slot mismatch: have %x, want %x", val, sval) } + if val := copyTwo.GetCommittedState(addr, skey); val != sval { t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval) } @@ -1061,12 +1112,15 @@ func TestCopyCopyCommitCopy(t *testing.T) { if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) } + if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) } + if val := state.GetState(addr, skey); val != sval { t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval) } + if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } @@ -1075,12 +1129,15 @@ func TestCopyCopyCommitCopy(t *testing.T) { if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("first copy balance mismatch: have %v, want %v", balance, 42) } + if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("first copy code mismatch: have %x, want %x", code, []byte("hello")) } + if val := copyOne.GetState(addr, skey); val != sval { t.Fatalf("first copy non-committed storage slot mismatch: have %x, want %x", val, sval) } + if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("first copy committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } @@ -1089,25 +1146,33 @@ func TestCopyCopyCommitCopy(t *testing.T) { if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("second copy pre-commit balance mismatch: have %v, want %v", balance, 42) } + if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("second copy pre-commit code mismatch: have %x, want %x", code, []byte("hello")) } + if val := copyTwo.GetState(addr, skey); val != sval { t.Fatalf("second copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval) } + if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } + copyTwo.Commit(false) + if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("second copy post-commit balance mismatch: have %v, want %v", balance, 42) } + if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("second copy post-commit code mismatch: have %x, want %x", code, []byte("hello")) } + if val := copyTwo.GetState(addr, skey); val != sval { t.Fatalf("second copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval) } + if val := copyTwo.GetCommittedState(addr, skey); val != sval { t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval) } @@ -1116,12 +1181,15 @@ func TestCopyCopyCommitCopy(t *testing.T) { if balance := copyThree.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("third copy balance mismatch: have %v, want %v", balance, 42) } + if code := copyThree.GetCode(addr); !bytes.Equal(code, []byte("hello")) { t.Fatalf("third copy code mismatch: have %x, want %x", code, []byte("hello")) } + if val := copyThree.GetState(addr, skey); val != sval { t.Fatalf("third copy non-committed storage slot mismatch: have %x, want %x", val, sval) } + if val := copyThree.GetCommittedState(addr, skey); val != sval { t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, sval) } @@ -1169,12 +1237,15 @@ func TestMissingTrieNodes(t *testing.T) { // Create an initial state with a few accounts memDb := rawdb.NewMemoryDatabase() db := NewDatabase(memDb) + var root common.Hash + state, _ := New(common.Hash{}, db, nil) addr := common.BytesToAddress([]byte("so")) { state.SetBalance(addr, big.NewInt(1)) state.SetCode(addr, []byte{1, 2, 3}) + a2 := common.BytesToAddress([]byte("another")) state.SetBalance(a2, big.NewInt(100)) state.SetCode(a2, []byte{1, 2, 4}) @@ -1195,6 +1266,7 @@ func TestMissingTrieNodes(t *testing.T) { memDb.Delete(k) } } + balance := state.GetBalance(addr) // The removed elem should lead to it returning zero balance if exp, got := uint64(0), balance.Uint64(); got != exp { @@ -1202,6 +1274,7 @@ func TestMissingTrieNodes(t *testing.T) { } // Modify the state state.SetBalance(addr, big.NewInt(2)) + root, err := state.Commit(false) if err == nil { t.Fatalf("expected error, got root :%x", root) @@ -1226,7 +1299,9 @@ func TestStateDBAccessList(t *testing.T) { t.Helper() // convert to common.Address form var addresses []common.Address + var addressMap = make(map[common.Address]struct{}) + for _, astring := range astrings { address := addr(astring) addresses = append(addresses, address) @@ -1249,10 +1324,13 @@ func TestStateDBAccessList(t *testing.T) { if !state.AddressInAccessList(addr(addrString)) { t.Fatalf("scope missing address/slots %v", addrString) } + var address = addr(addrString) // convert to common.Hash form var slots []common.Hash + var slotMap = make(map[common.Hash]struct{}) + for _, slotString := range slotStrings { s := slot(slotString) slots = append(slots, s) @@ -1284,6 +1362,7 @@ func TestStateDBAccessList(t *testing.T) { // Make a copy stateCopy1 := state.Copy() + if exp, got := 4, state.journal.length(); exp != got { t.Fatalf("journal length mismatch: have %d, want %d", got, exp) } @@ -1292,6 +1371,7 @@ func TestStateDBAccessList(t *testing.T) { state.AddSlotToAccessList(addr("bb"), slot("01")) state.AddSlotToAccessList(addr("bb"), slot("02")) state.AddAddressToAccessList(addr("aa")) + if exp, got := 4, state.journal.length(); exp != got { t.Fatalf("journal length mismatch: have %d, want %d", got, exp) } @@ -1300,6 +1380,7 @@ func TestStateDBAccessList(t *testing.T) { state.AddSlotToAccessList(addr("aa"), slot("01")) // 6 state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8 state.AddAddressToAccessList(addr("cc")) + if exp, got := 8, state.journal.length(); exp != got { t.Fatalf("journal length mismatch: have %d, want %d", got, exp) } @@ -1311,72 +1392,92 @@ func TestStateDBAccessList(t *testing.T) { // now start rolling back changes state.journal.revert(state, 7) + if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok { t.Fatalf("slot present, expected missing") } + verifyAddrs("aa", "bb", "cc") verifySlots("aa", "01") verifySlots("bb", "01", "02", "03") state.journal.revert(state, 6) + if state.AddressInAccessList(addr("cc")) { t.Fatalf("addr present, expected missing") } + verifyAddrs("aa", "bb") verifySlots("aa", "01") verifySlots("bb", "01", "02", "03") state.journal.revert(state, 5) + if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok { t.Fatalf("slot present, expected missing") } + verifyAddrs("aa", "bb") verifySlots("bb", "01", "02", "03") state.journal.revert(state, 4) + if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok { t.Fatalf("slot present, expected missing") } + verifyAddrs("aa", "bb") verifySlots("bb", "01", "02") state.journal.revert(state, 3) + if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok { t.Fatalf("slot present, expected missing") } + verifyAddrs("aa", "bb") verifySlots("bb", "01") state.journal.revert(state, 2) + if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok { t.Fatalf("slot present, expected missing") } + verifyAddrs("aa", "bb") state.journal.revert(state, 1) + if state.AddressInAccessList(addr("bb")) { t.Fatalf("addr present, expected missing") } + verifyAddrs("aa") state.journal.revert(state, 0) + if state.AddressInAccessList(addr("aa")) { t.Fatalf("addr present, expected missing") } + if got, exp := len(state.accessList.addresses), 0; got != exp { t.Fatalf("expected empty, got %d", got) } + if got, exp := len(state.accessList.slots), 0; got != exp { t.Fatalf("expected empty, got %d", got) } // Check the copy // Make a copy state = stateCopy1 + verifyAddrs("aa", "bb") verifySlots("bb", "01", "02") + if got, exp := len(state.accessList.addresses), 2; got != exp { t.Fatalf("expected empty, got %d", got) } + if got, exp := len(state.accessList.slots), 1; got != exp { t.Fatalf("expected empty, got %d", got) } diff --git a/core/state/sync.go b/core/state/sync.go index f3b70f08c4..9cd353e62c 100644 --- a/core/state/sync.go +++ b/core/state/sync.go @@ -46,6 +46,7 @@ func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(k return err } } + var obj types.StateAccount if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil { return err @@ -53,8 +54,10 @@ func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(k syncer.AddSubTrie(obj.Root, path, parent, parentPath, onSlot) syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), path, parent, parentPath) + return nil } syncer = trie.NewSync(root, database, onAccount, scheme) + return syncer } diff --git a/core/state/sync_test.go b/core/state/sync_test.go index e8c686c7e2..1e561fdc7e 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -47,6 +47,7 @@ func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) { // Fill it with some arbitrary data var accounts []*testAccount + for i := byte(0); i < 96; i++ { obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i})) acc := &testAccount{address: common.BytesToAddress([]byte{i})} @@ -61,15 +62,19 @@ func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) { obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i}) acc.code = []byte{i, i, i, i, i} } + if i%5 == 0 { for j := byte(0); j < 5; j++ { hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}) obj.SetState(sdb, hash, hash) } } + state.updateStateObject(obj) + accounts = append(accounts, acc) } + root, _ := state.Commit(false) // Return the generated state @@ -84,16 +89,20 @@ func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accou if err != nil { t.Fatalf("failed to create state trie at %x: %v", root, err) } + if err := checkStateConsistency(db, root); err != nil { t.Fatalf("inconsistent state trie at %x: %v", root, err) } + for i, acc := range accounts { if balance := state.GetBalance(acc.address); balance.Cmp(acc.balance) != 0 { t.Errorf("account %d: balance mismatch: have %v, want %v", i, balance, acc.balance) } + if nonce := state.GetNonce(acc.address); nonce != acc.nonce { t.Errorf("account %d: nonce mismatch: have %v, want %v", i, nonce, acc.nonce) } + if code := state.GetCode(acc.address); !bytes.Equal(code, acc.code) { t.Errorf("account %d: code mismatch: have %x, want %x", i, code, acc.code) } @@ -115,6 +124,7 @@ func checkTrieConsistency(db ethdb.Database, root common.Hash) error { it := t.NodeIterator(nil) for it.Next(true) { } + return it.Error() } @@ -124,13 +134,16 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error { if _, err := db.Get(root.Bytes()); err != nil { return nil // Consider a non existent state consistent. } + state, err := New(root, NewDatabase(db), nil) if err != nil { return err } + it := NewNodeIterator(state) for it.Next() { } + return it.Error } @@ -237,15 +250,19 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { if err := rlp.DecodeBytes(srcTrie.MustGet(node.syncPath[0]), &acc); err != nil { t.Fatalf("failed to decode account on path %x: %v", node.syncPath[0], err) } + id := trie.StorageTrieID(srcRoot, common.BytesToHash(node.syncPath[0]), acc.Root) + stTrie, err := trie.New(id, srcDb.TrieDB()) if err != nil { t.Fatalf("failed to retriev storage trie for path %x: %v", node.syncPath[1], err) } + data, _, err := stTrie.GetNode(node.syncPath[1]) if err != nil { t.Fatalf("failed to retrieve node data for path %x: %v", node.syncPath[1], err) } + nodeResults[i] = trie.NodeSyncResult{Path: node.path, Data: data} } } else { @@ -253,6 +270,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { if err != nil { t.Fatalf("failed to retrieve node data for key %v", []byte(node.path)) } + nodeResults[i] = trie.NodeSyncResult{Path: node.path, Data: data} } } @@ -268,10 +286,12 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { t.Errorf("failed to process result %v", err) } } + batch := dstDb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() paths, nodes, codes = sched.Missing(count) @@ -374,10 +394,12 @@ func TestIterativeDelayedStateSync(t *testing.T) { nodeProcessed = len(nodeResults) } + batch := dstDb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() paths, nodes, codes = sched.Missing(0) @@ -477,6 +499,7 @@ func testIterativeRandomStateSync(t *testing.T, count int) { if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() nodeQueue = make(map[string]stateElement) @@ -577,10 +600,12 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { } } } + batch := dstDb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() paths, nodes, codes := sched.Missing(0) @@ -608,6 +633,7 @@ func TestIncompleteStateSync(t *testing.T) { // 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{}{} @@ -692,10 +718,12 @@ func TestIncompleteStateSync(t *testing.T) { } } } + batch := dstDb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() for _, root := range nodehashes { diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index a227aff0bb..c5f1bf1687 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -68,6 +68,7 @@ func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePre storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil), storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil), } + return p } @@ -86,6 +87,7 @@ func (p *triePrefetcher) close() { for _, key := range fetcher.used { delete(fetcher.seen, string(key)) } + p.accountWasteMeter.Mark(int64(len(fetcher.seen))) } else { p.storageLoadMeter.Mark(int64(len(fetcher.seen))) @@ -95,6 +97,7 @@ func (p *triePrefetcher) close() { for _, key := range fetcher.used { delete(fetcher.seen, string(key)) } + p.storageWasteMeter.Mark(int64(len(fetcher.seen))) } } @@ -129,14 +132,17 @@ func (p *triePrefetcher) copy() *triePrefetcher { if fetch == nil { continue } + copy.fetches[root] = p.db.CopyTrie(fetch) } + return copy } // Otherwise we're copying an active fetcher, retrieve the current states for id, fetcher := range p.fetchers { copy.fetches[id] = fetcher.peek() } + return copy } @@ -148,11 +154,13 @@ func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr comm } // Active fetcher, schedule the retrievals id := p.trieID(owner, root) + fetcher := p.fetchers[id] if fetcher == nil { fetcher = newSubfetcher(p.db, p.root, owner, root, addr) p.fetchers[id] = fetcher } + fetcher.schedule(keys) } @@ -167,6 +175,7 @@ func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie { p.deliveryMissMeter.Mark(1) return nil } + return p.db.CopyTrie(trie) } // Otherwise the prefetcher is active, bail if no trie was prefetched for this root @@ -184,6 +193,7 @@ func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie { p.deliveryMissMeter.Mark(1) return nil } + return trie } @@ -241,6 +251,7 @@ func newSubfetcher(db Database, state common.Hash, owner common.Hash, root commo seen: make(map[string]struct{}), } go sf.loop() + return sf } @@ -272,6 +283,7 @@ func (sf *subfetcher) peek() Trie { if sf.trie == nil { return nil } + return sf.db.CopyTrie(sf.trie) } } @@ -308,6 +320,7 @@ func (sf *subfetcher) loop() { log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) return } + sf.trie = trie } // Trie opened successfully, keep prefetching items @@ -328,6 +341,7 @@ func (sf *subfetcher) loop() { sf.lock.Lock() sf.tasks = append(sf.tasks, tasks[i:]...) sf.lock.Unlock() + return case ch := <-sf.copy: @@ -344,6 +358,7 @@ func (sf *subfetcher) loop() { } else { sf.trie.GetStorage(sf.addr, task) } + sf.seen[string(task)] = struct{}{} } } diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go index e261e8a5ee..356b8830fb 100644 --- a/core/state/trie_prefetcher_test.go +++ b/core/state/trie_prefetcher_test.go @@ -36,10 +36,12 @@ func filledStateDB() *StateDB { state.SetBalance(addr, big.NewInt(42)) // Change the account trie state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetState(addr, skey, sval) // Change the storage trie + for i := 0; i < 100; i++ { sk := common.BigToHash(big.NewInt(int64(i))) state.SetState(addr, sk, sk) // Change the storage trie } + return state } @@ -58,12 +60,16 @@ func TestCopyAndClose(t *testing.T) { cpy.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}) cpy.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}) c := cpy.trie(common.Hash{}, db.originalRoot) + prefetcher.close() + cpy2 := cpy.copy() cpy2.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}) d := cpy2.trie(common.Hash{}, db.originalRoot) + cpy.close() cpy2.close() + if a.Hash() != b.Hash() || a.Hash() != c.Hash() || a.Hash() != d.Hash() { t.Fatalf("Invalid trie, hashes should be equal: %v %v %v %v", a.Hash(), b.Hash(), c.Hash(), d.Hash()) } @@ -77,9 +83,11 @@ func TestUseAfterClose(t *testing.T) { a := prefetcher.trie(common.Hash{}, db.originalRoot) prefetcher.close() b := prefetcher.trie(common.Hash{}, db.originalRoot) + if a == nil { t.Fatal("Prefetching before close should not return nil") } + if b != nil { t.Fatal("Trie after close should return nil") } @@ -93,18 +101,23 @@ func TestCopyClose(t *testing.T) { cpy := prefetcher.copy() a := prefetcher.trie(common.Hash{}, db.originalRoot) b := cpy.trie(common.Hash{}, db.originalRoot) + prefetcher.close() c := prefetcher.trie(common.Hash{}, db.originalRoot) d := cpy.trie(common.Hash{}, db.originalRoot) + if a == nil { t.Fatal("Prefetching before close should not return nil") } + if b == nil { t.Fatal("Copy trie should return nil") } + if c != nil { t.Fatal("Trie after close should return nil") } + if d == nil { t.Fatal("Copy trie should not return nil") } diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index cbbefa53a1..381152e89a 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -58,6 +58,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c ) // Iterate over and process the individual transactions byzantium := p.config.IsByzantium(block.Number()) + for i, tx := range block.Transactions() { // If block precaching was interrupted, abort if interrupt != nil && interrupt.Load() { @@ -70,6 +71,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c } statedb.SetTxContext(tx.Hash(), i) + if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { return // Ugh, something went horribly wrong, bail out } @@ -92,5 +94,6 @@ func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasP evm.Reset(NewEVMTxContext(msg), statedb) // Add addresses to access list if applicable _, err := ApplyMessage(evm, msg, gaspool, context.Background()) + return err } diff --git a/core/state_processor.go b/core/state_processor.go index 1aba120edd..42af6dc037 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -71,6 +71,7 @@ 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 @@ -89,10 +90,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } statedb.SetTxContext(tx.Hash(), i) + receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, interruptCtx) if err != nil { 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...) } @@ -165,11 +168,13 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta // Update the state with pending changes. var root []byte + if config.IsByzantium(blockNumber) { statedb.Finalise(true) } else { root = statedb.IntermediateRoot(config.IsEIP158(blockNumber)).Bytes() } + *usedGas += result.UsedGas // Create a new receipt for the transaction, storing the intermediate root and gas used @@ -180,6 +185,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta } else { receipt.Status = types.ReceiptStatusSuccessful } + receipt.TxHash = tx.Hash() receipt.GasUsed = result.UsedGas @@ -194,6 +200,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta receipt.BlockHash = blockHash receipt.BlockNumber = blockNumber receipt.TransactionIndex = uint(statedb.TxIndex()) + return receipt, err } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 0a368f8c36..5fca0ba801 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -65,10 +65,12 @@ func TestStateProcessorErrors(t *testing.T) { key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("0202020202020202020202020202020202020202020202020202002020202020") ) + var makeTx = func(key *ecdsa.PrivateKey, 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, key) return tx } + var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction { tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ Nonce: nonce, @@ -110,10 +112,13 @@ func TestStateProcessorErrors(t *testing.T) { } blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) ) + defer blockchain.Stop() + bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) tooBigNumber := new(big.Int).Set(bigNumber) tooBigNumber.Add(tooBigNumber, common.Big1) + for i, tt := range []struct { txs []*types.Transaction want string @@ -214,10 +219,12 @@ func TestStateProcessorErrors(t *testing.T) { }, } { block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) + _, 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) } @@ -251,6 +258,7 @@ func TestStateProcessorErrors(t *testing.T) { blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) parallelBlockchain, _ = NewParallelBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil) ) + defer blockchain.Stop() defer parallelBlockchain.Stop() @@ -267,10 +275,12 @@ func TestStateProcessorErrors(t *testing.T) { }, } { block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) + _, err := bc.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) } @@ -295,6 +305,7 @@ func TestStateProcessorErrors(t *testing.T) { blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) parallelBlockchain, _ = NewParallelBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{ParallelEnable: true, ParallelSpeculativeProcesses: 8}, nil, nil, nil) ) + defer blockchain.Stop() defer parallelBlockchain.Stop() @@ -311,10 +322,12 @@ func TestStateProcessorErrors(t *testing.T) { }, } { block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) + _, err := bc.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) } @@ -361,11 +374,11 @@ func TestStateProcessorErrors(t *testing.T) { tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} smallInitCode = [320]byte{} ) + defer blockchain.Stop() defer parallelBlockchain.Stop() for _, bc := range []*BlockChain{blockchain, parallelBlockchain} { - for i, tt := range []struct { txs []*types.Transaction want string @@ -384,10 +397,12 @@ func TestStateProcessorErrors(t *testing.T) { }, } { block := GenerateBadBlock(genesis, beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) + _, err := bc.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) } @@ -427,26 +442,32 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr if config.IsShanghai(header.Time) { header.WithdrawalsHash = &types.EmptyWithdrawalsHash } + 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 // TODO marcello double check if config.IsShanghai(header.Time) { return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil)) } + return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)) } diff --git a/core/state_transition.go b/core/state_transition.go index b26259e37a..b8ac74fd8b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -56,6 +56,7 @@ func (result *ExecutionResult) Return() []byte { if result.Err != nil { return nil } + return common.CopyBytes(result.ReturnData) } @@ -65,6 +66,7 @@ func (result *ExecutionResult) Revert() []byte { if result.Err != vm.ErrExecutionReverted { return nil } + return common.CopyBytes(result.ReturnData) } @@ -83,6 +85,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b if dataLen > 0 { // Zero and non-zero bytes are priced differently var nz uint64 + for _, byt := range data { if byt != 0 { nz++ @@ -93,15 +96,18 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b if isEIP2028 { nonZeroGas = params.TxDataNonZeroGasEIP2028 } + if (math.MaxUint64-gas)/nonZeroGas < nz { return 0, ErrGasUintOverflow } + gas += nz * nonZeroGas z := dataLen - nz if (math.MaxUint64-gas)/params.TxDataZeroGas < z { return 0, ErrGasUintOverflow } + gas += z * params.TxDataZeroGas if isContractCreation && isEIP3860 { @@ -113,10 +119,12 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b gas += lenWords * params.InitCodeWordGas } } + if accessList != nil { gas += uint64(len(accessList)) * params.TxAccessListAddressGas gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas } + return gas, nil } @@ -270,6 +278,7 @@ func (st *StateTransition) buyGas() error { st.initialGas = st.msg.GasLimit st.state.SubBalance(st.msg.From, mgval) + return nil } @@ -323,6 +332,7 @@ func (st *StateTransition) preCheck() error { } } } + return st.buyGas() } @@ -406,6 +416,7 @@ func (st *StateTransition) TransitionDb(interruptCtx context.Context) (*Executio ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err ) + if contractCreation { // nolint : contextcheck ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, msg.Value) diff --git a/core/tests/blockchain_repair_test.go b/core/tests/blockchain_repair_test.go index ad776fd56c..40b82b8843 100644 --- a/core/tests/blockchain_repair_test.go +++ b/core/tests/blockchain_repair_test.go @@ -1847,6 +1847,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { if _, err := back.BlockChain().InsertChain(canonblocks[:tt.commitBlock]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } + if tt.commitBlock > 0 { err = back.BlockChain().StateCache().TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true) if err != nil { @@ -1955,7 +1956,6 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { func TestIssue23496(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)))) - // Create a temporary persistent database datadir := t.TempDir() @@ -1967,6 +1967,7 @@ func TestIssue23496(t *testing.T) { 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 @@ -2018,6 +2019,7 @@ func TestIssue23496(t *testing.T) { if _, err := chain.InsertChain(blocks[2:3]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } + chain.StateCache().TrieDB().Commit(blocks[2].Root(), false) // Insert the remaining blocks @@ -2078,6 +2080,7 @@ func TestIssue23496(t *testing.T) { if head := chain.CurrentBlock(); head.Number.Uint64() != uint64(4) { t.Errorf("Head block mismatch: have %d, want %d", head.Number, uint64(4)) } + if layer := chain.Snapshots().Snapshot(blocks[2].Root()); layer == nil { t.Error("Failed to regenerate the snapshot of known state") } diff --git a/core/tests/blockchain_sethead_test.go b/core/tests/blockchain_sethead_test.go index 6ac8b9b3c2..9f9c730d18 100644 --- a/core/tests/blockchain_sethead_test.go +++ b/core/tests/blockchain_sethead_test.go @@ -57,21 +57,26 @@ func (tt *rewindTest) dump(crash bool) string { buffer := new(strings.Builder) fmt.Fprint(buffer, "Chain:\n G") + for i := 0; i < tt.canonicalBlocks; i++ { fmt.Fprintf(buffer, "->C%d", i+1) } fmt.Fprint(buffer, " (HEAD)\n") + if tt.sidechainBlocks > 0 { fmt.Fprintf(buffer, " └") + for i := 0; i < tt.sidechainBlocks; i++ { fmt.Fprintf(buffer, "->S%d", i+1) } fmt.Fprintf(buffer, "\n") } + fmt.Fprintf(buffer, "\n") if tt.canonicalBlocks > int(tt.freezeThreshold) { fmt.Fprint(buffer, "Frozen:\n G") + for i := 0; i < tt.canonicalBlocks-int(tt.freezeThreshold); i++ { fmt.Fprintf(buffer, "->C%d", i+1) } @@ -79,10 +84,13 @@ func (tt *rewindTest) dump(crash bool) string { } else { fmt.Fprintf(buffer, "Frozen: none\n") } + fmt.Fprintf(buffer, "Commit: G") + if tt.commitBlock > 0 { fmt.Fprintf(buffer, ", C%d", tt.commitBlock) } + fmt.Fprint(buffer, "\n") if tt.pivotBlock == nil { @@ -90,31 +98,38 @@ func (tt *rewindTest) dump(crash bool) string { } else { fmt.Fprintf(buffer, "Pivot : C%d\n", *tt.pivotBlock) } + if crash { fmt.Fprintf(buffer, "\nCRASH\n\n") } else { fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", tt.setheadBlock) } + fmt.Fprintf(buffer, "------------------------------\n\n") if tt.expFrozen > 0 { fmt.Fprint(buffer, "Expected in freezer:\n G") + for i := 0; i < tt.expFrozen-1; i++ { fmt.Fprintf(buffer, "->C%d", i+1) } fmt.Fprintf(buffer, "\n\n") } + if tt.expFrozen > 0 { if tt.expFrozen >= tt.expCanonicalBlocks { fmt.Fprintf(buffer, "Expected in leveldb: none\n") } else { fmt.Fprintf(buffer, "Expected in leveldb:\n C%d)", tt.expFrozen-1) + for i := tt.expFrozen - 1; i < tt.expCanonicalBlocks; i++ { fmt.Fprintf(buffer, "->C%d", i+1) } fmt.Fprint(buffer, "\n") + if tt.expSidechainBlocks > tt.expFrozen { fmt.Fprintf(buffer, " └") + for i := tt.expFrozen - 1; i < tt.expSidechainBlocks; i++ { fmt.Fprintf(buffer, "->S%d", i+1) } @@ -123,26 +138,32 @@ func (tt *rewindTest) dump(crash bool) string { } } else { fmt.Fprint(buffer, "Expected in leveldb:\n G") + for i := tt.expFrozen; i < tt.expCanonicalBlocks; i++ { fmt.Fprintf(buffer, "->C%d", i+1) } fmt.Fprint(buffer, "\n") + if tt.expSidechainBlocks > tt.expFrozen { fmt.Fprintf(buffer, " └") + for i := tt.expFrozen; i < tt.expSidechainBlocks; i++ { fmt.Fprintf(buffer, "->S%d", i+1) } fmt.Fprintf(buffer, "\n") } } + fmt.Fprintf(buffer, "\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 block : G\n") } else { fmt.Fprintf(buffer, "Expected head block : C%d\n", tt.expHeadBlock) } + return buffer.String() } @@ -1953,7 +1974,6 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { // 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(false)) - // Create a temporary persistent database datadir := t.TempDir() @@ -1980,6 +2000,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { SnapshotLimit: 0, // Disable snapshot } ) + if snapshots { config.SnapshotLimit = 256 config.SnapshotWait = true @@ -2010,6 +2031,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } + if tt.commitBlock > 0 { err = chain.StateCache().TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true) if err != nil { @@ -2022,6 +2044,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { } } } + if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil { t.Fatalf("Failed to import canonical chain tail: %v", err) } @@ -2029,6 +2052,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { for _, block := range sideblocks { chain.StateCache().TrieDB().Dereference(block.Root()) } + for _, block := range canonblocks { chain.StateCache().TrieDB().Dereference(block.Root()) } @@ -2037,6 +2061,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { Freeze(threshold uint64) error Ancients() (uint64, error) } + db.(freezer).Freeze(tt.freezeThreshold) // Set the simulated pivot block @@ -2063,6 +2088,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { if head := chain.CurrentBlock(); head.Number.Uint64() != tt.expHeadBlock { t.Errorf("Head block mismatch: have %d, want %d", head.Number, tt.expHeadBlock) } + if frozen, err := db.(freezer).Ancients(); err != nil { t.Errorf("Failed to retrieve ancient count: %v\n", err) } else if int(frozen) != tt.expFrozen { @@ -2078,47 +2104,58 @@ func verifyNoGaps(t *testing.T, chain *core.BlockChain, canonical bool, inserted t.Helper() var end uint64 + for i := uint64(0); i <= uint64(len(inserted)); i++ { header := chain.GetHeaderByNumber(i) if header == nil && end == 0 { end = i } + if header != nil && end > 0 { if canonical { t.Errorf("Canonical header gap between #%d-#%d", end, i-1) } else { t.Errorf("Sidechain header gap between #%d-#%d", end, i-1) } + end = 0 // Reset for further gap detection } } + end = 0 + for i := uint64(0); i <= uint64(len(inserted)); i++ { block := chain.GetBlockByNumber(i) if block == nil && end == 0 { end = i } + if block != nil && end > 0 { if canonical { t.Errorf("Canonical block gap between #%d-#%d", end, i-1) } else { t.Errorf("Sidechain block gap between #%d-#%d", end, i-1) } + end = 0 // Reset for further gap detection } } + end = 0 + for i := uint64(1); i <= uint64(len(inserted)); i++ { receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()) if receipts == nil && end == 0 { end = i } + if receipts != nil && end > 0 { if canonical { t.Errorf("Canonical receipt gap between #%d-#%d", end, i-1) } else { t.Errorf("Sidechain receipt gap between #%d-#%d", end, i-1) } + end = 0 // Reset for further gap detection } } @@ -2140,6 +2177,7 @@ func verifyCutoff(t *testing.T, chain *core.BlockChain, canonical bool, inserted t.Errorf("Sidechain header #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) } } + if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block == nil { if canonical { t.Errorf("Canonical block #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) @@ -2147,6 +2185,7 @@ func verifyCutoff(t *testing.T, chain *core.BlockChain, canonical bool, inserted t.Errorf("Sidechain block #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) } } + if receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()); receipts == nil { if canonical { t.Errorf("Canonical receipts #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) @@ -2162,6 +2201,7 @@ func verifyCutoff(t *testing.T, chain *core.BlockChain, canonical bool, inserted t.Errorf("Sidechain header #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) } } + if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block != nil { if canonical { t.Errorf("Canonical block #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) @@ -2169,6 +2209,7 @@ func verifyCutoff(t *testing.T, chain *core.BlockChain, canonical bool, inserted t.Errorf("Sidechain block #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) } } + if receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()); receipts != nil { if canonical { t.Errorf("Canonical receipts #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) diff --git a/core/tests/blockchain_snapshot_test.go b/core/tests/blockchain_snapshot_test.go index fe87b7e227..30ae6684a9 100644 --- a/core/tests/blockchain_snapshot_test.go +++ b/core/tests/blockchain_snapshot_test.go @@ -99,11 +99,13 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*core.BlockChain, []*type } 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 { @@ -128,6 +130,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*core.BlockChain, []*type } } } + if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { t.Fatalf("Failed to import canonical chain tail: %v", err) } @@ -180,21 +183,26 @@ func (basic *snapshotTestBasic) dump() string { buffer := new(strings.Builder) fmt.Fprint(buffer, "Chain:\n G") + 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 basic.commitBlock > 0 { fmt.Fprintf(buffer, ", C%d", basic.commitBlock) } + fmt.Fprint(buffer, "\n") fmt.Fprintf(buffer, "Snapshot: G") + if basic.snapshotBlock > 0 { fmt.Fprintf(buffer, ", C%d", basic.snapshotBlock) } + fmt.Fprint(buffer, "\n") //if crash { @@ -205,22 +213,26 @@ func (basic *snapshotTestBasic) dump() string { fmt.Fprintf(buffer, "------------------------------\n\n") fmt.Fprint(buffer, "Expected in leveldb:\n G") + 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", 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", basic.expHeadBlock) } + if basic.expSnapshotBottom == 0 { fmt.Fprintf(buffer, "Expected snapshot disk : G\n") } else { fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", basic.expSnapshotBottom) } + return buffer.String() } @@ -279,6 +291,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { 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 @@ -289,6 +302,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } + newchain.Stop() newchain, err = core.NewBlockChain(newdb, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil, nil) @@ -333,6 +347,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } + newchain.InsertChain(gappedBlocks) newchain.Stop() @@ -400,6 +415,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, } + newchain, err := core.NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) @@ -417,6 +433,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { SnapshotLimit: 256, SnapshotWait: false, // Don't wait rebuild } + _, err = core.NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) diff --git a/core/txpool/journal.go b/core/txpool/journal.go index 1b330b0c3c..18781c3a7d 100644 --- a/core/txpool/journal.go +++ b/core/txpool/journal.go @@ -64,9 +64,11 @@ func (journal *journal) load(add func([]*types.Transaction) []error) error { // Skip the parsing if the journal file doesn't exist at all return nil } + if err != nil { return err } + defer input.Close() // Temporarily discard any journal additions (don't double add on load) @@ -84,14 +86,17 @@ func (journal *journal) load(add func([]*types.Transaction) []error) error { for _, err := range add(txs) { if err != nil { log.Debug("Failed to add journaled transaction", "err", err) + dropped++ } } } + var ( failure error batch types.Transactions ) + for { // Parse the next transaction and terminate on error tx := new(types.Transaction) @@ -99,9 +104,11 @@ func (journal *journal) load(add func([]*types.Transaction) []error) error { if err != io.EOF { failure = err } + if batch.Len() > 0 { loadBatch(batch) } + break } // New transaction parsed, queue up for later, import if threshold is reached @@ -122,9 +129,11 @@ func (journal *journal) insert(tx *types.Transaction) error { if journal.writer == nil { return errNoActiveJournal } + if err := rlp.Encode(journal.writer, tx); err != nil { return err } + return nil } @@ -136,6 +145,7 @@ func (journal *journal) rotate(all map[common.Address]types.Transactions) error if err := journal.writer.Close(); err != nil { return err } + journal.writer = nil } // Generate a new journal with the contents of the current pool @@ -143,7 +153,9 @@ func (journal *journal) rotate(all map[common.Address]types.Transactions) error if err != nil { return err } + journaled := 0 + for _, txs := range all { for _, tx := range txs { if err = rlp.Encode(replacement, tx); err != nil { @@ -151,19 +163,24 @@ func (journal *journal) rotate(all map[common.Address]types.Transactions) error return err } } + journaled += len(txs) } + replacement.Close() // Replace the live journal with the newly generated one if err = os.Rename(journal.path+".new", journal.path); err != nil { return err } + sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return err } + journal.writer = sink + log.Info("Regenerated local transaction journal", "transactions", journaled, "accounts", len(all)) return nil @@ -177,5 +194,6 @@ func (journal *journal) close() error { err = journal.writer.Close() journal.writer = nil } + return err } diff --git a/core/txpool/list.go b/core/txpool/list.go index 5923ec6406..f647a68f06 100644 --- a/core/txpool/list.go +++ b/core/txpool/list.go @@ -51,6 +51,7 @@ func (h *nonceHeap) Pop() interface{} { x := old[n-1] old[n-1] = 0 *h = old[0 : n-1] + return x } @@ -133,6 +134,7 @@ func (m *sortedMap) Forward(threshold uint64) types.Transactions { m.cacheMu.Lock() if m.cache != nil { hitCacheCounter.Inc(1) + m.cache = m.cache[len(removed):] } m.cacheMu.Unlock() @@ -154,6 +156,7 @@ func (m *sortedMap) Filter(filter func(*types.Transaction) bool) types.Transacti if len(removed) > 0 { m.reheap(false) } + return removed } @@ -202,9 +205,11 @@ func (m *sortedMap) filter(filter func(*types.Transaction) bool) types.Transacti for nonce, tx := range m.items { if filter(tx) { removed = append(removed, tx) + delete(m.items, nonce) } } + if len(removed) > 0 { m.cacheMu.Lock() m.cache = nil @@ -213,6 +218,7 @@ func (m *sortedMap) filter(filter func(*types.Transaction) bool) types.Transacti resetCacheGauge.Inc(1) } + return removed } @@ -414,7 +420,6 @@ func (m *sortedMap) Flatten() types.Transactions { // transaction with the highest nonce func (m *sortedMap) LastElement() *types.Transaction { return m.lastElement() - } // list is a "list" of transactions belonging to an account, sorted by account @@ -534,6 +539,7 @@ func (l *list) Filter(costLimit *uint256.Int, gasLimit uint64) (types.Transactio if len(removed) == 0 { return nil, nil } + var invalids types.Transactions // If the list was strict, filter anything above the lowest nonce if l.strict { @@ -584,6 +590,7 @@ func (l *list) Remove(tx *types.Transaction) (bool, types.Transactions) { return true, txs } + return true, nil } @@ -694,6 +701,7 @@ func (h *priceHeap) Pop() interface{} { x := old[n-1] old[n-1] = nil h.list = old[0 : n-1] + return x } @@ -771,8 +779,10 @@ func (l *pricedList) underpricedFor(h *priceHeap, tx *types.Transaction) bool { if l.all.GetRemote(head.Hash()) == nil { // Removed or migrated l.stales.Add(-1) heap.Pop(h) + continue } + break } // Check if the transaction is underpriced or not @@ -791,6 +801,7 @@ func (l *pricedList) underpricedFor(h *priceHeap, tx *types.Transaction) bool { // Note local transaction won't be considered for eviction. func (l *pricedList) Discard(slots int, force bool) (types.Transactions, bool) { drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop + for slots > 0 { if len(l.urgent.list)*floatingRatio > len(l.floating.list)*urgentRatio || floatingRatio == 0 { // Discard stale transactions if found during cleanup @@ -822,8 +833,10 @@ func (l *pricedList) Discard(slots int, force bool) (types.Transactions, bool) { for _, tx := range drop { heap.Push(&l.urgent, tx) } + return nil, false } + return drop, true } @@ -831,7 +844,9 @@ func (l *pricedList) Discard(slots int, force bool) (types.Transactions, bool) { func (l *pricedList) Reheap() { l.reheapMu.Lock() defer l.reheapMu.Unlock() + start := time.Now() + l.stales.Store(0) l.urgent.list = make([]*types.Transaction, 0, l.all.RemoteCount()) l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { @@ -847,6 +862,7 @@ func (l *pricedList) Reheap() { // if the floating queue was empty. floatingCount := len(l.urgent.list) * floatingRatio / (urgentRatio + floatingRatio) l.floating.list = make([]*types.Transaction, floatingCount) + for i := 0; i < floatingCount; i++ { l.floating.list[i] = heap.Pop(&l.urgent).(*types.Transaction) } diff --git a/core/txpool/list_test.go b/core/txpool/list_test.go index 46c94357c1..11032e09bb 100644 --- a/core/txpool/list_test.go +++ b/core/txpool/list_test.go @@ -48,6 +48,7 @@ func TestStrictListAdd(t *testing.T) { if len(list.txs.items) != len(txs) { t.Errorf("transaction count mismatch: have %d, want %d", len(list.txs.items), len(txs)) } + for i, tx := range txs { if list.txs.items[tx.Nonce()] != tx { t.Errorf("item %d: transaction mismatch: have %v, want %v", i, list.txs.items[tx.Nonce()], tx) @@ -65,6 +66,7 @@ func BenchmarkListAdd(b *testing.B) { } // Insert the transactions in a random order priceLimit := big.NewInt(int64(DefaultConfig.PriceLimit)) + b.ResetTimer() b.ReportAllocs() diff --git a/core/txpool/noncer.go b/core/txpool/noncer.go index ba7fbedad5..c540e2a019 100644 --- a/core/txpool/noncer.go +++ b/core/txpool/noncer.go @@ -53,6 +53,7 @@ func (txn *noncer) get(addr common.Address) uint64 { txn.nonces[addr] = nonce } } + return txn.nonces[addr] } @@ -76,9 +77,11 @@ func (txn *noncer) setIfLower(addr common.Address, nonce uint64) { txn.nonces[addr] = nonce } } + if txn.nonces[addr] <= nonce { return } + txn.nonces[addr] = nonce } diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index dd7874cf83..33f17cc20f 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -216,34 +216,42 @@ func (config *Config) sanitize() Config { log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second) conf.Rejournal = time.Second } + if conf.PriceLimit < 1 { log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultConfig.PriceLimit) conf.PriceLimit = DefaultConfig.PriceLimit } + if conf.PriceBump < 1 { log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump) conf.PriceBump = DefaultConfig.PriceBump } + if conf.AccountSlots < 1 { log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultConfig.AccountSlots) conf.AccountSlots = DefaultConfig.AccountSlots } + if conf.GlobalSlots < 1 { log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultConfig.GlobalSlots) conf.GlobalSlots = DefaultConfig.GlobalSlots } + if conf.AccountQueue < 1 { log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultConfig.AccountQueue) conf.AccountQueue = DefaultConfig.AccountQueue } + if conf.GlobalQueue < 1 { log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue) conf.GlobalQueue = DefaultConfig.GlobalQueue } + if conf.Lifetime < 1 { log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime) conf.Lifetime = DefaultConfig.Lifetime } + return conf } @@ -333,10 +341,12 @@ func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain, } pool.locals = newAccountSet(pool.signer) + for _, addr := range config.Locals { log.Info("Setting new local account", "address", addr) pool.locals.add(addr) } + pool.priced = newPricedList(pool.all) pool.reset(nil, chain.CurrentBlock()) @@ -356,6 +366,7 @@ func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain, if err := pool.journal.load(pool.AddLocals); err != nil { log.Warn("Failed to load transaction journal", "err", err) } + if err := pool.journal.rotate(pool.local()); err != nil { log.Warn("Failed to rotate transaction journal", "err", err) } @@ -364,6 +375,7 @@ func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain, // Subscribe events from blockchain and start the main event loop. pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh) pool.wg.Add(1) + go pool.loop() return pool @@ -384,12 +396,14 @@ func (pool *TxPool) loop() { // Track the previous head headers for transaction reorgs head = pool.chain.CurrentBlock() ) + defer report.Stop() defer evict.Stop() defer journal.Stop() // Notify tests that the init phase is done close(pool.initDoneCh) + for { select { // Handle ChainHeadEvent @@ -482,6 +496,7 @@ func (pool *TxPool) Stop() { if pool.journal != nil { pool.journal.close() } + log.Info("Transaction pool stopped") } @@ -586,6 +601,7 @@ func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common defer pool.mu.Unlock() pool.pendingMu.RLock() + pending := make(map[common.Address]types.Transactions, len(pool.pending)) for addr, list := range pool.pending { pending[addr] = list.Flatten() @@ -858,11 +874,13 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { // Deduct the cost of a transaction replaced by this sum.Sub(sum, repl.Cost()) } + if balance.Cmp(sum) < 0 { log.Trace("Replacing transactions would overdraft", "sender", from, "balance", pool.currentState.GetBalance(from), "required", sum) return ErrOverdraft } } + return nil } @@ -879,6 +897,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if pool.all.Get(hash) != nil { log.Trace("Discarding already known transaction", "hash", hash) knownTxMeter.Mark(1) + return false, ErrAlreadyKnown } // Make the local flag. If it's from local source or it's from the network but @@ -889,6 +908,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if err := pool.validateTx(tx, isLocal); err != nil { log.Trace("Discarding invalid transaction", "hash", hash, "err", err) invalidTxMeter.Mark(1) + return false, err } @@ -901,6 +921,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if !isLocal && pool.priced.Underpriced(tx) { log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCapUint(), "gasFeeCap", tx.GasFeeCapUint()) underpricedTxMeter.Mark(1) + return false, ErrUnderpriced } @@ -922,6 +943,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if !isLocal && !success { log.Trace("Discarding overflown transaction", "hash", hash) overflowedTxMeter.Mark(1) + return false, ErrTxPoolOverflow } // If the new transaction is a future transaction it should never churn pending transactions @@ -1003,12 +1025,15 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e pool.locals.add(from) pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time. } + if isLocal { localGauge.Inc(1) } + pool.journalTx(from, tx) log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To()) + return replaced, nil } @@ -1035,6 +1060,7 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo if pool.queue[from] == nil { pool.queue[from] = newList(false) } + inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) if !inserted { // An older transaction was better, discard this @@ -1055,6 +1081,7 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo 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) @@ -1063,6 +1090,7 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo if _, exist := pool.beats[from]; !exist { pool.beats[from] = time.Now() } + return old != nil, nil } @@ -1073,6 +1101,7 @@ func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) { if pool.journal == nil || !pool.locals.contains(from) { return } + if err := pool.journal.insert(tx); err != nil { log.Warn("Failed to journal local transaction", "err", err) } @@ -1099,6 +1128,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T if pool.pending[addr] == nil { pool.pending[addr] = newList(true) } + list := pool.pending[addr] inserted, old := list.Add(tx, pool.config.PriceBump) @@ -1194,6 +1224,7 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { if pool.all.Get(hash) != nil { errs = append(errs, ErrAlreadyKnown) + knownTxMeter.Mark(1) continue @@ -1209,6 +1240,7 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { _, err = types.Sender(pool.signer, tx) if err != nil { errs = append(errs, ErrInvalidSender) + invalidTxMeter.Mark(1) continue @@ -1375,7 +1407,6 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus { pool.mu.RUnlock() } - // implicit else: the tx may have been included into a block between // checking pool.Get and obtaining the lock. In that case, TxStatusUnknown is correct } @@ -1408,6 +1439,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) int { // Remove it from the list of known transactions pool.all.Remove(hash) + if outofbound { pool.priced.Removed(1) } @@ -1535,6 +1567,7 @@ func (pool *TxPool) scheduleReorgLoop() { } else { reset.newHead = req.newHead } + launchNextRun = true pool.reorgDoneCh <- nextDone @@ -1545,6 +1578,7 @@ func (pool *TxPool) scheduleReorgLoop() { } else { dirtyAccounts.merge(req) } + launchNextRun = true pool.reorgDoneCh <- nextDone @@ -1555,6 +1589,7 @@ func (pool *TxPool) scheduleReorgLoop() { if _, ok := queuedEvents[addr]; !ok { queuedEvents[addr] = newSortedMap() } + queuedEvents[addr].Put(tx) case <-curDone: @@ -1565,7 +1600,9 @@ func (pool *TxPool) scheduleReorgLoop() { if curDone != nil { <-curDone } + close(nextDone) + return } } @@ -1604,7 +1641,6 @@ func (pool *TxPool) runReorg(ctx context.Context, done chan struct{}, reset *txp if reset != nil { tracing.ElapsedTime(ctx, span, "03 reset-head reorg", func(_ context.Context, innerSpan trace.Span) { - // Reset from the old head to the new, rescheduling any reorged transactions tracing.ElapsedTime(ctx, innerSpan, "04 reset-head-itself reorg", func(_ context.Context, innerSpan trace.Span) { pool.reset(reset.oldHead, reset.newHead) @@ -1659,7 +1695,6 @@ func (pool *TxPool) runReorg(ctx context.Context, done chan struct{}, reset *txp //nolint:nestif if reset != nil { tracing.ElapsedTime(ctx, span, "new block", func(_ context.Context, innerSpan trace.Span) { - tracing.ElapsedTime(ctx, innerSpan, "06 demoteUnexecutables", func(_ context.Context, _ trace.Span) { pool.demoteUnexecutables() }) @@ -1762,10 +1797,12 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { } else { // Reorg seems shallow enough to pull in all transactions into memory var discarded, included types.Transactions + var ( rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64()) add = pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64()) ) + if rem == nil { // This can happen if a setHead is performed, where we simply discard the old // head from the chain. @@ -1784,30 +1821,38 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { } 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 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 } } + 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) } } @@ -1816,11 +1861,13 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { if newHead == nil { newHead = pool.chain.CurrentBlock() // Special case during testing } + statedb, err := pool.chain.StateAt(newHead.Root) if err != nil { log.Error("Failed to reset txpool state", "err", err) return } + pool.currentState = statedb pool.pendingNonces = newNoncer(statedb) pool.currentMaxGas.Store(newHead.GasLimit) @@ -2043,6 +2090,7 @@ func (pool *TxPool) truncatePending() { pool.priced.Removed(capsLen) pendingGauge.Dec(int64(capsLen)) + if pool.locals.contains(offenders[i]) { localGauge.Dec(int64(capsLen)) } @@ -2059,7 +2107,6 @@ func (pool *TxPool) truncatePending() { // If still above threshold, reduce to limit or min allowance if pending > pool.config.GlobalSlots && len(offenders) > 0 { - pool.pendingMu.RLock() for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots { @@ -2107,17 +2154,20 @@ func (pool *TxPool) truncateQueue() { for _, list := range pool.queue { queued += uint64(list.Len()) } + if queued <= pool.config.GlobalQueue { return } // Sort all accounts with queued transactions by heartbeat addresses := make(addressesByHeartbeat, 0, len(pool.queue)) + for addr := range pool.queue { if !pool.locals.contains(addr) { // don't drop locals addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]}) } } + sort.Sort(sort.Reverse(addresses)) var ( @@ -2300,6 +2350,7 @@ func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet { for _, addr := range addrs { as.add(addr) } + return as } @@ -2309,6 +2360,7 @@ func (as *accountSet) contains(addr common.Address) bool { defer as.m.RUnlock() _, exist := as.accounts[addr] + return exist } @@ -2328,6 +2380,7 @@ func (as *accountSet) containsTx(tx *types.Transaction) bool { if addr, err := types.Sender(as.signer, tx); err == nil { return as.contains(addr) } + return false } @@ -2370,6 +2423,7 @@ func (as *accountSet) merge(other *accountSet) { if _, ok = as.accounts[addr]; !ok { as.accountsFlatted = append(as.accountsFlatted, addr) } + as.accounts[addr] = struct{}{} } } @@ -2415,6 +2469,7 @@ func (t *lookup) Range(f func(hash common.Hash, tx *types.Transaction, local boo } } } + if remote { for key, value := range t.remotes { if !f(key, value, false) { @@ -2432,6 +2487,7 @@ func (t *lookup) Get(hash common.Hash) *types.Transaction { if tx := t.locals[hash]; tx != nil { return tx } + return t.remotes[hash] } @@ -2507,10 +2563,12 @@ func (t *lookup) Remove(hash common.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)) @@ -2525,6 +2583,7 @@ func (t *lookup) RemoteToLocals(locals *accountSet) int { defer t.lock.Unlock() var migrated int + for hash, tx := range t.remotes { if locals.containsTx(tx) { locals.m.Lock() @@ -2532,21 +2591,26 @@ func (t *lookup) RemoteToLocals(locals *accountSet) int { locals.m.Unlock() delete(t.remotes, hash) + migrated += 1 } } + return migrated } // RemotesBelowTip finds all remote transactions below the given tip threshold. func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions { found := make(types.Transactions, 0, 128) + t.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { if tx.GasTipCapIntCmp(threshold) < 0 { found = append(found, tx) } + return true }, false, true) // Only iterate remotes + return found } diff --git a/core/txpool/txpool2_test.go b/core/txpool/txpool2_test.go index 697dfca65a..5a9068bd41 100644 --- a/core/txpool/txpool2_test.go +++ b/core/txpool/txpool2_test.go @@ -35,10 +35,13 @@ func pricedValuedTransaction(nonce uint64, value int64, gaslimit uint64, gaspric func count(t *testing.T, pool *TxPool) (pending int, queued int) { t.Helper() + pending, queued = pool.stats() + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } + return pending, queued } @@ -47,6 +50,7 @@ func fillPool(tb testing.TB, pool *TxPool) { // Create a number of test accounts, fund them and make transactions executableTxs := types.Transactions{} nonExecutableTxs := types.Transactions{} + for i := 0; i < 384; i++ { key, _ := crypto.GenerateKey() pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(10000000000)) @@ -64,6 +68,7 @@ func fillPool(tb testing.TB, pool *TxPool) { if have, want := pending, slots; have != want { tb.Fatalf("have %d, want %d", have, want) } + if have, want := queued, 0; have != want { tb.Fatalf("have %d, want %d", have, want) } @@ -83,6 +88,7 @@ func TestTransactionFutureAttack(t *testing.T) { config := testTxPoolConfig config.GlobalQueue = 100 config.GlobalSlots = 100 + pool := NewTxPool(config, eip1559Config, blockchain) defer pool.Stop() fillPool(t, pool) @@ -91,16 +97,19 @@ func TestTransactionFutureAttack(t *testing.T) { { key, _ := crypto.GenerateKey() pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) + futureTxs := types.Transactions{} for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 100000, big.NewInt(500), key)) } + for i := 0; i < 5; i++ { pool.AddRemotesSync(futureTxs) newPending, newQueued := count(t, pool) t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots()) } } + newPending, _ := pool.Stats() // Pending should not have been touched if have, want := newPending, pending; have < want { @@ -116,6 +125,7 @@ func TestTransactionFuture1559(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + pool := NewTxPool(testTxPoolConfig, eip1559Config, blockchain) defer pool.Stop() @@ -127,12 +137,14 @@ func TestTransactionFuture1559(t *testing.T) { { key, _ := crypto.GenerateKey() pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) + futureTxs := types.Transactions{} for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { futureTxs = append(futureTxs, dynamicFeeTx(1000+uint64(j), 100000, big.NewInt(200), big.NewInt(101), key)) } pool.AddRemotesSync(futureTxs) } + newPending, _ := pool.Stats() // Pending should not have been touched if have, want := newPending, pending; have != want { @@ -148,6 +160,7 @@ func TestTransactionZAttack(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + pool := NewTxPool(testTxPoolConfig, eip1559Config, blockchain) defer pool.Stop() // Create a number of test accounts, fund them and make transactions @@ -155,7 +168,9 @@ func TestTransactionZAttack(t *testing.T) { countInvalidPending := func() int { t.Helper() + var ivpendingNum int + pendingtxs, _ := pool.Content() for account, txs := range pendingtxs { cur_balance := new(big.Int).Set(pool.currentState.GetBalance(account)) @@ -167,9 +182,11 @@ func TestTransactionZAttack(t *testing.T) { } } } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } + return ivpendingNum } ivPending := countInvalidPending() @@ -188,6 +205,7 @@ func TestTransactionZAttack(t *testing.T) { { key, _ := crypto.GenerateKey() pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) + for j := 0; j < int(pool.config.GlobalSlots); j++ { overDraftTxs = append(overDraftTxs, pricedValuedTransaction(uint64(j), 600000000000, 21000, big.NewInt(500), key)) } @@ -200,6 +218,7 @@ func TestTransactionZAttack(t *testing.T) { newPending, newQueued := count(t, pool) newIvPending := countInvalidPending() + t.Logf("pool.all.Slots(): %d\n", pool.all.Slots()) t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots()) t.Logf("invalid pending: %d\n", newIvPending) @@ -218,18 +237,21 @@ func BenchmarkFutureAttack(b *testing.B) { config := testTxPoolConfig config.GlobalQueue = 100 config.GlobalSlots = 100 + pool := NewTxPool(config, eip1559Config, blockchain) defer pool.Stop() fillPool(b, pool) key, _ := crypto.GenerateKey() pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) + futureTxs := types.Transactions{} for n := 0; n < b.N; n++ { futureTxs = append(futureTxs, pricedTransaction(1000+uint64(n), 100000, big.NewInt(500), key)) } b.ResetTimer() + for i := 0; i < 5; i++ { pool.AddRemotesSync(futureTxs) } diff --git a/core/txpool/txpool_test.go b/core/txpool/txpool_test.go index f356ce5407..0b2a1681cd 100644 --- a/core/txpool/txpool_test.go +++ b/core/txpool/txpool_test.go @@ -84,6 +84,7 @@ type testBlockChain struct { func newTestBlockChain(gasLimit uint64, statedb *state.StateDB, chainHeadFeed *event.Feed) *testBlockChain { bc := testBlockChain{statedb: statedb, chainHeadFeed: new(event.Feed)} bc.gasLimit.Store(gasLimit) + return &bc } @@ -120,6 +121,7 @@ func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key rand.Read(data) tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(0), gaslimit, gasprice, data), types.HomesteadSigner{}, key) + return tx } @@ -135,6 +137,7 @@ func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int, Data: nil, AccessList: nil, }) + return tx } @@ -152,6 +155,7 @@ func setupPoolWithConfig(config *params.ChainConfig, txPoolConfig Config, gasLim // wait for the pool to initialize <-pool.initDoneCh + return pool, key } @@ -167,6 +171,7 @@ func validatePoolInternals(pool *TxPool) error { } pool.priced.Reheap() + priced, remote := pool.priced.urgent.Len()+pool.priced.floating.Len(), pool.all.RemoteCount() if priced != remote { return fmt.Errorf("total priced transaction count %d != %d", priced, remote) @@ -210,6 +215,7 @@ func validateEvents(events chan core.NewTxsEvent, count int) error { return fmt.Errorf("event #%d not fired", len(received)) } } + if len(received) > count { return fmt.Errorf("more than %d events fired: %v", count, received[count:]) } @@ -222,6 +228,7 @@ func validateEvents(events chan core.NewTxsEvent, count int) error { // reading the event channel and pushing into it, so better wait a bit ensuring // really nothing gets injected. } + return nil } @@ -243,6 +250,7 @@ func (c *testChain) State() (*state.StateDB, error) { // state multiple times and by delaying it a bit we simulate // a state change between those fetches. stdb := c.statedb + if *c.trigger { c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) // simulate that the new head block included tx0 and tx1 @@ -250,6 +258,7 @@ func (c *testChain) State() (*state.StateDB, error) { c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether)) *c.trigger = false } + return stdb, nil } @@ -290,6 +299,7 @@ func TestStateChangeDuringReset(t *testing.T) { // trigger state change in the background trigger = true + <-pool.requestReset(nil, nil) nonce = pool.Nonce(address) @@ -331,6 +341,7 @@ func TestInvalidTransactions(t *testing.T) { // Intrinsic gas too low testAddBalance(pool, from, big.NewInt(1)) + if err, want := pool.AddRemote(tx), core.ErrIntrinsicGas; !errors.Is(err, want) { t.Errorf("want %v have %v", want, err) } @@ -343,6 +354,7 @@ func TestInvalidTransactions(t *testing.T) { testSetNonce(pool, from, 1) testAddBalance(pool, from, big.NewInt(0xffffffffffffff)) + tx = transaction(0, 100000, key) if err, want := pool.AddRemote(tx), core.ErrNonceTooLow; !errors.Is(err, want) { t.Errorf("want %v have %v", want, err) @@ -503,10 +515,12 @@ func TestChainFork(t *testing.T) { if _, err := pool.add(tx, false); err != nil { t.Error("didn't expect error", err) } + pool.removeTx(tx.Hash(), true) // reset the pool's internal state resetState() + if _, err := pool.add(tx, false); err != nil { t.Error("didn't expect error", err) } @@ -537,6 +551,7 @@ func TestDoubleNonce(t *testing.T) { if replace, err := pool.add(tx1, false); err != nil || replace { t.Errorf("first transaction insert failed (%v) or reported replacement (%v)", err, replace) } + if replace, err := pool.add(tx2, false); err != nil || !replace { t.Errorf("second transaction insert failed (%v) or not reported replacement (%v)", err, replace) } @@ -547,6 +562,7 @@ func TestDoubleNonce(t *testing.T) { if pool.pending[addr].Len() != 1 { t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) } @@ -561,6 +577,7 @@ func TestDoubleNonce(t *testing.T) { if pool.pending[addr].Len() != 1 { t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) } @@ -580,6 +597,7 @@ func TestMissingNonce(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, addr, big.NewInt(100000000000000)) + tx := transaction(1, 100000, key) if _, err := pool.add(tx, false); err != nil { t.Error("didn't expect error", err) @@ -594,6 +612,7 @@ func TestMissingNonce(t *testing.T) { if pool.queue[addr].Len() != 1 { t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) } + if pool.all.Count() != 1 { t.Error("expected 1 total transactions, got", pool.all.Count()) } @@ -603,6 +622,7 @@ func TestNonceRecovery(t *testing.T) { t.Parallel() const n = 10 + pool, key := setupPool() defer pool.Stop() @@ -618,6 +638,7 @@ func TestNonceRecovery(t *testing.T) { // simulate some weird re-order of transactions and missing nonce(s) testSetNonce(pool, addr, n-1) <-pool.requestReset(nil, nil) + if fn := pool.Nonce(addr); fn != n-1 { t.Errorf("expected nonce to be %d, got %d", n-1, fn) } @@ -644,6 +665,7 @@ func TestDropping(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) @@ -670,6 +692,7 @@ func TestDropping(t *testing.T) { if pool.queue[account].Len() != 3 { t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) } + if pool.all.Count() != 6 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) } @@ -685,6 +708,7 @@ func TestDropping(t *testing.T) { if pool.queue[account].Len() != 3 { t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) } + if pool.all.Count() != 6 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) } @@ -696,9 +720,11 @@ func TestDropping(t *testing.T) { if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { t.Errorf("funded pending transaction missing: %v", tx0) } + if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; !ok { t.Errorf("funded pending transaction missing: %v", tx0) } + if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok { t.Errorf("out-of-fund pending transaction present: %v", tx1) } @@ -707,12 +733,15 @@ func TestDropping(t *testing.T) { if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } + if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } + if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { t.Errorf("out-of-fund queued transaction present: %v", tx11) } + if pool.all.Count() != 4 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4) } @@ -724,6 +753,7 @@ func TestDropping(t *testing.T) { if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { t.Errorf("funded pending transaction missing: %v", tx0) } + if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok { t.Errorf("over-gased pending transaction present: %v", tx1) } @@ -732,9 +762,11 @@ func TestDropping(t *testing.T) { if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } + if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { t.Errorf("over-gased queued transaction present: %v", tx11) } + if pool.all.Count() != 2 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2) } @@ -765,6 +797,7 @@ func TestPostponing(t *testing.T) { } // Add a batch consecutive pending transactions for validation txs := []*types.Transaction{} + for i, key := range keys { for j := 0; j < 100; j++ { var tx *types.Transaction @@ -773,9 +806,11 @@ func TestPostponing(t *testing.T) { } else { tx = transaction(uint64(j), 50000, key) } + txs = append(txs, tx) } } + for i, err := range pool.AddRemotesSync(txs) { if err != nil { t.Fatalf("tx %d: failed to add transactions: %v", i, err) @@ -791,6 +826,7 @@ func TestPostponing(t *testing.T) { if len(pool.queue) != 0 { t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) } + if pool.all.Count() != len(txs) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) } @@ -806,6 +842,7 @@ func TestPostponing(t *testing.T) { if len(pool.queue) != 0 { t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) } + if pool.all.Count() != len(txs) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) } @@ -813,6 +850,7 @@ func TestPostponing(t *testing.T) { for _, addr := range accs { testAddBalance(pool, addr, big.NewInt(-1)) } + <-pool.requestReset(nil, nil) // The first account's first transaction remains valid, check that subsequent @@ -833,6 +871,7 @@ func TestPostponing(t *testing.T) { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx) } + if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok { t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx) } @@ -840,6 +879,7 @@ func TestPostponing(t *testing.T) { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx) } + if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx) } @@ -866,6 +906,7 @@ func TestPostponing(t *testing.T) { } } } + if pool.all.Count() != len(txs)/2 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)/2) } @@ -886,6 +927,7 @@ func TestGapFilling(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, testTxPoolConfig.AccountQueue+5) + sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -894,10 +936,12 @@ func TestGapFilling(t *testing.T) { transaction(0, 100000, key), transaction(2, 100000, key), }) + pending, queued := pool.Stats() if pending != 1 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) } + if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } @@ -905,6 +949,7 @@ func TestGapFilling(t *testing.T) { if err := validateEvents(events, 1); err != nil { t.Fatalf("original event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -912,16 +957,20 @@ func TestGapFilling(t *testing.T) { if err := pool.addRemoteSync(transaction(1, 100000, key)); err != nil { t.Fatalf("failed to add gapped transaction: %v", err) } + pending, queued = pool.Stats() if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } + if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } + if err := validateEvents(events, 2); err != nil { t.Fatalf("gap-filling event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -961,6 +1010,7 @@ func TestQueueAccountLimiting(t *testing.T) { } } } + if pool.all.Count() != int(testTxPoolConfig.AccountQueue) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue) } @@ -1048,6 +1098,7 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { keys[i], _ = crypto.GenerateKey() testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) } + local := keys[len(keys)-1] // Generate and queue a batch of transactions @@ -1065,12 +1116,15 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { pool.AddRemotesSync(txs) queued := 0 + for addr, list := range pool.queue { if list.Len() > int(config.AccountQueue) { t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) } + queued += list.Len() } + if queued > int(config.GlobalQueue) { t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) } @@ -1085,12 +1139,15 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { // If locals are disabled, the previous eviction algorithm should apply here too if nolocals { queued := 0 + for addr, list := range pool.queue { if list.Len() > int(config.AccountQueue) { t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) } + queued += list.Len() } + if queued > int(config.GlobalQueue) { t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) } @@ -1148,16 +1205,20 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } + if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(1), remote)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } + pending, queued := pool.Stats() if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } + if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1170,9 +1231,11 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } + if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1184,6 +1247,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } + if nolocals { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) @@ -1193,6 +1257,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1207,9 +1272,11 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } + if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1218,18 +1285,22 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if err := pool.AddLocal(pricedTransaction(4, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } + if err := pool.addRemoteSync(pricedTransaction(4, 100000, big.NewInt(1), remote)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } + time.Sleep(5 * evictionInterval) // A half lifetime pass // Queue executable transactions, the life cycle should be restarted. if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), remote)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } + time.Sleep(6 * evictionInterval) // All gapped transactions shouldn't be kicked out @@ -1237,19 +1308,23 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } + if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } // The whole life time pass after last promotion, kick out stale transactions time.Sleep(2 * config.Lifetime) + pending, queued = pool.Stats() if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } + if nolocals { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) @@ -1259,6 +1334,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1279,6 +1355,7 @@ func TestPendingLimiting(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, testTxPoolConfig.AccountQueue+5) + sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -1298,12 +1375,15 @@ func TestPendingLimiting(t *testing.T) { t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) } } + if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5) } + if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil { t.Fatalf("event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1335,6 +1415,7 @@ func TestPendingGlobalLimiting(t *testing.T) { nonces := make(map[common.Address]uint64) txs := types.Transactions{} + for _, key := range keys { addr := crypto.PubkeyToAddress(key.PublicKey) for j := 0; j < int(config.GlobalSlots)/len(keys)*2; j++ { @@ -1356,6 +1437,7 @@ func TestPendingGlobalLimiting(t *testing.T) { if pending > int(config.GlobalSlots) { t.Fatalf("total pending transactions overflow allowance: %d > %d", pending, config.GlobalSlots) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1409,9 +1491,11 @@ func TestAllowedTxSize(t *testing.T) { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } + if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1444,6 +1528,7 @@ func TestCapClearsFromAll(t *testing.T) { } // Import the batch and verify that limits have been enforced pool.AddRemotes(txs) + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1475,6 +1560,7 @@ func TestPendingMinimumAllowance(t *testing.T) { nonces := make(map[common.Address]uint64) txs := types.Transactions{} + for _, key := range keys { addr := crypto.PubkeyToAddress(key.PublicKey) for j := 0; j < int(config.AccountSlots)*2; j++ { @@ -1515,6 +1601,7 @@ func TestRepricing(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -1577,6 +1664,7 @@ func TestRepricing(t *testing.T) { if err := validateEvents(events, 0); err != nil { t.Fatalf("reprice event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1597,6 +1685,7 @@ func TestRepricing(t *testing.T) { if err := validateEvents(events, 0); err != nil { t.Fatalf("post-reprice event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1615,6 +1704,7 @@ func TestRepricing(t *testing.T) { if err := validateEvents(events, 1); err != nil { t.Fatalf("post-reprice local event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1655,6 +1745,7 @@ func TestRepricingDynamicFee(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -1698,6 +1789,7 @@ func TestRepricingDynamicFee(t *testing.T) { if err := validateEvents(events, 7); err != nil { t.Fatalf("original event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1717,6 +1809,7 @@ func TestRepricingDynamicFee(t *testing.T) { if err := validateEvents(events, 0); err != nil { t.Fatalf("reprice event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1742,6 +1835,7 @@ func TestRepricingDynamicFee(t *testing.T) { if err := validateEvents(events, 0); err != nil { t.Fatalf("post-reprice event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1760,6 +1854,7 @@ func TestRepricingDynamicFee(t *testing.T) { if err := validateEvents(events, 1); err != nil { t.Fatalf("post-reprice local event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1834,6 +1929,7 @@ func TestRepricingKeepsLocals(t *testing.T) { t.Fatal(err) } } + pending, queued := pool.Stats() expPending, expQueued := 1000, 1000 validate := func() { @@ -1841,6 +1937,7 @@ func TestRepricingKeepsLocals(t *testing.T) { if pending != expPending { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, expPending) } + if queued != expQueued { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, expQueued) } @@ -1883,6 +1980,7 @@ func TestUnderpricing(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -1910,12 +2008,15 @@ func TestUnderpricing(t *testing.T) { if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } + if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } + if err := validateEvents(events, 3); err != nil { t.Fatalf("original event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1931,9 +2032,11 @@ func TestUnderpricing(t *testing.T) { if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - t.Fatalf("failed to add well priced transaction: %v", err) } + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2 t.Fatalf("failed to add well priced transaction: %v", err) } + if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3 t.Fatalf("failed to add well priced transaction: %v", err) } @@ -1941,10 +2044,12 @@ func TestUnderpricing(t *testing.T) { if err := pool.AddRemote(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != ErrFutureReplacePending { t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending) } + pending, queued = pool.Stats() if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } + if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } @@ -1961,20 +2066,25 @@ func TestUnderpricing(t *testing.T) { if err := pool.AddLocal(ltx); err != nil { t.Fatalf("failed to append underpriced local transaction: %v", err) } + ltx = pricedTransaction(0, 100000, big.NewInt(0), keys[3]) if err := pool.AddLocal(ltx); err != nil { t.Fatalf("failed to add new underpriced local transaction: %v", err) } + pending, queued = pool.Stats() if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } + if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } + if err := validateEvents(events, 2); err != nil { t.Fatalf("local event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1999,6 +2109,7 @@ func TestStableUnderpricing(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -2019,12 +2130,15 @@ func TestStableUnderpricing(t *testing.T) { if pending != int(config.GlobalSlots) { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) } + if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } + if err := validateEvents(events, int(config.GlobalSlots)); err != nil { t.Fatalf("original event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2032,16 +2146,20 @@ func TestStableUnderpricing(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { t.Fatalf("failed to add well priced transaction: %v", err) } + pending, queued = pool.Stats() if pending != int(config.GlobalSlots) { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) } + if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } + if err := validateEvents(events, 1); err != nil { t.Fatalf("additional event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2063,6 +2181,7 @@ func TestUnderpricingDynamicFee(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -2090,12 +2209,15 @@ func TestUnderpricingDynamicFee(t *testing.T) { if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } + if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } + if err := validateEvents(events, 3); err != nil { t.Fatalf("original event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2121,10 +2243,12 @@ func TestUnderpricingDynamicFee(t *testing.T) { if err := pool.AddRemote(tx); err != nil { // +K1:3, -K1:0 => Pend K0:0 K2:0; Que K1:2 K1:3 t.Fatalf("failed to add well priced transaction: %v", err) } + pending, queued = pool.Stats() if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } + if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } @@ -2141,20 +2265,25 @@ func TestUnderpricingDynamicFee(t *testing.T) { if err := pool.AddLocal(ltx); err != nil { t.Fatalf("failed to append underpriced local transaction: %v", err) } + ltx = dynamicFeeTx(0, 100000, big.NewInt(0), big.NewInt(0), keys[3]) if err := pool.AddLocal(ltx); err != nil { t.Fatalf("failed to add new underpriced local transaction: %v", err) } + pending, queued = pool.Stats() if pending != 3 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } + if queued != 1 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } + if err := validateEvents(events, 2); err != nil { t.Fatalf("local event firing failed: %v", err) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2188,6 +2317,7 @@ func TestDualHeapEviction(t *testing.T) { // Create a test accounts and fund it key, _ := crypto.GenerateKey() testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000000)) + if urgent { tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+1+i)), big.NewInt(int64(1+i)), key) highTip = tx @@ -2195,8 +2325,10 @@ func TestDualHeapEviction(t *testing.T) { tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key) highCap = tx } + pool.AddRemotesSync([]*types.Transaction{tx}) } + pending, queued := pool.Stats() if pending+queued != 20 { t.Fatalf("transaction count mismatch: have %d, want %d", pending+queued, 10) @@ -2204,6 +2336,7 @@ func TestDualHeapEviction(t *testing.T) { } add(false) + for baseFee = 0; baseFee <= 1000; baseFee += 100 { pool.priced.SetBaseFee(uint256.NewInt(uint64(baseFee))) add(true) @@ -2292,6 +2425,7 @@ func TestDeduplication(t *testing.T) { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2311,6 +2445,7 @@ func TestReplacement(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -2400,6 +2535,7 @@ func TestReplacementDynamicFee(t *testing.T) { // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) defer sub.Unsubscribe() @@ -2450,6 +2586,7 @@ func TestReplacementDynamicFee(t *testing.T) { if stage == "queued" { count = 0 } + if err := validateEvents(events, count); err != nil { t.Fatalf("cheap %s replacement event firing failed: %v", stage, err) } @@ -2488,6 +2625,7 @@ func TestReplacementDynamicFee(t *testing.T) { if stage == "queued" { count = 0 } + if err := validateEvents(events, count); err != nil { t.Fatalf("replacement %s event firing failed: %v", stage, err) } @@ -2518,6 +2656,7 @@ func testJournaling(t *testing.T, nolocals bool) { if err != nil { t.Fatalf("failed to create temporary journal: %v", err) } + journal := file.Name() defer os.Remove(journal) @@ -2547,22 +2686,28 @@ func testJournaling(t *testing.T, nolocals bool) { if err := pool.AddLocal(pricedTransaction(0, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } + if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } + if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), remote)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } + pending, queued := pool.Stats() if pending != 4 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 4) } + if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2577,6 +2722,7 @@ func testJournaling(t *testing.T, nolocals bool) { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } + if nolocals { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) @@ -2586,6 +2732,7 @@ func testJournaling(t *testing.T, nolocals bool) { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2603,6 +2750,7 @@ func testJournaling(t *testing.T, nolocals bool) { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } + if nolocals { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) @@ -2612,9 +2760,11 @@ func testJournaling(t *testing.T, nolocals bool) { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } + pool.Stop() } @@ -2651,9 +2801,11 @@ func TestStatusCheck(t *testing.T) { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } + if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } + if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -2662,6 +2814,7 @@ func TestStatusCheck(t *testing.T) { for i, tx := range txs { hashes[i] = tx.Hash() } + hashes = append(hashes, common.Hash{}) statuses := pool.Status(hashes) @@ -2713,6 +2866,7 @@ func benchmarkPendingDemotion(b *testing.B, size int) { // Benchmark the speed of pool validation b.ResetTimer() b.ReportAllocs() + for i := 0; i < b.N; i++ { pool.demoteUnexecutables() } @@ -2738,6 +2892,7 @@ func benchmarkFuturePromotion(b *testing.B, size int) { } // Benchmark the speed of pool validation b.ResetTimer() + for i := 0; i < b.N; i++ { pool.promoteExecutables(nil) } @@ -2847,6 +3002,7 @@ func BenchmarkPoolMining(b *testing.B) { account := crypto.PubkeyToAddress(localKeyPub) const balanceStr = "1_000_000_000" + balance, ok := big.NewInt(0).SetString(balanceStr, 0) if !ok { b.Fatal("incorrect initial balance", balanceStr) @@ -2924,22 +3080,28 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) { 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, _ := setupPool() testAddBalance(pool, account, big.NewInt(100000000)) + for _, local := range locals { pool.AddLocal(local) } + b.StartTimer() // Assign a high enough balance for testing testAddBalance(pool, remoteAddr, big.NewInt(100000000)) + for i := 0; i < len(remotes); i++ { pool.AddRemotes([]*types.Transaction{remotes[i]}) } @@ -3482,6 +3644,7 @@ func testPoolBatchInsert(t *testing.T, cfg txPoolRapidConfig) { now := time.Now() pendingAddedCh := make(chan struct{}, 1024) + pool, key := setupPoolWithConfig(params.TestChainConfig, testTxPoolConfig, cfg.gasLimit, MakeWithPromoteTxCh(pendingAddedCh)) defer pool.Stop() @@ -3532,10 +3695,12 @@ func testPoolBatchInsert(t *testing.T, cfg txPoolRapidConfig) { } var wg sync.WaitGroup + wg.Add(1) go func() { defer wg.Done() + now = time.Now() testAddBalance(pool, localKey.account, initialBalance) @@ -3597,6 +3762,7 @@ func testPoolBatchInsert(t *testing.T, cfg txPoolRapidConfig) { } pendingStat, queuedStat := pool.Stats() + currentTxPoolStats = pendingStat + queuedStat if currentTxPoolStats == 0 { cancel() @@ -3673,8 +3839,7 @@ func testPoolBatchInsert(t *testing.T, cfg txPoolRapidConfig) { block++ cancel() - - //time.Sleep(time.Second) + // time.Sleep(time.Second) } rt.Logf("case completed totalTxs %d %v\n\n", txs.totalTxs, time.Since(now)) @@ -4485,7 +4650,6 @@ func apiWithMining(tb testing.TB, balanceStr string, batchesSize int, singleCase case res := <-ch: fmt.Fprint(io.Discard, res) } - }, done, "SubscribeNewTxsEvent", apiTickerDuration, timeoutDuration, i) }() } diff --git a/core/types/block.go b/core/types/block.go index 6d9a8d29ed..dfda5a9b1d 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -39,7 +39,9 @@ type BlockNonce [8]byte // EncodeNonce converts the given integer to a block nonce. func EncodeNonce(i uint64) BlockNonce { var n BlockNonce + binary.BigEndian.PutUint64(n[:], i) + return n } @@ -127,6 +129,7 @@ func (h *Header) Size() common.StorageSize { if h.BaseFee != nil { baseFeeBits = h.BaseFee.BitLen() } + return headerSize + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+baseFeeBits)/8) } @@ -138,19 +141,23 @@ func (h *Header) SanityCheck() error { if h.Number != nil && !h.Number.IsUint64() { return fmt.Errorf("too large block number: bitlen %d", h.Number.BitLen()) } + if h.Difficulty != nil { if diffLen := h.Difficulty.BitLen(); diffLen > 80 { return fmt.Errorf("too large block difficulty: bitlen %d", diffLen) } } + if eLen := len(h.Extra); eLen > 100*1024 { return fmt.Errorf("too large block extradata: size %d", eLen) } + if h.BaseFee != nil { if bfLen := h.BaseFee.BitLen(); bfLen > 256 { return fmt.Errorf("too large base fee: bitlen %d", bfLen) } } + return nil } @@ -160,6 +167,7 @@ func (h *Header) EmptyBody() bool { if h.WithdrawalsHash == nil { return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash } + return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash && *h.WithdrawalsHash == EmptyWithdrawalsHash } @@ -232,6 +240,7 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* } else { b.header.UncleHash = CalcUncleHash(uncles) b.uncles = make([]*Header, len(uncles)) + for i := range uncles { b.uncles[i] = CopyHeader(uncles[i]) } @@ -276,16 +285,20 @@ func CopyHeader(h *Header) *Header { if cpy.Difficulty = new(big.Int); h.Difficulty != nil { cpy.Difficulty.Set(h.Difficulty) } + if cpy.Number = new(big.Int); h.Number != nil { cpy.Number.Set(h.Number) } + if h.BaseFee != nil { cpy.BaseFee = new(big.Int).Set(h.BaseFee) } + if len(h.Extra) > 0 { cpy.Extra = make([]byte, len(h.Extra)) copy(cpy.Extra, h.Extra) } + if h.WithdrawalsHash != nil { cpy.WithdrawalsHash = new(common.Hash) *cpy.WithdrawalsHash = *h.WithdrawalsHash @@ -299,18 +312,23 @@ func CopyHeader(h *Header) *Header { copy(cpy.TxDependency[i], dep) } } + return &cpy } // DecodeRLP decodes the Ethereum func (b *Block) DecodeRLP(s *rlp.Stream) error { var eb extblock + _, size, _ := s.Kind() + if err := s.Decode(&eb); err != nil { return err } + b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals b.size.Store(rlp.ListSize(size)) + return nil } @@ -335,6 +353,7 @@ func (b *Block) Transaction(hash common.Hash) *Transaction { return transaction } } + return nil } @@ -361,6 +380,7 @@ func (b *Block) BaseFee() *big.Int { if b.header.BaseFee == nil { return nil } + return new(big.Int).Set(b.header.BaseFee) } @@ -379,9 +399,11 @@ func (b *Block) Size() uint64 { if size := b.size.Load(); size != nil { return size.(uint64) } + c := writeCounter(0) rlp.Encode(&c, b) b.size.Store(uint64(c)) + return uint64(c) } @@ -402,6 +424,7 @@ func CalcUncleHash(uncles []*Header) common.Hash { if len(uncles) == 0 { return EmptyUncleHash } + return rlpHash(uncles) } @@ -426,9 +449,11 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { uncles: make([]*Header, len(uncles)), } copy(block.transactions, transactions) + for i := range uncles { block.uncles[i] = CopyHeader(uncles[i]) } + return block } @@ -438,6 +463,7 @@ func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block { b.withdrawals = make([]*Withdrawal, len(withdrawals)) copy(b.withdrawals, withdrawals) } + return b } @@ -447,8 +473,10 @@ func (b *Block) Hash() common.Hash { if hash := b.hash.Load(); hash != nil { return hash.(common.Hash) } + v := b.header.Hash() b.hash.Store(v) + return v } @@ -462,12 +490,15 @@ func HeaderParentHashFromRLP(header []byte) common.Hash { if err != nil { return common.Hash{} } + parentHash, _, err := rlp.SplitString(listContent) if err != nil { return common.Hash{} } + if len(parentHash) != 32 { return common.Hash{} } + return common.BytesToHash(parentHash) } diff --git a/core/types/block_test.go b/core/types/block_test.go index 672e285840..7a5f5687df 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -34,7 +34,9 @@ import ( // from bcValidBlockTest.json, "SimpleTx" func TestBlockEncoding(t *testing.T) { blockEnc := common.FromHex("f90260f901f9a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { t.Fatal("decode error: ", err) } @@ -57,12 +59,15 @@ func TestBlockEncoding(t *testing.T) { tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil) 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) } @@ -115,7 +120,9 @@ func TestTxDependencyBlockEncoding(t *testing.T) { func TestEIP1559BlockEncoding(t *testing.T) { blockEnc := common.FromHex("f9030bf901fea083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4843b9aca00f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { t.Fatal("decode error: ", err) } @@ -160,6 +167,7 @@ func TestEIP1559BlockEncoding(t *testing.T) { Data: []byte{}, } tx2 := NewTx(txdata) + tx2, err := tx2.WithSignature(LatestSignerForChainID(big.NewInt(1)), common.Hex2Bytes("fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a800")) if err != nil { t.Fatal("invalid signature error: ", err) @@ -169,10 +177,12 @@ func TestEIP1559BlockEncoding(t *testing.T) { 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(), tx2.Type()) + 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) } @@ -180,7 +190,9 @@ func TestEIP1559BlockEncoding(t *testing.T) { 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) } @@ -234,6 +246,7 @@ func TestEIP2718BlockEncoding(t *testing.T) { if err != nil { t.Fatal("encode error: ", err) } + if !bytes.Equal(ourBlockEnc, blockEnc) { t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) } @@ -243,6 +256,7 @@ func TestUncleHash(t *testing.T) { uncles := make([]*Header, 0) h := CalcUncleHash(uncles) exp := common.HexToHash("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") + if h != exp { t.Fatalf("empty uncle hash is wrong, got %x != %x", h, exp) } @@ -252,10 +266,12 @@ var benchBuffer = bytes.NewBuffer(make([]byte, 0, 32000)) func BenchmarkEncodeBlock(b *testing.B) { block := makeBenchBlock() + b.ResetTimer() for i := 0; i < b.N; i++ { benchBuffer.Reset() + if err := rlp.Encode(benchBuffer, block); err != nil { b.Fatal(err) } @@ -280,6 +296,7 @@ func (h *testHasher) Reset() { func (h *testHasher) Update(key, val []byte) error { h.hasher.Write(key) h.hasher.Write(val) + return nil } @@ -295,6 +312,7 @@ func makeBenchBlock() *Block { signer = LatestSigner(params.TestChainConfig) uncles = make([]*Header, 3) ) + header := &Header{ Difficulty: math.BigPow(11, 11), Number: math.BigPow(2, 9), @@ -303,18 +321,22 @@ func makeBenchBlock() *Block { Time: 9876543, Extra: []byte("coolest block on chain"), } + for i := range txs { amount := math.BigPow(2, int64(i)) price := big.NewInt(300000) data := make([]byte, 100) tx := NewTransaction(uint64(i), common.Address{}, amount, 123457, price, data) + signedTx, err := SignTx(tx, signer, key) if err != nil { panic(err) } + txs[i] = signedTx receipts[i] = NewReceipt(make([]byte, 32), false, tx.Gas()) } + for i := range uncles { uncles[i] = &Header{ Difficulty: math.BigPow(11, 11), @@ -325,6 +347,7 @@ func makeBenchBlock() *Block { Extra: []byte("benchmark uncle"), } } + return NewBlock(header, txs, uncles, receipts, newHasher()) } @@ -345,6 +368,7 @@ func TestRlpDecodeParentHash(t *testing.T) { // | BaseFee | dynamic| *big.Int | 64 bits | mainnetTd := new(big.Int) mainnetTd.SetString("5ad3c2c71bbff854908", 16) + if rlpData, err := rlp.EncodeToBytes(&Header{ ParentHash: want, Difficulty: mainnetTd, diff --git a/core/types/bloom9.go b/core/types/bloom9.go index a560a20724..93396cc9ab 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -44,7 +44,9 @@ type Bloom [BloomByteLength]byte // It panics if b is not of suitable size. func BytesToBloom(b []byte) Bloom { var bloom Bloom + bloom.SetBytes(b) + return bloom } @@ -54,6 +56,7 @@ func (b *Bloom) SetBytes(d []byte) { if len(b) < len(d) { panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d))) } + copy(b[BloomByteLength-len(d):], d) } @@ -85,6 +88,7 @@ func (b Bloom) Bytes() []byte { // Test checks if the given topic is present in the bloom filter func (b Bloom) Test(topic []byte) bool { i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) + return v1 == v1&b[i1] && v2 == v2&b[i2] && v3 == v3&b[i3] @@ -103,35 +107,45 @@ func (b *Bloom) UnmarshalText(input []byte) error { // CreateBloom creates a bloom filter out of the give Receipts (+Logs) func CreateBloom(receipts Receipts) Bloom { buf := make([]byte, 6) + var bin Bloom + for _, receipt := range receipts { for _, log := range receipt.Logs { bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { bin.add(b[:], buf) } } } + return bin } // LogsBloom returns the bloom bytes for the given logs func LogsBloom(logs []*Log) []byte { buf := make([]byte, 6) + var bin Bloom + for _, log := range logs { bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { bin.add(b[:], buf) } } + return bin[:] } // Bloom9 returns the bloom filter for the given data func Bloom9(data []byte) []byte { var b Bloom + b.SetBytes(data) + return b.Bytes() } diff --git a/core/types/bloom9_test.go b/core/types/bloom9_test.go index d3178d112e..454a97ceeb 100644 --- a/core/types/bloom9_test.go +++ b/core/types/bloom9_test.go @@ -47,6 +47,7 @@ func TestBloom(t *testing.T) { t.Error("expected", data, "to test true") } } + for _, data := range negative { if bloom.Test([]byte(data)) { t.Error("did not expect", data, "to test true") @@ -57,6 +58,7 @@ func TestBloom(t *testing.T) { // TestBloomExtensively does some more thorough tests func TestBloomExtensively(t *testing.T) { var exp = common.HexToHash("c8d3ca65cdb4874300a9e39475508f23ed6da09fdbc487f89a2dcf50b09eb263") + var b Bloom // Add 100 "random" things for i := 0; i < 100; i++ { @@ -64,12 +66,16 @@ func TestBloomExtensively(t *testing.T) { b.Add([]byte(data)) //b.Add(new(big.Int).SetBytes([]byte(data))) } + got := crypto.Keccak256Hash(b.Bytes()) if got != exp { t.Errorf("Got %x, exp %x", got, exp) } + var b2 Bloom + b2.SetBytes(b.Bytes()) + got2 := crypto.Keccak256Hash(b2.Bytes()) if got != got2 { t.Errorf("Got %x, exp %x", got, got2) @@ -86,6 +92,7 @@ func BenchmarkBloom9(b *testing.B) { func BenchmarkBloom9Lookup(b *testing.B) { toTest := []byte("testtest") bloom := new(Bloom) + for i := 0; i < b.N; i++ { bloom.Test(toTest) } @@ -96,6 +103,7 @@ func BenchmarkCreateBloom(b *testing.B) { NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil), NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil), } + var rSmall = Receipts{ &Receipt{ Status: ReceiptStatusFailed, @@ -128,12 +136,15 @@ func BenchmarkCreateBloom(b *testing.B) { } b.Run("small", func(b *testing.B) { b.ReportAllocs() + var bl Bloom for i := 0; i < b.N; i++ { bl = CreateBloom(rSmall) } b.StopTimer() + var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) if got != exp { b.Errorf("Got %x, exp %x", got, exp) @@ -141,12 +152,15 @@ func BenchmarkCreateBloom(b *testing.B) { }) b.Run("large", func(b *testing.B) { b.ReportAllocs() + var bl Bloom for i := 0; i < b.N; i++ { bl = CreateBloom(rLarge) } b.StopTimer() + var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) if got != exp { b.Errorf("Got %x, exp %x", got, exp) diff --git a/core/types/bor_receipt.go b/core/types/bor_receipt.go index 8439973872..4c0a48fc82 100644 --- a/core/types/bor_receipt.go +++ b/core/types/bor_receipt.go @@ -25,6 +25,7 @@ var ( func BorReceiptKey(number uint64, hash common.Hash) []byte { enc := make([]byte, 8) binary.BigEndian.PutUint64(enc, number) + return append(append(borReceiptPrefix, enc...), hash.Bytes()...) } @@ -65,6 +66,7 @@ func DeriveFieldsForBorReceipt(receipt *Receipt, hash common.Hash, number uint64 receipt.Logs[j].Index = uint(logIndex) logIndex++ } + return nil } diff --git a/core/types/gen_access_tuple.go b/core/types/gen_access_tuple.go index fc48a84cc0..d1aa898517 100644 --- a/core/types/gen_access_tuple.go +++ b/core/types/gen_access_tuple.go @@ -15,9 +15,11 @@ func (a AccessTuple) MarshalJSON() ([]byte, error) { 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) } @@ -27,17 +29,23 @@ func (a *AccessTuple) UnmarshalJSON(input []byte) error { 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_account_rlp.go b/core/types/gen_account_rlp.go index 5181d88411..f4565058c3 100644 --- a/core/types/gen_account_rlp.go +++ b/core/types/gen_account_rlp.go @@ -12,16 +12,20 @@ func (obj *StateAccount) EncodeRLP(_w io.Writer) error { w := rlp.NewEncoderBuffer(_w) _tmp0 := w.List() w.WriteUint64(obj.Nonce) + if obj.Balance == nil { w.Write(rlp.EmptyString) } else { if obj.Balance.Sign() == -1 { return rlp.ErrNegativeBigInt } + w.WriteBigInt(obj.Balance) } + w.WriteBytes(obj.Root[:]) w.WriteBytes(obj.CodeHash) w.ListEnd(_tmp0) + return w.Flush() } diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 61b7f128ca..9b2d4dd969 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -33,9 +33,10 @@ func (h Header) MarshalJSON() ([]byte, error) { Nonce BlockNonce `json:"nonce"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` - TxDependency [][]uint64 `json:"txDependency" rlp:"optional"` + TxDependency [][]uint64 `json:"txDependency" rlp:"optional"` Hash common.Hash `json:"hash"` } + var enc Header enc.ParentHash = h.ParentHash enc.UncleHash = h.UncleHash @@ -56,6 +57,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.WithdrawalsHash = h.WithdrawalsHash enc.TxDependency = h.TxDependency enc.Hash = h.Hash() + return json.Marshal(&enc) } @@ -79,77 +81,107 @@ func (h *Header) UnmarshalJSON(input []byte) error { Nonce *BlockNonce `json:"nonce"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` - TxDependency [][]uint64 `json:"txDependency" rlp:"optional"` + TxDependency [][]uint64 `json:"txDependency" rlp:"optional"` } + var dec Header if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.ParentHash == nil { return errors.New("missing required field 'parentHash' for Header") } + h.ParentHash = *dec.ParentHash + if dec.UncleHash == nil { return errors.New("missing required field 'sha3Uncles' for Header") } + h.UncleHash = *dec.UncleHash if dec.Coinbase != nil { h.Coinbase = *dec.Coinbase } + if dec.Root == nil { return errors.New("missing required field 'stateRoot' for Header") } + h.Root = *dec.Root + if dec.TxHash == nil { return errors.New("missing required field 'transactionsRoot' for Header") } + h.TxHash = *dec.TxHash + if dec.ReceiptHash == nil { return errors.New("missing required field 'receiptsRoot' for Header") } + h.ReceiptHash = *dec.ReceiptHash + if dec.Bloom == nil { return errors.New("missing required field 'logsBloom' for Header") } + h.Bloom = *dec.Bloom + if dec.Difficulty == nil { return errors.New("missing required field 'difficulty' for Header") } + h.Difficulty = (*big.Int)(dec.Difficulty) + if dec.Number == nil { return errors.New("missing required field 'number' for Header") } + h.Number = (*big.Int)(dec.Number) + if dec.GasLimit == nil { return errors.New("missing required field 'gasLimit' for Header") } + h.GasLimit = uint64(*dec.GasLimit) + if dec.GasUsed == nil { return errors.New("missing required field 'gasUsed' for Header") } + h.GasUsed = uint64(*dec.GasUsed) + if dec.Time == nil { return errors.New("missing required field 'timestamp' for Header") } + h.Time = uint64(*dec.Time) + if dec.Extra == nil { return errors.New("missing required field 'extraData' for Header") } + h.Extra = *dec.Extra if dec.MixDigest != nil { h.MixDigest = *dec.MixDigest } + if dec.Nonce != nil { h.Nonce = *dec.Nonce } + if dec.BaseFee != nil { h.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.WithdrawalsHash != nil { h.WithdrawalsHash = dec.WithdrawalsHash } + if dec.TxDependency != nil { h.TxDependency = dec.TxDependency } + return nil } diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go index 7d00a78ccc..4f5c6b86dc 100644 --- a/core/types/gen_header_rlp.go +++ b/core/types/gen_header_rlp.go @@ -18,22 +18,27 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBytes(obj.TxHash[:]) w.WriteBytes(obj.ReceiptHash[:]) w.WriteBytes(obj.Bloom[:]) + if obj.Difficulty == nil { w.Write(rlp.EmptyString) } else { if obj.Difficulty.Sign() == -1 { return rlp.ErrNegativeBigInt } + w.WriteBigInt(obj.Difficulty) } + if obj.Number == nil { w.Write(rlp.EmptyString) } else { if obj.Number.Sign() == -1 { return rlp.ErrNegativeBigInt } + w.WriteBigInt(obj.Number) } + w.WriteUint64(obj.GasLimit) w.WriteUint64(obj.GasUsed) w.WriteUint64(obj.Time) @@ -43,6 +48,7 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { _tmp1 := obj.BaseFee != nil _tmp2 := obj.WithdrawalsHash != nil _tmp3 := len(obj.TxDependency) > 0 + if _tmp1 || _tmp2 || _tmp3 { if obj.BaseFee == nil { w.Write(rlp.EmptyString) @@ -50,9 +56,11 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { if obj.BaseFee.Sign() == -1 { return rlp.ErrNegativeBigInt } + w.WriteBigInt(obj.BaseFee) } } + if _tmp2 { if obj.WithdrawalsHash == nil { w.Write([]byte{0x80}) @@ -60,17 +68,24 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBytes(obj.WithdrawalsHash[:]) } } + if _tmp3 { _tmp3 := w.List() + for _, _tmp4 := range obj.TxDependency { _tmp5 := w.List() + for _, _tmp6 := range _tmp4 { w.WriteUint64(_tmp6) } + w.ListEnd(_tmp5) } + w.ListEnd(_tmp3) } + w.ListEnd(_tmp0) + return w.Flush() } diff --git a/core/types/gen_log_json.go b/core/types/gen_log_json.go index 90e1c14d90..22c95359fe 100644 --- a/core/types/gen_log_json.go +++ b/core/types/gen_log_json.go @@ -25,6 +25,7 @@ func (l Log) MarshalJSON() ([]byte, error) { Index hexutil.Uint `json:"logIndex"` Removed bool `json:"removed"` } + var enc Log enc.Address = l.Address enc.Topics = l.Topics @@ -35,6 +36,7 @@ func (l Log) MarshalJSON() ([]byte, error) { enc.BlockHash = l.BlockHash enc.Index = hexutil.Uint(l.Index) enc.Removed = l.Removed + return json.Marshal(&enc) } @@ -51,40 +53,53 @@ func (l *Log) UnmarshalJSON(input []byte) error { Index *hexutil.Uint `json:"logIndex"` Removed *bool `json:"removed"` } + var dec Log if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Address == nil { return errors.New("missing required field 'address' for Log") } + l.Address = *dec.Address + if dec.Topics == nil { return errors.New("missing required field 'topics' for Log") } + l.Topics = dec.Topics + if dec.Data == nil { return errors.New("missing required field 'data' for Log") } + l.Data = *dec.Data if dec.BlockNumber != nil { l.BlockNumber = uint64(*dec.BlockNumber) } + if dec.TxHash == nil { return errors.New("missing required field 'transactionHash' for Log") } + l.TxHash = *dec.TxHash if dec.TxIndex != nil { l.TxIndex = uint(*dec.TxIndex) } + if dec.BlockHash != nil { l.BlockHash = *dec.BlockHash } + if dec.Index != nil { l.Index = uint(*dec.Index) } + if dec.Removed != nil { l.Removed = *dec.Removed } + return nil } diff --git a/core/types/gen_log_rlp.go b/core/types/gen_log_rlp.go index 4a6c6b0094..dbf7369fa3 100644 --- a/core/types/gen_log_rlp.go +++ b/core/types/gen_log_rlp.go @@ -13,11 +13,14 @@ func (obj *rlpLog) EncodeRLP(_w io.Writer) error { _tmp0 := w.List() w.WriteBytes(obj.Address[:]) _tmp1 := w.List() + for _, _tmp2 := range obj.Topics { w.WriteBytes(_tmp2[:]) } + w.ListEnd(_tmp1) w.WriteBytes(obj.Data) w.ListEnd(_tmp0) + return w.Flush() } diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index 8d85dd5b9c..7836f86e6f 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -30,6 +30,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex hexutil.Uint `json:"transactionIndex"` } + var enc Receipt enc.Type = hexutil.Uint64(r.Type) enc.PostState = r.PostState @@ -44,6 +45,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { enc.BlockHash = r.BlockHash enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) + return json.Marshal(&enc) } @@ -64,53 +66,71 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex *hexutil.Uint `json:"transactionIndex"` } + var dec Receipt 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 } + if dec.Status != nil { r.Status = uint64(*dec.Status) } + if dec.CumulativeGasUsed == nil { return errors.New("missing required field 'cumulativeGasUsed' for Receipt") } + r.CumulativeGasUsed = uint64(*dec.CumulativeGasUsed) + if dec.Bloom == nil { return errors.New("missing required field 'logsBloom' for Receipt") } + r.Bloom = *dec.Bloom + if dec.Logs == nil { return errors.New("missing required field 'logs' for Receipt") } + r.Logs = dec.Logs + if dec.TxHash == nil { return errors.New("missing required field 'transactionHash' for Receipt") } + r.TxHash = *dec.TxHash if dec.ContractAddress != nil { r.ContractAddress = *dec.ContractAddress } + if dec.GasUsed == nil { return errors.New("missing required field 'gasUsed' for Receipt") } + r.GasUsed = uint64(*dec.GasUsed) if dec.EffectiveGasPrice != nil { r.EffectiveGasPrice = (*big.Int)(dec.EffectiveGasPrice) } + if dec.BlockHash != nil { r.BlockHash = *dec.BlockHash } + if dec.BlockNumber != nil { r.BlockNumber = (*big.Int)(dec.BlockNumber) } + if dec.TransactionIndex != nil { r.TransactionIndex = uint(*dec.TransactionIndex) } + return nil } diff --git a/core/types/gen_withdrawal_json.go b/core/types/gen_withdrawal_json.go index 983a7f7a12..1b40111864 100644 --- a/core/types/gen_withdrawal_json.go +++ b/core/types/gen_withdrawal_json.go @@ -19,11 +19,13 @@ func (w Withdrawal) MarshalJSON() ([]byte, error) { Address common.Address `json:"address"` Amount hexutil.Uint64 `json:"amount"` } + var enc Withdrawal enc.Index = hexutil.Uint64(w.Index) enc.Validator = hexutil.Uint64(w.Validator) enc.Address = w.Address enc.Amount = hexutil.Uint64(w.Amount) + return json.Marshal(&enc) } @@ -35,21 +37,27 @@ func (w *Withdrawal) UnmarshalJSON(input []byte) error { Address *common.Address `json:"address"` Amount *hexutil.Uint64 `json:"amount"` } + var dec Withdrawal if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Index != nil { w.Index = uint64(*dec.Index) } + if dec.Validator != nil { w.Validator = uint64(*dec.Validator) } + if dec.Address != nil { w.Address = *dec.Address } + if dec.Amount != nil { w.Amount = uint64(*dec.Amount) } + return nil } diff --git a/core/types/gen_withdrawal_rlp.go b/core/types/gen_withdrawal_rlp.go index d0b4e0147a..433938ec60 100644 --- a/core/types/gen_withdrawal_rlp.go +++ b/core/types/gen_withdrawal_rlp.go @@ -16,5 +16,6 @@ func (obj *Withdrawal) EncodeRLP(_w io.Writer) error { w.WriteBytes(obj.Address[:]) w.WriteUint64(obj.Amount) w.ListEnd(_tmp0) + return w.Flush() } diff --git a/core/types/hashing.go b/core/types/hashing.go index fbdeaf0d07..fa2c8805e9 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -43,6 +43,7 @@ func rlpHash(x interface{}) (h common.Hash) { sha.Reset() rlp.Encode(sha, x) sha.Read(h[:]) + return h } @@ -55,6 +56,7 @@ func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { sha.Write([]byte{prefix}) rlp.Encode(sha, x) sha.Read(h[:]) + return h } @@ -102,15 +104,18 @@ func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { 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 index 7a6e57cd6d..9cfd3f4f0c 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -38,16 +38,20 @@ func TestDeriveSha(t *testing.T) { if err != nil { t.Fatal(err) } + for len(txs) < 1000 { exp := types.DeriveSha(txs, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) 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...) } } @@ -64,11 +68,14 @@ func TestEIP2718DeriveSha(t *testing.T) { }, } { 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)) } @@ -80,11 +87,15 @@ func BenchmarkDeriveSha200(b *testing.B) { 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, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) } @@ -93,10 +104,12 @@ func BenchmarkDeriveSha200(b *testing.B) { 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) } @@ -109,6 +122,7 @@ func TestFuzzDeriveSha(t *testing.T) { seed := rndSeed + i exp := types.DeriveSha(newDummy(i), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) 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) @@ -119,6 +133,7 @@ func TestFuzzDeriveSha(t *testing.T) { // TestDerivableList contains testcases found via fuzzing func TestDerivableList(t *testing.T) { type tcase []string + tcs := []tcase{ { "0xc041", @@ -137,6 +152,7 @@ func TestDerivableList(t *testing.T) { for i, tc := range tcs[1:] { exp := types.DeriveSha(flatList(tc), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { t.Fatalf("case %d: got %x exp %x", i, got, exp) } @@ -148,21 +164,28 @@ func genTxs(num uint64) (types.Transactions, error) { 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 } @@ -177,6 +200,7 @@ func newDummy(seed int) *dummyDerivableList { // don't use lists longer than 4K items d.len = int(src.Int63() & 0x0FFF) d.seed = seed + return d } @@ -194,8 +218,10 @@ func (d *dummyDerivableList) EncodeIndex(i int, w *bytes.Buffer) { 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("\"%#x\",\n", buf.Bytes()) } diff --git a/core/types/log.go b/core/types/log.go index e489191368..f20ba28fc0 100644 --- a/core/types/log.go +++ b/core/types/log.go @@ -80,9 +80,11 @@ func (l *Log) EncodeRLP(w io.Writer) error { // DecodeRLP implements rlp.Decoder. func (l *Log) DecodeRLP(s *rlp.Stream) error { var dec rlpLog + err := s.Decode(&dec) if err == nil { l.Address, l.Topics, l.Data = dec.Address, dec.Topics, dec.Data } + return err } diff --git a/core/types/log_test.go b/core/types/log_test.go index 0e56acfe4a..264e6a2c85 100644 --- a/core/types/log_test.go +++ b/core/types/log_test.go @@ -103,10 +103,12 @@ var unmarshalLogTests = map[string]struct { func TestUnmarshalLog(t *testing.T) { dumper := spew.ConfigState{DisableMethods: true, Indent: " "} + for name, test := range unmarshalLogTests { var log *Log err := json.Unmarshal([]byte(test.input), &log) checkError(t, name, err, test.wantError) + if test.wantError == nil && err == nil { if !reflect.DeepEqual(log, test.want) { t.Errorf("test %q:\nGOT %sWANT %s", name, dumper.Sdump(log), dumper.Sdump(test.want)) @@ -121,12 +123,15 @@ func checkError(t *testing.T, testname string, got, want error) bool { t.Errorf("test %q: got no error, want %q", testname, want) return false } + return true } + if want == nil { t.Errorf("test %q: unexpected error %q", testname, got) } else if got.Error() != want.Error() { t.Errorf("test %q: got error %q, want %q", testname, got, want) } + return false } diff --git a/core/types/receipt.go b/core/types/receipt.go index 61b3b35178..4f8a660cc3 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -109,6 +109,7 @@ func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt { } else { r.Status = ReceiptStatusSuccessful } + return r } @@ -119,12 +120,15 @@ func (r *Receipt) EncodeRLP(w io.Writer) error { if r.Type == LegacyTxType { return rlp.Encode(w, data) } + buf := encodeBufferPool.Get().(*bytes.Buffer) defer encodeBufferPool.Put(buf) buf.Reset() + if err := r.encodeTyped(data, buf); err != nil { return err } + return rlp.Encode(w, buf.Bytes()) } @@ -139,9 +143,12 @@ func (r *Receipt) MarshalBinary() ([]byte, error) { if r.Type == LegacyTxType { return rlp.EncodeToBytes(r) } + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + var buf bytes.Buffer err := r.encodeTyped(data, &buf) + return buf.Bytes(), err } @@ -149,6 +156,7 @@ func (r *Receipt) MarshalBinary() ([]byte, error) { // from an RLP stream. func (r *Receipt) DecodeRLP(s *rlp.Stream) error { kind, _, err := s.Kind() + switch { case err != nil: return err @@ -158,7 +166,9 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { if err := s.Decode(&dec); err != nil { return err } + r.Type = LegacyTxType + return r.setFromRLP(dec) default: // It's an EIP-2718 typed tx receipt. @@ -166,6 +176,7 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { if err != nil { return err } + return r.decodeTyped(b) } } @@ -176,11 +187,14 @@ func (r *Receipt) UnmarshalBinary(b []byte) error { if len(b) > 0 && b[0] > 0x7f { // It's a legacy receipt decode the RLP var data receiptRLP + err := rlp.DecodeBytes(b, &data) if err != nil { return err } + r.Type = LegacyTxType + return r.setFromRLP(data) } // It's an EIP2718 typed transaction envelope. @@ -192,14 +206,18 @@ func (r *Receipt) decodeTyped(b []byte) error { if len(b) <= 1 { return errShortTypedReceipt } + switch b[0] { case DynamicFeeTxType, AccessListTxType: var data receiptRLP + err := rlp.DecodeBytes(b[1:], &data) if err != nil { return err } + r.Type = b[0] + return r.setFromRLP(data) default: return ErrTxTypeNotSupported @@ -222,6 +240,7 @@ func (r *Receipt) setStatus(postStateOrStatus []byte) error { default: return fmt.Errorf("invalid receipt status %x", postStateOrStatus) } + return nil } @@ -230,8 +249,10 @@ func (r *Receipt) statusEncoding() []byte { if r.Status == ReceiptStatusFailed { return receiptStatusFailedRLP } + return receiptStatusSuccessfulRLP } + return r.PostState } @@ -240,9 +261,11 @@ func (r *Receipt) statusEncoding() []byte { 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)) } + return size } @@ -258,13 +281,16 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { w.WriteBytes((*Receipt)(r).statusEncoding()) w.WriteUint64(r.CumulativeGasUsed) logList := w.List() + for _, log := range r.Logs { if err := rlp.Encode(w, log); err != nil { return err } } + w.ListEnd(logList) w.ListEnd(outerList) + return w.Flush() } @@ -275,9 +301,11 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { if err := s.Decode(&stored); err != nil { return err } + if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { return err } + r.CumulativeGasUsed = stored.CumulativeGasUsed r.Logs = stored.Logs r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) @@ -295,6 +323,7 @@ func (rs Receipts) Len() int { return len(rs) } 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) @@ -317,9 +346,11 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu signer := MakeSigner(config, new(big.Int).SetUint64(number)) logIndex := uint(0) + if len(txs) != len(rs) { return errors.New("transaction and receipt count mismatch") } + for i := 0; i < len(rs); i++ { // The transaction type and hash can be retrieved from the transaction itself rs[i].Type = txs[i].Type() @@ -358,5 +389,6 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu logIndex++ } } + return nil } diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index d9a741db5d..e0d17e0ab0 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -86,7 +86,9 @@ var ( func TestDecodeEmptyTypedReceipt(t *testing.T) { input := []byte{0x80} + var r Receipt + err := rlp.DecodeBytes(input, &r) if err != errShortTypedReceipt { t.Fatal("wrong error:", err) @@ -252,6 +254,7 @@ func TestDeriveFields(t *testing.T) { // Re-derive receipts. basefee := big.NewInt(1000) derivedReceipts := clearComputedFieldsOnReceipts(receipts) + err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), basefee, txs) if err != nil { t.Fatalf("DeriveFields(...) = %v, want ", err) @@ -262,10 +265,12 @@ func TestDeriveFields(t *testing.T) { if err != nil { t.Fatal("error marshaling input receipts:", err) } + r2, err := json.MarshalIndent(derivedReceipts, "", " ") if err != nil { t.Fatal("error marshaling derived receipts:", err) } + d := diff.Diff(string(r1), string(r2)) if d != "" { t.Fatal("receipts differ:", d) @@ -276,8 +281,10 @@ func TestDeriveFields(t *testing.T) { // 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) @@ -286,16 +293,20 @@ func TestTypedReceiptEncodingDecoding(t *testing.T) { } { 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) } } @@ -303,25 +314,32 @@ func TestTypedReceiptEncodingDecoding(t *testing.T) { func TestReceiptMarshalBinary(t *testing.T) { // Legacy Receipt legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt}) + have, err := legacyReceipt.MarshalBinary() if err != nil { t.Fatalf("marshal binary error: %v", err) } + legacyReceipts := Receipts{legacyReceipt} buf := new(bytes.Buffer) legacyReceipts.EncodeIndex(0, buf) + haveEncodeIndex := buf.Bytes() if !bytes.Equal(have, haveEncodeIndex) { t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) } + buf.Reset() + if err := legacyReceipt.EncodeRLP(buf); err != nil { t.Fatalf("encode rlp error: %v", err) } + haveRLPEncode := buf.Bytes() if !bytes.Equal(have, haveRLPEncode) { t.Errorf("BinaryMarshal and EncodeRLP mismatch for legacy tx, got %x want %x", have, haveRLPEncode) } + legacyWant := common.FromHex("f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") if !bytes.Equal(have, legacyWant) { t.Errorf("encoded RLP mismatch, got %x want %x", have, legacyWant) @@ -329,17 +347,22 @@ func TestReceiptMarshalBinary(t *testing.T) { // 2930 Receipt buf.Reset() + accessListReceipt.Bloom = CreateBloom(Receipts{accessListReceipt}) + have, err = accessListReceipt.MarshalBinary() if err != nil { t.Fatalf("marshal binary error: %v", err) } + accessListReceipts := Receipts{accessListReceipt} accessListReceipts.EncodeIndex(0, buf) + haveEncodeIndex = buf.Bytes() if !bytes.Equal(have, haveEncodeIndex) { t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) } + accessListWant := common.FromHex("01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") if !bytes.Equal(have, accessListWant) { t.Errorf("encoded RLP mismatch, got %x want %x", have, accessListWant) @@ -347,17 +370,22 @@ func TestReceiptMarshalBinary(t *testing.T) { // 1559 Receipt buf.Reset() + eip1559Receipt.Bloom = CreateBloom(Receipts{eip1559Receipt}) + have, err = eip1559Receipt.MarshalBinary() if err != nil { t.Fatalf("marshal binary error: %v", err) } + eip1559Receipts := Receipts{eip1559Receipt} eip1559Receipts.EncodeIndex(0, buf) + haveEncodeIndex = buf.Bytes() if !bytes.Equal(have, haveEncodeIndex) { t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) } + eip1559Want := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") if !bytes.Equal(have, eip1559Want) { t.Errorf("encoded RLP mismatch, got %x want %x", have, eip1559Want) @@ -368,9 +396,11 @@ func TestReceiptUnmarshalBinary(t *testing.T) { // Legacy Receipt legacyBinary := common.FromHex("f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") gotLegacyReceipt := new(Receipt) + if err := gotLegacyReceipt.UnmarshalBinary(legacyBinary); err != nil { t.Fatalf("unmarshal binary error: %v", err) } + legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt}) if !reflect.DeepEqual(gotLegacyReceipt, legacyReceipt) { t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotLegacyReceipt, legacyReceipt) @@ -379,9 +409,11 @@ func TestReceiptUnmarshalBinary(t *testing.T) { // 2930 Receipt accessListBinary := common.FromHex("01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") gotAccessListReceipt := new(Receipt) + if err := gotAccessListReceipt.UnmarshalBinary(accessListBinary); err != nil { t.Fatalf("unmarshal binary error: %v", err) } + accessListReceipt.Bloom = CreateBloom(Receipts{accessListReceipt}) if !reflect.DeepEqual(gotAccessListReceipt, accessListReceipt) { t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotAccessListReceipt, accessListReceipt) @@ -390,9 +422,11 @@ func TestReceiptUnmarshalBinary(t *testing.T) { // 1559 Receipt eip1559RctBinary := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") got1559Receipt := new(Receipt) + if err := got1559Receipt.UnmarshalBinary(eip1559RctBinary); err != nil { t.Fatalf("unmarshal binary error: %v", err) } + eip1559Receipt.Bloom = CreateBloom(Receipts{eip1559Receipt}) if !reflect.DeepEqual(got1559Receipt, eip1559Receipt) { t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", got1559Receipt, eip1559Receipt) @@ -404,6 +438,7 @@ func clearComputedFieldsOnReceipts(receipts []*Receipt) []*Receipt { for i, receipt := range receipts { r[i] = clearComputedFieldsOnReceipt(receipt) } + return r } @@ -416,11 +451,13 @@ func clearComputedFieldsOnReceipt(receipt *Receipt) *Receipt { cpy.ContractAddress = common.Address{0xff, 0xff, 0x33} cpy.GasUsed = 0xffffffff cpy.Logs = clearComputedFieldsOnLogs(receipt.Logs) + return &cpy } func clearComputedFieldsOnLogs(logs []*Log) []*Log { l := make([]*Log, len(logs)) + for i, log := range logs { cpy := *log cpy.BlockNumber = math.MaxUint32 @@ -430,5 +467,6 @@ func clearComputedFieldsOnLogs(logs []*Log) []*Log { cpy.Index = math.MaxUint32 l[i] = &cpy } + return l } diff --git a/core/types/transaction.go b/core/types/transaction.go index 9eb18f4412..13062b94f9 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -64,6 +64,7 @@ type Transaction struct { func NewTx(inner TxData) *Transaction { tx := new(Transaction) tx.setDecoded(inner.copy(), 0) + return tx } @@ -109,9 +110,11 @@ func (tx *Transaction) EncodeRLP(w io.Writer) error { 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()) } @@ -128,35 +131,43 @@ 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 } // 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, rlp.ListSize(size)) } + return err default: // 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, uint64(len(b))) } + return err } } @@ -167,11 +178,14 @@ 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, uint64(len(b))) + return nil } // It's an EIP2718 typed transaction envelope. @@ -179,7 +193,9 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error { if err != nil { return err } + tx.setDecoded(inner, uint64(len(b))) + return nil } @@ -188,14 +204,17 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { if len(b) <= 1 { return nil, errShortTypedTx } + switch b[0] { case AccessListTxType: var inner AccessListTx err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err case DynamicFeeTxType: var inner DynamicFeeTx err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err default: return nil, ErrTxTypeNotSupported @@ -206,6 +225,7 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { func (tx *Transaction) setDecoded(inner TxData, size uint64) { tx.inner = inner tx.time = time.Now() + if size > 0 { tx.size.Store(&size) } @@ -217,6 +237,7 @@ func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int, maybeProtected boo } var plainV byte + if isProtectedV(v) { chainID := deriveChainId(v).Uint64() plainV = byte(v.Uint64() - 35 - 2*chainID) @@ -230,6 +251,7 @@ func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int, maybeProtected boo // must already be equal to the recovery id. plainV = byte(v.Uint64()) } + if !crypto.ValidateSignatureValues(plainV, r, s, false) { return ErrInvalidSig } @@ -371,11 +393,14 @@ func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) { if baseFee == nil { return tx.GasTipCap(), nil } + var err error + gasFeeCap := tx.GasFeeCap() if gasFeeCap.Cmp(baseFee) == -1 { err = ErrGasFeeCapTooLow } + return math.BigMin(tx.GasTipCap(), gasFeeCap.Sub(gasFeeCap, baseFee)), err } @@ -391,6 +416,7 @@ func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) if baseFee == nil { return tx.GasTipCapCmp(other) } + return tx.EffectiveGasTipValue(baseFee).Cmp(other.EffectiveGasTipValue(baseFee)) } @@ -399,6 +425,7 @@ func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) i if baseFee == nil { return tx.GasTipCapIntCmp(other) } + return tx.EffectiveGasTipValue(baseFee).Cmp(other) } @@ -489,6 +516,7 @@ func (tx *Transaction) Size() uint64 { if size := tx.size.Load(); size != nil { return *size } + c := writeCounter(0) rlp.Encode(&c, &tx.inner) @@ -496,7 +524,9 @@ func (tx *Transaction) Size() uint64 { if tx.Type() != LegacyTxType { size += 1 // type byte } + tx.size.Store(&size) + return size } @@ -507,8 +537,10 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e if err != nil { return nil, err } + cpy := tx.inner.copy() cpy.setSignatureValues(signer.ChainID(), v, r, s) + return &Transaction{inner: cpy, time: tx.time}, nil } @@ -589,6 +621,7 @@ func NewTxWithMinerFee(tx *Transaction, baseFee *uint256.Int) (*TxWithMinerFee, if err != nil { return nil, err } + return &TxWithMinerFee{ tx: tx, minerFee: minerFee, @@ -607,6 +640,7 @@ func (s TxByPriceAndTime) Less(i, j int) bool { if cmp == 0 { return s[i].tx.time.Before(s[j].tx.time) } + return cmp > 0 } func (s TxByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } @@ -621,6 +655,7 @@ func (s *TxByPriceAndTime) Pop() interface{} { x := old[n-1] old[n-1] = nil *s = old[0 : n-1] + return x } @@ -707,6 +742,7 @@ func (t *TransactionsByPriceAndNonce) Peek() *Transaction { if len(t.heads) == 0 { return nil } + return t.heads[0].tx } @@ -717,9 +753,11 @@ func (t *TransactionsByPriceAndNonce) Shift() { if wrapped, err := NewTxWithMinerFee(txs[0], t.baseFee); err == nil { t.heads[0], t.txs[acc] = wrapped, txs[1:] heap.Fix(&t.heads, 0) + return } } + heap.Pop(&t.heads) } @@ -739,6 +777,8 @@ func copyAddressPtr(a *common.Address) *common.Address { if a == nil { return nil } + cpy := *a + return &cpy } diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index 41f99942a0..52e73ff930 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -95,6 +95,7 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.R = (*hexutil.Big)(itx.R) enc.S = (*hexutil.Big)(itx.S) } + return json.Marshal(&enc) } @@ -108,46 +109,65 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { // 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 @@ -161,46 +181,65 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { 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 @@ -214,50 +253,71 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { 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.MaxPriorityFeePerGas == nil { return errors.New("missing required field 'maxPriorityFeePerGas' for txdata") } + itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas) + if dec.MaxFeePerGas == nil { return errors.New("missing required field 'maxFeePerGas' for txdata") } + itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas) + if dec.Gas == nil { return errors.New("missing required field 'gas' for txdata") } + 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 diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 6aa73076b8..6a9d0d7d37 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -39,6 +39,7 @@ type sigCache struct { // MakeSigner returns a Signer based on the given chain config and block number. func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { var signer Signer + switch { case config.IsLondon(blockNumber): signer = NewLondonSigner(config.ChainID) @@ -51,6 +52,7 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { default: signer = FrontierSigner{} } + return signer } @@ -66,13 +68,16 @@ func LatestSigner(config *params.ChainConfig) Signer { if config.LondonBlock != nil { return NewLondonSigner(config.ChainID) } + if config.BerlinBlock != nil { return NewEIP2930Signer(config.ChainID) } + if config.EIP155Block != nil { return NewEIP155Signer(config.ChainID) } } + return HomesteadSigner{} } @@ -87,16 +92,19 @@ func LatestSignerForChainID(chainID *big.Int) Signer { if chainID == nil { return HomesteadSigner{} } + return NewLondonSigner(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) if err != nil { return nil, err } + return tx.WithSignature(s, sig) } @@ -104,10 +112,12 @@ func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, err 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) } @@ -118,6 +128,7 @@ func MustSignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) *Transaction if err != nil { panic(err) } + return tx } @@ -186,13 +197,16 @@ func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { if tx.Type() != DynamicFeeTxType { return s.eip2930Signer.Sender(tx) } + V, R, S := tx.RawSignatureValues() // DynamicFee 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)) + if tx.ChainId().Cmp(s.chainId) != 0 { return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId) } + return recoverPlain(s.Hash(tx), R, S, V, true) } @@ -211,8 +225,10 @@ func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 { return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId) } + R, S, _ = decodeSignature(sig) V = big.NewInt(int64(sig[64])) + return R, S, V, nil } @@ -222,6 +238,7 @@ func (s londonSigner) Hash(tx *Transaction) common.Hash { if tx.Type() != DynamicFeeTxType { return s.eip2930Signer.Hash(tx) } + return prefixedRlpHash( tx.Type(), []interface{}{ @@ -256,11 +273,13 @@ func (s eip2930Signer) Equal(s2 Signer) bool { 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: @@ -270,9 +289,11 @@ func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { default: return common.Address{}, ErrTxTypeNotSupported } + if tx.ChainId().Cmp(s.chainId) != 0 { return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId) } + return recoverPlain(s.Hash(tx), R, S, V, true) } @@ -286,11 +307,13 @@ func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 { return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId) } + R, S, _ = decodeSignature(sig) V = big.NewInt(int64(sig[64])) default: return nil, nil, nil, ErrTxTypeNotSupported } + return R, S, V, nil } @@ -340,6 +363,7 @@ func NewEIP155Signer(chainId *big.Int) EIP155Signer { if chainId == nil { chainId = new(big.Int) } + return EIP155Signer{ chainId: chainId, chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)), @@ -361,15 +385,19 @@ 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{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId) } + V, R, S := tx.RawSignatureValues() V = new(big.Int).Sub(V, s.chainIdMul) V.Sub(V, big8) + return recoverPlain(s.Hash(tx), R, S, V, true) } @@ -379,11 +407,13 @@ func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big 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) } + return R, S, V, nil } @@ -424,7 +454,9 @@ func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) { if tx.Type() != LegacyTxType { return common.Address{}, ErrTxTypeNotSupported } + v, r, s := tx.RawSignatureValues() + return recoverPlain(hs.Hash(tx), r, s, v, true) } @@ -445,7 +477,9 @@ 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) } @@ -455,7 +489,9 @@ func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v * if tx.Type() != LegacyTxType { return nil, nil, nil, ErrTxTypeNotSupported } + r, s, v = decodeSignature(sig) + return r, s, v, nil } @@ -512,9 +548,11 @@ 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 } @@ -522,6 +560,7 @@ func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (commo if Vb.BitLen() > 8 { return common.Address{}, ErrInvalidSig } + V := byte(Vb.Uint64() - 27) if !crypto.ValidateSignatureValues(V, R, S, homestead) { return common.Address{}, ErrInvalidSig @@ -537,11 +576,15 @@ func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (commo if err != nil { return common.Address{}, err } + if len(pub) == 0 || pub[0] != 4 { return common.Address{}, errors.New("invalid public key") } + var addr common.Address + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr, nil } @@ -552,8 +595,11 @@ func deriveChainId(v *big.Int) *big.Int { if v == 27 || v == 28 { return new(big.Int) } + return new(big.Int).SetUint64((v - 35) / 2) } + v = new(big.Int).Sub(v, big.NewInt(35)) + return v.Div(v, big.NewInt(2)) } diff --git a/core/types/transaction_signing_test.go b/core/types/transaction_signing_test.go index 2a9ceb0952..eee933f1b7 100644 --- a/core/types/transaction_signing_test.go +++ b/core/types/transaction_signing_test.go @@ -31,6 +31,7 @@ func TestEIP155Signing(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) signer := NewEIP155Signer(big.NewInt(18)) + tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil), signer, key) if err != nil { t.Fatal(err) @@ -40,6 +41,7 @@ func TestEIP155Signing(t *testing.T) { if err != nil { t.Fatal(err) } + if from != addr { t.Errorf("exected from and address to be equal. Got %x want %x", from, addr) } @@ -50,10 +52,12 @@ func TestEIP155ChainId(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) signer := NewEIP155Signer(big.NewInt(18)) + tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil), signer, key) if err != nil { t.Fatal(err) } + if !tx.Protected() { t.Fatal("expected tx to be protected") } @@ -63,6 +67,7 @@ func TestEIP155ChainId(t *testing.T) { } tx = NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil) + tx, err = SignTx(tx, HomesteadSigner{}, key) if err != nil { t.Fatal(err) @@ -96,6 +101,7 @@ func TestEIP155SigningVitalik(t *testing.T) { signer := NewEIP155Signer(big.NewInt(1)) var tx *Transaction + err := rlp.DecodeBytes(common.Hex2Bytes(test.txRlp), &tx) if err != nil { t.Errorf("%d: %v", i, err) @@ -121,6 +127,7 @@ func TestChainId(t *testing.T) { tx := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil) var err error + tx, err = SignTx(tx, NewEIP155Signer(big.NewInt(1)), key) if err != nil { t.Fatal(err) diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index c2e7d7705f..7806907d2f 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -78,7 +78,9 @@ var ( func TestDecodeEmptyTypedTx(t *testing.T) { input := []byte{0x80} + var tx Transaction + err := rlp.DecodeBytes(input, &tx) if err != errShortTypedTx { t.Fatal("wrong error:", err) @@ -90,6 +92,7 @@ func TestTransactionSigHash(t *testing.T) { if homestead.Hash(emptyTx) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") { t.Errorf("empty transaction hash mismatch, got %x", emptyTx.Hash()) } + if homestead.Hash(rightvrsTx) != common.HexToHash("fe7a79529ed5f7c3375d06b26b186a8644e0e16c373d7a12be41c62d6042b77a") { t.Errorf("RightVRS transaction hash mismatch, got %x", rightvrsTx.Hash()) } @@ -100,6 +103,7 @@ func TestTransactionEncode(t *testing.T) { if err != nil { t.Fatalf("encode error: %v", err) } + should := common.FromHex("f86103018207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a8255441ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa08887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3") if !bytes.Equal(txb, should) { t.Errorf("encoded RLP mismatch, got %x", txb) @@ -111,6 +115,7 @@ func TestEIP2718TransactionSigHash(t *testing.T) { 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)) } @@ -173,17 +178,21 @@ func TestEIP2930Signer(t *testing.T) { 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 !errors.Is(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 !errors.Is(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) @@ -199,6 +208,7 @@ func TestEIP2718TransactionEncode(t *testing.T) { 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) @@ -210,6 +220,7 @@ func TestEIP2718TransactionEncode(t *testing.T) { 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) @@ -220,17 +231,20 @@ func TestEIP2718TransactionEncode(t *testing.T) { func decodeTx(data []byte) (*Transaction, error) { var tx Transaction t, err := &tx, rlp.Decode(bytes.NewReader(data), &tx) + return t, err } func defaultTestKey() (*ecdsa.PrivateKey, common.Address) { key, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") addr := crypto.PubkeyToAddress(key.PublicKey) + return key, addr } func TestRecipientEmpty(t *testing.T) { _, addr := defaultTestKey() + tx, err := decodeTx(common.Hex2Bytes("f8498080808080011ca09b16de9d5bdee2cf56c28d16275a4da68cd30273e2525f3959f5d62557489921a0372ebd8fb3345f7db7b5a86d42e24d36e983e259b0664ceb8c227ec9af572f3d")) if err != nil { t.Fatal(err) @@ -240,6 +254,7 @@ func TestRecipientEmpty(t *testing.T) { if err != nil { t.Fatal(err) } + if addr != from { t.Fatal("derived address doesn't match") } @@ -257,6 +272,7 @@ func TestRecipientNormal(t *testing.T) { if err != nil { t.Fatal(err) } + if addr != from { t.Fatal("derived address doesn't match") } @@ -294,11 +310,14 @@ func testTransactionPriceNonceSort(t *testing.T, baseFeeBig *big.Int) { // Generate a batch of transactions with overlapping values, but shifted nonces groups := map[common.Address]Transactions{} expectedCount := 0 + for start, key := range keys { addr := crypto.PubkeyToAddress(key.PublicKey) count := 25 + for i := 0; i < 25; i++ { var tx *Transaction + gasFeeCap := rand.Intn(50) if baseFee == nil { tx = NewTx(&LegacyTx{ @@ -319,16 +338,20 @@ func testTransactionPriceNonceSort(t *testing.T, baseFeeBig *big.Int) { GasTipCap: big.NewInt(int64(rand.Intn(gasFeeCap + 1))), Data: nil, }) + if count == 25 && uint64(gasFeeCap) < baseFee.Uint64() { count = i } } + tx, err := SignTx(tx, signer, key) if err != nil { t.Fatalf("failed to sign tx: %s", err) } + groups[addr] = append(groups[addr], tx) } + expectedCount += count } // Sort the transactions and cross check the nonce ordering @@ -337,11 +360,14 @@ func testTransactionPriceNonceSort(t *testing.T, baseFeeBig *big.Int) { txs := Transactions{} for tx := txset.Peek(); tx != nil; tx = txset.Peek() { txs = append(txs, tx) + txset.Shift() } + if len(txs) != expectedCount { t.Errorf("expected %d transactions, found %d", expectedCount, len(txs)) } + for i, txi := range txs { fromi, _ := Sender(signer, txi) @@ -374,6 +400,7 @@ func testTransactionPriceNonceSort(t *testing.T, baseFeeBig *big.Int) { if err != nil || nextErr != nil { t.Errorf("error calculating effective tip") } + if fromi != fromNext && tip.Cmp(nextTip) < 0 { t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice()) } @@ -389,10 +416,12 @@ func TestTransactionTimeSort(t *testing.T) { for i := 0; i < len(keys); i++ { keys[i], _ = crypto.GenerateKey() } + signer := HomesteadSigner{} // Generate a batch of transactions with overlapping prices, but different creation times groups := map[common.Address]Transactions{} + for start, key := range keys { addr := crypto.PubkeyToAddress(key.PublicKey) @@ -407,13 +436,17 @@ func TestTransactionTimeSort(t *testing.T) { txs := Transactions{} for tx := txset.Peek(); tx != nil; tx = txset.Peek() { txs = append(txs, tx) + txset.Shift() } + if len(txs) != len(keys) { t.Errorf("expected %d transactions, found %d", len(keys), len(txs)) } + for i, txi := range txs { fromi, _ := Sender(signer, txi) + if i+1 < len(txs) { next := txs[i+1] fromNext, _ := Sender(signer, next) @@ -435,14 +468,17 @@ func TestTransactionCoding(t *testing.T) { if err != nil { t.Fatalf("could not generate key: %v", err) } + 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: // Legacy tx. @@ -492,6 +528,7 @@ func TestTransactionCoding(t *testing.T) { AccessList: accesses, } } + tx, err := SignNewTx(key, signer, txdata) if err != nil { t.Fatalf("could not sign transaction: %v", err) @@ -501,6 +538,7 @@ func TestTransactionCoding(t *testing.T) { if err != nil { t.Fatal(err) } + if err := assertEqual(parsedTx, tx); err != nil { t.Fatal(err) } @@ -510,6 +548,7 @@ func TestTransactionCoding(t *testing.T) { if err != nil { t.Fatal(err) } + if err := assertEqual(parsedTx, tx); err != nil { t.Fatal(err) } @@ -521,10 +560,12 @@ func encodeDecodeJSON(tx *Transaction) (*Transaction, error) { 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 } @@ -533,10 +574,12 @@ func encodeDecodeBinary(tx *Transaction) (*Transaction, error) { 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 } @@ -545,14 +588,17 @@ func assertEqual(orig *Transaction, cpy *Transaction) error { 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 } @@ -562,6 +608,7 @@ func TestTransactionSizes(t *testing.T) { signer := NewLondonSigner(big.NewInt(123)) key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") to := common.HexToAddress("0x01") + for i, txdata := range []TxData{ &AccessListTx{ ChainID: big.NewInt(123), @@ -605,6 +652,7 @@ func TestTransactionSizes(t *testing.T) { if err != nil { t.Fatalf("test %d: %v", i, err) } + bin, _ := tx.MarshalBinary() // Check initial calc @@ -620,6 +668,7 @@ func TestTransactionSizes(t *testing.T) { if err := utx.UnmarshalBinary(bin); err != nil { t.Fatalf("test %d: failed to unmarshal tx: %v", i, err) } + if have, want := int(utx.Size()), len(bin); have != want { t.Errorf("test %d: (unmarshalled) size wrong, have %d want %d", i, have, want) } diff --git a/core/types/tx_access_list.go b/core/types/tx_access_list.go index dee144c27e..b024efa46b 100644 --- a/core/types/tx_access_list.go +++ b/core/types/tx_access_list.go @@ -41,6 +41,7 @@ func (al AccessList) StorageKeys() int { for _, tuple := range al { sum += len(tuple.StorageKeys) } + return sum } @@ -75,12 +76,15 @@ func (tx *AccessListTx) copy() TxData { 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) @@ -90,15 +94,19 @@ func (tx *AccessListTx) copy() TxData { cpy.gasPriceUint256, _ = uint256.FromBig(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 } diff --git a/core/types/tx_dynamic_fee.go b/core/types/tx_dynamic_fee.go index 661a78dc37..914d4304b0 100644 --- a/core/types/tx_dynamic_fee.go +++ b/core/types/tx_dynamic_fee.go @@ -61,12 +61,15 @@ func (tx *DynamicFeeTx) copy() TxData { 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.GasTipCap != nil { cpy.GasTipCap.Set(tx.GasTipCap) @@ -76,6 +79,7 @@ func (tx *DynamicFeeTx) copy() TxData { cpy.gasTipCapUint256, _ = uint256.FromBig(tx.GasTipCap) } } + if tx.GasFeeCap != nil { cpy.GasFeeCap.Set(tx.GasFeeCap) @@ -85,15 +89,19 @@ func (tx *DynamicFeeTx) copy() TxData { cpy.gasFeeCapUint256, _ = uint256.FromBig(tx.GasFeeCap) } } + 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 } @@ -141,10 +149,12 @@ func (tx *DynamicFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.I if baseFee == nil { return dst.Set(tx.GasFeeCap) } + tip := dst.Sub(tx.GasFeeCap, baseFee) if tip.Cmp(tx.GasTipCap) > 0 { tip.Set(tx.GasTipCap) } + return tip.Add(tip, baseFee) } diff --git a/core/types/tx_legacy.go b/core/types/tx_legacy.go index 11e4f25ca7..1e46eda5d2 100644 --- a/core/types/tx_legacy.go +++ b/core/types/tx_legacy.go @@ -78,6 +78,7 @@ func (tx *LegacyTx) copy() TxData { if tx.Value != nil { cpy.Value.Set(tx.Value) } + if tx.GasPrice != nil { cpy.GasPrice.Set(tx.GasPrice) @@ -87,15 +88,19 @@ func (tx *LegacyTx) copy() TxData { cpy.gasPriceUint256, _ = uint256.FromBig(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 } diff --git a/core/types/types_test.go b/core/types/types_test.go index 1fb386d5de..5517498942 100644 --- a/core/types/types_test.go +++ b/core/types/types_test.go @@ -44,6 +44,7 @@ func benchRLP(b *testing.B, encode bool) { key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") to := common.HexToAddress("0x00000000000000000000000000000000deadbeef") signer := NewLondonSigner(big.NewInt(1337)) + for _, tc := range []struct { name string obj interface{} @@ -125,6 +126,7 @@ func benchRLP(b *testing.B, encode bool) { if encode { b.Run(tc.name, func(b *testing.B) { b.ReportAllocs() + var null = &devnull{} for i := 0; i < b.N; i++ { rlp.Encode(null, tc.obj) @@ -136,6 +138,7 @@ func benchRLP(b *testing.B, encode bool) { // Test decoding b.Run(tc.name, func(b *testing.B) { b.ReportAllocs() + for i := 0; i < b.N; i++ { if err := rlp.DecodeBytes(data, tc.obj); err != nil { b.Fatal(err) diff --git a/core/vm/analysis.go b/core/vm/analysis.go index 1f40da2331..fcf9c1e38f 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -37,6 +37,7 @@ func (bits bitvec) set1(pos uint64) { func (bits bitvec) setN(flag uint16, pos uint64) { a := flag << (pos % 8) bits[pos/8] |= byte(a) + if b := byte(a >> 8); b != 0 { bits[pos/8+1] = b } @@ -80,17 +81,20 @@ func codeBitmapInternal(code, bits bitvec) bitvec { if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false). continue } + numbits := op - PUSH1 + 1 if numbits >= 8 { for ; numbits >= 16; numbits -= 16 { bits.set16(pc) pc += 16 } + for ; numbits >= 8; numbits -= 8 { bits.set8(pc) pc += 8 } } + switch numbits { case 1: bits.set1(pc) @@ -115,5 +119,6 @@ func codeBitmapInternal(code, bits bitvec) bitvec { pc += 7 } } + return bits } diff --git a/core/vm/analysis_test.go b/core/vm/analysis_test.go index 398861f8ae..ab37d0dace 100644 --- a/core/vm/analysis_test.go +++ b/core/vm/analysis_test.go @@ -66,6 +66,7 @@ func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) { code := make([]byte, analysisCodeSize) bench.SetBytes(analysisCodeSize) bench.ResetTimer() + for i := 0; i < bench.N; i++ { codeBitmap(code) } @@ -76,6 +77,7 @@ func BenchmarkJumpdestHashing_1200k(bench *testing.B) { code := make([]byte, analysisCodeSize) bench.SetBytes(analysisCodeSize) bench.ResetTimer() + for i := 0; i < bench.N; i++ { crypto.Keccak256Hash(code) } @@ -84,24 +86,31 @@ func BenchmarkJumpdestHashing_1200k(bench *testing.B) { func BenchmarkJumpdestOpAnalysis(bench *testing.B) { var op OpCode + bencher := func(b *testing.B) { code := make([]byte, analysisCodeSize) b.SetBytes(analysisCodeSize) + for i := range code { code[i] = byte(op) } + bits := make(bitvec, len(code)/8+1+4) + b.ResetTimer() + for i := 0; i < b.N; i++ { for j := range bits { bits[j] = 0 } + codeBitmapInternal(code, bits) } } for op = PUSH1; op <= PUSH32; op++ { bench.Run(op.String(), bencher) } + op = JUMPDEST bench.Run(op.String(), bencher) op = STOP diff --git a/core/vm/common.go b/core/vm/common.go index 90ba4a4ad1..3ff3354198 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -28,6 +28,7 @@ func calcMemSize64(off, l *uint256.Int) (uint64, bool) { if !l.IsUint64() { return 0, true } + return calcMemSize64WithUint(off, l.Uint64()) } @@ -44,6 +45,7 @@ func calcMemSize64WithUint(off *uint256.Int, length64 uint64) (uint64, bool) { if overflow { return 0, true } + val := offset64 + length64 // if value < either of it's parts, then it overflowed return val, val < offset64 @@ -56,10 +58,12 @@ func getData(data []byte, start uint64, size uint64) []byte { if start > length { start = length } + end := start + size if end > length { end = length } + return common.RightPadBytes(data[start:end], int(size)) } @@ -78,5 +82,6 @@ func allZero(b []byte) bool { return false } } + return true } diff --git a/core/vm/contract.go b/core/vm/contract.go index bb0902969e..811f5b7816 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -93,6 +93,7 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool { if OpCode(c.Code[udest]) != JUMPDEST { return false } + return c.isCode(udest) } @@ -117,6 +118,7 @@ func (c *Contract) isCode(udest uint64) bool { } // Also stash it in current contract for faster access c.analysis = analysis + return analysis.codeSegment(udest) } // We don't have the code hash, most likely a piece of initcode not already @@ -126,6 +128,7 @@ func (c *Contract) isCode(udest uint64) bool { if c.analysis == nil { c.analysis = codeBitmap(c.Code) } + return c.analysis.codeSegment(udest) } @@ -163,7 +166,9 @@ func (c *Contract) UseGas(gas uint64) (ok bool) { if c.Gas < gas { return false } + c.Gas -= gas + return true } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 5138702eaf..ba7539737c 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -115,12 +115,15 @@ func init() { for k := range PrecompiledContractsHomestead { PrecompiledAddressesHomestead = append(PrecompiledAddressesHomestead, k) } + for k := range PrecompiledContractsByzantium { PrecompiledAddressesByzantium = append(PrecompiledAddressesByzantium, k) } + for k := range PrecompiledContractsIstanbul { PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) } + for k := range PrecompiledContractsBerlin { PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) } @@ -150,8 +153,10 @@ func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uin if suppliedGas < gasCost { return nil, 0, ErrOutOfGas } + suppliedGas -= gasCost output, err := p.Run(input) + return output, suppliedGas, err } @@ -221,6 +226,7 @@ func (c *ripemd160hash) RequiredGas(input []byte) uint64 { func (c *ripemd160hash) Run(input []byte) ([]byte, error) { ripemd := ripemd160.New() ripemd.Write(input) + return common.LeftPadBytes(ripemd.Sum(nil), 32), nil } @@ -287,6 +293,7 @@ func modexpMultComplexity(x *big.Int) *big.Int { new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680), ) } + return x } @@ -297,6 +304,7 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { expLen = new(big.Int).SetBytes(getData(input, 32, 32)) modLen = new(big.Int).SetBytes(getData(input, 64, 32)) ) + if len(input) > 96 { input = input[96:] } else { @@ -318,11 +326,13 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { if bitlen := expHead.BitLen(); bitlen > 0 { msb = bitlen - 1 } + adjExpLen := new(big.Int) if expLen.Cmp(big32) > 0 { adjExpLen.Sub(expLen, big32) 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)) @@ -342,6 +352,7 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { gas.Mul(gas, math.BigMax(adjExpLen, big1)) // 2. Different divisor (`GQUADDIVISOR`) (3) gas.Div(gas, big3) + if gas.BitLen() > 64 { return math.MaxUint64 } @@ -349,8 +360,10 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { if gas.Uint64() < 200 { return 200 } + return gas.Uint64() } + gas = modexpMultComplexity(gas) gas.Mul(gas, math.BigMax(adjExpLen, big1)) gas.Div(gas, big20) @@ -358,6 +371,7 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { if gas.BitLen() > 64 { return math.MaxUint64 } + return gas.Uint64() } @@ -367,6 +381,7 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) { expLen = new(big.Int).SetBytes(getData(input, 32, 32)).Uint64() modLen = new(big.Int).SetBytes(getData(input, 64, 32)).Uint64() ) + if len(input) > 96 { input = input[96:] } else { @@ -383,6 +398,7 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) { mod = new(big.Int).SetBytes(getData(input, baseLen+expLen, modLen)) v []byte ) + switch { case mod.BitLen() == 0: // Modulo 0 is undefined, return zero @@ -393,6 +409,7 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) { default: v = base.Exp(base, exp, mod).Bytes() } + return common.LeftPadBytes(v, int(modLen)), nil } @@ -403,6 +420,7 @@ func newCurvePoint(blob []byte) (*bn256.G1, error) { if _, err := p.Unmarshal(blob); err != nil { return nil, err } + return p, nil } @@ -413,6 +431,7 @@ func newTwistPoint(blob []byte) (*bn256.G2, error) { if _, err := p.Unmarshal(blob); err != nil { return nil, err } + return p, nil } @@ -423,12 +442,15 @@ func runBn256Add(input []byte) ([]byte, error) { if err != nil { return nil, err } + y, err := newCurvePoint(getData(input, 64, 64)) if err != nil { return nil, err } + res := new(bn256.G1) res.Add(x, y) + return res.Marshal(), nil } @@ -465,8 +487,10 @@ func runBn256ScalarMul(input []byte) ([]byte, error) { if err != nil { return nil, err } + res := new(bn256.G1) res.ScalarMult(p, new(big.Int).SetBytes(getData(input, 64, 32))) + return res.Marshal(), nil } @@ -519,15 +543,18 @@ func runBn256Pairing(input []byte) ([]byte, error) { cs []*bn256.G1 ts []*bn256.G2 ) + for i := 0; i < len(input); i += 192 { c, err := newCurvePoint(input[i : i+64]) if err != nil { return nil, err } + t, err := newTwistPoint(input[i+64 : i+192]) if err != nil { return nil, err } + cs = append(cs, c) ts = append(ts, t) } @@ -535,6 +562,7 @@ func runBn256Pairing(input []byte) ([]byte, error) { if bn256.PairingCheck(cs, ts) { return true32Byte, nil } + return false32Byte, nil } @@ -572,6 +600,7 @@ func (c *blake2F) RequiredGas(input []byte) uint64 { if len(input) != blake2FInputLength { return 0 } + return uint64(binary.BigEndian.Uint32(input[0:4])) } @@ -591,6 +620,7 @@ func (c *blake2F) Run(input []byte) ([]byte, error) { if len(input) != blake2FInputLength { return nil, errBlake2FInvalidInputLength } + if input[212] != blake2FNonFinalBlockBytes && input[212] != blake2FFinalBlockBytes { return nil, errBlake2FInvalidFinalFlag } @@ -603,14 +633,17 @@ func (c *blake2F) Run(input []byte) ([]byte, error) { m [16]uint64 t [2]uint64 ) + for i := 0; i < 8; i++ { offset := 4 + i*8 h[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) } + for i := 0; i < 16; i++ { offset := 68 + i*8 m[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) } + t[0] = binary.LittleEndian.Uint64(input[196:204]) t[1] = binary.LittleEndian.Uint64(input[204:212]) @@ -618,10 +651,12 @@ func (c *blake2F) Run(input []byte) ([]byte, error) { blake2b.F(&h, m, t, final, rounds) output := make([]byte, 64) + for i := 0; i < 8; i++ { offset := i * 8 binary.LittleEndian.PutUint64(output[offset:offset+8], h[i]) } + return output, nil } @@ -647,7 +682,9 @@ func (c *bls12381G1Add) Run(input []byte) ([]byte, error) { if len(input) != 256 { return nil, errBLS12381InvalidInputLength } + var err error + var p0, p1 *bls12381.PointG1 // Initialize G1 @@ -685,7 +722,9 @@ func (c *bls12381G1Mul) Run(input []byte) ([]byte, error) { if len(input) != 160 { return nil, errBLS12381InvalidInputLength } + var err error + var p0 *bls12381.PointG1 // Initialize G1 @@ -733,10 +772,13 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) { // G1 multiplication call expects `160*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). // Output is an encoding of multiexponentiation operation result - single G1 point (`128` bytes). k := len(input) / 160 + if len(input) == 0 || len(input)%160 != 0 { return nil, errBLS12381InvalidInputLength } + var err error + points := make([]*bls12381.PointG1, k) scalars := make([]*big.Int, k) @@ -778,7 +820,9 @@ func (c *bls12381G2Add) Run(input []byte) ([]byte, error) { if len(input) != 512 { return nil, errBLS12381InvalidInputLength } + var err error + var p0, p1 *bls12381.PointG2 // Initialize G2 @@ -816,7 +860,9 @@ func (c *bls12381G2Mul) Run(input []byte) ([]byte, error) { if len(input) != 288 { return nil, errBLS12381InvalidInputLength } + var err error + var p0 *bls12381.PointG2 // Initialize G2 @@ -864,10 +910,13 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) { // > G2 multiplication call expects `288*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). // > Output is an encoding of multiexponentiation operation result - single G2 point (`256` bytes). k := len(input) / 288 + if len(input) == 0 || len(input)%288 != 0 { return nil, errBLS12381InvalidInputLength } + var err error + points := make([]*bls12381.PointG2, k) scalars := make([]*big.Int, k) @@ -910,6 +959,7 @@ func (c *bls12381Pairing) Run(input []byte) ([]byte, error) { // > Output is a `32` bytes where last single byte is `0x01` if pairing result is equal to multiplicative identity in a pairing target field and `0x00` otherwise // > (which is equivalent of Big Endian encoding of Solidity values `uint256(1)` and `uin256(0)` respectively). k := len(input) / 384 + if len(input) == 0 || len(input)%384 != 0 { return nil, errBLS12381InvalidInputLength } @@ -939,6 +989,7 @@ func (c *bls12381Pairing) Run(input []byte) ([]byte, error) { if !g1.InCorrectSubgroup(p1) { return nil, errBLS12381G1PointSubgroup } + if !g2.InCorrectSubgroup(p2) { return nil, errBLS12381G2PointSubgroup } @@ -953,6 +1004,7 @@ func (c *bls12381Pairing) Run(input []byte) ([]byte, error) { if e.Check() { out[31] = 1 } + return out, nil } @@ -968,8 +1020,10 @@ func decodeBLS12381FieldElement(in []byte) ([]byte, error) { return nil, errBLS12381InvalidFieldElementTopBytes } } + out := make([]byte, 48) copy(out[:], in[16:]) + return out, nil } @@ -1026,15 +1080,19 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { // Decode input field element fe := make([]byte, 96) + c0, err := decodeBLS12381FieldElement(input[:64]) if err != nil { return nil, err } + copy(fe[48:], c0) + c1, err := decodeBLS12381FieldElement(input[64:]) if err != nil { return nil, err } + copy(fe[:48], c1) // Initialize G2 diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index b22d999e6c..53f8de5945 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -101,6 +101,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) } + if expGas := test.Gas; expGas != gas { t.Errorf("%v: gas wrong, expected %d, got %d", test.Name, expGas, gas) } @@ -134,6 +135,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing p := allPrecompiles[common.HexToAddress(addr)] in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) + t.Run(test.Name, func(t *testing.T) { _, _, err := RunPrecompiledContract(p, in, gas) if err.Error() != test.ExpectedError { @@ -151,6 +153,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { if test.NoBenchmark { return } + p := allPrecompiles[common.HexToAddress(addr)] in := common.Hex2Bytes(test.Input) reqGas := p.RequiredGas(in) @@ -163,17 +166,22 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) { bench.ReportAllocs() + start := time.Now() + bench.ResetTimer() + for i := 0; i < bench.N; i++ { copy(data, in) res, _, err = RunPrecompiledContract(p, data, reqGas) } bench.StopTimer() + elapsed := uint64(time.Since(start)) if elapsed < 1 { elapsed = 1 } + gasUsed := reqGas * uint64(bench.N) bench.ReportMetric(float64(reqGas), "gas/op") // Keep it as uint64, multiply 100 to get two digit float later @@ -184,6 +192,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.Error(err) return } + if common.Bytes2Hex(res) != test.Expected { bench.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) return @@ -248,6 +257,7 @@ func TestPrecompiledModExpOOG(t *testing.T) { if err != nil { t.Fatal(err) } + for _, test := range modexpTests { testPrecompiledOOG("05", test, t) } @@ -277,6 +287,7 @@ func testJson(name, addr string, t *testing.T) { if err != nil { t.Fatal(err) } + for _, test := range tests { testPrecompiled(addr, test, t) } @@ -287,6 +298,7 @@ func testJsonFail(name, addr string, t *testing.T) { if err != nil { t.Fatal(err) } + for _, test := range tests { testPrecompiledFailure(addr, test, t) } @@ -297,6 +309,7 @@ func benchJson(name, addr string, b *testing.B) { if err != nil { b.Fatal(err) } + for _, test := range tests { benchmarkPrecompiled(addr, test, b) } @@ -338,8 +351,10 @@ func loadJson(name string) ([]precompiledTest, error) { if err != nil { return nil, err } + var testcases []precompiledTest err = json.Unmarshal(data, &testcases) + return testcases, err } @@ -348,8 +363,10 @@ func loadJsonFail(name string) ([]precompiledFailureTest, error) { if err != nil { return nil, err } + var testcases []precompiledFailureTest err = json.Unmarshal(data, &testcases) + return testcases, err } @@ -359,9 +376,11 @@ func BenchmarkPrecompiledBLS12381G1MultiExpWorstCase(b *testing.B) { "0000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" input := task + for i := 0; i < 4787; i++ { input = input + task } + testcase := precompiledTest{ Input: input, Expected: "0000000000000000000000000000000005a6310ea6f2a598023ae48819afc292b4dfcb40aabad24a0c2cb6c19769465691859eeb2a764342a810c5038d700f18000000000000000000000000000000001268ac944437d15923dc0aec00daa9250252e43e4b35ec7a19d01f0d6cd27f6e139d80dae16ba1c79cc7f57055a93ff5", @@ -379,6 +398,7 @@ func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) { "000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" input := task + for i := 0; i < 1040; i++ { input = input + task } diff --git a/core/vm/eips.go b/core/vm/eips.go index 6d4f37eb7a..85a2f41baa 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -45,7 +45,9 @@ func EnableEIP(eipNum int, jt *JumpTable) error { if !ok { return fmt.Errorf("undefined eip %d", eipNum) } + enablerFn(jt) + return nil } @@ -58,7 +60,9 @@ func ActivateableEips() []string { for k := range activators { nums = append(nums, fmt.Sprintf("%d", k)) } + sort.Strings(nums) + return nums } @@ -85,6 +89,7 @@ func enable1884(jt *JumpTable) { func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(scope.Contract.Address())) scope.Stack.push(balance) + return nil, nil } @@ -104,6 +109,7 @@ func enable1344(jt *JumpTable) { func opChainID(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID) scope.Stack.push(chainId) + return nil, nil } @@ -220,6 +226,7 @@ func opTstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { baseFee, _ := uint256.FromBig(interpreter.evm.Context.BaseFee) scope.Stack.push(baseFee) + return nil, nil } diff --git a/core/vm/evm.go b/core/vm/evm.go index aa601f812b..9c26831bc6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -43,6 +43,7 @@ type ( func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract + switch { case evm.chainRules.IsBerlin: precompiles = PrecompiledContractsBerlin @@ -53,7 +54,9 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { default: precompiles = PrecompiledContractsHomestead } + p, ok := precompiles[addr] + return p, ok } @@ -135,6 +138,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } evm.interpreter = NewEVMInterpreter(evm) + return evm } @@ -182,6 +186,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } + snapshot := evm.StateDB.Snapshot() p, isPrecompile := evm.precompile(addr) debug := evm.Config.Tracer != nil @@ -198,10 +203,13 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas evm.Config.Tracer.CaptureExit(ret, 0, nil) } } + return nil, gas, nil } + evm.StateDB.CreateAccount(addr) } + evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode @@ -243,6 +251,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // when we're in homestead this also counts for code storage gas errors. if err != nil { evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { gas = 0 } @@ -250,6 +259,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas //} else { // evm.StateDB.DiscardSnapshot(snapshot) } + return ret, gas, err } @@ -272,6 +282,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } + var snapshot = evm.StateDB.Snapshot() // Invoke tracer hooks that signal entering/exiting a call frame @@ -294,12 +305,15 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, ret, err = evm.interpreter.PreRun(contract, input, false, nil) gas = contract.Gas } + if err != nil { evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { gas = 0 } } + return ret, gas, err } @@ -313,6 +327,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + var snapshot = evm.StateDB.Snapshot() // Invoke tracer hooks that signal entering/exiting a call frame @@ -338,12 +353,15 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by ret, err = evm.interpreter.PreRun(contract, input, false, nil) gas = contract.Gas } + if err != nil { evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { gas = 0 } } + return ret, gas, err } @@ -394,12 +412,15 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte ret, err = evm.interpreter.PreRun(contract, input, true, nil) gas = contract.Gas } + if err != nil { evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { gas = 0 } } + return ret, gas, err } @@ -412,6 +433,7 @@ func (c *codeAndHash) Hash() common.Hash { if c.hash == (common.Hash{}) { c.hash = crypto.Keccak256Hash(c.code) } + return c.hash } @@ -422,13 +444,16 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.depth > int(params.CallCreateDepth) { return nil, common.Address{}, gas, ErrDepth } + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } + nonce := evm.StateDB.GetNonce(caller.Address()) if nonce+1 < nonce { return nil, common.Address{}, gas, ErrNonceUintOverflow } + 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 @@ -443,9 +468,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Create a new account on the state snapshot := evm.StateDB.Snapshot() evm.StateDB.CreateAccount(address) + if evm.chainRules.IsEIP158 { evm.StateDB.SetNonce(address, 1) } + 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. @@ -491,6 +518,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // when we're in homestead this also counts for code storage gas errors. if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { contract.UseGas(contract.Gas) } @@ -503,6 +531,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.Config.Tracer.CaptureExit(ret, gas-contract.Gas, err) } } + return ret, address, contract.Gas, err } @@ -519,6 +548,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I 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(), salt.Bytes32(), codeAndHash.Hash().Bytes()) + return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2) } diff --git a/core/vm/gas.go b/core/vm/gas.go index 5cf1d852d2..a076ec6187 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -45,6 +45,7 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u return gas, nil } } + if !callCost.IsUint64() { return 0, ErrGasUintOverflow } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index dfd44d2ac9..9e2532f529 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -38,6 +38,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { if newMemSize > 0x1FFFFFFFE0 { return 0, ErrGasUintOverflow } + newMemSizeWords := toWordSize(newMemSize) newMemSize = newMemSizeWords * 32 @@ -52,6 +53,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { return fee, nil } + return 0, nil } @@ -82,6 +84,7 @@ func memoryCopierGas(stackpos int) gasFunc { if gas, overflow = math.SafeAdd(gas, words); overflow { return 0, ErrGasUintOverflow } + return gas, nil } } @@ -136,16 +139,20 @@ 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(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.NetSstoreInitGas, nil } + if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(params.NetSstoreClearRefund) } + return params.NetSstoreCleanGas, nil // write existing slot (2.1.2) } + if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) evm.StateDB.SubRefund(params.NetSstoreClearRefund) @@ -153,6 +160,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi evm.StateDB.AddRefund(params.NetSstoreClearRefund) } } + if original == value { if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) evm.StateDB.AddRefund(params.NetSstoreResetClearRefund) @@ -160,6 +168,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi evm.StateDB.AddRefund(params.NetSstoreResetRefund) } } + return params.NetSstoreDirtyGas, nil } @@ -188,21 +197,26 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m y, x = stack.Back(1), stack.Back(0) 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(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.SstoreSetGasEIP2200, nil } + if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } + return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) } + if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200) @@ -210,6 +224,7 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } } + if original == value { if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) @@ -217,6 +232,7 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) } } + return params.SloadGasEIP2200, nil // dirty update (2.2) } @@ -235,17 +251,21 @@ func makeGasLog(n uint64) gasFunc { if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { return 0, ErrGasUintOverflow } + if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { return 0, ErrGasUintOverflow } var memorySizeGas uint64 + if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { return 0, ErrGasUintOverflow } + if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { return 0, ErrGasUintOverflow } + return gas, nil } } @@ -255,16 +275,20 @@ func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor if err != nil { return 0, err } + wordGas, overflow := stack.Back(1).Uint64WithOverflow() if overflow { return 0, ErrGasUintOverflow } + if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { return 0, ErrGasUintOverflow } + if gas, overflow = math.SafeAdd(gas, wordGas); overflow { return 0, ErrGasUintOverflow } + return gas, nil } @@ -289,16 +313,20 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS if err != nil { return 0, err } + wordGas, overflow := stack.Back(2).Uint64WithOverflow() if overflow { return 0, ErrGasUintOverflow } + if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { return 0, ErrGasUintOverflow } + if gas, overflow = math.SafeAdd(gas, wordGas); overflow { return 0, ErrGasUintOverflow } + return gas, nil } @@ -348,9 +376,11 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem gas = expByteLen * params.ExpByteFrontier // no overflow check required. Max is 256 * ExpByte gas overflow bool ) + if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { return 0, ErrGasUintOverflow } + return gas, nil } @@ -361,9 +391,11 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor gas = expByteLen * params.ExpByteEIP158 // no overflow check required. Max is 256 * ExpByte gas overflow bool ) + if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { return 0, ErrGasUintOverflow } + return gas, nil } @@ -373,6 +405,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize transfersValue = !stack.Back(2).IsZero() address = common.Address(stack.Back(1).Bytes20()) ) + if evm.chainRules.IsEIP158 { if transfersValue && evm.StateDB.Empty(address) { gas += params.CallNewAccountGas @@ -380,13 +413,16 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } else if !evm.StateDB.Exist(address) { gas += params.CallNewAccountGas } + if transfersValue { gas += params.CallValueTransferGas } + memoryGas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err } + var overflow bool if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow @@ -396,9 +432,11 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if err != nil { return 0, err } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + return gas, nil } @@ -407,23 +445,29 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory if err != nil { return 0, err } + var ( gas uint64 overflow bool ) + if stack.Back(2).Sign() != 0 { gas += params.CallValueTransferGas } + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + return gas, nil } @@ -432,14 +476,17 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if err != nil { return 0, err } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } + var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + return gas, nil } @@ -448,14 +495,17 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if err != nil { return 0, err } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } + var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + return gas, nil } @@ -464,6 +514,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // EIP150 homestead gas reprice fork: if evm.chainRules.IsEIP150 { gas = params.SelfdestructGasEIP150 + var address = common.Address(stack.Back(0).Bytes20()) if evm.chainRules.IsEIP158 { @@ -479,5 +530,6 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if !evm.StateDB.HasSuicided(contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } + return gas, nil } diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 66798d1e44..29850fe6e6 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -44,6 +44,7 @@ func TestMemoryGasCost(t *testing.T) { if (err == ErrGasUintOverflow) != tt.overflow { t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == ErrGasUintOverflow, tt.overflow) } + if v != tt.cost { t.Errorf("test %d: gas cost mismatch: have %v, want %v", i, v, tt.cost) } @@ -99,9 +100,11 @@ func TestEIP2200(t *testing.T) { if err != tt.failure { t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) } + if used := tt.gaspool - gas; used != tt.used { t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used) } + if refund := vmenv.StateDB.GetRefund(); refund != tt.refund { t.Errorf("test %d: gas refund mismatch: have %v, want %v", i, refund, tt.refund) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 6d57fb969f..540640f4b0 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -27,60 +27,70 @@ import ( func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.Add(&x, y) + return nil, nil } func opSub(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.Sub(&x, y) + return nil, nil } func opMul(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.Mul(&x, y) + return nil, nil } func opDiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.Div(&x, y) + return nil, nil } func opSdiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.SDiv(&x, y) + return nil, nil } func opMod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.Mod(&x, y) + return nil, nil } func opSmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.SMod(&x, y) + return nil, nil } func opExp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { base, exponent := scope.Stack.pop(), scope.Stack.peek() exponent.Exp(&base, exponent) + return nil, nil } func opSignExtend(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { back, num := scope.Stack.pop(), scope.Stack.peek() num.ExtendSign(num, &back) + return nil, nil } func opNot(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x := scope.Stack.peek() x.Not(x) + return nil, nil } @@ -91,6 +101,7 @@ func opLt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, } else { y.Clear() } + return nil, nil } @@ -101,6 +112,7 @@ func opGt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, } else { y.Clear() } + return nil, nil } @@ -111,6 +123,7 @@ func opSlt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte } else { y.Clear() } + return nil, nil } @@ -121,6 +134,7 @@ func opSgt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte } else { y.Clear() } + return nil, nil } @@ -131,6 +145,7 @@ func opEq(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, } else { y.Clear() } + return nil, nil } @@ -141,30 +156,35 @@ func opIszero(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } else { x.Clear() } + return nil, nil } func opAnd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.And(&x, y) + return nil, nil } func opOr(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.Or(&x, y) + return nil, nil } func opXor(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y := scope.Stack.pop(), scope.Stack.peek() y.Xor(&x, y) + return nil, nil } func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { th, val := scope.Stack.pop(), scope.Stack.peek() val.Byte(&th) + return nil, nil } @@ -175,12 +195,14 @@ func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } else { z.AddMod(&x, &y, z) } + return nil, nil } func opMulmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() z.MulMod(&x, &y, z) + return nil, nil } @@ -195,6 +217,7 @@ func opSHL(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte } else { value.Clear() } + return nil, nil } @@ -209,6 +232,7 @@ func opSHR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte } else { value.Clear() } + return nil, nil } @@ -224,10 +248,13 @@ func opSAR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte // Max negative shift: all bits set value.SetAllOne() } + return nil, nil } + n := uint(shift.Uint64()) value.SRsh(value, n) + return nil, nil } @@ -240,6 +267,7 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( } else { interpreter.hasher.Reset() } + interpreter.hasher.Write(data) interpreter.hasher.Read(interpreter.hasherBuf[:]) @@ -249,6 +277,7 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( } size.SetBytes(interpreter.hasherBuf[:]) + return nil, nil } func opAddress(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { @@ -260,6 +289,7 @@ func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) + return nil, nil } @@ -275,6 +305,7 @@ func opCaller(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b func opCallValue(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { v, _ := uint256.FromBig(scope.Contract.value) scope.Stack.push(v) + return nil, nil } @@ -286,6 +317,7 @@ func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } else { x.Clear() } + return nil, nil } @@ -300,6 +332,7 @@ func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext dataOffset = scope.Stack.pop() length = scope.Stack.pop() ) + dataOffset64, overflow := dataOffset.Uint64WithOverflow() if overflow { dataOffset64 = 0xffffffffffffffff @@ -330,18 +363,23 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte } // we can reuse dataOffset now (aliasing it for clarity) var end = dataOffset + end.Add(&dataOffset, &length) + end64, overflow := end.Uint64WithOverflow() if overflow || uint64(len(interpreter.returnData)) < end64 { return nil, ErrReturnDataOutOfBounds } + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) + return nil, nil } func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) + return nil, nil } @@ -349,6 +387,7 @@ func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ l := new(uint256.Int) l.SetUint64(uint64(len(scope.Contract.Code))) scope.Stack.push(l) + return nil, nil } @@ -358,10 +397,12 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ codeOffset = scope.Stack.pop() length = scope.Stack.pop() ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { uint64CodeOffset = 0xffffffffffffffff } + codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) @@ -376,10 +417,12 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) codeOffset = stack.pop() length = stack.pop() ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { uint64CodeOffset = 0xffffffffffffffff } + addr := common.Address(a.Bytes20()) codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) @@ -416,39 +459,47 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) + if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else { slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) } + return nil, nil } func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { v, _ := uint256.FromBig(interpreter.evm.GasPrice) scope.Stack.push(v) + return nil, nil } func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { num := scope.Stack.peek() num64, overflow := num.Uint64WithOverflow() + if overflow { num.Clear() return nil, nil } + var upper, lower 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.Context.GetHash(num64).Bytes()) } else { num.Clear() } + return nil, nil } @@ -465,18 +516,21 @@ func opTimestamp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( func opNumber(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { v, _ := uint256.FromBig(interpreter.evm.Context.BlockNumber) scope.Stack.push(v) + return nil, nil } func opDifficulty(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { v, _ := uint256.FromBig(interpreter.evm.Context.Difficulty) scope.Stack.push(v) + return nil, nil } func opRandom(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { v := new(uint256.Int).SetBytes(interpreter.evm.Context.Random.Bytes()) scope.Stack.push(v) + return nil, nil } @@ -494,6 +548,7 @@ func opMload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by v := scope.Stack.peek() offset := int64(v.Uint64()) v.SetBytes(scope.Memory.GetPtr(offset, 32)) + return nil, nil } @@ -501,12 +556,14 @@ func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // pop value of the stack mStart, val := scope.Stack.pop(), scope.Stack.pop() scope.Memory.Set32(mStart.Uint64(), &val) + return nil, nil } func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { off, val := scope.Stack.pop(), scope.Stack.pop() scope.Memory.store[off.Uint64()] = byte(val.Uint64()) + return nil, nil } @@ -515,6 +572,7 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by hash := common.Hash(loc.Bytes32()) val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) loc.SetBytes(val.Bytes()) + return nil, nil } @@ -522,9 +580,11 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b if interpreter.readOnly { return nil, ErrWriteProtection } + loc := scope.Stack.pop() val := scope.Stack.pop() interpreter.evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) + return nil, nil } @@ -532,11 +592,14 @@ func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt if interpreter.evm.abort.Load() { return nil, errStopToken } + pos := scope.Stack.pop() if !scope.Contract.validJumpdest(&pos) { return nil, ErrInvalidJump } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop + return nil, nil } @@ -544,13 +607,16 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by if interpreter.evm.abort.Load() { return nil, errStopToken } + pos, cond := scope.Stack.pop(), scope.Stack.pop() if !cond.IsZero() { if !scope.Contract.validJumpdest(&pos) { return nil, ErrInvalidJump } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop } + return nil, nil } @@ -577,12 +643,14 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b if interpreter.readOnly { return nil, ErrWriteProtection } + var ( value = scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop() input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) gas = scope.Contract.Gas ) + if interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 } @@ -608,6 +676,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } else { stackvalue.SetBytes(addr.Bytes()) } + scope.Stack.push(&stackvalue) scope.Contract.Gas += returnGas @@ -615,7 +684,9 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b interpreter.returnData = res // set REVERT data to return data buffer return res, nil } + interpreter.returnData = nil // clear dirty return data buffer + return nil, nil } @@ -623,6 +694,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] if interpreter.readOnly { return nil, ErrWriteProtection } + var ( endowment = scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop() @@ -640,6 +712,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] if !endowment.IsZero() { bigEndowment = endowment.ToBig() } + res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas, bigEndowment, &salt) // Push item on the stack based on the returned error. @@ -648,6 +721,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] } else { stackvalue.SetBytes(addr.Bytes()) } + scope.Stack.push(&stackvalue) scope.Contract.Gas += returnGas @@ -655,7 +729,9 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] interpreter.returnData = res // set REVERT data to return data buffer return res, nil } + interpreter.returnData = nil // clear dirty return data buffer + return nil, nil } @@ -674,6 +750,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt if interpreter.readOnly && !value.IsZero() { return nil, ErrWriteProtection } + var bigVal = big0 //TODO: use uint256.Int instead of converting with toBig() // By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls), @@ -690,13 +767,17 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } else { temp.SetOne() } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } + scope.Contract.Gas += returnGas interpreter.returnData = ret + return ret, nil } @@ -714,6 +795,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ //TODO: use uint256.Int instead of converting with toBig() var bigVal = big0 + if !value.IsZero() { gas += params.CallStipend bigVal = value.ToBig() @@ -725,13 +807,17 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } else { temp.SetOne() } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } + scope.Contract.Gas += returnGas interpreter.returnData = ret + return ret, nil } @@ -753,13 +839,17 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } else { temp.SetOne() } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } + scope.Contract.Gas += returnGas interpreter.returnData = ret + return ret, nil } @@ -781,13 +871,17 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } else { temp.SetOne() } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } + scope.Contract.Gas += returnGas interpreter.returnData = ret + return ret, nil } @@ -803,6 +897,7 @@ func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) interpreter.returnData = ret + return ret, ErrExecutionReverted } @@ -818,6 +913,7 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext if interpreter.readOnly { return nil, ErrWriteProtection } + beneficiary := scope.Stack.pop() balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) @@ -827,6 +923,7 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) tracer.CaptureExit([]byte{}, 0, nil) } + return nil, errStopToken } @@ -838,9 +935,11 @@ func makeLog(size int) executionFunc { if interpreter.readOnly { return nil, ErrWriteProtection } + topics := make([]common.Hash, size) stack := scope.Stack mStart, mSize := stack.pop(), stack.pop() + for i := 0; i < size; i++ { addr := stack.pop() topics[i] = addr.Bytes32() @@ -866,12 +965,14 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by codeLen = uint64(len(scope.Contract.Code)) integer = new(uint256.Int) ) + *pc += 1 if *pc < codeLen { scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) } else { scope.Stack.push(integer.Clear()) } + return nil, nil } @@ -895,6 +996,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { scope.Contract.Code[startMin:endMin], pushByteSize))) *pc += size + return nil, nil } } @@ -911,6 +1013,7 @@ func makeDup(size int64) executionFunc { func makeSwap(size int64) executionFunc { // switch n + 1 otherwise n would be swapped with n size++ + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { scope.Stack.swap(int(size)) return nil, nil diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 0534d27263..ad00f32599 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -70,11 +70,13 @@ func init() { } // Params are combined so each param is used on each 'side' commonParams = make([]*twoOperandParams, len(params)*len(params)) + for i, x := range params { for j, y := range params { commonParams[i*len(params)+j] = &twoOperandParams{x, y} } } + twoOpMethods = map[string]executionFunc{ "add": opAdd, "sub": opSub, @@ -112,12 +114,15 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.X)) y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Y)) expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) + stack.push(x) stack.push(y) opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) } + actual := stack.pop() if actual.Cmp(expected) != 0 { @@ -206,6 +211,7 @@ func TestAddMod(t *testing.T) { evmInterpreter = NewEVMInterpreter(env) pc = uint64(0) ) + tests := []struct { x string y string @@ -226,10 +232,12 @@ func TestAddMod(t *testing.T) { y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.y)) z := new(uint256.Int).SetBytes(common.Hex2Bytes(test.z)) expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.expected)) + stack.push(z) stack.push(y) stack.push(x) opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -275,6 +283,7 @@ func TestWriteExpectedValues(t *testing.T) { } _ = os.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644) + if err != nil { t.Fatal(err) } @@ -288,7 +297,9 @@ func TestJsonTestcases(t *testing.T) { if err != nil { t.Fatal("Failed to read file", err) } + var testcases []TwoOperandTestcase + json.Unmarshal(data, &testcases) testTwoOperandOp(t, testcases, twoOpMethods[name], name) } @@ -308,13 +319,18 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { for i, arg := range args { intArgs[i] = new(uint256.Int).SetBytes(common.Hex2Bytes(arg)) } + pc := uint64(0) + bench.ResetTimer() + for i := 0; i < bench.N; i++ { for _, arg := range intArgs { stack.push(arg) } - _ , _ = op(&pc, evmInterpreter, scope) + + _, _ = op(&pc, evmInterpreter, scope) + stack.pop() } bench.StopTimer() @@ -544,18 +560,23 @@ func TestOpMstore(t *testing.T) { ) env.interpreter = evmInterpreter + mem.Resize(64) + pc := uint64(0) v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int)) opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } + stack.push(new(uint256.Int).SetUint64(0x1)) stack.push(new(uint256.Int)) opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -570,12 +591,15 @@ func BenchmarkOpMstore(bench *testing.B) { ) env.interpreter = evmInterpreter + mem.Resize(64) + pc := uint64(0) memStart := new(uint256.Int) value := new(uint256.Int).SetUint64(0x1337) bench.ResetTimer() + for i := 0; i < bench.N; i++ { stack.push(value) stack.push(memStart) @@ -610,14 +634,16 @@ func TestOpTstore(t *testing.T) { stack.push(new(uint256.Int).SetBytes(value)) // push the location to the stack stack.push(new(uint256.Int)) - _ , _ = opTstore(&pc, evmInterpreter, &scopeContext) + + _, _ = opTstore(&pc, evmInterpreter, &scopeContext) // there should be no elements on the stack after TSTORE if stack.len() != 0 { t.Fatal("stack wrong size") } // push the location to the stack stack.push(new(uint256.Int)) - _ , _ = opTload(&pc, evmInterpreter, &scopeContext) + + _, _ = opTload(&pc, evmInterpreter, &scopeContext) // there should be one element on the stack after TLOAD if stack.len() != 1 { t.Fatal("stack wrong size") @@ -637,12 +663,16 @@ func BenchmarkOpKeccak256(bench *testing.B) { mem = NewMemory() evmInterpreter = NewEVMInterpreter(env) ) + env.interpreter = evmInterpreter + mem.Resize(32) + pc := uint64(0) start := new(uint256.Int) bench.ResetTimer() + for i := 0; i < bench.N; i++ { stack.push(uint256.NewInt(32)) stack.push(start) @@ -741,15 +771,20 @@ func TestRandom(t *testing.T) { pc = uint64(0) evmInterpreter = env.interpreter ) + opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } + actual := stack.pop() + expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes())) if overflow { t.Errorf("Testcase %v: invalid overflow", tt.name) } + if actual.Cmp(expected) != 0 { t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual) } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index a0fda2af6c..0a10ae2750 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -338,9 +338,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i var dynamicCost uint64 dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) cost += dynamicCost // for tracing + if err != nil || !contract.UseGas(dynamicCost) { return nil, ErrOutOfGas } + if memorySize > 0 { mem.Resize(memorySize) } @@ -356,6 +358,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i if err != nil { break } + pc++ } @@ -416,6 +419,7 @@ func (in *EVMInterpreter) RunWithDelay(contract *Contract, input []byte, readOnl defer func() { returnStack(stack) }() + contract.Input = input if debug { @@ -479,9 +483,11 @@ func (in *EVMInterpreter) RunWithDelay(contract *Contract, input []byte, readOnl } else if sLen > operation.maxStack { return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } + if !contract.UseGas(cost) { return nil, ErrOutOfGas } + if operation.dynamicGas != nil { // All ops with a dynamic memory usage also has a dynamic gas cost. var memorySize uint64 @@ -505,6 +511,7 @@ func (in *EVMInterpreter) RunWithDelay(contract *Contract, input []byte, readOnl var dynamicCost uint64 dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) cost += dynamicCost // for tracing + if err != nil || !contract.UseGas(dynamicCost) { return nil, ErrOutOfGas } @@ -514,11 +521,13 @@ func (in *EVMInterpreter) RunWithDelay(contract *Contract, input []byte, readOnl logged = true } + if memorySize > 0 { mem.Resize(memorySize) } } else if debug { in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) + logged = true } // execute the operation @@ -526,6 +535,7 @@ func (in *EVMInterpreter) RunWithDelay(contract *Contract, input []byte, readOnl if err != nil { break } + pc++ } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index dbf75e3876..c98455b8f6 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -76,6 +76,7 @@ func validate(jt JumpTable) JumpTable { panic(fmt.Sprintf("op %v has dynamic memory but not dynamic gas", OpCode(i).String())) } } + return jt } @@ -95,6 +96,7 @@ func newMergeInstructionSet() JumpTable { minStack: minStack(0, 1), maxStack: maxStack(0, 1), } + return validate(instructionSet) } @@ -104,6 +106,7 @@ func newLondonInstructionSet() JumpTable { instructionSet := newBerlinInstructionSet() enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529 enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198 + return validate(instructionSet) } @@ -112,6 +115,7 @@ func newLondonInstructionSet() JumpTable { func newBerlinInstructionSet() JumpTable { instructionSet := newIstanbulInstructionSet() enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 + return validate(instructionSet) } @@ -163,6 +167,7 @@ func newConstantinopleInstructionSet() JumpTable { maxStack: maxStack(4, 1), memorySize: memoryCreate2, } + return validate(instructionSet) } @@ -199,6 +204,7 @@ func newByzantiumInstructionSet() JumpTable { maxStack: maxStack(2, 0), memorySize: memoryRevert, } + return validate(instructionSet) } @@ -206,6 +212,7 @@ func newByzantiumInstructionSet() JumpTable { func newSpuriousDragonInstructionSet() JumpTable { instructionSet := newTangerineWhistleInstructionSet() instructionSet[EXP].dynamicGas = gasExpEIP158 + return validate(instructionSet) } @@ -219,6 +226,7 @@ func newTangerineWhistleInstructionSet() JumpTable { instructionSet[CALL].constantGas = params.CallGasEIP150 instructionSet[CALLCODE].constantGas = params.CallGasEIP150 instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150 + return validate(instructionSet) } @@ -234,6 +242,7 @@ func newHomesteadInstructionSet() JumpTable { maxStack: maxStack(6, 1), memorySize: memoryDelegateCall, } + return validate(instructionSet) } diff --git a/core/vm/memory.go b/core/vm/memory.go index 35b7299960..a6af1f4862 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -41,6 +41,7 @@ func (m *Memory) Set(offset, size uint64, value []byte) { if offset+size > uint64(len(m.store)) { panic("invalid memory: store empty") } + copy(m.store[offset:offset+size], value) } } diff --git a/core/vm/memory_table.go b/core/vm/memory_table.go index e35ca84e0e..b34de9266c 100644 --- a/core/vm/memory_table.go +++ b/core/vm/memory_table.go @@ -61,13 +61,16 @@ func memoryCall(stack *Stack) (uint64, bool) { if overflow { return 0, true } + y, overflow := calcMemSize64(stack.Back(3), stack.Back(4)) if overflow { return 0, true } + if x > y { return x, false } + return y, false } func memoryDelegateCall(stack *Stack) (uint64, bool) { @@ -75,13 +78,16 @@ func memoryDelegateCall(stack *Stack) (uint64, bool) { if overflow { return 0, true } + y, overflow := calcMemSize64(stack.Back(2), stack.Back(3)) if overflow { return 0, true } + if x > y { return x, false } + return y, false } @@ -90,13 +96,16 @@ func memoryStaticCall(stack *Stack) (uint64, bool) { if overflow { return 0, true } + y, overflow := calcMemSize64(stack.Back(2), stack.Back(3)) if overflow { return 0, true } + if x > y { return x, false } + return y, false } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 551e1f5f11..e0e256f200 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -42,6 +42,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { cost = params.ColdSloadCostEIP2929 // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + if !addrPresent { // Once we're done with YOLOv2 and schedule this for mainnet, might // be good to remove this panic here, which is just really a @@ -49,6 +50,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { panic("impossible case: address was not present in access list during sstore op") } } + value := common.Hash(y.Bytes32()) if current == value { // noop (1) @@ -56,11 +58,13 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // return params.SloadGasEIP2200, nil return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS } + 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 } + if value == (common.Hash{}) { // delete slot (2.1.2b) evm.StateDB.AddRefund(clearingRefund) } @@ -68,6 +72,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) return cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929), nil // write existing slot (2.1.2) } + if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) evm.StateDB.SubRefund(clearingRefund) @@ -75,6 +80,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { evm.StateDB.AddRefund(clearingRefund) } } + if original == value { if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) // EIP 2200 Original clause: @@ -110,6 +116,7 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me evm.StateDB.AddSlotToAccessList(contract.Address(), slot) return params.ColdSloadCostEIP2929, nil } + return params.WarmStorageReadCostEIP2929, nil } @@ -124,17 +131,21 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo if err != nil { return 0, err } + addr := common.Address(stack.peek().Bytes20()) // Check slot presence in the access list if !evm.StateDB.AddressInAccessList(addr) { evm.StateDB.AddAddressToAccessList(addr) + var overflow bool // We charge (cold-warm), since 'warm' is already charged as constantGas if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { return 0, ErrGasUintOverflow } + return gas, nil } + return gas, nil } @@ -154,6 +165,7 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem // The warm storage read cost is already charged as constantGas return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil } + return 0, nil } @@ -165,6 +177,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so // the cost to charge for cold access, if any, is Cold - Warm coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + if !warmAccess { evm.StateDB.AddAddressToAccessList(addr) // Charge the remaining difference here already, to correctly calculate available @@ -187,6 +200,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { // outside of this function, as part of the dynamic gas, and that will make it // also become correctly reported to tracers. contract.Gas += coldCost + return gas + coldCost, nil } } @@ -226,19 +240,24 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { gas uint64 address = common.Address(stack.peek().Bytes20()) ) + if !evm.StateDB.AddressInAccessList(address) { // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(address) + gas = params.ColdAccountAccessCostEIP2929 } // if empty and transfers value if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { gas += params.CreateBySelfdestructGas } + if refundsEnabled && !evm.StateDB.HasSuicided(contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } + return gas, nil } + return gasFunc } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index c7a420239a..4181f95f9e 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -72,23 +72,29 @@ func setDefaults(cfg *Config) { if cfg.Difficulty == nil { cfg.Difficulty = new(big.Int) } + if cfg.GasLimit == 0 { cfg.GasLimit = math.MaxUint64 } + if cfg.GasPrice == nil { cfg.GasPrice = new(big.Int) } + if cfg.Value == nil { cfg.Value = new(big.Int) } + if cfg.BlockNumber == nil { cfg.BlockNumber = new(big.Int) } + if cfg.GetHashFn == nil { cfg.GetHashFn = func(n uint64) common.Hash { return common.BytesToHash(crypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) } } + if cfg.BaseFee == nil { cfg.BaseFee = big.NewInt(params.InitialBaseFee) } @@ -103,11 +109,13 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { if cfg == nil { cfg = new(Config) } + setDefaults(cfg) if cfg.State == nil { cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) } + var ( address = common.BytesToAddress([]byte("contract")) vmenv = NewEnv(cfg) @@ -130,6 +138,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { cfg.Value, nil, ) + return ret, cfg.State, err } @@ -138,11 +147,13 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { if cfg == nil { cfg = new(Config) } + setDefaults(cfg) if cfg.State == nil { cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) } + var ( vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) @@ -159,6 +170,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { cfg.GasLimit, cfg.Value, ) + return code, address, leftOverGas, err } @@ -190,5 +202,6 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er cfg.Value, nil, ) + return ret, leftOverGas, err } diff --git a/core/vm/runtime/runtime_example_test.go b/core/vm/runtime/runtime_example_test.go index b7d0ddc384..975b6e3b49 100644 --- a/core/vm/runtime/runtime_example_test.go +++ b/core/vm/runtime/runtime_example_test.go @@ -28,6 +28,7 @@ func ExampleExecute() { if err != nil { fmt.Println(err) } + fmt.Println(ret) // Output: // [96 96 96 64 82 96 8 86 91 0] diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 9f401523bc..1c43b496c2 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -51,15 +51,19 @@ func TestDefaults(t *testing.T) { if cfg.GasLimit == 0 { t.Error("didn't expect gaslimit to be zero") } + if cfg.GasPrice == nil { t.Error("expected time to be non nil") } + if cfg.Value == nil { t.Error("expected time to be non nil") } + if cfg.GetHashFn == nil { t.Error("expected time to be non nil") } + if cfg.BlockNumber == nil { t.Error("expected block number to be non nil") } @@ -139,16 +143,19 @@ func BenchmarkCall(b *testing.B) { if err != nil { b.Fatal(err) } + creceived, err := abi.Pack("confirmReceived") if err != nil { b.Fatal(err) } + refund, err := abi.Pack("refund") if err != nil { b.Fatal(err) } b.ResetTimer() + for i := 0; i < b.N; i++ { for j := 0; j < 400; j++ { Execute(code, cpurchase, nil) @@ -189,6 +196,7 @@ func benchmarkEVM_Create(bench *testing.B, code string) { } // Warm up the intpools and stuff bench.ResetTimer() + for i := 0; i < bench.N; i++ { Call(receiver, []byte{}, &runtimeConfig) } @@ -223,6 +231,7 @@ func fakeHeader(n uint64, parentHash common.Hash) *types.Header { Difficulty: big.NewInt(0), GasLimit: 100000, } + return &header } @@ -294,6 +303,7 @@ func TestBlockhash(t *testing.T) { // The method call to 'test()' input := common.Hex2Bytes("f8a8fd6d") chain := &dummyChain{} + ret, _, err := Execute(data, input, &Config{ GetHashFn: core.GetHashFn(header, chain), BlockNumber: new(big.Int).Set(header.Number), @@ -301,6 +311,7 @@ func TestBlockhash(t *testing.T) { if err != nil { t.Fatalf("expected no error, got %v", err) } + if len(ret) != 96 { t.Fatalf("expected returndata to be 96 bytes, got %d", len(ret)) } @@ -308,15 +319,19 @@ func TestBlockhash(t *testing.T) { zero := new(big.Int).SetBytes(ret[0:32]) first := new(big.Int).SetBytes(ret[32:64]) last := new(big.Int).SetBytes(ret[64:96]) + if zero.BitLen() != 0 { t.Fatalf("expected zeroes, got %x", ret[0:32]) } + if first.Uint64() != 999 { t.Fatalf("second block should be 999, got %d (%x)", first, ret[32:64]) } + if last.Uint64() != 744 { t.Fatalf("last block should be 744, got %d (%x)", last, ret[64:96]) } + if exp, got := 255, chain.counter; exp != got { t.Errorf("suboptimal; too much chain iteration, expected %d, got %d", exp, got) } @@ -329,26 +344,32 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode setDefaults(cfg) cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) cfg.GasLimit = gas + if len(tracerCode) > 0 { tracer, err := tracers.DefaultDirectory.New(tracerCode, new(tracers.Context), nil) if err != nil { b.Fatal(err) } + cfg.EVMConfig = vm.Config{ Tracer: tracer, } } + var ( destination = common.BytesToAddress([]byte("contract")) vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) + cfg.State.CreateAccount(destination) + eoa := common.HexToAddress("E0") { cfg.State.CreateAccount(eoa) cfg.State.SetNonce(eoa, 100) } + reverting := common.HexToAddress("EE") { cfg.State.CreateAccount(reverting) @@ -368,6 +389,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode b.Run(name, func(b *testing.B) { b.ReportAllocs() + for i := 0; i < b.N; i++ { // nolint: errcheck vmenv.Call(sender, destination, nil, gas, cfg.Value, nil) @@ -485,18 +507,19 @@ func BenchmarkSimpleLoop(b *testing.B) { benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", "", b) benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", "", b) benchmarkNonModifyingCode(100000000, callRevertingContractWithInput, "call-reverting-100M", "", b) - - //benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b) - //benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b) + // benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b) + // benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b) } // TestEip2929Cases contains various testcases that are used for // EIP-2929 about gas repricings func TestEip2929Cases(t *testing.T) { t.Skip("Test only useful for generating documentation") + id := 1 prettyPrint := func(comment string, code []byte) { instrs := make([]string, 0) + it := asm.NewInstructionIterator(code) for it.Next() { if it.Arg() != nil && 0 < len(it.Arg()) { @@ -505,8 +528,11 @@ func TestEip2929Cases(t *testing.T) { instrs = append(instrs, fmt.Sprintf("%v", it.Op())) } } + ops := strings.Join(instrs, ", ") + fmt.Printf("### Case %d\n\n", id) + id++ fmt.Printf("%v\n\nBytecode: \n```\n%#x\n```\nOperations: \n```\n%v\n```\n\n", @@ -670,11 +696,13 @@ func TestColdAccountAccessCost(t *testing.T) { Tracer: tracer, }, }) + have := tracer.StructLogs()[tc.step].GasCost if want := tc.want; have != want { for ii, op := range tracer.StructLogs() { t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost) } + t.Fatalf("tescase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want) } } @@ -820,6 +848,7 @@ func TestRuntimeJSTracer(t *testing.T) { byte(vm.SELFDESTRUCT), } main := common.HexToAddress("0xaa") + for i, jsTracer := range jsTracers { for j, tc := range tests { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) @@ -834,6 +863,7 @@ func TestRuntimeJSTracer(t *testing.T) { if err != nil { t.Fatal(err) } + _, _, err = Call(main, nil, &Config{ GasLimit: 1000000, State: statedb, @@ -843,10 +873,12 @@ func TestRuntimeJSTracer(t *testing.T) { if err != nil { t.Fatal("didn't expect error", err) } + res, err := tracer.GetResult() if err != nil { t.Fatal(err) } + if have, want := string(res), tc.results[i]; have != want { t.Errorf("wrong result for tracer %d testcase %d, have \n%v\nwant\n%v\n", i, j, have, want) } @@ -865,10 +897,12 @@ func TestJSTracerCreateTx(t *testing.T) { code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)} statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil) if err != nil { t.Fatal(err) } + _, _, _, err = Create(code, &Config{ State: statedb, EVMConfig: vm.Config{ @@ -882,6 +916,7 @@ func TestJSTracerCreateTx(t *testing.T) { if err != nil { t.Fatal(err) } + if have, want := string(res), `"0,0"`; have != want { t.Errorf("wrong result for tracer, have \n%v\nwant\n%v\n", have, want) } diff --git a/core/vm/stack.go b/core/vm/stack.go index e1a957e244..82c5a27624 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -57,6 +57,7 @@ func (st *Stack) push(d *uint256.Int) { func (st *Stack) pop() (ret uint256.Int) { ret = st.data[len(st.data)-1] st.data = st.data[:len(st.data)-1] + return } diff --git a/crypto/blake2b/blake2b.go b/crypto/blake2b/blake2b.go index 7ecaab8139..eb166f29e3 100644 --- a/crypto/blake2b/blake2b.go +++ b/crypto/blake2b/blake2b.go @@ -52,25 +52,33 @@ var iv = [8]uint64{ // Sum512 returns the BLAKE2b-512 checksum of the data. func Sum512(data []byte) [Size]byte { var sum [Size]byte + checkSum(&sum, Size, data) + return sum } // Sum384 returns the BLAKE2b-384 checksum of the data. func Sum384(data []byte) [Size384]byte { var sum [Size]byte + var sum384 [Size384]byte + checkSum(&sum, Size384, data) copy(sum384[:], sum[:Size384]) + return sum384 } // Sum256 returns the BLAKE2b-256 checksum of the data. func Sum256(data []byte) [Size256]byte { var sum [Size]byte + var sum256 [Size256]byte + checkSum(&sum, Size256, data) copy(sum256[:], sum[:Size256]) + return sum256 } @@ -105,6 +113,7 @@ func F(h *[8]uint64, m [16]uint64, c [2]uint64, final bool, rounds uint32) { if final { flag = 0xFFFFFFFFFFFFFFFF } + f(h, &m, c[0], c[1], flag, uint64(rounds)) } @@ -112,21 +121,25 @@ func newDigest(hashSize int, key []byte) (*digest, error) { if hashSize < 1 || hashSize > Size { return nil, errHashSize } + if len(key) > Size { return nil, errKeySize } + d := &digest{ size: hashSize, keyLen: len(key), } copy(d.key[:], key) d.Reset() + return d, nil } func checkSum(sum *[Size]byte, hashSize int, data []byte) { h := iv h[0] ^= uint64(hashSize) | (1 << 16) | (1 << 24) + var c [2]uint64 if length := len(data); length > BlockSize { @@ -134,16 +147,19 @@ func checkSum(sum *[Size]byte, hashSize int, data []byte) { if length == n { n -= BlockSize } + hashBlocks(&h, &c, 0, data[:n]) data = data[n:] } var block [BlockSize]byte offset := copy(block[:], data) + remaining := uint64(BlockSize - offset) if c[0] < remaining { c[1]-- } + c[0] -= remaining hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) @@ -155,6 +171,7 @@ func checkSum(sum *[Size]byte, hashSize int, data []byte) { func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { var m [16]uint64 + c0, c1 := c[0], c[1] for i := 0; i < len(blocks); { @@ -162,12 +179,15 @@ func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { if c0 < BlockSize { c1++ } + for j := range m { m[j] = binary.LittleEndian.Uint64(blocks[i:]) i += 8 } + f(h, &m, c0, c1, flag, 12) } + c[0], c[1] = c0, c1 } @@ -191,17 +211,21 @@ func (d *digest) MarshalBinary() ([]byte, error) { if d.keyLen != 0 { return nil, errors.New("crypto/blake2b: cannot marshal MACs") } + b := make([]byte, 0, marshaledSize) b = append(b, magic...) + for i := 0; i < 8; i++ { b = appendUint64(b, d.h[i]) } + b = appendUint64(b, d.c[0]) b = appendUint64(b, d.c[1]) // Maximum value for size is 64 b = append(b, byte(d.size)) b = append(b, d.block[:]...) b = append(b, byte(d.offset)) + return b, nil } @@ -209,13 +233,16 @@ func (d *digest) UnmarshalBinary(b []byte) error { if len(b) < len(magic) || string(b[:len(magic)]) != magic { return errors.New("crypto/blake2b: invalid hash state identifier") } + if len(b) != marshaledSize { return errors.New("crypto/blake2b: invalid hash state size") } + b = b[len(magic):] for i := 0; i < 8; i++ { b, d.h[i] = consumeUint64(b) } + b, d.c[0] = consumeUint64(b) b, d.c[1] = consumeUint64(b) d.size = int(b[0]) @@ -223,6 +250,7 @@ func (d *digest) UnmarshalBinary(b []byte) error { copy(d.block[:], b[:BlockSize]) b = b[BlockSize:] d.offset = int(b[0]) + return nil } @@ -234,6 +262,7 @@ func (d *digest) Reset() { d.h = iv d.h[0] ^= uint64(d.size) | (uint64(d.keyLen) << 8) | (1 << 16) | (1 << 24) d.offset, d.c[0], d.c[1] = 0, 0, 0 + if d.keyLen > 0 { d.block = d.key d.offset = BlockSize @@ -249,6 +278,7 @@ func (d *digest) Write(p []byte) (n int, err error) { d.offset += copy(d.block[d.offset:], p) return } + copy(d.block[d.offset:], p[:remaining]) hashBlocks(&d.h, &d.c, 0, d.block[:]) d.offset = 0 @@ -260,6 +290,7 @@ func (d *digest) Write(p []byte) (n int, err error) { if length == nn { nn -= BlockSize } + hashBlocks(&d.h, &d.c, 0, p[:nn]) p = p[nn:] } @@ -273,12 +304,15 @@ func (d *digest) Write(p []byte) (n int, err error) { func (d *digest) Sum(sum []byte) []byte { var hash [Size]byte + d.finalize(&hash) + return append(sum, hash[:d.size]...) } func (d *digest) finalize(hash *[Size]byte) { var block [BlockSize]byte + copy(block[:], d.block[:d.offset]) remaining := uint64(BlockSize - d.offset) @@ -286,6 +320,7 @@ func (d *digest) finalize(hash *[Size]byte) { if c[0] < remaining { c[1]-- } + c[0] -= remaining h := d.h @@ -298,14 +333,18 @@ func (d *digest) finalize(hash *[Size]byte) { func appendUint64(b []byte, x uint64) []byte { var a [8]byte + binary.BigEndian.PutUint64(a[:], x) + return append(b, a[:]...) } //nolint:unused,deadcode func appendUint32(b []byte, x uint32) []byte { var a [4]byte + binary.BigEndian.PutUint32(a[:], x) + return append(b, a[:]...) } diff --git a/crypto/blake2b/blake2b_f_test.go b/crypto/blake2b/blake2b_f_test.go index 4e07d131cd..4b46d4ccba 100644 --- a/crypto/blake2b/blake2b_f_test.go +++ b/crypto/blake2b/blake2b_f_test.go @@ -10,7 +10,6 @@ func TestF(t *testing.T) { for i, test := range testVectorsF { t.Run(fmt.Sprintf("test vector %v", i), func(t *testing.T) { //toEthereumTestCase(test) - h := test.hIn F(&h, test.m, test.c, test.f, test.rounds) diff --git a/crypto/blake2b/blake2b_generic.go b/crypto/blake2b/blake2b_generic.go index 61e678fdf5..f575874fb0 100644 --- a/crypto/blake2b/blake2b_generic.go +++ b/crypto/blake2b/blake2b_generic.go @@ -28,6 +28,7 @@ var precomputed = [10][16]byte{ // nolint:unused,deadcode func hashBlocksGeneric(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { var m [16]uint64 + c0, c1 := c[0], c[1] for i := 0; i < len(blocks); { @@ -35,12 +36,15 @@ func hashBlocksGeneric(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { if c0 < BlockSize { c1++ } + for j := range m { m[j] = binary.LittleEndian.Uint64(blocks[i:]) i += 8 } + fGeneric(h, &m, c0, c1, flag, 12) } + c[0], c[1] = c0, c1 } @@ -170,6 +174,7 @@ func fGeneric(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds ui v4 ^= v9 v4 = bits.RotateLeft64(v4, -63) } + h[0] ^= v0 ^ v8 h[1] ^= v1 ^ v9 h[2] ^= v2 ^ v10 diff --git a/crypto/blake2b/blake2b_test.go b/crypto/blake2b/blake2b_test.go index 9d24444a27..f1b5fd70ad 100644 --- a/crypto/blake2b/blake2b_test.go +++ b/crypto/blake2b/blake2b_test.go @@ -22,18 +22,24 @@ func TestHashes(t *testing.T) { if useAVX2 { t.Log("AVX2 version") testHashes(t) + useAVX2 = false } + if useAVX { t.Log("AVX version") testHashes(t) + useAVX = false } + if useSSE4 { t.Log("SSE4 version") testHashes(t) + useSSE4 = false } + t.Log("generic version") testHashes(t) } @@ -46,18 +52,24 @@ func TestHashes2X(t *testing.T) { if useAVX2 { t.Log("AVX2 version") testHashes2X(t) + useAVX2 = false } + if useAVX { t.Log("AVX version") testHashes2X(t) + useAVX = false } + if useSSE4 { t.Log("SSE4 version") testHashes2X(t) + useSSE4 = false } + t.Log("generic version") testHashes2X(t) } @@ -67,22 +79,26 @@ func TestMarshal(t *testing.T) { for i := range input { input[i] = byte(i) } + for _, size := range []int{Size, Size256, Size384, 12, 25, 63} { for i := 0; i < 256; i++ { h, err := New(size, nil) if err != nil { t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err) } + h2, err := New(size, nil) if err != nil { t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err) } h.Write(input[:i/2]) + halfstate, err := h.(encoding.BinaryMarshaler).MarshalBinary() if err != nil { t.Fatalf("size=%d, len(input)=%d: could not marshal: %v", size, i, err) } + err = h2.(encoding.BinaryUnmarshaler).UnmarshalBinary(halfstate) if err != nil { t.Fatalf("size=%d, len(input)=%d: could not unmarshal: %v", size, i, err) @@ -90,6 +106,7 @@ func TestMarshal(t *testing.T) { h.Write(input[i/2 : i]) sum := h.Sum(nil) + h2.Write(input[i/2 : i]) sum2 := h2.Sum(nil) @@ -101,7 +118,9 @@ func TestMarshal(t *testing.T) { if err != nil { t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err) } + h3.Write(input[:i]) + sum3 := h3.Sum(nil) if !bytes.Equal(sum, sum3) { t.Fatalf("size=%d, len(input)=%d: sum = %v, want %v", size, i, sum, sum3) @@ -132,6 +151,7 @@ func testHashes(t *testing.T) { } h.Reset() + for j := 0; j < i; j++ { h.Write(input[j : j+1]) } @@ -163,26 +183,32 @@ func testHashes2X(t *testing.T) { if _, err := h.Write(input); err != nil { t.Fatalf("#%d (single write): error from Write: %v", i, err) } + if _, err := h.Read(sum); err != nil { t.Fatalf("#%d (single write): error from Read: %v", i, err) } + if n, err := h.Read(sum); n != 0 || err != io.EOF { t.Fatalf("#%d (single write): Read did not return (0, io.EOF) after exhaustion, got (%v, %v)", i, n, err) } + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex) } h.Reset() + for j := 0; j < len(input); j++ { h.Write(input[j : j+1]) } + for j := 0; j < len(sum); j++ { h = h.Clone() if _, err := h.Read(sum[j : j+1]); err != nil { t.Fatalf("#%d (byte-by-byte) - Read %d: error from Read: %v", i, j, err) } } + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex) } @@ -192,6 +218,7 @@ func testHashes2X(t *testing.T) { if err != nil { t.Fatalf("#unknown length: error from NewXOF: %v", err) } + if _, err := h.Write(input); err != nil { t.Fatalf("#unknown length: error from Write: %v", err) } @@ -221,6 +248,7 @@ func generateSequence(out []byte, seed uint32) { func computeMAC(msg []byte, hashSize int, key []byte) (sum []byte) { var h hash.Hash + switch hashSize { case Size: h, _ = New512(key) @@ -235,6 +263,7 @@ func computeMAC(msg []byte, hashSize int, key []byte) (sum []byte) { } h.Write(msg) + return h.Sum(sum) } @@ -251,7 +280,9 @@ func computeHash(msg []byte, hashSize int) (sum []byte) { return hash[:] case 20: var hash [64]byte + checkSum(&hash, 20, msg) + return hash[:20] default: panic("unexpected hashSize") @@ -267,6 +298,7 @@ func TestSelfTest(t *testing.T) { key := make([]byte, 64) h, _ := New256(nil) + for _, hashSize := range hashLens { for _, msgLength := range msgLens { generateSequence(msg[:msgLength], uint32(msgLength)) // unkeyed hash @@ -287,6 +319,7 @@ func TestSelfTest(t *testing.T) { 0x03, 0xd7, 0x63, 0xb8, 0xbb, 0xad, 0x2e, 0x73, 0x7f, 0x5e, 0x76, 0x5a, 0x7b, 0xcc, 0xd4, 0x75, } + if !bytes.Equal(sum, expected[:]) { t.Fatalf("got %x, wanted %x", sum, expected) } @@ -299,11 +332,13 @@ func benchmarkSum(b *testing.B, size int, sse4, avx, avx2 bool) { defer func(sse4, avx, avx2 bool) { useSSE4, useAVX, useAVX2 = sse4, avx, avx2 }(useSSE4, useAVX, useAVX2) + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 data := make([]byte, size) b.SetBytes(int64(size)) b.ResetTimer() + for i := 0; i < b.N; i++ { Sum512(data) } @@ -314,12 +349,15 @@ func benchmarkWrite(b *testing.B, size int, sse4, avx, avx2 bool) { defer func(sse4, avx, avx2 bool) { useSSE4, useAVX, useAVX2 = sse4, avx, avx2 }(useSSE4, useAVX, useAVX2) + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 data := make([]byte, size) h, _ := New512(nil) + b.SetBytes(int64(size)) b.ResetTimer() + for i := 0; i < b.N; i++ { h.Write(data) } diff --git a/crypto/blake2b/blake2x.go b/crypto/blake2b/blake2x.go index 52c414db0e..bc6aa48586 100644 --- a/crypto/blake2b/blake2x.go +++ b/crypto/blake2b/blake2x.go @@ -51,14 +51,17 @@ func NewXOF(size uint32, key []byte) (XOF, error) { if len(key) > Size { return nil, errKeySize } + if size == magicUnknownOutputLength { // 2^32-1 indicates an unknown number of bytes and thus isn't a // valid length. return nil, errors.New("blake2b: XOF length too large") } + if size == OutputLengthUnknown { size = magicUnknownOutputLength } + x := &xof{ d: digest{ size: Size, @@ -68,6 +71,7 @@ func NewXOF(size uint32, key []byte) (XOF, error) { } copy(x.d.key[:], key) x.Reset() + return x, nil } @@ -85,6 +89,7 @@ func (x *xof) Write(p []byte) (n int, err error) { if x.readMode { panic("blake2b: write to XOF after read") } + return x.d.Write(p) } @@ -106,6 +111,7 @@ func (x *xof) Reset() { if x.remaining == magicUnknownOutputLength { x.remaining = maxOutputLength } + x.offset, x.nodeOffset = 0, 0 x.readMode = false } @@ -131,8 +137,10 @@ func (x *xof) Read(p []byte) (n int, err error) { if n < blockRemaining { x.offset += copy(p, x.block[x.offset:]) x.remaining -= uint64(n) + return } + copy(p, x.block[x.offset:]) p = p[blockRemaining:] x.offset = 0 @@ -141,6 +149,7 @@ func (x *xof) Read(p []byte) (n int, err error) { for len(p) >= Size { binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ x.d.initConfig(&x.cfg) @@ -156,7 +165,9 @@ func (x *xof) Read(p []byte) (n int, err error) { if x.remaining < uint64(Size) { x.cfg[0] = byte(x.remaining) } + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ x.d.initConfig(&x.cfg) @@ -166,6 +177,7 @@ func (x *xof) Read(p []byte) (n int, err error) { x.offset = copy(p, x.block[:todo]) x.remaining -= uint64(todo) } + return } diff --git a/crypto/bls12381/arithmetic_fallback.go b/crypto/bls12381/arithmetic_fallback.go index c09ae0d91c..9a5c891c90 100644 --- a/crypto/bls12381/arithmetic_fallback.go +++ b/crypto/bls12381/arithmetic_fallback.go @@ -171,6 +171,7 @@ func sub(z, x, y *fe) { z[3], b = bits.Sub64(x[3], y[3], b) z[4], b = bits.Sub64(x[4], y[4], b) z[5], b = bits.Sub64(x[5], y[5], b) + if b != 0 { var c uint64 z[0], c = bits.Add64(z[0], 13402431016077863595, 0) @@ -190,6 +191,7 @@ func subAssign(z, x *fe) { 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) + if b != 0 { var c uint64 z[0], c = bits.Add64(z[0], 13402431016077863595, 0) @@ -216,6 +218,7 @@ func neg(z *fe, x *fe) { z.zero() return } + var borrow uint64 z[0], borrow = bits.Sub64(13402431016077863595, x[0], 0) z[1], borrow = bits.Sub64(2210141511517208575, x[1], borrow) @@ -227,6 +230,7 @@ func neg(z *fe, x *fe) { func mul(z, x, y *fe) { var t [6]uint64 + var c [3]uint64 { // round 0 @@ -345,7 +349,6 @@ func mul(z, x, y *fe) { } func square(z, x *fe) { - var p [6]uint64 var u, v uint64 @@ -354,6 +357,7 @@ func square(z, x *fe) { u, p[0] = bits.Mul64(x[0], x[0]) m := p[0] * 9940570264628428797 C := madd0(m, 13402431016077863595, p[0]) + var t uint64 t, u, v = madd1sb(x[0], x[1], u) C, p[0] = madd2(m, 2210141511517208575, v, C) @@ -372,6 +376,7 @@ func square(z, x *fe) { C := madd0(m, 13402431016077863595, p[0]) u, v = madd1(x[1], x[1], p[1]) C, p[0] = madd2(m, 2210141511517208575, v, C) + var t uint64 t, u, v = madd2sb(x[1], x[2], p[2], u) C, p[1] = madd2(m, 7435674573564081700, v, C) @@ -389,6 +394,7 @@ func square(z, x *fe) { C, p[0] = madd2(m, 2210141511517208575, p[1], C) u, v = madd1(x[2], x[2], p[2]) C, p[1] = madd2(m, 7435674573564081700, v, C) + var t uint64 t, u, v = madd2sb(x[2], x[3], p[3], u) C, p[2] = madd2(m, 7239337960414712511, v, C) @@ -405,6 +411,7 @@ func square(z, x *fe) { C, p[1] = madd2(m, 7435674573564081700, p[2], C) u, v = madd1(x[3], x[3], p[3]) C, p[2] = madd2(m, 7239337960414712511, v, C) + var t uint64 t, u, v = madd2sb(x[3], x[4], p[4], u) C, p[3] = madd2(m, 5412103778470702295, v, C) @@ -467,10 +474,12 @@ func square(z, x *fe) { func madd(a, b, t, u, v uint64) (uint64, uint64, uint64) { var carry uint64 + hi, lo := bits.Mul64(a, b) v, carry = bits.Add64(lo, v, 0) u, carry = bits.Add64(hi, u, carry) t, _ = bits.Add64(t, 0, carry) + return t, u, v } @@ -480,26 +489,31 @@ func madd0(a, b, c uint64) (hi uint64) { hi, lo = bits.Mul64(a, b) _, carry = bits.Add64(lo, c, 0) hi, _ = bits.Add64(hi, 0, carry) + return } // madd1 hi, lo = a*b + c func madd1(a, b, c uint64) (hi uint64, lo uint64) { var carry uint64 + hi, lo = bits.Mul64(a, b) lo, carry = bits.Add64(lo, c, 0) hi, _ = bits.Add64(hi, 0, carry) + return } // madd2 hi, lo = a*b + c + d func madd2(a, b, c, d uint64) (hi uint64, lo uint64) { var carry uint64 + hi, lo = bits.Mul64(a, b) c, carry = bits.Add64(c, d, 0) hi, _ = bits.Add64(hi, 0, carry) lo, carry = bits.Add64(lo, c, 0) hi, _ = bits.Add64(hi, 0, carry) + return } @@ -516,6 +530,7 @@ func madd2s(a, b, c, d, e uint64) (superhi, hi, lo uint64) { lo, carry = bits.Add64(lo, sum, 0) hi, _ = bits.Add64(hi, 0, carry) hi, _ = bits.Add64(hi, 0, d) + return } @@ -528,6 +543,7 @@ func madd1s(a, b, d, e uint64) (superhi, hi, lo uint64) { lo, carry = bits.Add64(lo, e, 0) hi, _ = bits.Add64(hi, 0, carry) hi, _ = bits.Add64(hi, 0, d) + return } @@ -542,6 +558,7 @@ func madd2sb(a, b, c, e uint64) (superhi, hi, lo uint64) { hi, _ = bits.Add64(hi, 0, carry) lo, carry = bits.Add64(lo, sum, 0) hi, _ = bits.Add64(hi, 0, carry) + return } @@ -553,15 +570,18 @@ func madd1sb(a, b, e uint64) (superhi, hi, lo uint64) { hi, superhi = bits.Add64(hi, hi, carry) lo, carry = bits.Add64(lo, e, 0) hi, _ = bits.Add64(hi, 0, carry) + return } func madd3(a, b, c, d, e uint64) (hi uint64, lo uint64) { var carry uint64 + hi, lo = bits.Mul64(a, b) c, carry = bits.Add64(c, d, 0) hi, _ = bits.Add64(hi, 0, carry) lo, carry = bits.Add64(lo, c, 0) hi, _ = bits.Add64(hi, e, carry) + return } diff --git a/crypto/bls12381/field_element.go b/crypto/bls12381/field_element.go index 9fdddc6184..ee57ade3b4 100644 --- a/crypto/bls12381/field_element.go +++ b/crypto/bls12381/field_element.go @@ -42,11 +42,14 @@ type fe12 [2]fe6 func (fe *fe) setBytes(in []byte) *fe { size := 48 l := len(in) + if l >= size { l = size } + padded := make([]byte, size) copy(padded[size-l:], in[:]) + var a int for i := 0; i < 6; i++ { a = size - i*8 @@ -55,6 +58,7 @@ func (fe *fe) setBytes(in []byte) *fe { uint64(padded[a-5])<<32 | uint64(padded[a-6])<<40 | uint64(padded[a-7])<<48 | uint64(padded[a-8])<<56 } + return fe } @@ -66,10 +70,12 @@ func (fe *fe) setString(s string) (*fe, error) { if s[:2] == "0x" { s = s[2:] } + bytes, err := hex.DecodeString(s) if err != nil { return nil, err } + return fe.setBytes(bytes), nil } @@ -80,11 +86,13 @@ func (fe *fe) set(fe2 *fe) *fe { fe[3] = fe2[3] fe[4] = fe2[4] fe[5] = fe2[5] + return fe } func (fe *fe) bytes() []byte { out := make([]byte, 48) + var a int for i := 0; i < 6; i++ { a = 48 - i*8 @@ -97,6 +105,7 @@ func (fe *fe) bytes() []byte { out[a-7] = byte(fe[i] >> 48) out[a-8] = byte(fe[i] >> 56) } + return out } @@ -108,6 +117,7 @@ func (fe *fe) string() (s string) { for i := 5; i >= 0; i-- { s = fmt.Sprintf("%s%16.16x", s, fe[i]) } + return "0x" + s } @@ -118,6 +128,7 @@ func (fe *fe) zero() *fe { fe[3] = 0 fe[4] = 0 fe[5] = 0 + return fe } @@ -130,6 +141,7 @@ func (fe *fe) rand(r io.Reader) (*fe, error) { if err != nil { return nil, err } + return fe.setBig(bi), nil } @@ -163,6 +175,7 @@ func (fe *fe) cmp(fe2 *fe) int { return -1 } } + return 0 } @@ -173,6 +186,7 @@ func (fe *fe) equal(fe2 *fe) bool { func (e *fe) sign() bool { r := new(fe) fromMont(r, e) + return r[0]&1 == 0 } @@ -193,24 +207,28 @@ func (fe *fe) mul2() uint64 { fe[2] = fe[2]<<1 | fe[1]>>63 fe[1] = fe[1]<<1 | fe[0]>>63 fe[0] = fe[0] << 1 + return e } func (e *fe2) zero() *fe2 { e[0].zero() e[1].zero() + return e } func (e *fe2) one() *fe2 { e[0].one() e[1].zero() + return e } func (e *fe2) set(e2 *fe2) *fe2 { e[0].set(&e2[0]) e[1].set(&e2[1]) + return e } @@ -219,10 +237,12 @@ func (e *fe2) rand(r io.Reader) (*fe2, error) { if err != nil { return nil, err } + a1, err := new(fe).rand(r) if err != nil { return nil, err } + return &fe2{*a0, *a1}, nil } @@ -244,7 +264,9 @@ func (e *fe2) sign() bool { fromMont(r, &e[0]) return r[0]&1 == 0 } + fromMont(r, &e[1]) + return r[0]&1 == 0 } @@ -252,6 +274,7 @@ func (e *fe6) zero() *fe6 { e[0].zero() e[1].zero() e[2].zero() + return e } @@ -259,6 +282,7 @@ func (e *fe6) one() *fe6 { e[0].one() e[1].zero() e[2].zero() + return e } @@ -266,6 +290,7 @@ func (e *fe6) set(e2 *fe6) *fe6 { e[0].set(&e2[0]) e[1].set(&e2[1]) e[2].set(&e2[2]) + return e } @@ -274,14 +299,17 @@ func (e *fe6) rand(r io.Reader) (*fe6, error) { if err != nil { return nil, err } + a1, err := new(fe2).rand(r) if err != nil { return nil, err } + a2, err := new(fe2).rand(r) if err != nil { return nil, err } + return &fe6{*a0, *a1, *a2}, nil } @@ -300,18 +328,21 @@ func (e *fe6) equal(e2 *fe6) bool { func (e *fe12) zero() *fe12 { e[0].zero() e[1].zero() + return e } func (e *fe12) one() *fe12 { e[0].one() e[1].zero() + return e } func (e *fe12) set(e2 *fe12) *fe12 { e[0].set(&e2[0]) e[1].set(&e2[1]) + return e } @@ -320,10 +351,12 @@ func (e *fe12) rand(r io.Reader) (*fe12, error) { if err != nil { return nil, err } + a1, err := new(fe6).rand(r) if err != nil { return nil, err } + return &fe12{*a0, *a1}, nil } diff --git a/crypto/bls12381/field_element_test.go b/crypto/bls12381/field_element_test.go index 70bbe5cfe5..f5b376f32f 100644 --- a/crypto/bls12381/field_element_test.go +++ b/crypto/bls12381/field_element_test.go @@ -12,15 +12,19 @@ func TestFieldElementValidation(t *testing.T) { if !zero.isValid() { t.Fatal("zero must be valid") } + one := new(fe).one() if !one.isValid() { t.Fatal("one must be valid") } + if modulus.isValid() { t.Fatal("modulus must be invalid") } + n := modulus.big() n.Add(n, big.NewInt(1)) + if new(fe).setBig(n).isValid() { t.Fatal("number greater than modulus must be invalid") } @@ -32,16 +36,20 @@ func TestFieldElementEquality(t *testing.T) { if !zero.equal(zero) { t.Fatal("0 == 0") } + one := new(fe).one() if !one.equal(one) { t.Fatal("1 == 1") } + a, _ := new(fe).rand(rand.Reader) if !a.equal(a) { t.Fatal("a == a") } + b := new(fe) add(b, a, one) + if a.equal(b) { t.Fatal("a != a + 1") } @@ -50,17 +58,21 @@ func TestFieldElementEquality(t *testing.T) { if !zero2.equal(zero2) { t.Fatal("0 == 0") } + one2 := new(fe2).one() if !one2.equal(one2) { t.Fatal("1 == 1") } + a2, _ := new(fe2).rand(rand.Reader) if !a2.equal(a2) { t.Fatal("a == a") } + b2 := new(fe2) fp2 := newFp2() fp2.add(b2, a2, one2) + if a2.equal(b2) { t.Fatal("a != a + 1") } @@ -69,17 +81,21 @@ func TestFieldElementEquality(t *testing.T) { if !zero6.equal(zero6) { t.Fatal("0 == 0") } + one6 := new(fe6).one() if !one6.equal(one6) { t.Fatal("1 == 1") } + a6, _ := new(fe6).rand(rand.Reader) if !a6.equal(a6) { t.Fatal("a == a") } + b6 := new(fe6) fp6 := newFp6(fp2) fp6.add(b6, a6, one6) + if a6.equal(b6) { t.Fatal("a != a + 1") } @@ -88,17 +104,21 @@ func TestFieldElementEquality(t *testing.T) { if !zero12.equal(zero12) { t.Fatal("0 == 0") } + one12 := new(fe12).one() if !one12.equal(one12) { t.Fatal("1 == 1") } + a12, _ := new(fe12).rand(rand.Reader) if !a12.equal(a12) { t.Fatal("a == a") } + b12 := new(fe12) fp12 := newFp12(fp6) fp12.add(b12, a12, one12) + if a12.equal(b12) { t.Fatal("a != a + 1") } @@ -110,21 +130,26 @@ func TestFieldElementHelpers(t *testing.T) { if !zero.isZero() { t.Fatal("'zero' is not zero") } + one := new(fe).one() if !one.isOne() { t.Fatal("'one' is not one") } + odd := new(fe).setBig(big.NewInt(1)) if !odd.isOdd() { t.Fatal("1 must be odd") } + if odd.isEven() { t.Fatal("1 must not be even") } + even := new(fe).setBig(big.NewInt(2)) if !even.isEven() { t.Fatal("2 must be even") } + if even.isOdd() { t.Fatal("2 must not be odd") } @@ -133,6 +158,7 @@ func TestFieldElementHelpers(t *testing.T) { if !zero2.isZero() { t.Fatal("'zero' is not zero, 2") } + one2 := new(fe2).one() if !one2.isOne() { t.Fatal("'one' is not one, 2") @@ -142,6 +168,7 @@ func TestFieldElementHelpers(t *testing.T) { if !zero6.isZero() { t.Fatal("'zero' is not zero, 6") } + one6 := new(fe6).one() if !one6.isOne() { t.Fatal("'one' is not one, 6") @@ -151,6 +178,7 @@ func TestFieldElementHelpers(t *testing.T) { if !zero12.isZero() { t.Fatal("'zero' is not zero, 12") } + one12 := new(fe12).one() if !one12.isOne() { t.Fatal("'one' is not one, 12") @@ -160,10 +188,12 @@ func TestFieldElementHelpers(t *testing.T) { func TestFieldElementSerialization(t *testing.T) { t.Run("zero", func(t *testing.T) { in := make([]byte, 48) + fe := new(fe).setBytes(in) if !fe.isZero() { t.Fatal("bad serialization") } + if !bytes.Equal(in, fe.bytes()) { t.Fatal("bad serialization") } @@ -172,6 +202,7 @@ func TestFieldElementSerialization(t *testing.T) { for i := 0; i < fuz; i++ { a, _ := new(fe).rand(rand.Reader) b := new(fe).setBytes(a.bytes()) + if !a.equal(b) { t.Fatal("bad serialization") } @@ -181,6 +212,7 @@ func TestFieldElementSerialization(t *testing.T) { for i := 0; i < fuz; i++ { a, _ := new(fe).rand(rand.Reader) b := new(fe).setBig(a.big()) + if !a.equal(b) { t.Fatal("bad encoding or decoding") } @@ -189,10 +221,12 @@ func TestFieldElementSerialization(t *testing.T) { t.Run("string", func(t *testing.T) { for i := 0; i < fuz; i++ { a, _ := new(fe).rand(rand.Reader) + b, err := new(fe).setString(a.string()) if err != nil { t.Fatal(err) } + if !a.equal(b) { t.Fatal("bad encoding or decoding") } @@ -203,24 +237,31 @@ func TestFieldElementSerialization(t *testing.T) { func TestFieldElementByteInputs(t *testing.T) { zero := new(fe).zero() in := make([]byte, 0) + a := new(fe).setBytes(in) if !a.equal(zero) { t.Fatal("bad serialization") } + in = make([]byte, 48) + a = new(fe).setBytes(in) if !a.equal(zero) { t.Fatal("bad serialization") } + in = make([]byte, 64) + a = new(fe).setBytes(in) if !a.equal(zero) { t.Fatal("bad serialization") } + in = make([]byte, 49) in[47] = 1 normalOne := &fe{1, 0, 0, 0, 0, 0} a = new(fe).setBytes(in) + if !a.equal(normalOne) { t.Fatal("bad serialization") } @@ -229,21 +270,28 @@ func TestFieldElementByteInputs(t *testing.T) { func TestFieldElementCopy(t *testing.T) { a, _ := new(fe).rand(rand.Reader) b := new(fe).set(a) + if !a.equal(b) { t.Fatal("bad copy, 1") } + a2, _ := new(fe2).rand(rand.Reader) b2 := new(fe2).set(a2) + if !a2.equal(b2) { t.Fatal("bad copy, 2") } + a6, _ := new(fe6).rand(rand.Reader) b6 := new(fe6).set(a6) + if !a6.equal(b6) { t.Fatal("bad copy, 6") } + a12, _ := new(fe12).rand(rand.Reader) b12 := new(fe12).set(a12) + if !a12.equal(b12) { t.Fatal("bad copy, 12") } diff --git a/crypto/bls12381/fp.go b/crypto/bls12381/fp.go index 09f6f49bc0..0e9f24181e 100644 --- a/crypto/bls12381/fp.go +++ b/crypto/bls12381/fp.go @@ -23,14 +23,19 @@ import ( func fromBytes(in []byte) (*fe, error) { fe := &fe{} + if len(in) != 48 { return nil, errors.New("input string should be equal 48 bytes") } + fe.setBytes(in) + if !fe.isValid() { return nil, errors.New("must be less than modulus") } + toMont(fe, fe) + return fe, nil } @@ -39,7 +44,9 @@ func fromBig(in *big.Int) (*fe, error) { if !fe.isValid() { return nil, errors.New("invalid input string") } + toMont(fe, fe) + return fe, nil } @@ -48,28 +55,34 @@ func fromString(in string) (*fe, error) { if err != nil { return nil, err } + if !fe.isValid() { return nil, errors.New("invalid input string") } + toMont(fe, fe) + return fe, nil } func toBytes(e *fe) []byte { e2 := new(fe) fromMont(e2, e) + return e2.bytes() } func toBig(e *fe) *big.Int { e2 := new(fe) fromMont(e2, e) + return e2.big() } func toString(e *fe) (s string) { e2 := new(fe) fromMont(e2, e) + return e2.string() } @@ -85,6 +98,7 @@ func exp(c, a *fe, e *big.Int) { z := new(fe).set(r1) for i := e.BitLen(); i >= 0; i-- { mul(z, z, z) + if e.Bit(i) == 1 { mul(z, z, a) } @@ -97,12 +111,16 @@ func inverse(inv, e *fe) { inv.zero() return } + u := new(fe).set(&modulus) v := new(fe).set(e) s := &fe{1} r := &fe{0} + var k int + var z uint64 + var found = false // Phase 1 for i := 0; i < 768; i++ { @@ -110,11 +128,13 @@ func inverse(inv, e *fe) { found = true break } + if u.isEven() { u.div2(0) s.mul2() } else if v.isEven() { v.div2(0) + z += r.mul2() } else if u.cmp(v) == 1 { lsubAssign(u, v) @@ -127,6 +147,7 @@ func inverse(inv, e *fe) { laddAssign(s, r) z += r.mul2() } + k += 1 } @@ -143,6 +164,7 @@ func inverse(inv, e *fe) { if r.cmp(&modulus) != -1 || z > 0 { lsubAssign(r, &modulus) } + u.set(&modulus) lsubAssign(u, r) @@ -157,11 +179,13 @@ func sqrt(c, a *fe) bool { u, v := new(fe).set(a), new(fe) exp(c, a, pPlus1Over4) square(v, c) + return u.equal(v) } func isQuadraticNonResidue(elem *fe) bool { result := new(fe) exp(result, elem, pMinus1Over2) + return !result.isOne() } diff --git a/crypto/bls12381/fp12.go b/crypto/bls12381/fp12.go index 51e949fe5f..9645d067c6 100644 --- a/crypto/bls12381/fp12.go +++ b/crypto/bls12381/fp12.go @@ -35,12 +35,15 @@ type fp12temp struct { func newFp12Temp() fp12temp { t2 := [9]*fe2{} t6 := [5]*fe6{} + for i := 0; i < len(t2); i++ { t2[i] = &fe2{} } + for i := 0; i < len(t6); i++ { t6[i] = &fe6{} } + return fp12temp{t2, t6, &fe12{}} } @@ -49,6 +52,7 @@ func newFp12(fp6 *fp6) *fp12 { if fp6 == nil { return &fp12{t, newFp6(nil)} } + return &fp12{t, fp6} } @@ -60,15 +64,19 @@ func (e *fp12) fromBytes(in []byte) (*fe12, error) { if len(in) != 576 { return nil, errors.New("input string should be larger than 96 bytes") } + fp6 := e.fp6 + c1, err := fp6.fromBytes(in[:288]) if err != nil { return nil, err } + c0, err := fp6.fromBytes(in[288:]) if err != nil { return nil, err } + return &fe12{*c0, *c1}, nil } @@ -77,6 +85,7 @@ func (e *fp12) toBytes(a *fe12) []byte { out := make([]byte, 576) copy(out[:288], fp6.toBytes(&a[1])) copy(out[288:], fp6.toBytes(&a[0])) + return out } @@ -118,6 +127,7 @@ func (e *fp12) neg(c, a *fe12) { func (e *fp12) conjugate(c, a *fe12) { fp6 := e.fp6 + c[0].set(&a[0]) fp6.neg(&c[1], &a[1]) } @@ -230,6 +240,7 @@ func (e *fp12) exp(c, a *fe12, s *big.Int) { z := e.one() for i := s.BitLen() - 1; i >= 0; i-- { e.square(z, z) + if s.Bit(i) == 1 { e.mul(z, z, a) } @@ -241,6 +252,7 @@ func (e *fp12) cyclotomicExp(c, a *fe12, s *big.Int) { z := e.one() for i := s.BitLen() - 1; i >= 0; i-- { e.cyclotomicSquare(z, z) + if s.Bit(i) == 1 { e.mul(z, z, a) } @@ -252,6 +264,7 @@ func (e *fp12) frobeniusMap(c, a *fe12, power uint) { fp6 := e.fp6 fp6.frobeniusMap(&c[0], &a[0], power) fp6.frobeniusMap(&c[1], &a[1], power) + switch power { case 0: return @@ -266,6 +279,7 @@ func (e *fp12) frobeniusMapAssign(a *fe12, power uint) { fp6 := e.fp6 fp6.frobeniusMapAssign(&a[0], power) fp6.frobeniusMapAssign(&a[1], power) + switch power { case 0: return diff --git a/crypto/bls12381/fp2.go b/crypto/bls12381/fp2.go index 0f1c5a23ac..412567964a 100644 --- a/crypto/bls12381/fp2.go +++ b/crypto/bls12381/fp2.go @@ -34,6 +34,7 @@ func newFp2Temp() fp2Temp { for i := 0; i < len(t); i++ { t[i] = &fe{} } + return fp2Temp{t} } @@ -46,14 +47,17 @@ func (e *fp2) fromBytes(in []byte) (*fe2, error) { if len(in) != 96 { return nil, errors.New("length of input string should be 96 bytes") } + c1, err := fromBytes(in[:48]) if err != nil { return nil, err } + c0, err := fromBytes(in[48:]) if err != nil { return nil, err } + return &fe2{*c0, *c1}, nil } @@ -61,6 +65,7 @@ func (e *fp2) toBytes(a *fe2) []byte { out := make([]byte, 96) copy(out[:48], toBytes(&a[1])) copy(out[48:], toBytes(&a[0])) + return out } @@ -200,6 +205,7 @@ func (e *fp2) exp(c, a *fe2, s *big.Int) { z := e.one() for i := s.BitLen() - 1; i >= 0; i-- { e.square(z, z) + if s.Bit(i) == 1 { e.mul(z, z, a) } @@ -209,10 +215,12 @@ func (e *fp2) exp(c, a *fe2, s *big.Int) { func (e *fp2) frobeniusMap(c, a *fe2, power uint) { c[0].set(&a[0]) + if power%2 == 1 { neg(&c[1], &a[1]) return } + c[1].set(&a[1]) } @@ -230,15 +238,19 @@ func (e *fp2) sqrt(c, a *fe2) bool { e.square(alpha, a1) e.mul(alpha, alpha, a) e.mul(x0, a1, a) + if alpha.equal(negativeOne2) { neg(&c[0], &x0[1]) c[1].set(&x0[0]) + return true } + e.add(alpha, alpha, e.one()) e.exp(alpha, alpha, pMinus1Over2) e.mul(c, alpha, x0) e.square(alpha, c) + return alpha.equal(u) } @@ -248,5 +260,6 @@ func (e *fp2) isQuadraticNonResidue(a *fe2) bool { square(c0, &a[0]) square(c1, &a[1]) add(c1, c1, c0) + return isQuadraticNonResidue(c1) } diff --git a/crypto/bls12381/fp6.go b/crypto/bls12381/fp6.go index 304173baa3..bd916c3542 100644 --- a/crypto/bls12381/fp6.go +++ b/crypto/bls12381/fp6.go @@ -35,6 +35,7 @@ func newFp6Temp() fp6Temp { for i := 0; i < len(t); i++ { t[i] = &fe2{} } + return fp6Temp{t} } @@ -43,6 +44,7 @@ func newFp6(f *fp2) *fp6 { if f == nil { return &fp6{newFp2(), t} } + return &fp6{f, t} } @@ -50,19 +52,24 @@ func (e *fp6) fromBytes(b []byte) (*fe6, error) { if len(b) < 288 { return nil, errors.New("input string should be larger than 288 bytes") } + fp2 := e.fp2 + u2, err := fp2.fromBytes(b[:96]) if err != nil { return nil, err } + u1, err := fp2.fromBytes(b[96:192]) if err != nil { return nil, err } + u0, err := fp2.fromBytes(b[192:]) if err != nil { return nil, err } + return &fe6{*u0, *u1, *u2}, nil } @@ -72,6 +79,7 @@ func (e *fp6) toBytes(a *fe6) []byte { copy(out[:96], fp2.toBytes(&a[2])) copy(out[96:192], fp2.toBytes(&a[1])) copy(out[192:], fp2.toBytes(&a[0])) + return out } @@ -280,6 +288,7 @@ func (e *fp6) exp(c, a *fe6, s *big.Int) { z := e.one() for i := s.BitLen() - 1; i >= 0; i-- { e.square(z, z) + if s.Bit(i) == 1 { e.mul(z, z, a) } @@ -317,6 +326,7 @@ func (e *fp6) frobeniusMap(c, a *fe6, power uint) { fp2.frobeniusMap(&c[0], &a[0], power) fp2.frobeniusMap(&c[1], &a[1], power) fp2.frobeniusMap(&c[2], &a[2], power) + switch power % 6 { case 0: return @@ -335,7 +345,9 @@ func (e *fp6) frobeniusMapAssign(a *fe6, power uint) { fp2.frobeniusMapAssign(&a[0], power) fp2.frobeniusMapAssign(&a[1], power) fp2.frobeniusMapAssign(&a[2], power) + t := e.t + switch power % 6 { case 0: return diff --git a/crypto/bls12381/fp_test.go b/crypto/bls12381/fp_test.go index 0bad35de16..dcd90bb6f7 100644 --- a/crypto/bls12381/fp_test.go +++ b/crypto/bls12381/fp_test.go @@ -10,13 +10,16 @@ import ( func TestFpSerialization(t *testing.T) { t.Run("zero", func(t *testing.T) { in := make([]byte, 48) + fe, err := fromBytes(in) if err != nil { t.Fatal(err) } + if !fe.isZero() { t.Fatal("bad serialization") } + if !bytes.Equal(in, toBytes(fe)) { t.Fatal("bad serialization") } @@ -24,10 +27,12 @@ func TestFpSerialization(t *testing.T) { t.Run("bytes", func(t *testing.T) { for i := 0; i < fuz; i++ { a, _ := new(fe).rand(rand.Reader) + b, err := fromBytes(toBytes(a)) if err != nil { t.Fatal(err) } + if !a.equal(b) { t.Fatal("bad serialization") } @@ -36,10 +41,12 @@ func TestFpSerialization(t *testing.T) { t.Run("string", func(t *testing.T) { for i := 0; i < fuz; i++ { a, _ := new(fe).rand(rand.Reader) + b, err := fromString(toString(a)) if err != nil { t.Fatal(err) } + if !a.equal(b) { t.Fatal("bad encoding or decoding") } @@ -48,10 +55,12 @@ func TestFpSerialization(t *testing.T) { t.Run("big", func(t *testing.T) { for i := 0; i < fuz; i++ { a, _ := new(fe).rand(rand.Reader) + b, err := fromBig(toBig(a)) if err != nil { t.Fatal(err) } + if !a.equal(b) { t.Fatal("bad encoding or decoding") } @@ -67,27 +76,35 @@ func TestFpAdditionCrossAgainstBigInt(t *testing.T) { big_a := toBig(a) big_b := toBig(b) big_c := new(big.Int) + add(c, a, b) out_1 := toBytes(c) out_2 := padBytes(big_c.Add(big_a, big_b).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { t.Fatal("cross test against big.Int is not satisfied A") } + double(c, a) out_1 = toBytes(c) out_2 = padBytes(big_c.Add(big_a, big_a).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { t.Fatal("cross test against big.Int is not satisfied B") } + sub(c, a, b) out_1 = toBytes(c) out_2 = padBytes(big_c.Sub(big_a, big_b).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { t.Fatal("cross test against big.Int is not satisfied C") } + neg(c, a) out_1 = toBytes(c) out_2 = padBytes(big_c.Neg(big_a).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { t.Fatal("cross test against big.Int is not satisfied D") } @@ -102,23 +119,28 @@ func TestFpAdditionCrossAgainstBigIntAssigned(t *testing.T) { addAssign(a, b) out_1 := toBytes(a) out_2 := padBytes(big_a.Add(big_a, big_b).Mod(big_a, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { t.Fatal("cross test against big.Int is not satisfied A") } + a, _ = new(fe).rand(rand.Reader) big_a = toBig(a) doubleAssign(a) out_1 = toBytes(a) out_2 = padBytes(big_a.Add(big_a, big_a).Mod(big_a, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { t.Fatal("cross test against big.Int is not satisfied B") } + a, _ = new(fe).rand(rand.Reader) b, _ = new(fe).rand(rand.Reader) big_a, big_b = toBig(a), toBig(b) subAssign(a, b) out_1 = toBytes(a) out_2 = padBytes(big_a.Sub(big_a, big_b).Mod(big_a, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { t.Fatal("cross test against big.Int is not satisfied A") } @@ -132,54 +154,74 @@ func TestFpAdditionProperties(t *testing.T) { b, _ := new(fe).rand(rand.Reader) c_1, c_2 := new(fe), new(fe) add(c_1, a, zero) + if !c_1.equal(a) { t.Fatal("a + 0 == a") } + sub(c_1, a, zero) + if !c_1.equal(a) { t.Fatal("a - 0 == a") } + double(c_1, zero) + if !c_1.equal(zero) { t.Fatal("2 * 0 == 0") } + neg(c_1, zero) + if !c_1.equal(zero) { t.Fatal("-0 == 0") } + sub(c_1, zero, a) neg(c_2, a) + if !c_1.equal(c_2) { t.Fatal("0-a == -a") } + double(c_1, a) add(c_2, a, a) + if !c_1.equal(c_2) { t.Fatal("2 * a == a + a") } + add(c_1, a, b) add(c_2, b, a) + if !c_1.equal(c_2) { t.Fatal("a + b = b + a") } + sub(c_1, a, b) sub(c_2, b, a) neg(c_2, c_2) + if !c_1.equal(c_2) { t.Fatal("a - b = - ( b - a )") } + c_x, _ := new(fe).rand(rand.Reader) + add(c_1, a, b) add(c_1, c_1, c_x) add(c_2, a, c_x) add(c_2, c_2, b) + if !c_1.equal(c_2) { t.Fatal("(a + b) + c == (a + c ) + b") } + sub(c_1, a, b) sub(c_1, c_1, c_x) sub(c_2, a, c_x) sub(c_2, c_2, b) + if !c_1.equal(c_2) { t.Fatal("(a - b) - c == (a - c ) -b") } @@ -193,49 +235,64 @@ func TestFpAdditionPropertiesAssigned(t *testing.T) { _, _ = a.rand(rand.Reader) b.set(a) addAssign(a, zero) + if !a.equal(b) { t.Fatal("a + 0 == a") } + subAssign(a, zero) + if !a.equal(b) { t.Fatal("a - 0 == a") } + a.set(zero) doubleAssign(a) + if !a.equal(zero) { t.Fatal("2 * 0 == 0") } + a.set(zero) subAssign(a, b) neg(b, b) + if !a.equal(b) { t.Fatal("0-a == -a") } + _, _ = a.rand(rand.Reader) b.set(a) doubleAssign(a) addAssign(b, b) + if !a.equal(b) { t.Fatal("2 * a == a + a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) c_1, c_2 := new(fe).set(a), new(fe).set(b) addAssign(c_1, b) addAssign(c_2, a) + if !c_1.equal(c_2) { t.Fatal("a + b = b + a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) + c_1.set(a) c_2.set(b) subAssign(c_1, b) subAssign(c_2, a) neg(c_2, c_2) + if !c_1.equal(c_2) { t.Fatal("a - b = - ( b - a )") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) c, _ := new(fe).rand(rand.Reader) @@ -244,17 +301,21 @@ func TestFpAdditionPropertiesAssigned(t *testing.T) { addAssign(a, c) addAssign(b, c) addAssign(b, a0) + if !a.equal(b) { t.Fatal("(a + b) + c == (b + c) + a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) _, _ = c.rand(rand.Reader) + a0.set(a) subAssign(a, b) subAssign(a, c) subAssign(a0, c) subAssign(a0, b) + if !a.equal(a0) { t.Fatal("(a - b) - c == (a - c) -b") } @@ -268,21 +329,26 @@ func TestFpLazyOperations(t *testing.T) { c, _ := new(fe).rand(rand.Reader) c0 := new(fe) c1 := new(fe) + ladd(c0, a, b) add(c1, a, b) mul(c0, c0, c) mul(c1, c1, c) + if !c0.equal(c1) { // l+ operator stands for lazy addition t.Fatal("(a + b) * c == (a l+ b) * c") } + _, _ = a.rand(rand.Reader) b.set(a) ldouble(a, a) ladd(b, b, b) + if !a.equal(b) { t.Fatal("2 l* a = a l+ a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) _, _ = c.rand(rand.Reader) @@ -292,6 +358,7 @@ func TestFpLazyOperations(t *testing.T) { mul(a, a, c) subAssign(a0, b) mul(a0, a0, c) + if !a.equal(a0) { t.Fatal("((a l- b) + p) * c = (a-b) * c") } @@ -306,9 +373,11 @@ func TestFpMultiplicationCrossAgainstBigInt(t *testing.T) { big_a := toBig(a) big_b := toBig(b) big_c := new(big.Int) + mul(c, a, b) out_1 := toBytes(c) out_2 := padBytes(big_c.Mul(big_a, big_b).Mod(big_c, modulus.big()).Bytes(), 48) + if !bytes.Equal(out_1, out_2) { t.Fatal("cross test against big.Int is not satisfied") } @@ -322,37 +391,51 @@ func TestFpMultiplicationProperties(t *testing.T) { zero, one := new(fe).zero(), new(fe).one() c_1, c_2 := new(fe), new(fe) mul(c_1, a, zero) + if !c_1.equal(zero) { t.Fatal("a * 0 == 0") } + mul(c_1, a, one) + if !c_1.equal(a) { t.Fatal("a * 1 == a") } + mul(c_1, a, b) mul(c_2, b, a) + if !c_1.equal(c_2) { t.Fatal("a * b == b * a") } + c_x, _ := new(fe).rand(rand.Reader) + mul(c_1, a, b) mul(c_1, c_1, c_x) mul(c_2, c_x, b) mul(c_2, c_2, a) + if !c_1.equal(c_2) { t.Fatal("(a * b) * c == (a * c) * b") } + square(a, zero) + if !a.equal(zero) { t.Fatal("0^2 == 0") } + square(a, one) + if !a.equal(one) { t.Fatal("1^2 == 1") } + _, _ = a.rand(rand.Reader) square(c_1, a) mul(c_2, a, a) + if !c_1.equal(c_1) { t.Fatal("a^2 == a*a") } @@ -364,27 +447,37 @@ func TestFpExponentiation(t *testing.T) { a, _ := new(fe).rand(rand.Reader) u := new(fe) exp(u, a, big.NewInt(0)) + if !u.isOne() { t.Fatal("a^0 == 1") } + exp(u, a, big.NewInt(1)) + if !u.equal(a) { t.Fatal("a^1 == a") } + v := new(fe) + mul(u, a, a) mul(u, u, u) mul(u, u, u) exp(v, a, big.NewInt(8)) + if !u.equal(v) { t.Fatal("((a^2)^2)^2 == a^8") } + p := modulus.big() exp(u, a, p) + if !u.equal(a) { t.Fatal("a^p == a") } + exp(u, a, p.Sub(p, big.NewInt(1))) + if !u.isOne() { t.Fatal("a^(p-1) == 1") } @@ -396,23 +489,30 @@ func TestFpInversion(t *testing.T) { u := new(fe) zero, one := new(fe).zero(), new(fe).one() inverse(u, zero) + if !u.equal(zero) { t.Fatal("(0^-1) == 0)") } + inverse(u, one) + if !u.equal(one) { t.Fatal("(1^-1) == 1)") } + a, _ := new(fe).rand(rand.Reader) inverse(u, a) mul(u, u, a) + if !u.equal(one) { t.Fatal("(r*a) * r*(a^-1) == r)") } + v := new(fe) p := modulus.big() exp(u, a, p.Sub(p, big.NewInt(2))) inverse(v, a) + if !v.equal(u) { t.Fatal("a^(p-2) == a^-1") } @@ -424,14 +524,18 @@ func TestFpSquareRoot(t *testing.T) { if sqrt(r, nonResidue1) { t.Fatal("non residue cannot have a sqrt") } + for i := 0; i < fuz; i++ { a, _ := new(fe).rand(rand.Reader) aa, rr, r := &fe{}, &fe{}, &fe{} square(aa, a) + if !sqrt(r, aa) { t.Fatal("bad sqrt 1") } + square(rr, r) + if !rr.equal(aa) { t.Fatal("bad sqrt 2") } @@ -442,19 +546,24 @@ func TestFpNonResidue(t *testing.T) { if !isQuadraticNonResidue(nonResidue1) { t.Fatal("element is quadratic non residue, 1") } + if isQuadraticNonResidue(new(fe).one()) { t.Fatal("one is not quadratic non residue") } + if !isQuadraticNonResidue(new(fe).zero()) { t.Fatal("should accept zero as quadratic non residue") } + for i := 0; i < fuz; i++ { a, _ := new(fe).rand(rand.Reader) square(a, a) + if isQuadraticNonResidue(new(fe).one()) { t.Fatal("element is not quadratic non residue") } } + for i := 0; i < fuz; i++ { a, _ := new(fe).rand(rand.Reader) if !sqrt(new(fe), a) { @@ -469,12 +578,15 @@ func TestFpNonResidue(t *testing.T) { func TestFp2Serialization(t *testing.T) { field := newFp2() + for i := 0; i < fuz; i++ { a, _ := new(fe2).rand(rand.Reader) + b, err := field.fromBytes(field.toBytes(a)) if err != nil { t.Fatal(err) } + if !a.equal(b) { t.Fatal("bad serialization") } @@ -490,54 +602,74 @@ func TestFp2AdditionProperties(t *testing.T) { c_1 := field.new() c_2 := field.new() field.add(c_1, a, zero) + if !c_1.equal(a) { t.Fatal("a + 0 == a") } + field.sub(c_1, a, zero) + if !c_1.equal(a) { t.Fatal("a - 0 == a") } + field.double(c_1, zero) + if !c_1.equal(zero) { t.Fatal("2 * 0 == 0") } + field.neg(c_1, zero) + if !c_1.equal(zero) { t.Fatal("-0 == 0") } + field.sub(c_1, zero, a) field.neg(c_2, a) + if !c_1.equal(c_2) { t.Fatal("0-a == -a") } + field.double(c_1, a) field.add(c_2, a, a) + if !c_1.equal(c_2) { t.Fatal("2 * a == a + a") } + field.add(c_1, a, b) field.add(c_2, b, a) + if !c_1.equal(c_2) { t.Fatal("a + b = b + a") } + field.sub(c_1, a, b) field.sub(c_2, b, a) field.neg(c_2, c_2) + if !c_1.equal(c_2) { t.Fatal("a - b = - ( b - a )") } + c_x, _ := new(fe2).rand(rand.Reader) + field.add(c_1, a, b) field.add(c_1, c_1, c_x) field.add(c_2, a, c_x) field.add(c_2, c_2, b) + if !c_1.equal(c_2) { t.Fatal("(a + b) + c == (a + c ) + b") } + field.sub(c_1, a, b) field.sub(c_1, c_1, c_x) field.sub(c_2, a, c_x) field.sub(c_2, c_2, b) + if !c_1.equal(c_2) { t.Fatal("(a - b) - c == (a - c ) -b") } @@ -546,55 +678,71 @@ func TestFp2AdditionProperties(t *testing.T) { func TestFp2AdditionPropertiesAssigned(t *testing.T) { field := newFp2() + for i := 0; i < fuz; i++ { zero := new(fe2).zero() a, b := new(fe2), new(fe2) _, _ = a.rand(rand.Reader) b.set(a) field.addAssign(a, zero) + if !a.equal(b) { t.Fatal("a + 0 == a") } + field.subAssign(a, zero) + if !a.equal(b) { t.Fatal("a - 0 == a") } + a.set(zero) field.doubleAssign(a) + if !a.equal(zero) { t.Fatal("2 * 0 == 0") } + a.set(zero) field.subAssign(a, b) field.neg(b, b) + if !a.equal(b) { t.Fatal("0-a == -a") } + _, _ = a.rand(rand.Reader) b.set(a) field.doubleAssign(a) field.addAssign(b, b) + if !a.equal(b) { t.Fatal("2 * a == a + a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) c_1, c_2 := new(fe2).set(a), new(fe2).set(b) field.addAssign(c_1, b) field.addAssign(c_2, a) + if !c_1.equal(c_2) { t.Fatal("a + b = b + a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) + c_1.set(a) c_2.set(b) field.subAssign(c_1, b) field.subAssign(c_2, a) field.neg(c_2, c_2) + if !c_1.equal(c_2) { t.Fatal("a - b = - ( b - a )") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) c, _ := new(fe2).rand(rand.Reader) @@ -603,17 +751,21 @@ func TestFp2AdditionPropertiesAssigned(t *testing.T) { field.addAssign(a, c) field.addAssign(b, c) field.addAssign(b, a0) + if !a.equal(b) { t.Fatal("(a + b) + c == (b + c) + a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) _, _ = c.rand(rand.Reader) + a0.set(a) field.subAssign(a, b) field.subAssign(a, c) field.subAssign(a0, c) field.subAssign(a0, b) + if !a.equal(a0) { t.Fatal("(a - b) - c == (a - c) -b") } @@ -622,24 +774,29 @@ func TestFp2AdditionPropertiesAssigned(t *testing.T) { func TestFp2LazyOperations(t *testing.T) { field := newFp2() + for i := 0; i < fuz; i++ { a, _ := new(fe2).rand(rand.Reader) b, _ := new(fe2).rand(rand.Reader) c, _ := new(fe2).rand(rand.Reader) c0 := new(fe2) c1 := new(fe2) + field.ladd(c0, a, b) field.add(c1, a, b) field.mulAssign(c0, c) field.mulAssign(c1, c) + if !c0.equal(c1) { // l+ operator stands for lazy addition t.Fatal("(a + b) * c == (a l+ b) * c") } + _, _ = a.rand(rand.Reader) b.set(a) field.ldouble(a, a) field.ladd(b, b, b) + if !a.equal(b) { t.Fatal("2 l* a = a l+ a") } @@ -648,6 +805,7 @@ func TestFp2LazyOperations(t *testing.T) { func TestFp2MultiplicationProperties(t *testing.T) { field := newFp2() + for i := 0; i < fuz; i++ { a, _ := new(fe2).rand(rand.Reader) b, _ := new(fe2).rand(rand.Reader) @@ -655,37 +813,51 @@ func TestFp2MultiplicationProperties(t *testing.T) { one := field.one() c_1, c_2 := field.new(), field.new() field.mul(c_1, a, zero) + if !c_1.equal(zero) { t.Fatal("a * 0 == 0") } + field.mul(c_1, a, one) + if !c_1.equal(a) { t.Fatal("a * 1 == a") } + field.mul(c_1, a, b) field.mul(c_2, b, a) + if !c_1.equal(c_2) { t.Fatal("a * b == b * a") } + c_x, _ := new(fe2).rand(rand.Reader) + field.mul(c_1, a, b) field.mul(c_1, c_1, c_x) field.mul(c_2, c_x, b) field.mul(c_2, c_2, a) + if !c_1.equal(c_2) { t.Fatal("(a * b) * c == (a * c) * b") } + field.square(a, zero) + if !a.equal(zero) { t.Fatal("0^2 == 0") } + field.square(a, one) + if !a.equal(one) { t.Fatal("1^2 == 1") } + _, _ = a.rand(rand.Reader) field.square(c_1, a) field.mul(c_2, a, a) + if !c_2.equal(c_1) { t.Fatal("a^2 == a*a") } @@ -694,39 +866,51 @@ func TestFp2MultiplicationProperties(t *testing.T) { func TestFp2MultiplicationPropertiesAssigned(t *testing.T) { field := newFp2() + for i := 0; i < fuz; i++ { a, _ := new(fe2).rand(rand.Reader) zero, one := new(fe2).zero(), new(fe2).one() field.mulAssign(a, zero) + if !a.equal(zero) { t.Fatal("a * 0 == 0") } + _, _ = a.rand(rand.Reader) a0 := new(fe2).set(a) field.mulAssign(a, one) + if !a.equal(a0) { t.Fatal("a * 1 == a") } + _, _ = a.rand(rand.Reader) b, _ := new(fe2).rand(rand.Reader) + a0.set(a) field.mulAssign(a, b) field.mulAssign(b, a0) + if !a.equal(b) { t.Fatal("a * b == b * a") } + c, _ := new(fe2).rand(rand.Reader) + a0.set(a) field.mulAssign(a, b) field.mulAssign(a, c) field.mulAssign(a0, c) field.mulAssign(a0, b) + if !a.equal(a0) { t.Fatal("(a * b) * c == (a * c) * b") } + a0.set(a) field.squareAssign(a) field.mulAssign(a0, a0) + if !a.equal(a0) { t.Fatal("a^2 == a*a") } @@ -735,22 +919,28 @@ func TestFp2MultiplicationPropertiesAssigned(t *testing.T) { func TestFp2Exponentiation(t *testing.T) { field := newFp2() + for i := 0; i < fuz; i++ { a, _ := new(fe2).rand(rand.Reader) u := field.new() field.exp(u, a, big.NewInt(0)) + if !u.equal(field.one()) { t.Fatal("a^0 == 1") } + field.exp(u, a, big.NewInt(1)) + if !u.equal(a) { t.Fatal("a^1 == a") } + v := field.new() field.mul(u, a, a) field.mul(u, u, u) field.mul(u, u, u) field.exp(v, a, big.NewInt(8)) + if !u.equal(v) { t.Fatal("((a^2)^2)^2 == a^8") } @@ -763,17 +953,22 @@ func TestFp2Inversion(t *testing.T) { zero := field.zero() one := field.one() field.inverse(u, zero) + if !u.equal(zero) { t.Fatal("(0 ^ -1) == 0)") } + field.inverse(u, one) + if !u.equal(one) { t.Fatal("(1 ^ -1) == 1)") } + for i := 0; i < fuz; i++ { a, _ := new(fe2).rand(rand.Reader) field.inverse(u, a) field.mul(u, u, a) + if !u.equal(one) { t.Fatal("(r * a) * r * (a ^ -1) == r)") } @@ -782,6 +977,7 @@ func TestFp2Inversion(t *testing.T) { func TestFp2SquareRoot(t *testing.T) { field := newFp2() + for z := 0; z < 1000; z++ { zi := new(fe) sub(zi, &modulus, &fe{uint64(z * z)}) @@ -789,31 +985,39 @@ func TestFp2SquareRoot(t *testing.T) { r := &fe2{*zi, fe{0}} toMont(&r[0], &r[0]) toMont(&r[1], &r[1]) + c := field.new() // sqrt((-z*z, 0)) = (0, z) if !field.sqrt(c, r) { t.Fatal("z*z does have a square root") } + e := &fe2{fe{uint64(0)}, fe{uint64(z)}} toMont(&e[0], &e[0]) toMont(&e[1], &e[1]) field.square(e, e) field.square(c, c) + if !e.equal(c) { t.Fatal("square root failed") } } + if field.sqrt(field.new(), nonResidue2) { t.Fatal("non residue cannot have a sqrt") } + for i := 0; i < fuz; i++ { a, _ := new(fe2).rand(rand.Reader) aa, rr, r := field.new(), field.new(), field.new() field.square(aa, a) + if !field.sqrt(r, aa) { t.Fatal("bad sqrt 1") } + field.square(rr, r) + if !rr.equal(aa) { t.Fatal("bad sqrt 2") } @@ -825,19 +1029,24 @@ func TestFp2NonResidue(t *testing.T) { if !field.isQuadraticNonResidue(nonResidue2) { t.Fatal("element is quadratic non residue, 1") } + if field.isQuadraticNonResidue(new(fe2).one()) { t.Fatal("one is not quadratic non residue") } + if !field.isQuadraticNonResidue(new(fe2).zero()) { t.Fatal("should accept zero as quadratic non residue") } + for i := 0; i < fuz; i++ { a, _ := new(fe2).rand(rand.Reader) field.squareAssign(a) + if field.isQuadraticNonResidue(new(fe2).one()) { t.Fatal("element is not quadratic non residue") } } + for i := 0; i < fuz; i++ { a, _ := new(fe2).rand(rand.Reader) if !field.sqrt(new(fe2), a) { @@ -852,12 +1061,15 @@ func TestFp2NonResidue(t *testing.T) { func TestFp6Serialization(t *testing.T) { field := newFp6(nil) + for i := 0; i < fuz; i++ { a, _ := new(fe6).rand(rand.Reader) + b, err := field.fromBytes(field.toBytes(a)) if err != nil { t.Fatal(err) } + if !a.equal(b) { t.Fatal("bad serialization") } @@ -873,54 +1085,74 @@ func TestFp6AdditionProperties(t *testing.T) { c_1 := field.new() c_2 := field.new() field.add(c_1, a, zero) + if !c_1.equal(a) { t.Fatal("a + 0 == a") } + field.sub(c_1, a, zero) + if !c_1.equal(a) { t.Fatal("a - 0 == a") } + field.double(c_1, zero) + if !c_1.equal(zero) { t.Fatal("2 * 0 == 0") } + field.neg(c_1, zero) + if !c_1.equal(zero) { t.Fatal("-0 == 0") } + field.sub(c_1, zero, a) field.neg(c_2, a) + if !c_1.equal(c_2) { t.Fatal("0-a == -a") } + field.double(c_1, a) field.add(c_2, a, a) + if !c_1.equal(c_2) { t.Fatal("2 * a == a + a") } + field.add(c_1, a, b) field.add(c_2, b, a) + if !c_1.equal(c_2) { t.Fatal("a + b = b + a") } + field.sub(c_1, a, b) field.sub(c_2, b, a) field.neg(c_2, c_2) + if !c_1.equal(c_2) { t.Fatal("a - b = - ( b - a )") } + c_x, _ := new(fe6).rand(rand.Reader) + field.add(c_1, a, b) field.add(c_1, c_1, c_x) field.add(c_2, a, c_x) field.add(c_2, c_2, b) + if !c_1.equal(c_2) { t.Fatal("(a + b) + c == (a + c ) + b") } + field.sub(c_1, a, b) field.sub(c_1, c_1, c_x) field.sub(c_2, a, c_x) field.sub(c_2, c_2, b) + if !c_1.equal(c_2) { t.Fatal("(a - b) - c == (a - c ) -b") } @@ -929,55 +1161,71 @@ func TestFp6AdditionProperties(t *testing.T) { func TestFp6AdditionPropertiesAssigned(t *testing.T) { field := newFp6(nil) + for i := 0; i < fuz; i++ { zero := new(fe6).zero() a, b := new(fe6), new(fe6) _, _ = a.rand(rand.Reader) b.set(a) field.addAssign(a, zero) + if !a.equal(b) { t.Fatal("a + 0 == a") } + field.subAssign(a, zero) + if !a.equal(b) { t.Fatal("a - 0 == a") } + a.set(zero) field.doubleAssign(a) + if !a.equal(zero) { t.Fatal("2 * 0 == 0") } + a.set(zero) field.subAssign(a, b) field.neg(b, b) + if !a.equal(b) { t.Fatal("0-a == -a") } + _, _ = a.rand(rand.Reader) b.set(a) field.doubleAssign(a) field.addAssign(b, b) + if !a.equal(b) { t.Fatal("2 * a == a + a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) c_1, c_2 := new(fe6).set(a), new(fe6).set(b) field.addAssign(c_1, b) field.addAssign(c_2, a) + if !c_1.equal(c_2) { t.Fatal("a + b = b + a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) + c_1.set(a) c_2.set(b) field.subAssign(c_1, b) field.subAssign(c_2, a) field.neg(c_2, c_2) + if !c_1.equal(c_2) { t.Fatal("a - b = - ( b - a )") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) c, _ := new(fe6).rand(rand.Reader) @@ -986,17 +1234,21 @@ func TestFp6AdditionPropertiesAssigned(t *testing.T) { field.addAssign(a, c) field.addAssign(b, c) field.addAssign(b, a0) + if !a.equal(b) { t.Fatal("(a + b) + c == (b + c) + a") } + _, _ = a.rand(rand.Reader) _, _ = b.rand(rand.Reader) _, _ = c.rand(rand.Reader) + a0.set(a) field.subAssign(a, b) field.subAssign(a, c) field.subAssign(a0, c) field.subAssign(a0, b) + if !a.equal(a0) { t.Fatal("(a - b) - c == (a - c) -b") } @@ -1005,26 +1257,32 @@ func TestFp6AdditionPropertiesAssigned(t *testing.T) { func TestFp6SparseMultiplication(t *testing.T) { fp6 := newFp6(nil) + var a, b, u *fe6 for j := 0; j < fuz; j++ { a, _ = new(fe6).rand(rand.Reader) b, _ = new(fe6).rand(rand.Reader) u, _ = new(fe6).rand(rand.Reader) + b[2].zero() fp6.mul(u, a, b) fp6.mulBy01(a, a, &b[0], &b[1]) + if !a.equal(u) { t.Fatal("bad mul by 01") } } + for j := 0; j < fuz; j++ { a, _ = new(fe6).rand(rand.Reader) b, _ = new(fe6).rand(rand.Reader) u, _ = new(fe6).rand(rand.Reader) + b[2].zero() b[0].zero() fp6.mul(u, a, b) fp6.mulBy1(a, a, &b[1]) + if !a.equal(u) { t.Fatal("bad mul by 1") } @@ -1033,6 +1291,7 @@ func TestFp6SparseMultiplication(t *testing.T) { func TestFp6MultiplicationProperties(t *testing.T) { field := newFp6(nil) + for i := 0; i < fuz; i++ { a, _ := new(fe6).rand(rand.Reader) b, _ := new(fe6).rand(rand.Reader) @@ -1040,37 +1299,51 @@ func TestFp6MultiplicationProperties(t *testing.T) { one := field.one() c_1, c_2 := field.new(), field.new() field.mul(c_1, a, zero) + if !c_1.equal(zero) { t.Fatal("a * 0 == 0") } + field.mul(c_1, a, one) + if !c_1.equal(a) { t.Fatal("a * 1 == a") } + field.mul(c_1, a, b) field.mul(c_2, b, a) + if !c_1.equal(c_2) { t.Fatal("a * b == b * a") } + c_x, _ := new(fe6).rand(rand.Reader) + field.mul(c_1, a, b) field.mul(c_1, c_1, c_x) field.mul(c_2, c_x, b) field.mul(c_2, c_2, a) + if !c_1.equal(c_2) { t.Fatal("(a * b) * c == (a * c) * b") } + field.square(a, zero) + if !a.equal(zero) { t.Fatal("0^2 == 0") } + field.square(a, one) + if !a.equal(one) { t.Fatal("1^2 == 1") } + _, _ = a.rand(rand.Reader) field.square(c_1, a) field.mul(c_2, a, a) + if !c_2.equal(c_1) { t.Fatal("a^2 == a*a") } @@ -1079,33 +1352,43 @@ func TestFp6MultiplicationProperties(t *testing.T) { func TestFp6MultiplicationPropertiesAssigned(t *testing.T) { field := newFp6(nil) + for i := 0; i < fuz; i++ { a, _ := new(fe6).rand(rand.Reader) zero, one := new(fe6).zero(), new(fe6).one() field.mulAssign(a, zero) + if !a.equal(zero) { t.Fatal("a * 0 == 0") } + _, _ = a.rand(rand.Reader) a0 := new(fe6).set(a) field.mulAssign(a, one) + if !a.equal(a0) { t.Fatal("a * 1 == a") } + _, _ = a.rand(rand.Reader) b, _ := new(fe6).rand(rand.Reader) + a0.set(a) field.mulAssign(a, b) field.mulAssign(b, a0) + if !a.equal(b) { t.Fatal("a * b == b * a") } + c, _ := new(fe6).rand(rand.Reader) + a0.set(a) field.mulAssign(a, b) field.mulAssign(a, c) field.mulAssign(a0, c) field.mulAssign(a0, b) + if !a.equal(a0) { t.Fatal("(a * b) * c == (a * c) * b") } @@ -1114,22 +1397,28 @@ func TestFp6MultiplicationPropertiesAssigned(t *testing.T) { func TestFp6Exponentiation(t *testing.T) { field := newFp6(nil) + for i := 0; i < fuz; i++ { a, _ := new(fe6).rand(rand.Reader) u := field.new() field.exp(u, a, big.NewInt(0)) + if !u.equal(field.one()) { t.Fatal("a^0 == 1") } + field.exp(u, a, big.NewInt(1)) + if !u.equal(a) { t.Fatal("a^1 == a") } + v := field.new() field.mul(u, a, a) field.mul(u, u, u) field.mul(u, u, u) field.exp(v, a, big.NewInt(8)) + if !u.equal(v) { t.Fatal("((a^2)^2)^2 == a^8") } @@ -1143,16 +1432,21 @@ func TestFp6Inversion(t *testing.T) { zero := field.zero() one := field.one() field.inverse(u, zero) + if !u.equal(zero) { t.Fatal("(0^-1) == 0)") } + field.inverse(u, one) + if !u.equal(one) { t.Fatal("(1^-1) == 1)") } + a, _ := new(fe6).rand(rand.Reader) field.inverse(u, a) field.mul(u, u, a) + if !u.equal(one) { t.Fatal("(r*a) * r*(a^-1) == r)") } @@ -1161,12 +1455,15 @@ func TestFp6Inversion(t *testing.T) { func TestFp12Serialization(t *testing.T) { field := newFp12(nil) + for i := 0; i < fuz; i++ { a, _ := new(fe12).rand(rand.Reader) + b, err := field.fromBytes(field.toBytes(a)) if err != nil { t.Fatal(err) } + if !a.equal(b) { t.Fatal("bad serialization") } @@ -1182,54 +1479,74 @@ func TestFp12AdditionProperties(t *testing.T) { c_1 := field.new() c_2 := field.new() field.add(c_1, a, zero) + if !c_1.equal(a) { t.Fatal("a + 0 == a") } + field.sub(c_1, a, zero) + if !c_1.equal(a) { t.Fatal("a - 0 == a") } + field.double(c_1, zero) + if !c_1.equal(zero) { t.Fatal("2 * 0 == 0") } + field.neg(c_1, zero) + if !c_1.equal(zero) { t.Fatal("-0 == 0") } + field.sub(c_1, zero, a) field.neg(c_2, a) + if !c_1.equal(c_2) { t.Fatal("0-a == -a") } + field.double(c_1, a) field.add(c_2, a, a) + if !c_1.equal(c_2) { t.Fatal("2 * a == a + a") } + field.add(c_1, a, b) field.add(c_2, b, a) + if !c_1.equal(c_2) { t.Fatal("a + b = b + a") } + field.sub(c_1, a, b) field.sub(c_2, b, a) field.neg(c_2, c_2) + if !c_1.equal(c_2) { t.Fatal("a - b = - ( b - a )") } + c_x, _ := new(fe12).rand(rand.Reader) + field.add(c_1, a, b) field.add(c_1, c_1, c_x) field.add(c_2, a, c_x) field.add(c_2, c_2, b) + if !c_1.equal(c_2) { t.Fatal("(a + b) + c == (a + c ) + b") } + field.sub(c_1, a, b) field.sub(c_1, c_1, c_x) field.sub(c_2, a, c_x) field.sub(c_2, c_2, b) + if !c_1.equal(c_2) { t.Fatal("(a - b) - c == (a - c ) -b") } @@ -1238,6 +1555,7 @@ func TestFp12AdditionProperties(t *testing.T) { func TestFp12MultiplicationProperties(t *testing.T) { field := newFp12(nil) + for i := 0; i < fuz; i++ { a, _ := new(fe12).rand(rand.Reader) b, _ := new(fe12).rand(rand.Reader) @@ -1245,37 +1563,51 @@ func TestFp12MultiplicationProperties(t *testing.T) { one := field.one() c_1, c_2 := field.new(), field.new() field.mul(c_1, a, zero) + if !c_1.equal(zero) { t.Fatal("a * 0 == 0") } + field.mul(c_1, a, one) + if !c_1.equal(a) { t.Fatal("a * 1 == a") } + field.mul(c_1, a, b) field.mul(c_2, b, a) + if !c_1.equal(c_2) { t.Fatal("a * b == b * a") } + c_x, _ := new(fe12).rand(rand.Reader) + field.mul(c_1, a, b) field.mul(c_1, c_1, c_x) field.mul(c_2, c_x, b) field.mul(c_2, c_2, a) + if !c_1.equal(c_2) { t.Fatal("(a * b) * c == (a * c) * b") } + field.square(a, zero) + if !a.equal(zero) { t.Fatal("0^2 == 0") } + field.square(a, one) + if !a.equal(one) { t.Fatal("1^2 == 1") } + _, _ = a.rand(rand.Reader) field.square(c_1, a) field.mul(c_2, a, a) + if !c_2.equal(c_1) { t.Fatal("a^2 == a*a") } @@ -1284,33 +1616,43 @@ func TestFp12MultiplicationProperties(t *testing.T) { func TestFp12MultiplicationPropertiesAssigned(t *testing.T) { field := newFp12(nil) + for i := 0; i < fuz; i++ { a, _ := new(fe12).rand(rand.Reader) zero, one := new(fe12).zero(), new(fe12).one() field.mulAssign(a, zero) + if !a.equal(zero) { t.Fatal("a * 0 == 0") } + _, _ = a.rand(rand.Reader) a0 := new(fe12).set(a) field.mulAssign(a, one) + if !a.equal(a0) { t.Fatal("a * 1 == a") } + _, _ = a.rand(rand.Reader) b, _ := new(fe12).rand(rand.Reader) + a0.set(a) field.mulAssign(a, b) field.mulAssign(b, a0) + if !a.equal(b) { t.Fatal("a * b == b * a") } + c, _ := new(fe12).rand(rand.Reader) + a0.set(a) field.mulAssign(a, b) field.mulAssign(a, c) field.mulAssign(a0, c) field.mulAssign(a0, b) + if !a.equal(a0) { t.Fatal("(a * b) * c == (a * c) * b") } @@ -1319,16 +1661,19 @@ func TestFp12MultiplicationPropertiesAssigned(t *testing.T) { func TestFp12SparseMultiplication(t *testing.T) { fp12 := newFp12(nil) + var a, b, u *fe12 for j := 0; j < fuz; j++ { a, _ = new(fe12).rand(rand.Reader) b, _ = new(fe12).rand(rand.Reader) u, _ = new(fe12).rand(rand.Reader) + b[0][2].zero() b[1][0].zero() b[1][2].zero() fp12.mul(u, a, b) fp12.mulBy014Assign(a, &b[0][0], &b[0][1], &b[1][1]) + if !a.equal(u) { t.Fatal("bad mul by 01") } @@ -1337,22 +1682,28 @@ func TestFp12SparseMultiplication(t *testing.T) { func TestFp12Exponentiation(t *testing.T) { field := newFp12(nil) + for i := 0; i < fuz; i++ { a, _ := new(fe12).rand(rand.Reader) u := field.new() field.exp(u, a, big.NewInt(0)) + if !u.equal(field.one()) { t.Fatal("a^0 == 1") } + field.exp(u, a, big.NewInt(1)) + if !u.equal(a) { t.Fatal("a^1 == a") } + v := field.new() field.mul(u, a, a) field.mul(u, u, u) field.mul(u, u, u) field.exp(v, a, big.NewInt(8)) + if !u.equal(v) { t.Fatal("((a^2)^2)^2 == a^8") } @@ -1366,16 +1717,21 @@ func TestFp12Inversion(t *testing.T) { zero := field.zero() one := field.one() field.inverse(u, zero) + if !u.equal(zero) { t.Fatal("(0^-1) == 0)") } + field.inverse(u, one) + if !u.equal(one) { t.Fatal("(1^-1) == 1)") } + a, _ := new(fe12).rand(rand.Reader) field.inverse(u, a) field.mul(u, u, a) + if !u.equal(one) { t.Fatal("(r*a) * r*(a^-1) == r)") } @@ -1386,7 +1742,9 @@ func BenchmarkMultiplication(t *testing.B) { a, _ := new(fe).rand(rand.Reader) b, _ := new(fe).rand(rand.Reader) c, _ := new(fe).rand(rand.Reader) + t.ResetTimer() + for i := 0; i < t.N; i++ { mul(c, a, b) } @@ -1395,7 +1753,9 @@ 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) } @@ -1403,9 +1763,12 @@ func BenchmarkInverse(t *testing.B) { func padBytes(in []byte, size int) []byte { out := make([]byte, size) + if len(in) > size { panic("bad input for padding") } + copy(out[size-len(in):], in) + return out } diff --git a/crypto/bls12381/g1.go b/crypto/bls12381/g1.go index bcb898027a..23d9cf0ec7 100644 --- a/crypto/bls12381/g1.go +++ b/crypto/bls12381/g1.go @@ -31,6 +31,7 @@ func (p *PointG1) Set(p2 *PointG1) *PointG1 { p[0].set(&p2[0]) p[1].set(&p2[1]) p[2].set(&p2[2]) + return p } @@ -39,6 +40,7 @@ func (p *PointG1) Zero() *PointG1 { p[0].zero() p[1].one() p[2].zero() + return p } @@ -62,6 +64,7 @@ func newTempG1() tempG1 { for i := 0; i < 9; i++ { t[i] = &fe{} } + return tempG1{t} } @@ -75,11 +78,14 @@ func (g *G1) fromBytesUnchecked(in []byte) (*PointG1, error) { if err != nil { return nil, err } + p1, err := fromBytes(in[48:]) if err != nil { return nil, err } + p2 := new(fe).one() + return &PointG1{*p0, *p1, *p2}, nil } @@ -92,10 +98,12 @@ func (g *G1) FromBytes(in []byte) (*PointG1, error) { if len(in) != 96 { return nil, errors.New("input string should be equal or larger than 96") } + p0, err := fromBytes(in[:48]) if err != nil { return nil, err } + p1, err := fromBytes(in[48:]) if err != nil { return nil, err @@ -104,11 +112,14 @@ func (g *G1) FromBytes(in []byte) (*PointG1, error) { if p0.isZero() && p1.isZero() { return g.Zero(), nil } + p2 := new(fe).one() + p := &PointG1{*p0, *p1, *p2} if !g.IsOnCurve(p) { return nil, errors.New("point is not on curve") } + return p, nil } @@ -117,6 +128,7 @@ func (g *G1) DecodePoint(in []byte) (*PointG1, error) { if len(in) != 128 { return nil, errors.New("invalid g1 point length") } + pointBytes := make([]byte, 96) // decode x xBytes, err := decodeFieldElement(in[:64]) @@ -128,8 +140,10 @@ func (g *G1) DecodePoint(in []byte) (*PointG1, error) { if err != nil { return nil, err } + copy(pointBytes[:48], xBytes) copy(pointBytes[48:], yBytes) + return g.FromBytes(pointBytes) } @@ -141,9 +155,11 @@ func (g *G1) ToBytes(p *PointG1) []byte { if g.IsZero(p) { return out } + g.Affine(p) copy(out[:48], toBytes(&p[0])) copy(out[48:], toBytes(&p[1])) + return out } @@ -155,6 +171,7 @@ func (g *G1) EncodePoint(p *PointG1) []byte { copy(out[16:], outRaw[:48]) // encode y copy(out[64+16:], outRaw[48:]) + return out } @@ -184,9 +201,11 @@ func (g *G1) Equal(p1, p2 *PointG1) bool { if g.IsZero(p1) { return g.IsZero(p2) } + if g.IsZero(p2) { return g.IsZero(p1) } + t := g.t square(t[0], &p1[2]) square(t[1], &p2[2]) @@ -196,6 +215,7 @@ func (g *G1) Equal(p1, p2 *PointG1) bool { mul(t[1], t[1], &p2[2]) mul(t[1], t[1], &p1[1]) mul(t[0], t[0], &p2[1]) + return t[0].equal(t[1]) && t[2].equal(t[3]) } @@ -203,6 +223,7 @@ func (g *G1) Equal(p1, p2 *PointG1) bool { func (g *G1) InCorrectSubgroup(p *PointG1) bool { tmp := &PointG1{} g.MulScalar(tmp, p, q) + return g.IsZero(tmp) } @@ -211,6 +232,7 @@ func (g *G1) IsOnCurve(p *PointG1) bool { if g.IsZero(p) { return true } + t := g.t square(t[0], &p[1]) square(t[1], &p[0]) @@ -220,6 +242,7 @@ func (g *G1) IsOnCurve(p *PointG1) bool { mul(t[2], t[2], t[3]) mul(t[2], b, t[2]) add(t[1], t[1], t[2]) + return t[0].equal(t[1]) } @@ -233,6 +256,7 @@ func (g *G1) Affine(p *PointG1) *PointG1 { if g.IsZero(p) { return p } + if !g.IsAffine(p) { t := g.t inverse(t[0], &p[2]) @@ -242,6 +266,7 @@ func (g *G1) Affine(p *PointG1) *PointG1 { mul(&p[1], &p[1], t[0]) p[2].one() } + return p } @@ -251,9 +276,11 @@ func (g *G1) Add(r, p1, p2 *PointG1) *PointG1 { if g.IsZero(p1) { return r.Set(p2) } + if g.IsZero(p2) { return r.Set(p1) } + t := g.t square(t[7], &p1[2]) mul(t[1], &p2[0], t[7]) @@ -263,12 +290,15 @@ func (g *G1) Add(r, p1, p2 *PointG1) *PointG1 { mul(t[3], &p1[0], t[8]) mul(t[4], &p2[2], t[8]) mul(t[2], &p1[1], t[4]) + if t[1].equal(t[3]) { if t[0].equal(t[2]) { return g.Double(r, p1) } + return r.Zero() } + sub(t[1], t[1], t[3]) double(t[4], t[1]) square(t[4], t[4]) @@ -290,6 +320,7 @@ func (g *G1) Add(r, p1, p2 *PointG1) *PointG1 { sub(t[0], t[0], t[7]) sub(t[0], t[0], t[8]) mul(&r[2], t[0], t[1]) + return r } @@ -299,6 +330,7 @@ func (g *G1) Double(r, p *PointG1) *PointG1 { if g.IsZero(p) { return r.Set(p) } + t := g.t square(t[0], &p[0]) square(t[1], &p[1]) @@ -322,6 +354,7 @@ func (g *G1) Double(r, p *PointG1) *PointG1 { mul(t[0], &p[1], &p[2]) r[1].set(t[1]) double(&r[2], t[0]) + return r } @@ -330,6 +363,7 @@ func (g *G1) Neg(r, p *PointG1) *PointG1 { r[0].set(&p[0]) r[2].set(&p[2]) neg(&r[1], &p[1]) + return r } @@ -338,6 +372,7 @@ func (g *G1) Sub(c, a, b *PointG1) *PointG1 { d := &PointG1{} g.Neg(d, b) g.Add(c, a, d) + return c } @@ -345,13 +380,16 @@ func (g *G1) Sub(c, a, b *PointG1) *PointG1 { func (g *G1) MulScalar(c, p *PointG1, e *big.Int) *PointG1 { q, n := &PointG1{}, &PointG1{} n.Set(p) + l := e.BitLen() for i := 0; i < l; i++ { if e.Bit(i) == 1 { g.Add(q, q, n) } + g.Double(n, n) } + return c.Set(q) } @@ -368,51 +406,65 @@ func (g *G1) MultiExp(r *PointG1, points []*PointG1, powers []*big.Int) (*PointG if len(points) != len(powers) { return nil, errors.New("point and scalar vectors should be in same length") } + var c uint32 = 3 if len(powers) >= 32 { c = uint32(math.Ceil(math.Log10(float64(len(powers))))) } + bucketSize, numBits := (1<= 0; i-- { g.Add(sum, sum, bucket[i]) g.Add(acc, acc, sum) } + windows[j] = g.New() windows[j].Set(acc) + j++ cur += c } acc.Zero() + for i := len(windows) - 1; i >= 0; i-- { for j := uint32(0); j < c; j++ { g.Double(acc, acc) } g.Add(acc, acc, windows[i]) } + return r.Set(acc), nil } @@ -425,10 +477,13 @@ func (g *G1) MapToCurve(in []byte) (*PointG1, error) { if err != nil { return nil, err } + x, y := swuMapG1(u) isogenyMapG1(x, y) + one := new(fe).one() p := &PointG1{*x, *y, *one} g.ClearCofactor(p) + return g.Affine(p), nil } diff --git a/crypto/bls12381/g1_test.go b/crypto/bls12381/g1_test.go index 87140459fb..5b1f956a1a 100644 --- a/crypto/bls12381/g1_test.go +++ b/crypto/bls12381/g1_test.go @@ -16,6 +16,7 @@ func (g *G1) one() *PointG1 { "08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", ), ) + return one } @@ -24,6 +25,7 @@ func (g *G1) rand() *PointG1 { if err != nil { panic(err) } + return g.MulScalar(&PointG1{}, g.one(), k) } @@ -32,21 +34,26 @@ func TestG1Serialization(t *testing.T) { for i := 0; i < fuz; i++ { a := g1.rand() buf := g1.ToBytes(a) + b, err := g1.FromBytes(buf) if err != nil { t.Fatal(err) } + if !g1.Equal(a, b) { t.Fatal("bad serialization from/to") } } + for i := 0; i < fuz; i++ { a := g1.rand() encoded := g1.EncodePoint(a) + b, err := g1.DecodePoint(encoded) if err != nil { t.Fatal(err) } + if !g1.Equal(a, b) { t.Fatal("bad serialization encode/decode") } @@ -56,10 +63,13 @@ func TestG1Serialization(t *testing.T) { func TestG1IsOnCurve(t *testing.T) { g := NewG1() zero := g.Zero() + if !g.IsOnCurve(zero) { t.Fatal("zero must be on curve") } + one := new(fe).one() + p := &PointG1{*one, *one, *one} if g.IsOnCurve(p) { t.Fatal("(1, 1) is not on curve") @@ -70,65 +80,89 @@ func TestG1AdditiveProperties(t *testing.T) { g := NewG1() t0, t1 := g.New(), g.New() zero := g.Zero() + for i := 0; i < fuz; i++ { a, b := g.rand(), g.rand() g.Add(t0, a, zero) + if !g.Equal(t0, a) { t.Fatal("a + 0 == a") } + g.Add(t0, zero, zero) + if !g.Equal(t0, zero) { t.Fatal("0 + 0 == 0") } + g.Sub(t0, a, zero) + if !g.Equal(t0, a) { t.Fatal("a - 0 == a") } + g.Sub(t0, zero, zero) + if !g.Equal(t0, zero) { t.Fatal("0 - 0 == 0") } + g.Neg(t0, zero) + if !g.Equal(t0, zero) { t.Fatal("- 0 == 0") } + g.Sub(t0, zero, a) g.Neg(t0, t0) + if !g.Equal(t0, a) { t.Fatal(" - (0 - a) == a") } + g.Double(t0, zero) + if !g.Equal(t0, zero) { t.Fatal("2 * 0 == 0") } + g.Double(t0, a) g.Sub(t0, t0, a) + if !g.Equal(t0, a) || !g.IsOnCurve(t0) { t.Fatal(" (2 * a) - a == a") } + g.Add(t0, a, b) g.Add(t1, b, a) + if !g.Equal(t0, t1) { t.Fatal("a + b == b + a") } + g.Sub(t0, a, b) g.Sub(t1, b, a) g.Neg(t1, t1) + if !g.Equal(t0, t1) { t.Fatal("a - b == - ( b - a )") } + c := g.rand() g.Add(t0, a, b) g.Add(t0, t0, c) g.Add(t1, a, c) g.Add(t1, t1, b) + if !g.Equal(t0, t1) { t.Fatal("(a + b) + c == (a + c ) + b") } + g.Sub(t0, a, b) g.Sub(t0, t0, c) g.Sub(t1, a, c) g.Sub(t1, t1, b) + if !g.Equal(t0, t1) { t.Fatal("(a - b) - c == (a - c) -b") } @@ -139,34 +173,45 @@ func TestG1MultiplicativeProperties(t *testing.T) { g := NewG1() t0, t1 := g.New(), g.New() zero := g.Zero() + for i := 0; i < fuz; i++ { a := g.rand() s1, s2, s3 := randScalar(q), randScalar(q), randScalar(q) sone := big.NewInt(1) + g.MulScalar(t0, zero, s1) + if !g.Equal(t0, zero) { t.Fatal(" 0 ^ s == 0") } + g.MulScalar(t0, a, sone) + if !g.Equal(t0, a) { t.Fatal(" a ^ 1 == a") } + g.MulScalar(t0, zero, s1) + if !g.Equal(t0, zero) { t.Fatal(" 0 ^ s == a") } + g.MulScalar(t0, a, s1) g.MulScalar(t0, t0, s2) s3.Mul(s1, s2) g.MulScalar(t1, a, s3) + if !g.Equal(t0, t1) { t.Errorf(" (a ^ s1) ^ s2 == a ^ (s1 * s2)") } + g.MulScalar(t0, a, s1) g.MulScalar(t1, a, s2) g.Add(t0, t0, t1) s3.Add(s1, s2) g.MulScalar(t1, a, s3) + if !g.Equal(t0, t1) { t.Errorf(" (a ^ s1) + (a ^ s2) == a ^ (s1 + s2)") } @@ -176,14 +221,18 @@ func TestG1MultiplicativeProperties(t *testing.T) { func TestG1MultiExpExpected(t *testing.T) { g := NewG1() one := g.one() + var scalars [2]*big.Int + var bases [2]*PointG1 + scalars[0] = big.NewInt(2) scalars[1] = big.NewInt(3) bases[0], bases[1] = new(PointG1).Set(one), new(PointG1).Set(one) expected, result := g.New(), g.New() g.MulScalar(expected, one, big.NewInt(5)) _, _ = g.MultiExp(result, bases[:], scalars[:]) + if !g.Equal(expected, result) { t.Fatal("bad multi-exponentiation") } @@ -208,8 +257,10 @@ func TestG1MultiExpBatch(t *testing.T) { g.MulScalar(tmp, bases[i], scalars[i]) g.Add(expected, expected, tmp) } + result := g.New() _, _ = g.MultiExp(result, bases, scalars) + if !g.Equal(expected, result) { t.Fatal("bad multi-exponentiation") } @@ -242,10 +293,12 @@ func TestG1MapToCurve(t *testing.T) { }, } { g := NewG1() + p0, err := g.MapToCurve(v.u) if err != nil { t.Fatal("map to curve fails", i, err) } + if !bytes.Equal(g.ToBytes(p0), v.expected) { t.Fatal("map to curve fails", i) } @@ -255,7 +308,9 @@ func TestG1MapToCurve(t *testing.T) { func BenchmarkG1Add(t *testing.B) { g1 := NewG1() a, b, c := g1.rand(), g1.rand(), PointG1{} + t.ResetTimer() + for i := 0; i < t.N; i++ { g1.Add(&c, a, b) } @@ -265,7 +320,9 @@ func BenchmarkG1Mul(t *testing.B) { worstCaseScalar, _ := new(big.Int).SetString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16) g1 := NewG1() a, e, c := g1.rand(), worstCaseScalar, PointG1{} + t.ResetTimer() + for i := 0; i < t.N; i++ { g1.MulScalar(&c, a, e) } @@ -274,7 +331,9 @@ func BenchmarkG1Mul(t *testing.B) { func BenchmarkG1MapToCurve(t *testing.B) { a := make([]byte, 48) g1 := NewG1() + t.ResetTimer() + for i := 0; i < t.N; i++ { _, err := g1.MapToCurve(a) if err != nil { diff --git a/crypto/bls12381/g2.go b/crypto/bls12381/g2.go index 4d6f1ff11d..c181e821f5 100644 --- a/crypto/bls12381/g2.go +++ b/crypto/bls12381/g2.go @@ -32,6 +32,7 @@ func (p *PointG2) Set(p2 *PointG2) *PointG2 { p[0].set(&p2[0]) p[1].set(&p2[1]) p[2].set(&p2[2]) + return p } @@ -40,6 +41,7 @@ func (p *PointG2) Zero() *PointG2 { p[0].zero() p[1].one() p[2].zero() + return p } @@ -62,7 +64,9 @@ func newG2(f *fp2) *G2 { if f == nil { f = newFp2() } + t := newTempG2() + return &G2{f, t} } @@ -71,6 +75,7 @@ func newTempG2() tempG2 { for i := 0; i < 9; i++ { t[i] = &fe2{} } + return tempG2{t} } @@ -84,11 +89,14 @@ func (g *G2) fromBytesUnchecked(in []byte) (*PointG2, error) { if err != nil { return nil, err } + p1, err := g.f.fromBytes(in[96:]) if err != nil { return nil, err } + p2 := new(fe2).one() + return &PointG2{*p0, *p1, *p2}, nil } @@ -101,10 +109,12 @@ func (g *G2) FromBytes(in []byte) (*PointG2, error) { if len(in) != 192 { return nil, errors.New("input string should be equal or larger than 192") } + p0, err := g.f.fromBytes(in[:96]) if err != nil { return nil, err } + p1, err := g.f.fromBytes(in[96:]) if err != nil { return nil, err @@ -113,11 +123,14 @@ func (g *G2) FromBytes(in []byte) (*PointG2, error) { if p0.isZero() && p1.isZero() { return g.Zero(), nil } + p2 := new(fe2).one() + p := &PointG2{*p0, *p1, *p2} if !g.IsOnCurve(p) { return nil, errors.New("point is not on curve") } + return p, nil } @@ -126,27 +139,34 @@ func (g *G2) DecodePoint(in []byte) (*PointG2, error) { if len(in) != 256 { return nil, errors.New("invalid g2 point length") } + pointBytes := make([]byte, 192) + x0Bytes, err := decodeFieldElement(in[:64]) if err != nil { return nil, err } + x1Bytes, err := decodeFieldElement(in[64:128]) if err != nil { return nil, err } + y0Bytes, err := decodeFieldElement(in[128:192]) if err != nil { return nil, err } + y1Bytes, err := decodeFieldElement(in[192:]) if err != nil { return nil, err } + copy(pointBytes[:48], x1Bytes) copy(pointBytes[48:96], x0Bytes) copy(pointBytes[96:144], y1Bytes) copy(pointBytes[144:192], y0Bytes) + return g.FromBytes(pointBytes) } @@ -158,9 +178,11 @@ func (g *G2) ToBytes(p *PointG2) []byte { if g.IsZero(p) { return out } + g.Affine(p) copy(out[:96], g.f.toBytes(&p[0])) copy(out[96:], g.f.toBytes(&p[1])) + return out } @@ -175,6 +197,7 @@ func (g *G2) EncodePoint(p *PointG2) []byte { // encode y copy(out[144:144+48], outRaw[144:]) copy(out[208:208+48], outRaw[96:144]) + return out } @@ -204,9 +227,11 @@ func (g *G2) Equal(p1, p2 *PointG2) bool { if g.IsZero(p1) { return g.IsZero(p2) } + if g.IsZero(p2) { return g.IsZero(p1) } + t := g.t g.f.square(t[0], &p1[2]) g.f.square(t[1], &p2[2]) @@ -216,6 +241,7 @@ func (g *G2) Equal(p1, p2 *PointG2) bool { g.f.mul(t[1], t[1], &p2[2]) g.f.mul(t[1], t[1], &p1[1]) g.f.mul(t[0], t[0], &p2[1]) + return t[0].equal(t[1]) && t[2].equal(t[3]) } @@ -223,6 +249,7 @@ func (g *G2) Equal(p1, p2 *PointG2) bool { func (g *G2) InCorrectSubgroup(p *PointG2) bool { tmp := &PointG2{} g.MulScalar(tmp, p, q) + return g.IsZero(tmp) } @@ -231,6 +258,7 @@ func (g *G2) IsOnCurve(p *PointG2) bool { if g.IsZero(p) { return true } + t := g.t g.f.square(t[0], &p[1]) g.f.square(t[1], &p[0]) @@ -240,6 +268,7 @@ func (g *G2) IsOnCurve(p *PointG2) bool { g.f.mul(t[2], t[2], t[3]) g.f.mul(t[2], b2, t[2]) g.f.add(t[1], t[1], t[2]) + return t[0].equal(t[1]) } @@ -253,6 +282,7 @@ func (g *G2) Affine(p *PointG2) *PointG2 { if g.IsZero(p) { return p } + if !g.IsAffine(p) { t := g.t g.f.inverse(t[0], &p[2]) @@ -262,6 +292,7 @@ func (g *G2) Affine(p *PointG2) *PointG2 { g.f.mul(&p[1], &p[1], t[0]) p[2].one() } + return p } @@ -271,9 +302,11 @@ func (g *G2) Add(r, p1, p2 *PointG2) *PointG2 { if g.IsZero(p1) { return r.Set(p2) } + if g.IsZero(p2) { return r.Set(p1) } + t := g.t g.f.square(t[7], &p1[2]) g.f.mul(t[1], &p2[0], t[7]) @@ -283,12 +316,15 @@ func (g *G2) Add(r, p1, p2 *PointG2) *PointG2 { g.f.mul(t[3], &p1[0], t[8]) g.f.mul(t[4], &p2[2], t[8]) g.f.mul(t[2], &p1[1], t[4]) + if t[1].equal(t[3]) { if t[0].equal(t[2]) { return g.Double(r, p1) } + return r.Zero() } + g.f.sub(t[1], t[1], t[3]) g.f.double(t[4], t[1]) g.f.square(t[4], t[4]) @@ -310,6 +346,7 @@ func (g *G2) Add(r, p1, p2 *PointG2) *PointG2 { g.f.sub(t[0], t[0], t[7]) g.f.sub(t[0], t[0], t[8]) g.f.mul(&r[2], t[0], t[1]) + return r } @@ -319,6 +356,7 @@ func (g *G2) Double(r, p *PointG2) *PointG2 { if g.IsZero(p) { return r.Set(p) } + t := g.t g.f.square(t[0], &p[0]) g.f.square(t[1], &p[1]) @@ -342,6 +380,7 @@ func (g *G2) Double(r, p *PointG2) *PointG2 { g.f.mul(t[0], &p[1], &p[2]) r[1].set(t[1]) g.f.double(&r[2], t[0]) + return r } @@ -350,6 +389,7 @@ func (g *G2) Neg(r, p *PointG2) *PointG2 { r[0].set(&p[0]) g.f.neg(&r[1], &p[1]) r[2].set(&p[2]) + return r } @@ -358,6 +398,7 @@ func (g *G2) Sub(c, a, b *PointG2) *PointG2 { d := &PointG2{} g.Neg(d, b) g.Add(c, a, d) + return c } @@ -365,13 +406,16 @@ func (g *G2) Sub(c, a, b *PointG2) *PointG2 { func (g *G2) MulScalar(c, p *PointG2, e *big.Int) *PointG2 { q, n := &PointG2{}, &PointG2{} n.Set(p) + l := e.BitLen() for i := 0; i < l; i++ { if e.Bit(i) == 1 { g.Add(q, q, n) } + g.Double(n, n) } + return c.Set(q) } @@ -388,51 +432,65 @@ func (g *G2) MultiExp(r *PointG2, points []*PointG2, powers []*big.Int) (*PointG if len(points) != len(powers) { return nil, errors.New("point and scalar vectors should be in same length") } + var c uint32 = 3 if len(powers) >= 32 { c = uint32(math.Ceil(math.Log10(float64(len(powers))))) } + bucketSize, numBits := (1<= 0; i-- { g.Add(sum, sum, bucket[i]) g.Add(acc, acc, sum) } + windows[j] = g.New() windows[j].Set(acc) + j++ cur += c } acc.Zero() + for i := len(windows) - 1; i >= 0; i-- { for j := uint32(0); j < c; j++ { g.Double(acc, acc) } g.Add(acc, acc, windows[i]) } + return r.Set(acc), nil } @@ -442,14 +500,18 @@ func (g *G2) MultiExp(r *PointG2, points []*PointG2, powers []*big.Int) (*PointG // Input byte slice should be a valid field element, otherwise an error is returned. func (g *G2) MapToCurve(in []byte) (*PointG2, error) { fp2 := g.f + u, err := fp2.fromBytes(in) if err != nil { return nil, err } + x, y := swuMapG2(fp2, u) isogenyMapG2(fp2, x, y) + z := new(fe2).one() q := &PointG2{*x, *y, *z} g.ClearCofactor(q) + return g.Affine(q), nil } diff --git a/crypto/bls12381/g2_test.go b/crypto/bls12381/g2_test.go index 4d1f3a19ac..1caf7c0146 100644 --- a/crypto/bls12381/g2_test.go +++ b/crypto/bls12381/g2_test.go @@ -18,6 +18,7 @@ func (g *G2) one() *PointG2 { "0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801", ), ) + return one } @@ -26,6 +27,7 @@ func (g *G2) rand() *PointG2 { if err != nil { panic(err) } + return g.MulScalar(&PointG2{}, g.one(), k) } @@ -34,21 +36,26 @@ func TestG2Serialization(t *testing.T) { for i := 0; i < fuz; i++ { a := g2.rand() buf := g2.ToBytes(a) + b, err := g2.FromBytes(buf) if err != nil { t.Fatal(err) } + if !g2.Equal(a, b) { t.Fatal("bad serialization from/to") } } + for i := 0; i < fuz; i++ { a := g2.rand() encoded := g2.EncodePoint(a) + b, err := g2.DecodePoint(encoded) if err != nil { t.Fatal(err) } + if !g2.Equal(a, b) { t.Fatal("bad serialization encode/decode") } @@ -58,10 +65,13 @@ func TestG2Serialization(t *testing.T) { func TestG2IsOnCurve(t *testing.T) { g := NewG2() zero := g.Zero() + if !g.IsOnCurve(zero) { t.Fatal("zero must be on curve") } + one := new(fe2).one() + p := &PointG2{*one, *one, *one} if g.IsOnCurve(p) { t.Fatal("(1, 1) is not on curve") @@ -72,66 +82,90 @@ func TestG2AdditiveProperties(t *testing.T) { g := NewG2() t0, t1 := g.New(), g.New() zero := g.Zero() + for i := 0; i < fuz; i++ { a, b := g.rand(), g.rand() _, _, _ = b, t1, zero g.Add(t0, a, zero) + if !g.Equal(t0, a) { t.Fatal("a + 0 == a") } + g.Add(t0, zero, zero) + if !g.Equal(t0, zero) { t.Fatal("0 + 0 == 0") } + g.Sub(t0, a, zero) + if !g.Equal(t0, a) { t.Fatal("a - 0 == a") } + g.Sub(t0, zero, zero) + if !g.Equal(t0, zero) { t.Fatal("0 - 0 == 0") } + g.Neg(t0, zero) + if !g.Equal(t0, zero) { t.Fatal("- 0 == 0") } + g.Sub(t0, zero, a) g.Neg(t0, t0) + if !g.Equal(t0, a) { t.Fatal(" - (0 - a) == a") } + g.Double(t0, zero) + if !g.Equal(t0, zero) { t.Fatal("2 * 0 == 0") } + g.Double(t0, a) g.Sub(t0, t0, a) + if !g.Equal(t0, a) || !g.IsOnCurve(t0) { t.Fatal(" (2 * a) - a == a") } + g.Add(t0, a, b) g.Add(t1, b, a) + if !g.Equal(t0, t1) { t.Fatal("a + b == b + a") } + g.Sub(t0, a, b) g.Sub(t1, b, a) g.Neg(t1, t1) + if !g.Equal(t0, t1) { t.Fatal("a - b == - ( b - a )") } + c := g.rand() g.Add(t0, a, b) g.Add(t0, t0, c) g.Add(t1, a, c) g.Add(t1, t1, b) + if !g.Equal(t0, t1) { t.Fatal("(a + b) + c == (a + c ) + b") } + g.Sub(t0, a, b) g.Sub(t0, t0, c) g.Sub(t1, a, c) g.Sub(t1, t1, b) + if !g.Equal(t0, t1) { t.Fatal("(a - b) - c == (a - c) -b") } @@ -142,34 +176,45 @@ func TestG2MultiplicativeProperties(t *testing.T) { g := NewG2() t0, t1 := g.New(), g.New() zero := g.Zero() + for i := 0; i < fuz; i++ { a := g.rand() s1, s2, s3 := randScalar(q), randScalar(q), randScalar(q) sone := big.NewInt(1) + g.MulScalar(t0, zero, s1) + if !g.Equal(t0, zero) { t.Fatal(" 0 ^ s == 0") } + g.MulScalar(t0, a, sone) + if !g.Equal(t0, a) { t.Fatal(" a ^ 1 == a") } + g.MulScalar(t0, zero, s1) + if !g.Equal(t0, zero) { t.Fatal(" 0 ^ s == a") } + g.MulScalar(t0, a, s1) g.MulScalar(t0, t0, s2) s3.Mul(s1, s2) g.MulScalar(t1, a, s3) + if !g.Equal(t0, t1) { t.Errorf(" (a ^ s1) ^ s2 == a ^ (s1 * s2)") } + g.MulScalar(t0, a, s1) g.MulScalar(t1, a, s2) g.Add(t0, t0, t1) s3.Add(s1, s2) g.MulScalar(t1, a, s3) + if !g.Equal(t0, t1) { t.Errorf(" (a ^ s1) + (a ^ s2) == a ^ (s1 + s2)") } @@ -179,14 +224,18 @@ func TestG2MultiplicativeProperties(t *testing.T) { func TestG2MultiExpExpected(t *testing.T) { g := NewG2() one := g.one() + var scalars [2]*big.Int + var bases [2]*PointG2 + scalars[0] = big.NewInt(2) scalars[1] = big.NewInt(3) bases[0], bases[1] = new(PointG2).Set(one), new(PointG2).Set(one) expected, result := g.New(), g.New() g.MulScalar(expected, one, big.NewInt(5)) _, _ = g.MultiExp(result, bases[:], scalars[:]) + if !g.Equal(expected, result) { t.Fatal("bad multi-exponentiation") } @@ -211,8 +260,10 @@ func TestG2MultiExpBatch(t *testing.T) { g.MulScalar(tmp, bases[i], scalars[i]) g.Add(expected, expected, tmp) } + result := g.New() _, _ = g.MultiExp(result, bases, scalars) + if !g.Equal(expected, result) { t.Fatal("bad multi-exponentiation") } @@ -245,10 +296,12 @@ func TestG2MapToCurve(t *testing.T) { }, } { g := NewG2() + p0, err := g.MapToCurve(v.u) if err != nil { t.Fatal("map to curve fails", i, err) } + if !bytes.Equal(g.ToBytes(p0), v.expected) { t.Fatal("map to curve fails", i) } @@ -258,7 +311,9 @@ func TestG2MapToCurve(t *testing.T) { func BenchmarkG2Add(t *testing.B) { g2 := NewG2() a, b, c := g2.rand(), g2.rand(), PointG2{} + t.ResetTimer() + for i := 0; i < t.N; i++ { g2.Add(&c, a, b) } @@ -268,7 +323,9 @@ func BenchmarkG2Mul(t *testing.B) { worstCaseScalar, _ := new(big.Int).SetString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16) g2 := NewG2() a, e, c := g2.rand(), worstCaseScalar, PointG2{} + t.ResetTimer() + for i := 0; i < t.N; i++ { g2.MulScalar(&c, a, e) } @@ -277,7 +334,9 @@ func BenchmarkG2Mul(t *testing.B) { func BenchmarkG2SWUMap(t *testing.B) { a := make([]byte, 96) g2 := NewG2() + t.ResetTimer() + for i := 0; i < t.N; i++ { _, err := g2.MapToCurve(a) if err != nil { diff --git a/crypto/bls12381/gt.go b/crypto/bls12381/gt.go index 2ac265e956..9ed91553a7 100644 --- a/crypto/bls12381/gt.go +++ b/crypto/bls12381/gt.go @@ -67,9 +67,11 @@ func (g *GT) FromBytes(in []byte) (*E, error) { if err != nil { return nil, err } + if !g.IsValid(e) { return e, errors.New("invalid element") } + return e, nil } @@ -82,6 +84,7 @@ func (g *GT) ToBytes(e *E) []byte { func (g *GT) IsValid(e *E) bool { r := g.New() g.fp12.exp(r, e, q) + return r.isOne() } diff --git a/crypto/bls12381/isogeny.go b/crypto/bls12381/isogeny.go index a63f585dd0..ee5836e779 100644 --- a/crypto/bls12381/isogeny.go +++ b/crypto/bls12381/isogeny.go @@ -26,6 +26,7 @@ func isogenyMapG1(x, y *fe) { xDen.set(params[1][degree]) yNum.set(params[2][degree]) yDen.set(params[3][degree]) + for i := degree - 1; i >= 0; i-- { mul(xNum, xNum, x) mul(xDen, xDen, x) @@ -57,6 +58,7 @@ func isogenyMapG2(e *fp2, x, y *fe2) { xDen := new(fe2).set(params[1][degree]) yNum := new(fe2).set(params[2][degree]) yDen := new(fe2).set(params[3][degree]) + for i := degree - 1; i >= 0; i-- { e.mul(xNum, xNum, x) e.mul(xDen, xDen, x) diff --git a/crypto/bls12381/pairing.go b/crypto/bls12381/pairing.go index d292d7c3a5..24e78bd7fc 100644 --- a/crypto/bls12381/pairing.go +++ b/crypto/bls12381/pairing.go @@ -42,6 +42,7 @@ func NewPairingEngine() *Engine { fp12 := newFp12(fp6) g1 := NewG1() g2 := newG2(fp2) + return &Engine{ fp2: fp2, fp12: fp12, @@ -61,7 +62,9 @@ func newEngineTemp() pairingEngineTemp { for i := 0; i < 10; i++ { t2[i] = &fe2{} } + t12 := [9]fe12{} + return pairingEngineTemp{t2, t12} } @@ -72,6 +75,7 @@ func (e *Engine) AddPair(g1 *PointG1, g2 *PointG2) *Engine { e.affine(p) e.pairs = append(e.pairs, p) } + return e } @@ -79,6 +83,7 @@ func (e *Engine) AddPair(g1 *PointG1, g2 *PointG2) *Engine { func (e *Engine) AddPairInv(g1 *PointG1, g2 *PointG2) *Engine { e.G1.Neg(g1, g1) e.AddPair(g1, g2) + return e } @@ -167,15 +172,19 @@ func (e *Engine) preCompute(ellCoeffs *[68][3]fe2, twistPoint *PointG2) { if e.G2.IsZero(twistPoint) { return } + r := new(PointG2).Set(twistPoint) j := 0 + for i := x.BitLen() - 2; i >= 0; i-- { e.doublingStep(&ellCoeffs[j], r) + if x.Bit(i) != 0 { j++ ellCoeffs[j] = fe6{} e.additionStep(&ellCoeffs[j], r, twistPoint) } + j++ } } @@ -183,22 +192,29 @@ func (e *Engine) preCompute(ellCoeffs *[68][3]fe2, twistPoint *PointG2) { func (e *Engine) millerLoop(f *fe12) { pairs := e.pairs ellCoeffs := make([][68][3]fe2, len(pairs)) + for i := 0; i < len(pairs); i++ { e.preCompute(&ellCoeffs[i], pairs[i].g2) } + fp12, fp2 := e.fp12, e.fp2 t := e.t2 + f.one() + j := 0 + for i := 62; /* x.BitLen() - 2 */ i >= 0; i-- { if i != 62 { fp12.square(f, f) } + for i := 0; i <= len(pairs)-1; i++ { fp2.mulByFq(t[0], &ellCoeffs[i][j][2], &pairs[i].g1[1]) fp2.mulByFq(t[1], &ellCoeffs[i][j][1], &pairs[i].g1[0]) fp12.mulBy014Assign(f, &ellCoeffs[i][j][0], t[1], t[0]) } + if x.Bit(i) != 0 { j++ for i := 0; i <= len(pairs)-1; i++ { @@ -207,6 +223,7 @@ func (e *Engine) millerLoop(f *fe12) { fp12.mulBy014Assign(f, &ellCoeffs[i][j][0], t[1], t[0]) } } + j++ } fp12.conjugate(f, f) @@ -259,8 +276,10 @@ func (e *Engine) calculate() *fe12 { if len(e.pairs) == 0 { return f } + e.millerLoop(f) e.finalExp(f) + return f } @@ -273,6 +292,7 @@ func (e *Engine) Check() bool { func (e *Engine) Result() *E { r := e.calculate() e.Reset() + return r } diff --git a/crypto/bls12381/pairing_test.go b/crypto/bls12381/pairing_test.go index 77676fe9b1..bb66bd9357 100644 --- a/crypto/bls12381/pairing_test.go +++ b/crypto/bls12381/pairing_test.go @@ -11,6 +11,7 @@ func TestPairingExpected(t *testing.T) { bls := NewPairingEngine() G1, G2 := bls.G1, bls.G2 GT := bls.GT() + expected, err := GT.FromBytes( common.FromHex("" + "0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631" + @@ -30,10 +31,12 @@ func TestPairingExpected(t *testing.T) { if err != nil { t.Fatal(err) } + r := bls.AddPair(G1.One(), G2.One()).Result() if !r.Equal(expected) { t.Fatal("bad pairing") } + if !GT.IsValid(r) { t.Fatal("element is not in correct subgroup") } @@ -48,10 +51,12 @@ func TestPairingNonDegeneracy(t *testing.T) { bls.Reset() { bls.AddPair(g1One, g2One) + e := bls.Result() if e.IsOne() { t.Fatal("pairing result is not expected to be one") } + if !GT.IsValid(e) { t.Fatal("pairing result is not valid") } @@ -60,6 +65,7 @@ func TestPairingNonDegeneracy(t *testing.T) { bls.Reset() { bls.AddPair(g1One, g2Zero) + e := bls.Result() if !e.IsOne() { t.Fatal("pairing result is expected to be one") @@ -69,6 +75,7 @@ func TestPairingNonDegeneracy(t *testing.T) { bls.Reset() { bls.AddPair(g1Zero, g2One) + e := bls.Result() if !e.IsOne() { t.Fatal("pairing result is expected to be one") @@ -80,6 +87,7 @@ func TestPairingNonDegeneracy(t *testing.T) { bls.AddPair(g1Zero, g2One) bls.AddPair(g1One, g2Zero) bls.AddPair(g1Zero, g2Zero) + e := bls.Result() if !e.IsOne() { t.Fatal("pairing result is expected to be one") @@ -107,10 +115,12 @@ func TestPairingNonDegeneracy(t *testing.T) { if err != nil { t.Fatal(err) } + bls.AddPair(g1Zero, g2One) bls.AddPair(g1One, g2Zero) bls.AddPair(g1Zero, g2Zero) bls.AddPair(g1One, g2One) + e := bls.Result() if !e.Equal(expected) { t.Fatal("bad pairing") @@ -132,7 +142,9 @@ func TestPairingBilinearity(t *testing.T) { g1.MulScalar(P1, G1, a) g2.MulScalar(P2, G2, b) e1 := bls.AddPair(P1, P2).Result() + gt.Exp(e0, e0, c) + if !e0.Equal(e1) { t.Fatal("bad pairing, 1") } @@ -202,6 +214,7 @@ func TestPairingMulti(t *testing.T) { T1, T2 := g1.One(), g2.One() g1.MulScalar(T1, T1, targetExp) bls.AddPairInv(T1, T2) + if !bls.Check() { t.Fatal("fail multi pairing") } @@ -212,6 +225,7 @@ func TestPairingEmpty(t *testing.T) { if !bls.Check() { t.Fatal("empty check should be accepted") } + if !bls.Result().IsOne() { t.Fatal("empty pairing result should be one") } @@ -221,10 +235,14 @@ func BenchmarkPairing(t *testing.B) { bls := NewPairingEngine() g1, g2, gt := bls.G1, bls.G2, bls.GT() bls.AddPair(g1.One(), g2.One()) + e := gt.New() + t.ResetTimer() + for i := 0; i < t.N; i++ { e = bls.calculate() } + _ = e } diff --git a/crypto/bls12381/swu.go b/crypto/bls12381/swu.go index e78753b240..01d55f6a5f 100644 --- a/crypto/bls12381/swu.go +++ b/crypto/bls12381/swu.go @@ -20,6 +20,7 @@ package bls12381 // follows the implementation at draft-irtf-cfrg-hash-to-curve-06. func swuMapG1(u *fe) (*fe, *fe) { var params = swuParamsForG1 + var tv [4]*fe for i := 0; i < 4; i++ { tv[i] = new(fe) @@ -27,28 +28,35 @@ func swuMapG1(u *fe) (*fe, *fe) { square(tv[0], u) mul(tv[0], tv[0], params.z) square(tv[1], tv[0]) + x1 := new(fe) add(x1, tv[0], tv[1]) inverse(x1, x1) e1 := x1.isZero() one := new(fe).one() add(x1, x1, one) + if e1 { x1.set(params.zInv) } + mul(x1, x1, params.minusBOverA) + gx1 := new(fe) square(gx1, x1) add(gx1, gx1, params.a) mul(gx1, gx1, x1) add(gx1, gx1, params.b) + x2 := new(fe) mul(x2, tv[0], x1) mul(tv[1], tv[0], tv[1]) + gx2 := new(fe) mul(gx2, gx1, tv[1]) e2 := !isQuadraticNonResidue(gx1) x, y2 := new(fe), new(fe) + if e2 { x.set(x1) y2.set(gx1) @@ -56,11 +64,14 @@ func swuMapG1(u *fe) (*fe, *fe) { x.set(x2) y2.set(gx2) } + y := new(fe) sqrt(y, y2) + if y.sign() != u.sign() { neg(y, y) } + return x, y } @@ -70,7 +81,9 @@ func swuMapG2(e *fp2, u *fe2) (*fe2, *fe2) { if e == nil { e = newFp2() } + params := swuParamsForG2 + var tv [4]*fe2 for i := 0; i < 4; i++ { tv[i] = e.new() @@ -83,9 +96,11 @@ func swuMapG2(e *fp2, u *fe2) (*fe2, *fe2) { e.inverse(x1, x1) e1 := x1.isZero() e.add(x1, x1, e.one()) + if e1 { x1.set(params.zInv) } + e.mul(x1, x1, params.minusBOverA) gx1 := e.new() e.square(gx1, x1) @@ -99,6 +114,7 @@ func swuMapG2(e *fp2, u *fe2) (*fe2, *fe2) { e.mul(gx2, gx1, tv[1]) e2 := !e.isQuadraticNonResidue(gx1) x, y2 := e.new(), e.new() + if e2 { x.set(x1) y2.set(gx1) @@ -106,11 +122,14 @@ func swuMapG2(e *fp2, u *fe2) (*fe2, *fe2) { x.set(x2) y2.set(gx2) } + y := e.new() e.sqrt(y, y2) + if y.sign() != u.sign() { e.neg(y, y) } + return x, y } diff --git a/crypto/bls12381/utils.go b/crypto/bls12381/utils.go index de8bf495fe..37e7503229 100644 --- a/crypto/bls12381/utils.go +++ b/crypto/bls12381/utils.go @@ -39,7 +39,9 @@ func decodeFieldElement(in []byte) ([]byte, error) { return nil, errors.New("invalid field element top bytes") } } + out := make([]byte, 48) copy(out[:], in[16:]) + return out, nil } diff --git a/crypto/bn256/cloudflare/bn256.go b/crypto/bn256/cloudflare/bn256.go index 4f607af2ad..e14577e84c 100644 --- a/crypto/bn256/cloudflare/bn256.go +++ b/crypto/bn256/cloudflare/bn256.go @@ -60,7 +60,9 @@ func (e *G1) ScalarBaseMult(k *big.Int) *G1 { if e.p == nil { e.p = &curvePoint{} } + e.p.Mul(curveGen, k) + return e } @@ -69,7 +71,9 @@ func (e *G1) ScalarMult(a *G1, k *big.Int) *G1 { if e.p == nil { e.p = &curvePoint{} } + e.p.Mul(a.p, k) + return e } @@ -78,7 +82,9 @@ func (e *G1) Add(a, b *G1) *G1 { if e.p == nil { e.p = &curvePoint{} } + e.p.Add(a.p, b.p) + return e } @@ -87,7 +93,9 @@ func (e *G1) Neg(a *G1) *G1 { if e.p == nil { e.p = &curvePoint{} } + e.p.Neg(a.p) + return e } @@ -96,7 +104,9 @@ func (e *G1) Set(a *G1) *G1 { if e.p == nil { e.p = &curvePoint{} } + e.p.Set(a.p) + return e } @@ -110,10 +120,12 @@ func (e *G1) Marshal() []byte { } e.p.MakeAffine() + ret := make([]byte, numBytes*2) if e.p.IsInfinity() { return ret } + temp := &gfP{} montDecode(temp, &e.p.x) @@ -138,10 +150,12 @@ func (e *G1) Unmarshal(m []byte) ([]byte, error) { } else { e.p.x, e.p.y = gfP{0}, gfP{0} } + var err error if err = e.p.x.Unmarshal(m); err != nil { return nil, err } + if err = e.p.y.Unmarshal(m[numBytes:]); err != nil { return nil, err } @@ -163,6 +177,7 @@ func (e *G1) Unmarshal(m []byte) ([]byte, error) { return nil, errors.New("bn256: malformed point") } } + return m[2*numBytes:], nil } @@ -192,7 +207,9 @@ func (e *G2) ScalarBaseMult(k *big.Int) *G2 { if e.p == nil { e.p = &twistPoint{} } + e.p.Mul(twistGen, k) + return e } @@ -201,7 +218,9 @@ func (e *G2) ScalarMult(a *G2, k *big.Int) *G2 { if e.p == nil { e.p = &twistPoint{} } + e.p.Mul(a.p, k) + return e } @@ -210,7 +229,9 @@ func (e *G2) Add(a, b *G2) *G2 { if e.p == nil { e.p = &twistPoint{} } + e.p.Add(a.p, b.p) + return e } @@ -219,7 +240,9 @@ func (e *G2) Neg(a *G2) *G2 { if e.p == nil { e.p = &twistPoint{} } + e.p.Neg(a.p) + return e } @@ -228,7 +251,9 @@ func (e *G2) Set(a *G2) *G2 { if e.p == nil { e.p = &twistPoint{} } + e.p.Set(a.p) + return e } @@ -242,10 +267,12 @@ func (e *G2) Marshal() []byte { } e.p.MakeAffine() + ret := make([]byte, numBytes*4) if e.p.IsInfinity() { return ret } + temp := &gfP{} montDecode(temp, &e.p.x.x) @@ -272,16 +299,20 @@ func (e *G2) Unmarshal(m []byte) ([]byte, error) { if e.p == nil { e.p = &twistPoint{} } + var err error if err = e.p.x.x.Unmarshal(m); err != nil { return nil, err } + if err = e.p.x.y.Unmarshal(m[numBytes:]); err != nil { return nil, err } + if err = e.p.y.x.Unmarshal(m[2*numBytes:]); err != nil { return nil, err } + if err = e.p.y.y.Unmarshal(m[3*numBytes:]); err != nil { return nil, err } @@ -304,6 +335,7 @@ func (e *G2) Unmarshal(m []byte) ([]byte, error) { return nil, errors.New("bn256: malformed point") } } + return m[4*numBytes:], nil } @@ -327,8 +359,10 @@ func PairingCheck(a []*G1, b []*G2) bool { if a[i].p.IsInfinity() || b[i].p.IsInfinity() { continue } + acc.Mul(acc, miller(b[i].p, a[i].p)) } + return finalExponentiation(acc).IsOne() } @@ -348,7 +382,9 @@ func (e *GT) ScalarMult(a *GT, k *big.Int) *GT { if e.p == nil { e.p = &gfP12{} } + e.p.Exp(a.p, k) + return e } @@ -357,7 +393,9 @@ func (e *GT) Add(a, b *GT) *GT { if e.p == nil { e.p = &gfP12{} } + e.p.Mul(a.p, b.p) + return e } @@ -366,7 +404,9 @@ func (e *GT) Neg(a *GT) *GT { if e.p == nil { e.p = &gfP12{} } + e.p.Conjugate(a.p) + return e } @@ -375,7 +415,9 @@ func (e *GT) Set(a *GT) *GT { if e.p == nil { e.p = &gfP12{} } + e.p.Set(a.p) + return e } @@ -383,6 +425,7 @@ func (e *GT) Set(a *GT) *GT { func (e *GT) Finalize() *GT { ret := finalExponentiation(e.p) e.p.Set(ret) + return e } @@ -445,39 +488,51 @@ func (e *GT) Unmarshal(m []byte) ([]byte, error) { if err = e.p.x.x.x.Unmarshal(m); err != nil { return nil, err } + if err = e.p.x.x.y.Unmarshal(m[numBytes:]); err != nil { return nil, err } + if err = e.p.x.y.x.Unmarshal(m[2*numBytes:]); err != nil { return nil, err } + if err = e.p.x.y.y.Unmarshal(m[3*numBytes:]); err != nil { return nil, err } + if err = e.p.x.z.x.Unmarshal(m[4*numBytes:]); err != nil { return nil, err } + if err = e.p.x.z.y.Unmarshal(m[5*numBytes:]); err != nil { return nil, err } + if err = e.p.y.x.x.Unmarshal(m[6*numBytes:]); err != nil { return nil, err } + if err = e.p.y.x.y.Unmarshal(m[7*numBytes:]); err != nil { return nil, err } + if err = e.p.y.y.x.Unmarshal(m[8*numBytes:]); err != nil { return nil, err } + if err = e.p.y.y.y.Unmarshal(m[9*numBytes:]); err != nil { return nil, err } + if err = e.p.y.z.x.Unmarshal(m[10*numBytes:]); err != nil { return nil, err } + if err = e.p.y.z.y.Unmarshal(m[11*numBytes:]); err != nil { return nil, err } + montEncode(&e.p.x.x.x, &e.p.x.x.x) montEncode(&e.p.x.x.y, &e.p.x.x.y) montEncode(&e.p.x.y.x, &e.p.x.y.x) diff --git a/crypto/bn256/cloudflare/bn256_test.go b/crypto/bn256/cloudflare/bn256_test.go index 481e2f78c3..acf1bb15f9 100644 --- a/crypto/bn256/cloudflare/bn256_test.go +++ b/crypto/bn256/cloudflare/bn256_test.go @@ -11,13 +11,16 @@ func TestG1Marshal(t *testing.T) { if err != nil { t.Fatal(err) } + ma := Ga.Marshal() Gb := new(G1) + _, err = Gb.Unmarshal(ma) if err != nil { t.Fatal(err) } + mb := Gb.Marshal() if !bytes.Equal(ma, mb) { @@ -30,13 +33,16 @@ func TestG2Marshal(t *testing.T) { if err != nil { t.Fatal(err) } + ma := Ga.Marshal() Gb := new(G2) + _, err = Gb.Unmarshal(ma) if err != nil { t.Fatal(err) } + mb := Gb.Marshal() if !bytes.Equal(ma, mb) { @@ -99,6 +105,7 @@ func TestG2SelfAddition(t *testing.T) { if !p.p.IsOnCurve() { t.Fatal("p isn't on curve") } + m := p.Add(p, p).Marshal() if _, err := p.Unmarshal(m); err != nil { t.Fatalf("p.Add(p, p) ∉ G₂: %v", err) @@ -107,6 +114,7 @@ func TestG2SelfAddition(t *testing.T) { func BenchmarkG1(b *testing.B) { x, _ := rand.Int(rand.Reader, Order) + b.ResetTimer() for i := 0; i < b.N; i++ { @@ -116,6 +124,7 @@ func BenchmarkG1(b *testing.B) { func BenchmarkG2(b *testing.B) { x, _ := rand.Int(rand.Reader, Order) + b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/crypto/bn256/cloudflare/curve.go b/crypto/bn256/cloudflare/curve.go index 16f0489e33..a29a1b03de 100644 --- a/crypto/bn256/cloudflare/curve.go +++ b/crypto/bn256/cloudflare/curve.go @@ -22,9 +22,11 @@ var curveGen = &curvePoint{ func (c *curvePoint) String() string { c.MakeAffine() + x, y := &gfP{}, &gfP{} montDecode(x, &c.x) montDecode(y, &c.y) + return "(" + x.String() + ", " + y.String() + ")" } @@ -38,6 +40,7 @@ func (c *curvePoint) Set(a *curvePoint) { // IsOnCurve returns true iff c is on the curve. func (c *curvePoint) IsOnCurve() bool { c.MakeAffine() + if c.IsInfinity() { return true } @@ -67,6 +70,7 @@ func (c *curvePoint) Add(a, b *curvePoint) { c.Set(b) return } + if b.IsInfinity() { c.Set(a) return @@ -90,6 +94,7 @@ func (c *curvePoint) Add(a, b *curvePoint) { gfpMul(s1, &a.y, t) s2 := &gfP{} + gfpMul(t, &a.z, z12) gfpMul(s2, &b.y, t) @@ -113,11 +118,13 @@ func (c *curvePoint) Add(a, b *curvePoint) { gfpMul(j, h, i) gfpSub(t, s2, s1) + yEqual := *t == gfP{0} if xEqual && yEqual { c.Double(a) return } + r := &gfP{} gfpAdd(r, t, t) @@ -193,10 +200,12 @@ func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int) { sum := &curvePoint{} sum.SetInfinity() + t := &curvePoint{} for i := len(multiScalar) - 1; i >= 0; i-- { t.Double(sum) + if multiScalar[i] == 0 { sum.Set(t) } else { @@ -213,6 +222,7 @@ func (c *curvePoint) MakeAffine() { c.x = gfP{0} c.y = *newGFp(1) c.t = gfP{0} + return } diff --git a/crypto/bn256/cloudflare/example_test.go b/crypto/bn256/cloudflare/example_test.go index 6c285995cb..60ae6c6bf1 100644 --- a/crypto/bn256/cloudflare/example_test.go +++ b/crypto/bn256/cloudflare/example_test.go @@ -15,7 +15,6 @@ func TestExamplePair(t *testing.T) { // This implements the tripartite Diffie-Hellman algorithm from "A One // Round Protocol for Tripartite Diffie-Hellman", A. Joux. // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf - // Each of three parties, a, b and c, generate a private value. a, _ := rand.Int(rand.Reader, Order) b, _ := rand.Int(rand.Reader, Order) diff --git a/crypto/bn256/cloudflare/gfp.go b/crypto/bn256/cloudflare/gfp.go index b15e1697e1..e4a941fb0b 100644 --- a/crypto/bn256/cloudflare/gfp.go +++ b/crypto/bn256/cloudflare/gfp.go @@ -16,6 +16,7 @@ func newGFp(x int64) (out *gfP) { } montEncode(out, out) + return out } @@ -42,6 +43,7 @@ func (e *gfP) Invert(f *gfP) { if (bits[word]>>bit)&1 == 1 { gfpMul(sum, sum, power) } + gfpMul(power, power, power) } } @@ -71,10 +73,12 @@ func (e *gfP) Unmarshal(in []byte) error { if e[i] < p2[i] { return nil } + if e[i] > p2[i] { return errors.New("bn256: coordinate exceeds modulus") } } + return errors.New("bn256: coordinate equals modulus") } diff --git a/crypto/bn256/cloudflare/gfp12.go b/crypto/bn256/cloudflare/gfp12.go index 93fb368a7b..295a1d6c4b 100644 --- a/crypto/bn256/cloudflare/gfp12.go +++ b/crypto/bn256/cloudflare/gfp12.go @@ -21,18 +21,21 @@ func (e *gfP12) String() string { func (e *gfP12) Set(a *gfP12) *gfP12 { e.x.Set(&a.x) e.y.Set(&a.y) + return e } func (e *gfP12) SetZero() *gfP12 { e.x.SetZero() e.y.SetZero() + return e } func (e *gfP12) SetOne() *gfP12 { e.x.SetZero() e.y.SetOne() + return e } @@ -47,12 +50,14 @@ func (e *gfP12) IsOne() bool { func (e *gfP12) Conjugate(a *gfP12) *gfP12 { e.x.Neg(&a.x) e.y.Set(&a.y) + return e } func (e *gfP12) Neg(a *gfP12) *gfP12 { e.x.Neg(&a.x) e.y.Neg(&a.y) + return e } @@ -61,6 +66,7 @@ func (e *gfP12) Frobenius(a *gfP12) *gfP12 { e.x.Frobenius(&a.x) e.y.Frobenius(&a.y) e.x.MulScalar(&e.x, xiToPMinus1Over6) + return e } @@ -69,6 +75,7 @@ func (e *gfP12) FrobeniusP2(a *gfP12) *gfP12 { e.x.FrobeniusP2(&a.x) e.x.MulGFP(&e.x, xiToPSquaredMinus1Over6) e.y.FrobeniusP2(&a.y) + return e } @@ -76,18 +83,21 @@ func (e *gfP12) FrobeniusP4(a *gfP12) *gfP12 { e.x.FrobeniusP4(&a.x) e.x.MulGFP(&e.x, xiToPSquaredMinus1Over3) e.y.FrobeniusP4(&a.y) + return e } func (e *gfP12) Add(a, b *gfP12) *gfP12 { e.x.Add(&a.x, &b.x) e.y.Add(&a.y, &b.y) + return e } func (e *gfP12) Sub(a, b *gfP12) *gfP12 { e.x.Sub(&a.x, &b.x) e.y.Sub(&a.y, &b.y) + return e } @@ -101,12 +111,14 @@ func (e *gfP12) Mul(a, b *gfP12) *gfP12 { e.x.Set(tx) e.y.Add(ty, t) + return e } func (e *gfP12) MulScalar(a *gfP12, b *gfP6) *gfP12 { e.x.Mul(&e.x, b) e.y.Mul(&e.y, b) + return e } @@ -116,6 +128,7 @@ func (c *gfP12) Exp(a *gfP12, power *big.Int) *gfP12 { for i := power.BitLen() - 1; i >= 0; i-- { t.Square(sum) + if power.Bit(i) != 0 { sum.Mul(t, a) } else { @@ -124,6 +137,7 @@ func (c *gfP12) Exp(a *gfP12, power *big.Int) *gfP12 { } c.Set(sum) + return c } @@ -140,6 +154,7 @@ func (e *gfP12) Square(a *gfP12) *gfP12 { e.x.Add(v0, v0) e.y.Set(ty) + return e } @@ -156,5 +171,6 @@ func (e *gfP12) Invert(a *gfP12) *gfP12 { e.x.Neg(&a.x) e.y.Set(&a.y) e.MulScalar(e, t2) + return e } diff --git a/crypto/bn256/cloudflare/gfp2.go b/crypto/bn256/cloudflare/gfp2.go index 90a89e8b47..fdd923e4ad 100644 --- a/crypto/bn256/cloudflare/gfp2.go +++ b/crypto/bn256/cloudflare/gfp2.go @@ -14,6 +14,7 @@ func gfP2Decode(in *gfP2) *gfP2 { out := &gfP2{} montDecode(&out.x, &in.x) montDecode(&out.y, &in.y) + return out } @@ -24,18 +25,21 @@ func (e *gfP2) String() string { func (e *gfP2) Set(a *gfP2) *gfP2 { e.x.Set(&a.x) e.y.Set(&a.y) + return e } func (e *gfP2) SetZero() *gfP2 { e.x = gfP{0} e.y = gfP{0} + return e } func (e *gfP2) SetOne() *gfP2 { e.x = gfP{0} e.y = *newGFp(1) + return e } @@ -52,24 +56,28 @@ func (e *gfP2) IsOne() bool { func (e *gfP2) Conjugate(a *gfP2) *gfP2 { e.y.Set(&a.y) gfpNeg(&e.x, &a.x) + return e } func (e *gfP2) Neg(a *gfP2) *gfP2 { gfpNeg(&e.x, &a.x) gfpNeg(&e.y, &a.y) + return e } func (e *gfP2) Add(a, b *gfP2) *gfP2 { gfpAdd(&e.x, &a.x, &b.x) gfpAdd(&e.y, &a.y, &b.y) + return e } func (e *gfP2) Sub(a, b *gfP2) *gfP2 { gfpSub(&e.x, &a.x, &b.x) gfpSub(&e.y, &a.y, &b.y) + return e } @@ -88,12 +96,14 @@ func (e *gfP2) Mul(a, b *gfP2) *gfP2 { e.x.Set(tx) e.y.Set(ty) + return e } func (e *gfP2) MulScalar(a *gfP2, b *gfP) *gfP2 { gfpMul(&e.x, &a.x, b) gfpMul(&e.y, &a.y, b) + return e } @@ -118,6 +128,7 @@ func (e *gfP2) MulXi(a *gfP2) *gfP2 { e.x.Set(tx) e.y.Set(ty) + return e } @@ -134,6 +145,7 @@ func (e *gfP2) Square(a *gfP2) *gfP2 { e.x.Set(tx) e.y.Set(ty) + return e } @@ -152,5 +164,6 @@ func (e *gfP2) Invert(a *gfP2) *gfP2 { gfpMul(&e.x, t1, inv) gfpMul(&e.y, &a.y, inv) + return e } diff --git a/crypto/bn256/cloudflare/gfp6.go b/crypto/bn256/cloudflare/gfp6.go index a42734911c..7a64f45289 100644 --- a/crypto/bn256/cloudflare/gfp6.go +++ b/crypto/bn256/cloudflare/gfp6.go @@ -18,6 +18,7 @@ func (e *gfP6) Set(a *gfP6) *gfP6 { e.x.Set(&a.x) e.y.Set(&a.y) e.z.Set(&a.z) + return e } @@ -25,6 +26,7 @@ func (e *gfP6) SetZero() *gfP6 { e.x.SetZero() e.y.SetZero() e.z.SetZero() + return e } @@ -32,6 +34,7 @@ func (e *gfP6) SetOne() *gfP6 { e.x.SetZero() e.y.SetZero() e.z.SetOne() + return e } @@ -47,6 +50,7 @@ func (e *gfP6) Neg(a *gfP6) *gfP6 { e.x.Neg(&a.x) e.y.Neg(&a.y) e.z.Neg(&a.z) + return e } @@ -57,6 +61,7 @@ func (e *gfP6) Frobenius(a *gfP6) *gfP6 { e.x.Mul(&e.x, xiTo2PMinus2Over3) e.y.Mul(&e.y, xiToPMinus1Over3) + return e } @@ -67,6 +72,7 @@ func (e *gfP6) FrobeniusP2(a *gfP6) *gfP6 { // τ^(p²) = ττ^(p²-1) = τξ^((p²-1)/3) e.y.MulScalar(&a.y, xiToPSquaredMinus1Over3) e.z.Set(&a.z) + return e } @@ -74,6 +80,7 @@ func (e *gfP6) FrobeniusP4(a *gfP6) *gfP6 { e.x.MulScalar(&a.x, xiToPSquaredMinus1Over3) e.y.MulScalar(&a.y, xiTo2PSquaredMinus2Over3) e.z.Set(&a.z) + return e } @@ -81,6 +88,7 @@ func (e *gfP6) Add(a, b *gfP6) *gfP6 { e.x.Add(&a.x, &b.x) e.y.Add(&a.y, &b.y) e.z.Add(&a.z, &b.z) + return e } @@ -88,6 +96,7 @@ func (e *gfP6) Sub(a, b *gfP6) *gfP6 { e.x.Sub(&a.x, &b.x) e.y.Sub(&a.y, &b.y) e.z.Sub(&a.z, &b.z) + return e } @@ -118,6 +127,7 @@ func (e *gfP6) Mul(a, b *gfP6) *gfP6 { e.x.Set(tx) e.y.Set(ty) e.z.Set(tz) + return e } @@ -125,6 +135,7 @@ func (e *gfP6) MulScalar(a *gfP6, b *gfP2) *gfP6 { e.x.Mul(&a.x, b) e.y.Mul(&a.y, b) e.z.Mul(&a.z, b) + return e } @@ -132,6 +143,7 @@ func (e *gfP6) MulGFP(a *gfP6, b *gfP) *gfP6 { e.x.MulScalar(&a.x, b) e.y.MulScalar(&a.y, b) e.z.MulScalar(&a.z, b) + return e } @@ -143,6 +155,7 @@ func (e *gfP6) MulTau(a *gfP6) *gfP6 { e.y.Set(&a.z) e.x.Set(ty) e.z.Set(tz) + return e } @@ -156,6 +169,7 @@ func (e *gfP6) Square(a *gfP6) *gfP6 { c1 := (&gfP2{}).Add(&a.y, &a.z) c1.Square(c1).Sub(c1, v0).Sub(c1, v1) + xiV2 := (&gfP2{}).MulXi(v2) c1.Add(c1, xiV2) @@ -165,13 +179,13 @@ func (e *gfP6) Square(a *gfP6) *gfP6 { e.x.Set(c2) e.y.Set(c1) e.z.Set(c0) + return e } func (e *gfP6) Invert(a *gfP6) *gfP6 { // See "Implementing cryptographic pairings", M. Scott, section 3.2. // ftp://136.206.11.249/pub/crypto/pairings.pdf - // Here we can give a short explanation of how it works: let j be a cubic root of // unity in GF(p²) so that 1+j+j²=0. // Then (xτ² + yτ + z)(xj²τ² + yjτ + z)(xjτ² + yj²τ + z) @@ -209,5 +223,6 @@ func (e *gfP6) Invert(a *gfP6) *gfP6 { e.x.Mul(C, F) e.y.Mul(B, F) e.z.Mul(A, F) + return e } diff --git a/crypto/bn256/cloudflare/gfp_test.go b/crypto/bn256/cloudflare/gfp_test.go index 16ab2a8410..89da2cd648 100644 --- a/crypto/bn256/cloudflare/gfp_test.go +++ b/crypto/bn256/cloudflare/gfp_test.go @@ -12,6 +12,7 @@ func TestGFpNeg(t *testing.T) { h := &gfP{} gfpNeg(h, n) + if *h != *w { t.Errorf("negation mismatch: have %#x, want %#x", *h, *w) } @@ -26,6 +27,7 @@ func TestGFpAdd(t *testing.T) { h := &gfP{} gfpAdd(h, a, b) + if *h != *w { t.Errorf("addition mismatch: have %#x, want %#x", *h, *w) } @@ -40,6 +42,7 @@ func TestGFpSub(t *testing.T) { h := &gfP{} gfpSub(h, a, b) + if *h != *w { t.Errorf("subtraction mismatch: have %#x, want %#x", *h, *w) } @@ -54,6 +57,7 @@ func TestGFpMul(t *testing.T) { h := &gfP{} gfpMul(h, a, b) + if *h != *w { t.Errorf("multiplication mismatch: have %#x, want %#x", *h, *w) } diff --git a/crypto/bn256/cloudflare/lattice.go b/crypto/bn256/cloudflare/lattice.go index f9ace4d9fc..56c294f27a 100644 --- a/crypto/bn256/cloudflare/lattice.go +++ b/crypto/bn256/cloudflare/lattice.go @@ -95,6 +95,7 @@ func (l *lattice) Multi(scalar *big.Int) []uint8 { } out := make([]uint8, maxLen) + for j, x := range decomp { for i := 0; i < maxLen; i++ { out[i] += uint8(x.Bit(i)) << uint(j) diff --git a/crypto/bn256/cloudflare/main_test.go b/crypto/bn256/cloudflare/main_test.go index c0c85457be..fb097f58c3 100644 --- a/crypto/bn256/cloudflare/main_test.go +++ b/crypto/bn256/cloudflare/main_test.go @@ -13,6 +13,7 @@ func TestRandomG2Marshal(t *testing.T) { t.Error(err) continue } + t.Logf("%v: %x\n", n, g2.Marshal()) } } @@ -33,38 +34,50 @@ func TestPairings(t *testing.T) { p1 := Pair(a1, b1) pn1 := Pair(a1, bn1) np1 := Pair(an1, b1) + if pn1.String() != np1.String() { t.Error("Pairing mismatch: e(a, -b) != e(-a, b)") } + if !PairingCheck([]*G1{a1, an1}, []*G2{b1, b1}) { t.Error("MultiAte check gave false negative!") } + p0 := new(GT).Add(p1, pn1) p0_2 := Pair(a1, b0) + if p0.String() != p0_2.String() { t.Error("Pairing mismatch: e(a, b) * e(a, -b) != 1") } + p0_3 := new(GT).ScalarMult(p1, bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617")) if p0.String() != p0_3.String() { t.Error("Pairing mismatch: e(a, b) has wrong order") } + p2 := Pair(a2, b1) p2_2 := Pair(a1, b2) p2_3 := new(GT).ScalarMult(p1, bigFromBase10("2")) + if p2.String() != p2_2.String() { t.Error("Pairing mismatch: e(a, b * 2) != e(a * 2, b)") } + if p2.String() != p2_3.String() { t.Error("Pairing mismatch: e(a, b * 2) != e(a, b) ** 2") } + if p2.String() == p1.String() { t.Error("Pairing is degenerate!") } + if PairingCheck([]*G1{a1, a1}, []*G2{b1, b1}) { t.Error("MultiAte check gave false positive!") } + p999 := Pair(a37, b27) p999_2 := Pair(a1, b999) + if p999.String() != p999_2.String() { t.Error("Pairing mismatch: e(a * 37, b * 27) != e(a, b * 999)") } diff --git a/crypto/bn256/cloudflare/optate.go b/crypto/bn256/cloudflare/optate.go index b71e50e3a2..9964a92a23 100644 --- a/crypto/bn256/cloudflare/optate.go +++ b/crypto/bn256/cloudflare/optate.go @@ -70,6 +70,7 @@ func lineFunctionDouble(r *twistPoint, q *curvePoint) (a, b, c *gfP2, rOut *twis rOut.z.Add(&r.y, &r.z).Square(&rOut.z).Sub(&rOut.z, B).Sub(&rOut.z, &r.t) rOut.y.Sub(D, &rOut.x).Mul(&rOut.y, E) + t := (&gfP2{}).Add(C, C) t.Add(t, t).Add(t, t) rOut.y.Sub(&rOut.y, t) @@ -140,11 +141,13 @@ func miller(q *twistPoint, p *curvePoint) *gfP12 { for i := len(sixuPlus2NAF) - 1; i > 0; i-- { a, b, c, newR := lineFunctionDouble(r, bAffine) + if i != len(sixuPlus2NAF)-1 { ret.Square(ret) } mulLine(ret, a, b, c) + r = newR switch sixuPlus2NAF[i-1] { @@ -157,6 +160,7 @@ func miller(q *twistPoint, p *curvePoint) *gfP12 { } mulLine(ret, a, b, c) + r = newR } @@ -196,11 +200,13 @@ func miller(q *twistPoint, p *curvePoint) *gfP12 { r2.Square(&q1.y) a, b, c, newR := lineFunctionAdd(r, q1, bAffine, r2) mulLine(ret, a, b, c) + r = newR r2.Square(&minusQ2.y) a, b, c, newR = lineFunctionAdd(r, minusQ2, bAffine, r2) mulLine(ret, a, b, c) + r = newR return ret @@ -242,6 +248,7 @@ func finalExponentiation(in *gfP12) *gfP12 { y1 := (&gfP12{}).Conjugate(t1) y5 := (&gfP12{}).Conjugate(fu2) y3.Conjugate(y3) + y4 := (&gfP12{}).Mul(fu, fu2p) y4.Conjugate(y4) @@ -267,5 +274,6 @@ func optimalAte(a *twistPoint, b *curvePoint) *gfP12 { if a.IsInfinity() || b.IsInfinity() { ret.SetOne() } + return ret } diff --git a/crypto/bn256/cloudflare/twist.go b/crypto/bn256/cloudflare/twist.go index 2c7a69a4d7..04f7123cd3 100644 --- a/crypto/bn256/cloudflare/twist.go +++ b/crypto/bn256/cloudflare/twist.go @@ -33,6 +33,7 @@ var twistGen = &twistPoint{ func (c *twistPoint) String() string { c.MakeAffine() x, y := gfP2Decode(&c.x), gfP2Decode(&c.y) + return "(" + x.String() + ", " + y.String() + ")" } @@ -46,6 +47,7 @@ func (c *twistPoint) Set(a *twistPoint) { // IsOnCurve returns true iff c is on the curve. func (c *twistPoint) IsOnCurve() bool { c.MakeAffine() + if c.IsInfinity() { return true } @@ -57,8 +59,10 @@ func (c *twistPoint) IsOnCurve() bool { if *y2 != *x3 { return false } + cneg := &twistPoint{} cneg.Mul(c, Order) + return cneg.z.IsZero() } @@ -75,11 +79,11 @@ func (c *twistPoint) IsInfinity() bool { func (c *twistPoint) Add(a, b *twistPoint) { // For additional comments, see the same function in curve.go. - if a.IsInfinity() { c.Set(b) return } + if b.IsInfinity() { c.Set(a) return @@ -105,17 +109,21 @@ func (c *twistPoint) Add(a, b *twistPoint) { j := (&gfP2{}).Mul(h, i) t.Sub(s2, s1) + yEqual := t.IsZero() if xEqual && yEqual { c.Double(a) return } + r := (&gfP2{}).Add(t, t) v := (&gfP2{}).Mul(u1, i) t4 := (&gfP2{}).Square(r) + t.Add(v, v) + t6 := (&gfP2{}).Sub(t4, j) c.x.Sub(t6, t) @@ -166,6 +174,7 @@ func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int) { for i := scalar.BitLen(); i >= 0; i-- { t.Double(sum) + if scalar.Bit(i) != 0 { sum.Add(t, a) } else { @@ -183,6 +192,7 @@ func (c *twistPoint) MakeAffine() { c.x.SetZero() c.y.SetOne() c.t.SetZero() + return } diff --git a/crypto/bn256/google/bn256.go b/crypto/bn256/google/bn256.go index 0a9d5cd35d..dc1c08b41a 100644 --- a/crypto/bn256/google/bn256.go +++ b/crypto/bn256/google/bn256.go @@ -40,6 +40,7 @@ type G1 struct { // RandomG1 returns x and g₁ˣ where x is a random, non-zero number read from r. func RandomG1(r io.Reader) (*big.Int, *G1, error) { var k *big.Int + var err error for { @@ -47,6 +48,7 @@ func RandomG1(r io.Reader) (*big.Int, *G1, error) { if err != nil { return nil, nil, err } + if k.Sign() > 0 { break } @@ -70,7 +72,9 @@ func (e *G1) ScalarBaseMult(k *big.Int) *G1 { if e.p == nil { e.p = newCurvePoint(nil) } + e.p.Mul(curveGen, k, new(bnPool)) + return e } @@ -79,7 +83,9 @@ func (e *G1) ScalarMult(a *G1, k *big.Int) *G1 { if e.p == nil { e.p = newCurvePoint(nil) } + e.p.Mul(a.p, k, new(bnPool)) + return e } @@ -89,7 +95,9 @@ func (e *G1) Add(a, b *G1) *G1 { if e.p == nil { e.p = newCurvePoint(nil) } + e.p.Add(a.p, b.p, new(bnPool)) + return e } @@ -98,7 +106,9 @@ func (e *G1) Neg(a *G1) *G1 { if e.p == nil { e.p = newCurvePoint(nil) } + e.p.Negative(a.p) + return e } @@ -135,11 +145,15 @@ func (e *G1) Unmarshal(m []byte) ([]byte, error) { if e.p == nil { e.p = newCurvePoint(nil) } + e.p.x.SetBytes(m[0*numBytes : 1*numBytes]) + if e.p.x.Cmp(P) >= 0 { return nil, errors.New("bn256: coordinate exceeds modulus") } + e.p.y.SetBytes(m[1*numBytes : 2*numBytes]) + if e.p.y.Cmp(P) >= 0 { return nil, errors.New("bn256: coordinate exceeds modulus") } @@ -157,6 +171,7 @@ func (e *G1) Unmarshal(m []byte) ([]byte, error) { return nil, errors.New("bn256: malformed point") } } + return m[2*numBytes:], nil } @@ -169,6 +184,7 @@ type G2 struct { // RandomG1 returns x and g₂ˣ where x is a random, non-zero number read from r. func RandomG2(r io.Reader) (*big.Int, *G2, error) { var k *big.Int + var err error for { @@ -176,6 +192,7 @@ func RandomG2(r io.Reader) (*big.Int, *G2, error) { if err != nil { return nil, nil, err } + if k.Sign() > 0 { break } @@ -200,7 +217,9 @@ func (e *G2) ScalarBaseMult(k *big.Int) *G2 { if e.p == nil { e.p = newTwistPoint(nil) } + e.p.Mul(twistGen, k, new(bnPool)) + return e } @@ -209,7 +228,9 @@ func (e *G2) ScalarMult(a *G2, k *big.Int) *G2 { if e.p == nil { e.p = newTwistPoint(nil) } + e.p.Mul(a.p, k, new(bnPool)) + return e } @@ -219,7 +240,9 @@ func (e *G2) Add(a, b *G2) *G2 { if e.p == nil { e.p = newTwistPoint(nil) } + e.p.Add(a.p, b.p, new(bnPool)) + return e } @@ -260,19 +283,27 @@ func (e *G2) Unmarshal(m []byte) ([]byte, error) { if e.p == nil { e.p = newTwistPoint(nil) } + e.p.x.x.SetBytes(m[0*numBytes : 1*numBytes]) + if e.p.x.x.Cmp(P) >= 0 { return nil, errors.New("bn256: coordinate exceeds modulus") } + e.p.x.y.SetBytes(m[1*numBytes : 2*numBytes]) + if e.p.x.y.Cmp(P) >= 0 { return nil, errors.New("bn256: coordinate exceeds modulus") } + e.p.y.x.SetBytes(m[2*numBytes : 3*numBytes]) + if e.p.y.x.Cmp(P) >= 0 { return nil, errors.New("bn256: coordinate exceeds modulus") } + e.p.y.y.SetBytes(m[3*numBytes : 4*numBytes]) + if e.p.y.y.Cmp(P) >= 0 { return nil, errors.New("bn256: coordinate exceeds modulus") } @@ -293,6 +324,7 @@ func (e *G2) Unmarshal(m []byte) ([]byte, error) { return nil, errors.New("bn256: malformed point") } } + return m[4*numBytes:], nil } @@ -311,7 +343,9 @@ func (e *GT) ScalarMult(a *GT, k *big.Int) *GT { if e.p == nil { e.p = newGFp12(nil) } + e.p.Exp(a.p, k, new(bnPool)) + return e } @@ -320,7 +354,9 @@ func (e *GT) Add(a, b *GT) *GT { if e.p == nil { e.p = newGFp12(nil) } + e.p.Mul(a.p, b.p, new(bnPool)) + return e } @@ -329,7 +365,9 @@ func (e *GT) Neg(a *GT) *GT { if e.p == nil { e.p = newGFp12(nil) } + e.p.Invert(a.p, new(bnPool)) + return e } @@ -416,8 +454,10 @@ func PairingCheck(a []*G1, b []*G2) bool { if a[i].p.IsInfinity() || b[i].p.IsInfinity() { continue } + acc.Mul(acc, miller(b[i].p, a[i].p, pool), pool) } + ret := finalExponentiation(acc, pool) acc.Put(pool) @@ -437,6 +477,7 @@ func (pool *bnPool) Get() *big.Int { } pool.count++ + l := len(pool.bns) if l == 0 { return new(big.Int) @@ -444,6 +485,7 @@ func (pool *bnPool) Get() *big.Int { bn := pool.bns[l-1] pool.bns = pool.bns[:l-1] + return bn } @@ -451,6 +493,7 @@ func (pool *bnPool) Put(bn *big.Int) { if pool == nil { return } + pool.bns = append(pool.bns, bn) pool.count-- } diff --git a/crypto/bn256/google/bn256_test.go b/crypto/bn256/google/bn256_test.go index a4497ada9b..16f1016fc0 100644 --- a/crypto/bn256/google/bn256_test.go +++ b/crypto/bn256/google/bn256_test.go @@ -167,6 +167,7 @@ func TestOrderG1(t *testing.T) { one := new(G1).ScalarBaseMult(new(big.Int).SetInt64(1)) g.Add(g, one) g.p.MakeAffine(nil) + if g.p.x.Cmp(one.p.x) != 0 || g.p.y.Cmp(one.p.y) != 0 { t.Errorf("1+0 != 1 in G1") } @@ -181,6 +182,7 @@ func TestOrderG2(t *testing.T) { one := new(G2).ScalarBaseMult(new(big.Int).SetInt64(1)) g.Add(g, one) g.p.MakeAffine(nil) + if g.p.x.x.Cmp(one.p.x.x) != 0 || g.p.x.y.Cmp(one.p.x.y) != 0 || g.p.y.x.Cmp(one.p.y.x) != 0 || @@ -191,6 +193,7 @@ func TestOrderG2(t *testing.T) { func TestOrderGT(t *testing.T) { gt := Pair(&G1{curveGen}, &G2{twistGen}) + g := new(GT).ScalarMult(gt, Order) if !g.p.IsOne() { t.Error("GT has incorrect order") @@ -219,6 +222,7 @@ func TestBilinearity(t *testing.T) { func TestG1Marshal(t *testing.T) { g := new(G1).ScalarBaseMult(new(big.Int).SetInt64(1)) form := g.Marshal() + _, err := new(G1).Unmarshal(form) if err != nil { t.Fatalf("failed to unmarshal") @@ -231,6 +235,7 @@ func TestG1Marshal(t *testing.T) { if _, err = g2.Unmarshal(form); err != nil { t.Fatalf("failed to unmarshal ∞") } + if !g2.p.IsInfinity() { t.Fatalf("∞ unmarshaled incorrectly") } @@ -239,6 +244,7 @@ func TestG1Marshal(t *testing.T) { func TestG2Marshal(t *testing.T) { g := new(G2).ScalarBaseMult(new(big.Int).SetInt64(1)) form := g.Marshal() + _, err := new(G2).Unmarshal(form) if err != nil { t.Fatalf("failed to unmarshal") @@ -247,9 +253,11 @@ func TestG2Marshal(t *testing.T) { g.ScalarBaseMult(Order) form = g.Marshal() g2 := new(G2) + if _, err = g2.Unmarshal(form); err != nil { t.Fatalf("failed to unmarshal ∞") } + if !g2.p.IsInfinity() { t.Fatalf("∞ unmarshaled incorrectly") } diff --git a/crypto/bn256/google/curve.go b/crypto/bn256/google/curve.go index 819cb81da7..779633a16d 100644 --- a/crypto/bn256/google/curve.go +++ b/crypto/bn256/google/curve.go @@ -60,9 +60,11 @@ func (c *curvePoint) IsOnCurve() bool { xxx.Mul(xxx, c.x) yy.Sub(yy, xxx) yy.Sub(yy, curveB) + if yy.Sign() < 0 || yy.Cmp(P) >= 0 { yy.Mod(yy, P) } + return yy.Sign() == 0 } @@ -79,6 +81,7 @@ func (c *curvePoint) Add(a, b *curvePoint, pool *bnPool) { c.Set(b) return } + if b.IsInfinity() { c.Set(a) return @@ -91,10 +94,12 @@ func (c *curvePoint) Add(a, b *curvePoint, pool *bnPool) { // where u1 = x1·z2², s1 = y1·z2³ and u1 = x2·z1², s2 = y2·z1³ z1z1 := pool.Get().Mul(a.z, a.z) z1z1.Mod(z1z1, P) + z2z2 := pool.Get().Mul(b.z, b.z) z2z2.Mod(z2z2, P) u1 := pool.Get().Mul(a.x, z2z2) u1.Mod(u1, P) + u2 := pool.Get().Mul(b.x, z1z1) u2.Mod(u2, P) @@ -127,11 +132,13 @@ func (c *curvePoint) Add(a, b *curvePoint, pool *bnPool) { j.Mod(j, P) t.Sub(s2, s1) + yEqual := t.Sign() == 0 if xEqual && yEqual { c.Double(a, pool) return } + r := pool.Get().Add(t, t) v := pool.Get().Mul(u1, i) @@ -141,6 +148,7 @@ func (c *curvePoint) Add(a, b *curvePoint, pool *bnPool) { t4 := pool.Get().Mul(r, r) t4.Mod(t4, P) t.Add(v, v) + t6 := pool.Get().Sub(t4, j) c.x.Sub(t6, t) @@ -184,6 +192,7 @@ func (c *curvePoint) Double(a *curvePoint, pool *bnPool) { // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 A := pool.Get().Mul(a.x, a.x) A.Mod(A, P) + B := pool.Get().Mul(a.y, a.y) B.Mod(B, P) C_ := pool.Get().Mul(B, B) @@ -228,10 +237,12 @@ func (c *curvePoint) Double(a *curvePoint, pool *bnPool) { func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int, pool *bnPool) *curvePoint { sum := newCurvePoint(pool) sum.SetInfinity() + t := newCurvePoint(pool) for i := scalar.BitLen(); i >= 0; i-- { t.Double(sum, pool) + if scalar.Bit(i) != 0 { sum.Add(t, a, pool) } else { @@ -242,6 +253,7 @@ func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int, pool *bnPool) *curvePoi c.Set(sum) sum.Put(pool) t.Put(pool) + return c } @@ -251,16 +263,20 @@ func (c *curvePoint) MakeAffine(pool *bnPool) *curvePoint { if words := c.z.Bits(); len(words) == 1 && words[0] == 1 { return c } + if c.IsInfinity() { c.x.SetInt64(0) c.y.SetInt64(1) c.z.SetInt64(0) c.t.SetInt64(0) + return c } + zInv := pool.Get().ModInverse(c.z, P) t := pool.Get().Mul(c.y, zInv) t.Mod(t, P) + zInv2 := pool.Get().Mul(zInv, zInv) zInv2.Mod(zInv2, P) c.y.Mul(t, zInv2) diff --git a/crypto/bn256/google/example_test.go b/crypto/bn256/google/example_test.go index b2d19807a2..3defabefbf 100644 --- a/crypto/bn256/google/example_test.go +++ b/crypto/bn256/google/example_test.go @@ -12,7 +12,6 @@ func ExamplePair() { // This implements the tripartite Diffie-Hellman algorithm from "A One // Round Protocol for Tripartite Diffie-Hellman", A. Joux. // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf - // Each of three parties, a, b and c, generate a private value. a, _ := rand.Int(rand.Reader, Order) b, _ := rand.Int(rand.Reader, Order) diff --git a/crypto/bn256/google/gfp12.go b/crypto/bn256/google/gfp12.go index f084eddf21..888ed5f920 100644 --- a/crypto/bn256/google/gfp12.go +++ b/crypto/bn256/google/gfp12.go @@ -34,18 +34,21 @@ func (e *gfP12) Put(pool *bnPool) { func (e *gfP12) Set(a *gfP12) *gfP12 { e.x.Set(a.x) e.y.Set(a.y) + return e } func (e *gfP12) SetZero() *gfP12 { e.x.SetZero() e.y.SetZero() + return e } func (e *gfP12) SetOne() *gfP12 { e.x.SetZero() e.y.SetOne() + return e } @@ -67,12 +70,14 @@ func (e *gfP12) IsOne() bool { func (e *gfP12) Conjugate(a *gfP12) *gfP12 { e.x.Negative(a.x) e.y.Set(a.y) + return a } func (e *gfP12) Negative(a *gfP12) *gfP12 { e.x.Negative(a.x) e.y.Negative(a.y) + return e } @@ -81,6 +86,7 @@ func (e *gfP12) Frobenius(a *gfP12, pool *bnPool) *gfP12 { e.x.Frobenius(a.x, pool) e.y.Frobenius(a.y, pool) e.x.MulScalar(e.x, xiToPMinus1Over6, pool) + return e } @@ -89,18 +95,21 @@ func (e *gfP12) FrobeniusP2(a *gfP12, pool *bnPool) *gfP12 { e.x.FrobeniusP2(a.x) e.x.MulGFP(e.x, xiToPSquaredMinus1Over6) e.y.FrobeniusP2(a.y) + return e } func (e *gfP12) Add(a, b *gfP12) *gfP12 { e.x.Add(a.x, b.x) e.y.Add(a.y, b.y) + return e } func (e *gfP12) Sub(a, b *gfP12) *gfP12 { e.x.Sub(a.x, b.x) e.y.Sub(a.y, b.y) + return e } @@ -121,22 +130,26 @@ func (e *gfP12) Mul(a, b *gfP12, pool *bnPool) *gfP12 { tx.Put(pool) ty.Put(pool) t.Put(pool) + return e } func (e *gfP12) MulScalar(a *gfP12, b *gfP6, pool *bnPool) *gfP12 { e.x.Mul(e.x, b, pool) e.y.Mul(e.y, b, pool) + return e } func (c *gfP12) Exp(a *gfP12, power *big.Int, pool *bnPool) *gfP12 { sum := newGFp12(pool) sum.SetOne() + t := newGFp12(pool) for i := power.BitLen() - 1; i >= 0; i-- { t.Square(sum, pool) + if power.Bit(i) != 0 { sum.Mul(t, a, pool) } else { @@ -160,6 +173,7 @@ func (e *gfP12) Square(a *gfP12, pool *bnPool) *gfP12 { t := newGFp6(pool) t.MulTau(a.x, pool) t.Add(a.y, t) + ty := newGFp6(pool) ty.Add(a.x, a.y) ty.Mul(ty, t, pool) diff --git a/crypto/bn256/google/gfp2.go b/crypto/bn256/google/gfp2.go index 3981f6cb4f..dfcdc9eb4f 100644 --- a/crypto/bn256/google/gfp2.go +++ b/crypto/bn256/google/gfp2.go @@ -25,6 +25,7 @@ func newGFp2(pool *bnPool) *gfP2 { func (e *gfP2) String() string { x := new(big.Int).Mod(e.x, P) y := new(big.Int).Mod(e.y, P) + return "(" + x.String() + "," + y.String() + ")" } @@ -36,18 +37,21 @@ func (e *gfP2) Put(pool *bnPool) { func (e *gfP2) Set(a *gfP2) *gfP2 { e.x.Set(a.x) e.y.Set(a.y) + return e } func (e *gfP2) SetZero() *gfP2 { e.x.SetInt64(0) e.y.SetInt64(0) + return e } func (e *gfP2) SetOne() *gfP2 { e.x.SetInt64(0) e.y.SetInt64(1) + return e } @@ -55,6 +59,7 @@ func (e *gfP2) Minimal() { if e.x.Sign() < 0 || e.x.Cmp(P) >= 0 { e.x.Mod(e.x, P) } + if e.y.Sign() < 0 || e.y.Cmp(P) >= 0 { e.y.Mod(e.y, P) } @@ -68,47 +73,56 @@ func (e *gfP2) IsOne() bool { if e.x.Sign() != 0 { return false } + words := e.y.Bits() + return len(words) == 1 && words[0] == 1 } func (e *gfP2) Conjugate(a *gfP2) *gfP2 { e.y.Set(a.y) e.x.Neg(a.x) + return e } func (e *gfP2) Negative(a *gfP2) *gfP2 { e.x.Neg(a.x) e.y.Neg(a.y) + return e } func (e *gfP2) Add(a, b *gfP2) *gfP2 { e.x.Add(a.x, b.x) e.y.Add(a.y, b.y) + return e } func (e *gfP2) Sub(a, b *gfP2) *gfP2 { e.x.Sub(a.x, b.x) e.y.Sub(a.y, b.y) + return e } func (e *gfP2) Double(a *gfP2) *gfP2 { e.x.Lsh(a.x, 1) e.y.Lsh(a.y, 1) + return e } func (c *gfP2) Exp(a *gfP2, power *big.Int, pool *bnPool) *gfP2 { sum := newGFp2(pool) sum.SetOne() + t := newGFp2(pool) for i := power.BitLen() - 1; i >= 0; i-- { t.Square(sum, pool) + if power.Bit(i) != 0 { sum.Mul(t, a, pool) } else { @@ -148,6 +162,7 @@ func (e *gfP2) Mul(a, b *gfP2, pool *bnPool) *gfP2 { func (e *gfP2) MulScalar(a *gfP2, b *big.Int) *gfP2 { e.x.Mul(a.x, b) e.y.Mul(a.y, b) + return e } @@ -197,6 +212,7 @@ func (e *gfP2) Invert(a *gfP2, pool *bnPool) *gfP2 { // ftp://136.206.11.249/pub/crypto/pairings.pdf t := pool.Get() t.Mul(a.y, a.y) + t2 := pool.Get() t2.Mul(a.x, a.x) t.Add(t, t2) diff --git a/crypto/bn256/google/gfp6.go b/crypto/bn256/google/gfp6.go index 218856617c..e4e8f11a9c 100644 --- a/crypto/bn256/google/gfp6.go +++ b/crypto/bn256/google/gfp6.go @@ -36,6 +36,7 @@ func (e *gfP6) Set(a *gfP6) *gfP6 { e.x.Set(a.x) e.y.Set(a.y) e.z.Set(a.z) + return e } @@ -43,6 +44,7 @@ func (e *gfP6) SetZero() *gfP6 { e.x.SetZero() e.y.SetZero() e.z.SetZero() + return e } @@ -50,6 +52,7 @@ func (e *gfP6) SetOne() *gfP6 { e.x.SetZero() e.y.SetZero() e.z.SetOne() + return e } @@ -71,6 +74,7 @@ func (e *gfP6) Negative(a *gfP6) *gfP6 { e.x.Negative(a.x) e.y.Negative(a.y) e.z.Negative(a.z) + return e } @@ -81,6 +85,7 @@ func (e *gfP6) Frobenius(a *gfP6, pool *bnPool) *gfP6 { e.x.Mul(e.x, xiTo2PMinus2Over3, pool) e.y.Mul(e.y, xiToPMinus1Over3, pool) + return e } @@ -91,6 +96,7 @@ func (e *gfP6) FrobeniusP2(a *gfP6) *gfP6 { // τ^(p²) = ττ^(p²-1) = τξ^((p²-1)/3) e.y.MulScalar(a.y, xiToPSquaredMinus1Over3) e.z.Set(a.z) + return e } @@ -98,6 +104,7 @@ func (e *gfP6) Add(a, b *gfP6) *gfP6 { e.x.Add(a.x, b.x) e.y.Add(a.y, b.y) e.z.Add(a.z, b.z) + return e } @@ -105,6 +112,7 @@ func (e *gfP6) Sub(a, b *gfP6) *gfP6 { e.x.Sub(a.x, b.x) e.y.Sub(a.y, b.y) e.z.Sub(a.z, b.z) + return e } @@ -112,6 +120,7 @@ func (e *gfP6) Double(a *gfP6) *gfP6 { e.x.Double(a.x) e.y.Double(a.y) e.z.Double(a.z) + return e } @@ -119,7 +128,6 @@ func (e *gfP6) Mul(a, b *gfP6, pool *bnPool) *gfP6 { // "Multiplication and Squaring on Pairing-Friendly Fields" // Section 4, Karatsuba method. // http://eprint.iacr.org/2006/471.pdf - v0 := newGFp2(pool) v0.Mul(a.z, b.z, pool) v1 := newGFp2(pool) @@ -129,8 +137,10 @@ func (e *gfP6) Mul(a, b *gfP6, pool *bnPool) *gfP6 { t0 := newGFp2(pool) t0.Add(a.x, a.y) + t1 := newGFp2(pool) t1.Add(b.x, b.y) + tz := newGFp2(pool) tz.Mul(t0, t1, pool) @@ -141,6 +151,7 @@ func (e *gfP6) Mul(a, b *gfP6, pool *bnPool) *gfP6 { t0.Add(a.y, a.z) t1.Add(b.y, b.z) + ty := newGFp2(pool) ty.Mul(t0, t1, pool) ty.Sub(ty, v0) @@ -150,6 +161,7 @@ func (e *gfP6) Mul(a, b *gfP6, pool *bnPool) *gfP6 { t0.Add(a.x, a.z) t1.Add(b.x, b.z) + tx := newGFp2(pool) tx.Mul(t0, t1, pool) tx.Sub(tx, v0) @@ -168,6 +180,7 @@ func (e *gfP6) Mul(a, b *gfP6, pool *bnPool) *gfP6 { v0.Put(pool) v1.Put(pool) v2.Put(pool) + return e } @@ -175,6 +188,7 @@ func (e *gfP6) MulScalar(a *gfP6, b *gfP2, pool *bnPool) *gfP6 { e.x.Mul(a.x, b, pool) e.y.Mul(a.y, b, pool) e.z.Mul(a.z, b, pool) + return e } @@ -182,6 +196,7 @@ func (e *gfP6) MulGFP(a *gfP6, b *big.Int) *gfP6 { e.x.MulScalar(a.x, b) e.y.MulScalar(a.y, b) e.z.MulScalar(a.z, b) + return e } @@ -214,6 +229,7 @@ func (e *gfP6) Square(a *gfP6, pool *bnPool) *gfP6 { c1.Square(c1, pool) c1.Sub(c1, v0) c1.Sub(c1, v1) + xiV2 := newGFp2(pool).MulXi(v2, pool) c1.Add(c1, xiV2) @@ -241,7 +257,6 @@ func (e *gfP6) Square(a *gfP6, pool *bnPool) *gfP6 { func (e *gfP6) Invert(a *gfP6, pool *bnPool) *gfP6 { // See "Implementing cryptographic pairings", M. Scott, section 3.2. // ftp://136.206.11.249/pub/crypto/pairings.pdf - // Here we can give a short explanation of how it works: let j be a cubic root of // unity in GF(p²) so that 1+j+j²=0. // Then (xτ² + yτ + z)(xj²τ² + yjτ + z)(xjτ² + yj²τ + z) diff --git a/crypto/bn256/google/main_test.go b/crypto/bn256/google/main_test.go index c0c85457be..fb097f58c3 100644 --- a/crypto/bn256/google/main_test.go +++ b/crypto/bn256/google/main_test.go @@ -13,6 +13,7 @@ func TestRandomG2Marshal(t *testing.T) { t.Error(err) continue } + t.Logf("%v: %x\n", n, g2.Marshal()) } } @@ -33,38 +34,50 @@ func TestPairings(t *testing.T) { p1 := Pair(a1, b1) pn1 := Pair(a1, bn1) np1 := Pair(an1, b1) + if pn1.String() != np1.String() { t.Error("Pairing mismatch: e(a, -b) != e(-a, b)") } + if !PairingCheck([]*G1{a1, an1}, []*G2{b1, b1}) { t.Error("MultiAte check gave false negative!") } + p0 := new(GT).Add(p1, pn1) p0_2 := Pair(a1, b0) + if p0.String() != p0_2.String() { t.Error("Pairing mismatch: e(a, b) * e(a, -b) != 1") } + p0_3 := new(GT).ScalarMult(p1, bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617")) if p0.String() != p0_3.String() { t.Error("Pairing mismatch: e(a, b) has wrong order") } + p2 := Pair(a2, b1) p2_2 := Pair(a1, b2) p2_3 := new(GT).ScalarMult(p1, bigFromBase10("2")) + if p2.String() != p2_2.String() { t.Error("Pairing mismatch: e(a, b * 2) != e(a * 2, b)") } + if p2.String() != p2_3.String() { t.Error("Pairing mismatch: e(a, b * 2) != e(a, b) ** 2") } + if p2.String() == p1.String() { t.Error("Pairing is degenerate!") } + if PairingCheck([]*G1{a1, a1}, []*G2{b1, b1}) { t.Error("MultiAte check gave false positive!") } + p999 := Pair(a37, b27) p999_2 := Pair(a1, b999) + if p999.String() != p999_2.String() { t.Error("Pairing mismatch: e(a * 37, b * 27) != e(a, b * 999)") } diff --git a/crypto/bn256/google/optate.go b/crypto/bn256/google/optate.go index 9d6957062e..dd939ee86f 100644 --- a/crypto/bn256/google/optate.go +++ b/crypto/bn256/google/optate.go @@ -7,7 +7,6 @@ package bn256 func lineFunctionAdd(r, p *twistPoint, q *curvePoint, r2 *gfP2, pool *bnPool) (a, b, c *gfP2, rOut *twistPoint) { // See the mixed addition algorithm from "Faster Computation of the // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf - B := newGFp2(pool).Mul(p.x, r.t, pool) D := newGFp2(pool).Add(p.y, r.z) @@ -55,6 +54,7 @@ func lineFunctionAdd(r, p *twistPoint, q *curvePoint, r2 *gfP2, pool *bnPool) (a t2.Mul(L1, p.x, pool) t2.Add(t2, t2) + a = newGFp2(pool) a.Sub(t2, t) @@ -85,7 +85,6 @@ func lineFunctionAdd(r, p *twistPoint, q *curvePoint, r2 *gfP2, pool *bnPool) (a func lineFunctionDouble(r *twistPoint, q *curvePoint, pool *bnPool) (a, b, c *gfP2, rOut *twistPoint) { // See the doubling algorithm for a=0 from "Faster Computation of the // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf - A := newGFp2(pool).Square(r.x, pool) B := newGFp2(pool).Square(r.y, pool) C_ := newGFp2(pool).Square(B, pool) @@ -121,6 +120,7 @@ func lineFunctionDouble(r *twistPoint, q *curvePoint, pool *bnPool) (a, b, c *gf t.Mul(E, r.t, pool) t.Add(t, t) + b = newGFp2(pool) b.SetZero() b.Sub(b, t) @@ -161,6 +161,7 @@ func mulLine(ret *gfP12, a, b, c *gfP2, pool *bnPool) { t := newGFp2(pool) t.Add(b, c) + t2 := newGFp6(pool) t2.x.SetZero() t2.y.Set(a) @@ -212,6 +213,7 @@ func miller(q *twistPoint, p *curvePoint, pool *bnPool) *gfP12 { for i := len(sixuPlus2NAF) - 1; i > 0; i-- { a, b, c, newR := lineFunctionDouble(r, bAffine, pool) + if i != len(sixuPlus2NAF)-1 { ret.Square(ret, pool) } @@ -393,5 +395,6 @@ func optimalAte(a *twistPoint, b *curvePoint, pool *bnPool) *gfP12 { if a.IsInfinity() || b.IsInfinity() { ret.SetOne() } + return ret } diff --git a/crypto/bn256/google/twist.go b/crypto/bn256/google/twist.go index 43364ff5b7..9184a49c12 100644 --- a/crypto/bn256/google/twist.go +++ b/crypto/bn256/google/twist.go @@ -80,8 +80,10 @@ func (c *twistPoint) IsOnCurve() bool { if yy.x.Sign() != 0 || yy.y.Sign() != 0 { return false } + cneg := newTwistPoint(pool) cneg.Mul(c, Order, pool) + return cneg.z.IsZero() } @@ -95,11 +97,11 @@ func (c *twistPoint) IsInfinity() bool { func (c *twistPoint) Add(a, b *twistPoint, pool *bnPool) { // For additional comments, see the same function in curve.go. - if a.IsInfinity() { c.Set(b) return } + if b.IsInfinity() { c.Set(a) return @@ -125,17 +127,21 @@ func (c *twistPoint) Add(a, b *twistPoint, pool *bnPool) { j := newGFp2(pool).Mul(h, i, pool) t.Sub(s2, s1) + yEqual := t.IsZero() if xEqual && yEqual { c.Double(a, pool) return } + r := newGFp2(pool).Add(t, t) v := newGFp2(pool).Mul(u1, i, pool) t4 := newGFp2(pool).Square(r, pool) + t.Add(v, v) + t6 := newGFp2(pool).Sub(t4, j) c.x.Sub(t6, t) @@ -208,10 +214,12 @@ func (c *twistPoint) Double(a *twistPoint, pool *bnPool) { func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int, pool *bnPool) *twistPoint { sum := newTwistPoint(pool) sum.SetInfinity() + t := newTwistPoint(pool) for i := scalar.BitLen(); i >= 0; i-- { t.Double(sum, pool) + if scalar.Bit(i) != 0 { sum.Add(t, a, pool) } else { @@ -222,6 +230,7 @@ func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int, pool *bnPool) *twistPoi c.Set(sum) sum.Put(pool) t.Put(pool) + return c } @@ -231,13 +240,16 @@ func (c *twistPoint) MakeAffine(pool *bnPool) *twistPoint { if c.z.IsOne() { return c } + if c.IsInfinity() { c.x.SetZero() c.y.SetOne() c.z.SetZero() c.t.SetZero() + return c } + zInv := newGFp2(pool).Invert(c.z, pool) t := newGFp2(pool).Mul(c.y, zInv, pool) zInv2 := newGFp2(pool).Square(zInv, pool) diff --git a/crypto/crypto.go b/crypto/crypto.go index e51b63beca..03c6995ffc 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -69,6 +69,7 @@ func HashData(kh KeccakState, data []byte) (h common.Hash) { kh.Reset() kh.Write(data) kh.Read(h[:]) + return h } @@ -76,10 +77,13 @@ func HashData(kh KeccakState, data []byte) (h common.Hash) { func Keccak256(data ...[]byte) []byte { b := make([]byte, 32) d := NewKeccakState() + for _, b := range data { d.Write(b) } + d.Read(b) + return b } @@ -90,7 +94,9 @@ func Keccak256Hash(data ...[]byte) (h common.Hash) { for _, b := range data { d.Write(b) } + d.Read(h[:]) + return h } @@ -100,6 +106,7 @@ func Keccak512(data ...[]byte) []byte { for _, b := range data { d.Write(b) } + return d.Sum(nil) } @@ -134,9 +141,11 @@ func ToECDSAUnsafe(d []byte) *ecdsa.PrivateKey { func toECDSA(d []byte, strict bool) (*ecdsa.PrivateKey, error) { priv := new(ecdsa.PrivateKey) priv.PublicKey.Curve = S256() + if strict && 8*len(d) != priv.Params().BitSize { return nil, fmt.Errorf("invalid length, need %d bits", priv.Params().BitSize) } + priv.D = new(big.Int).SetBytes(d) // The priv.D must < N @@ -152,6 +161,7 @@ func toECDSA(d []byte, strict bool) (*ecdsa.PrivateKey, error) { if priv.PublicKey.X == nil { return nil, errors.New("invalid private key") } + return priv, nil } @@ -160,6 +170,7 @@ func FromECDSA(priv *ecdsa.PrivateKey) []byte { if priv == nil { return nil } + return math.PaddedBigBytes(priv.D, priv.Params().BitSize/8) } @@ -169,6 +180,7 @@ func UnmarshalPubkey(pub []byte) (*ecdsa.PublicKey, error) { if x == nil { return nil, errInvalidPubkey } + return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil } @@ -176,6 +188,7 @@ func FromECDSAPub(pub *ecdsa.PublicKey) []byte { if pub == nil || pub.X == nil || pub.Y == nil { return nil } + return elliptic.Marshal(S256(), pub.X, pub.Y) } @@ -187,6 +200,7 @@ func HexToECDSA(hexkey string) (*ecdsa.PrivateKey, error) { } else if err != nil { return nil, errors.New("invalid hex data for private key") } + return ToECDSA(b) } @@ -200,12 +214,14 @@ func LoadECDSA(file string) (*ecdsa.PrivateKey, error) { r := bufio.NewReader(fd) buf := make([]byte, 64) + n, err := readASCII(buf, r) if err != nil { return nil, err } else if n != len(buf) { return nil, fmt.Errorf("key file too short, want 64 hex characters") } + if err := checkKeyFileEnd(r); err != nil { return nil, err } @@ -218,6 +234,7 @@ func LoadECDSA(file string) (*ecdsa.PrivateKey, error) { func readASCII(buf []byte, r *bufio.Reader) (n int, err error) { for ; n < len(buf); n++ { buf[n], err = r.ReadByte() + switch { case err == io.EOF || buf[n] < '!': return n, nil @@ -225,6 +242,7 @@ func readASCII(buf []byte, r *bufio.Reader) (n int, err error) { return n, err } } + return n, nil } @@ -232,6 +250,7 @@ func readASCII(buf []byte, r *bufio.Reader) (n int, err error) { func checkKeyFileEnd(r *bufio.Reader) error { for i := 0; ; i++ { b, err := r.ReadByte() + switch { case err == io.EOF: return nil diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index da123cf980..7fa0bce775 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -45,6 +45,7 @@ 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) } @@ -52,6 +53,7 @@ func TestToECDSAErrors(t *testing.T) { if _, err := HexToECDSA("0000000000000000000000000000000000000000000000000000000000000000"); err == nil { t.Fatal("HexToECDSA should've returned error") } + if _, err := HexToECDSA("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); err == nil { t.Fatal("HexToECDSA should've returned error") } @@ -69,6 +71,7 @@ func TestUnmarshalPubkey(t *testing.T) { if err != errInvalidPubkey || key != nil { t.Fatalf("expected error, got %v, %v", err, key) } + key, err = UnmarshalPubkey([]byte{1, 2, 3}) if err != errInvalidPubkey || key != nil { t.Fatalf("expected error, got %v, %v", err, key) @@ -82,10 +85,12 @@ func TestUnmarshalPubkey(t *testing.T) { Y: hexutil.MustDecodeBig("0xb01abc6e1db640cf3106b520344af1d58b00b57823db3e1407cbc433e1b6d04d"), } ) + key, err = UnmarshalPubkey(enc) if err != nil { t.Fatalf("expected no error, got %v", err) } + if !reflect.DeepEqual(key, dec) { t.Fatal("wrong result") } @@ -96,15 +101,19 @@ func TestSign(t *testing.T) { addr := common.HexToAddress(testAddrHex) msg := Keccak256([]byte("foo")) + sig, err := Sign(msg, key) if err != nil { t.Errorf("Sign error: %s", err) } + recoveredPub, err := Ecrecover(msg, sig) if err != nil { t.Errorf("ECRecover error: %s", err) } + pubKey, _ := UnmarshalPubkey(recoveredPub) + recoveredAddr := PubkeyToAddress(*pubKey) if addr != recoveredAddr { t.Errorf("Address mismatch: want: %x have: %x", addr, recoveredAddr) @@ -115,6 +124,7 @@ func TestSign(t *testing.T) { if err != nil { t.Errorf("ECRecover error: %s", err) } + recoveredAddr2 := PubkeyToAddress(*recoveredPub2) if addr != recoveredAddr2 { t.Errorf("Address mismatch: want: %x have: %x", addr, recoveredAddr2) @@ -125,6 +135,7 @@ func TestInvalidSign(t *testing.T) { if _, err := Sign(make([]byte, 1), nil); err == nil { t.Errorf("expected sign with hash 1 byte to error") } + if _, err := Sign(make([]byte, 33), nil); err == nil { t.Errorf("expected sign with hash 33 byte to error") } @@ -140,6 +151,7 @@ func TestNewContractAddress(t *testing.T) { caddr0 := CreateAddress(addr, 0) caddr1 := CreateAddress(addr, 1) caddr2 := CreateAddress(addr, 2) + checkAddr(t, common.HexToAddress("333c3310824b7c685133f2bedb2ca4b8b4df633d"), caddr0) checkAddr(t, common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), caddr1) checkAddr(t, common.HexToAddress("c9ddedf451bc62ce88bf9292afb13df35b670699"), caddr2) @@ -185,11 +197,13 @@ func TestLoadECDSA(t *testing.T) { if err != nil { t.Fatal(err) } + filename := f.Name() f.WriteString(test.input) f.Close() _, err = LoadECDSA(filename) + switch { case err != nil && test.err == "": t.Fatalf("unexpected error for input %q:\n %v", test.input, err) @@ -206,18 +220,22 @@ func TestSaveECDSA(t *testing.T) { if err != nil { t.Fatal(err) } + file := f.Name() f.Close() + defer os.Remove(file) key, _ := HexToECDSA(testPrivHex) if err := SaveECDSA(file, key); err != nil { t.Fatal(err) } + loaded, err := LoadECDSA(file) if err != nil { t.Fatal(err) } + if !reflect.DeepEqual(key, loaded) { t.Fatal("loaded key not equal to saved key") } diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index 64b5a99d03..2e19fc05a1 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -83,6 +83,7 @@ type PrivateKey struct { func (prv *PrivateKey) ExportECDSA() *ecdsa.PrivateKey { pub := &prv.PublicKey pubECDSA := pub.ExportECDSA() + return &ecdsa.PrivateKey{PublicKey: *pubECDSA, D: prv.D} } @@ -99,15 +100,19 @@ func GenerateKey(rand io.Reader, curve elliptic.Curve, params *ECIESParams) (prv if err != nil { return } + prv = new(PrivateKey) prv.PublicKey.X = x prv.PublicKey.Y = y prv.PublicKey.Curve = curve prv.D = new(big.Int).SetBytes(pb) + if params == nil { params = ParamsFromCurve(curve) } + prv.PublicKey.Params = params + return } @@ -122,6 +127,7 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b if prv.PublicKey.Curve != pub.Curve { return nil, ErrInvalidCurve } + if skLen+macLen > MaxSharedKeyLength(pub) { return nil, ErrSharedKeyTooBig } @@ -134,6 +140,7 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b sk = make([]byte, skLen+macLen) skBytes := x.Bytes() copy(sk[len(sk)-len(skBytes):], skBytes) + return sk, nil } @@ -146,6 +153,7 @@ var ( func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { counterBytes := make([]byte, 4) k := make([]byte, 0, roundup(kdLen, hash.Size())) + for counter := uint32(1); len(k) < kdLen; counter++ { binary.BigEndian.PutUint32(counterBytes, counter) hash.Reset() @@ -154,6 +162,7 @@ func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { hash.Write(s1) k = hash.Sum(k) } + return k[:kdLen] } @@ -167,9 +176,11 @@ func deriveKeys(hash hash.Hash, z, s1 []byte, keyLen int) (Ke, Km []byte) { K := concatKDF(hash, z, s1, 2*keyLen) Ke = K[:keyLen] Km = K[keyLen:] + hash.Reset() hash.Write(Km) Km = hash.Sum(Km[:0]) + return Ke, Km } @@ -180,6 +191,7 @@ func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte { mac.Write(msg) mac.Write(shared) tag := mac.Sum(nil) + return tag } @@ -187,6 +199,7 @@ func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte { func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) { iv = make([]byte, params.BlockSize) _, err = io.ReadFull(rand, iv) + return } @@ -201,11 +214,13 @@ func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, if err != nil { return } + ctr := cipher.NewCTR(c, iv) ct = make([]byte, len(m)+params.BlockSize) copy(ct, iv) ctr.XORKeyStream(ct[params.BlockSize:], m) + return } @@ -221,6 +236,7 @@ func symDecrypt(params *ECIESParams, key, ct []byte) (m []byte, err error) { m = make([]byte, len(ct)-params.BlockSize) ctr.XORKeyStream(m, ct[params.BlockSize:]) + return } @@ -260,6 +276,7 @@ func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err e copy(ct, Rb) copy(ct[len(Rb):], em) copy(ct[len(Rb)+len(em):], d) + return ct, nil } @@ -268,6 +285,7 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { if len(c) == 0 { return nil, ErrInvalidMessage } + params, err := pubkeyParams(&prv.PublicKey) if err != nil { return nil, err @@ -298,6 +316,7 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { R := new(PublicKey) R.Curve = prv.PublicKey.Curve R.X, R.Y = elliptic.Unmarshal(R.Curve, c[:rLen]) + if R.X == nil { return nil, ErrInvalidPublicKey } @@ -306,6 +325,7 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { if err != nil { return nil, err } + Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) d := messageTag(params.Hash, Km, c[mStart:mEnd], s2) diff --git a/crypto/ecies/ecies_test.go b/crypto/ecies/ecies_test.go index 8ca42c9c8e..7c2c7f61f5 100644 --- a/crypto/ecies/ecies_test.go +++ b/crypto/ecies/ecies_test.go @@ -55,6 +55,7 @@ func TestKDF(t *testing.T) { for _, test := range tests { h := sha256.New() + k := concatKDF(h, []byte("input"), nil, test.length) if !bytes.Equal(k, test.output) { t.Fatalf("KDF: generated key %x does not match expected output %x", k, test.output) @@ -78,6 +79,7 @@ func TestSharedKey(t *testing.T) { if err != nil { t.Fatal(err) } + skLen := MaxSharedKeyLength(&prv1.PublicKey) / 2 prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) @@ -112,12 +114,15 @@ func TestSharedKeyPadding(t *testing.T) { if prv0.PublicKey.X.Cmp(x0) != 0 { t.Errorf("mismatched prv0.X:\nhave: %x\nwant: %x\n", prv0.PublicKey.X.Bytes(), x0.Bytes()) } + if prv0.PublicKey.Y.Cmp(y0) != 0 { t.Errorf("mismatched prv0.Y:\nhave: %x\nwant: %x\n", prv0.PublicKey.Y.Bytes(), y0.Bytes()) } + if prv1.PublicKey.X.Cmp(x1) != 0 { t.Errorf("mismatched prv1.X:\nhave: %x\nwant: %x\n", prv1.PublicKey.X.Bytes(), x1.Bytes()) } + if prv1.PublicKey.Y.Cmp(y1) != 0 { t.Errorf("mismatched prv1.Y:\nhave: %x\nwant: %x\n", prv1.PublicKey.Y.Bytes(), y1.Bytes()) } @@ -177,7 +182,9 @@ func BenchmarkGenSharedKeyP256(b *testing.B) { if err != nil { b.Fatal(err) } + b.ResetTimer() + for i := 0; i < b.N; i++ { _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) if err != nil { @@ -192,7 +199,9 @@ func BenchmarkGenSharedKeyS256(b *testing.B) { if err != nil { b.Fatal(err) } + b.ResetTimer() + for i := 0; i < b.N; i++ { _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) if err != nil { @@ -214,6 +223,7 @@ func TestEncryptDecrypt(t *testing.T) { } message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv2.PublicKey, message, nil, nil) if err != nil { t.Fatal(err) @@ -239,8 +249,10 @@ func TestDecryptShared2(t *testing.T) { if err != nil { t.Fatal(err) } + message := []byte("Hello, world.") shared2 := []byte("shared data 2") + ct, err := Encrypt(rand.Reader, &prv.PublicKey, message, nil, shared2) if err != nil { t.Fatal(err) @@ -251,6 +263,7 @@ func TestDecryptShared2(t *testing.T) { if err != nil { t.Fatal(err) } + if !bytes.Equal(pt, message) { t.Fatal("ecies: plaintext doesn't match message") } @@ -259,6 +272,7 @@ func TestDecryptShared2(t *testing.T) { if _, err = prv.Decrypt(ct, nil, nil); err == nil { t.Fatal("ecies: decrypting without shared data didn't fail") } + if _, err = prv.Decrypt(ct, nil, []byte("garbage")); err == nil { t.Fatal("ecies: decrypting with incorrect shared data didn't fail") } @@ -316,6 +330,7 @@ func testParamSelection(t *testing.T, c testCase) { } message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv2.PublicKey, message, nil, nil) if err != nil { t.Fatalf("%s (%s)\n", err.Error(), c.Name) @@ -347,6 +362,7 @@ func TestBasicKeyValidation(t *testing.T) { } message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv.PublicKey, message, nil, nil) if err != nil { t.Fatal(err) @@ -354,6 +370,7 @@ func TestBasicKeyValidation(t *testing.T) { for _, b := range badBytes { ct[0] = b + _, err := prv.Decrypt(ct, nil, nil) if err != ErrInvalidPublicKey { t.Fatal("ecies: validated an invalid key") @@ -367,6 +384,7 @@ func TestBox(t *testing.T) { pub2 := &prv2.PublicKey message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, pub2, message, nil, nil) if err != nil { t.Fatal(err) @@ -376,9 +394,11 @@ func TestBox(t *testing.T) { if err != nil { t.Fatal(err) } + if !bytes.Equal(pt, message) { t.Fatal("ecies: plaintext doesn't match message") } + if _, err = prv1.Decrypt(ct, nil, nil); err == nil { t.Fatal("ecies: encryption should not have succeeded") } @@ -417,6 +437,7 @@ func hexKey(prv string) *PrivateKey { if err != nil { panic(err) } + return ImportECDSA(key) } @@ -425,5 +446,6 @@ func decode(s string) []byte { if err != nil { panic(err) } + return bytes } diff --git a/crypto/ecies/params.go b/crypto/ecies/params.go index 39e7c89473..fe818df184 100644 --- a/crypto/ecies/params.go +++ b/crypto/ecies/params.go @@ -137,8 +137,10 @@ func pubkeyParams(key *PublicKey) (*ECIESParams, error) { return nil, ErrUnsupportedECIESParameters } } + if params.KeyLen > maxKeyLen { return nil, ErrInvalidKeyLen } + return params, nil } diff --git a/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go index 9b26ab2928..bdedf06b8c 100644 --- a/crypto/secp256k1/curve.go +++ b/crypto/secp256k1/curve.go @@ -120,6 +120,7 @@ func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big. zinvsq.Mul(zinvsq, zinv) yOut = new(big.Int).Mul(y, zinvsq) yOut.Mod(yOut, BitCurve.P) + return } @@ -130,13 +131,16 @@ func (BitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) { if x1.Sign() == 0 && y1.Sign() == 0 { return x2, y2 } + if x2.Sign() == 0 && y2.Sign() == 0 { return x1, y1 } + z := new(big.Int).SetInt64(1) if x1.Cmp(x2) == 0 && y1.Cmp(y2) == 0 { return BitCurve.affineFromJacobian(BitCurve.doubleJacobian(x1, y1, z)) } + return BitCurve.affineFromJacobian(BitCurve.addJacobian(x1, y1, z, x2, y2, z)) } @@ -146,17 +150,21 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl z1z1 := new(big.Int).Mul(z1, z1) z1z1.Mod(z1z1, BitCurve.P) + z2z2 := new(big.Int).Mul(z2, z2) z2z2.Mod(z2z2, BitCurve.P) u1 := new(big.Int).Mul(x1, z2z2) u1.Mod(u1, BitCurve.P) + u2 := new(big.Int).Mul(x2, z1z1) u2.Mod(u2, BitCurve.P) + h := new(big.Int).Sub(u2, u1) if h.Sign() == -1 { h.Add(h, BitCurve.P) } + i := new(big.Int).Lsh(h, 1) i.Mul(i, i) j := new(big.Int).Mul(h, i) @@ -164,14 +172,18 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int s1 := new(big.Int).Mul(y1, z2) s1.Mul(s1, z2z2) s1.Mod(s1, BitCurve.P) + s2 := new(big.Int).Mul(y2, z1) s2.Mul(s2, z1z1) s2.Mod(s2, BitCurve.P) + r := new(big.Int).Sub(s2, s1) if r.Sign() == -1 { r.Add(r, BitCurve.P) } + r.Lsh(r, 1) + v := new(big.Int).Mul(u1, i) x3 := new(big.Int).Set(r) @@ -182,6 +194,7 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int x3.Mod(x3, BitCurve.P) y3 := new(big.Int).Set(r) + v.Sub(v, x3) y3.Mul(y3, v) s1.Mul(s1, j) @@ -192,13 +205,17 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int z3 := new(big.Int).Add(z1, z2) z3.Mul(z3, z3) z3.Sub(z3, z1z1) + if z3.Sign() == -1 { z3.Add(z3, BitCurve.P) } + z3.Sub(z3, z2z2) + if z3.Sign() == -1 { z3.Add(z3, BitCurve.P) } + z3.Mul(z3, h) z3.Mod(z3, BitCurve.P) @@ -215,7 +232,6 @@ func (BitCurve *BitCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) { // returns its double, also in Jacobian form. func (BitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) { // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l - a := new(big.Int).Mul(x, x) //X1² b := new(big.Int).Mul(y, y) //Y1² c := new(big.Int).Mul(b, b) //B² @@ -259,6 +275,7 @@ func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte { ret[0] = 4 // uncompressed point flag readBits(x, ret[1:1+byteLen]) readBits(y, ret[1+byteLen:]) + return ret } @@ -269,11 +286,14 @@ func (BitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) { if len(data) != 1+2*byteLen { return } + if data[0] != 4 { // uncompressed form return } + x = new(big.Int).SetBytes(data[1 : 1+byteLen]) y = new(big.Int).SetBytes(data[1+byteLen:]) + return } diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index ef2a3a3790..6a1cd4b1c5 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -21,6 +21,7 @@ func generateKeyPair() (pubkey, privkey []byte) { if err != nil { panic(err) } + pubkey = elliptic.Marshal(S256(), key.X, key.Y) privkey = make([]byte, 32) @@ -35,6 +36,7 @@ func csprngEntropy(n int) []byte { if _, err := io.ReadFull(rand.Reader, buf); err != nil { panic("reading from crypto/rand failed: " + err.Error()) } + return buf } @@ -42,6 +44,7 @@ func randSig() []byte { sig := csprngEntropy(65) sig[32] &= 0x70 sig[64] %= 4 + return sig } @@ -52,9 +55,11 @@ func compactSigCheck(t *testing.T, sig []byte) { if b < 0 { t.Errorf("highest bit is negative: %d", b) } + if ((b >> 7) == 1) != ((b & 0x80) == 0x80) { t.Errorf("highest bit: %d bit >> 7: %d", b, b>>7) } + if (b & 0x80) == 0x80 { t.Errorf("highest bit: %d bit & 0x80: %d", b, b&0x80) } @@ -63,20 +68,26 @@ func compactSigCheck(t *testing.T, sig []byte) { func TestSignatureValidity(t *testing.T) { pubkey, seckey := generateKeyPair() msg := csprngEntropy(32) + sig, err := Sign(msg, seckey) if err != nil { t.Errorf("signature error: %s", err) } + compactSigCheck(t, sig) + if len(pubkey) != 65 { t.Errorf("pubkey length mismatch: want: 65 have: %d", len(pubkey)) } + if len(seckey) != 32 { t.Errorf("seckey length mismatch: want: 32 have: %d", len(seckey)) } + if len(sig) != 65 { t.Errorf("sig length mismatch: want: 65 have: %d", len(sig)) } + recid := int(sig[64]) if recid > 4 || recid < 0 { t.Errorf("sig recid mismatch: want: within 0 to 4 have: %d", int(sig[64])) @@ -88,6 +99,7 @@ func TestInvalidRecoveryID(t *testing.T) { msg := csprngEntropy(32) sig, _ := Sign(msg, seckey) sig[64] = 99 + _, err := RecoverPubkey(msg, sig) if err != ErrInvalidRecoveryID { t.Fatalf("got %q, want %q", err, ErrInvalidRecoveryID) @@ -97,14 +109,17 @@ func TestInvalidRecoveryID(t *testing.T) { func TestSignAndRecover(t *testing.T) { pubkey1, seckey := generateKeyPair() msg := csprngEntropy(32) + sig, err := Sign(msg, seckey) if err != nil { t.Errorf("signature error: %s", err) } + pubkey2, err := RecoverPubkey(msg, sig) if err != nil { t.Errorf("recover error: %s", err) } + if !bytes.Equal(pubkey1, pubkey2) { t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2) } @@ -119,10 +134,12 @@ func TestSignDeterministic(t *testing.T) { if err != nil { t.Fatal(err) } + sig2, err := Sign(msg, seckey) if err != nil { t.Fatal(err) } + if !bytes.Equal(sig1, sig2) { t.Fatal("signatures not equal") } @@ -148,13 +165,16 @@ func signAndRecoverWithRandomMessages(t *testing.T, keys func() ([]byte, []byte) for i := 0; i < TestCount; i++ { pubkey1, seckey := keys() msg := csprngEntropy(32) + sig, err := Sign(msg, seckey) if err != nil { t.Fatalf("signature error: %s", err) } + if sig == nil { t.Fatal("signature is nil") } + compactSigCheck(t, sig) // TODO: why do we flip around the recovery id? @@ -164,9 +184,11 @@ func signAndRecoverWithRandomMessages(t *testing.T, keys func() ([]byte, []byte) if err != nil { t.Fatalf("recover error: %s", err) } + if pubkey2 == nil { t.Error("pubkey is nil") } + if !bytes.Equal(pubkey1, pubkey2) { t.Fatalf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2) } @@ -207,10 +229,12 @@ func TestRecoverSanity(t *testing.T) { msg, _ := hex.DecodeString("ce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008") sig, _ := hex.DecodeString("90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301") pubkey1, _ := hex.DecodeString("04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652") + pubkey2, err := RecoverPubkey(msg, sig) if err != nil { t.Fatalf("recover error: %s", err) } + if !bytes.Equal(pubkey1, pubkey2) { t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2) } @@ -219,6 +243,7 @@ func TestRecoverSanity(t *testing.T) { func BenchmarkSign(b *testing.B) { _, seckey := generateKeyPair() msg := csprngEntropy(32) + b.ResetTimer() for i := 0; i < b.N; i++ { @@ -230,6 +255,7 @@ func BenchmarkRecover(b *testing.B) { msg := csprngEntropy(32) _, seckey := generateKeyPair() sig, _ := Sign(msg, seckey) + b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/crypto/signature_cgo.go b/crypto/signature_cgo.go index 3a32755f5e..264e365124 100644 --- a/crypto/signature_cgo.go +++ b/crypto/signature_cgo.go @@ -41,6 +41,7 @@ func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) { } x, y := elliptic.Unmarshal(S256(), s) + return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil } @@ -56,8 +57,10 @@ func Sign(digestHash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { if len(digestHash) != DigestLength { return nil, fmt.Errorf("hash is required to be exactly %d bytes (%d)", DigestLength, len(digestHash)) } + seckey := math.PaddedBigBytes(prv.D, prv.Params().BitSize/8) defer zeroBytes(seckey) + return secp256k1.Sign(digestHash, seckey) } @@ -74,6 +77,7 @@ func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) { if x == nil { return nil, fmt.Errorf("invalid public key") } + return &ecdsa.PublicKey{X: x, Y: y, Curve: S256()}, nil } diff --git a/crypto/signature_test.go b/crypto/signature_test.go index aecff76bfb..a8383fe87d 100644 --- a/crypto/signature_test.go +++ b/crypto/signature_test.go @@ -39,6 +39,7 @@ func TestEcrecover(t *testing.T) { if err != nil { t.Fatalf("recover error: %s", err) } + if !bytes.Equal(pubkey, testpubkey) { t.Errorf("pubkey mismatch: want: %x have: %x", testpubkey, pubkey) } @@ -49,6 +50,7 @@ func TestVerifySignature(t *testing.T) { if !VerifySignature(testpubkey, testmsg, sig) { t.Errorf("can't verify signature with uncompressed key") } + if !VerifySignature(testpubkeyc, testmsg, sig) { t.Errorf("can't verify signature with compressed key") } @@ -56,20 +58,26 @@ func TestVerifySignature(t *testing.T) { if VerifySignature(nil, testmsg, sig) { t.Errorf("signature valid with no key") } + if VerifySignature(testpubkey, nil, sig) { t.Errorf("signature valid with no message") } + if VerifySignature(testpubkey, testmsg, nil) { t.Errorf("nil signature valid") } + if VerifySignature(testpubkey, testmsg, append(common.CopyBytes(sig), 1, 2, 3)) { t.Errorf("signature valid with extra bytes at the end") } + if VerifySignature(testpubkey, testmsg, sig[:len(sig)-2]) { t.Errorf("signature valid even though it's incomplete") } + wrongkey := common.CopyBytes(testpubkey) wrongkey[10]++ + if VerifySignature(wrongkey, testmsg, sig) { t.Errorf("signature valid with with wrong public key") } @@ -80,6 +88,7 @@ func TestVerifySignatureMalleable(t *testing.T) { sig := hexutil.MustDecode("0x638a54215d80a6713c8d523a6adc4e6e73652d859103a36b700851cb0e61b66b8ebfc1a610c57d732ec6e0a8f06a9a7a28df5051ece514702ff9cdff0b11f454") key := hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138") msg := hexutil.MustDecode("0xd301ce462d3e639518f482c7f03821fec1e602018630ce621e1e7851c12343a6") + if VerifySignature(key, msg, sig) { t.Error("VerifySignature returned true for malleable signature") } @@ -90,15 +99,19 @@ func TestDecompressPubkey(t *testing.T) { if err != nil { t.Fatal(err) } + if uncompressed := FromECDSAPub(key); !bytes.Equal(uncompressed, testpubkey) { t.Errorf("wrong public key result: got %x, want %x", uncompressed, testpubkey) } + if _, err := DecompressPubkey(nil); err == nil { t.Errorf("no error for nil pubkey") } + if _, err := DecompressPubkey(testpubkeyc[:5]); err == nil { t.Errorf("no error for incomplete pubkey") } + if _, err := DecompressPubkey(append(common.CopyBytes(testpubkeyc), 1, 2, 3)); err == nil { t.Errorf("no error for pubkey with extra bytes at the end") } @@ -110,6 +123,7 @@ func TestCompressPubkey(t *testing.T) { X: math.MustParseBig256("0xe32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a"), Y: math.MustParseBig256("0x0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652"), } + compressed := CompressPubkey(key) if !bytes.Equal(compressed, testpubkeyc) { t.Errorf("wrong public key result: got %x, want %x", compressed, testpubkeyc) @@ -124,10 +138,12 @@ func TestPubkeyRandom(t *testing.T) { if err != nil { t.Fatalf("iteration %d: %v", i, err) } + pubkey2, err := DecompressPubkey(CompressPubkey(&key.PublicKey)) if err != nil { t.Fatalf("iteration %d: %v", i, err) } + if !reflect.DeepEqual(key.PublicKey, *pubkey2) { t.Fatalf("iteration %d: keys not equal", i) } diff --git a/crypto/signify/signify.go b/crypto/signify/signify.go index ebe33b1ddd..9313696c4b 100644 --- a/crypto/signify/signify.go +++ b/crypto/signify/signify.go @@ -40,12 +40,15 @@ func parsePrivateKey(key string) (k ed25519.PrivateKey, header []byte, keyNum [] 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 keydata[40:], keydata[:2], keydata[32:40], nil } @@ -58,12 +61,15 @@ func SignFile(input string, output string, key string, untrustedComment string, if strings.IndexByte(untrustedComment, '\n') >= 0 { return errors.New("untrusted comment must not contain newline") } + 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()) } @@ -72,6 +78,7 @@ func SignFile(input string, output string, key string, untrustedComment string, if err != nil { return err } + skey, header, keyNum, err := parsePrivateKey(key) if err != nil { return err @@ -79,6 +86,7 @@ func SignFile(input string, output string, key string, untrustedComment string, // Create the main data signature. rawSig := ed25519.Sign(skey, filedata) + var dataSig []byte dataSig = append(dataSig, header...) dataSig = append(dataSig, keyNum...) @@ -92,6 +100,7 @@ func SignFile(input string, output string, key string, untrustedComment string, // 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) diff --git a/crypto/signify/signify_test.go b/crypto/signify/signify_test.go index 9bac2c825f..2c78f7162c 100644 --- a/crypto/signify/signify_test.go +++ b/crypto/signify/signify_test.go @@ -37,6 +37,7 @@ func TestSignify(t *testing.T) { if err != nil { t.Fatal(err) } + defer os.Remove(tmpFile.Name()) defer tmpFile.Close() @@ -69,6 +70,7 @@ func TestSignify(t *testing.T) { if err != nil { t.Fatal(err) } + if !valid { t.Fatal("invalid signature") } @@ -79,6 +81,7 @@ func TestSignifyTrustedCommentTooManyLines(t *testing.T) { if err != nil { t.Fatal(err) } + defer os.Remove(tmpFile.Name()) defer tmpFile.Close() @@ -102,6 +105,7 @@ func TestSignifyTrustedCommentTooManyLinesLF(t *testing.T) { if err != nil { t.Fatal(err) } + defer os.Remove(tmpFile.Name()) defer tmpFile.Close() @@ -125,6 +129,7 @@ func TestSignifyTrustedCommentEmpty(t *testing.T) { if err != nil { t.Fatal(err) } + defer os.Remove(tmpFile.Name()) defer tmpFile.Close() diff --git a/eth/api.go b/eth/api.go index 905b0165a9..d3ff788c15 100644 --- a/eth/api.go +++ b/eth/api.go @@ -90,6 +90,7 @@ func (api *MinerAPI) Start(threads *int) error { if threads == nil { return api.e.StartMining(runtime.NumCPU()) } + return api.e.StartMining(*threads) } @@ -104,6 +105,7 @@ func (api *MinerAPI) SetExtra(extra string) (bool, error) { if err := api.e.Miner().SetExtra([]byte(extra)); err != nil { return false, err } + return true, nil } @@ -114,6 +116,7 @@ func (api *MinerAPI) SetGasPrice(gasPrice hexutil.Big) bool { api.e.lock.Unlock() api.e.txPool.SetGasPrice((*big.Int)(&gasPrice)) + return true } @@ -151,10 +154,12 @@ func (api *AdminAPI) ExportChain(file string, first *uint64, last *uint64) (bool if first == nil && last != nil { return false, errors.New("last cannot be specified without first") } + if first != nil && last == nil { head := api.eth.BlockChain().CurrentHeader().Number.Uint64() last = &head } + if _, err := os.Stat(file); err == nil { // File already exists. Allowing overwrite could be a DoS vector, // since the 'file' may point to arbitrary paths on the drive. @@ -181,6 +186,7 @@ func (api *AdminAPI) ExportChain(file string, first *uint64, last *uint64) (bool } else if err := api.eth.BlockChain().Export(writer); err != nil { return false, err } + return true, nil } @@ -214,6 +220,7 @@ func (api *AdminAPI) ImportChain(file string) (bool, error) { stream := rlp.NewStream(reader, 0) blocks, index := make([]*types.Block, 0, 2500), 0 + for batch := 0; ; batch++ { // Load a batch of blocks from the input file for len(blocks) < cap(blocks) { @@ -223,9 +230,11 @@ func (api *AdminAPI) ImportChain(file string) (bool, error) { } else if err != nil { return false, fmt.Errorf("block %d: failed to parse: %v", index, err) } + blocks = append(blocks, block) index++ } + if len(blocks) == 0 { break } @@ -238,8 +247,10 @@ func (api *AdminAPI) ImportChain(file string) (bool, error) { if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil { return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err) } + blocks = blocks[:0] } + return true, nil } @@ -260,6 +271,7 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { OnlyWithAddresses: true, Max: AccountRangeMaxResults, // Sanity limit over RPC } + if blockNr == rpc.PendingBlockNumber { // If we're dumping the pending state, we need to request // both the pending block as well as the pending state from @@ -280,6 +292,7 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { if block == nil { return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) } + header = block.Header() } @@ -291,6 +304,7 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { if err != nil { return state.Dump{}, err } + return stateDb.RawDump(opts), nil } @@ -299,6 +313,7 @@ func (api *DebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.By if preimage := rawdb.ReadPreimage(api.eth.ChainDb(), hash); preimage != nil { return preimage, nil } + return nil, errors.New("unknown preimage") } @@ -317,25 +332,30 @@ func (api *DebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, 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 { blockRlp = err.Error() // Hacky, but hey, it works } else { blockRlp = fmt.Sprintf("%#x", rlpBytes) } + if blockJSON, err = ethapi.RPCMarshalBlock(block, true, true, api.eth.APIBackend.ChainConfig(), api.eth.chainDb); err != nil { blockJSON = map[string]interface{}{"error": err.Error()} } + results = append(results, &BadBlockArgs{ Hash: block.Hash(), RLP: blockRlp, Block: blockJSON, }) } + return results, nil } @@ -345,6 +365,7 @@ const AccountRangeMaxResults = 256 // AccountRange enumerates all accounts in the given block and start point in paging request func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error) { var stateDb *state.StateDB + var err error if number, ok := blockNrOrHash.Number(); ok { @@ -366,11 +387,14 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex if block == nil { return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) } + header = block.Header() } + if header == nil { return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) } + stateDb, err = api.eth.BlockChain().StateAt(header.Root) if err != nil { return state.IteratorDump{}, err @@ -381,6 +405,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex if block == nil { return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex()) } + stateDb, err = api.eth.BlockChain().StateAt(block.Root()) if err != nil { return state.IteratorDump{}, err @@ -399,6 +424,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex if maxResults > AccountRangeMaxResults || maxResults <= 0 { opts.Max = AccountRangeMaxResults } + return stateDb.IteratorDump(opts), nil } @@ -435,25 +461,31 @@ func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, if err != nil { return StorageRangeResult{}, err } + if st == nil { return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress) } + return storageRangeAt(st, keyStart, maxResult) } func storageRangeAt(st state.Trie, start []byte, maxResult int) (StorageRangeResult, error) { it := trie.NewIterator(st.NodeIterator(start)) result := StorageRangeResult{Storage: storageMap{}} + for i := 0; i < maxResult && it.Next(); i++ { _, content, _, err := rlp.Split(it.Value) if err != nil { return StorageRangeResult{}, err } + e := storageEntry{Value: common.BytesToHash(content)} + if preimage := st.GetKey(it.Key); preimage != nil { preimage := common.BytesToHash(preimage) e.Key = &preimage } + result.Storage[common.BytesToHash(it.Key)] = e } // Add the 'next key' so clients can continue downloading. @@ -461,6 +493,7 @@ func storageRangeAt(st state.Trie, start []byte, maxResult int) (StorageRangeRes next := common.BytesToHash(it.Key) result.NextKey = &next } + return result, nil } @@ -480,6 +513,7 @@ func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64 if endNum == nil { endBlock = startBlock startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) + if startBlock == nil { return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) } @@ -489,6 +523,7 @@ func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64 return nil, fmt.Errorf("end block %d not found", *endNum) } } + return api.getModifiedAccounts(startBlock, endBlock) } @@ -499,6 +534,7 @@ func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64 // With one parameter, returns the list of accounts modified in the specified block. func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) { var startBlock, endBlock *types.Block + startBlock = api.eth.blockchain.GetBlockByHash(startHash) if startBlock == nil { return nil, fmt.Errorf("start block %x not found", startHash) @@ -507,6 +543,7 @@ func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *c if endHash == nil { endBlock = startBlock startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) + if startBlock == nil { return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) } @@ -516,6 +553,7 @@ func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *c return nil, fmt.Errorf("end block %x not found", *endHash) } } + return api.getModifiedAccounts(startBlock, endBlock) } @@ -523,6 +561,7 @@ func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]c if startBlock.Number().Uint64() >= endBlock.Number().Uint64() { return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) } + triedb := api.eth.BlockChain().StateCache().TrieDB() oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startBlock.Root()), triedb) @@ -534,17 +573,21 @@ func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]c if err != nil { return nil, err } + diff, _ := trie.NewDifferenceIterator(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{})) iter := trie.NewIterator(diff) var dirty []common.Address + for iter.Next() { key := newTrie.GetKey(iter.Key) if key == nil { return nil, fmt.Errorf("no preimage found for hash %x", iter.Key) } + dirty = append(dirty, common.BytesToAddress(key)) } + return dirty, nil } @@ -555,11 +598,14 @@ func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]c // either forwards or backwards func (api *DebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64, error) { db := api.eth.ChainDb() + var pivot uint64 + if p := rawdb.ReadLastPivotNumber(db); p != nil { pivot = *p log.Info("Found fast-sync pivot marker", "number", pivot) } + var resolveNum = func(num rpc.BlockNumber) (uint64, error) { // We don't have state for pending (-2), so treat it as latest if num.Int64() < 0 { @@ -571,6 +617,7 @@ func (api *DebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64, error } return uint64(num.Int64()), nil } + var ( start uint64 end uint64 @@ -578,30 +625,39 @@ func (api *DebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64, error lastLog time.Time err error ) + if start, err = resolveNum(from); err != nil { return 0, err } + if end, err = resolveNum(to); err != nil { return 0, err } + if start == end { return 0, fmt.Errorf("from and to needs to be different") } + if start > end { delta = -1 } + for i := int64(start); i != int64(end); i += delta { if time.Since(lastLog) > 8*time.Second { log.Info("Finding roots", "from", start, "to", end, "at", i) + lastLog = time.Now() } + if i < int64(pivot) { continue } + h := api.eth.BlockChain().GetHeaderByNumber(uint64(i)) if h == nil { return 0, fmt.Errorf("missing header %d", i) } + if ok, _ := api.eth.ChainDb().Has(h.Root[:]); ok { return uint64(i), nil } diff --git a/eth/api_backend.go b/eth/api_backend.go index d0eef55dc4..564aab59c8 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -102,6 +102,7 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb return nil, errors.New("safe block not found") } + return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil } @@ -109,16 +110,20 @@ func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash if blockNr, ok := blockNrOrHash.Number(); ok { return b.HeaderByNumber(ctx, blockNr) } + if hash, ok := blockNrOrHash.Hash(); ok { header := b.eth.blockchain.GetHeaderByHash(hash) if header == nil { return nil, errors.New("header for hash not found") } + if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { return nil, errors.New("hash is not currently canonical") } + return header, nil } + return nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -157,6 +162,7 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil } + return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil } @@ -181,20 +187,25 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r if blockNr, ok := blockNrOrHash.Number(); ok { return b.BlockByNumber(ctx, blockNr) } + if hash, ok := blockNrOrHash.Hash(); ok { header := b.eth.blockchain.GetHeaderByHash(hash) if header == nil { return nil, errors.New("header for hash not found") } + if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { return nil, errors.New("hash is not currently canonical") } + block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64()) if block == nil { return nil, errors.New("header found, but block body is missing") } + return block, nil } + return nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -213,10 +224,13 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B if err != nil { return nil, nil, err } + if header == nil { return nil, nil, errors.New("header not found") } + stateDb, err := b.eth.BlockChain().StateAt(header.Root) + return stateDb, header, err } @@ -224,20 +238,26 @@ func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockN if blockNr, ok := blockNrOrHash.Number(); ok { return b.StateAndHeaderByNumber(ctx, blockNr) } + if hash, ok := blockNrOrHash.Hash(); ok { header, err := b.HeaderByHash(ctx, hash) if err != nil { return nil, nil, err } + if header == nil { return nil, nil, errors.New("header for hash not found") } + if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { return nil, nil, errors.New("hash is not currently canonical") } + stateDb, err := b.eth.BlockChain().StateAt(header.Root) + return stateDb, header, err } + return nil, nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -253,6 +273,7 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { if header := b.eth.blockchain.GetHeaderByHash(hash); header != nil { return b.eth.blockchain.GetTd(hash, header.Number.Uint64()) } + return nil } @@ -260,6 +281,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st if vmConfig == nil { vmConfig = b.eth.blockchain.GetVMConfig() } + txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) @@ -303,10 +325,13 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { pending := b.eth.txPool.Pending(context.Background(), false) + var txs types.Transactions + for _, batch := range pending { txs = append(txs, batch...) } + return txs, nil } diff --git a/eth/api_test.go b/eth/api_test.go index 316c0899d9..5fe8db9cd5 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -46,14 +46,17 @@ func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, st if len(result.Accounts) != expectedNum { t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts)) } + for address := range result.Accounts { if address == (common.Address{}) { t.Fatalf("empty address returned") } + if !statedb.Exist(address) { t.Fatalf("account not found in state %s", address.Hex()) } } + return result } @@ -78,12 +81,14 @@ func TestAccountRange(t *testing.T) { addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes()) addrs[i] = addr state.SetBalance(addrs[i], big.NewInt(1)) + if _, ok := m[addr]; ok { t.Fatalf("bad") } else { m[addr] = true } } + state.Commit(true) root := state.IntermediateRoot(true) @@ -91,21 +96,25 @@ func TestAccountRange(t *testing.T) { if err != nil { t.Fatal(err) } + accountRangeTest(t, &trie, state, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2) // test pagination firstResult := accountRangeTest(t, &trie, state, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults) secondResult := accountRangeTest(t, &trie, state, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults) hList := make(resultHash, 0) + for addr1 := range firstResult.Accounts { // If address is empty, then it makes no sense to compare // them as they might be two different accounts. if addr1 == (common.Address{}) { continue } + if _, duplicate := secondResult.Accounts[addr1]; duplicate { t.Fatalf("pagination test failed: results should not overlap") } + hList = append(hList, crypto.Keccak256Hash(addr1.Bytes())) } // Test to see if it's possible to recover from the middle of the previous @@ -114,6 +123,7 @@ func TestAccountRange(t *testing.T) { middleH := hList[AccountRangeMaxResults/2] middleResult := accountRangeTest(t, &trie, state, middleH, AccountRangeMaxResults, AccountRangeMaxResults) missing, infirst, insecond := 0, 0, 0 + for h := range middleResult.Accounts { if _, ok := firstResult.Accounts[h]; ok { infirst++ @@ -123,12 +133,15 @@ func TestAccountRange(t *testing.T) { missing++ } } + if missing != 0 { t.Fatalf("%d hashes in the 'middle' set were neither in the first not the second set", missing) } + if infirst != AccountRangeMaxResults/2 { t.Fatalf("Imbalance in the number of first-test results: %d != %d", infirst, AccountRangeMaxResults/2) } + if insecond != AccountRangeMaxResults/2 { t.Fatalf("Imbalance in the number of second-test results: %d != %d", insecond, AccountRangeMaxResults/2) } @@ -141,8 +154,10 @@ func TestEmptyAccountRange(t *testing.T) { statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) st, _ = state.New(common.Hash{}, statedb, nil) ) + st.Commit(true) st.IntermediateRoot(true) + results := st.IteratorDump(&state.DumpConfig{ SkipCode: true, SkipStorage: true, @@ -152,6 +167,7 @@ func TestEmptyAccountRange(t *testing.T) { if bytes.Equal(results.Next, (common.Hash{}).Bytes()) { t.Fatalf("Empty results should not return a second page") } + if len(results.Accounts) != 0 { t.Fatalf("Empty state should not return addresses: %v", results.Accounts) } @@ -177,6 +193,7 @@ func TestStorageRangeAt(t *testing.T) { keys[3]: {Key: &common.Hash{0x03}, Value: common.Hash{0x04}}, } ) + for _, entry := range storage { state.SetState(addr, *entry.Key, entry.Value) } @@ -218,6 +235,7 @@ func TestStorageRangeAt(t *testing.T) { if err != nil { t.Error(err) } + if !reflect.DeepEqual(result, test.want) { t.Fatalf("wrong result for range %#x.., limit %d:\ngot %s\nwant %s", test.start, test.limit, dumper.Sdump(result), dumper.Sdump(&test.want)) diff --git a/eth/backend.go b/eth/backend.go index 32abf35e69..b05aa385f1 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -117,13 +117,16 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run ethereum.Ethereum in light sync mode, use les.LightEthereum") } + if !config.SyncMode.IsValid() { 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", 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 { config.TrieCleanCache += config.TrieDirtyCache * 3 / 5 @@ -131,8 +134,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } else { config.TrieCleanCache += config.TrieDirtyCache } + config.TrieDirtyCache = 0 } + log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) // Assemble the Ethereum object @@ -178,6 +183,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { config.TxPool.AllowUnprotectedTxs = true } + gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice @@ -207,6 +213,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if bcVersion != nil { dbVer = fmt.Sprintf("%d", *bcVersion) } + log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer) if !config.SkipBcVersionCheck { @@ -216,9 +223,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if bcVersion != nil { // only print warning on upgrade, not on init log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion) } + rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) } } + var ( vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, @@ -252,6 +261,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } + ethereum.engine.VerifyHeader(ethereum.blockchain, ethereum.blockchain.CurrentHeader(), true) // TODO think on it // BOR changes @@ -268,6 +278,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { // Permit the downloader to use the trie cache allowance during fast sync cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit + checkpoint := config.Checkpoint if checkpoint == nil { checkpoint = params.TrustedCheckpoints[ethereum.blockchain.Genesis().Hash()] @@ -296,6 +307,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { // Setup DNS discovery iterators. dnsclient := dnsdisc.NewClient(dnsdisc.Config{}) + ethereum.ethDialCandidates, err = dnsclient.NewIterator(ethereum.config.EthDiscoveryURLs...) if err != nil { return nil, err @@ -330,10 +342,12 @@ func makeExtraData(extra []byte) []byte { runtime.GOOS, }) } + if uint64(len(extra)) > params.MaximumExtraDataSize { log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize) extra = nil } + return extra } @@ -393,6 +407,7 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) { if etherbase != (common.Address{}) { return etherbase, nil } + return common.Address{}, fmt.Errorf("etherbase must be explicitly specified") } @@ -411,6 +426,7 @@ func (s *Ethereum) isLocalBlock(header *types.Header) bool { s.lock.RLock() etherbase := s.etherbase s.lock.RUnlock() + if author == etherbase { return true } @@ -421,6 +437,7 @@ func (s *Ethereum) isLocalBlock(header *types.Header) bool { return true } } + return false } @@ -447,6 +464,7 @@ func (s *Ethereum) shouldPreserve(header *types.Header) bool { if _, ok := s.engine.(*clique.Clique); ok { return false } + return s.isLocalBlock(header) } @@ -467,11 +485,14 @@ func (s *Ethereum) StartMining(threads int) error { type threaded interface { SetThreads(threads int) } + if th, ok := s.engine.(threaded); ok { log.Info("Updated mining threads", "threads", threads) + if threads == 0 { threads = -1 // Disable the miner from within } + th.SetThreads(threads) } // If the miner was not running, initialize it @@ -499,6 +520,7 @@ func (s *Ethereum) StartMining(threads int) error { cli = c } } + if cli != nil { wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) if wallet == nil || err != nil { @@ -538,6 +560,7 @@ func (s *Ethereum) StopMining() { type threaded interface { SetThreads(threads int) } + if th, ok := s.engine.(threaded); ok { th.SetThreads(-1) } @@ -598,10 +621,12 @@ func (s *Ethereum) Start() error { // Figure out a max peers count based on the server limits maxPeers := s.p2pServer.MaxPeers + if s.config.LightServ > 0 { if s.config.LightPeers >= s.p2pServer.MaxPeers { return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, s.p2pServer.MaxPeers) } + maxPeers -= s.config.LightPeers } diff --git a/eth/bloombits.go b/eth/bloombits.go index 0cb7050d23..99c34c71bf 100644 --- a/eth/bloombits.go +++ b/eth/bloombits.go @@ -54,6 +54,7 @@ func (eth *Ethereum) startBloomHandlers(sectionSize uint64) { case request := <-eth.bloomRequests: task := <-request task.Bitsets = make([][]byte, len(task.Sections)) + for i, section := range task.Sections { head := rawdb.ReadCanonicalHash(eth.chainDb, (section+1)*sectionSize-1) if compVector, err := rawdb.ReadBloomBits(eth.chainDb, task.Bit, section, head); err == nil { diff --git a/eth/bor_api_backend.go b/eth/bor_api_backend.go index bbfc385185..dd07d96edd 100644 --- a/eth/bor_api_backend.go +++ b/eth/bor_api_backend.go @@ -16,6 +16,7 @@ import ( // GetRootHash returns root hash for given start and end block func (b *EthAPIBackend) GetRootHash(ctx context.Context, starBlockNr uint64, endBlockNr uint64) (string, error) { var api *bor.API + for _, _api := range b.eth.Engine().APIs(b.eth.BlockChain()) { if _api.Namespace == "bor" { api = _api.Service.(*bor.API) @@ -30,6 +31,7 @@ func (b *EthAPIBackend) GetRootHash(ctx context.Context, starBlockNr uint64, end if err != nil { return "", err } + return root, nil } @@ -49,6 +51,7 @@ func (b *EthAPIBackend) GetBorBlockLogs(ctx context.Context, hash common.Hash) ( if receipt == nil { return nil, nil } + return receipt.Logs, nil } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 9f51f6695b..8a8fcc7beb 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -46,6 +46,7 @@ func Register(stack *node.Node, backend *eth.Ethereum) error { Authenticated: true, }, }) + return nil } @@ -210,6 +211,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl defer api.forkchoiceLock.Unlock() log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash) + if update.HeadBlockHash == (common.Hash{}) { log.Warn("Forkchoice requested update to zero hash") return engine.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? @@ -275,6 +277,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1) ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty ) + if td == nil || (block.NumberU64() > 0 && ptd == nil) { log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd) return engine.STATUS_INVALID, errors.New("TDs unavailable for TDD check") @@ -297,6 +300,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl PayloadID: id, } } + if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash { // Block is not canonical, set head. if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil { @@ -312,6 +316,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number) return valid(nil), nil } + api.eth.SetSynced() // If the beacon client also advertised a finalized block, mark the local @@ -339,6 +344,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl log.Warn("Safe block not available in database") return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not available in database")) } + if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash { log.Warn("Safe block not in canonical chain") return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain")) @@ -382,6 +388,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl // the configuration of the node. func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.TransitionConfigurationV1) (*engine.TransitionConfigurationV1, error) { log.Trace("Engine API request received", "method", "ExchangeTransitionConfiguration", "ttd", config.TerminalTotalDifficulty) + if config.TerminalTotalDifficulty == nil { return nil, errors.New("invalid terminal total difficulty") } @@ -395,6 +402,7 @@ func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.Transit log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty) return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty) } + if config.TerminalBlockHash != (common.Hash{}) { if hash := api.eth.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash { return &engine.TransitionConfigurationV1{ @@ -403,6 +411,7 @@ func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.Transit TerminalBlockNumber: config.TerminalBlockNumber, }, nil } + return nil, fmt.Errorf("invalid terminal block hash") } @@ -426,10 +435,12 @@ func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.Execu func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) + data := api.localBlocks.get(payloadID) if data == nil { return nil, engine.UnknownPayload } + return data, nil } @@ -473,6 +484,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.Payloa defer api.newPayloadLock.Unlock() log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) + block, err := engine.ExecutableDataToBlock(params) if err != nil { log.Debug("Invalid NewPayload params", "params", params, "error", err) @@ -522,6 +534,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.Payloa log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd) return engine.INVALID_TERMINAL_BLOCK, nil } + if block.Time() <= parent.Time() { log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time()) return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil @@ -533,13 +546,16 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.Payloa if api.eth.SyncMode() != downloader.FullSync { return api.delayPayloadImport(block) } + if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { api.remoteBlocks.put(block.Hash(), block.Header()) log.Warn("State not available, ignoring new payload") return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil } + log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number) + if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil { log.Warn("NewPayloadV1: inserting block failed", "error", err) @@ -557,6 +573,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.Payloa merger.ReachTTD() api.eth.Downloader().Cancel() } + hash := block.Hash() return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil @@ -680,6 +697,7 @@ func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.Pa currentHash = latestValid.Hash() } } + errorMsg := err.Error() return engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: ¤tHash, ValidationError: &errorMsg} diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index d46abce09c..e8fc7f6a18 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -67,6 +67,7 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { config.TerminalTotalDifficultyPassed = true engine = beaconConsensus.NewFaker() } + genesis := &core.Genesis{ Config: &config, Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, @@ -82,6 +83,7 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { tx, _ := types.SignTx(types.NewTransaction(testNonce, common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"), big.NewInt(1), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), types.LatestSigner(&config), testKey) g.AddTx(tx) + testNonce++ } _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, n, generate) @@ -102,15 +104,18 @@ func TestEth2AssembleBlock(t *testing.T) { t.Skip("bor due to burn contract") genesis, blocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, blocks) defer n.Close() api := NewConsensusAPI(ethservice) signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID) + tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) if err != nil { t.Fatalf("error signing transaction, err=%v", err) } + ethservice.TxPool().AddLocal(tx) blockParams := engine.PayloadAttributes{ @@ -147,6 +152,7 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { t.Skip("bor due to burn contract") genesis, blocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, blocks[:9]) defer n.Close() @@ -167,6 +173,7 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { func TestSetHeadBeforeTotalDifficulty(t *testing.T) { genesis, blocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, blocks) defer n.Close() @@ -176,6 +183,7 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) { SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } + if resp, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { t.Errorf("fork choice updated should not error: %v", err) } else if resp.PayloadStatus.Status != engine.INVALID_TERMINAL_BLOCK.Status { @@ -245,6 +253,7 @@ func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan co if len(logsCh) != wantNew { t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew) } + if len(rmLogsCh) != wantRemoved { t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved) } @@ -252,6 +261,7 @@ func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan co for i := 0; i < len(logsCh); i++ { <-logsCh } + for i := 0; i < len(rmLogsCh); i++ { <-rmLogsCh } @@ -259,12 +269,15 @@ func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan co func TestInvalidPayloadTimestamp(t *testing.T) { genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() + var ( api = NewConsensusAPI(ethservice) parent = ethservice.BlockChain().CurrentBlock() ) + tests := []struct { time uint64 shouldErr bool @@ -291,6 +304,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) { SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } + _, err := api.ForkchoiceUpdatedV1(fcState, ¶ms) if test.shouldErr && err == nil { t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err) @@ -305,6 +319,7 @@ func TestEth2NewBlock(t *testing.T) { t.Skip("ETH2 in Bor") genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() @@ -318,6 +333,7 @@ func TestEth2NewBlock(t *testing.T) { // The event channels. newLogCh := make(chan []*types.Log, 10) rmLogsCh := make(chan core.RemovedLogsEvent, 10) + ethservice.BlockChain().SubscribeLogsEvent(newLogCh) ethservice.BlockChain().SubscribeRemovedLogsEvent(rmLogsCh) @@ -338,6 +354,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } + newResp, err := api.NewPayloadV1(*execData) switch { @@ -348,6 +365,7 @@ func TestEth2NewBlock(t *testing.T) { case ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1: t.Fatalf("Chain head shouldn't be updated") } + checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) fcState := engine.ForkchoiceStateV1{ @@ -363,6 +381,7 @@ func TestEth2NewBlock(t *testing.T) { if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want { t.Fatalf("Chain head should be updated, have %d want %d", have, want) } + checkLogEvents(t, newLogCh, rmLogsCh, 1, 0) parent = block @@ -411,6 +430,7 @@ func TestEth2NewBlock(t *testing.T) { if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() { t.Fatalf("Chain head should be updated") } + parent, head = block, block.NumberU64() } } @@ -476,13 +496,16 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) } ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256} + ethservice, err := eth.New(n, ethcfg) if err != nil { t.Fatal("can't create eth service:", err) } + if err := n.Start(); err != nil { t.Fatal("can't start node:", err) } + if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil { n.Close() t.Fatal("can't import test blocks:", err) @@ -496,8 +519,10 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) func TestFullAPI(t *testing.T) { genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() + var ( parent = ethservice.BlockChain().CurrentBlock() // This EVM code generates a log when the contract is created. @@ -516,6 +541,7 @@ func TestFullAPI(t *testing.T) { func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal) []*types.Header { api := NewConsensusAPI(ethservice) + var blocks []*types.Header for i := 0; i < n; i++ { @@ -555,6 +581,7 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He if ethservice.BlockChain().CurrentFinalBlock().Number.Uint64() != payload.Number-1 { t.Fatal("Finalized block should be updated") } + parent = ethservice.BlockChain().CurrentBlock() blocks = append(blocks, parent) } @@ -564,6 +591,7 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He func TestExchangeTransitionConfig(t *testing.T) { genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() @@ -574,6 +602,7 @@ func TestExchangeTransitionConfig(t *testing.T) { TerminalBlockHash: common.Hash{}, TerminalBlockNumber: 0, } + if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil { t.Fatal("expected error on invalid config, invalid ttd") } @@ -1074,10 +1103,13 @@ func TestSimultaneousNewBlock(t *testing.T) { testErr error errMu sync.Mutex ) + wg.Add(10) + for ii := 0; ii < 10; ii++ { go func() { defer wg.Done() + if newResp, err := api.NewPayloadV1(*execData); err != nil { errMu.Lock() testErr = fmt.Errorf("Failed to insert block: %w", err) @@ -1090,6 +1122,7 @@ func TestSimultaneousNewBlock(t *testing.T) { }() } wg.Wait() + if testErr != nil { t.Fatal(testErr) } @@ -1116,11 +1149,13 @@ func TestSimultaneousNewBlock(t *testing.T) { testErr error errMu sync.Mutex ) + wg.Add(10) // Do each FCU 10 times for ii := 0; ii < 10; ii++ { go func() { defer wg.Done() + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { errMu.Lock() testErr = fmt.Errorf("Failed to insert block: %w", err) @@ -1129,6 +1164,7 @@ func TestSimultaneousNewBlock(t *testing.T) { }() } wg.Wait() + if testErr != nil { t.Fatal(testErr) } diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go index 07f20b4cc3..8fb2be5c46 100644 --- a/eth/catalyst/queue.go +++ b/eth/catalyst/queue.go @@ -81,10 +81,12 @@ func (q *payloadQueue) get(id engine.PayloadID) *engine.ExecutionPayloadEnvelope if item == nil { return nil // no more items } + if item.id == id { return item.payload.Resolve() } } + return nil } @@ -149,9 +151,11 @@ func (q *headerQueue) get(hash common.Hash) *types.Header { if item == nil { return nil // no more items } + if item.hash == hash { return item.header } } + return nil } diff --git a/eth/downloader/beaconsync.go b/eth/downloader/beaconsync.go index 6c01733b32..80aab21822 100644 --- a/eth/downloader/beaconsync.go +++ b/eth/downloader/beaconsync.go @@ -87,6 +87,7 @@ func (b *beaconBackfiller) resume() { b.lock.Unlock() return } + b.filling = true b.filled = nil b.started = make(chan struct{}) @@ -132,6 +133,7 @@ func (b *beaconBackfiller) setMode(mode SyncMode) { if !updated || !filling { return } + log.Error("Downloader sync mode changed mid-run", "old", mode.String(), "new", mode.String()) b.suspend() b.resume() @@ -183,6 +185,7 @@ func (d *Downloader) beaconSync(mode SyncMode, head *types.Header, final *types. if err := d.skeleton.Sync(head, final, force); err != nil { return err } + return nil } @@ -203,6 +206,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) { default: chainHead = d.lightchain.CurrentHeader() } + number := chainHead.Number.Uint64() // Retrieve the skeleton bounds and ensure they are linked to the local chain @@ -214,6 +218,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) { log.Error("Failed to retrieve beacon bounds", "err", err) return 0, err } + var linked bool switch d.getMode() { case FullSync: @@ -223,6 +228,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) { default: linked = d.blockchain.HasHeader(beaconTail.ParentHash, beaconTail.Number.Uint64()-1) } + if !linked { // This is a programming error. The chain backfiller was called with a // tail that's not linked to the local chain. Whilst this should never @@ -242,6 +248,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) { log.Warn("Beacon head lower than local chain", "beacon", number, "local", end) end = number } + for start+1 < end { // Split our chain interval in two, and request the hash to cross check check := (start + end) / 2 @@ -250,6 +257,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) { n := h.Number.Uint64() var known bool + switch d.getMode() { case FullSync: known = d.blockchain.HasBlock(h.Hash(), n) @@ -258,12 +266,15 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) { default: known = d.lightchain.HasHeader(h.Hash(), n) } + if !known { end = check continue } + start = check } + return start, nil } @@ -291,6 +302,7 @@ func (d *Downloader) fetchBeaconHeaders(from uint64) error { localHeaders = d.readHeaderRange(tail, int(count)) log.Warn("Retrieved beacon headers from local", "from", from, "count", count) } + for { // Some beacon headers might have appeared since the last cycle, make // sure we're always syncing to all available ones @@ -340,6 +352,7 @@ func (d *Downloader) fetchBeaconHeaders(from uint64) error { headers = make([]*types.Header, 0, maxHeadersProcess) hashes = make([]common.Hash, 0, maxHeadersProcess) ) + for i := 0; i < maxHeadersProcess && from <= head.Number.Uint64(); i++ { header := d.skeleton.Header(from) @@ -360,6 +373,7 @@ func (d *Downloader) fetchBeaconHeaders(from uint64) error { hashes = append(hashes, headers[i].Hash()) from++ } + if len(headers) > 0 { log.Trace("Scheduling new beacon headers", "count", len(headers), "from", from-uint64(len(headers))) select { diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 1e926ddac1..9fc3eb31ff 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -227,6 +227,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain Bl if lightchain == nil { lightchain = chain } + dl := &Downloader{ stateDB: stateDb, mux: mux, @@ -247,6 +248,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain Bl dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success)) go dl.stateFetcher() + return dl } @@ -264,6 +266,7 @@ func (d *Downloader) Progress() ethereum.SyncProgress { current := uint64(0) mode := d.getMode() + switch { case d.blockchain != nil && mode == FullSync: current = d.blockchain.CurrentBlock().Number.Uint64() @@ -274,6 +277,7 @@ func (d *Downloader) Progress() ethereum.SyncProgress { default: log.Error("Unknown downloader chain/mode combo", "light", d.lightchain != nil, "full", d.blockchain != nil, "mode", mode) } + progress, pending := d.SnapSyncer.Progress() return ethereum.SyncProgress{ @@ -310,11 +314,14 @@ func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { } else { logger = log.New("peer", id[:8]) } + 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) return err } + return nil } @@ -335,11 +342,14 @@ func (d *Downloader) UnregisterPeer(id string) error { } else { logger = log.New("peer", id[:8]) } + logger.Trace("Unregistering sync peer") + if err := d.peers.Unregister(id); err != nil { logger.Error("Failed to unregister sync peer", "err", err) return err } + d.queue.Revoke(id) return nil @@ -354,11 +364,13 @@ func (d *Downloader) LegacySync(id string, head common.Hash, td, ttd *big.Int, m 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) || errors.Is(err, whitelist.ErrCheckpointMismatch) { log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err) + if d.dropPeer == nil { // The dropPeer method is nil when `--copydb` is used for a local copy. // Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored @@ -366,13 +378,16 @@ func (d *Downloader) LegacySync(id string, head common.Hash, td, ttd *big.Int, m } else { d.dropPeer(id) } + return err } + if errors.Is(err, ErrMergeTransition) { return err // This is an expected fault, don't keep printing it in a spin-loop } log.Warn("Synchronisation failed, retrying", "err", err) + return err } @@ -408,6 +423,7 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int, if d.notified.CompareAndSwap(false, true) { log.Info("Block synchronisation started") } + if mode == SnapSync { // Snap sync uses the snapshot namespace to store potentially flakey data until // sync completely heals and finishes. Pause snapshot maintenance in the mean- @@ -426,6 +442,7 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int, default: } } + for empty := false; !empty; { select { case <-d.headerProcCh: @@ -452,9 +469,11 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int, return errUnknownPeer } } + if beaconPing != nil { close(beaconPing) } + return d.syncWithPeer(p, hash, td, ttd, beaconMode) } @@ -466,6 +485,7 @@ func (d *Downloader) getMode() SyncMode { // specified peer and head hash. func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *big.Int, beaconMode bool) (err error) { d.mux.Post(StartEvent{}) + defer func() { // reset on error if err != nil { @@ -475,6 +495,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * d.mux.Post(DoneEvent{latest}) } }() + mode := d.getMode() if !beaconMode { @@ -482,6 +503,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * } else { log.Debug("Backfilling with the network", "mode", mode) } + defer func(start time.Time) { log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start))) }(time.Now()) @@ -500,6 +522,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * if err != nil { return err } + if latest.Number.Uint64() > uint64(fsMinFullBlocks) { number := latest.Number.Uint64() - uint64(fsMinFullBlocks) @@ -510,6 +533,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * if number < oldest.Number.Uint64() { count := int(oldest.Number.Uint64() - number) // it's capped by fsMinFullBlocks headers := d.readHeaderRange(oldest, count) + if len(headers) == count { pivot = headers[len(headers)-1] log.Warn("Retrieved pivot header from local", "number", pivot.Number, "hash", pivot.Hash(), "latest", latest.Number, "oldest", oldest.Number) @@ -532,6 +556,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * if mode == SnapSync && pivot == nil { pivot = d.blockchain.CurrentBlock() } + height := latest.Number.Uint64() var origin uint64 @@ -548,10 +573,12 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * return err } } + d.syncStatsLock.Lock() if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin { d.syncStatsChainOrigin = origin } + d.syncStatsChainHeight = height d.syncStatsLock.Unlock() @@ -571,9 +598,11 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * } d.committed.Store(true) + if mode == SnapSync && pivot.Number.Uint64() != 0 { d.committed.Store(false) } + if mode == SnapSync { // Set the ancient data limitation. If we are running snap sync, all block // data older than ancientLimit will be written to the ancient store. More @@ -611,12 +640,14 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * d.ancientLimit = 0 } } + frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. // If a part of blockchain data has already been written into active store, // disable the ancient style insertion explicitly. if origin >= frozen && frozen != 0 { d.ancientLimit = 0 + log.Info("Disabling direct-ancient mode", "origin", origin, "ancient", frozen-1) } else if d.ancientLimit > 0 { log.Debug("Enabling direct-ancient mode", "ancient", d.ancientLimit) @@ -630,9 +661,11 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * } // Initiate the sync using a concurrent header and content retrieval algorithm d.queue.Prepare(origin+1, mode) + if d.syncInitHook != nil { d.syncInitHook(origin, height) } + var headerFetcher func() error if !beaconMode { // In legacy mode, headers are retrieved from the network @@ -641,12 +674,14 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * // In beacon mode, headers are served by the skeleton syncer headerFetcher = func() error { return d.fetchBeaconHeaders(origin + 1) } } + fetchers := []func() error{ headerFetcher, // Headers are always retrieved func() error { return d.fetchBodies(origin+1, beaconMode) }, // Bodies are retrieved during normal and snap sync func() error { return d.fetchReceipts(origin+1, beaconMode) }, // Receipts are retrieved during snap sync func() error { return d.processHeaders(origin+1, td, ttd, beaconMode) }, } + if mode == SnapSync { d.pivotLock.Lock() d.pivotHeader = pivot @@ -656,6 +691,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * } else if mode == FullSync { fetchers = append(fetchers, func() error { return d.processFullSyncContent(ttd, beaconMode) }) } + return d.spawnSync(fetchers) } @@ -664,12 +700,15 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * func (d *Downloader) spawnSync(fetchers []func() error) error { errc := make(chan error, len(fetchers)) d.cancelWg.Add(len(fetchers)) + for _, fn := range fetchers { fn := fn + go func() { defer d.cancelWg.Done(); errc <- fn() }() } // Wait for the first error, then terminate the others. var err error + for i := 0; i < len(fetchers); i++ { if i == len(fetchers)-1 { // Close the queue when all fetchers have exited. @@ -677,12 +716,14 @@ func (d *Downloader) spawnSync(fetchers []func() error) error { // it has processed the queue. d.queue.Close() } + if err = <-errc; err != nil && err != errCanceled { break } } d.queue.Close() d.Cancel() + return err } @@ -734,14 +775,17 @@ func (d *Downloader) Terminate() { // a remote peer. func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *types.Header, err error) { p.log.Debug("Retrieving remote chain head") + mode := d.getMode() // Request the advertised remote head block and wait for the response latest, _ := p.peer.Head() + fetch := 1 if mode == SnapSync { fetch = 2 // head + pivot headers } + headers, hashes, err := d.fetchHeadersByHash(p, latest, fetch, fsMinFullBlocks-1, true) if err != nil { return nil, nil, err @@ -757,11 +801,14 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty if (mode == SnapSync || mode == LightSync) && head.Number.Uint64() < d.checkpoint { return nil, nil, fmt.Errorf("%w: remote head %d below checkpoint %d", errUnsyncedPeer, head.Number, d.checkpoint) } + if len(headers) == 1 { if mode == SnapSync && head.Number.Uint64() > uint64(fsMinFullBlocks) { return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer) } + p.log.Debug("Remote head identified, no pivot", "number", head.Number, "hash", hashes[0]) + return head, nil, nil } // At this point we have 2 headers in total and the first is the @@ -770,6 +817,7 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty if pivot.Number.Uint64() != head.Number.Uint64()-uint64(fsMinFullBlocks) { return nil, nil, fmt.Errorf("%w: remote pivot %d != requested %d", errInvalidChain, pivot.Number, head.Number.Uint64()-uint64(fsMinFullBlocks)) } + return head, pivot, nil } @@ -803,11 +851,14 @@ func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, ui if requestBottom < 0 { requestBottom = 0 } + totalSpan := requestHead - requestBottom + span := 1 + totalSpan/MaxCount if span < 2 { span = 2 } + if span > 16 { span = 16 } @@ -816,14 +867,18 @@ func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, ui if count > MaxCount { count = MaxCount } + if count < 2 { count = 2 } + from = requestHead - (count-1)*span if from < 0 { from = 0 } + max := from + (count-1)*span + return int64(from), count, span - 1, uint64(max) } @@ -853,6 +908,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) localHeight uint64 remoteHeight = remoteHeader.Number.Uint64() ) + mode := d.getMode() switch mode { case FullSync: @@ -862,6 +918,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) default: localHeight = d.lightchain.CurrentHeader().Number.Uint64() } + p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight) // Recap floor value for binary search @@ -869,6 +926,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) if d.getMode() == LightSync { maxForkAncestry = lightMaxForkAncestry } + if localHeight >= maxForkAncestry { // We're above the max reorg threshold, find the earliest fork point floor = int64(localHeight - maxForkAncestry) @@ -884,6 +942,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) if floor >= int64(d.genesis)-1 { break } + header = d.lightchain.GetHeaderByHash(header.ParentHash) } } @@ -909,6 +968,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) if err != nil { return 0, err } + return ancestor, nil } @@ -916,6 +976,7 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight) p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip) + headers, hashes, err := d.fetchHeadersByNumber(p, uint64(from), count, skip, false) if err != nil { return 0, err @@ -947,6 +1008,7 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re n := headers[i].Number.Uint64() var known bool + switch mode { case FullSync: known = d.blockchain.HasBlock(h, n) @@ -955,6 +1017,7 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re default: known = d.lightchain.HasHeader(h, n) } + if known { number, hash = n, h break @@ -966,9 +1029,12 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re p.log.Warn("Ancestor below allowance", "number", number, "hash", hash, "allowance", floor) return 0, errInvalidAncestor } + p.log.Debug("Found common ancestor", "number", number, "hash", hash) + return number, nil } + return 0, errNoAncestorFound } @@ -980,6 +1046,7 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, if floor > 0 { start = uint64(floor) } + p.log.Trace("Binary searching for common ancestor", "start", start, "end", end) for start+1 < end { @@ -1000,6 +1067,7 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, n := headers[0].Number.Uint64() var known bool + switch mode { case FullSync: known = d.blockchain.HasBlock(h, n) @@ -1008,15 +1076,18 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, default: known = d.lightchain.HasHeader(h, n) } + if !known { end = check continue } + header := d.lightchain.GetHeaderByHash(h) // Independent of sync mode, header surely exists if header.Number.Uint64() != check { p.log.Warn("Received non requested header", "number", header.Number, "hash", header.Hash(), "request", check) return 0, fmt.Errorf("%w: non-requested header (%d)", errBadPeer, header.Number) } + start = check hash = h } @@ -1025,7 +1096,9 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, p.log.Warn("Ancestor below allowance", "number", start, "hash", hash, "allowance", floor) return 0, errInvalidAncestor } + p.log.Debug("Found common ancestor", "number", start, "hash", hash) + return start, nil } @@ -1048,6 +1121,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e ancestor = from mode = d.getMode() ) + for { // Pull the next batch of headers, it either: // - Pivot check to see if the chain moved too far @@ -1058,6 +1132,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e hashes []common.Hash err error ) + switch { case pivoting: d.pivotLock.RLock() @@ -1075,6 +1150,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e p.log.Trace("Fetching full headers", "count", MaxHeaderFetch, "from", from) headers, hashes, err = d.fetchHeadersByNumber(p, from, MaxHeaderFetch, 0, false) } + switch err { case nil: // Headers retrieved, continue with processing @@ -1099,6 +1175,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e case d.headerProcCh <- nil: case <-d.cancelCh: } + return fmt.Errorf("%w: header request failed: %v", errBadPeer, err) } // If the pivot is being checked, move if it became stale and run the real retrieval @@ -1116,10 +1193,12 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e log.Warn("Peer sent invalid next pivot", "have", have, "want", want) return fmt.Errorf("%w: next pivot number %d != requested %d", errInvalidChain, have, want) } + if have, want := headers[1].Number.Uint64(), pivot+2*uint64(fsMinFullBlocks)-8; have != want { log.Warn("Peer sent invalid pivot confirmer", "have", have, "want", want) return fmt.Errorf("%w: next pivot confirmer number %d != requested %d", errInvalidChain, have, want) } + log.Warn("Pivot seemingly stale, moving", "old", pivot, "new", headers[0].Number) pivot = headers[0].Number.Uint64() @@ -1134,6 +1213,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e } // Disable the pivot check and fetch the next batch of headers pivoting = false + continue } // If the skeleton's finished, pull any remaining head headers directly from the origin @@ -1143,8 +1223,11 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e p.log.Warn("Peer withheld skeleton headers", "advertised", head, "withheld", from+uint64(MaxHeaderFetch)-1) return fmt.Errorf("%w: withheld skeleton headers: advertised %d, withheld #%d", errStallingPeer, head, from+uint64(MaxHeaderFetch)-1) } + p.log.Debug("No skeleton, fetching headers directly") + skeleton = false + continue } // If no more headers are inbound, notify the content fetchers and return @@ -1170,12 +1253,14 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e } // If we received a skeleton batch, resolve internals concurrently var progressed bool + if skeleton { filled, hashset, proced, err := d.fillHeaderSkeleton(from, headers) if err != nil { p.log.Debug("Skeleton chain invalid", "err", err) return fmt.Errorf("%w: %v", errInvalidChain, err) } + headers = filled[proced:] hashes = hashset[proced:] @@ -1213,6 +1298,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e if delay > n { delay = n } + headers = headers[:n-delay] hashes = hashes[:n-delay] } @@ -1241,6 +1327,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) e case <-d.cancelCh: return errCanceled } + from += uint64(len(headers)) } // If we're still skeleton filling snap sync, check pivot staleness @@ -1268,10 +1355,12 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ( if err != nil { log.Debug("Skeleton fill failed", "err", err) } + filled, hashes, proced := d.queue.RetrieveHeaders() if err == nil { log.Debug("Skeleton fill succeeded", "filled", len(filled), "processed", proced) } + return filled, hashes, proced, err } @@ -1280,9 +1369,11 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ( // and also periodically checking for timeouts. func (d *Downloader) fetchBodies(from uint64, beaconMode bool) error { log.Debug("Downloading block bodies", "origin", from) + err := d.concurrentFetch((*bodyQueue)(d), beaconMode) log.Debug("Block body download terminated", "err", err) + return err } @@ -1291,9 +1382,11 @@ func (d *Downloader) fetchBodies(from uint64, beaconMode bool) error { // and also periodically checking for timeouts. func (d *Downloader) fetchReceipts(from uint64, beaconMode bool) error { log.Debug("Downloading receipts", "origin", from) + err := d.concurrentFetch((*receiptQueue)(d), beaconMode) log.Debug("Receipt download terminated", "err", err) + return err } @@ -1307,6 +1400,7 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode rollbackErr error mode = d.getMode() ) + defer func() { if rollback > 0 { lastHeader, lastFastBlock, lastBlock := d.lightchain.CurrentHeader().Number, common.Big0, common.Big0 @@ -1314,15 +1408,18 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode lastFastBlock = d.blockchain.CurrentSnapBlock().Number lastBlock = d.blockchain.CurrentBlock().Number } + if err := d.lightchain.SetHead(rollback - 1); err != nil { // -1 to target the parent of the first uncertain block // We're already unwinding the stack, only print the error to make it more visible log.Error("Failed to roll back chain segment", "head", rollback-1, "err", err) } + curFastBlock, curBlock := common.Big0, common.Big0 if mode != LightSync { curFastBlock = d.blockchain.CurrentSnapBlock().Number curBlock = d.blockchain.CurrentBlock().Number } + log.Warn("Rolled back chain segment", "header", fmt.Sprintf("%d->%d", lastHeader, d.lightchain.CurrentHeader().Number), "snap", fmt.Sprintf("%d->%d", lastFastBlock, curFastBlock), @@ -1386,12 +1483,14 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode } // Disable any rollback and return rollback = 0 + return nil } // Otherwise split the chunk of headers into batches and process them headers, hashes := task.headers, task.hashes gotHeaders = true + for len(headers) > 0 { // Terminate if something failed in between processing chunks select { @@ -1405,6 +1504,7 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode if limit > len(headers) { limit = len(headers) } + chunkHeaders := headers[:limit] chunkHashes := hashes[:limit] @@ -1430,6 +1530,7 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode rejected []*types.Header td *big.Int ) + if !beaconMode && ttd != nil { td = d.blockchain.GetTd(chunkHeaders[0].ParentHash, chunkHeaders[0].Number.Uint64()-1) if td == nil { @@ -1437,6 +1538,7 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode log.Error("Failed to retrieve parent header TD", "number", chunkHeaders[0].Number.Uint64()-1, "hash", chunkHeaders[0].ParentHash) return fmt.Errorf("%w: parent TD missing", errInvalidChain) } + for i, header := range chunkHeaders { td = new(big.Int).Add(td, header.Difficulty) if td.Cmp(ttd) >= 0 { @@ -1450,10 +1552,12 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode } else { chunkHeaders, rejected = chunkHeaders[:i], chunkHeaders[i:] } + break } } } + if len(chunkHeaders) > 0 { if n, err := d.lightchain.InsertHeaderChain(chunkHeaders, frequency); err != nil { rollbackErr = err @@ -1462,7 +1566,9 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode if (mode == SnapSync || frequency > 1) && n > 0 && rollback == 0 { rollback = chunkHeaders[0].Number.Uint64() } + log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err) + return fmt.Errorf("%w: %v", errInvalidChain, err) } // All verifications passed, track all headers within the allowed limits @@ -1475,11 +1581,13 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode } } } + if len(rejected) != 0 { // Merge threshold reached, stop importing, but don't roll back rollback = 0 log.Info("Legacy sync reached merge threshold", "number", rejected[0].Number, "hash", rejected[0].Hash(), "td", td, "ttd", ttd) + return ErrMergeTransition } } @@ -1501,6 +1609,7 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode return fmt.Errorf("%w: stale headers", errBadPeer) } } + headers = headers[limit:] hashes = hashes[limit:] origin += uint64(limit) @@ -1530,6 +1639,7 @@ func (d *Downloader) processFullSyncContent(ttd *big.Int, beaconMode bool) error if len(results) == 0 { return nil } + if d.chainInsertHook != nil { d.chainInsertHook(results) } @@ -1540,6 +1650,7 @@ func (d *Downloader) processFullSyncContent(ttd *big.Int, beaconMode bool) error rejected []*fetchResult td *big.Int ) + if !beaconMode && ttd != nil { td = d.blockchain.GetTd(results[0].Header.ParentHash, results[0].Header.Number.Uint64()-1) if td == nil { @@ -1547,6 +1658,7 @@ func (d *Downloader) processFullSyncContent(ttd *big.Int, beaconMode bool) error log.Error("Failed to retrieve parent block TD", "number", results[0].Header.Number.Uint64()-1, "hash", results[0].Header.ParentHash) return fmt.Errorf("%w: parent TD missing", errInvalidChain) } + for i, result := range results { td = new(big.Int).Add(td, result.Header.Difficulty) if td.Cmp(ttd) >= 0 { @@ -1560,13 +1672,16 @@ func (d *Downloader) processFullSyncContent(ttd *big.Int, beaconMode bool) error } else { results, rejected = results[:i], results[i:] } + break } } } + if err := d.importBlockResults(results); err != nil { return err } + if len(rejected) != 0 { log.Info("Legacy sync reached merge threshold", "number", rejected[0].Header.Number, "hash", rejected[0].Header.Hash(), "td", td, "ttd", ttd) return ErrMergeTransition @@ -1590,6 +1705,7 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error { "firstnum", first.Number, "firsthash", first.Hash(), "lastnum", last.Number, "lasthash", last.Hash(), ) + blocks := make([]*types.Block, len(results)) for i, result := range results { blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals) @@ -1626,6 +1742,7 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error { return fmt.Errorf("%w: %v", errInvalidChain, err) } + return nil } @@ -1658,6 +1775,7 @@ func (d *Downloader) processSnapSyncContent() error { oldPivot *fetchResult // Locked in pivot block, might change eventually oldTail []*fetchResult // Downloaded content after the pivot ) + for { // Wait for the next batch of downloaded data to be available, and if the pivot // block became stale, move the goalpost @@ -1676,6 +1794,7 @@ func (d *Downloader) processSnapSyncContent() error { default: } } + if d.chainInsertHook != nil { d.chainInsertHook(results) } @@ -1721,10 +1840,12 @@ func (d *Downloader) processSnapSyncContent() error { rawdb.WriteLastPivotNumber(d.stateDB, pivot.Number.Uint64()) } } + P, beforeP, afterP := splitAroundPivot(pivot.Number.Uint64(), results) if err := d.commitSnapSyncData(beforeP, sync); err != nil { return err } + if P != nil { // If new pivot block found, cancel old state retrieval and restart if oldPivot != P { @@ -1732,6 +1853,7 @@ func (d *Downloader) processSnapSyncContent() error { sync = d.syncState(P.Header.Root) go closeOnErr(sync) + oldPivot = P } // Wait for completion, occasionally checking for pivot staleness @@ -1740,9 +1862,11 @@ func (d *Downloader) processSnapSyncContent() error { if sync.err != nil { return sync.err } + if err := d.commitPivotBlock(P); err != nil { return err } + oldPivot = nil case <-time.After(time.Second): @@ -1761,6 +1885,7 @@ func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, bef if len(results) == 0 { return nil, nil, nil } + if lastNum := results[len(results)-1].Header.Number.Uint64(); lastNum < pivot { // the pivot is somewhere in the future return nil, results, nil @@ -1768,6 +1893,7 @@ func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, bef // This can also be optimized, but only happens very seldom for _, result := range results { num := result.Header.Number.Uint64() + switch { case num < pivot: before = append(before, result) @@ -1777,6 +1903,7 @@ func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, bef after = append(after, result) } } + return p, before, after } @@ -1800,16 +1927,20 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state "firstnum", first.Number, "firsthash", first.Hash(), "lastnumn", last.Number, "lasthash", last.Hash(), ) + blocks := make([]*types.Block, len(results)) receipts := make([]types.Receipts, len(results)) + for i, result := range results { blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals) receipts[i] = result.Receipts } + if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil { log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) return fmt.Errorf("%w: %v", errInvalidChain, err) } + return nil } @@ -1821,11 +1952,13 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error { if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{result.Receipts}, d.ancientLimit); err != nil { return err } + if err := d.blockchain.SnapSyncCommitHead(block.Hash()); err != nil { return err } d.committed.Store(true) + return nil } @@ -1838,6 +1971,7 @@ func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) erro if err != nil { return err } + return d.SnapSyncer.OnAccounts(peer, packet.ID, hashes, accounts, packet.Proof) case *snap.StorageRangesPacket: diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 128bedcce9..11ee4c9809 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -65,6 +65,7 @@ func newTester(t *testing.T) *downloadTester { // newTester creates a new downloader test mocker. func newTesterWithNotification(t *testing.T, success func()) *downloadTester { freezer := t.TempDir() + db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false) if err != nil { panic(err) @@ -79,10 +80,12 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester { Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, BaseFee: big.NewInt(params.InitialBaseFee), } + chain, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if err != nil { panic(err) } + tester := &downloadTester{ freezer: freezer, chain: chain, @@ -91,6 +94,7 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester { //nolint: staticcheck tester.downloader = New(0, db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, nil, whitelist.NewService(10)) + return tester } @@ -123,6 +127,7 @@ func (dl *downloadTester) sync(id string, td *big.Int, mode SyncMode) error { // Downloader is still accepting packets, can block a peer up panic("downloader active post sync cycle") // panic will be caught by tester } + return err } @@ -142,9 +147,11 @@ func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block if err := dl.downloader.RegisterPeer(id, version, peer); err != nil { panic(err) } + if err := dl.downloader.SnapSyncer.Register(peer); err != nil { panic(err) } + return peer } @@ -175,13 +182,16 @@ func (dlp *downloadTesterPeer) Head() (common.Hash, *big.Int) { func unmarshalRlpHeaders(rlpdata []rlp.RawValue) []*types.Header { var headers = make([]*types.Header, len(rlpdata)) + for i, data := range rlpdata { var h types.Header if err := rlp.DecodeBytes(data, &h); err != nil { panic(err) } + headers[i] = &h } + return headers } @@ -208,6 +218,7 @@ func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount i } } } + hashes := make([]common.Hash, len(headers)) for i, header := range headers { hashes[i] = header.Hash() @@ -223,9 +234,11 @@ func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount i Time: 1, Done: make(chan error, 1), // Ignore the returned status } + go func() { sink <- res }() + return req, nil } @@ -252,6 +265,7 @@ func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, } } } + hashes := make([]common.Hash, len(headers)) for i, header := range headers { hashes[i] = header.Hash() @@ -267,9 +281,11 @@ func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, Time: 1, Done: make(chan error, 1), // Ignore the returned status } + go func() { sink <- res }() + return req, nil } @@ -284,16 +300,19 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et bodies[i] = new(eth.BlockBody) rlp.DecodeBytes(blob, bodies[i]) } + var ( txsHashes = make([]common.Hash, len(bodies)) uncleHashes = make([]common.Hash, len(bodies)) withdrawalHashes = make([]common.Hash, len(bodies)) ) + hasher := trie.NewStackTrie(nil) for i, body := range bodies { txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher) uncleHashes[i] = types.CalcUncleHash(body.Uncles) } + req := ð.Request{ Peer: dlp.id, } @@ -304,9 +323,11 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et Time: 1, Done: make(chan error, 1), // Ignore the returned status } + go func() { sink <- res }() + return req, nil } @@ -320,11 +341,14 @@ func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan * for i, blob := range blobs { rlp.DecodeBytes(blob, &receipts[i]) } + hasher := trie.NewStackTrie(nil) hashes = make([]common.Hash, len(receipts)) + for i, receipt := range receipts { hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher) } + req := ð.Request{ Peer: dlp.id, } @@ -335,9 +359,11 @@ func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan * Time: 1, Done: make(chan error, 1), // Ignore the returned status } + go func() { sink <- res }() + return req, nil } @@ -368,6 +394,7 @@ func (dlp *downloadTesterPeer) RequestAccountRange(id uint64, root, origin, limi hashes, accounts, _ := res.Unpack() go dlp.dl.downloader.SnapSyncer.OnAccounts(dlp, id, hashes, accounts, proofs) + return nil } @@ -395,6 +422,7 @@ func (dlp *downloadTesterPeer) RequestStorageRanges(id uint64, root common.Hash, hashes, slots := res.Unpack() go dlp.dl.downloader.SnapSyncer.OnStorage(dlp, id, hashes, slots, proofs) + return nil } @@ -405,8 +433,10 @@ func (dlp *downloadTesterPeer) RequestByteCodes(id uint64, hashes []common.Hash, Hashes: hashes, Bytes: bytes, } + codes := snap.ServiceGetByteCodesQuery(dlp.chain, req) go dlp.dl.downloader.SnapSyncer.OnByteCodes(dlp, id, codes) + return nil } @@ -419,8 +449,10 @@ func (dlp *downloadTesterPeer) RequestTrieNodes(id uint64, root common.Hash, pat Paths: paths, Bytes: bytes, } + nodes, _ := snap.ServiceGetTrieNodesQuery(dlp.chain, req, time.Now()) go dlp.dl.downloader.SnapSyncer.OnTrieNodes(dlp, id, nodes) + return nil } @@ -439,6 +471,7 @@ func assertOwnChain(t *testing.T, tester *downloadTester, length int) { if tester.downloader.getMode() == LightSync { blocks, receipts = 1, 1 } + if hs := int(tester.chain.CurrentHeader().Number.Uint64()) + 1; hs != headers { t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers) } @@ -489,6 +522,7 @@ func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("peer", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chain.blocks)) } @@ -538,11 +572,13 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { tester.lock.RLock() retrieved := int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1 tester.lock.RUnlock() + if retrieved >= targetBlocks+1 { break } // Wait a bit for sync to throttle itself var cached, frozen int + for start := time.Now(); time.Since(start) < 3*time.Second; { time.Sleep(25 * time.Millisecond) @@ -570,6 +606,7 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { tester.lock.RLock() retrieved = int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1 tester.lock.RUnlock() + if cached != blockCacheMaxItems && cached != blockCacheMaxItems-reorgProtHeaderDelay && retrieved+cached+frozen != targetBlocks+1 && retrieved+cached+frozen != targetBlocks+1-reorgProtHeaderDelay { t.Fatalf("block count mismatch: have %v, want %v (owned %v, blocked %v, target %v)", cached, blockCacheMaxItems, retrieved, frozen, targetBlocks+1) } @@ -581,6 +618,7 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { } // Check that we haven't pulled more blocks than available assertOwnChain(t, tester, targetBlocks+1) + if err := <-errc; err != nil { t.Fatalf("block synchronization failed: %v", err) } @@ -626,12 +664,14 @@ func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("fork A", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chainA.blocks)) // Synchronise with the second peer and make sure that fork is pulled too if err := tester.sync("fork B", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chainB.blocks)) } @@ -675,12 +715,14 @@ func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("light", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chainA.blocks)) // Synchronise with the second peer and make sure that fork is pulled too if err := tester.sync("heavy", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chainB.blocks)) } @@ -718,6 +760,7 @@ func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { chainA := testChainForkLightA chainB := testChainForkLightB + tester.newPeer("original", protocol, chainA.blocks[1:]) tester.newPeer("rewriter", protocol, chainB.blocks[1:]) @@ -725,6 +768,7 @@ func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("original", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chainA.blocks)) // Synchronise with the second peer and ensure that the fork is rejected to being too old @@ -768,12 +812,14 @@ func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Create a long enough forked chain chainA := testChainForkLightA chainB := testChainForkHeavy + tester.newPeer("original", protocol, chainA.blocks[1:]) // Synchronise with the peer and make sure all blocks were retrieved if err := tester.sync("original", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chainA.blocks)) tester.newPeer("heavy-rewriter", protocol, chainB.blocks[1:]) @@ -818,6 +864,7 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) { // Make sure canceling works with a pristine downloader tester.downloader.Cancel() + if !tester.downloader.queue.Idle() { t.Errorf("download queue not idle") } @@ -825,7 +872,9 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("peer", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + tester.downloader.Cancel() + if !tester.downloader.queue.Idle() { t.Errorf("download queue not idle") } @@ -869,9 +918,11 @@ func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { id := fmt.Sprintf("peer #%d", i) tester.newPeer(id, protocol, chain.shorten(len(chain.blocks) / (i + 1)).blocks[1:]) } + if err := tester.sync("peer #0", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chain.blocks)) } @@ -917,6 +968,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chain.blocks)) // Check that no peers have been dropped off @@ -965,6 +1017,7 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { // Instrument the downloader to signal body requests var bodiesHave, receiptsHave atomic.Int32 + tester.downloader.bodyFetchHook = func(headers []*types.Header) { bodiesHave.Add(int32(len(headers))) } @@ -975,15 +1028,18 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("peer", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chain.blocks)) // Validate the number of block bodies that should have been requested bodiesNeeded, receiptsNeeded := 0, 0 + for _, block := range chain.blocks[1:] { if mode != LightSync && (len(block.Transactions()) > 0 || len(block.Uncles()) > 0) { bodiesNeeded++ } } + for _, block := range chain.blocks[1:] { if mode == SnapSync && len(block.Transactions()) > 0 { receiptsNeeded++ @@ -1040,9 +1096,11 @@ func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { } // Synchronise with the valid peer and make sure sync succeeds tester.newPeer("valid", protocol, chain.blocks[1:]) + if err := tester.sync("valid", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chain.blocks)) } @@ -1088,9 +1146,11 @@ func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { } // Synchronise with the valid peer and make sure sync succeeds tester.newPeer("valid", protocol, chain.blocks[1:]) + if err := tester.sync("valid", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chain.blocks)) } @@ -1124,6 +1184,7 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("fast-attack", nil, mode); err == nil { t.Fatalf("succeeded fast attacker synchronisation") } + if head := tester.chain.CurrentHeader().Number.Int64(); int(head) > MaxHeaderFetch { t.Errorf("rollback head mismatch: have %v, want at most %v", head, MaxHeaderFetch) } @@ -1139,9 +1200,11 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("block-attack", nil, mode); err == nil { t.Fatalf("succeeded block attacker synchronisation") } + if head := tester.chain.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) } + if mode == SnapSync { if head := tester.chain.CurrentBlock().Number.Uint64(); head != 0 { t.Errorf("fast sync pivot block #%d not rolled back", head) @@ -1156,14 +1219,17 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { for i := missing; i < len(chain.blocks); i++ { withholdAttacker.withholdHeaders[chain.blocks[i].Hash()] = struct{}{} } + tester.downloader.syncInitHook = nil } if err := tester.sync("withhold-attack", nil, mode); err == nil { t.Fatalf("succeeded withholding attacker synchronisation") } + if head := tester.chain.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) } + if mode == SnapSync { if head := tester.chain.CurrentBlock().Number.Uint64(); head != 0 { t.Errorf("fast sync pivot block #%d not rolled back", head) @@ -1174,9 +1240,11 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { // sync. Note, we can't assert anything about the receipts since we won't purge the // database of them, hence we can't use assertOwnChain. tester.newPeer("valid", protocol, chain.blocks[1:]) + if err := tester.sync("valid", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, len(chain.blocks)) } @@ -1213,6 +1281,7 @@ func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { chain := testChainBase.shorten(1) tester.newPeer("attack", protocol, chain.blocks[1:]) + if err := tester.sync("attack", big.NewInt(1000000), mode); err != errStallingPeer { t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errStallingPeer) } @@ -1253,12 +1322,14 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { // Run the tests and check disconnection status tester := newTester(t) defer tester.terminate() + chain := testChainBase.shorten(1) for i, tt := range tests { // Register a new peer and ensure its presence id := fmt.Sprintf("test %d", i) tester.newPeer(id, protocol, chain.blocks[1:]) + if _, ok := tester.peers[id]; !ok { t.Fatalf("test %d: registered peer not found", i) } @@ -1266,6 +1337,7 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { tester.downloader.synchroniseMock = func(string, common.Hash) error { return tt.result } tester.downloader.LegacySync(id, tester.chain.Genesis().Hash(), big.NewInt(1000), nil, FullSync) + if _, ok := tester.peers[id]; !ok != tt.drop { t.Errorf("test %d: peer drop mismatch for %v: have %v, want %v", i, tt.result, !ok, tt.drop) } @@ -1311,17 +1383,20 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester.downloader.syncInitHook = func(origin, latest uint64) { starting <- struct{}{} + <-progress } checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) // Synchronise half the blocks and check initial progress tester.newPeer("peer-half", protocol, chain.shorten(len(chain.blocks) / 2).blocks[1:]) + pending := new(sync.WaitGroup) pending.Add(1) go func() { defer pending.Done() + if err := tester.sync("peer-half", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1331,13 +1406,16 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { HighestBlock: uint64(len(chain.blocks)/2 - 1), }) progress <- struct{}{} + pending.Wait() // Synchronise all the blocks and check continuation progress tester.newPeer("peer-full", protocol, chain.blocks[1:]) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("peer-full", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1351,6 +1429,7 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Check final progress after successful sync progress <- struct{}{} + pending.Wait() checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ StartingBlock: uint64(len(chain.blocks)/2 - 1), @@ -1410,16 +1489,20 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester.downloader.syncInitHook = func(origin, latest uint64) { starting <- struct{}{} + <-progress } checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) // Synchronise with one of the forks and check progress tester.newPeer("fork A", protocol, chainA.blocks[1:]) + pending := new(sync.WaitGroup) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("fork A", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1430,6 +1513,7 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { HighestBlock: uint64(len(chainA.blocks) - 1), }) progress <- struct{}{} + pending.Wait() // Simulate a successful sync above the fork @@ -1438,8 +1522,10 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Synchronise with the second fork and check progress resets tester.newPeer("fork B", protocol, chainB.blocks[1:]) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("fork B", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1453,6 +1539,7 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Check final progress after successful sync progress <- struct{}{} + pending.Wait() checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ StartingBlock: uint64(len(testChainBase.blocks)) - 1, @@ -1501,6 +1588,7 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester.downloader.syncInitHook = func(origin, latest uint64) { starting <- struct{}{} + <-progress } checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) @@ -1513,8 +1601,10 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { pending := new(sync.WaitGroup) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("faulty", nil, mode); err == nil { panic("succeeded faulty synchronisation") } @@ -1524,15 +1614,19 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { HighestBlock: uint64(len(chain.blocks) - 1), }) progress <- struct{}{} + pending.Wait() + afterFailedSync := tester.downloader.Progress() // Synchronise with a good peer and check that the progress origin remind the same // after a failure tester.newPeer("valid", protocol, chain.blocks[1:]) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("valid", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1542,6 +1636,7 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Check final progress after successful sync progress <- struct{}{} + pending.Wait() checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ CurrentBlock: uint64(len(chain.blocks) - 1), @@ -1579,6 +1674,7 @@ func TestFakedSyncProgress67Light(t *testing.T) { func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester := newTester(t) defer tester.terminate() + chain := testChainBase.shorten(blockCacheMaxItems - 15) // Set a sync init hook to catch progress changes @@ -1586,6 +1682,7 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { progress := make(chan struct{}) tester.downloader.syncInitHook = func(origin, latest uint64) { starting <- struct{}{} + <-progress } checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) @@ -1593,13 +1690,17 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Create and sync with an attacker that promises a higher chain than available. attacker := tester.newPeer("attack", protocol, chain.blocks[1:]) numMissing := 5 + for i := len(chain.blocks) - 2; i > len(chain.blocks)-numMissing; i-- { attacker.withholdHeaders[chain.blocks[i].Hash()] = struct{}{} } + pending := new(sync.WaitGroup) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("attack", nil, mode); err == nil { panic("succeeded attacker synchronisation") } @@ -1609,7 +1710,9 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { HighestBlock: uint64(len(chain.blocks) - 1), }) progress <- struct{}{} + pending.Wait() + afterFailedSync := tester.downloader.Progress() // Synchronise with a good peer and check that the progress height has been reduced to @@ -1620,6 +1723,7 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { go func() { defer pending.Done() + if err := tester.sync("valid", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1631,6 +1735,7 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { }) // Check final progress after successful sync. progress <- struct{}{} + pending.Wait() checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ CurrentBlock: uint64(len(validChain.blocks) - 1), @@ -1677,13 +1782,16 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { } reqs := func(from, count, span int) []int { var r []int + num := from for len(r) < count { r = append(r, num) num += span + 1 } + return r } + for i, tt := range testCases { i := i tt := tt @@ -1695,9 +1803,11 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { if max != uint64(data[len(data)-1]) { t.Errorf("test %d: wrong last value %d != %d", i, data[len(data)-1], max) } + failed := false if len(data) != len(tt.expected) { failed = true + t.Errorf("test %d: length wrong, expected %d got %d", i, len(tt.expected), len(data)) } else { for j, n := range data { @@ -1707,9 +1817,11 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { } } } + if failed { res := strings.ReplaceAll(fmt.Sprint(data), " ", ",") exp := strings.ReplaceAll(fmt.Sprint(tt.expected), " ", ",") + t.Logf("got: %v\n", res) t.Logf("exp: %v\n", exp) t.Errorf("test %d: wrong values", i) @@ -1762,9 +1874,11 @@ func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) { if mode == SnapSync || mode == LightSync { expect = errUnsyncedPeer } + if err := tester.sync("peer", nil, mode); !errors.Is(err, expect) { t.Fatalf("block sync error mismatch: have %v, want %v", err, expect) } + if mode == SnapSync || mode == LightSync { assertOwnChain(t, tester, 1) } else { @@ -1803,6 +1917,7 @@ func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() success := make(chan struct{}) + tester := newTesterWithNotification(t, func() { close(success) }) @@ -1815,6 +1930,7 @@ func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) { if c.local > 0 { tester.chain.InsertChain(chain.blocks[1 : c.local+1]) } + if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { t.Fatalf("Failed to beacon sync chain %v %v", c.name, err) } diff --git a/eth/downloader/fetchers_concurrent.go b/eth/downloader/fetchers_concurrent.go index 6c94cad838..97458cfb8b 100644 --- a/eth/downloader/fetchers_concurrent.go +++ b/eth/downloader/fetchers_concurrent.go @@ -90,6 +90,7 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { req.Close() } }() + ordering := make(map[*eth.Request]int) timeouts := prque.New[int64, *eth.Request](func(data *eth.Request, index int) { ordering[data] = index @@ -125,6 +126,7 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { // Prepare the queue and fetch block parts until the block header fetcher's done finished := false + for { // Short circuit if we lost all our peers if d.peers.Len() == 0 && !beaconMode { @@ -141,6 +143,7 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { idles []*peerConnection caps []int ) + for _, peer := range d.peers.AllPeers() { pending, stale := pending[peer.id], stales[peer.id] if pending == nil && stale == nil { @@ -156,6 +159,7 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { } } } + sort.Sort(&peerCapacitySort{idles, caps}) var ( @@ -163,12 +167,14 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { throttled bool queued = queue.pending() ) + for _, peer := range idles { // Short circuit if throttling activated or there are no more // queued tasks to be retrieved if throttled { break } + if queued = queue.pending(); queued == 0 { break } @@ -179,10 +185,13 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { if progress { progressed = true } + if throttle { throttled = true + throttleCounter.Inc(1) } + if request == nil { continue } @@ -197,12 +206,14 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { queue.unreserve(peer.id) // TODO(karalabe): This needs a non-expiration method continue } + pending[peer.id] = req ttl := d.peers.rates.TargetTimeout() ordering[req] = timeouts.Size() timeouts.Push(req, -time.Now().Add(ttl).UnixNano()) + if timeouts.Size() == 1 { timeout.Reset(ttl) } @@ -231,6 +242,7 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { if _, ok := pending[peerid]; ok { event.peer.log.Error("Pending request exists for joining peer") } + if _, ok := stales[peerid]; ok { event.peer.log.Error("Stale request exists for joining peer") } @@ -246,18 +258,22 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { if index, live := ordering[req]; live { timeouts.Remove(index) + if index == 0 { if !timeout.Stop() { <-timeout.C } + if timeouts.Size() > 0 { _, exp := timeouts.Peek() timeout.Reset(time.Until(time.Unix(0, -exp))) } } + delete(ordering, req) } } + if req, ok := stales[peerid]; ok { delete(stales, peerid) req.Close() @@ -272,6 +288,7 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { if now, at := time.Now(), time.Unix(0, -exp); now.Before(at) { log.Error("Timeout triggered but not reached", "left", at.Sub(now)) timeout.Reset(at.Sub(now)) + continue } // Stop tracking the timed out request from a timing perspective, @@ -282,6 +299,7 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { stales[req.Peer] = req timeouts.Pop() // Popping an item will reorder indices in `ordering`, delete after, otherwise will resurrect! + if timeouts.Size() > 0 { _, exp := timeouts.Peek() timeout.Reset(time.Until(time.Unix(0, -exp))) @@ -312,6 +330,7 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { log.Error("Delivery timeout from unknown peer", "peer", req.Peer) continue } + if fails > 2 { queue.updateCapacity(peer, 0, 0) } else { @@ -335,15 +354,18 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { index, live := ordering[res.Req] if live { timeouts.Remove(index) + if index == 0 { if !timeout.Stop() { <-timeout.C } + if timeouts.Size() > 0 { _, exp := timeouts.Peek() timeout.Reset(time.Until(time.Unix(0, -exp))) } } + delete(ordering, res.Req) } // Delete the pending request (if it still exists) and mark the peer idle diff --git a/eth/downloader/fetchers_concurrent_bodies.go b/eth/downloader/fetchers_concurrent_bodies.go index 9440972c6d..a403efec87 100644 --- a/eth/downloader/fetchers_concurrent_bodies.go +++ b/eth/downloader/fetchers_concurrent_bodies.go @@ -68,6 +68,7 @@ func (q *bodyQueue) unreserve(peer string) int { } else { log.Debug("Body delivery stalling", "peer", peer) } + return fails } @@ -75,6 +76,7 @@ func (q *bodyQueue) unreserve(peer string) int { // one and sending it to the remote peer for fulfillment. func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) { peer.log.Trace("Requesting new batch of bodies", "count", len(req.Headers), "from", req.Headers[0].Number) + if q.bodyFetchHook != nil { q.bodyFetchHook(req.Headers) } @@ -83,6 +85,7 @@ func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan for _, header := range req.Headers { hashes = append(hashes, header.Hash()) } + return peer.peer.RequestBodies(hashes, resCh) } @@ -93,6 +96,7 @@ func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, er hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes, withdrawal hashes} accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1], withdrawals, hashsets[2]) + switch { case err == nil && len(txs) == 0: peer.log.Trace("Requested bodies delivered") @@ -101,5 +105,6 @@ func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, er default: peer.log.Debug("Failed to deliver retrieved bodies", "err", err) } + return accepted, err } diff --git a/eth/downloader/fetchers_concurrent_headers.go b/eth/downloader/fetchers_concurrent_headers.go index 84c7f20986..255e9e61ef 100644 --- a/eth/downloader/fetchers_concurrent_headers.go +++ b/eth/downloader/fetchers_concurrent_headers.go @@ -68,6 +68,7 @@ func (q *headerQueue) unreserve(peer string) int { } else { log.Debug("Header delivery stalling", "peer", peer) } + return fails } @@ -85,6 +86,7 @@ func (q *headerQueue) deliver(peer *peerConnection, packet *eth.Response) (int, hashes := packet.Meta.([]common.Hash) accepted, err := q.queue.DeliverHeaders(peer.id, headers, hashes, q.headerProcCh) + switch { case err == nil && len(headers) == 0: peer.log.Trace("Requested headers delivered") @@ -93,5 +95,6 @@ func (q *headerQueue) deliver(peer *peerConnection, packet *eth.Response) (int, default: peer.log.Debug("Failed to deliver retrieved headers", "err", err) } + return accepted, err } diff --git a/eth/downloader/fetchers_concurrent_receipts.go b/eth/downloader/fetchers_concurrent_receipts.go index 1c853c2184..9907e1c340 100644 --- a/eth/downloader/fetchers_concurrent_receipts.go +++ b/eth/downloader/fetchers_concurrent_receipts.go @@ -68,6 +68,7 @@ func (q *receiptQueue) unreserve(peer string) int { } else { log.Debug("Receipt delivery stalling", "peer", peer) } + return fails } @@ -75,13 +76,16 @@ func (q *receiptQueue) unreserve(peer string) int { // one and sending it to the remote peer for fulfillment. func (q *receiptQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) { peer.log.Trace("Requesting new batch of receipts", "count", len(req.Headers), "from", req.Headers[0].Number) + if q.receiptFetchHook != nil { q.receiptFetchHook(req.Headers) } + hashes := make([]common.Hash, 0, len(req.Headers)) for _, header := range req.Headers { hashes = append(hashes, header.Hash()) } + return peer.peer.RequestReceipts(hashes, resCh) } @@ -92,6 +96,7 @@ func (q *receiptQueue) deliver(peer *peerConnection, packet *eth.Response) (int, hashes := packet.Meta.([]common.Hash) // {receipt hashes} accepted, err := q.queue.DeliverReceipts(peer.id, receipts, hashes) + switch { case err == nil && len(receipts) == 0: peer.log.Trace("Requested receipts delivered") @@ -100,5 +105,6 @@ func (q *receiptQueue) deliver(peer *peerConnection, packet *eth.Response) (int, default: peer.log.Debug("Failed to deliver retrieved receipts", "err", err) } + return accepted, err } diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go index d388b9ee4d..58fd803ed5 100644 --- a/eth/downloader/modes.go +++ b/eth/downloader/modes.go @@ -70,5 +70,6 @@ func (mode *SyncMode) UnmarshalText(text []byte) error { default: return fmt.Errorf(`unknown sync mode %q, want "full", "snap" or "light"`, text) } + return nil } diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index 6b82694959..52ea0f69d5 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -132,6 +132,7 @@ func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int { if cap > MaxHeaderFetch { cap = MaxHeaderFetch } + return cap } @@ -142,6 +143,7 @@ func (p *peerConnection) BodyCapacity(targetRTT time.Duration) int { if cap > MaxBlockFetch { cap = MaxBlockFetch } + return cap } @@ -152,6 +154,7 @@ func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int { if cap > MaxReceiptFetch { cap = MaxReceiptFetch } + return cap } @@ -168,6 +171,7 @@ func (p *peerConnection) MarkLacking(hash common.Hash) { break } } + p.lacking[hash] = struct{}{} } @@ -178,6 +182,7 @@ func (p *peerConnection) Lacks(hash common.Hash) bool { defer p.lock.RUnlock() _, ok := p.lacking[hash] + return ok } @@ -235,15 +240,18 @@ func (ps *peerSet) Register(p *peerConnection) error { ps.lock.Unlock() return errAlreadyRegistered } + p.rates = msgrate.NewTracker(ps.rates.MeanCapacities(), ps.rates.MedianRoundTrip()) if err := ps.rates.Track(p.id, p.rates); err != nil { ps.lock.Unlock() return err } + ps.peers[p.id] = p ps.lock.Unlock() ps.events.Send(&peeringEvent{peer: p, join: true}) + return nil } @@ -251,16 +259,19 @@ func (ps *peerSet) Register(p *peerConnection) error { // actions to/from that particular entity. func (ps *peerSet) Unregister(id string) error { ps.lock.Lock() + p, ok := ps.peers[id] if !ok { ps.lock.Unlock() return errNotRegistered } + delete(ps.peers, id) ps.rates.Untrack(id) ps.lock.Unlock() ps.events.Send(&peeringEvent{peer: p, join: false}) + return nil } @@ -289,6 +300,7 @@ func (ps *peerSet) AllPeers() []*peerConnection { for _, p := range ps.peers { list = append(list, p) } + return list } diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index c39a20851c..40df24cac1 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -79,9 +79,11 @@ func newFetchResult(header *types.Header, fastSync bool) *fetchResult { } else if header.WithdrawalsHash != nil { item.Withdrawals = make(types.Withdrawals, 0) } + if fastSync && !header.EmptyReceipts() { item.pending.Store(item.pending.Load() | (1 << receiptType)) } + return item } @@ -160,6 +162,7 @@ func newQueue(blockCacheLimit int, thresholdInitialSize int) *queue { lock: lock, } q.Reset(blockCacheLimit, thresholdInitialSize) + return q } @@ -296,6 +299,7 @@ func (q *queue) Schedule(headers []*types.Header, hashes []common.Hash, from uin // Insert all the headers prioritised by the contained block number inserts := make([]*types.Header, 0, len(headers)) + for i, header := range headers { // Make sure chain order is honoured and preserved throughout hash := hashes[i] @@ -303,6 +307,7 @@ func (q *queue) Schedule(headers []*types.Header, hashes []common.Hash, from uin log.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", from) break } + if q.headerHead != (common.Hash{}) && q.headerHead != header.ParentHash { log.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) break @@ -325,10 +330,12 @@ func (q *queue) Schedule(headers []*types.Header, hashes []common.Hash, from uin q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64())) } } + inserts = append(inserts, header) q.headerHead = hash from++ } + return inserts } @@ -341,6 +348,7 @@ func (q *queue) Results(block bool) []*fetchResult { if !block && !q.resultCache.HasCompletedItems() { return nil } + closed := false for !closed && !q.resultCache.HasCompletedItems() { // In order to wait on 'active', we need to obtain the lock. @@ -368,12 +376,15 @@ func (q *queue) Results(block bool) []*fetchResult { for _, uncle := range result.Uncles { size += uncle.Size() } + for _, receipt := range result.Receipts { size += receipt.Size() } + for _, tx := range result.Transactions { size += common.StorageSize(tx.Size()) } + q.resultSize = common.StorageSize(blockCacheSizeWeight)*size + (1-common.StorageSize(blockCacheSizeWeight))*q.resultSize } @@ -397,6 +408,7 @@ func (q *queue) Results(block bool) []*fetchResult { info = append(info, "throttle", throttleThreshold) log.Debug("Downloader queue stats", info...) } + return results } @@ -447,12 +459,14 @@ func (q *queue) ReserveHeaders(p *peerConnection, count int) *fetchRequest { if send == 0 { return nil } + request := &fetchRequest{ Peer: p, From: send, Time: time.Now(), } q.headerPendPool[p.id] = request + return request } @@ -496,6 +510,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common if taskQueue.Empty() { return nil, false, true } + if _, ok := pendPool[p.id]; ok { return nil, false, false } @@ -504,6 +519,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common skip := make([]*types.Header, 0) progress := false throttled := false + for proc := 0; len(send) < count && !taskQueue.Empty(); proc++ { // the task queue will pop items in order, so the highest prio block // is also the lowest block number. @@ -517,12 +533,18 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common // Don't put back in the task queue, this item has already been // delivered upstream taskQueue.PopItem() + progress = true + delete(taskPool, header.Hash()) + proc = proc - 1 + log.Error("Fetch reservation already delivered", "number", header.Number.Uint64()) + continue } + if throttle { // There are no resultslots available. Leave it in the task queue // However, if there are any left as 'skipped', we should not tell @@ -531,18 +553,22 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common throttled = len(skip) == 0 break } + if err != nil { // this most definitely should _not_ happen log.Warn("Failed to reserve headers", "err", err) // There are no resultslots available. Leave it in the task queue break } + if item.Done(kind) { // If it's a noop, we can skip this task delete(taskPool, header.Hash()) taskQueue.PopItem() + proc = proc - 1 progress = true + continue } // Remove it from the task queue @@ -558,6 +584,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common for _, header := range skip { taskQueue.Push(header, -int64(header.Number.Uint64())) } + if q.resultCache.HasCompletedItems() { // Wake Results, resultCache was modified q.active.Signal() @@ -566,12 +593,14 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common if len(send) == 0 { return nil, progress, throttled } + request := &fetchRequest{ Peer: p, Headers: send, Time: time.Now(), } pendPool[p.id] = request + return request, progress, throttled } @@ -586,16 +615,20 @@ func (q *queue) Revoke(peerID string) { q.headerTaskQueue.Push(request.From, -int64(request.From)) delete(q.headerPendPool, peerID) } + if request, ok := q.blockPendPool[peerID]; ok { for _, header := range request.Headers { q.blockTaskQueue.Push(header, -int64(header.Number.Uint64())) } + delete(q.blockPendPool, peerID) } + if request, ok := q.receiptPendPool[peerID]; ok { for _, header := range request.Headers { q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64())) } + delete(q.receiptPendPool, peerID) } } @@ -607,6 +640,7 @@ func (q *queue) ExpireHeaders(peer string) int { defer q.lock.Unlock() headerTimeoutMeter.Mark(1) + return q.expire(peer, q.headerPendPool, q.headerTaskQueue) } @@ -617,6 +651,7 @@ func (q *queue) ExpireBodies(peer string) int { defer q.lock.Unlock() bodyTimeoutMeter.Mark(1) + return q.expire(peer, q.blockPendPool, q.blockTaskQueue) } @@ -627,6 +662,7 @@ func (q *queue) ExpireReceipts(peer string) int { defer q.lock.Unlock() receiptTimeoutMeter.Mark(1) + return q.expire(peer, q.receiptPendPool, q.receiptTaskQueue) } @@ -646,15 +682,18 @@ func (q *queue) expire(peer string, pendPool map[string]*fetchRequest, taskQueue log.Error("Expired request does not exist", "peer", peer) return 0 } + delete(pendPool, peer) // Return any non-satisfied requests to the pool if req.From > 0 { taskQueue.(*prque.Prque[int64, uint64]).Push(req.From, -int64(req.From)) } + for _, header := range req.Headers { taskQueue.(*prque.Prque[int64, *types.Header]).Push(header, -int64(header.Number.Uint64())) } + return len(req.Headers) } @@ -682,6 +721,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []comm headerDropMeter.Mark(int64(len(headers))) return 0, errNoFetchesPending } + delete(q.headerPendPool, id) headerReqTimer.UpdateSince(request.Time) @@ -694,24 +734,33 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []comm if accepted { if headers[0].Number.Uint64() != request.From { logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", hashes[0], "expected", request.From) + accepted = false } else if hashes[len(headers)-1] != target { logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", hashes[len(headers)-1], "expected", target) + accepted = false } } + if accepted { parentHash := hashes[0] + for i, header := range headers[1:] { hash := hashes[i+1] if want := request.From + 1 + uint64(i); header.Number.Uint64() != want { logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want) + accepted = false + break } + if parentHash != header.ParentHash { logger.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) + accepted = false + break } // Set-up parent hash for next round @@ -728,9 +777,11 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []comm q.headerPeerMiss[id] = make(map[uint64]struct{}) miss = q.headerPeerMiss[id] } + miss[request.From] = struct{}{} q.headerTaskQueue.Push(request.From, -int64(request.From)) + return 0, errors.New("delivery not accepted") } // Clean up a successful fetch and try to deliver any sub-results @@ -743,6 +794,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []comm for q.headerProced+ready < len(q.headerResults) && q.headerResults[q.headerProced+ready] != nil { ready += MaxHeaderFetch } + if ready > 0 { // Headers are ready for delivery, gather them and push forward (non blocking) processHeaders := make([]*types.Header, ready) @@ -765,6 +817,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []comm if len(q.headerTaskPool) == 0 { q.headerContCh <- false } + return len(headers), nil } @@ -781,6 +834,7 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH if txListHashes[index] != header.TxHash { return errInvalidBody } + if uncleListHashes[index] != header.UncleHash { return errInvalidBody } @@ -794,10 +848,12 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH if withdrawalLists[index] == nil { return errInvalidBody } + if withdrawalListHashes[index] != *header.WithdrawalsHash { return errInvalidBody } } + return nil } @@ -807,6 +863,7 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH result.Withdrawals = withdrawalLists[index] result.SetBodyDone() } + return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, bodyReqTimer, bodyInMeter, bodyDropMeter, len(txLists), validate, reconstruct) } @@ -822,12 +879,14 @@ func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt, recei if receiptListHashes[index] != header.ReceiptHash { return errInvalidReceipt } + return nil } reconstruct := func(index int, result *fetchResult) { result.Receipts = receiptList[index] result.SetReceiptsDone() } + return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, receiptReqTimer, receiptInMeter, receiptDropMeter, len(receiptList), validate, reconstruct) } @@ -848,6 +907,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, resDropMeter.Mark(int64(results)) return 0, errNoFetchesPending } + delete(pendPool, id) reqTimer.UpdateSince(request.Time) @@ -866,6 +926,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, i int hashes []common.Hash ) + for _, header := range request.Headers { // Short circuit assembly if no more fetch results are found if i >= results { @@ -876,6 +937,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, failure = err break } + hashes = append(hashes, header.Hash()) i++ } @@ -888,12 +950,15 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, // or it was indeed a no-op. This should not happen, but if it does it's // not something to panic about log.Error("Delivery stale", "stale", stale, "number", header.Number.Uint64(), "err", err) + failure = errStaleDelivery } // Clean up a successful fetch delete(taskPool, hashes[accepted]) + accepted++ } + resDropMeter.Mark(int64(results - accepted)) // Return all failed or missing fetches to the queue @@ -904,6 +969,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, if accepted > 0 { q.active.Signal() } + if failure == nil { return accepted, nil } @@ -911,6 +977,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, if accepted > 0 { return accepted, fmt.Errorf("partial failure: %v", failure) } + return accepted, fmt.Errorf("%w: %v", failure, errStaleDelivery) } diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go index 8b1ada199a..19b9d73060 100644 --- a/eth/downloader/queue_test.go +++ b/eth/downloader/queue_test.go @@ -43,13 +43,16 @@ func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Bloc // Add one tx to every secondblock if !empty && i%2 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) if err != nil { panic(err) } + block.AddTx(tx) } }) + return blocks, receipts } @@ -82,6 +85,7 @@ func (chain *chainData) headers() []*types.Header { for i, b := range chain.blocks { hdrs[i] = b.Header() } + return hdrs } @@ -94,6 +98,7 @@ func dummyPeer(id string) *peerConnection { id: id, lacking: make(map[common.Hash]struct{}), } + return p } @@ -105,7 +110,9 @@ func TestBasics(t *testing.T) { if !q.Idle() { t.Errorf("new queue should be idle") } + q.Prepare(1, SnapSync) + if res := q.Results(false); len(res) != 0 { t.Fatal("new queue should have 0 results") } @@ -113,13 +120,17 @@ func TestBasics(t *testing.T) { // Schedule a batch of headers headers := chain.headers() hashes := make([]common.Hash, len(headers)) + for i, header := range headers { hashes[i] = header.Hash() } + q.Schedule(headers, hashes, 1) + if q.Idle() { t.Errorf("queue should not be idle") } + if got, exp := q.PendingBodies(), chain.Len(); got != exp { t.Errorf("wrong pending block count, got %d, exp %d", got, exp) } @@ -131,6 +142,7 @@ func TestBasics(t *testing.T) { // queue that a certain peer will deliver them for us { peer := dummyPeer("peer-1") + fetchReq, _, throttle := q.ReserveBodies(peer, 50) if !throttle { // queue size is only 10, so throttling should occur @@ -140,13 +152,16 @@ func TestBasics(t *testing.T) { if got, exp := len(fetchReq.Headers), 5; got != exp { t.Fatalf("expected %d requests, got %d", exp, got) } + if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp { 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) } @@ -163,9 +178,11 @@ func TestBasics(t *testing.T) { t.Fatalf("should have no fetches, got %d", len(fetchReq.Headers)) } } + 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) } @@ -173,6 +190,7 @@ func TestBasics(t *testing.T) { // The receipt delivering peer should not be affected // by the throttling of body deliveries peer := dummyPeer("peer-3") + fetchReq, _, throttle := q.ReserveReceipts(peer, 50) if !throttle { // queue size is only 10, so throttling should occur @@ -182,16 +200,20 @@ func TestBasics(t *testing.T) { if got, exp := len(fetchReq.Headers), 5; got != exp { t.Fatalf("expected %d requests, got %d", exp, got) } + if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp { 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-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) } @@ -207,16 +229,21 @@ func TestEmptyBlocks(t *testing.T) { // Schedule a batch of headers headers := emptyChain.headers() hashes := make([]common.Hash, len(headers)) + for i, header := range headers { hashes[i] = header.Hash() } + q.Schedule(headers, hashes, 1) + if q.Idle() { t.Errorf("queue should not be idle") } + if got, exp := q.PendingBodies(), len(emptyChain.blocks); got != exp { t.Errorf("wrong pending block count, got %d, exp %d", got, exp) } + if got, exp := q.PendingReceipts(), 0; got != exp { t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp) } @@ -239,9 +266,11 @@ 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()) } @@ -254,12 +283,15 @@ func TestEmptyBlocks(t *testing.T) { t.Fatal("there should be no receipt 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) } @@ -276,24 +308,33 @@ func XTestDelivery(t *testing.T) { world.receipts = rec world.chain = blo world.progress(10) + if false { log.Root().SetHandler(log.StdoutHandler) } + q := newQueue(10, 10) + var wg sync.WaitGroup + q.Prepare(1, SnapSync) wg.Add(1) + go func() { // deliver headers defer wg.Done() + c := 1 + for { //fmt.Printf("getting headers from %d\n", c) headers := world.headers(c) hashes := make([]common.Hash, len(headers)) + for i, header := range headers { hashes[i] = header.Hash() } + l := len(headers) //fmt.Printf("scheduling %d headers, first %d last %d\n", // l, headers[0].Number.Uint64(), headers[len(headers)-1].Number.Uint64()) @@ -302,10 +343,13 @@ func XTestDelivery(t *testing.T) { } }() wg.Add(1) + go func() { // collect results defer wg.Done() + tot := 0 + for { res := q.Results(true) tot += len(res) @@ -315,12 +359,15 @@ func XTestDelivery(t *testing.T) { } }() wg.Add(1) + go func() { defer wg.Done() // reserve body fetch i := 4 + for { peer := dummyPeer(fmt.Sprintf("peer-%d", i)) + f, _, _ := q.ReserveBodies(peer, rand.Intn(30)) if f != nil { var ( @@ -328,22 +375,27 @@ func XTestDelivery(t *testing.T) { txset [][]*types.Transaction uncleset [][]*types.Header ) + numToSkip := rand.Intn(len(f.Headers)) for _, hdr := range f.Headers[0 : len(f.Headers)-numToSkip] { txset = append(txset, world.getTransactions(hdr.Number.Uint64())) uncleset = append(uncleset, emptyList) } + var ( txsHashes = make([]common.Hash, len(txset)) uncleHashes = make([]common.Hash, len(uncleset)) ) + hasher := trie.NewStackTrie(nil) for i, txs := range txset { txsHashes[i] = types.DeriveSha(types.Transactions(txs), hasher) } + for i, uncles := range uncleset { uncleHashes[i] = types.CalcUncleHash(uncles) } + time.Sleep(100 * time.Millisecond) _, err := q.DeliverBodies(peer.id, txset, txsHashes, uncleset, uncleHashes, nil, nil) @@ -352,6 +404,7 @@ func XTestDelivery(t *testing.T) { } } else { i++ + time.Sleep(200 * time.Millisecond) } } @@ -360,6 +413,7 @@ func XTestDelivery(t *testing.T) { defer wg.Done() // reserve receiptfetch peer := dummyPeer("peer-3") + for { f, _, _ := q.ReserveReceipts(peer, rand.Intn(50)) if f != nil { @@ -367,15 +421,19 @@ func XTestDelivery(t *testing.T) { for _, hdr := range f.Headers { rcs = append(rcs, world.getReceipts(hdr.Number.Uint64())) } + hasher := trie.NewStackTrie(nil) hashes := make([]common.Hash, len(rcs)) + for i, receipt := range rcs { hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher) } + _, err := q.DeliverReceipts(peer.id, rcs, hashes) if err != nil { fmt.Printf("delivered %d receipts %v\n", len(rcs), err) } + time.Sleep(100 * time.Millisecond) } else { time.Sleep(200 * time.Millisecond) @@ -383,21 +441,26 @@ func XTestDelivery(t *testing.T) { } }() wg.Add(1) + go func() { defer wg.Done() + for i := 0; i < 50; i++ { time.Sleep(300 * time.Millisecond) //world.tick() //fmt.Printf("trying to progress\n") world.progress(rand.Intn(100)) } + for i := 0; i < 50; i++ { time.Sleep(2990 * time.Millisecond) } }() wg.Add(1) + go func() { defer wg.Done() + for { time.Sleep(990 * time.Millisecond) fmt.Printf("world block tip is %d\n", @@ -410,6 +473,7 @@ func XTestDelivery(t *testing.T) { func newNetwork() *network { var l sync.RWMutex + return &network{ cond: sync.NewCond(&l), offset: 1, // block 1 is at blocks[0] @@ -435,6 +499,7 @@ func (n *network) getReceipts(blocknum uint64) types.Receipts { fmt.Printf("Err, got %d exp %d\n", got, blocknum) panic("sd") } + return n.receipts[index] } @@ -456,7 +521,9 @@ func (n *network) progress(numBlocks int) { func (n *network) headers(from int) []*types.Header { numHeaders := 128 + var hdrs []*types.Header + index := from - n.offset for index >= len(n.chain) { @@ -469,11 +536,14 @@ func (n *network) headers(from int) []*types.Header { } n.lock.RLock() defer n.lock.RUnlock() + for i, b := range n.chain[index:] { hdrs = append(hdrs, b.Header()) + if i >= numHeaders { break } } + return hdrs } diff --git a/eth/downloader/resultstore.go b/eth/downloader/resultstore.go index 7f7f5a89e2..a26387905a 100644 --- a/eth/downloader/resultstore.go +++ b/eth/downloader/resultstore.go @@ -63,7 +63,9 @@ func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 { if threshold >= limit { threshold = limit } + r.throttleThreshold = threshold + return r.throttleThreshold } @@ -81,14 +83,17 @@ func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, thro defer r.lock.Unlock() var index int + item, index, stale, throttled, err = r.getFetchResult(header.Number.Uint64()) if err != nil || stale || throttled { return stale, throttled, item, err } + if item == nil { item = newFetchResult(header, fastSync) r.items[index] = item } + return stale, throttled, item, err } @@ -101,6 +106,7 @@ func (r *resultStore) GetDeliverySlot(headerNumber uint64) (*fetchResult, bool, defer r.lock.RUnlock() res, _, stale, _, err := r.getFetchResult(headerNumber) + return res, stale, err } @@ -115,12 +121,16 @@ func (r *resultStore) getFetchResult(headerNumber uint64) (item *fetchResult, in err = fmt.Errorf("%w: index allocation went beyond available resultStore space "+ "(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d", errInvalidChain, index, headerNumber, r.resultOffset, len(r.items)) + return nil, index, stale, throttle, err } + if stale { return nil, index, stale, throttle, nil } + item = r.items[index] + return item, index, stale, throttle, nil } @@ -133,9 +143,11 @@ func (r *resultStore) HasCompletedItems() bool { if len(r.items) == 0 { return false } + if item := r.items[0]; item != nil && item.AllDone() { return true } + return false } @@ -147,16 +159,19 @@ func (r *resultStore) countCompleted() int { // We iterate from the already known complete point, and see // if any more has completed since last count index := r.indexIncomplete.Load() + for ; ; index++ { if index >= int32(len(r.items)) { break } + result := r.items[index] if result == nil || !result.AllDone() { break } } r.indexIncomplete.Store(index) + return int(index) } @@ -169,11 +184,13 @@ func (r *resultStore) GetCompleted(limit int) []*fetchResult { if limit > completed { limit = completed } + results := make([]*fetchResult, limit) copy(results, r.items[:limit]) // Delete the results from the cache and clear the tail. copy(r.items, r.items[limit:]) + for i := len(r.items) - limit; i < len(r.items); i++ { r.items[i] = nil } diff --git a/eth/downloader/skeleton.go b/eth/downloader/skeleton.go index cc6c299286..59f81cd9e2 100644 --- a/eth/downloader/skeleton.go +++ b/eth/downloader/skeleton.go @@ -231,6 +231,7 @@ func newSkeleton(db ethdb.Database, peers *peerSet, drop peerDropFn, filler back terminated: make(chan struct{}), } go sk.startup() + return sk } @@ -269,6 +270,7 @@ func (s *skeleton) startup() { // higher layers request termination. There's no fancy explicit error // signalling as the sync loop should never terminate (TM). newhead, err := s.sync(head) + switch { case err == errSyncLinked: // Sync cycle linked up to the genesis block. Tear down the loop @@ -298,6 +300,7 @@ func (s *skeleton) startup() { // error. Abort and wait until Geth requests a termination. errc := <-s.terminate errc <- err + return } } @@ -314,6 +317,7 @@ func (s *skeleton) Terminate() error { // Wait for full shutdown (not necessary, but cleaner) <-s.terminated + return err } @@ -325,6 +329,7 @@ func (s *skeleton) Terminate() error { // fed header. What the syncer does with it is the syncer's problem. func (s *skeleton) Sync(head *types.Header, final *types.Header, force bool) error { log.Trace("New skeleton head announced", "number", head.Number, "hash", head.Hash(), "force", force) + errc := make(chan error) select { @@ -385,6 +390,7 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) { requestFails = make(chan *headerRequest) responses = make(chan *headerResponse) ) + cancel := make(chan struct{}) defer close(cancel) @@ -410,6 +416,7 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) { if s.syncStarting != nil { s.syncStarting() } + for { // Something happened, try to assign new tasks to any idle peers if !linked { @@ -423,6 +430,7 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) { peerid := event.peer.id if event.join { log.Debug("Joining skeleton peer", "id", peerid) + s.idles[peerid] = event.peer } else { log.Debug("Leaving skeleton peer", "id", peerid) @@ -449,6 +457,7 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) { return event.header, errSyncReorged } event.errc <- errReorgDenied + continue } event.errc <- nil // head extension accepted @@ -475,6 +484,7 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) { log.Debug("Beacon sync linked to local chain") return nil, errSyncLinked } + if merged { log.Debug("Beacon sync merged subchains") return nil, errSyncMerged @@ -508,12 +518,15 @@ func (s *skeleton) initSync(head *types.Header) { Tail: number, Next: head.ParentHash, } + for len(s.progress.Subchains) > 0 { // If the last chain is above the new head, delete altogether lastchain := s.progress.Subchains[0] if lastchain.Tail >= headchain.Tail { log.Debug("Dropping skeleton subchain", "head", lastchain.Head, "tail", lastchain.Tail) + s.progress.Subchains = s.progress.Subchains[1:] + continue } // Otherwise truncate the last chain if needed and abort trimming @@ -521,11 +534,13 @@ func (s *skeleton) initSync(head *types.Header) { log.Debug("Trimming skeleton subchain", "oldhead", lastchain.Head, "newhead", headchain.Tail-1, "tail", lastchain.Tail) lastchain.Head = headchain.Tail - 1 } + break } // If the last subchain can be extended, we're lucky. Otherwise, create // a new subchain sync task. var extended bool + if n := len(s.progress.Subchains); n > 0 { lastchain := s.progress.Subchains[0] if lastchain.Head == headchain.Tail-1 { @@ -537,8 +552,10 @@ func (s *skeleton) initSync(head *types.Header) { } } } + if !extended { log.Debug("Created new skeleton subchain", "head", number, "tail", number) + s.progress.Subchains = append([]*subchain{headchain}, s.progress.Subchains...) } // Update the database with the new sync stats and insert the new @@ -554,6 +571,7 @@ func (s *skeleton) initSync(head *types.Header) { if err := batch.Write(); err != nil { log.Crit("Failed to write skeleton sync status", "err", err) } + return } } @@ -577,6 +595,7 @@ func (s *skeleton) initSync(head *types.Header) { if err := batch.Write(); err != nil { log.Crit("Failed to write initial skeleton sync status", "err", err) } + log.Debug("Created initial skeleton subchain", "head", number, "tail", number) } @@ -586,6 +605,7 @@ func (s *skeleton) saveSyncStatus(db ethdb.KeyValueWriter) { if err != nil { panic(err) // This can only fail during implementation } + rawdb.WriteSkeletonSyncStatus(db, status) } @@ -621,18 +641,23 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force if force { log.Warn("Beacon chain reorged", "tail", lastchain.Tail, "head", lastchain.Head, "newHead", number) } + return true } + if lastchain.Head+1 < number { if force { log.Warn("Beacon chain gapped", "head", lastchain.Head, "newHead", number) } + return true } + if parent := rawdb.ReadSkeletonHeader(s.db, number-1); parent.Hash() != head.ParentHash { if force { log.Warn("Beacon chain forked", "ancestor", parent.Number, "hash", parent.Hash(), "want", head.ParentHash) } + return true } // New header seems to be in the last subchain range. Unwind any extra headers @@ -643,12 +668,15 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force batch := s.db.NewBatch() rawdb.WriteSkeletonHeader(batch, head) + lastchain.Head = number + s.saveSyncStatus(batch) if err := batch.Write(); err != nil { log.Crit("Failed to write skeleton sync status", "err", err) } + return false } @@ -660,13 +688,16 @@ func (s *skeleton) assignTasks(success chan *headerResponse, fail chan *headerRe caps: make([]int, 0, len(s.idles)), } targetTTL := s.peers.rates.TargetTimeout() + for _, peer := range s.idles { idlers.peers = append(idlers.peers, peer) idlers.caps = append(idlers.caps, s.peers.rates.Capacity(peer.id, eth.BlockHeadersMsg, targetTTL)) } + if len(idlers.peers) == 0 { return } + sort.Sort(idlers) // Find header regions not yet downloading and fill them @@ -691,14 +722,17 @@ func (s *skeleton) assignTasks(success chan *headerResponse, fail chan *headerRe // 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.requests[reqid]; ok { continue } + break } // Generate the network query and send it to the peer @@ -737,13 +771,17 @@ func (s *skeleton) executeTask(peer *peerConnection, req *headerRequest) { if req.head < requestHeaders { requestCount = int(req.head) } + peer.log.Trace("Fetching skeleton headers", "from", req.head, "count", requestCount) + netreq, err := peer.peer.RequestHeadersByNumber(req.head, requestCount, 0, true, resCh) if err != nil { peer.log.Trace("Failed to request headers", "err", err) s.scheduleRevertRequest(req) + return } + defer netreq.Close() // Wait until the response arrives, the request is cancelled or times out @@ -788,24 +826,28 @@ func (s *skeleton) executeTask(peer *peerConnection, req *headerRequest) { // No headers were delivered, reject the response and reschedule peer.log.Debug("No headers delivered") res.Done <- errors.New("no headers delivered") + s.scheduleRevertRequest(req) case headers[0].Number.Uint64() != req.head: // Header batch anchored at non-requested number peer.log.Debug("Invalid header response head", "have", headers[0].Number, "want", req.head) res.Done <- errors.New("invalid header batch anchor") + s.scheduleRevertRequest(req) case req.head >= requestHeaders && len(headers) != requestHeaders: // Invalid number of non-genesis headers delivered, reject the response and reschedule peer.log.Debug("Invalid non-genesis header count", "have", len(headers), "want", requestHeaders) res.Done <- errors.New("not enough non-genesis headers delivered") + s.scheduleRevertRequest(req) case req.head < requestHeaders && uint64(len(headers)) != req.head: // Invalid number of genesis headers delivered, reject the response and reschedule peer.log.Debug("Invalid genesis header count", "have", len(headers), "want", headers[0].Number.Uint64()) res.Done <- errors.New("not enough genesis headers delivered") + s.scheduleRevertRequest(req) default: @@ -815,7 +857,9 @@ func (s *skeleton) executeTask(peer *peerConnection, req *headerRequest) { if headers[i].ParentHash != headers[i+1].Hash() { peer.log.Debug("Invalid hash progression", "index", i, "wantparenthash", headers[i].ParentHash, "haveparenthash", headers[i+1].Hash()) res.Done <- errors.New("invalid hash progression") + s.scheduleRevertRequest(req) + return } } @@ -905,6 +949,7 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo res.peer.log.Error("Unexpected header packet") return false, false } + delete(s.requests, res.reqid) // Insert the delivered headers into the scratch space independent of the @@ -918,6 +963,7 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo } // Try to consume any head headers, validating the boundary conditions batch := s.db.NewBatch() + for s.scratchSpace[0] != nil { // Next batch of headers available, cross-reference with the subchain // we are extending and either accept or discard @@ -937,16 +983,19 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo } s.drop(s.scratchOwners[0]) s.scratchOwners[0] = "" + break } // Scratch delivery matches required subchain, deliver the batch of // headers and push the subchain forward var consumed int + for _, header := range s.scratchSpace[:requestHeaders] { if header != nil { // nil when the genesis is reached consumed++ rawdb.WriteSkeletonHeader(batch, header) + s.pulled++ s.progress.Subchains[0].Tail-- @@ -970,6 +1019,7 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo } } } + head := s.progress.Subchains[0].Head tail := s.progress.Subchains[0].Tail next := s.progress.Subchains[0].Next @@ -1026,10 +1076,12 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo s.progress.Subchains = s.progress.Subchains[:1] } } + break } // Batch of headers consumed, shift the download window forward copy(s.scratchSpace, s.scratchSpace[requestHeaders:]) + for i := 0; i < requestHeaders; i++ { s.scratchSpace[scratchHeaders-i-1] = nil } @@ -1052,17 +1104,21 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo if s.progress.Subchains[1].Tail >= s.progress.Subchains[0].Tail { // Fully overwritten, get rid of the subchain as a whole log.Debug("Previous subchain fully overwritten", "head", head, "tail", tail, "next", next) + s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...) + continue } else { // Partially overwritten, trim the head to the overwritten size log.Debug("Previous subchain partially overwritten", "head", head, "tail", tail, "next", next) + s.progress.Subchains[1].Head = s.progress.Subchains[0].Tail - 1 } // If the old subchain is an extension of the new one, merge the two // and let the skeleton syncer restart (to clean internal state) if rawdb.ReadSkeletonHeader(s.db, s.progress.Subchains[1].Head).Hash() == s.progress.Subchains[0].Next { log.Debug("Previous subchain merged", "head", head, "tail", tail, "next", next) + s.progress.Subchains[0].Tail = s.progress.Subchains[1].Tail s.progress.Subchains[0].Next = s.progress.Subchains[1].Next @@ -1078,6 +1134,7 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo } } s.saveSyncStatus(batch) + if err := batch.Write(); err != nil { log.Crit("Failed to write skeleton headers and progress", "err", err) } @@ -1086,6 +1143,7 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo if linked { left = 0 } + if time.Since(s.logged) > 8*time.Second || left == 0 { s.logged = time.Now() @@ -1096,6 +1154,7 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo log.Info("Syncing beacon headers", "downloaded", s.pulled, "left", left, "eta", common.PrettyDuration(eta)) } } + return linked, merged } @@ -1191,15 +1250,18 @@ func (s *skeleton) Bounds() (head *types.Header, tail *types.Header, final *type if len(status) == 0 { return nil, nil, nil, errors.New("beacon sync not yet started") } + progress := new(skeletonProgress) if err := json.Unmarshal(status, progress); err != nil { return nil, nil, nil, err } + head = rawdb.ReadSkeletonHeader(s.db, progress.Subchains[0].Head) if head == nil { return nil, nil, nil, fmt.Errorf("head skeleton header %d is missing", progress.Subchains[0].Head) } + tail = rawdb.ReadSkeletonHeader(s.db, progress.Subchains[0].Tail) if tail == nil { diff --git a/eth/downloader/skeleton_test.go b/eth/downloader/skeleton_test.go index 6b82bc8e4b..8cf2ad4dad 100644 --- a/eth/downloader/skeleton_test.go +++ b/eth/downloader/skeleton_test.go @@ -147,6 +147,7 @@ func (p *skeletonTestPeer) RequestHeadersByNumber(origin uint64, amount int, ski if p.serve != nil { headers = p.serve(origin) } + if headers == nil { headers = make([]*types.Header, 0, amount) if len(p.headers) > int(origin) { // Don't serve headers if we're missing the origin @@ -158,6 +159,7 @@ func (p *skeletonTestPeer) RequestHeadersByNumber(origin uint64, amount int, ski if header == nil { continue } + headers = append(headers, header) } } @@ -180,13 +182,16 @@ func (p *skeletonTestPeer) RequestHeadersByNumber(origin uint64, amount int, ski Time: 1, Done: make(chan error), } + go func() { sink <- res + if err := <-res.Done; err != nil { log.Warn("Skeleton test peer response rejected", "err", err) p.dropped.Add(1) } }() + return req, nil } @@ -216,6 +221,7 @@ func TestSkeletonSyncInit(t *testing.T) { block49B = &types.Header{Number: big.NewInt(49), Extra: []byte("B")} block50 = &types.Header{Number: big.NewInt(50), ParentHash: block49.Hash()} ) + tests := []struct { headers []*types.Header // Database content (beside the genesis) oldstate []*subchain // Old sync state with various interrupted subchains @@ -360,9 +366,11 @@ func TestSkeletonSyncInit(t *testing.T) { db := rawdb.NewMemoryDatabase() rawdb.WriteHeader(db, genesis) + for _, header := range tt.headers { rawdb.WriteSkeletonHeader(db, header) } + if tt.oldstate != nil { blob, _ := json.Marshal(&skeletonProgress{Subchains: tt.oldstate}) rawdb.WriteSkeletonSyncStatus(db, blob) @@ -379,16 +387,19 @@ func TestSkeletonSyncInit(t *testing.T) { // Ensure the correct resulting sync status var progress skeletonProgress + json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress) if len(progress.Subchains) != len(tt.newstate) { t.Errorf("test %d: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.newstate)) continue } + for j := 0; j < len(progress.Subchains); j++ { if progress.Subchains[j].Head != tt.newstate[j].Head { t.Errorf("test %d: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.newstate[j].Head) } + if progress.Subchains[j].Tail != tt.newstate[j].Tail { t.Errorf("test %d: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.newstate[j].Tail) } @@ -407,6 +418,7 @@ func TestSkeletonSyncExtend(t *testing.T) { block50 = &types.Header{Number: big.NewInt(50), ParentHash: block49.Hash()} block51 = &types.Header{Number: big.NewInt(51), ParentHash: block50.Hash()} ) + tests := []struct { head *types.Header // New head header to announce to reorg to extend *types.Header // New head header to announce to extend with @@ -493,20 +505,24 @@ func TestSkeletonSyncExtend(t *testing.T) { if err := skeleton.Sync(tt.extend, nil, false); err != tt.err { t.Errorf("test %d: extension failure mismatch: have %v, want %v", i, err, tt.err) } + skeleton.Terminate() // Ensure the correct resulting sync status var progress skeletonProgress + json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress) if len(progress.Subchains) != len(tt.newstate) { t.Errorf("test %d: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.newstate)) continue } + for j := 0; j < len(progress.Subchains); j++ { if progress.Subchains[j].Head != tt.newstate[j].Head { t.Errorf("test %d: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.newstate[j].Head) } + if progress.Subchains[j].Tail != tt.newstate[j].Tail { t.Errorf("test %d: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.newstate[j].Tail) } @@ -518,7 +534,6 @@ func TestSkeletonSyncExtend(t *testing.T) { // peers without duplicates or other strange side effects. func TestSkeletonSyncRetrievals(t *testing.T) { //log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) - // Since skeleton headers don't need to be meaningful, beyond a parent hash // progression, create a long fake chain to test with. chain := []*types.Header{{Number: big.NewInt(0)}} @@ -541,6 +556,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) { Extra: []byte("B"), // force a different hash }) } + tests := []struct { fill bool // Whether to run a real backfiller in this test case unpredictable bool // Whether to ignore drops/serves due to uncertain packet assignments @@ -823,7 +839,9 @@ func TestSkeletonSyncRetrievals(t *testing.T) { if p := peerset.Peer(peer); p != nil { p.peer.(*skeletonTestPeer).dropped.Add(1) } + peerset.Unregister(peer) + dropped[peer]++ } // Create a backfiller if we need to run more advanced tests @@ -873,14 +891,17 @@ func TestSkeletonSyncRetrievals(t *testing.T) { if len(progress.Subchains) != len(tt.midstate) { return fmt.Errorf("test %d, mid state: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.midstate)) } + for j := 0; j < len(progress.Subchains); j++ { if progress.Subchains[j].Head != tt.midstate[j].Head { return fmt.Errorf("test %d, mid state: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.midstate[j].Head) } + if progress.Subchains[j].Tail != tt.midstate[j].Tail { return fmt.Errorf("test %d, mid state: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.midstate[j].Tail) } } + return nil } @@ -889,10 +910,12 @@ func TestSkeletonSyncRetrievals(t *testing.T) { time.Sleep(waitTime) // Check the post-init end state if it matches the required results json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress) + if err := check(); err == nil { break } } + if err := check(); err != nil { t.Error(err) continue @@ -922,6 +945,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) { if tt.newHead != nil { skeleton.Sync(tt.newHead, nil, true) } + if tt.newPeer != nil { if err := peerset.Register(newPeerConnection(tt.newPeer.id, eth.ETH66, tt.newPeer, log.New("id", tt.newPeer.id))); err != nil { t.Errorf("test %d: failed to register new peer: %v", i, err) @@ -933,14 +957,17 @@ func TestSkeletonSyncRetrievals(t *testing.T) { if len(progress.Subchains) != len(tt.endstate) { return fmt.Errorf("test %d, end state: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.endstate)) } + for j := 0; j < len(progress.Subchains); j++ { if progress.Subchains[j].Head != tt.endstate[j].Head { return fmt.Errorf("test %d, end state: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.endstate[j].Head) } + if progress.Subchains[j].Tail != tt.endstate[j].Tail { return fmt.Errorf("test %d, end state: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.endstate[j].Tail) } } + return nil } waitStart = time.Now() @@ -949,10 +976,12 @@ func TestSkeletonSyncRetrievals(t *testing.T) { time.Sleep(waitTime) // Check the post-init end state if it matches the required results json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress) + if err := check(); err == nil { break } } + if err := check(); err != nil { t.Error(err) continue diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index 501af63ed5..b846697782 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -37,6 +37,7 @@ func (d *Downloader) syncState(root common.Hash) *stateSync { s.err = errCancelStateFetch close(s.done) } + return s } @@ -119,5 +120,6 @@ func (s *stateSync) Cancel() error { s.cancelOnce.Do(func() { close(s.cancel) }) + return s.Wait() } diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index 8801561e45..ecbf871892 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -65,10 +65,12 @@ func init() { testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis) var forkLen = int(fullMaxForkAncestry + 50) + var wg sync.WaitGroup // Generate the test chains to seed the peers with wg.Add(3) + go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }() go func() { testChainForkLightB = testChainBase.makeFork(forkLen, false, 2); wg.Done() }() go func() { testChainForkHeavy = testChainBase.makeFork(forkLen, true, 3); wg.Done() }() @@ -104,12 +106,14 @@ func init() { testChainForkHeavy.shorten(len(testChainBase.blocks) + 79), } wg.Add(len(chains)) + for _, chain := range chains { go func(blocks []*types.Block) { newTestBlockchain(blocks) wg.Done() }(chain.blocks[1:]) } + wg.Wait() // Mark the chains pregenerated. Generating a new one will lead to a panic. @@ -126,6 +130,7 @@ func newTestChain(length int, genesis *types.Block) *testChain { blocks: []*types.Block{genesis}, } tc.generate(length-1, 0, genesis, false) + return tc } @@ -133,6 +138,7 @@ func newTestChain(length int, genesis *types.Block) *testChain { func (tc *testChain) makeFork(length int, heavy bool, seed byte) *testChain { fork := tc.copy(len(tc.blocks) + length) fork.generate(length, seed, tc.blocks[len(tc.blocks)-1], heavy) + return fork } @@ -142,6 +148,7 @@ func (tc *testChain) shorten(length int) *testChain { if length > len(tc.blocks) { panic(fmt.Errorf("can't shorten test chain to %d blocks, it's only %d blocks long", length, len(tc.blocks))) } + return tc.copy(length) } @@ -149,9 +156,11 @@ func (tc *testChain) copy(newlen int) *testChain { if newlen > len(tc.blocks) { newlen = len(tc.blocks) } + cpy := &testChain{ blocks: append([]*types.Block{}, tc.blocks[:newlen]...), } + return cpy } @@ -169,10 +178,12 @@ func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) // Include transactions to the miner to make blocks more interesting. if parent == tc.blocks[0] && i%22 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) if err != nil { panic(err) } + block.AddTx(tx) } // if the block number is a multiple of 5, add a bonus uncle to the block @@ -205,10 +216,12 @@ func newTestBlockchain(blocks []*types.Block) *core.BlockChain { if len(blocks) > 0 { head = blocks[len(blocks)-1].Hash() } + testBlockchainsLock.Lock() if _, ok := testBlockchains[head]; !ok { testBlockchains[head] = new(testBlockchain) } + tbc := testBlockchains[head] testBlockchainsLock.Unlock() @@ -217,14 +230,18 @@ func newTestBlockchain(blocks []*types.Block) *core.BlockChain { if pregenerated { panic("Requested chain generation outside of init") } + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, testGspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if err != nil { panic(err) } + if n, err := chain.InsertChain(blocks); err != nil { panic(fmt.Sprintf("block %d: %v", n, err)) } + tbc.chain = chain }) + return tbc.chain } diff --git a/eth/downloader/whitelist/service_test.go b/eth/downloader/whitelist/service_test.go index cf0b076cfd..b3c1f8b010 100644 --- a/eth/downloader/whitelist/service_test.go +++ b/eth/downloader/whitelist/service_test.go @@ -215,6 +215,7 @@ func TestSplitChain(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() + past, future := splitChain(tc.current, tc.chain) require.Equal(t, len(past), tc.result.pastLength) require.Equal(t, len(future), tc.result.futureLength) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index e144f323b6..b9f4c5f61f 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -107,6 +107,7 @@ func init() { home = user.HomeDir } } + if runtime.GOOS == "darwin" { Defaults.Ethash.DatasetDir = filepath.Join(home, "Library", "Ethash") } else if runtime.GOOS == "windows" { @@ -268,6 +269,7 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, et if ethConfig.DevFakeAuthor { log.Warn("Sanitizing DevFakeAuthor", "Use DevFakeAuthor with", "--bor.withoutheimdall") } + var heimdallClient bor.IHeimdallClient if ethConfig.RunHeimdall && ethConfig.UseHeimdallApp { heimdallClient = heimdallapp.NewHeimdallAppClient() @@ -289,6 +291,7 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, et case ethash.ModeShared: log.Warn("Ethash used in shared mode") } + engine = ethash.New(ethash.Config{ PowMode: ethashConfig.PowMode, CacheDir: stack.ResolvePath(ethashConfig.CacheDir), @@ -303,5 +306,6 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, et }, notify, noverify) engine.(*ethash.Ethash).SetThreads(-1) // Disable CPU mining } + return beacon.New(engine) } diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 7a21ccd40e..4ef94b47fe 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -62,6 +62,7 @@ func (c Config) MarshalTOML() (interface{}, error) { CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` OverrideShanghai *uint64 `toml:",omitempty"` } + var enc Config enc.Genesis = c.Genesis enc.NetworkId = c.NetworkId @@ -106,6 +107,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.Checkpoint = c.Checkpoint enc.CheckpointOracle = c.CheckpointOracle enc.OverrideShanghai = c.OverrideShanghai + return &enc, nil } @@ -156,133 +158,176 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` OverrideShanghai *uint64 `toml:",omitempty"` } + var dec Config if err := unmarshal(&dec); err != nil { return err } + if dec.Genesis != nil { c.Genesis = dec.Genesis } + if dec.NetworkId != nil { c.NetworkId = *dec.NetworkId } + if dec.SyncMode != nil { c.SyncMode = *dec.SyncMode } + if dec.EthDiscoveryURLs != nil { c.EthDiscoveryURLs = dec.EthDiscoveryURLs } + if dec.SnapDiscoveryURLs != nil { c.SnapDiscoveryURLs = dec.SnapDiscoveryURLs } + if dec.NoPruning != nil { c.NoPruning = *dec.NoPruning } + if dec.NoPrefetch != nil { c.NoPrefetch = *dec.NoPrefetch } + if dec.TxLookupLimit != nil { c.TxLookupLimit = *dec.TxLookupLimit } + if dec.RequiredBlocks != nil { c.RequiredBlocks = dec.RequiredBlocks } + if dec.LightServ != nil { c.LightServ = *dec.LightServ } + if dec.LightIngress != nil { c.LightIngress = *dec.LightIngress } + if dec.LightEgress != nil { c.LightEgress = *dec.LightEgress } + if dec.LightPeers != nil { c.LightPeers = *dec.LightPeers } + if dec.LightNoPrune != nil { c.LightNoPrune = *dec.LightNoPrune } + if dec.LightNoSyncServe != nil { c.LightNoSyncServe = *dec.LightNoSyncServe } + if dec.SyncFromCheckpoint != nil { c.SyncFromCheckpoint = *dec.SyncFromCheckpoint } + if dec.UltraLightServers != nil { c.UltraLightServers = dec.UltraLightServers } + if dec.UltraLightFraction != nil { c.UltraLightFraction = *dec.UltraLightFraction } + if dec.UltraLightOnlyAnnounce != nil { c.UltraLightOnlyAnnounce = *dec.UltraLightOnlyAnnounce } + if dec.SkipBcVersionCheck != nil { c.SkipBcVersionCheck = *dec.SkipBcVersionCheck } + if dec.DatabaseHandles != nil { c.DatabaseHandles = *dec.DatabaseHandles } + if dec.DatabaseCache != nil { c.DatabaseCache = *dec.DatabaseCache } + if dec.DatabaseFreezer != nil { c.DatabaseFreezer = *dec.DatabaseFreezer } + if dec.TrieCleanCache != nil { c.TrieCleanCache = *dec.TrieCleanCache } + if dec.TrieCleanCacheJournal != nil { c.TrieCleanCacheJournal = *dec.TrieCleanCacheJournal } + if dec.TrieCleanCacheRejournal != nil { c.TrieCleanCacheRejournal = *dec.TrieCleanCacheRejournal } + if dec.TrieDirtyCache != nil { c.TrieDirtyCache = *dec.TrieDirtyCache } + if dec.TrieTimeout != nil { c.TrieTimeout = *dec.TrieTimeout } + if dec.SnapshotCache != nil { c.SnapshotCache = *dec.SnapshotCache } + if dec.Preimages != nil { c.Preimages = *dec.Preimages } + if dec.FilterLogCacheSize != nil { c.FilterLogCacheSize = *dec.FilterLogCacheSize } + if dec.Miner != nil { c.Miner = *dec.Miner } + if dec.Ethash != nil { c.Ethash = *dec.Ethash } + if dec.TxPool != nil { c.TxPool = *dec.TxPool } + if dec.GPO != nil { c.GPO = *dec.GPO } + if dec.EnablePreimageRecording != nil { c.EnablePreimageRecording = *dec.EnablePreimageRecording } + if dec.DocRoot != nil { c.DocRoot = *dec.DocRoot } + if dec.RPCGasCap != nil { c.RPCGasCap = *dec.RPCGasCap } + if dec.RPCEVMTimeout != nil { c.RPCEVMTimeout = *dec.RPCEVMTimeout } + if dec.RPCTxFeeCap != nil { c.RPCTxFeeCap = *dec.RPCTxFeeCap } + if dec.Checkpoint != nil { c.Checkpoint = dec.Checkpoint } + if dec.CheckpointOracle != nil { c.CheckpointOracle = dec.CheckpointOracle } @@ -290,5 +335,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.OverrideShanghai != nil { c.OverrideShanghai = dec.OverrideShanghai } + return nil } diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 35608031d9..589fc10fd9 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -141,6 +141,7 @@ func (inject *blockOrHeaderInject) number() uint64 { if inject.header != nil { return inject.header.Number.Uint64() } + return inject.block.NumberU64() } @@ -149,6 +150,7 @@ func (inject *blockOrHeaderInject) hash() common.Hash { if inject.header != nil { return inject.header.Hash() } + return inject.block.Hash() } @@ -336,8 +338,10 @@ func (f *BlockFetcher) loop() { fetchTimer = time.NewTimer(0) completeTimer = time.NewTimer(0) ) + <-fetchTimer.C // clear out the channel <-completeTimer.C + defer fetchTimer.Stop() defer completeTimer.Stop() @@ -350,8 +354,10 @@ func (f *BlockFetcher) loop() { } // Import any queued blocks that could potentially fit height := f.chainHeight() + for !f.queue.Empty() { op := f.queue.PopItem() + hash := op.hash() if f.queueChangeHook != nil { f.queueChangeHook(hash, false) @@ -360,9 +366,11 @@ func (f *BlockFetcher) loop() { number := op.number() if number > height+1 { f.queue.Push(op, -int64(number)) + if f.queueChangeHook != nil { f.queueChangeHook(hash, true) } + break } // Otherwise if fresh and still unknown, try and import @@ -370,6 +378,7 @@ func (f *BlockFetcher) loop() { f.forgetBlock(hash) continue } + if f.light { f.importHeaders(op.origin, op.header) } else { @@ -390,8 +399,10 @@ func (f *BlockFetcher) loop() { if count > hashLimit { log.Debug("Peer exceeded outstanding announces", "peer", notification.origin, "limit", hashLimit) blockAnnounceDOSMeter.Mark(1) + break } + if notification.number == 0 { break } @@ -399,20 +410,25 @@ func (f *BlockFetcher) loop() { if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist { log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist) blockAnnounceDropMeter.Mark(1) + break } // All is well, schedule the announce if block's not yet downloading if _, ok := f.fetching[notification.hash]; ok { break } + if _, ok := f.completing[notification.hash]; ok { break } + f.announces[notification.origin] = count f.announced[notification.hash] = append(f.announced[notification.hash], notification) + if f.announceChangeHook != nil && len(f.announced[notification.hash]) == 1 { f.announceChangeHook(notification.hash, true) } + if len(f.announced) == 1 { f.rescheduleFetch(fetchTimer) } @@ -426,6 +442,7 @@ func (f *BlockFetcher) loop() { if f.light { continue } + f.enqueue(op.origin, nil, op.block) case hash := <-f.done: @@ -444,9 +461,11 @@ func (f *BlockFetcher) loop() { if f.light { timeout = 0 } + if time.Since(announces[0].time) > timeout { // Pick a random peer to retrieve from, reset all others announce := announces[rand.Intn(len(announces))] + f.forgetHash(hash) // If the block still didn't arrive, queue for fetching @@ -466,8 +485,10 @@ func (f *BlockFetcher) loop() { if f.fetchingHook != nil { f.fetchingHook(hashes) } + for _, hash := range hashes { headerFetchMeter.Mark(1) + go func(hash common.Hash) { resCh := make(chan *eth.Response) @@ -506,6 +527,7 @@ func (f *BlockFetcher) loop() { for hash, announces := range f.fetched { // Pick a random peer to retrieve from, reset all others announce := announces[rand.Intn(len(announces))] + f.forgetHash(hash) // If the block still didn't arrive, queue for completion @@ -522,6 +544,7 @@ func (f *BlockFetcher) loop() { if f.completingHook != nil { f.completingHook(hashes) } + fetchBodies := f.completing[hashes[0]].fetchBodies bodyFetchMeter.Mark(int64(len(hashes))) @@ -571,6 +594,7 @@ func (f *BlockFetcher) loop() { // Split the batch of headers into unknown ones (to return to the caller), // known incomplete ones (requiring body retrievals) and completed blocks. unknown, incomplete, complete, lightHeaders := []*types.Header{}, []*blockAnnounce{}, []*types.Block{}, []*blockAnnounce{} + for _, header := range task.headers { hash := header.Hash() @@ -581,6 +605,7 @@ func (f *BlockFetcher) loop() { log.Trace("Invalid block number fetched", "peer", announce.origin, "hash", header.Hash(), "announced", announce.number, "provided", header.Number) f.dropPeer(announce.origin) f.forgetHash(hash) + continue } // Collect all headers only if we are running in light @@ -590,7 +615,9 @@ func (f *BlockFetcher) loop() { announce.header = header lightHeaders = append(lightHeaders, announce) } + f.forgetHash(hash) + continue } // Only keep if not imported by other means @@ -607,6 +634,7 @@ func (f *BlockFetcher) loop() { complete = append(complete, block) f.completing[hash] = announce + continue } // Otherwise add to the list of blocks needing completion @@ -620,6 +648,7 @@ func (f *BlockFetcher) loop() { unknown = append(unknown, header) } } + headerFilterOutMeter.Mark(int64(len(unknown))) select { case filter <- &headerFilterTask{headers: unknown, time: task.time}: @@ -632,6 +661,7 @@ func (f *BlockFetcher) loop() { if _, ok := f.completing[hash]; ok { continue } + f.fetched[hash] = append(f.fetched[hash], announce) if len(f.fetched) == 1 { f.rescheduleComplete(completeTimer) @@ -657,6 +687,7 @@ func (f *BlockFetcher) loop() { return } bodyFilterInMeter.Mark(int64(len(task.transactions))) + blocks := []*types.Block{} // abort early if there's nothing explicitly requested if len(f.completing) > 0 { @@ -667,24 +698,30 @@ func (f *BlockFetcher) loop() { uncleHash common.Hash // calculated lazily and reused txnHash common.Hash // calculated lazily and reused ) + for hash, announce := range f.completing { if f.queued[hash] != nil || announce.origin != task.peer { continue } + if uncleHash == (common.Hash{}) { uncleHash = types.CalcUncleHash(task.uncles[i]) } + if uncleHash != announce.header.UncleHash { continue } + if txnHash == (common.Hash{}) { txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), trie.NewStackTrie(nil)) } + if txnHash != announce.header.TxHash { continue } // Mark the body matched, reassemble if still unknown matched = true + if f.getBlock(hash) == nil { block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i]) block.ReceivedAt = task.time @@ -693,14 +730,17 @@ func (f *BlockFetcher) loop() { f.forgetHash(hash) } } + if matched { task.transactions = append(task.transactions[:i], task.transactions[i+1:]...) task.uncles = append(task.uncles[:i], task.uncles[i+1:]...) i-- + continue } } } + bodyFilterOutMeter.Mark(int64(len(task.transactions))) select { case filter <- task: @@ -736,6 +776,7 @@ func (f *BlockFetcher) rescheduleFetch(fetch *time.Timer) { earliest = announces[0].time } } + fetch.Reset(arriveTimeout - time.Since(earliest)) } @@ -752,6 +793,7 @@ func (f *BlockFetcher) rescheduleComplete(complete *time.Timer) { earliest = announces[0].time } } + complete.Reset(gatherSlack - time.Since(earliest)) } @@ -762,6 +804,7 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B hash common.Hash number uint64 ) + if header != nil { hash, number = header.Hash(), header.Number.Uint64() } else { @@ -773,6 +816,7 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B log.Debug("Discarded delivered header or block, exceeded allowance", "peer", peer, "number", number, "hash", hash, "limit", blockLimit) blockBroadcastDOSMeter.Mark(1) f.forgetHash(hash) + return } // Discard any past or too distant blocks @@ -780,6 +824,7 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B log.Debug("Discarded delivered header or block, too far away", "peer", peer, "number", number, "hash", hash, "distance", dist) blockBroadcastDropMeter.Mark(1) f.forgetHash(hash) + return } // Schedule the block for future importing @@ -790,12 +835,15 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B } else { op.block = block } + f.queues[peer] = count f.queued[hash] = op f.queue.Push(op, -int64(number)) + if f.queueChangeHook != nil { f.queueChangeHook(hash, true) } + log.Debug("Queued delivered header or block", "peer", peer, "number", number, "hash", hash, "queued", f.queue.Size()) } } @@ -819,6 +867,7 @@ func (f *BlockFetcher) importHeaders(peer string, header *types.Header) { if err := f.verifyHeader(header); err != nil && err != consensus.ErrFutureBlock { log.Debug("Propagated header verification failed", "peer", peer, "number", header.Number, "hash", hash, "err", err) f.dropPeer(peer) + return } // Run the actual import and log any issues @@ -841,6 +890,7 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { // Run the import on a new thread log.Debug("Importing propagated block", "peer", peer, "number", block.Number(), "hash", hash) + go func() { defer func() { f.done <- hash }() @@ -855,6 +905,7 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { case nil: // All ok, quickly propagate to our peers blockBroadcastOutTimer.UpdateSince(block.ReceivedAt) + go f.broadcastBlock(block, true) case consensus.ErrFutureBlock: @@ -864,6 +915,7 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { // Something went very wrong, drop the peer log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err) f.dropPeer(peer) + return } // Run the actual import and log any issues @@ -873,6 +925,7 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { } // If import succeeded, broadcast the block blockAnnounceOutTimer.UpdateSince(block.ReceivedAt) + go f.broadcastBlock(block, false) // Invoke the testing hook if needed @@ -893,7 +946,9 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) { delete(f.announces, announce.origin) } } + delete(f.announced, hash) + if f.announceChangeHook != nil { f.announceChangeHook(hash, false) } @@ -904,6 +959,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) { if f.announces[announce.origin] <= 0 { delete(f.announces, announce.origin) } + delete(f.fetching, hash) } @@ -914,6 +970,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) { delete(f.announces, announce.origin) } } + delete(f.fetched, hash) // Remove any pending completions and decrement the DOS counters @@ -922,6 +979,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) { if f.announces[announce.origin] <= 0 { delete(f.announces, announce.origin) } + delete(f.completing, hash) } } @@ -934,6 +992,7 @@ func (f *BlockFetcher) forgetBlock(hash common.Hash) { if f.queues[insert.origin] == 0 { delete(f.queues, insert.origin) } + delete(f.queued, hash) } } diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index 9e5693c02e..38ce2400b7 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -59,10 +59,12 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common // If the block number is multiple of 3, send a bonus transaction to the miner if parent == genesis && i%3 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) if err != nil { panic(err) } + block.AddTx(tx) } // If the block number is a multiple of 5, add a bonus uncle to the block @@ -74,10 +76,12 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common hashes[len(hashes)-1] = parent.Hash() blockm := make(map[common.Hash]*types.Block, n+1) blockm[parent.Hash()] = parent + for i, b := range blocks { hashes[len(hashes)-i-2] = b.Hash() blockm[b.Hash()] = b } + return hashes, blockm } @@ -140,6 +144,7 @@ func (f *fetcherTester) chainHeight() uint64 { if f.fetcher.light { return f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64() } + return f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() } @@ -161,6 +166,7 @@ func (f *fetcherTester) insertHeaders(headers []*types.Header) (int, error) { f.hashes = append(f.hashes, header.Hash()) f.headers[header.Hash()] = header } + return 0, nil } @@ -182,6 +188,7 @@ func (f *fetcherTester) insertChain(blocks types.Blocks) (int, error) { f.hashes = append(f.hashes, block.Hash()) f.blocks[block.Hash()] = block } + return 0, nil } @@ -217,9 +224,11 @@ func (f *fetcherTester) makeHeaderFetcher(peer string, blocks map[common.Hash]*t Time: drift, Done: make(chan error, 1), // Ignore the returned status } + go func() { sink <- res }() + return req, nil } } @@ -250,6 +259,7 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ Uncles: uncles[i], } } + req := ð.Request{ Peer: peer, } @@ -259,9 +269,11 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ Time: drift, Done: make(chan error, 1), // Ignore the returned status } + go func() { sink <- res }() + return req, nil } } @@ -375,6 +387,7 @@ func testSequentialAnnouncements(t *testing.T, light bool) { // Iteratively announce blocks until all are imported imported := make(chan interface{}) + tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { if light { if header == nil { @@ -424,6 +437,7 @@ func testConcurrentAnnouncements(t *testing.T, light bool) { } // Iteratively announce blocks until all are imported imported := make(chan interface{}) + tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { if light { if header == nil { @@ -449,6 +463,7 @@ func testConcurrentAnnouncements(t *testing.T, light bool) { if int(counter) != targetBlocks { t.Fatalf("retrieval count mismatch: have %v, want %v", counter, targetBlocks) } + verifyChainHeight(t, tester, uint64(len(hashes)-1)) } @@ -469,9 +484,11 @@ func testOverlappingAnnouncements(t *testing.T, light bool) { // Iteratively announce blocks, but overlap them continuously overlap := 16 imported := make(chan interface{}, len(hashes)-1) + for i := 0; i < overlap; i++ { imported <- nil } + tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { if light { if header == nil { @@ -519,16 +536,20 @@ func testPendingDeduplication(t *testing.T, light bool) { // Simulate a long running fetch resink := make(chan *eth.Response) + req, err := headerFetcher(hash, resink) if err == nil { go func() { res := <-resink + time.Sleep(delay) sink <- res }() } + return req, err } + checkNonExist := func() bool { return tester.getBlock(hashes[0]) == nil } @@ -548,6 +569,7 @@ func testPendingDeduplication(t *testing.T, light bool) { if int(counter) != 1 { t.Fatalf("retrieval count mismatch: have %v, want %v", counter, 1) } + verifyChainHeight(t, tester, 1) } @@ -581,6 +603,7 @@ func testRandomArrivalImport(t *testing.T, light bool) { imported <- block } } + for i := len(hashes) - 1; i >= 0; i-- { if i != skip { tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) @@ -680,12 +703,14 @@ func TestDistantPropagationDiscarding(t *testing.T) { // Ensure that a block with a lower number than the threshold is discarded tester.fetcher.Enqueue("lower", blocks[hashes[low]]) time.Sleep(10 * time.Millisecond) + if !tester.fetcher.queue.Empty() { t.Fatalf("fetcher queued stale block") } // Ensure that a block with a higher number than the threshold is discarded tester.fetcher.Enqueue("higher", blocks[hashes[high]]) time.Sleep(10 * time.Millisecond) + if !tester.fetcher.queue.Empty() { t.Fatalf("fetcher queued future block") } @@ -768,6 +793,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { announced <- nil } tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher) + verifyAnnounce := func() { for i := 0; i < 2; i++ { select { @@ -788,6 +814,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { if !dropped { t.Fatalf("peer with invalid numbered announcement not dropped") } + goodHeaderFetcher := tester.makeHeaderFetcher("good", blocks, -gatherSlack) goodBodyFetcher := tester.makeBodyFetcher("good", blocks, 0) // Make sure a good announcement passes without a drop @@ -802,6 +829,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { if dropped { t.Fatalf("peer with valid numbered announcement dropped") } + verifyImportDone(t, imported) } @@ -877,8 +905,10 @@ func TestHashMemoryExhaustionAttack(t *testing.T) { if i < maxQueueDist { tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher) } + tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher) } + if count := atomic.LoadInt32(&announces); count != hashLimit+maxQueueDist { t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist) } @@ -913,6 +943,7 @@ func TestBlockMemoryExhaustionAttack(t *testing.T) { targetBlocks := hashLimit + 2*maxQueueDist hashes, blocks := makeChain(targetBlocks, 0, genesis) attack := make(map[common.Hash]*types.Block) + for i := byte(0); len(attack) < blockLimit+2*maxQueueDist; i++ { hashes, blocks := makeChain(maxQueueDist-1, i, unknownBlock) for _, hash := range hashes[:maxQueueDist-2] { @@ -923,7 +954,9 @@ func TestBlockMemoryExhaustionAttack(t *testing.T) { for _, block := range attack { tester.fetcher.Enqueue("attacker", block) } + time.Sleep(200 * time.Millisecond) + if queued := atomic.LoadInt32(&enqueued); queued != blockLimit { t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit) } @@ -932,6 +965,7 @@ func TestBlockMemoryExhaustionAttack(t *testing.T) { tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-3-i]]) } time.Sleep(100 * time.Millisecond) + if queued := atomic.LoadInt32(&enqueued); queued != blockLimit+maxQueueDist-1 { t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit+maxQueueDist-1) } diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go index 7854a9a5a8..681e4bbc4c 100644 --- a/eth/fetcher/tx_fetcher.go +++ b/eth/fetcher/tx_fetcher.go @@ -193,24 +193,24 @@ func NewTxFetcherForTests( hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, clock mclock.Clock, rand *mrand.Rand, txArrivalWait time.Duration) *TxFetcher { return &TxFetcher{ - notify: make(chan *txAnnounce), - cleanup: make(chan *txDelivery), - drop: make(chan *txDrop), - quit: make(chan struct{}), - waitlist: make(map[common.Hash]map[string]struct{}), - waittime: make(map[common.Hash]mclock.AbsTime), - waitslots: make(map[string]map[common.Hash]struct{}), - announces: make(map[string]map[common.Hash]struct{}), - announced: make(map[common.Hash]map[string]struct{}), - fetching: make(map[common.Hash]string), - requests: make(map[string]*txRequest), - alternates: make(map[common.Hash]map[string]struct{}), - underpriced: mapset.NewSet[common.Hash](), - hasTx: hasTx, - addTxs: addTxs, - fetchTxs: fetchTxs, - clock: clock, - rand: rand, + notify: make(chan *txAnnounce), + cleanup: make(chan *txDelivery), + drop: make(chan *txDrop), + quit: make(chan struct{}), + waitlist: make(map[common.Hash]map[string]struct{}), + waittime: make(map[common.Hash]mclock.AbsTime), + waitslots: make(map[string]map[common.Hash]struct{}), + announces: make(map[string]map[common.Hash]struct{}), + announced: make(map[common.Hash]map[string]struct{}), + fetching: make(map[common.Hash]string), + requests: make(map[string]*txRequest), + alternates: make(map[common.Hash]map[string]struct{}), + underpriced: mapset.NewSet[common.Hash](), + hasTx: hasTx, + addTxs: addTxs, + fetchTxs: fetchTxs, + clock: clock, + rand: rand, txArrivalWait: txArrivalWait, } } @@ -230,6 +230,7 @@ func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error { unknowns = make([]common.Hash, 0, len(hashes)) duplicate, underpriced int64 ) + for _, hash := range hashes { switch { case f.hasTx(hash): @@ -242,6 +243,7 @@ func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error { unknowns = append(unknowns, hash) } } + txAnnounceKnownMeter.Mark(duplicate) txAnnounceUnderpricedMeter.Mark(underpriced) @@ -249,6 +251,7 @@ func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error { if len(unknowns) == 0 { return nil } + announce := &txAnnounce{ origin: peer, hashes: unknowns, @@ -272,6 +275,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) underpricedMeter = txReplyUnderpricedMeter otherRejectMeter = txReplyOtherRejectMeter ) + if !direct { inMeter = txBroadcastInMeter knownMeter = txBroadcastKnownMeter @@ -292,11 +296,13 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) if end > len(txs) { end = len(txs) } + var ( duplicate int64 underpriced int64 otherreject int64 ) + batch := txs[i:end] for j, err := range f.addTxs(batch) { // Track the transaction hash if the price is too low for us. @@ -321,8 +327,10 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) default: otherreject++ } + added = append(added, batch[j].Hash()) } + knownMeter.Mark(duplicate) underpricedMeter.Mark(underpriced) otherRejectMeter.Mark(otherreject) @@ -401,6 +409,7 @@ func (f *TxFetcher) loop() { txAnnounceDOSMeter.Mark(int64(len(ann.hashes))) break } + want := used + len(ann.hashes) if want > maxTxAnnounces { txAnnounceDOSMeter.Mark(int64(want - maxTxAnnounces)) @@ -423,6 +432,7 @@ func (f *TxFetcher) loop() { } else { f.announces[ann.origin] = map[common.Hash]struct{}{hash: {}} } + continue } // If the transaction is not downloading, but is already queued @@ -436,6 +446,7 @@ func (f *TxFetcher) loop() { } else { f.announces[ann.origin] = map[common.Hash]struct{}{hash: {}} } + continue } // If the transaction is already known to the fetcher, but not @@ -449,6 +460,7 @@ func (f *TxFetcher) loop() { } else { f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: {}} } + continue } // Transaction unknown to the fetcher, insert it into the waiting list @@ -475,12 +487,14 @@ func (f *TxFetcher) loop() { // At least one transaction's waiting time ran out, push all expired // ones into the retrieval queues actives := make(map[string]struct{}) + for hash, instance := range f.waittime { if time.Duration(f.clock.Now()-instance)+txGatherSlack > f.txArrivalWait { // Transaction expired without propagation, schedule for retrieval if f.announced[hash] != nil { panic("announce tracker already contains waitlist item") } + f.announced[hash] = f.waitlist[hash] for peer := range f.waitlist[hash] { if announces := f.announces[peer]; announces != nil { @@ -488,12 +502,16 @@ func (f *TxFetcher) loop() { } else { f.announces[peer] = map[common.Hash]struct{}{hash: {}} } + delete(f.waitslots[peer], hash) + if len(f.waitslots[peer]) == 0 { delete(f.waitslots, peer) } + actives[peer] = struct{}{} } + delete(f.waittime, hash) delete(f.waitlist, hash) } @@ -528,17 +546,22 @@ func (f *TxFetcher) loop() { if _, ok := f.announced[hash]; ok { panic("announced tracker already contains alternate item") } + if f.alternates[hash] != nil { // nil if tx was broadcast during fetch f.announced[hash] = f.alternates[hash] } + delete(f.announced[hash], peer) + if len(f.announced[hash]) == 0 { delete(f.announced, hash) } + delete(f.announces[peer], hash) delete(f.alternates, hash) delete(f.fetching, hash) } + if len(f.announces[peer]) == 0 { delete(f.announces, peer) } @@ -560,19 +583,23 @@ func (f *TxFetcher) loop() { if _, ok := f.waitlist[hash]; ok { for peer, txset := range f.waitslots { delete(txset, hash) + if len(txset) == 0 { delete(f.waitslots, peer) } } + delete(f.waitlist, hash) delete(f.waittime, hash) } else { for peer, txset := range f.announces { delete(txset, hash) + if len(txset) == 0 { delete(f.announces, peer) } } + delete(f.announced, hash) delete(f.alternates, hash) @@ -585,8 +612,10 @@ func (f *TxFetcher) loop() { f.requests[origin].stolen = make(map[common.Hash]struct{}) stolen = f.requests[origin].stolen } + stolen[hash] = struct{}{} } + delete(f.fetching, hash) } } @@ -602,6 +631,7 @@ func (f *TxFetcher) loop() { log.Warn("Unexpected transaction delivery", "peer", delivery.origin) break } + delete(f.requests, delivery.origin) // Anything not delivered should be re-scheduled (with or without @@ -610,7 +640,9 @@ func (f *TxFetcher) loop() { for _, hash := range delivery.hashes { delivered[hash] = struct{}{} } + cutoff := len(req.hashes) // If nothing is delivered, assume everything is missing, don't retry!!! + for i, hash := range req.hashes { if _, ok := delivered[hash]; ok { cutoff = i @@ -624,21 +656,26 @@ func (f *TxFetcher) loop() { continue } } + if _, ok := delivered[hash]; !ok { if i < cutoff { delete(f.alternates[hash], delivery.origin) delete(f.announces[delivery.origin], hash) + if len(f.announces[delivery.origin]) == 0 { delete(f.announces, delivery.origin) } } + if len(f.alternates[hash]) > 0 { if _, ok := f.announced[hash]; ok { panic(fmt.Sprintf("announced tracker already contains alternate item: %v", f.announced[hash])) } + f.announced[hash] = f.alternates[hash] } } + delete(f.alternates, hash) delete(f.fetching, hash) } @@ -651,12 +688,15 @@ func (f *TxFetcher) loop() { if _, ok := f.waitslots[drop.peer]; ok { for hash := range f.waitslots[drop.peer] { delete(f.waitlist[hash], drop.peer) + if len(f.waitlist[hash]) == 0 { delete(f.waitlist, hash) delete(f.waittime, hash) } } + delete(f.waitslots, drop.peer) + if len(f.waitlist) > 0 { f.rescheduleWait(waitTimer, waitTrigger) } @@ -673,24 +713,29 @@ func (f *TxFetcher) loop() { } // Undelivered hash, reschedule if there's an alternative origin available delete(f.alternates[hash], drop.peer) + if len(f.alternates[hash]) == 0 { delete(f.alternates, hash) } else { f.announced[hash] = f.alternates[hash] delete(f.alternates, hash) } + delete(f.fetching, hash) } + delete(f.requests, drop.peer) } // Clean up general announcement tracking if _, ok := f.announces[drop.peer]; ok { for hash := range f.announces[drop.peer] { delete(f.announced[hash], drop.peer) + if len(f.announced[hash]) == 0 { delete(f.announced, hash) } } + delete(f.announces, drop.peer) } // If a request was cancelled, check if anything needs to be rescheduled @@ -727,6 +772,7 @@ func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) { if *timer != nil { (*timer).Stop() } + now := f.clock.Now() earliest := now @@ -763,14 +809,17 @@ func (f *TxFetcher) rescheduleTimeout(timer *mclock.Timer, trigger chan struct{} if *timer != nil { (*timer).Stop() } + now := f.clock.Now() earliest := now + for _, req := range f.requests { // If this request already timed out, skip it altogether if req.hashes == nil { continue } + if earliest > req.time { earliest = req.time if txFetchTimeout-time.Duration(now-earliest) < gatherSlack { @@ -778,6 +827,7 @@ func (f *TxFetcher) rescheduleTimeout(timer *mclock.Timer, trigger chan struct{} } } } + *timer = f.clock.AfterFunc(txFetchTimeout-time.Duration(now-earliest), func() { trigger <- struct{}{} }) @@ -793,6 +843,7 @@ func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, actives[peer] = struct{}{} } } + if len(actives) == 0 { return } @@ -803,10 +854,13 @@ func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, if f.requests[peer] != nil { return // continue in the for-each } + if len(f.announces[peer]) == 0 { return // continue in the for-each } + hashes := make([]common.Hash, 0, maxTxRetrievals) + f.forEachHash(f.announces[peer], func(hash common.Hash) bool { if _, ok := f.fetching[hash]; !ok { // Mark the hash as fetching and stash away possible alternates @@ -815,6 +869,7 @@ func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, if _, ok := f.alternates[hash]; ok { panic(fmt.Sprintf("alternate tracker already contains fetching item: %v", f.alternates[hash])) } + f.alternates[hash] = f.announced[hash] delete(f.announced, hash) @@ -824,6 +879,7 @@ func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, return false // break in the for-each } } + return true // continue in the for-each }) // If any hashes were allocated, request them from the peer @@ -855,6 +911,7 @@ func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string)) for peer := range peers { do(peer) } + return } // We're running the test suite, make iteration deterministic @@ -862,8 +919,10 @@ func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string)) for peer := range peers { list = append(list, peer) } + sort.Strings(list) rotateStrings(list, f.rand.Intn(len(list))) + for _, peer := range list { do(peer) } @@ -879,6 +938,7 @@ func (f *TxFetcher) forEachHash(hashes map[common.Hash]struct{}, do func(hash co return } } + return } // We're running the test suite, make iteration deterministic @@ -886,8 +946,10 @@ func (f *TxFetcher) forEachHash(hashes map[common.Hash]struct{}, do func(hash co for hash := range hashes { list = append(list, hash) } + sortHashes(list) rotateHashes(list, f.rand.Intn(len(list))) + for _, hash := range list { if !do(hash) { return diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go index 3ac65813d3..c06f5ae011 100644 --- a/eth/fetcher/tx_fetcher_test.go +++ b/eth/fetcher/tx_fetcher_test.go @@ -308,6 +308,7 @@ func TestTransactionFetcherSingletonRequesting(t *testing.T) { func TestTransactionFetcherFailedRescheduling(t *testing.T) { // Create a channel to control when tx requests can fail proceed := make(chan struct{}) + testTransactionFetcherParallel(t, txFetcherTest{ init: func() *TxFetcher { return NewTxFetcher( @@ -814,6 +815,7 @@ func TestTransactionFetcherDoSProtection(t *testing.T) { for i := 0; i < maxTxAnnounces+1; i++ { hashesA = append(hashesA, common.Hash{0x01, byte(i / 256), byte(i % 256)}) } + var hashesB []common.Hash for i := 0; i < maxTxAnnounces+1; i++ { hashesB = append(hashesB, common.Hash{0x02, byte(i / 256), byte(i % 256)}) @@ -925,6 +927,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) { for i := 0; i < maxTxUnderpricedSetSize+1; i++ { txs = append(txs, types.NewTransaction(rand.Uint64(), common.Address{byte(rand.Intn(256))}, new(big.Int), 0, new(big.Int), nil)) } + hashes := make([]common.Hash, len(txs)) for i, tx := range txs { hashes[i] = tx.Hash() @@ -1303,6 +1306,7 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { if err := fetcher.Notify(step.peer, step.hashes); err != nil { t.Errorf("step %d: %v", i, err) } + <-wait // Fetcher needs to process this, wait until it's done select { case <-wait: @@ -1314,10 +1318,12 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { if err := fetcher.Enqueue(step.peer, step.txs, step.direct); err != nil { t.Errorf("step %d: %v", i, err) } + <-wait // Fetcher needs to process this, wait until it's done case doWait: clock.Run(step.time) + if step.step { <-wait // Fetcher supposed to do something, wait until it's done } @@ -1326,6 +1332,7 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { if err := fetcher.Drop(string(step)); err != nil { t.Errorf("step %d: %v", i, err) } + <-wait // Fetcher needs to process this, wait until it's done case doFunc: @@ -1341,17 +1348,20 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { t.Errorf("step %d: peer %s missing from waitslots", i, peer) continue } + for _, hash := range hashes { if _, ok := waiting[hash]; !ok { t.Errorf("step %d, peer %s: hash %x missing from waitslots", i, peer, hash) } } + for hash := range waiting { if !containsHash(hashes, hash) { t.Errorf("step %d, peer %s: hash %x extra in waitslots", i, peer, hash) } } } + for peer := range fetcher.waitslots { if _, ok := step[peer]; !ok { t.Errorf("step %d: peer %s extra in waitslots", i, peer) @@ -1363,29 +1373,35 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { if _, ok := fetcher.waitlist[hash][peer]; !ok { t.Errorf("step %d, hash %x: peer %s missing from waitlist", i, hash, peer) } + if _, ok := fetcher.waittime[hash]; !ok { t.Errorf("step %d: hash %x missing from waittime", i, hash) } } } + for hash, peers := range fetcher.waitlist { if len(peers) == 0 { t.Errorf("step %d, hash %x: empty peerset in waitlist", i, hash) } + for peer := range peers { if !containsHash(step[peer], hash) { t.Errorf("step %d, hash %x: peer %s extra in waitlist", i, hash, peer) } } } + for hash := range fetcher.waittime { var found bool + for _, hashes := range step { if containsHash(hashes, hash) { found = true break } } + if !found { t.Errorf("step %d,: hash %x extra in waittime", i, hash) } @@ -1400,17 +1416,20 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { t.Errorf("step %d: peer %s missing from announces", i, peer) continue } + for _, hash := range hashes { if _, ok := scheduled[hash]; !ok { t.Errorf("step %d, peer %s: hash %x missing from announces", i, peer, hash) } } + for hash := range scheduled { if !containsHash(hashes, hash) { t.Errorf("step %d, peer %s: hash %x extra in announces", i, peer, hash) } } } + for peer := range fetcher.announces { if _, ok := step.tracking[peer]; !ok { t.Errorf("step %d: peer %s extra in announces", i, peer) @@ -1424,17 +1443,20 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { t.Errorf("step %d: peer %s missing from requests", i, peer) continue } + for _, hash := range hashes { if !containsHash(request.hashes, hash) { t.Errorf("step %d, peer %s: hash %x missing from requests", i, peer, hash) } } + for _, hash := range request.hashes { if !containsHash(hashes, hash) { t.Errorf("step %d, peer %s: hash %x extra in requests", i, peer, hash) } } } + for peer := range fetcher.requests { if _, ok := step.fetching[peer]; !ok { if _, ok := step.dangling[peer]; !ok { @@ -1442,6 +1464,7 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { } } } + for peer, hashes := range step.fetching { for _, hash := range hashes { if _, ok := fetcher.fetching[hash]; !ok { @@ -1449,18 +1472,22 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { } } } + for hash := range fetcher.fetching { var found bool + for _, req := range fetcher.requests { if containsHash(req.hashes, hash) { found = true break } } + if !found { t.Errorf("step %d: hash %x extra in fetching", i, hash) } } + for _, hashes := range step.fetching { for _, hash := range hashes { alternates := fetcher.alternates[hash] @@ -1468,16 +1495,19 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { t.Errorf("step %d: hash %x missing from alternates", i, hash) continue } + for peer := range alternates { if _, ok := fetcher.announces[peer]; !ok { t.Errorf("step %d: peer %s extra in alternates", i, peer) continue } + if _, ok := fetcher.announces[peer][hash]; !ok { t.Errorf("step %d, peer %s: hash %x extra in alternates", i, hash, peer) continue } } + for p := range fetcher.announced[hash] { if _, ok := alternates[p]; !ok { t.Errorf("step %d, hash %x: peer %s missing from alternates", i, hash, p) @@ -1486,17 +1516,20 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { } } } + for peer, hashes := range step.dangling { request := fetcher.requests[peer] if request == nil { t.Errorf("step %d: peer %s missing from requests", i, peer) continue } + for _, hash := range hashes { if !containsHash(request.hashes, hash) { t.Errorf("step %d, peer %s: hash %x missing from requests", i, peer, hash) } } + for _, hash := range request.hashes { if !containsHash(hashes, hash) { t.Errorf("step %d, peer %s: hash %x extra in requests", i, peer, hash) @@ -1507,25 +1540,30 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { // retrieval but not actively being downloaded are tracked only // in the stage 2 `announced` map. var queued []common.Hash + for _, hashes := range step.tracking { for _, hash := range hashes { var found bool + for _, hs := range step.fetching { if containsHash(hs, hash) { found = true break } } + if !found { queued = append(queued, hash) } } } + for _, hash := range queued { if _, ok := fetcher.announced[hash]; !ok { t.Errorf("step %d: hash %x missing from announced", i, hash) } } + for hash := range fetcher.announced { if !containsHash(queued, hash) { t.Errorf("step %d: hash %x extra in announced", i, hash) @@ -1557,5 +1595,6 @@ func containsHash(slice []common.Hash, hash common.Hash) bool { return true } } + return false } diff --git a/eth/filters/IBackend.go b/eth/filters/IBackend.go index 369e8af562..3216cd46a7 100644 --- a/eth/filters/IBackend.go +++ b/eth/filters/IBackend.go @@ -34,6 +34,7 @@ type MockBackendMockRecorder struct { func NewMockBackend(ctrl *gomock.Controller) *MockBackend { mock := &MockBackend{ctrl: ctrl} mock.recorder = &MockBackendMockRecorder{mock} + return mock } @@ -48,6 +49,7 @@ func (m *MockBackend) BloomStatus() (uint64, uint64) { ret := m.ctrl.Call(m, "BloomStatus") ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(uint64) + return ret0, ret1 } @@ -62,6 +64,7 @@ func (m *MockBackend) ChainConfig() *params.ChainConfig { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ChainConfig") ret0, _ := ret[0].(*params.ChainConfig) + return ret0 } @@ -76,6 +79,7 @@ func (m *MockBackend) ChainDb() ethdb.Database { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ChainDb") ret0, _ := ret[0].(ethdb.Database) + return ret0 } @@ -90,6 +94,7 @@ func (m *MockBackend) CurrentHeader() *types.Header { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CurrentHeader") ret0, _ := ret[0].(*types.Header) + return ret0 } @@ -105,6 +110,7 @@ func (m *MockBackend) GetBody(ctx context.Context, hash common.Hash, number rpc. ret := m.ctrl.Call(m, "GetBody", ctx, hash, number) ret0, _ := ret[0].(*types.Body) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -120,6 +126,7 @@ func (m *MockBackend) GetBorBlockLogs(ctx context.Context, blockHash common.Hash ret := m.ctrl.Call(m, "GetBorBlockLogs", ctx, blockHash) ret0, _ := ret[0].([]*types.Log) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -135,6 +142,7 @@ func (m *MockBackend) GetBorBlockReceipt(ctx context.Context, blockHash common.H ret := m.ctrl.Call(m, "GetBorBlockReceipt", ctx, blockHash) ret0, _ := ret[0].(*types.Receipt) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -150,6 +158,7 @@ func (m *MockBackend) GetLogs(ctx context.Context, blockHash common.Hash, number ret := m.ctrl.Call(m, "GetLogs", ctx, blockHash, number) ret0, _ := ret[0].([][]*types.Log) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -165,6 +174,7 @@ func (m *MockBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (t ret := m.ctrl.Call(m, "GetReceipts", ctx, blockHash) ret0, _ := ret[0].(types.Receipts) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -180,6 +190,7 @@ func (m *MockBackend) HeaderByHash(ctx context.Context, blockHash common.Hash) ( ret := m.ctrl.Call(m, "HeaderByHash", ctx, blockHash) ret0, _ := ret[0].(*types.Header) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -195,6 +206,7 @@ func (m *MockBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe ret := m.ctrl.Call(m, "HeaderByNumber", ctx, blockNr) ret0, _ := ret[0].(*types.Header) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -210,6 +222,7 @@ func (m *MockBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { ret := m.ctrl.Call(m, "PendingBlockAndReceipts") ret0, _ := ret[0].(*types.Block) ret1, _ := ret[1].(types.Receipts) + return ret0, ret1 } @@ -236,6 +249,7 @@ func (m *MockBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subsc m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubscribeChainEvent", ch) ret0, _ := ret[0].(event.Subscription) + return ret0 } @@ -250,6 +264,7 @@ func (m *MockBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubscribeLogsEvent", ch) ret0, _ := ret[0].(event.Subscription) + return ret0 } @@ -264,6 +279,7 @@ func (m *MockBackend) SubscribeNewTxsEvent(arg0 chan<- core.NewTxsEvent) event.S m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubscribeNewTxsEvent", arg0) ret0, _ := ret[0].(event.Subscription) + return ret0 } @@ -278,6 +294,7 @@ func (m *MockBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Su m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubscribePendingLogsEvent", ch) ret0, _ := ret[0].(event.Subscription) + return ret0 } @@ -292,6 +309,7 @@ func (m *MockBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubscribeRemovedLogsEvent", ch) ret0, _ := ret[0].(event.Subscription) + return ret0 } @@ -306,6 +324,7 @@ func (m *MockBackend) SubscribeStateSyncEvent(ch chan<- core.StateSyncEvent) eve m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubscribeStateSyncEvent", ch) ret0, _ := ret[0].(event.Subscription) + return ret0 } diff --git a/eth/filters/IDatabase.go b/eth/filters/IDatabase.go index fd0824b60d..83a172e64e 100644 --- a/eth/filters/IDatabase.go +++ b/eth/filters/IDatabase.go @@ -26,6 +26,7 @@ type MockDatabaseMockRecorder struct { func NewMockDatabase(ctrl *gomock.Controller) *MockDatabase { mock := &MockDatabase{ctrl: ctrl} mock.recorder = &MockDatabaseMockRecorder{mock} + return mock } @@ -40,6 +41,7 @@ func (m *MockDatabase) Ancient(arg0 string, arg1 uint64) ([]byte, error) { ret := m.ctrl.Call(m, "Ancient", arg0, arg1) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -55,6 +57,7 @@ func (m *MockDatabase) AncientRange(arg0 string, arg1, arg2, arg3 uint64) ([][]b ret := m.ctrl.Call(m, "AncientRange", arg0, arg1, arg2, arg3) ret0, _ := ret[0].([][]byte) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -70,6 +73,7 @@ func (m *MockDatabase) AncientSize(arg0 string) (uint64, error) { ret := m.ctrl.Call(m, "AncientSize", arg0) ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -85,6 +89,7 @@ func (m *MockDatabase) Ancients() (uint64, error) { ret := m.ctrl.Call(m, "Ancients") ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -99,6 +104,7 @@ func (m *MockDatabase) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) + return ret0 } @@ -113,6 +119,7 @@ func (m *MockDatabase) Compact(arg0, arg1 []byte) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Compact", arg0, arg1) ret0, _ := ret[0].(error) + return ret0 } @@ -127,6 +134,7 @@ func (m *MockDatabase) Delete(arg0 []byte) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Delete", arg0) ret0, _ := ret[0].(error) + return ret0 } @@ -142,6 +150,7 @@ func (m *MockDatabase) Get(arg0 []byte) ([]byte, error) { ret := m.ctrl.Call(m, "Get", arg0) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -157,6 +166,7 @@ func (m *MockDatabase) Has(arg0 []byte) (bool, error) { ret := m.ctrl.Call(m, "Has", arg0) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -172,6 +182,7 @@ func (m *MockDatabase) HasAncient(arg0 string, arg1 uint64) (bool, error) { ret := m.ctrl.Call(m, "HasAncient", arg0, arg1) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -186,6 +197,7 @@ func (m *MockDatabase) MigrateTable(arg0 string, arg1 func([]byte) ([]byte, erro m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MigrateTable", arg0, arg1) ret0, _ := ret[0].(error) + return ret0 } @@ -201,6 +213,7 @@ func (m *MockDatabase) ModifyAncients(arg0 func(ethdb.AncientWriteOp) error) (in ret := m.ctrl.Call(m, "ModifyAncients", arg0) ret0, _ := ret[0].(int64) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -215,6 +228,7 @@ func (m *MockDatabase) NewBatch() ethdb.Batch { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewBatch") ret0, _ := ret[0].(ethdb.Batch) + return ret0 } @@ -229,6 +243,7 @@ func (m *MockDatabase) NewBatchWithSize(arg0 int) ethdb.Batch { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewBatchWithSize", arg0) ret0, _ := ret[0].(ethdb.Batch) + return ret0 } @@ -243,6 +258,7 @@ func (m *MockDatabase) NewIterator(arg0, arg1 []byte) ethdb.Iterator { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewIterator", arg0, arg1) ret0, _ := ret[0].(ethdb.Iterator) + return ret0 } @@ -258,6 +274,7 @@ func (m *MockDatabase) NewSnapshot() (ethdb.Snapshot, error) { ret := m.ctrl.Call(m, "NewSnapshot") ret0, _ := ret[0].(ethdb.Snapshot) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -272,6 +289,7 @@ func (m *MockDatabase) Put(arg0, arg1 []byte) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Put", arg0, arg1) ret0, _ := ret[0].(error) + return ret0 } @@ -286,6 +304,7 @@ func (m *MockDatabase) ReadAncients(arg0 func(ethdb.AncientReader) error) error m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ReadAncients", arg0) ret0, _ := ret[0].(error) + return ret0 } @@ -301,6 +320,7 @@ func (m *MockDatabase) Stat(arg0 string) (string, error) { ret := m.ctrl.Call(m, "Stat", arg0) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -315,6 +335,7 @@ func (m *MockDatabase) Sync() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Sync") ret0, _ := ret[0].(error) + return ret0 } @@ -330,6 +351,7 @@ func (m *MockDatabase) Tail() (uint64, error) { ret := m.ctrl.Call(m, "Tail") ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -344,6 +366,7 @@ func (m *MockDatabase) TruncateHead(arg0 uint64) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "TruncateHead", arg0) ret0, _ := ret[0].(error) + return ret0 } @@ -358,6 +381,7 @@ func (m *MockDatabase) TruncateTail(arg0 uint64) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "TruncateTail", arg0) ret0, _ := ret[0].(error) + return ret0 } diff --git a/eth/filters/api.go b/eth/filters/api.go index 77959bcdae..39d9eb793b 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -78,8 +78,10 @@ func NewFilterAPI(system *FilterSystem, lightMode bool, borLogs bool) *FilterAPI // that have not been recently used. It is started when the API is created. func (api *FilterAPI) timeoutLoop(timeout time.Duration) { var toUninstall []*Subscription + ticker := time.NewTicker(timeout) defer ticker.Stop() + for { <-ticker.C api.filtersMu.Lock() @@ -87,6 +89,7 @@ func (api *FilterAPI) timeoutLoop(timeout time.Duration) { select { case <-f.deadline.C: toUninstall = append(toUninstall, f.s) + delete(api.filters, id) default: continue @@ -100,6 +103,7 @@ func (api *FilterAPI) timeoutLoop(timeout time.Duration) { for _, s := range toUninstall { s.Unsubscribe() } + toUninstall = nil } } @@ -132,6 +136,7 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID { api.filtersMu.Lock() delete(api.filters, pendingTxSub.ID) api.filtersMu.Unlock() + return } } @@ -162,6 +167,7 @@ func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) // To keep the original behaviour, send a single tx hash in one notification. // TODO(rjl493456442) Send a batch of tx hashes in one notification latest := api.sys.backend.CurrentHeader() + for _, tx := range txs { if fullTx != nil && *fullTx { rpcTx := ethapi.NewRPCPendingTransaction(tx, latest, chainConfig) @@ -208,6 +214,7 @@ func (api *FilterAPI) NewBlockFilter() rpc.ID { api.filtersMu.Lock() delete(api.filters, headerSub.ID) api.filtersMu.Unlock() + return } } @@ -301,6 +308,7 @@ type FilterCriteria ethereum.FilterQuery // In case "fromBlock" > "toBlock" an error is returned. func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { logs := make(chan []*types.Log) + logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) if err != nil { return "", err @@ -323,6 +331,7 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { api.filtersMu.Lock() delete(api.filters, logsSub.ID) api.filtersMu.Unlock() + return } } @@ -338,8 +347,11 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type } borConfig := api.chainConfig.Bor + var filter *Filter + var borLogsFilter *BorBlockLogsFilter + if crit.BlockHash != nil { // Block filter requested, construct a single-shot filter filter = api.sys.NewBlockFilter(*crit.BlockHash, crit.Addresses, crit.Topics) @@ -353,6 +365,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type if crit.FromBlock != nil { begin = crit.FromBlock.Int64() } + end := rpc.LatestBlockNumber.Int64() if crit.ToBlock != nil { end = crit.ToBlock.Int64() @@ -388,11 +401,13 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type // UninstallFilter removes the filter with the given filter id. func (api *FilterAPI) UninstallFilter(id rpc.ID) bool { api.filtersMu.Lock() + f, found := api.filters[id] if found { delete(api.filters, id) } api.filtersMu.Unlock() + if found { f.s.Unsubscribe() } @@ -431,6 +446,7 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo if f.crit.FromBlock != nil { begin = f.crit.FromBlock.Int64() } + end := rpc.LatestBlockNumber.Int64() if f.crit.ToBlock != nil { end = f.crit.ToBlock.Int64() @@ -479,12 +495,14 @@ func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { // receive timer value and reset timer <-f.deadline.C } + f.deadline.Reset(api.timeout) switch f.typ { case BlocksSubscription: hashes := f.hashes f.hashes = nil + return returnHashes(hashes), nil case PendingTransactionsSubscription: if f.fullTx { @@ -492,19 +510,24 @@ func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { for _, tx := range f.txs { txs = append(txs, ethapi.NewRPCPendingTransaction(tx, latest, chainConfig)) } + f.txs = nil + return txs, nil } else { hashes := make([]common.Hash, 0, len(f.txs)) for _, tx := range f.txs { hashes = append(hashes, tx.Hash()) } + f.txs = nil + return hashes, nil } case LogsSubscription, MinedAndPendingLogsSubscription: logs := f.logs f.logs = nil + return returnLogs(logs), nil } } @@ -518,6 +541,7 @@ func returnHashes(hashes []common.Hash) []common.Hash { if hashes == nil { return []common.Hash{} } + return hashes } @@ -527,6 +551,7 @@ func returnLogs(logs []*types.Log) []*types.Log { if logs == nil { return []*types.Log{} } + return logs } @@ -550,6 +575,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { // BlockHash is mutually exclusive with FromBlock/ToBlock criteria return fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other") } + args.BlockHash = raw.BlockHash } else { if raw.FromBlock != nil { @@ -573,6 +599,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { if err != nil { return fmt.Errorf("invalid address at index %d: %v", i, err) } + args.Addresses = append(args.Addresses, addr) } else { return fmt.Errorf("non-string address at index %d", i) @@ -583,6 +610,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { if err != nil { return fmt.Errorf("invalid address: %v", err) } + args.Addresses = []common.Address{addr} default: return errors.New("invalid addresses in query") @@ -593,6 +621,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { // JSON null values are converted to common.Hash{} and ignored by the filter manager. if len(raw.Topics) > 0 { args.Topics = make([][]common.Hash, len(raw.Topics)) + for i, t := range raw.Topics { switch topic := t.(type) { case nil: @@ -604,6 +633,7 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { if err != nil { return err } + args.Topics[i] = []common.Hash{top} case []interface{}: @@ -614,11 +644,13 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { args.Topics[i] = nil break } + if topic, ok := rawTopic.(string); ok { parsed, err := decodeTopic(topic) if err != nil { return err } + args.Topics[i] = append(args.Topics[i], parsed) } else { return fmt.Errorf("invalid topic(s)") @@ -638,6 +670,7 @@ func decodeAddress(s string) (common.Address, error) { if err == nil && len(b) != common.AddressLength { err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength) } + return common.BytesToAddress(b), err } @@ -646,5 +679,6 @@ func decodeTopic(s string) (common.Hash, error) { if err == nil && len(b) != common.HashLength { err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength) } + return common.BytesToHash(b), err } diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go index 0a80d0f8dd..4d17c8447c 100644 --- a/eth/filters/api_test.go +++ b/eth/filters/api_test.go @@ -41,144 +41,181 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) { if err := json.Unmarshal([]byte("{}"), &test0); err != nil { t.Fatal(err) } + if test0.FromBlock != nil { t.Fatalf("expected nil, got %d", test0.FromBlock) } + if test0.ToBlock != nil { t.Fatalf("expected nil, got %d", test0.ToBlock) } + if len(test0.Addresses) != 0 { t.Fatalf("expected 0 addresses, got %d", len(test0.Addresses)) } + if len(test0.Topics) != 0 { t.Fatalf("expected 0 topics, got %d topics", len(test0.Topics)) } // from, to block number var test1 FilterCriteria + vector := fmt.Sprintf(`{"fromBlock":"%#x","toBlock":"%#x"}`, fromBlock, toBlock) if err := json.Unmarshal([]byte(vector), &test1); err != nil { t.Fatal(err) } + if test1.FromBlock.Int64() != fromBlock.Int64() { t.Fatalf("expected FromBlock %d, got %d", fromBlock, test1.FromBlock) } + if test1.ToBlock.Int64() != toBlock.Int64() { t.Fatalf("expected ToBlock %d, got %d", toBlock, test1.ToBlock) } // single address var test2 FilterCriteria + vector = fmt.Sprintf(`{"address": "%s"}`, address0.Hex()) if err := json.Unmarshal([]byte(vector), &test2); err != nil { t.Fatal(err) } + if len(test2.Addresses) != 1 { t.Fatalf("expected 1 address, got %d address(es)", len(test2.Addresses)) } + if test2.Addresses[0] != address0 { t.Fatalf("expected address %x, got %x", address0, test2.Addresses[0]) } // multiple address var test3 FilterCriteria + vector = fmt.Sprintf(`{"address": ["%s", "%s"]}`, address0.Hex(), address1.Hex()) if err := json.Unmarshal([]byte(vector), &test3); err != nil { t.Fatal(err) } + if len(test3.Addresses) != 2 { t.Fatalf("expected 2 addresses, got %d address(es)", len(test3.Addresses)) } + if test3.Addresses[0] != address0 { t.Fatalf("expected address %x, got %x", address0, test3.Addresses[0]) } + if test3.Addresses[1] != address1 { t.Fatalf("expected address %x, got %x", address1, test3.Addresses[1]) } // single topic var test4 FilterCriteria + vector = fmt.Sprintf(`{"topics": ["%s"]}`, topic0.Hex()) if err := json.Unmarshal([]byte(vector), &test4); err != nil { t.Fatal(err) } + if len(test4.Topics) != 1 { t.Fatalf("expected 1 topic, got %d", len(test4.Topics)) } + if len(test4.Topics[0]) != 1 { t.Fatalf("expected len(topics[0]) to be 1, got %d", len(test4.Topics[0])) } + if test4.Topics[0][0] != topic0 { t.Fatalf("got %x, expected %x", test4.Topics[0][0], topic0) } // test multiple "AND" topics var test5 FilterCriteria + vector = fmt.Sprintf(`{"topics": ["%s", "%s"]}`, topic0.Hex(), topic1.Hex()) if err := json.Unmarshal([]byte(vector), &test5); err != nil { t.Fatal(err) } + if len(test5.Topics) != 2 { t.Fatalf("expected 2 topics, got %d", len(test5.Topics)) } + if len(test5.Topics[0]) != 1 { t.Fatalf("expected 1 topic, got %d", len(test5.Topics[0])) } + if test5.Topics[0][0] != topic0 { t.Fatalf("got %x, expected %x", test5.Topics[0][0], topic0) } + if len(test5.Topics[1]) != 1 { t.Fatalf("expected 1 topic, got %d", len(test5.Topics[1])) } + if test5.Topics[1][0] != topic1 { t.Fatalf("got %x, expected %x", test5.Topics[1][0], topic1) } // test optional topic var test6 FilterCriteria + vector = fmt.Sprintf(`{"topics": ["%s", null, "%s"]}`, topic0.Hex(), topic2.Hex()) if err := json.Unmarshal([]byte(vector), &test6); err != nil { t.Fatal(err) } + if len(test6.Topics) != 3 { t.Fatalf("expected 3 topics, got %d", len(test6.Topics)) } + if len(test6.Topics[0]) != 1 { t.Fatalf("expected 1 topic, got %d", len(test6.Topics[0])) } + if test6.Topics[0][0] != topic0 { t.Fatalf("got %x, expected %x", test6.Topics[0][0], topic0) } + if len(test6.Topics[1]) != 0 { t.Fatalf("expected 0 topic, got %d", len(test6.Topics[1])) } + if len(test6.Topics[2]) != 1 { t.Fatalf("expected 1 topic, got %d", len(test6.Topics[2])) } + if test6.Topics[2][0] != topic2 { t.Fatalf("got %x, expected %x", test6.Topics[2][0], topic2) } // test OR topics var test7 FilterCriteria + vector = fmt.Sprintf(`{"topics": [["%s", "%s"], null, ["%s", null]]}`, topic0.Hex(), topic1.Hex(), topic2.Hex()) if err := json.Unmarshal([]byte(vector), &test7); err != nil { t.Fatal(err) } + if len(test7.Topics) != 3 { t.Fatalf("expected 3 topics, got %d topics", len(test7.Topics)) } + if len(test7.Topics[0]) != 2 { t.Fatalf("expected 2 topics, got %d topics", len(test7.Topics[0])) } + if test7.Topics[0][0] != topic0 || test7.Topics[0][1] != topic1 { t.Fatalf("invalid topics expected [%x,%x], got [%x,%x]", topic0, topic1, test7.Topics[0][0], test7.Topics[0][1], ) } + if len(test7.Topics[1]) != 0 { t.Fatalf("expected 0 topic, got %d topics", len(test7.Topics[1])) } + if len(test7.Topics[2]) != 0 { t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2])) } diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go index 73b96b77af..89a45bc2d8 100644 --- a/eth/filters/bench_test.go +++ b/eth/filters/bench_test.go @@ -63,13 +63,16 @@ const benchFilterCnt = 2000 func benchmarkBloomBits(b *testing.B, sectionSize uint64) { b.Skip("test disabled: this tests presume (and modify) an existing datadir.") + benchDataDir := node.DefaultDataDir() + "/geth/chaindata" + b.Log("Running bloombits benchmark section size:", sectionSize) db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) if err != nil { b.Fatalf("error opening database at %v: %v", benchDataDir, err) } + head := rawdb.ReadHeadBlockHash(db) if head == (common.Hash{}) { b.Fatalf("chain data not found at %v", benchDataDir) @@ -77,6 +80,7 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { clearBloomBits(db) b.Log("Generating bloombits data...") + headNum := rawdb.ReadHeaderNumber(db, head) if headNum == nil || *headNum < sectionSize+512 { b.Fatalf("not enough blocks for running a benchmark") @@ -84,27 +88,35 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { start := time.Now() cnt := (*headNum - 512) / sectionSize + var dataSize, compSize uint64 + for sectionIdx := uint64(0); sectionIdx < cnt; sectionIdx++ { bc, err := bloombits.NewGenerator(uint(sectionSize)) if err != nil { b.Fatalf("failed to create generator: %v", err) } + var header *types.Header + for i := sectionIdx * sectionSize; i < (sectionIdx+1)*sectionSize; i++ { hash := rawdb.ReadCanonicalHash(db, i) if header = rawdb.ReadHeader(db, hash, i); header == nil { b.Fatalf("Error creating bloomBits data") return } + bc.AddBloom(uint(i-sectionIdx*sectionSize), header.Bloom) } + sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*sectionSize-1) + for i := 0; i < types.BloomBitLength; i++ { data, err := bc.Bitset(uint(i)) if err != nil { b.Fatalf("failed to retrieve bitset: %v", err) } + comp := bitutil.CompressBytes(data) dataSize += uint64(len(data)) compSize += uint64(len(comp)) @@ -116,17 +128,20 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { } d := time.Since(start) + b.Log("Finished generating bloombits data") b.Log(" ", d, "total ", d/time.Duration(cnt*sectionSize), "per block") b.Log(" data size:", dataSize, " compressed size:", compSize, " compression ratio:", float64(compSize)/float64(dataSize)) b.Log("Running filter benchmarks...") + start = time.Now() var ( backend *testBackend sys *FilterSystem ) + for i := 0; i < benchFilterCnt; i++ { if i%20 == 0 { db.Close() @@ -134,9 +149,11 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { backend = &testBackend{db: db, sections: cnt} sys = NewFilterSystem(backend, Config{}) } + var addr common.Address addr[0] = byte(i) addr[1] = byte(i / 256) + filter := sys.NewRangeFilter(0, int64(cnt*sectionSize-1), []common.Address{addr}, nil) if _, err := filter.Logs(context.Background()); err != nil { b.Error("filter.Logs error:", err) @@ -144,6 +161,7 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { } d = time.Since(start) + b.Log("Finished running filter benchmarks") b.Log(" ", d, "total ", d/time.Duration(benchFilterCnt), "per address", d*time.Duration(1000000)/time.Duration(benchFilterCnt*cnt*sectionSize), "per million blocks") db.Close() @@ -152,7 +170,9 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { //nolint:unused func clearBloomBits(db ethdb.Database) { var bloomBitsPrefix = []byte("bloomBits-") + fmt.Println("Clearing bloombits data...") + it := db.NewIterator(bloomBitsPrefix, nil) for it.Next() { db.Delete(it.Key()) @@ -162,16 +182,21 @@ func clearBloomBits(db ethdb.Database) { func BenchmarkNoBloomBits(b *testing.B) { b.Skip("test disabled: this tests presume (and modify) an existing datadir.") + benchDataDir := node.DefaultDataDir() + "/geth/chaindata" + b.Log("Running benchmark without bloombits") + db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) if err != nil { b.Fatalf("error opening database at %v: %v", benchDataDir, err) } + head := rawdb.ReadHeadBlockHash(db) if head == (common.Hash{}) { b.Fatalf("chain data not found at %v", benchDataDir) } + headNum := rawdb.ReadHeaderNumber(db, head) clearBloomBits(db) @@ -179,10 +204,13 @@ func BenchmarkNoBloomBits(b *testing.B) { _, sys := newTestFilterSystem(b, db, Config{}) b.Log("Running filter benchmarks...") + start := time.Now() filter := sys.NewRangeFilter(0, int64(*headNum), []common.Address{{}}, nil) filter.Logs(context.Background()) + d := time.Since(start) + b.Log("Finished running filter benchmarks") b.Log(" ", d, "total ", d*time.Duration(1000000)/time.Duration(*headNum+1), "per million blocks") db.Close() diff --git a/eth/filters/bor_api.go b/eth/filters/bor_api.go index 63028a3e30..742373f495 100644 --- a/eth/filters/bor_api.go +++ b/eth/filters/bor_api.go @@ -34,6 +34,7 @@ func (api *FilterAPI) GetBorBlockLogs(ctx context.Context, crit FilterCriteria) if crit.FromBlock != nil { begin = crit.FromBlock.Int64() } + end := rpc.LatestBlockNumber.Int64() if crit.ToBlock != nil { end = crit.ToBlock.Int64() @@ -47,6 +48,7 @@ func (api *FilterAPI) GetBorBlockLogs(ctx context.Context, crit FilterCriteria) if err != nil { return nil, err } + return returnLogs(logs), err } diff --git a/eth/filters/bor_filter.go b/eth/filters/bor_filter.go index 8590d79eb1..dd85670e83 100644 --- a/eth/filters/bor_filter.go +++ b/eth/filters/bor_filter.go @@ -56,6 +56,7 @@ func NewBorBlockLogsFilter(backend Backend, borConfig *params.BorConfig, block c // Create a generic filter and convert it into a block filter filter := newBorBlockLogsFilter(backend, borConfig, addresses, topics) filter.block = block + return filter } @@ -80,6 +81,7 @@ func (f *BorBlockLogsFilter) Logs(ctx context.Context) ([]*types.Log, error) { if receipt == nil { return nil, nil } + return f.borBlockLogs(ctx, receipt) } @@ -88,6 +90,7 @@ func (f *BorBlockLogsFilter) Logs(ctx context.Context) ([]*types.Log, error) { if header == nil { return nil, nil } + head := header.Number.Uint64() if f.begin == -1 { @@ -130,9 +133,11 @@ func (f *BorBlockLogsFilter) unindexedLogs(ctx context.Context, end uint64) ([]* if err != nil { return logs, err } + logs = append(logs, found...) sprintLength = f.borConfig.CalculateSprint(uint64(f.begin)) } + return logs, nil } @@ -141,6 +146,7 @@ func (f *BorBlockLogsFilter) borBlockLogs(ctx context.Context, receipt *types.Re if bloomFilter(receipt.Bloom, f.addresses, f.topics) { logs = filterLogs(receipt.Logs, nil, nil, f.addresses, f.topics) } + return logs, nil } diff --git a/eth/filters/bor_filter_system.go b/eth/filters/bor_filter_system.go index 904d99bf23..791bef0029 100644 --- a/eth/filters/bor_filter_system.go +++ b/eth/filters/bor_filter_system.go @@ -27,5 +27,6 @@ func (es *EventSystem) SubscribeNewDeposits(data chan *types.StateSyncData) *Sub installed: make(chan struct{}), err: make(chan error), } + return es.subscribe(sub) } diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 8ba482817e..c0c5694cff 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -47,20 +47,25 @@ func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Add // system. Since the bloombits are not positional, nil topics are permitted, // which get flattened into a nil byte slice. var filters [][][]byte + if len(addresses) > 0 { filter := make([][]byte, len(addresses)) for i, address := range addresses { filter[i] = address.Bytes() } + filters = append(filters, filter) } + for _, topicList := range topics { filter := make([][]byte, len(topicList)) for i, topic := range topicList { filter[i] = topic.Bytes() } + filters = append(filters, filter) } + size, _ := sys.backend.BloomStatus() // Create a generic filter and convert it into a range filter @@ -79,6 +84,7 @@ func (sys *FilterSystem) NewBlockFilter(block common.Hash, addresses []common.Ad // Create a generic filter and convert it into a block filter filter := newFilter(sys, addresses, topics) filter.block = &block + return filter } @@ -101,9 +107,11 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { if err != nil { return nil, err } + if header == nil { return nil, errors.New("unknown block") } + return f.blockLogs(ctx, header) } // Short-cut if all we care about is pending logs @@ -111,6 +119,7 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { if f.end != rpc.PendingBlockNumber.Int64() { return nil, errors.New("invalid block range") } + return f.pendingLogs() } // Figure out the limits of the filter range @@ -118,13 +127,16 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { if header == nil { return nil, nil } + var ( err error head = header.Number.Int64() pending = f.end == rpc.PendingBlockNumber.Int64() ) + resolveSpecial := func(number int64) (int64, error) { var hdr *types.Header + switch number { case rpc.LatestBlockNumber.Int64(): return head, nil @@ -145,11 +157,13 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { default: return number, nil } + return hdr.Number.Int64(), nil } if f.begin, err = resolveSpecial(f.begin); err != nil { return nil, err } + if f.end, err = resolveSpecial(f.end); err != nil { return nil, err } @@ -159,25 +173,31 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { end = uint64(f.end) size, sections = f.sys.backend.BloomStatus() ) + if indexed := sections * size; indexed > uint64(f.begin) { if indexed > end { logs, err = f.indexedLogs(ctx, end) } else { logs, err = f.indexedLogs(ctx, indexed-1) } + if err != nil { return logs, err } } + rest, err := f.unindexedLogs(ctx, end) logs = append(logs, rest...) + if pending { pendingLogs, err := f.pendingLogs() if err != nil { return nil, err } + logs = append(logs, pendingLogs...) } + return logs, err } @@ -207,8 +227,10 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err if err == nil { f.begin = int64(end) + 1 } + return logs, err } + f.begin = int64(number) + 1 // Retrieve the suggested block and pull any truly matching logs @@ -216,10 +238,12 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err if header == nil || err != nil { return logs, err } + found, err := f.checkMatches(ctx, header) if err != nil { return logs, err } + logs = append(logs, found...) case <-ctx.Done(): @@ -237,16 +261,20 @@ func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, e if f.begin%10 == 0 && ctx.Err() != nil { return logs, ctx.Err() } + header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) if header == nil || err != nil { return logs, err } + found, err := f.blockLogs(ctx, header) if err != nil { return logs, err } + logs = append(logs, found...) } + return logs, nil } @@ -255,6 +283,7 @@ func (f *Filter) blockLogs(ctx context.Context, header *types.Header) ([]*types. if bloomFilter(header.Bloom, f.addresses, f.topics) { return f.checkMatches(ctx, header) } + return nil, nil } @@ -271,6 +300,7 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ if err != nil { return nil, err } + logs := filterLogs(cached.logs, nil, nil, f.addresses, f.topics) if len(logs) == 0 { return nil, nil @@ -284,12 +314,14 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ if err != nil { return nil, err } + for i, log := range logs { // Copy log not to modify cache elements logcopy := *log logcopy.TxHash = body.Transactions[logcopy.TxIndex].Hash() logs[i] = &logcopy } + return logs, nil } @@ -301,8 +333,10 @@ func (f *Filter) pendingLogs() ([]*types.Log, error) { for _, r := range receipts { unfiltered = append(unfiltered, r.Logs...) } + return filterLogs(unfiltered, nil, nil, f.addresses, f.topics), nil } + return nil, nil } @@ -349,18 +383,21 @@ Logs: } ret = append(ret, log) } + return ret } func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool { if len(addresses) > 0 { var included bool + for _, addr := range addresses { if types.BloomLookup(bloom, addr) { included = true break } } + if !included { return false } @@ -368,15 +405,18 @@ func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]commo for _, sub := range topics { included := len(sub) == 0 // empty rule set == wildcard + for _, topic := range sub { if types.BloomLookup(bloom, topic) { included = true break } } + if !included { return false } } + return true } diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 8c911e4c98..a09476778a 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -49,9 +49,11 @@ func (cfg Config) withDefaults() Config { if cfg.Timeout == 0 { cfg.Timeout = 5 * time.Minute } + if cfg.LogCacheSize == 0 { cfg.LogCacheSize = 32 } + return cfg } @@ -89,6 +91,7 @@ type FilterSystem struct { // NewFilterSystem creates a filter system. func NewFilterSystem(backend Backend, config Config) *FilterSystem { config = config.withDefaults() + return &FilterSystem{ backend: backend, logsCache: lru.NewCache[common.Hash, *logCacheElem](config.LogCacheSize), @@ -112,13 +115,16 @@ func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Has if err != nil { return nil, err } + if logs == nil { return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString()) } // Database logs are un-derived. // Fill in whatever we can (txHash is inaccessible at this point). flattened := make([]*types.Log, 0) + var logIdx uint + for i, txLogs := range logs { for _, log := range txLogs { log.BlockHash = blockHash @@ -126,11 +132,14 @@ func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Has log.TxIndex = uint(i) log.Index = logIdx logIdx++ + flattened = append(flattened, log) } } + elem := &logCacheElem{logs: flattened} sys.logsCache.Add(blockHash, elem) + return elem, nil } @@ -138,11 +147,14 @@ func (sys *FilterSystem) cachedGetBody(ctx context.Context, elem *logCacheElem, if body := elem.body.Load(); body != nil { return body.(*types.Body), nil } + body, err := sys.backend.GetBody(ctx, hash, rpc.BlockNumber(number)) if err != nil { return nil, err } + elem.body.Store(body) + return body, nil } @@ -262,6 +274,7 @@ func NewEventSystem(sys *FilterSystem, lightMode bool) *EventSystem { } go m.eventLoop() + return m } @@ -307,6 +320,7 @@ func (sub *Subscription) Unsubscribe() { func (es *EventSystem) subscribe(sub *subscription) *Subscription { es.install <- sub <-sub.installed + return &Subscription{ID: sub.id, f: sub, es: es} } @@ -320,6 +334,7 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ } else { from = rpc.BlockNumber(crit.FromBlock.Int64()) } + if crit.ToBlock == nil { to = rpc.LatestBlockNumber } else { @@ -346,6 +361,7 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ if from >= 0 && to == rpc.LatestBlockNumber { return es.subscribeLogs(crit, logs), nil } + return nil, fmt.Errorf("invalid from and to block combination: from > to") } @@ -363,6 +379,7 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit ethereum.FilterQuery, logs installed: make(chan struct{}), err: make(chan error), } + return es.subscribe(sub) } @@ -380,6 +397,7 @@ func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*typ installed: make(chan struct{}), err: make(chan error), } + return es.subscribe(sub) } @@ -397,6 +415,7 @@ func (es *EventSystem) subscribePendingLogs(crit ethereum.FilterQuery, logs chan installed: make(chan struct{}), err: make(chan error), } + return es.subscribe(sub) } @@ -413,6 +432,7 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti installed: make(chan struct{}), err: make(chan error), } + return es.subscribe(sub) } @@ -429,6 +449,7 @@ func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subsc installed: make(chan struct{}), err: make(chan error), } + return es.subscribe(sub) } @@ -438,6 +459,7 @@ func (es *EventSystem) handleLogs(filters filterIndex, ev []*types.Log) { if len(ev) == 0 { return } + for _, f := range filters[LogsSubscription] { matchedLogs := filterLogs(ev, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) if len(matchedLogs) > 0 { @@ -450,6 +472,7 @@ func (es *EventSystem) handlePendingLogs(filters filterIndex, ev []*types.Log) { if len(ev) == 0 { return } + for _, f := range filters[PendingLogsSubscription] { matchedLogs := filterLogs(ev, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) if len(matchedLogs) > 0 { @@ -477,15 +500,18 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) for _, f := range filters[BlocksSubscription] { f.headers <- ev.Block.Header() } + if es.lightMode && len(filters[LogsSubscription]) > 0 { es.lightFilterNewHead(ev.Block.Header(), func(header *types.Header, remove bool) { for _, f := range filters[LogsSubscription] { if f.logsCrit.FromBlock != nil && header.Number.Cmp(f.logsCrit.FromBlock) < 0 { continue } + if f.logsCrit.ToBlock != nil && header.Number.Cmp(f.logsCrit.ToBlock) > 0 { continue } + if matchedLogs := es.lightFilterLogs(header, f.logsCrit.Addresses, f.logsCrit.Topics, remove); len(matchedLogs) > 0 { f.logs <- matchedLogs } @@ -497,19 +523,24 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func(*types.Header, bool)) { oldh := es.lastHead es.lastHead = newHeader + if oldh == nil { return } + newh := newHeader // find common ancestor, create list of rolled back and new block hashes var oldHeaders, newHeaders []*types.Header + for oldh.Hash() != newh.Hash() { if oldh.Number.Uint64() >= newh.Number.Uint64() { oldHeaders = append(oldHeaders, oldh) oldh = rawdb.ReadHeader(es.backend.ChainDb(), oldh.ParentHash, oldh.Number.Uint64()-1) } + if oldh.Number.Uint64() < newh.Number.Uint64() { newHeaders = append(newHeaders, newh) + newh = rawdb.ReadHeader(es.backend.ChainDb(), newh.ParentHash, newh.Number.Uint64()-1) if newh == nil { // happens when CHT syncing, nothing to do @@ -535,10 +566,12 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common. // Get the logs of the block ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() + cached, err := es.sys.cachedLogElem(ctx, header.Hash(), header.Number.Uint64()) if err != nil { return nil } + unfiltered := append([]*types.Log{}, cached.logs...) for i, log := range unfiltered { // Don't modify in-cache elements @@ -547,6 +580,7 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common. // Swap copy in-place unfiltered[i] = &logcopy } + logs := filterLogs(unfiltered, nil, nil, addresses, topics) // Txhash is already resolved if len(logs) > 0 && logs[0].TxHash != (common.Hash{}) { @@ -557,10 +591,12 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common. if err != nil { return nil } + for _, log := range logs { // logs are already copied, safe to modify log.TxHash = body.Transactions[log.TxIndex].Hash() } + return logs } @@ -604,6 +640,7 @@ func (es *EventSystem) eventLoop() { } else { index[f.typ][f.id] = f } + close(f.installed) case f := <-es.uninstall: @@ -614,6 +651,7 @@ func (es *EventSystem) eventLoop() { } else { delete(index[f.typ], f.id) } + close(f.err) // System stopped diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 6e8a149490..1b79ce5e88 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -76,22 +76,27 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe hash common.Hash num uint64 ) + switch blockNr { case rpc.LatestBlockNumber: hash = rawdb.ReadHeadBlockHash(b.db) + number := rawdb.ReadHeaderNumber(b.db, hash) if number == nil { //nolint:nilnil return nil, nil } + num = *number case rpc.FinalizedBlockNumber: hash = rawdb.ReadFinalizedBlockHash(b.db) + number := rawdb.ReadHeaderNumber(b.db, hash) if number == nil { //nolint:nilnil return nil, nil } + num = *number case rpc.SafeBlockNumber: return nil, errors.New("safe block not found") @@ -99,6 +104,7 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe num = uint64(blockNr) hash = rawdb.ReadCanonicalHash(b.db, num) } + return rawdb.ReadHeader(b.db, hash, num), nil } @@ -108,6 +114,7 @@ func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*type //nolint:nilnil return nil, nil } + return rawdb.ReadHeader(b.db, hash, *number), nil } @@ -115,6 +122,7 @@ func (b *testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc. if body := rawdb.ReadBody(b.db, hash, uint64(number)); body != nil { return body, nil } + return nil, errors.New("block body not found") } @@ -122,6 +130,7 @@ func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types. if number := rawdb.ReadHeaderNumber(b.db, hash); number != nil { return rawdb.ReadReceipts(b.db, hash, *number, params.TestChainConfig), nil } + return nil, nil } @@ -215,6 +224,7 @@ func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.Matc func newTestFilterSystem(t testing.TB, db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) { backend := &testBackend{db: db} sys := NewFilterSystem(backend, cfg) + return backend, sys } @@ -255,11 +265,13 @@ func TestBlockSubscription(t *testing.T) { if chainEvents[i1].Hash != header.Hash() { t.Errorf("sub0 received invalid hash on index %d, want %x, got %x", i1, chainEvents[i1].Hash, header.Hash()) } + i1++ case header := <-chan1: if chainEvents[i2].Hash != header.Hash() { t.Errorf("sub1 received invalid hash on index %d, want %x, got %x", i2, chainEvents[i2].Hash, header.Hash()) } + i2++ } } @@ -269,6 +281,7 @@ func TestBlockSubscription(t *testing.T) { }() time.Sleep(1 * time.Second) + for _, e := range chainEvents { backend.chainFeed.Send(e) } @@ -303,6 +316,7 @@ func TestPendingTxFilter(t *testing.T) { backend.txFeed.Send(core.NewTxsEvent{Txs: transactions}) timeout := time.Now().Add(1 * time.Second) + for { results, err := api.GetFilterChanges(fid0) if err != nil { @@ -310,6 +324,7 @@ func TestPendingTxFilter(t *testing.T) { } h := results.([]common.Hash) + hashes = append(hashes, h...) if len(hashes) >= len(transactions) { break @@ -326,6 +341,7 @@ func TestPendingTxFilter(t *testing.T) { t.Errorf("invalid number of transactions, want %d transactions(s), got %d", len(transactions), len(hashes)) return } + for i := range hashes { if hashes[i] != transactions[i].Hash() { t.Errorf("hashes[%d] invalid, want %x, got %x", i, transactions[i].Hash(), hashes[i]) @@ -360,6 +376,7 @@ func TestPendingTxFilterFullTx(t *testing.T) { backend.txFeed.Send(core.NewTxsEvent{Txs: transactions}) timeout := time.Now().Add(1 * time.Second) + for { results, err := api.GetFilterChanges(fid0) if err != nil { @@ -367,6 +384,7 @@ func TestPendingTxFilterFullTx(t *testing.T) { } tx := results.([]*ethapi.RPCTransaction) + txs = append(txs, tx...) if len(txs) >= len(transactions) { break @@ -383,6 +401,7 @@ func TestPendingTxFilterFullTx(t *testing.T) { t.Errorf("invalid number of transactions, want %d transactions(s), got %d", len(transactions), len(txs)) return } + for i := range txs { if txs[i].Hash != transactions[i].Hash() { t.Errorf("hashes[%d] invalid, want %x, got %x", i, transactions[i].Hash(), txs[i].Hash) @@ -426,8 +445,10 @@ func TestLogFilterCreation(t *testing.T) { if err != nil && test.success { t.Errorf("expected filter creation for case %d to success, got %v", i, err) } + if err == nil { api.UninstallFilter(id) + if !test.success { t.Errorf("expected testcase %d to fail with an error", i) } @@ -553,16 +574,20 @@ func TestLogFilter(t *testing.T) { // raise events time.Sleep(1 * time.Second) + if nsend := backend.logsFeed.Send(allLogs); nsend == 0 { t.Fatal("Logs event not delivered") } + if nsend := backend.pendingLogsFeed.Send(allLogs); nsend == 0 { t.Fatal("Pending logs event not delivered") } for i, tt := range testCases { var fetched []*types.Log + timeout := time.Now().Add(1 * time.Second) + for { // fetch all expected logs results, err := api.GetFilterChanges(tt.id) if err != nil { @@ -590,6 +615,7 @@ func TestLogFilter(t *testing.T) { if fetched[l].Removed { t.Errorf("expected log not to be removed for log %d in case %d", l, i) } + if !reflect.DeepEqual(fetched[l], tt.expected[l]) { t.Errorf("invalid log on index %d for case %d", l, i) } @@ -716,6 +742,7 @@ func TestPendingLogsSubscription(t *testing.T) { testCases[i].err = make(chan error, 1) var err error + testCases[i].sub, err = api.events.SubscribeLogs(testCases[i].crit, testCases[i].c) if err != nil { t.Fatalf("SubscribeLogs %d failed: %v\n", i, err) @@ -725,6 +752,7 @@ func TestPendingLogsSubscription(t *testing.T) { for n, test := range testCases { i := n tt := test + go func() { defer tt.sub.Unsubscribe() @@ -755,6 +783,7 @@ func TestPendingLogsSubscription(t *testing.T) { tt.err <- fmt.Errorf("expected log not to be removed for log %d in case %d", l, i) return } + if !reflect.DeepEqual(fetched[l], tt.expected[l]) { tt.err <- fmt.Errorf("invalid log on index %d for case %d\n", l, i) return @@ -774,6 +803,7 @@ func TestPendingLogsSubscription(t *testing.T) { if err != nil { t.Fatalf("test %d failed: %v", i, err) } + <-testCases[i].sub.Err() } } @@ -847,6 +877,7 @@ func TestLightFilterLogs(t *testing.T) { if i == 0 { return } + receipts[i-1].Bloom = types.CreateBloom(types.Receipts{receipts[i-1]}) b.AddUncheckedReceipt(receipts[i-1]) tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i - 1), To: &common.Address{}, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, key) @@ -856,6 +887,7 @@ func TestLightFilterLogs(t *testing.T) { rawdb.WriteBlock(db, block) rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) rawdb.WriteHeadBlockHash(db, block.Hash()) + if i > 0 { rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), []*types.Receipt{receipts[i-1]}) } @@ -866,23 +898,28 @@ func TestLightFilterLogs(t *testing.T) { if err != nil { t.Fatal(err) } + testCases[i].id = id } // raise events time.Sleep(1 * time.Second) + for _, block := range blocks { backend.chainFeed.Send(core.ChainEvent{Block: block, Hash: common.Hash{}, Logs: allLogs}) } for i, tt := range testCases { var fetched []*types.Log + timeout := time.Now().Add(1 * time.Second) + for { // fetch all expected logs results, err := api.GetFilterChanges(tt.id) if err != nil { t.Fatalf("Unable to fetch logs: %v", err) } + fetched = append(fetched, results.([]*types.Log)...) if len(fetched) >= len(tt.expected) { break @@ -904,10 +941,12 @@ func TestLightFilterLogs(t *testing.T) { if fetched[l].Removed { t.Errorf("expected log not to be removed for log %d in case %d", l, i) } + expected := *tt.expected[l] blockNum := expected.BlockNumber - 1 expected.BlockHash = blocks[blockNum].Hash() expected.TxHash = blocks[blockNum].Transactions()[0].Hash() + if !reflect.DeepEqual(fetched[l], &expected) { t.Errorf("invalid log on index %d for case %d", l, i) } @@ -920,6 +959,7 @@ func TestLightFilterLogs(t *testing.T) { // Please refer to #22131 for more details. func TestPendingTxFilterDeadlock(t *testing.T) { t.Parallel() + timeout := 100 * time.Millisecond var ( @@ -932,6 +972,7 @@ func TestPendingTxFilterDeadlock(t *testing.T) { go func() { // Bombard feed with txes until signal was received to stop i := uint64(0) + for { select { case <-done: @@ -941,6 +982,7 @@ func TestPendingTxFilterDeadlock(t *testing.T) { 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++ } }() @@ -957,9 +999,11 @@ func TestPendingTxFilterDeadlock(t *testing.T) { if err != nil { t.Fatalf("Filter should exist: %v\n", err) } + if len(hashes.([]common.Hash)) > 0 { break } + runtime.Gosched() } } @@ -987,5 +1031,6 @@ func flattenLogs(pl [][]*types.Log) []*types.Log { for _, l := range pl { logs = append(logs, l...) } + return logs } diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index d10e0f1d94..4bea788f25 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -37,6 +37,7 @@ func makeReceipt(addr common.Address) *types.Receipt { {Address: addr}, } receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + return receipt } @@ -56,7 +57,9 @@ func BenchmarkFilters(b *testing.B) { Config: params.TestChainConfig, } ) + defer db.Close() + _, chain, receipts := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 100010, func(i int, gen *core.BlockGen) { switch i { case 2403: @@ -88,6 +91,7 @@ func BenchmarkFilters(b *testing.B) { rawdb.WriteHeadBlockHash(db, block.Hash()) rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i]) } + b.ResetTimer() filter := sys.NewRangeFilter(0, -1, []common.Address{addr1, addr2, addr3, addr4}, nil) @@ -118,6 +122,7 @@ func TestFilters(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), } ) + defer db.Close() _, chain, receipts := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 1000, func(i int, gen *core.BlockGen) { @@ -169,6 +174,7 @@ func TestFilters(t *testing.T) { // and then import blocks. TODO(rjl493456442) try to get rid of the // manual database writes. gspec.MustCommit(db) + for i, block := range chain { rawdb.WriteBlock(db, block) rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) @@ -180,6 +186,7 @@ func TestFilters(t *testing.T) { rawdb.WriteFinalizedBlockHash(db, chain[998].Hash()) filter := sys.NewRangeFilter(0, -1, []common.Address{addr}, [][]common.Hash{{hash1, hash2, hash3, hash4}}) + logs, _ := filter.Logs(context.Background()) if len(logs) != 4 { t.Error("expected 4 log, got", len(logs)) @@ -224,16 +231,21 @@ func TestFilters(t *testing.T) { }, } { logs, _ := tc.f.Logs(context.Background()) + var haveHashes []common.Hash + for _, l := range logs { haveHashes = append(haveHashes, l.Topics[0]) } + if have, want := len(haveHashes), len(tc.wantHashes); have != want { t.Fatalf("test %d, have %d logs, want %d", i, have, want) } + if len(haveHashes) == 0 { continue } + if !reflect.DeepEqual(tc.wantHashes, haveHashes) { t.Fatalf("test %d, have %v want %v", i, haveHashes, tc.wantHashes) } diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index 82edf9a7fb..56b4f8bf76 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -90,19 +90,24 @@ func (s sortGasAndReward) Less(i, j int) bool { // fills in the rest of the fields. func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { chainconfig := oracle.backend.ChainConfig() + if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil { bf.results.baseFee = new(big.Int) } + if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) { bf.results.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header) } else { bf.results.nextBaseFee = new(big.Int) } + bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit) + if len(percentiles) == 0 { // rewards were not requested, return null return } + if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) { log.Error("Block or receipts are missing while reward percentiles are requested") return @@ -114,17 +119,21 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { for i := range bf.results.reward { bf.results.reward[i] = new(big.Int) } + return } sorter := make(sortGasAndReward, len(bf.block.Transactions())) + for i, tx := range bf.block.Transactions() { reward, _ := tx.EffectiveGasTip(bf.block.BaseFee()) sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} } + sort.Stable(sorter) var txIndex int + sumGasUsed := sorter[0].gasUsed for i, p := range percentiles { @@ -133,6 +142,7 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { txIndex++ sumGasUsed += sorter[txIndex].gasUsed } + bf.results.reward[i] = sorter[txIndex].reward } } @@ -154,6 +164,7 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNum if headBlock, err = oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err != nil { return nil, nil, 0, 0, err } + head := rpc.BlockNumber(headBlock.Number.Uint64()) // Fail if request block is beyond the chain's current head. @@ -167,6 +178,7 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNum resolved *types.Header err error ) + switch reqEnd { case rpc.PendingBlockNumber: if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil { @@ -188,6 +200,7 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNum case rpc.EarliestBlockNumber: resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.EarliestBlockNumber) } + if resolved == nil || err != nil { return nil, nil, 0, 0, err } @@ -203,6 +216,7 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNum if uint64(reqEnd+1) < blocks { blocks = uint64(reqEnd + 1) } + return pendingBlock, pendingReceipts, uint64(reqEnd), blocks, nil } @@ -224,41 +238,50 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL if blocks < 1 { return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks } + maxFeeHistory := oracle.maxHeaderHistory if len(rewardPercentiles) != 0 { maxFeeHistory = oracle.maxBlockHistory } + if blocks > maxFeeHistory { log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory) blocks = maxFeeHistory } + for i, p := range rewardPercentiles { if p < 0 || p > 100 { return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p) } + if i > 0 && p < rewardPercentiles[i-1] { return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p) } } + var ( pendingBlock *types.Block pendingReceipts []*types.Receipt err error ) + pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks) if err != nil || blocks == 0 { return common.Big0, nil, nil, nil, err } + oldestBlock := lastBlock + 1 - blocks var ( next = oldestBlock results = make(chan *blockFees, blocks) ) + percentileKey := make([]byte, 8*len(rewardPercentiles)) for i, p := range rewardPercentiles { binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p)) } + for i := 0; i < maxBlockFetchers && i < int(blocks); i++ { go func() { for { @@ -290,8 +313,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL } else { fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) } + if fees.header != nil && fees.err == nil { oracle.processBlock(fees, rewardPercentiles) + if fees.err == nil { oracle.historyCache.Add(cacheKey, fees.results) } @@ -303,18 +328,22 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL } }() } + var ( reward = make([][]*big.Int, blocks) baseFee = make([]*big.Int, blocks+1) gasUsedRatio = make([]float64, blocks) firstMissing = blocks ) + for ; blocks > 0; blocks-- { fees := <-results if fees.err != nil { return common.Big0, nil, nil, nil, fees.err } + i := fees.blockNumber - oldestBlock + if fees.results.baseFee != nil { reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio } else { @@ -324,14 +353,18 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL } } } + if firstMissing == 0 { return common.Big0, nil, nil, nil, nil } + if len(rewardPercentiles) != 0 { reward = reward[:firstMissing] } else { reward = nil } + baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing] + return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil } diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go index 1bcfb287a5..efa52f08e5 100644 --- a/eth/gasprice/feehistory_test.go +++ b/eth/gasprice/feehistory_test.go @@ -53,6 +53,7 @@ func TestFeeHistory(t *testing.T) { {false, 1000, 1000, 2, rpc.FinalizedBlockNumber, []float64{0, 10}, 24, 2, nil}, {false, 1000, 1000, 2, rpc.SafeBlockNumber, []float64{0, 10}, 24, 2, nil}, } + for i, c := range cases { config := Config{ MaxHeaderHistory: c.maxHeader, @@ -62,11 +63,14 @@ func TestFeeHistory(t *testing.T) { oracle := NewOracle(backend, config) first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent) + backend.teardown() + expReward := c.expCount if len(c.percent) == 0 { expReward = 0 } + expBaseFee := c.expCount if expBaseFee != 0 { expBaseFee++ @@ -75,15 +79,19 @@ func TestFeeHistory(t *testing.T) { if first.Uint64() != c.expFirst { t.Fatalf("Test case %d: first block mismatch, want %d, got %d", i, c.expFirst, first) } + if len(reward) != expReward { t.Fatalf("Test case %d: reward array length mismatch, want %d, got %d", i, expReward, len(reward)) } + if len(baseFee) != expBaseFee { t.Fatalf("Test case %d: baseFee array length mismatch, want %d, got %d", i, expBaseFee, len(baseFee)) } + if len(ratio) != c.expCount { t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio)) } + if err != c.expErr && !errors.Is(err, c.expErr) { t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err) } diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 6f29f26b75..a3a5cb7b1d 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -84,6 +84,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { blocks = 1 log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks) } + percent := params.Percentile if percent < 0 { percent = 0 @@ -92,11 +93,13 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { percent = 100 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) } + maxPrice := params.MaxPrice if maxPrice == nil || maxPrice.Int64() <= 0 { maxPrice = DefaultMaxPrice log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice) } + ignorePrice := params.IgnorePrice if ignorePrice == nil || ignorePrice.Int64() <= 0 { ignorePrice = DefaultIgnorePrice @@ -104,11 +107,13 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { } else if ignorePrice.Int64() > 0 { log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice) } + maxHeaderHistory := params.MaxHeaderHistory if maxHeaderHistory < 1 { maxHeaderHistory = 1 log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory) } + maxBlockHistory := params.MaxBlockHistory if maxBlockHistory < 1 { maxBlockHistory = 1 @@ -118,12 +123,14 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { cache := lru.NewCache[cacheKey, processedFees](2048) headEvent := make(chan core.ChainHeadEvent, 1) backend.SubscribeChainHeadEvent(headEvent) + go func() { var lastHead common.Hash for ev := range headEvent { if ev.Block.ParentHash() != lastHead { cache.Purge() } + lastHead = ev.Block.Hash() } }() @@ -144,12 +151,14 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { func (oracle *Oracle) ProcessCache() { headEvent := make(chan core.ChainHeadEvent, 1) oracle.backend.SubscribeChainHeadEvent(headEvent) + go func() { var lastHead common.Hash for ev := range headEvent { if ev.Block.ParentHash() != lastHead { oracle.historyCache.Purge() } + lastHead = ev.Block.Hash() } }() @@ -169,9 +178,11 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { oracle.cacheLock.RLock() lastHead, lastPrice := oracle.lastHead, oracle.lastPrice oracle.cacheLock.RUnlock() + if headHash == lastHead { return new(big.Int).Set(lastPrice), nil } + oracle.fetchLock.Lock() defer oracle.fetchLock.Unlock() @@ -179,9 +190,11 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { oracle.cacheLock.RLock() lastHead, lastPrice = oracle.lastHead, oracle.lastPrice oracle.cacheLock.RUnlock() + if headHash == lastHead { return new(big.Int).Set(lastPrice), nil } + var ( sent, exp int number = head.Number.Uint64() @@ -189,18 +202,22 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { quit = make(chan struct{}) results []*big.Int ) + for sent < oracle.checkBlocks && number > 0 { go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit) + sent++ exp++ number-- } + for exp > 0 { res := <-result if res.err != nil { close(quit) return new(big.Int).Set(lastPrice), res.err } + exp-- // Nothing returned. There are two special cases here: // - The block is empty @@ -214,20 +231,26 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { // is 2*checkBlocks. if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 { go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit) + sent++ exp++ number-- } + results = append(results, res.values...) } + price := lastPrice + if len(results) > 0 { sort.Sort(bigIntArray(results)) price = results[(len(results)-1)*oracle.percentile/100] } + if price.Cmp(oracle.maxPrice) > 0 { price = new(big.Int).Set(oracle.maxPrice) } + oracle.cacheLock.Lock() oracle.lastHead = headHash oracle.lastPrice = price @@ -262,6 +285,7 @@ func (s *txSorter) Less(i, j int) bool { // accepted into a block with an invalid effective tip. tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee) tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee) + return tip1.Cmp(tip2) < 0 } @@ -276,6 +300,7 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, b case result <- results{nil, err}: case <-quit: } + return } // Sort the transaction by effective tip in ascending sort. @@ -285,11 +310,13 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, b sort.Sort(sorter) var prices []*big.Int + for _, tx := range sorter.txs { tip, _ := tx.EffectiveGasTip(block.BaseFee()) if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 { continue } + sender, err := types.Sender(signer, tx) if err == nil && sender != block.Coinbase() { prices = append(prices, tip) diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 2465806727..61796355f2 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -45,18 +45,23 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber if number > testHead { return nil, nil } + if number == rpc.EarliestBlockNumber { number = 0 } + if number == rpc.FinalizedBlockNumber { return b.chain.CurrentFinalBlock(), nil } + if number == rpc.SafeBlockNumber { return b.chain.CurrentSafeBlock(), nil } + if number == rpc.LatestBlockNumber { number = testHead } + if number == rpc.PendingBlockNumber { if b.pending { number = testHead + 1 @@ -64,6 +69,7 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber return nil, nil } } + return b.chain.GetHeaderByNumber(uint64(number)), nil } @@ -71,18 +77,23 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) if number > testHead { return nil, nil } + if number == rpc.EarliestBlockNumber { number = 0 } + if number == rpc.FinalizedBlockNumber { number = rpc.BlockNumber(b.chain.CurrentFinalBlock().Number.Uint64()) } + if number == rpc.SafeBlockNumber { number = rpc.BlockNumber(b.chain.CurrentSafeBlock().Number.Uint64()) } + if number == rpc.LatestBlockNumber { number = testHead } + if number == rpc.PendingBlockNumber { if b.pending { number = testHead + 1 @@ -90,6 +101,7 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) return nil, nil } } + return b.chain.GetBlockByNumber(uint64(number)), nil } @@ -102,6 +114,7 @@ func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { block := b.chain.GetBlockByNumber(testHead + 1) return block, b.chain.GetReceiptsByHash(block.Hash()) } + return nil, nil } @@ -130,6 +143,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke } signer = types.LatestSigner(gspec.Config) ) + config.LondonBlock = londonBlock config.ArrowGlacierBlock = londonBlock config.GrayGlacierBlock = londonBlock @@ -161,6 +175,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke Data: []byte{}, } } + b.AddTx(types.MustSignNewTx(key, signer, txdata)) }) // Construct testing chain @@ -168,9 +183,11 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke if err != nil { t.Fatalf("Failed to create local chain, %v", err) } + chain.InsertChain(blocks) chain.SetFinalized(chain.GetBlockByNumber(25).Header()) chain.SetSafe(chain.GetBlockByNumber(25).Header()) + return &testBackend{chain: chain, pending: pending} } @@ -188,6 +205,7 @@ func TestSuggestTipCap(t *testing.T) { Percentile: 60, Default: big.NewInt(params.GWei), } + var cases = []struct { fork *big.Int // London fork number expect *big.Int // Expected gasprice suggestion @@ -198,16 +216,20 @@ func TestSuggestTipCap(t *testing.T) { {big.NewInt(32), big.NewInt(params.GWei * int64(30))}, // Fork point in last block {big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future } + for _, c := range cases { backend := newTestBackend(t, c.fork, false) oracle := NewOracle(backend, config) // The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G got, err := oracle.SuggestTipCap(context.Background()) + backend.teardown() + if err != nil { t.Fatalf("Failed to retrieve recommended gas price: %v", err) } + if got.Cmp(c.expect) != 0 { t.Fatalf("Gas price mismatch, want %d, got %d", c.expect, got) } diff --git a/eth/handler.go b/eth/handler.go index c40e3afe48..687f74513d 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -140,6 +140,7 @@ func newHandler(config *handlerConfig) (*handler, error) { if config.EventMux == nil { config.EventMux = new(event.TypeMux) // Nicety initialization for tests } + h := &handler{ networkID: config.Network, forkFilter: forkid.NewFilter(config.Chain), @@ -166,11 +167,9 @@ func newHandler(config *handlerConfig) (*handler, error) { if fullBlock.Number.Uint64() == 0 && snapBlock.Number.Uint64() > 0 { log.Warn("Preventing switching sync mode from full sync to snap sync") - // Note: Ideally this should never happen with bor, but to be extra // preventive we won't allow it to roll over to snap sync until // we have it working - // TODO(snap): Uncomment when we have snap sync working // h.snapSync = uint32(1) // log.Warn("Switch sync mode from full sync to snap sync") @@ -242,6 +241,7 @@ func newHandler(config *handlerConfig) (*handler, error) { return errors.New("unexpected post-merge header") } } + return h.chain.Engine().VerifyHeader(h.chain, header, true) } heighter := func() uint64 { @@ -252,6 +252,7 @@ func newHandler(config *handlerConfig) (*handler, error) { // after the transition. Print the warning log. if h.merger.PoSFinalized() { var ctx []interface{} + ctx = append(ctx, "blocks", len(blocks)) if len(blocks) > 0 { ctx = append(ctx, "firsthash", blocks[0].Hash()) @@ -259,7 +260,9 @@ func newHandler(config *handlerConfig) (*handler, error) { ctx = append(ctx, "lasthash", blocks[len(blocks)-1].Hash()) ctx = append(ctx, "lastnumber", blocks[len(blocks)-1].Number()) } + log.Warn("Unexpected insertion activity", ctx...) + return 0, errors.New("unexpected behavior after transition") } // If sync hasn't reached the checkpoint yet, deny importing weird blocks. @@ -281,6 +284,7 @@ func newHandler(config *handlerConfig) (*handler, error) { log.Warn("Snap syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash()) return 0, nil } + if h.merger.TDDReached() { // The blocks from the p2p network is regarded as untrusted // after the transition. In theory block gossip should be disabled @@ -293,21 +297,26 @@ func newHandler(config *handlerConfig) (*handler, error) { if ptd == nil { return 0, nil } + td := new(big.Int).Add(ptd, block.Difficulty()) if !h.chain.Config().IsTerminalPoWBlock(ptd, td) { log.Info("Filtered out non-termimal pow block", "number", block.NumberU64(), "hash", block.Hash()) return 0, nil } + if err := h.chain.InsertBlockWithoutSetHead(block); err != nil { return i, err } } + return 0, nil } + n, err := h.chain.InsertChain(blocks) if err == nil { atomic.StoreUint32(&h.acceptTxs, 1) // Mark initial sync done on any fetcher import } + return n, err } h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer) @@ -317,10 +326,12 @@ func newHandler(config *handlerConfig) (*handler, error) { if p == nil { return errors.New("unknown peer") } + return p.RequestTxs(hashes) } h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, h.txpool.AddRemotes, fetchTx, config.txArrivalWait) h.chainSync = newChainSyncer(h) + return h, nil } @@ -338,6 +349,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { if !h.chainSync.handlePeerEvent(peer) { return p2p.DiscQuitting } + h.peerWG.Add(1) defer h.peerWG.Done() @@ -355,7 +367,9 @@ 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 { if snap == nil { // If we are running snap-sync, we want to reserve roughly half the peer @@ -372,6 +386,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { return p2p.DiscTooManyPeers } } + peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) // Register the peer locally @@ -390,12 +405,14 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { 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 @@ -433,9 +450,11 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { if atomic.LoadUint32(&h.snapSync) == 1 { peer.Log().Warn("Dropping unsynced node during sync", "addr", peer.RemoteAddr(), "type", peer.Name()) res.Done <- errors.New("unsynced node cannot serve sync") + return } res.Done <- nil + return } // Validate the header and either drop the peer or continue @@ -443,6 +462,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { res.Done <- errors.New("too many headers in checkpoint response") return } + if headers[0].Hash() != h.checkpointHash { res.Done <- errors.New("checkpoint hash mismatch") return @@ -488,11 +508,14 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { res.Done <- errors.New("too many headers in required block response") return } + if headers[0].Number.Uint64() != number || headers[0].Hash() != hash { peer.Log().Info("Required block mismatch, dropping peer", "number", number, "hash", headers[0].Hash(), "want", hash) res.Done <- errors.New("required block mismatch") + return } + peer.Log().Debug("Peer required block verified", "number", number, "hash", hash) res.Done <- nil case <-timeout.C: @@ -517,6 +540,7 @@ func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error peer.Log().Warn("Snapshot extension registration failed", "err", err) return err } + return handler(peer) } @@ -551,6 +575,7 @@ func (h *handler) unregisterPeer(id string) { if peer.snapExt != nil { h.downloader.SnapSyncer.Unregister(id) } + h.downloader.UnregisterPeer(id) h.txFetcher.Drop(id) @@ -566,10 +591,12 @@ func (h *handler) Start(maxPeers int) { h.wg.Add(1) h.txsCh = make(chan core.NewTxsEvent, txChanSize) h.txsSub = h.txpool.SubscribeNewTxsEvent(h.txsCh) + go h.txBroadcastLoop() // broadcast mined blocks h.wg.Add(1) + h.minedBlockSub = h.eventMux.Subscribe(core.NewMinedBlockEvent{}) go h.minedBroadcastLoop() @@ -611,6 +638,7 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { return } } + hash := block.Hash() peers := h.peers.peersWithoutBlock(hash) @@ -629,7 +657,9 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { for _, peer := range transfer { peer.AsyncSendNewBlock(block, td) } + log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) + return } // Otherwise if the block is indeed in out own chain, announce it @@ -637,6 +667,7 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { for _, peer := range peers { peer.AsyncSendNewBlockHash(block) } + log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) } } @@ -669,16 +700,19 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { 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) peer.AsyncSendPooledTransactionHashes(hashes) } + log.Debug("Transaction broadcast", "txs", len(txs), "announce packs", annoPeers, "announced hashes", annoCount, "tx packs", directPeers, "broadcast txs", directCount) @@ -699,6 +733,7 @@ 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: diff --git a/eth/handler_bor_test.go b/eth/handler_bor_test.go index 857db70e95..7bf15b0650 100644 --- a/eth/handler_bor_test.go +++ b/eth/handler_bor_test.go @@ -86,6 +86,7 @@ func TestFetchWhitelistCheckpoints(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() + heimdall.fetchCheckpointCount = getMockFetchCheckpointFn(tc.count, tc.fetchErr) blockNums, blockHashes, err := handler.fetchWhitelistCheckpoints(ctx, bor, verifier, tc.first) diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 4ed6335769..db05c75645 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -46,6 +46,7 @@ func (h *ethHandler) PeerInfo(id enode.ID) interface{} { if p := h.peers.peer(id.String()); p != nil { return p.info() } + return nil } @@ -100,15 +101,18 @@ func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, 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 } @@ -137,5 +141,6 @@ func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td peer.SetHead(trueHead, trueTD) h.chainSync.handlePeerEvent(peer) } + return nil } diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index c1bdeb8ddb..1f9cc0b8f6 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -142,6 +142,7 @@ func testForkIDSplit(t *testing.T, protocol uint) { BloomCache: 1, }) ) + ethNoFork.Start(1000) ethProFork.Start(1000) @@ -159,6 +160,7 @@ func testForkIDSplit(t *testing.T, protocol uint) { peerNoFork := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pNoFork), p2pNoFork, nil) peerProFork := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pProFork), p2pProFork, nil) + defer peerNoFork.Close() defer peerProFork.Close() @@ -190,6 +192,7 @@ func testForkIDSplit(t *testing.T, protocol uint) { 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() @@ -221,6 +224,7 @@ func testForkIDSplit(t *testing.T, protocol uint) { peerNoFork = eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pNoFork), p2pNoFork, nil) peerProFork = eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pProFork), p2pProFork, nil) + defer peerNoFork.Close() defer peerProFork.Close() @@ -233,6 +237,7 @@ func testForkIDSplit(t *testing.T, protocol uint) { }(errc) var successes int + for i := 0; i < 2; i++ { select { case err := <-errc: @@ -272,6 +277,7 @@ func testRecvTransactions(t *testing.T, protocol uint) { handler.handler.acceptTxs = 1 // mark synced to accept transactions txs := make(chan core.NewTxsEvent) + sub := handler.txpool.SubscribeNewTxsEvent(txs) defer sub.Unsubscribe() @@ -282,6 +288,7 @@ func testRecvTransactions(t *testing.T, protocol uint) { src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, handler.txpool) sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, handler.txpool) + defer src.Close() defer sink.Close() @@ -294,6 +301,7 @@ func testRecvTransactions(t *testing.T, protocol uint) { head = handler.chain.CurrentBlock() td = handler.chain.GetTd(head.Hash(), head.Number.Uint64()) ) + 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") } @@ -344,6 +352,7 @@ func testSendTransactions(t *testing.T, protocol uint) { 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) @@ -354,6 +363,7 @@ func testSendTransactions(t *testing.T, protocol uint) { src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, handler.txpool) sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, handler.txpool) + defer src.Close() defer sink.Close() @@ -366,6 +376,7 @@ func testSendTransactions(t *testing.T, protocol uint) { head = handler.chain.CurrentBlock() td = handler.chain.GetTd(head.Hash(), head.Number.Uint64()) ) + 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") } @@ -374,10 +385,12 @@ func testSendTransactions(t *testing.T, protocol uint) { 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() @@ -394,6 +407,7 @@ func testSendTransactions(t *testing.T, protocol uint) { if _, ok := seen[hash]; ok { t.Errorf("duplicate transaction announced: %x", hash) } + seen[hash] = struct{}{} } case <-bcasts: @@ -404,6 +418,7 @@ func testSendTransactions(t *testing.T, protocol uint) { panic("unsupported protocol, please extend test") } } + for _, tx := range insert { if _, ok := seen[tx.Hash()]; !ok { t.Errorf("missing transaction: %x", tx.Hash()) @@ -453,6 +468,7 @@ func testTransactionPropagation(t *testing.T, protocol uint) { sourcePeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{byte(i + 1)}, "", nil, sourcePipe), sourcePipe, source.txpool) sinkPeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, sink.txpool) + defer sourcePeer.Close() defer sinkPeer.Close() @@ -479,6 +495,7 @@ func testTransactionPropagation(t *testing.T, protocol uint) { txs[nonce] = tx } + source.txpool.AddRemotes(txs) // Iterate through all the sinks and ensure they all got the transactions @@ -489,6 +506,7 @@ func testTransactionPropagation(t *testing.T, protocol uint) { arrived += len(event.Txs) case <-time.After(2 * time.Second): t.Errorf("sink %d: transaction propagation timed out: have %d, want %d", i, arrived, len(txs)) + timeout = true } } @@ -550,7 +568,9 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo } else { atomic.StoreUint32(&handler.handler.snapSync, 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")} @@ -566,6 +586,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo local := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pLocal), p2pLocal, handler.txpool) remote := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pRemote), p2pRemote, handler.txpool) + defer local.Close() defer remote.Close() @@ -583,6 +604,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo head = handler.chain.CurrentBlock() td = handler.chain.GetTd(head.Hash(), head.Number.Uint64()) ) + 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") } @@ -592,10 +614,12 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo if err != nil { t.Fatalf("failed to read checkpoint challenge: %v", err) } + request := new(eth.GetBlockHeadersPacket66) if err := msg.Decode(request); err != nil { t.Fatalf("failed to decode checkpoint challenge: %v", err) } + query := request.GetBlockHeadersPacket if query.Origin.Number != response.Number.Uint64() || query.Amount != 1 || query.Skip != 0 || query.Reverse { t.Fatalf("challenge mismatch: have [%d, %d, %d, %v] want [%d, %d, %d, %v]", @@ -627,6 +651,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo // Verify that the remote peer is maintained or dropped. if drop { <-handlerDone + if peers := handler.handler.peers.len(); peers != 0 { t.Fatalf("peer count mismatch: have %d, want %d", peers, 0) } @@ -666,6 +691,7 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { genesis = source.chain.Genesis() td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64()) ) + for i, sink := range sinks { sink := sink // Closure for gorotuine below @@ -675,15 +701,18 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { sourcePeer := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil) sinkPeer := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), 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 @@ -703,6 +732,7 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { // 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() { @@ -710,7 +740,9 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { done <- struct{}{} }() } + var received int + for { select { case <-done: @@ -720,6 +752,7 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { if received != bcasts { t.Errorf("broadcast count mismatch: have %d, want %d", received, bcasts) } + return } } @@ -755,6 +788,7 @@ func testBroadcastMalformedBlock(t *testing.T, protocol uint) { src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, source.txpool) sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, source.txpool) + defer src.Close() defer sink.Close() @@ -766,6 +800,7 @@ func testBroadcastMalformedBlock(t *testing.T, protocol uint) { 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") } @@ -774,6 +809,7 @@ func testBroadcastMalformedBlock(t *testing.T, protocol uint) { backend := new(testEthHandler) blocks := make(chan *types.Block, 1) + sub := backend.blockBroadcasts.Subscribe(blocks) defer sub.Unsubscribe() diff --git a/eth/handler_snap.go b/eth/handler_snap.go index 767416ffd6..d86fa01a24 100644 --- a/eth/handler_snap.go +++ b/eth/handler_snap.go @@ -40,6 +40,7 @@ func (h *snapHandler) PeerInfo(id enode.ID) interface{} { return p.snapExt.info() } } + return nil } diff --git a/eth/handler_test.go b/eth/handler_test.go index 092c0b6ecc..3e52871b97 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -88,7 +88,9 @@ func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error { for _, tx := range txs { p.pool[tx.Hash()] = tx } + p.txFeed.Send(core.NewTxsEvent{Txs: txs}) + return make([]error, len(txs)) } @@ -98,13 +100,16 @@ func (p *testTxPool) Pending(ctx context.Context, enforceTips bool) map[common.A 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 } @@ -144,6 +149,7 @@ func newTestHandlerWithBlocks(blocks int) *testHandler { if _, err := chain.InsertChain(bs); err != nil { panic(err) } + txpool := newTestTxPool() handler, _ := newHandler(&handlerConfig{ diff --git a/eth/peerset.go b/eth/peerset.go index b9cc1e03ac..14d2a7ea1b 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -84,6 +84,7 @@ func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { if _, ok := ps.peers[id]; ok { return errPeerAlreadyRegistered // avoid connections with the same id as existing ones } + if _, ok := ps.snapPend[id]; ok { return errPeerAlreadyRegistered // avoid connections with the same id as pending ones } @@ -91,9 +92,12 @@ func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { if wait, ok := ps.snapWait[id]; ok { delete(ps.snapWait, id) wait <- peer + return nil } + ps.snapPend[id] = peer + return nil } @@ -112,6 +116,7 @@ func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) { ps.lock.Unlock() 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 @@ -121,6 +126,7 @@ func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) { delete(ps.snapPend, id) ps.lock.Unlock() + return snap, nil } // Otherwise wait for `snap` to connect concurrently @@ -141,10 +147,12 @@ func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error { if ps.closed { return errPeerSetClosed } + id := peer.ID() if _, ok := ps.peers[id]; ok { return errPeerAlreadyRegistered } + eth := ðPeer{ Peer: peer, } @@ -152,7 +160,9 @@ func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error { eth.snapExt = &snapPeer{ext} ps.snapPeers++ } + ps.peers[id] = eth + return nil } @@ -166,10 +176,13 @@ func (ps *peerSet) unregisterPeer(id string) error { if !ok { return errPeerNotRegistered } + delete(ps.peers, id) + if peer.snapExt != nil { ps.snapPeers-- } + return nil } @@ -188,11 +201,13 @@ func (ps *peerSet) peersWithoutBlock(hash common.Hash) []*ethPeer { defer ps.lock.RUnlock() list := make([]*ethPeer, 0, len(ps.peers)) + for _, p := range ps.peers { if !p.KnownBlock(hash) { list = append(list, p) } } + return list } @@ -203,11 +218,13 @@ func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer { defer ps.lock.RUnlock() list := make([]*ethPeer, 0, len(ps.peers)) + for _, p := range ps.peers { if !p.KnownTransaction(hash) { list = append(list, p) } } + return list } @@ -239,11 +256,13 @@ func (ps *peerSet) peerWithHighestTD() *eth.Peer { bestPeer *eth.Peer bestTd *big.Int ) + for _, p := range ps.peers { if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 { bestPeer, bestTd = p.Peer, td } } + return bestPeer } @@ -255,5 +274,6 @@ func (ps *peerSet) close() { for _, p := range ps.peers { p.Disconnect(p2p.DiscQuitting) } + ps.closed = true } diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go index 3045303f22..45844a66c3 100644 --- a/eth/protocols/eth/broadcast.go +++ b/eth/protocols/eth/broadcast.go @@ -46,12 +46,14 @@ func (p *Peer) broadcastBlocks() { 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: @@ -70,6 +72,7 @@ func (p *Peer) broadcastTransactions() { 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 { @@ -79,13 +82,16 @@ func (p *Peer) broadcastTransactions() { 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 += common.StorageSize(tx.Size()) } + hashesCount++ } + queue = queue[:copy(queue, queue[hashesCount:])] // If there's anything available to transfer, fire up an async writer @@ -96,6 +102,7 @@ func (p *Peer) broadcastTransactions() { fail <- err return } + close(done) p.Log().Trace("Sent transactions", "count", len(txs)) }() @@ -137,6 +144,7 @@ func (p *Peer) announceTransactions() { 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 { @@ -148,6 +156,7 @@ func (p *Peer) announceTransactions() { pendingSizes []uint32 size common.StorageSize ) + for count = 0; count < len(queue) && size < maxTxPacketSize; count++ { if tx := p.txpool.Get(queue[count]); tx != nil { pending = append(pending, queue[count]) @@ -174,6 +183,7 @@ func (p *Peer) announceTransactions() { return } } + close(done) p.Log().Trace("Sent transaction announcements", "count", len(pending)) }() diff --git a/eth/protocols/eth/discovery.go b/eth/protocols/eth/discovery.go index 87857244b5..b1a986f706 100644 --- a/eth/protocols/eth/discovery.go +++ b/eth/protocols/eth/discovery.go @@ -44,6 +44,7 @@ func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) { go func() { defer sub.Unsubscribe() + for { select { case <-newHead: @@ -60,6 +61,7 @@ func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) { // currentENREntry constructs an `eth` ENR entry based on the current state of the chain. func currentENREntry(chain *core.BlockChain) *enrEntry { head := chain.CurrentHeader() + return &enrEntry{ ForkID: forkid.NewID(chain.Config(), chain.Genesis().Hash(), head.Number.Uint64(), head.Time), } diff --git a/eth/protocols/eth/dispatcher.go b/eth/protocols/eth/dispatcher.go index 0b16060978..3c0f36fc08 100644 --- a/eth/protocols/eth/dispatcher.go +++ b/eth/protocols/eth/dispatcher.go @@ -62,6 +62,7 @@ func (r *Request) Close() error { if r.peer == nil { // Tests mock out the dispatcher, skip internal cancellation return nil } + cancelOp := &cancel{ id: r.id, fail: make(chan error), @@ -71,7 +72,9 @@ func (r *Request) Close() error { if err := <-cancelOp.fail; err != nil { return err } + close(r.cancel) + return nil case <-r.peer.term: return errDisconnected diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 55dc77fab8..4d3987d444 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -96,6 +96,7 @@ 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 { version := version // Closure @@ -121,6 +122,7 @@ func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2 DialCandidates: dnsdisc, } } + return protocols } @@ -221,9 +223,11 @@ func handleMessage(backend Backend, peer *Peer) error { if err != nil { return err } + if msg.Size > maxMessageSize { return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) } + defer msg.Discard() var handlers = eth66 @@ -247,8 +251,10 @@ func handleMessage(backend Backend, peer *Peer) error { metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) }(time.Now()) } + if handler := handlers[msg.Code]; handler != nil { return handler(backend, msg, peer) } + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) } diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 888e3b5578..1b4af22fb5 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -178,10 +178,12 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { for i := range unknown { unknown[i] = byte(i) } + getHashes := func(from, limit uint64) (hashes []common.Hash) { for i := uint64(0); i < limit; i++ { hashes = append(hashes, backend.chain.GetCanonicalHash(from-1-i)) } + return hashes } // Create a batch of tests for various scenarios @@ -322,6 +324,7 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { RequestId: 123, GetBlockHeadersPacket: tt.query, }) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, &BlockHeadersPacket66{ RequestId: 123, BlockHeadersPacket: headers, @@ -337,6 +340,7 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { RequestId: 456, GetBlockHeadersPacket: tt.query, }) + expected := &BlockHeadersPacket66{RequestId: 456, BlockHeadersPacket: headers} if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, expected); err != nil { t.Errorf("test %d by hash: headers mismatch: %v", i, err) @@ -414,6 +418,7 @@ func testGetBlockBodies(t *testing.T, protocol uint) { bodies []*BlockBody seen = make(map[int64]bool) ) + for j := 0; j < tt.random; j++ { for { num := rand.Int63n(int64(backend.chain.CurrentBlock().Number.Uint64())) @@ -422,15 +427,19 @@ func testGetBlockBodies(t *testing.T, protocol uint) { 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(), Withdrawals: block.Withdrawals()}) } + break } } } + for j, hash := range tt.explicit { hashes = append(hashes, hash) + if tt.available[j] && len(bodies) < tt.expected { block := backend.chain.GetBlockByHash(hash) @@ -443,6 +452,7 @@ func testGetBlockBodies(t *testing.T, protocol uint) { RequestId: 123, GetBlockBodiesPacket: hashes, }) + if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, &BlockBodiesPacket66{ RequestId: 123, BlockBodiesPacket: bodies, @@ -514,6 +524,7 @@ func testGetNodeData(t *testing.T, protocol uint, drop bool) { // Collect all state tree hashes. var hashes []common.Hash + it := backend.db.NewIterator(nil, nil) for it.Next() { if key := it.Key(); len(key) == common.HashLength { @@ -527,6 +538,7 @@ func testGetNodeData(t *testing.T, protocol uint, drop bool) { RequestId: 123, GetNodeDataPacket: hashes, }) + msg, err := peer.app.ReadMsg() if !drop { @@ -537,11 +549,14 @@ func testGetNodeData(t *testing.T, protocol uint, drop bool) { if err != nil { return } + t.Fatalf("succeeded to read node data response on non-supporting protocol: %v", msg) } + if msg.Code != NodeDataMsg { t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, NodeDataMsg) } + var res NodeDataPacket66 if err := msg.Decode(&res); err != nil { t.Fatalf("failed to decode response node data: %v", err) @@ -567,6 +582,7 @@ func testGetNodeData(t *testing.T, protocol uint, drop bool) { for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ { root := backend.chain.GetBlockByNumber(i).Root() reconstructed, _ := state.New(root, state.NewDatabase(reconstructDB), nil) + for j, acc := range accounts { state, _ := backend.chain.StateAt(root) bw := state.GetBalance(acc) @@ -575,6 +591,7 @@ func testGetNodeData(t *testing.T, protocol uint, drop bool) { if (bw == nil) != (bh == nil) { t.Errorf("block %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) } + if bw != nil && bh != nil && bw.Cmp(bh) != 0 { t.Errorf("block %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) } @@ -658,6 +675,7 @@ func testGetBlockReceipts(t *testing.T, protocol uint) { RequestId: 123, GetReceiptsPacket: hashes, }) + if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, &ReceiptsPacket66{ RequestId: 123, ReceiptsPacket: receipts, diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index c15ec8bd47..0ded928328 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -35,7 +35,9 @@ func handleGetBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(&query); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + response := ServiceGetBlockHeadersQuery(backend.Chain(), query.GetBlockHeadersPacket, peer) + return peer.ReplyBlockHeadersRLP(query.RequestId, response) } @@ -62,14 +64,17 @@ func serviceNonContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBloc 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 = chain.GetHeaderByHash(query.Origin.Hash) if origin != nil { query.Origin.Number = origin.Number.Uint64() @@ -80,9 +85,11 @@ func serviceNonContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBloc } else { origin = chain.GetHeaderByNumber(query.Origin.Number) } + if origin == nil { break } + if rlpData, err := rlp.EncodeToBytes(origin); err != nil { log.Crit("Unable to encode our own headers", "err", err) } else { @@ -106,14 +113,17 @@ func serviceNonContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBloc 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 := chain.GetHeaderByNumber(next); header != nil { nextHash := header.Hash() expOldHash, _ := chain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) + if expOldHash == query.Origin.Hash { query.Origin.Hash, query.Origin.Number = nextHash, next } else { @@ -136,6 +146,7 @@ func serviceNonContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBloc query.Origin.Number += query.Skip + 1 } } + return headers } @@ -144,6 +155,7 @@ func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHe if count > maxHeadersServe { count = maxHeadersServe } + if query.Origin.Hash == (common.Hash{}) { // Number mode, just return the canon chain segment. The backend // delivers in [N, N-1, N-2..] descending order, so we need to @@ -152,12 +164,15 @@ func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHe if !query.Reverse { from = from + count - 1 } + headers := chain.GetHeadersFrom(from, count) + if !query.Reverse { for i, j := 0, len(headers)-1; i < j; i, j = i+1, j-1 { headers[i], headers[j] = headers[j], headers[i] } } + return headers } // Hash mode. @@ -166,6 +181,7 @@ func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHe hash = query.Origin.Hash header = chain.GetHeaderByHash(hash) ) + if header != nil { rlpData, _ := rlp.EncodeToBytes(header) headers = append(headers, rlpData) @@ -173,6 +189,7 @@ func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHe // We don't even have the origin header return headers } + num := header.Number.Uint64() if !query.Reverse { // Theoretically, we are tasked to deliver header by hash H, and onwards. @@ -182,11 +199,14 @@ func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHe // Not canon, we can't deliver descendants return headers } + descendants := chain.GetHeadersFrom(num+count-1, count-1) for i, j := 0, len(descendants)-1; i < j; i, j = i+1, j-1 { descendants[i], descendants[j] = descendants[j], descendants[i] } + headers = append(headers, descendants...) + return headers } { // Last mode: deliver ancestors of H @@ -195,9 +215,11 @@ func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHe if header == nil { break } + rlpData, _ := rlp.EncodeToBytes(header) headers = append(headers, rlpData) } + return headers } } @@ -208,7 +230,9 @@ func handleGetBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(&query); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + response := ServiceGetBlockBodiesQuery(backend.Chain(), query.GetBlockBodiesPacket) + return peer.ReplyBlockBodiesRLP(query.RequestId, response) } @@ -220,16 +244,19 @@ func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesPack bytes int bodies []rlp.RawValue ) + for lookups, hash := range query { if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe || lookups >= 2*maxBodiesServe { break } + if data := chain.GetBodyRLP(hash); len(data) != 0 { bodies = append(bodies, data) bytes += len(data) } } + return bodies } @@ -239,7 +266,9 @@ func handleGetNodeData66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(&query); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + response := ServiceGetNodeDataQuery(backend.Chain(), query.GetNodeDataPacket) + return peer.ReplyNodeData(query.RequestId, response) } @@ -251,6 +280,7 @@ func ServiceGetNodeDataQuery(chain *core.BlockChain, query GetNodeDataPacket) [] bytes int nodes [][]byte ) + for lookups, hash := range query { if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe || lookups >= 2*maxNodeDataServe { @@ -262,11 +292,13 @@ func ServiceGetNodeDataQuery(chain *core.BlockChain, query GetNodeDataPacket) [] // Read the contract code with prefix only to save unnecessary lookups. entry, err = chain.ContractCodeWithPrefix(hash) } + if err == nil && len(entry) > 0 { nodes = append(nodes, entry) bytes += len(entry) } } + return nodes } @@ -276,7 +308,9 @@ func handleGetReceipts66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(&query); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + response := ServiceGetReceiptsQuery(backend.Chain(), query.GetReceiptsPacket) + return peer.ReplyReceiptsRLP(query.RequestId, response) } @@ -288,6 +322,7 @@ func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsPacket) [] bytes int receipts []rlp.RawValue ) + for lookups, hash := range query { if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe || lookups >= 2*maxReceiptsServe { @@ -308,6 +343,7 @@ func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsPacket) [] bytes += len(encoded) } } + return receipts } @@ -331,17 +367,21 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(ann); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + if err := ann.sanityCheck(); err != nil { return 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 } + ann.Block.ReceivedAt = msg.Time() ann.Block.ReceivedFrom = peer @@ -357,13 +397,16 @@ func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + metadata := func() interface{} { hashes := make([]common.Hash, len(res.BlockHeadersPacket)) for i, header := range res.BlockHeadersPacket { hashes[i] = header.Hash() } + return hashes } + return peer.dispatchResponse(&Response{ id: res.RequestId, code: BlockHeadersMsg, @@ -377,12 +420,14 @@ func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + metadata := func() interface{} { var ( txsHashes = make([]common.Hash, len(res.BlockBodiesPacket)) uncleHashes = make([]common.Hash, len(res.BlockBodiesPacket)) withdrawalHashes = make([]common.Hash, len(res.BlockBodiesPacket)) ) + hasher := trie.NewStackTrie(nil) for i, body := range res.BlockBodiesPacket { txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher) @@ -395,6 +440,7 @@ func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { return [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes} } + return peer.dispatchResponse(&Response{ id: res.RequestId, code: BlockBodiesMsg, @@ -408,6 +454,7 @@ func handleNodeData66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + return peer.dispatchResponse(&Response{ id: res.RequestId, code: NodeDataMsg, @@ -421,14 +468,18 @@ func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + metadata := func() interface{} { hasher := trie.NewStackTrie(nil) hashes := make([]common.Hash, len(res.ReceiptsPacket)) + for i, receipt := range res.ReceiptsPacket { hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher) } + return hashes } + return peer.dispatchResponse(&Response{ id: res.RequestId, code: ReceiptsMsg, @@ -451,6 +502,7 @@ func handleNewPooledTransactionHashes66(backend Backend, msg Decoder, peer *Peer for _, hash := range *ann { peer.markTransaction(hash) } + return backend.Handle(peer, ann) } @@ -484,7 +536,9 @@ func handleGetPooledTransactions66(backend Backend, msg Decoder, peer *Peer) err 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) } @@ -495,6 +549,7 @@ func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsPac hashes []common.Hash txs []rlp.RawValue ) + for _, hash := range query { if bytes >= softResponseLimit { break @@ -513,6 +568,7 @@ func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsPac bytes += len(encoded) } } + return hashes, txs } @@ -526,13 +582,16 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error { 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) } @@ -546,13 +605,16 @@ func handlePooledTransactions66(backend Backend, msg Decoder, peer *Peer) error 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()) } + requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId) return backend.Handle(peer, &txs.PooledTransactionsPacket) diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go index 9a2769fa0d..dcb3840080 100644 --- a/eth/protocols/eth/handshake.go +++ b/eth/protocols/eth/handshake.go @@ -53,8 +53,10 @@ func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis 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: @@ -65,6 +67,7 @@ func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis 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 @@ -72,6 +75,7 @@ func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis if tdlen := p.td.BitLen(); tdlen > 100 { return fmt.Errorf("too large total difficulty: bitlen %d", tdlen) } + return nil } @@ -81,9 +85,11 @@ func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.H 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) } @@ -91,17 +97,22 @@ func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.H 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 index 5c6727d91c..662d91fd59 100644 --- a/eth/protocols/eth/handshake_test.go +++ b/eth/protocols/eth/handshake_test.go @@ -42,6 +42,7 @@ func testHandshake(t *testing.T, protocol uint) { td = backend.chain.GetTd(head.Hash(), head.Number.Uint64()) forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis().Hash(), backend.chain.CurrentHeader().Number.Uint64(), backend.chain.CurrentHeader().Time) ) + tests := []struct { code uint64 data interface{} diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 433858893c..8b091de0ab 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -62,6 +62,7 @@ func max(a, b int) int { if a > b { return a } + return b } @@ -145,6 +146,7 @@ func (p *Peer) Head() (hash common.Hash, td *big.Int) { defer p.lock.RUnlock() copy(hash[:], p.head[:]) + return hash, new(big.Int).Set(p.td) } @@ -195,6 +197,7 @@ func (p *Peer) SendTransactions(txs types.Transactions) error { for _, tx := range txs { p.knownTxs.Add(tx.Hash()) } + return p2p.Send(p.rw, TransactionsMsg, txs) } @@ -272,6 +275,7 @@ func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error request[i].Hash = hashes[i] request[i].Number = numbers[i] } + return p2p.Send(p.rw, NewBlockHashesMsg, request) } @@ -292,6 +296,7 @@ func (p *Peer) AsyncSendNewBlockHash(block *types.Block) { 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 p.knownBlocks.Add(block.Hash()) + return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{ Block: block, TD: td, @@ -347,6 +352,7 @@ func (p *Peer) ReplyReceiptsRLP(id uint64, receipts []rlp.RawValue) error { // single header. It is used solely by the fetcher. func (p *Peer) RequestOneHeader(hash common.Hash, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching single header", "hash", hash) + id := rand.Uint64() req := &Request{ @@ -367,6 +373,7 @@ func (p *Peer) RequestOneHeader(hash common.Hash, sink chan *Response) (*Request if err := p.dispatchRequest(req); err != nil { return nil, err } + return req, nil } @@ -374,6 +381,7 @@ func (p *Peer) RequestOneHeader(hash common.Hash, sink chan *Response) (*Request // specified header query, based on the hash of an origin block. func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) + id := rand.Uint64() req := &Request{ @@ -394,6 +402,7 @@ func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, re if err := p.dispatchRequest(req); err != nil { return nil, err } + return req, nil } @@ -401,6 +410,7 @@ func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, re // specified header query, based on the number of an origin block. func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) + id := rand.Uint64() req := &Request{ @@ -421,6 +431,7 @@ func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, rever if err := p.dispatchRequest(req); err != nil { return nil, err } + return req, nil } @@ -428,6 +439,7 @@ func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, rever // specified. func (p *Peer) RequestBodies(hashes []common.Hash, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) + id := rand.Uint64() req := &Request{ @@ -443,6 +455,7 @@ func (p *Peer) RequestBodies(hashes []common.Hash, sink chan *Response) (*Reques if err := p.dispatchRequest(req); err != nil { return nil, err } + return req, nil } @@ -450,6 +463,7 @@ func (p *Peer) RequestBodies(hashes []common.Hash, sink chan *Response) (*Reques // data, corresponding to the specified hashes. func (p *Peer) RequestNodeData(hashes []common.Hash, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of state data", "count", len(hashes)) + id := rand.Uint64() req := &Request{ @@ -465,12 +479,14 @@ func (p *Peer) RequestNodeData(hashes []common.Hash, sink chan *Response) (*Requ if err := p.dispatchRequest(req); err != nil { return nil, err } + return req, nil } // RequestReceipts fetches a batch of transaction receipts from a remote node. func (p *Peer) RequestReceipts(hashes []common.Hash, sink chan *Response) (*Request, error) { p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) + id := rand.Uint64() req := &Request{ @@ -486,15 +502,18 @@ func (p *Peer) RequestReceipts(hashes []common.Hash, sink chan *Response) (*Requ if err := p.dispatchRequest(req); err != nil { return nil, err } + return req, nil } // 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)) + id := rand.Uint64() requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id) + return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket66{ RequestId: id, GetPooledTransactionsPacket: hashes, @@ -520,6 +539,7 @@ func (k *knownCache) Add(hashes ...common.Hash) { for k.hashes.Cardinality() > max(0, k.max-len(hashes)) { k.hashes.Pop() } + for _, hash := range hashes { k.hashes.Add(hash) } diff --git a/eth/protocols/eth/peer_test.go b/eth/protocols/eth/peer_test.go index efbbbc6fff..9d02f29a4d 100644 --- a/eth/protocols/eth/peer_test.go +++ b/eth/protocols/eth/peer_test.go @@ -43,10 +43,12 @@ func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan // 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() { defer app.Close() @@ -54,6 +56,7 @@ func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan return Handle(backend, peer) }) }() + return &testPeer{app: app, net: net, Peer: peer}, errc } @@ -84,6 +87,7 @@ func TestPeerSet(t *testing.T) { // add item in batch s.Add(vals...) + if s.Cardinality() < size { t.Fatalf("bad size") } diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 68937e7080..dd55b9b87b 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -109,9 +109,11 @@ func (p *NewBlockHashesPacket) Unpack() ([]common.Hash, []uint64) { 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 } @@ -144,9 +146,11 @@ 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) } @@ -154,6 +158,7 @@ func (hn *HashOrNumber) EncodeRLP(w io.Writer) error { // into either a block hash or a block number. func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error { _, size, err := s.Kind() + switch { case err != nil: return err @@ -203,6 +208,7 @@ func (request *NewBlockPacket) sanityCheck() error { if tdlen := request.TD.BitLen(); tdlen > 100 { return fmt.Errorf("too large block TD: bitlen %d", tdlen) } + return nil } @@ -251,6 +257,7 @@ func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header, uncleset = make([][]*types.Header, len(*p)) withdrawalset = make([][]*types.Withdrawal, len(*p)) ) + for i, body := range *p { txset[i], uncleset[i], withdrawalset[i] = body.Transactions, body.Uncles, body.Withdrawals } diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go index a86fbb0a69..95c4fe2f78 100644 --- a/eth/protocols/eth/protocol_test.go +++ b/eth/protocols/eth/protocol_test.go @@ -57,11 +57,13 @@ func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { } 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) @@ -132,6 +134,7 @@ func TestEth66Messages(t *testing.T) { err error ) + header = &types.Header{ Difficulty: big.NewInt(2222), Number: big.NewInt(3333), @@ -147,10 +150,12 @@ func TestEth66Messages(t *testing.T) { "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) } @@ -160,6 +165,7 @@ func TestEth66Messages(t *testing.T) { Transactions: txs, Uncles: []*types.Header{header}, } + blockBodyRlp, err = rlp.EncodeToBytes(blockBody) if err != nil { t.Fatal(err) @@ -191,10 +197,12 @@ func TestEth66Messages(t *testing.T) { GasUsed: 111111, }, } + rlpData, err := rlp.EncodeToBytes(receipts) if err != nil { t.Fatal(err) } + receiptsRlp = rlpData } diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 55781ac54b..96ea6cb412 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -90,6 +90,7 @@ func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { }) protocols := make([]p2p.Protocol, len(ProtocolVersions)) + for i, version := range ProtocolVersions { version := version // Closure @@ -112,6 +113,7 @@ func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { DialCandidates: dnsdisc, } } + return protocols } @@ -135,10 +137,13 @@ func HandleMessage(backend Backend, peer *Peer) error { if err != nil { return err } + if msg.Size > maxMessageSize { return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) } + defer msg.Discard() + start := time.Now() // Track the amount of time it takes to serve the request and run the handler if metrics.Enabled { @@ -216,6 +221,7 @@ func HandleMessage(backend Backend, peer *Peer) error { } } } + requestTracker.Fulfil(peer.id, peer.version, StorageRangesMsg, res.ID) return backend.Handle(peer, res) @@ -241,6 +247,7 @@ func HandleMessage(backend Backend, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + requestTracker.Fulfil(peer.id, peer.version, ByteCodesMsg, res.ID) return backend.Handle(peer, res) @@ -268,6 +275,7 @@ func HandleMessage(backend Backend, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + requestTracker.Fulfil(peer.id, peer.version, TrieNodesMsg, res.ID) return backend.Handle(peer, res) @@ -288,6 +296,7 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac if err != nil { return nil, nil } + it, err := chain.Snapshots().AccountIterator(req.Root, req.Origin) if err != nil { return nil, nil @@ -298,6 +307,7 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac size uint64 last common.Hash ) + for it.Next() { hash, account := it.Hash(), common.CopyBytes(it.Account()) @@ -314,6 +324,7 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac if bytes.Compare(hash[:], req.Limit[:]) >= 0 { break } + if size > req.Bytes { break } @@ -326,16 +337,19 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac log.Warn("Failed to prove account range", "origin", req.Origin, "err", err) return nil, nil } + 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 nil, nil } } + var proofs [][]byte for _, blob := range proof.NodeList() { proofs = append(proofs, blob) } + return accounts, proofs } @@ -356,6 +370,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP 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) @@ -367,6 +382,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP 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 @@ -382,11 +398,13 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP last common.Hash abort bool ) + for it.Next() { if size >= hardLimit { abort = true break } + hash, slot := it.Hash(), common.CopyBytes(it.Slot()) // Track the returned interval for the Merkle proofs @@ -403,9 +421,11 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP break } } + if len(storage) > 0 { slots = append(slots, storage) } + it.Release() // Generate the Merkle proofs for the first and last storage slot, but @@ -418,26 +438,32 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP if err != nil { return nil, nil } + acc, err := accTrie.GetAccountByHash(account) if err != nil || acc == nil { return nil, nil } + id := trie.StorageTrieID(req.Root, account, acc.Root) + stTrie, err := trie.NewStateTrie(id, chain.StateCache().TrieDB()) if err != nil { return nil, nil } + 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 nil, nil } + 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 nil, nil } } + for _, blob := range proof.NodeList() { proofs = append(proofs, blob) } @@ -447,6 +473,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP break } } + return slots, proofs } @@ -456,6 +483,7 @@ func ServiceGetByteCodesQuery(chain *core.BlockChain, req *GetByteCodesPacket) [ if req.Bytes > softResponseLimit { req.Bytes = softResponseLimit } + if len(req.Hashes) > maxCodeLookups { req.Hashes = req.Hashes[:maxCodeLookups] } @@ -464,6 +492,7 @@ func ServiceGetByteCodesQuery(chain *core.BlockChain, req *GetByteCodesPacket) [ codes [][]byte bytes uint64 ) + for _, hash := range req.Hashes { if hash == types.EmptyCodeHash { // Peers should not request the empty code, but if they do, at @@ -473,10 +502,12 @@ func ServiceGetByteCodesQuery(chain *core.BlockChain, req *GetByteCodesPacket) [ codes = append(codes, blob) bytes += uint64(len(blob)) } + if bytes > req.Bytes { break } } + return codes } @@ -502,6 +533,7 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s bytes uint64 loads int // Trie hash expansions to count database reads ) + for _, pathset := range req.Paths { switch len(pathset) { case 0: @@ -512,9 +544,11 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s // If we're only retrieving an account trie node, fetch it directly blob, resolved, err := accTrie.GetNode(pathset[0]) loads += resolved // always account database reads, even for failures + if err != nil { break } + nodes = append(nodes, blob) bytes += uint64(len(blob)) @@ -526,30 +560,39 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s // but can look up the account via the trie instead. account, err := accTrie.GetAccountByHash(common.BytesToHash(pathset[0])) loads += 8 // We don't know the exact cost of lookup, this is an estimate + if err != nil || account == nil { break } + stRoot = account.Root } else { account, err := snap.Account(common.BytesToHash(pathset[0])) loads++ // always account database reads, even for failures + if err != nil || account == nil { break } + stRoot = common.BytesToHash(account.Root) } + id := trie.StorageTrieID(req.Root, common.BytesToHash(pathset[0]), stRoot) stTrie, err := trie.NewStateTrie(id, triedb) loads++ // always account database reads, even for failures + if err != nil { break } + for _, path := range pathset[1:] { blob, resolved, err := stTrie.GetNode(path) loads += resolved // always account database reads, even for failures + if err != nil { break } + nodes = append(nodes, blob) bytes += uint64(len(blob)) @@ -564,6 +607,7 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s break } } + return nodes, nil } diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go index 3db6e22cbd..3e397c0f92 100644 --- a/eth/protocols/snap/peer.go +++ b/eth/protocols/snap/peer.go @@ -37,6 +37,7 @@ type Peer struct { // version. func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer { id := p.ID().String() + return &Peer{ id: id, Peer: p, @@ -77,6 +78,7 @@ func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit co p.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) requestTracker.Track(p.id, p.version, GetAccountRangeMsg, AccountRangeMsg, id) + return p2p.Send(p.rw, GetAccountRangeMsg, &GetAccountRangePacket{ ID: id, Root: root, @@ -95,7 +97,9 @@ func (p *Peer) RequestStorageRanges(id uint64, root common.Hash, accounts []comm } else { p.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes)) } + requestTracker.Track(p.id, p.version, GetStorageRangesMsg, StorageRangesMsg, id) + return p2p.Send(p.rw, GetStorageRangesMsg, &GetStorageRangesPacket{ ID: id, Root: root, @@ -111,6 +115,7 @@ func (p *Peer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) e p.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes)) requestTracker.Track(p.id, p.version, GetByteCodesMsg, ByteCodesMsg, id) + return p2p.Send(p.rw, GetByteCodesMsg, &GetByteCodesPacket{ ID: id, Hashes: hashes, @@ -124,6 +129,7 @@ func (p *Peer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePat p.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes)) requestTracker.Track(p.id, p.version, GetTrieNodesMsg, TrieNodesMsg, id) + return p2p.Send(p.rw, GetTrieNodesMsg, &GetTrieNodesPacket{ ID: id, Root: root, diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go index 60a254f396..df7c95e8a5 100644 --- a/eth/protocols/snap/protocol.go +++ b/eth/protocols/snap/protocol.go @@ -103,13 +103,16 @@ func (p *AccountRangePacket) Unpack() ([]common.Hash, [][]byte, error) { 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 } @@ -143,14 +146,17 @@ func (p *StorageRangesPacket) Unpack() ([][]common.Hash, [][][]byte) { 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 } diff --git a/eth/protocols/snap/range.go b/eth/protocols/snap/range.go index 2627cb954b..376b921782 100644 --- a/eth/protocols/snap/range.go +++ b/eth/protocols/snap/range.go @@ -53,7 +53,9 @@ func (r *hashRange) Next() bool { if overflow { return false } + r.current = next + return true } @@ -69,13 +71,16 @@ func (r *hashRange) End() common.Hash { if overflow { return common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } + return next.SubUint64(next, 1).Bytes32() } // incHash returns the next hash, in lexicographical order (a.k.a plus one) func incHash(h common.Hash) common.Hash { var a uint256.Int + a.SetBytes32(h[:]) a.AddUint64(&a, 1) + return common.Hash(a.Bytes32()) } diff --git a/eth/protocols/snap/range_test.go b/eth/protocols/snap/range_test.go index 3461439e54..ade6fe7921 100644 --- a/eth/protocols/snap/range_test.go +++ b/eth/protocols/snap/range_test.go @@ -119,21 +119,26 @@ func TestHashRanges(t *testing.T) { starts = []common.Hash{{}} ends = []common.Hash{r.End()} ) + for r.Next() { starts = append(starts, r.Start()) ends = append(ends, r.End()) } + if len(starts) != len(tt.starts) { t.Errorf("test %d: starts count mismatch: have %d, want %d", i, len(starts), len(tt.starts)) } + for j := 0; j < len(starts) && j < len(tt.starts); j++ { if starts[j] != tt.starts[j] { t.Errorf("test %d, start %d: hash mismatch: have %x, want %x", i, j, starts[j], tt.starts[j]) } } + if len(ends) != len(tt.ends) { t.Errorf("test %d: ends count mismatch: have %d, want %d", i, len(ends), len(tt.ends)) } + for j := 0; j < len(ends) && j < len(tt.ends); j++ { if ends[j] != tt.ends[j] { t.Errorf("test %d, end %d: hash mismatch: have %x, want %x", i, j, ends[j], tt.ends[j]) diff --git a/eth/protocols/snap/sort_test.go b/eth/protocols/snap/sort_test.go index a6bbe2f09f..2ed06566b5 100644 --- a/eth/protocols/snap/sort_test.go +++ b/eth/protocols/snap/sort_test.go @@ -28,11 +28,13 @@ func hexToNibbles(s string) []byte { if len(s) >= 2 && s[0] == '0' && s[1] == 'x' { s = s[2:] } + var s2 []byte for _, ch := range []byte(s) { s2 = append(s2, '0') s2 = append(s2, ch) } + return common.Hex2Bytes(string(s2)) } @@ -47,10 +49,12 @@ func TestRequestSorting(t *testing.T) { data := hexToNibbles(path) return string(data) } + var ( hashes []common.Hash paths []string ) + for _, x := range []string{ "0x9", "0x012345678901234567890123456789010123456789012345678901234567890195", @@ -66,12 +70,14 @@ func TestRequestSorting(t *testing.T) { paths = append(paths, f(x)) hashes = append(hashes, common.Hash{}) } + _, _, syncPaths, pathsets := sortByAccountPath(paths, hashes) { var b = new(bytes.Buffer) for i := 0; i < len(syncPaths); i++ { fmt.Fprintf(b, "\n%d. paths %x", i, syncPaths[i]) } + want := ` 0. paths [0099] 1. paths [0123456789012345678901234567890101234567890123456789012345678901 00] @@ -92,6 +98,7 @@ func TestRequestSorting(t *testing.T) { for i := 0; i < len(pathsets); i++ { fmt.Fprintf(b, "\n%d. pathset %x", i, pathsets[i]) } + want := ` 0. pathset [0099] 1. pathset [0123456789012345678901234567890101234567890123456789012345678901 00 0095 0096 0097 0099 10 11 19] diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 105fbb3cd4..c9bf9fe571 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -519,8 +519,10 @@ func (s *Syncer) Register(peer SyncPeer) error { log.Error("Snap peer already registered", "id", id) s.lock.Unlock() + return errors.New("already registered") } + s.peers[id] = peer s.rates.Track(id, msgrate.NewTracker(s.rates.MeanCapacities(), s.rates.MedianRoundTrip())) @@ -534,6 +536,7 @@ func (s *Syncer) Register(peer SyncPeer) error { // Notify any active syncs that a new peer can be assigned data s.peerJoin.Send(id) + return nil } @@ -545,8 +548,10 @@ func (s *Syncer) Unregister(id string) error { log.Error("Snap peer not registered", "id", id) s.lock.Unlock() + return errors.New("not registered") } + delete(s.peers, id) s.rates.Untrack(id) @@ -562,6 +567,7 @@ func (s *Syncer) Unregister(id string) error { // Notify any active syncs that pending requests need to be reverted s.peerDrop.Send(id) + return nil } @@ -587,14 +593,17 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { } // 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() }() @@ -625,10 +634,12 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { }() // 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() @@ -647,10 +658,12 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { trienodeHealResps = make(chan *trienodeHealResponse) bytecodeHealResps = make(chan *bytecodeHealResponse) ) + 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 } @@ -729,6 +742,7 @@ func (s *Syncer) loadSyncStatus() { for _, task := range progress.Tasks { log.Debug("Scheduled account sync task", "from", task.Next, "last", task.Last) } + s.tasks = progress.Tasks for _, task := range s.tasks { task.genBatch = ethdb.HookedBatch{ @@ -740,6 +754,7 @@ func (s *Syncer) loadSyncStatus() { task.genTrie = trie.NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, val []byte) { rawdb.WriteTrieNode(task.genBatch, owner, path, hash, val, s.scheme) }) + for accountHash, subtasks := range task.SubTasks { for _, subtask := range subtasks { subtask.genBatch = ethdb.HookedBatch{ @@ -754,6 +769,7 @@ func (s *Syncer) loadSyncStatus() { } } } + s.lock.Lock() defer s.lock.Unlock() @@ -770,6 +786,7 @@ func (s *Syncer) loadSyncStatus() { s.trienodeHealBytes = progress.TrienodeHealBytes s.bytecodeHealSynced = progress.BytecodeHealSynced s.bytecodeHealBytes = progress.BytecodeHealBytes + return } } @@ -784,6 +801,7 @@ func (s *Syncer) loadSyncStatus() { 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), @@ -796,6 +814,7 @@ func (s *Syncer) loadSyncStatus() { // Make sure we don't overflow if the step is not a proper divisor last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } + batch := ethdb.HookedBatch{ Batch: s.db.NewBatch(), OnPut: func(key []byte, value []byte) { @@ -811,6 +830,7 @@ func (s *Syncer) loadSyncStatus() { rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme) }), }) + log.Debug("Created account sync task", "from", next, "last", last) next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) } @@ -823,6 +843,7 @@ func (s *Syncer) saveSyncStatus() { if err := task.genBatch.Write(); err != nil { log.Error("Failed to persist account slots", "err", err) } + for _, subtasks := range task.SubTasks { for _, subtask := range subtasks { if err := subtask.genBatch.Write(); err != nil { @@ -845,10 +866,12 @@ func (s *Syncer) saveSyncStatus() { 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) } @@ -856,11 +879,13 @@ func (s *Syncer) saveSyncStatus() { func (s *Syncer) Progress() (*SyncProgress, *SyncPending) { s.lock.Lock() defer s.lock.Unlock() + pending := new(SyncPending) if s.healer != nil { pending.TrienodeHeal = uint64(len(s.healer.trieTasks)) pending.BytecodeHeal = uint64(len(s.healer.codeTasks)) } + return s.extProgress, pending } @@ -901,6 +926,7 @@ func (s *Syncer) cleanStorageTasks() { j-- } } + if len(subtasks) > 0 { task.SubTasks[account] = subtasks continue @@ -911,7 +937,9 @@ func (s *Syncer) cleanStorageTasks() { task.needState[j] = false } } + delete(task.SubTasks, account) + task.pend-- // If this was the last pending task, forward the account task @@ -934,16 +962,20 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac caps: make([]int, 0, len(s.accountIdlers)), } targetTTL := s.rates.TargetTimeout() + for id := range s.accountIdlers { if _, ok := s.statelessPeers[id]; ok { continue } + idlers.ids = append(idlers.ids, id) idlers.caps = append(idlers.caps, s.rates.Capacity(id, AccountRangeMsg, targetTTL)) } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) // Iterate over all the tasks and try to find a pending one @@ -958,23 +990,28 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac if len(idlers.ids) == 0 { return } + var ( idle = idlers.ids[0] peer = s.peers[idle] cap = idlers.caps[0] ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // 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 @@ -999,6 +1036,7 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac delete(s.accountIdlers, idle) s.pend.Add(1) + go func(root common.Hash) { defer s.pend.Done() @@ -1006,9 +1044,11 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac if cap > maxRequestSize { cap = maxRequestSize } + if cap < minRequestSize { // Don't bother with peers below a bare minimum performance cap = minRequestSize } + if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, uint64(cap)); err != nil { peer.Log().Debug("Failed to request account range", "err", err) s.scheduleRevertAccountRequest(req) @@ -1031,16 +1071,20 @@ func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan * caps: make([]int, 0, len(s.bytecodeIdlers)), } targetTTL := s.rates.TargetTimeout() + for id := range s.bytecodeIdlers { if _, ok := s.statelessPeers[id]; ok { continue } + idlers.ids = append(idlers.ids, id) idlers.caps = append(idlers.caps, s.rates.Capacity(id, ByteCodesMsg, targetTTL)) } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) // Iterate over all the tasks and try to find a pending one @@ -1059,37 +1103,46 @@ func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan * if len(idlers.ids) == 0 { return } + var ( idle = idlers.ids[0] peer = s.peers[idle] cap = idlers.caps[0] ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // 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 if cap > maxCodeRequestCount { cap = maxCodeRequestCount } + hashes := make([]common.Hash, 0, cap) + for hash := range task.codeTasks { delete(task.codeTasks, hash) + hashes = append(hashes, hash) if len(hashes) >= cap { break } } + req := &bytecodeRequest{ peer: idle, id: reqid, @@ -1110,6 +1163,7 @@ func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan * delete(s.bytecodeIdlers, idle) s.pend.Add(1) + go func() { defer s.pend.Done() @@ -1134,16 +1188,20 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st caps: make([]int, 0, len(s.storageIdlers)), } targetTTL := s.rates.TargetTimeout() + for id := range s.storageIdlers { if _, ok := s.statelessPeers[id]; ok { continue } + idlers.ids = append(idlers.ids, id) idlers.caps = append(idlers.caps, s.rates.Capacity(id, StorageRangesMsg, targetTTL)) } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) // Iterate over all the tasks and try to find a pending one @@ -1162,23 +1220,28 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st if len(idlers.ids) == 0 { return } + var ( idle = idlers.ids[0] peer = s.peers[idle] cap = idlers.caps[0] ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // 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 @@ -1187,9 +1250,11 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st if cap > maxRequestSize { cap = maxRequestSize } + if cap < minRequestSize { // Don't bother with peers below a bare minimum performance cap = minRequestSize } + storageSets := cap / 1024 var ( @@ -1197,6 +1262,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st roots = make([]common.Hash, 0, storageSets) subtask *storageTask ) + for account, subtasks := range task.SubTasks { for _, st := range subtasks { // Skip any subtasks already filling @@ -1207,12 +1273,15 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st 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 account, root := range task.stateTasks { @@ -1231,6 +1300,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st if len(accounts) == 0 { continue } + req := &storageRequest{ peer: idle, id: reqid, @@ -1248,6 +1318,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st req.origin = subtask.Next req.limit = subtask.Last } + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { peer.Log().Debug("Storage request timed out", "reqid", reqid) s.rates.Update(idle, StorageRangesMsg, 0, 0) @@ -1257,6 +1328,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st delete(s.storageIdlers, idle) s.pend.Add(1) + go func(root common.Hash) { defer s.pend.Done() @@ -1265,6 +1337,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st if subtask != nil { origin, limit = req.origin[:], req.limit[:] } + if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, uint64(cap)); err != nil { log.Debug("Failed to request storage", "err", err) s.scheduleRevertStorageRequest(req) @@ -1290,16 +1363,20 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai caps: make([]int, 0, len(s.trienodeHealIdlers)), } targetTTL := s.rates.TargetTimeout() + for id := range s.trienodeHealIdlers { if _, ok := s.statelessPeers[id]; ok { continue } + idlers.ids = append(idlers.ids, id) idlers.caps = append(idlers.caps, s.rates.Capacity(id, TrieNodesMsg, targetTTL)) } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) // Iterate over pending tasks and try to find a peer to retrieve with @@ -1311,11 +1388,13 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai have = len(s.healer.trieTasks) + len(s.healer.codeTasks) want = maxTrieRequestCount + maxCodeRequestCount ) + if have < want { paths, hashes, codes := s.healer.scheduler.Missing(want - have) for i, path := range paths { s.healer.trieTasks[path] = hashes[i] } + for _, hash := range codes { s.healer.codeTasks[hash] = struct{}{} } @@ -1330,43 +1409,52 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai if len(idlers.ids) == 0 { return } + var ( idle = idlers.ids[0] peer = s.peers[idle] cap = idlers.caps[0] ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // 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 if cap > maxTrieRequestCount { cap = maxTrieRequestCount } + cap = int(float64(cap) / s.trienodeHealThrottle) if cap <= 0 { cap = 1 } + var ( hashes = make([]common.Hash, 0, cap) paths = make([]string, 0, cap) pathsets = make([]TrieNodePathSet, 0, cap) ) + for path, hash := range s.healer.trieTasks { delete(s.healer.trieTasks, path) paths = append(paths, path) hashes = append(hashes, hash) + if len(paths) >= cap { break } @@ -1394,6 +1482,7 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai delete(s.trienodeHealIdlers, idle) s.pend.Add(1) + go func(root common.Hash) { defer s.pend.Done() @@ -1418,16 +1507,20 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai caps: make([]int, 0, len(s.bytecodeHealIdlers)), } targetTTL := s.rates.TargetTimeout() + for id := range s.bytecodeHealIdlers { if _, ok := s.statelessPeers[id]; ok { continue } + idlers.ids = append(idlers.ids, id) idlers.caps = append(idlers.caps, s.rates.Capacity(id, ByteCodesMsg, targetTTL)) } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) // Iterate over pending tasks and try to find a peer to retrieve with @@ -1439,11 +1532,13 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai have = len(s.healer.trieTasks) + len(s.healer.codeTasks) want = maxTrieRequestCount + maxCodeRequestCount ) + if have < want { paths, hashes, codes := s.healer.scheduler.Missing(want - have) for i, path := range paths { s.healer.trieTasks[path] = hashes[i] } + for _, hash := range codes { s.healer.codeTasks[hash] = struct{}{} } @@ -1458,30 +1553,37 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai if len(idlers.ids) == 0 { return } + var ( idle = idlers.ids[0] peer = s.peers[idle] cap = idlers.caps[0] ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // 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 if cap > maxCodeRequestCount { cap = maxCodeRequestCount } + hashes := make([]common.Hash, 0, cap) + for hash := range s.healer.codeTasks { delete(s.healer.codeTasks, hash) @@ -1490,6 +1592,7 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai break } } + req := &bytecodeHealRequest{ peer: idle, id: reqid, @@ -1510,6 +1613,7 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai delete(s.bytecodeHealIdlers, idle) s.pend.Add(1) + go func() { defer s.pend.Done() @@ -1527,30 +1631,35 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai 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 { @@ -1563,15 +1672,19 @@ func (s *Syncer) revertRequests(peer string) { 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) } @@ -1613,6 +1726,7 @@ func (s *Syncer) revertAccountRequest(req *accountRequest) { // If there's a timeout timer still running, abort it and mark the account // task as not-pending, ready for rescheduling req.timeout.Stop() + if req.task.req == req { req.task.req = nil } @@ -1654,6 +1768,7 @@ func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) { // If there's a timeout timer still running, abort it and mark the code // retrievals as not-pending, ready for rescheduling req.timeout.Stop() + for _, hash := range req.hashes { req.task.codeTasks[hash] = struct{}{} } @@ -1695,6 +1810,7 @@ func (s *Syncer) revertStorageRequest(req *storageRequest) { // If there's a timeout timer still running, abort it and mark the storage // task as not-pending, ready for rescheduling req.timeout.Stop() + if req.subTask != nil { req.subTask.req = nil } else { @@ -1740,6 +1856,7 @@ func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { // If there's a timeout timer still running, abort it and mark the trie node // retrievals as not-pending, ready for rescheduling req.timeout.Stop() + for i, path := range req.paths { req.task.trieTasks[path] = req.hashes[i] } @@ -1781,6 +1898,7 @@ func (s *Syncer) revertBytecodeHealRequest(req *bytecodeHealRequest) { // If there's a timeout timer still running, abort it and mark the code // retrievals as not-pending, ready for rescheduling req.timeout.Stop() + for _, hash := range req.hashes { req.task.codeTasks[hash] = struct{}{} } @@ -1803,11 +1921,13 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { res.cont = false continue } + if cmp > 0 { // Chunk overflown, cut off excess res.hashes = res.hashes[:i] res.accounts = res.accounts[:i] res.cont = false // Mark range completed + break } } @@ -1841,14 +1961,17 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { // previous root hash. if subtasks, ok := res.task.SubTasks[res.hashes[i]]; ok { log.Debug("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++ } @@ -1881,6 +2004,7 @@ func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) { var ( codes uint64 ) + for i, hash := range res.hashes { code := res.codes[i] @@ -1898,12 +2022,16 @@ func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) { } // Push the bytecode into a database batch codes++ + rawdb.WriteCode(batch, hash, code) } + bytes := common.StorageSize(batch.ValueSize()) + if err := batch.Write(); err != nil { log.Crit("Failed to persist bytecodes", "err", err) } + s.bytecodeSynced += codes s.bytecodeBytes += bytes @@ -1926,12 +2054,14 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { if res.subTask != nil { res.subTask.req = nil } + batch := ethdb.HookedBatch{ Batch: s.db.NewBatch(), OnPut: func(key []byte, value []byte) { s.storageBytes += common.StorageSize(len(key) + len(value)) }, } + var ( slots int oldStorageBytes = s.storageBytes @@ -1950,6 +2080,7 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { if account != hash { continue } + acc := res.mainTask.res.accounts[j] // If the packet contains multiple contract storage slots, all @@ -1975,6 +2106,7 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { chunks = uint64(storageConcurrency) lastKey common.Hash ) + if len(keys) > 0 { lastKey = keys[len(keys)-1] } @@ -1988,10 +2120,12 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { if n := estimate / (2 * (maxRequestSize / 64)); n+1 < chunks { chunks = n + 1 } + log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "remaining", estimate, "chunks", chunks) } else { log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "chunks", chunks) } + r := newHashRange(lastKey, chunks) // Our first task is the one that was just filled by this response. @@ -2010,6 +2144,7 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme) }, account), }) + for r.Next() { batch := ethdb.HookedBatch{ Batch: s.db.NewBatch(), @@ -2027,9 +2162,11 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { }, account), }) } + for _, task := range tasks { log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", task.Next, "last", task.Last) } + res.mainTask.SubTasks[account] = tasks // Since we've just created the sub-tasks, this response @@ -2048,6 +2185,7 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { if cmp >= 0 { res.cont = false } + return cmp > 0 }) if index >= 0 { @@ -2104,10 +2242,12 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { } } } + if res.subTask.genBatch.ValueSize() > ethdb.IdealBatchSize || res.subTask.done { if err := res.subTask.genBatch.Write(); err != nil { log.Error("Failed to persist stack slots", "err", err) } + res.subTask.genBatch.Reset() } } @@ -2115,6 +2255,7 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { if err := batch.Write(); err != nil { log.Crit("Failed to persist storage slots", "err", err) } + s.storageSynced += uint64(slots) log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "bytes", s.storageBytes-oldStorageBytes) @@ -2136,6 +2277,7 @@ func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) { start = time.Now() fills int ) + for i, hash := range res.hashes { node := res.nodes[i] @@ -2144,6 +2286,7 @@ func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) { res.task.trieTasks[res.paths[i]] = res.hashes[i] continue } + fills++ // Push the trie node into the state syncer @@ -2161,6 +2304,7 @@ func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) { log.Error("Invalid trienode processed", "hash", hash, "err", err) } } + s.commitHealer(false) // Calculate the processing rate of one filled trie node @@ -2197,11 +2341,13 @@ func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) { } else { s.trienodeHealThrottle /= trienodeHealThrottleDecrease } + if s.trienodeHealThrottle > maxTrienodeHealThrottle { s.trienodeHealThrottle = maxTrienodeHealThrottle } else if s.trienodeHealThrottle < minTrienodeHealThrottle { s.trienodeHealThrottle = minTrienodeHealThrottle } + s.trienodeHealThrottled = time.Now() log.Debug("Updated trie node heal throttler", "rate", s.trienodeHealRate, "pending", pending, "throttle", s.trienodeHealThrottle) @@ -2212,13 +2358,16 @@ func (s *Syncer) commitHealer(force bool) { if !force && s.healer.scheduler.MemSize() < ethdb.IdealBatchSize { return } + 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", "type", "trienodes", "bytes", common.StorageSize(batch.ValueSize())) } @@ -2248,6 +2397,7 @@ func (s *Syncer) processBytecodeHealResponse(res *bytecodeHealResponse) { log.Error("Invalid bytecode processed", "hash", hash, "err", err) } } + s.commitHealer(false) } @@ -2260,6 +2410,7 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { if res == nil { return // nothing to forward } + task.res = nil // Persist the received account segments. These flat state maybe @@ -2273,10 +2424,12 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { s.accountBytes += common.StorageSize(len(key) + len(value)) }, } + for i, hash := range res.hashes { if task.needCode[i] || task.needState[i] { break } + slim := snapshot.SlimAccountRLP(res.accounts[i].Nonce, res.accounts[i].Balance, res.accounts[i].Root, res.accounts[i].CodeHash) rawdb.WriteAccountSnapshot(batch, hash, slim) @@ -2287,6 +2440,7 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { if err != nil { panic(err) // Really shouldn't ever happen } + task.genTrie.Update(hash[:], full) } } @@ -2294,6 +2448,7 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { if err := batch.Write(); err != nil { log.Crit("Failed to persist accounts", "err", err) } + s.accountSynced += uint64(len(res.accounts)) // Task filling persisted, push it the chunk marker forward to the first @@ -2302,6 +2457,7 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { if task.needCode[i] || task.needState[i] { return } + task.Next = incHash(hash) } // All accounts marked as complete, track if the entire task is done @@ -2315,12 +2471,15 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { log.Error("Failed to commit stack account", "err", err) } } + if task.genBatch.ValueSize() > ethdb.IdealBatchSize || task.done { if err := task.genBatch.Write(); err != nil { log.Error("Failed to persist stack account", "err", err) } + task.genBatch.Reset() } + log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "bytes", s.accountBytes-oldAccountBytes) } @@ -2331,9 +2490,11 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco for _, account := range accounts { size += common.StorageSize(len(account)) } + for _, node := range proof { size += common.StorageSize(len(node)) } + logger := peer.Log().New("reqid", id) logger.Trace("Delivering range of accounts", "hashes", len(hashes), "accounts", len(accounts), "proofs", len(proof), "bytes", size) @@ -2343,6 +2504,7 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco defer func() { s.lock.Lock() defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { s.accountIdlers[peer.ID()] = struct{}{} } @@ -2358,8 +2520,10 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco // 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) s.rates.Update(peer.ID(), AccountRangeMsg, time.Since(req.time), int(size)) @@ -2381,8 +2545,10 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco // Signal this request as failed, and ready for rescheduling s.scheduleRevertAccountRequest(req) + return nil } + root := s.root s.lock.Unlock() @@ -2391,31 +2557,39 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco 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] } + 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 } + accs := make([]*types.StateAccount, len(accounts)) + for i, account := range accounts { acc := new(types.StateAccount) 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, @@ -2427,6 +2601,7 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco case <-req.cancel: case <-req.stale: } + return nil } @@ -2440,6 +2615,7 @@ func (s *Syncer) OnByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error if syncing { return s.onByteCodes(peer, id, bytecodes) } + return s.onHealByteCodes(peer, id, bytecodes) } @@ -2450,6 +2626,7 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error for _, code := range bytecodes { size += common.StorageSize(len(code)) } + logger := peer.Log().New("reqid", id) logger.Trace("Delivering set of bytecodes", "bytecodes", len(bytecodes), "bytes", size) @@ -2459,6 +2636,7 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error defer func() { s.lock.Lock() defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { s.bytecodeIdlers[peer.ID()] = struct{}{} } @@ -2474,8 +2652,10 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error // 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) s.rates.Update(peer.ID(), ByteCodesMsg, time.Since(req.time), len(bytecodes)) @@ -2492,11 +2672,13 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error // yet synced. if len(bytecodes) == 0 { 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() @@ -2507,6 +2689,7 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error 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() @@ -2516,15 +2699,18 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error 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) // 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 @@ -2538,6 +2724,7 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error case <-req.cancel: case <-req.stale: } + return nil } @@ -2550,19 +2737,24 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo 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.Log().New("reqid", id) logger.Trace("Delivering ranges of storage slots", "accounts", len(hashes), "hashes", hashCount, "slots", slotCount, "proofs", len(proof), "size", size) @@ -2572,6 +2764,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo defer func() { s.lock.Lock() defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { s.storageIdlers[peer.ID()] = struct{}{} } @@ -2587,8 +2780,10 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo // 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) s.rates.Update(peer.ID(), StorageRangesMsg, time.Since(req.time), int(size)) @@ -2606,12 +2801,15 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo 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") } // Response is valid, but check if peer is signalling that it does not have @@ -2620,9 +2818,11 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo // synced to our head. if len(hashes) == 0 { logger.Debug("Peer rejected storage request") + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() s.scheduleRevertStorageRequest(req) // reschedule request + return nil } s.lock.Unlock() @@ -2636,12 +2836,15 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo 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 @@ -2650,6 +2853,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo if err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage slots failed proof", "err", err) + return err } } else { @@ -2661,10 +2865,12 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo if len(keys) > 0 { end = keys[len(keys)-1] } + 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 } } @@ -2684,6 +2890,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo case <-req.cancel: case <-req.stale: } + return nil } @@ -2694,6 +2901,7 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error for _, node := range trienodes { size += common.StorageSize(len(node)) } + logger := peer.Log().New("reqid", id) logger.Trace("Delivering set of healing trienodes", "trienodes", len(trienodes), "bytes", size) @@ -2703,6 +2911,7 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error defer func() { s.lock.Lock() defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { s.trienodeHealIdlers[peer.ID()] = struct{}{} } @@ -2718,8 +2927,10 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error // 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) s.rates.Update(peer.ID(), TrieNodesMsg, time.Since(req.time), len(trienodes)) @@ -2736,11 +2947,13 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error // yet synced. if len(trienodes) == 0 { 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() @@ -2753,6 +2966,7 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error nodes = make([][]byte, len(req.hashes)) fills uint64 ) + for i, j := 0, 0; i < len(trienodes); i++ { // Find the next hash that we've been served, leaving misses with nils hasher.Reset() @@ -2762,10 +2976,12 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { j++ } + if j < len(req.hashes) { nodes[j] = trienodes[i] fills++ j++ + continue } // We've either ran out of hashes, or got unrequested data @@ -2773,13 +2989,16 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error // 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 atomic.AddUint64(&s.trienodeHealPend, fills) + defer func() { atomic.AddUint64(&s.trienodeHealPend, ^(fills - 1)) }() + response := &trienodeHealResponse{ paths: req.paths, task: req.task, @@ -2791,6 +3010,7 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error case <-req.cancel: case <-req.stale: } + return nil } @@ -2801,6 +3021,7 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e for _, code := range bytecodes { size += common.StorageSize(len(code)) } + logger := peer.Log().New("reqid", id) logger.Trace("Delivering set of healing bytecodes", "bytecodes", len(bytecodes), "bytes", size) @@ -2810,6 +3031,7 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e defer func() { s.lock.Lock() defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { s.bytecodeHealIdlers[peer.ID()] = struct{}{} } @@ -2825,8 +3047,10 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e // 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) s.rates.Update(peer.ID(), ByteCodesMsg, time.Since(req.time), len(bytecodes)) @@ -2843,11 +3067,13 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e // yet synced. if len(bytecodes) == 0 { 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() @@ -2858,6 +3084,7 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e 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() @@ -2867,15 +3094,18 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e 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) // 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 @@ -2889,6 +3119,7 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e case <-req.cancel: case <-req.stale: } + return nil } @@ -2903,20 +3134,24 @@ func (s *Syncer) onHealState(paths [][]byte, value []byte) error { //nolint:nilerr return nil // Returning the error here would drop the remote peer } + blob := snapshot.SlimAccountRLP(account.Nonce, account.Balance, account.Root, account.CodeHash) rawdb.WriteAccountSnapshot(s.stateWriter, common.BytesToHash(paths[0]), blob) s.accountHealed += 1 s.accountHealedBytes += common.StorageSize(1 + common.HashLength + len(blob)) } + if len(paths) == 2 { rawdb.WriteStorageSnapshot(s.stateWriter, common.BytesToHash(paths[0]), common.BytesToHash(paths[1]), value) s.storageHealed += 1 s.storageHealedBytes += common.StorageSize(1 + 2*common.HashLength + len(value)) } + if s.stateWriter.ValueSize() > ethdb.IdealBatchSize { s.stateWriter.Write() // It's fine to ignore the error here s.stateWriter.Reset() } + return nil } @@ -2929,6 +3164,7 @@ func (s *Syncer) report(force bool) { s.reportSyncProgress(force) return } + s.reportHealProgress(force) } @@ -2943,14 +3179,17 @@ func (s *Syncer) reportSyncProgress(force bool) { 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), @@ -2960,6 +3199,7 @@ func (s *Syncer) reportSyncProgress(force bool) { if estBytes < 1.0 { return } + elapsed := time.Since(s.startTime) estTime := elapsed / time.Duration(synced) * time.Duration(estBytes) @@ -2970,6 +3210,7 @@ func (s *Syncer) reportSyncProgress(force bool) { storage = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageSynced), s.storageBytes.TerminalString()) bytecode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.bytecodeSynced), s.bytecodeBytes.TerminalString()) ) + log.Info("Syncing: state download in progress", "synced", progress, "state", synced, "accounts", accounts, "slots", storage, "codes", bytecode, "eta", common.PrettyDuration(estTime-elapsed)) } @@ -2980,6 +3221,7 @@ func (s *Syncer) reportHealProgress(force bool) { if !force && time.Since(s.logTime) < 8*time.Second { return } + s.logTime = time.Now() // Create a mega progress report @@ -2989,6 +3231,7 @@ func (s *Syncer) reportHealProgress(force bool) { accounts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.accountHealed), s.accountHealedBytes.TerminalString()) storage = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageHealed), s.storageHealedBytes.TerminalString()) ) + log.Info("Syncing: state healing in progress", "accounts", accounts, "slots", storage, "codes", bytecode, "nodes", trienode, "pending", s.healer.scheduler.Pending()) } @@ -3000,12 +3243,15 @@ func estimateRemainingSlots(hashes int, last common.Hash) (uint64, error) { if last == (common.Hash{}) { return 0, errors.New("last hash empty") } + space := new(big.Int).Mul(math.MaxBig256, big.NewInt(int64(hashes))) space.Div(space, last.Big()) + if !space.IsUint64() { // Gigantic address space probably due to too few or malicious slots return 0, errors.New("too few slots for estimation") } + return space.Uint64() - uint64(hashes), nil } @@ -3045,6 +3291,7 @@ func (t *healRequestSort) Len() int { func (t *healRequestSort) Less(i, j int) bool { a := t.syncPaths[i] b := t.syncPaths[j] + switch bytes.Compare(a[0], b[0]) { case -1: return true @@ -3055,12 +3302,15 @@ func (t *healRequestSort) Less(i, j int) bool { if len(a) < len(b) { return true } + if len(b) < len(a) { return false } + if len(a) == 2 { return bytes.Compare(a[1], b[1]) < 0 } + return false } @@ -3075,6 +3325,7 @@ func (t *healRequestSort) Swap(i, j int) { // OBS: This operation is moot if t has not first been sorted. func (t *healRequestSort) Merge() []TrieNodePathSet { var result []TrieNodePathSet + for _, path := range t.syncPaths { pathset := TrieNodePathSet(path) if len(path) == 1 { @@ -3093,6 +3344,7 @@ func (t *healRequestSort) Merge() []TrieNodePathSet { } } } + return result } @@ -3103,8 +3355,10 @@ func sortByAccountPath(paths []string, hashes []common.Hash) ([]string, []common for _, path := range paths { syncPaths = append(syncPaths, trie.NewSyncPath([]byte(path))) } + n := &healRequestSort{paths, hashes, syncPaths} sort.Sort(n) pathsets := n.Merge() + return n.paths, n.hashes, n.syncPaths, pathsets } diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 22902c764b..3d0701f4b9 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -48,7 +48,9 @@ func TestHashing(t *testing.T) { rand.Read(buf) bytecodes[i] = buf } + var want, got string + var old = func() { hasher := sha3.NewLegacyKeccak256() for i := 0; i < len(bytecodes); i++ { @@ -58,6 +60,7 @@ func TestHashing(t *testing.T) { got = fmt.Sprintf("%v\n%v", got, hash) } } + var new = func() { hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) var hash = make([]byte, 32) @@ -68,8 +71,10 @@ func TestHashing(t *testing.T) { want = fmt.Sprintf("%v\n%v", want, hash) } } + old() new() + if want != got { t.Errorf("want\n%v\ngot\n%v\n", want, got) } @@ -82,6 +87,7 @@ func BenchmarkHashing(b *testing.B) { rand.Read(buf) bytecodes[i] = buf } + var old = func() { hasher := sha3.NewLegacyKeccak256() for i := 0; i < len(bytecodes); i++ { @@ -90,6 +96,7 @@ func BenchmarkHashing(b *testing.B) { hasher.Sum(nil) } } + var new = func() { hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) var hash = make([]byte, 32) @@ -99,14 +106,17 @@ func BenchmarkHashing(b *testing.B) { 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() } @@ -179,15 +189,19 @@ Trienode requests: %d 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)) + t.nAccountRequests++ go t.accountRequestHandler(t, id, root, origin, limit, 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)) + t.nTrienodeRequests++ go t.trieRequestHandler(t, id, root, paths, bytes) + return nil } @@ -198,14 +212,18 @@ func (t *testPeer) RequestStorageRanges(id uint64, root common.Hash, accounts [] } 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.nBytecodeRequests++ 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 } @@ -213,6 +231,7 @@ func (t *testPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint6 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: @@ -221,6 +240,7 @@ func defaultTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, t.logger.Info("Error handling req", "error", err) break } + nodes = append(nodes, blob) default: account := t.storageTries[(common.BytesToHash(pathset[0]))] @@ -230,11 +250,14 @@ func defaultTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, t.logger.Info("Error handling req", "error", err) break } + nodes = append(nodes, blob) } } } + t.remote.OnTrieNodes(t, requestId, nodes) + return nil } @@ -244,20 +267,25 @@ func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, orig if err := t.remote.OnAccounts(t, id, keys, vals, proofs); err != nil { t.test.Errorf("Remote side rejected our delivery: %v", err) t.term() + return err } + return nil } func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) { var size uint64 + if limit == (common.Hash{}) { limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } + 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) @@ -275,15 +303,18 @@ func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.H 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 } @@ -294,6 +325,7 @@ func defaultStorageRequestHandler(t *testPeer, requestId uint64, root common.Has t.test.Errorf("Remote side rejected our delivery: %v", err) t.term() } + return nil } @@ -302,45 +334,55 @@ func defaultCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max for _, h := range hashes { bytecodes = append(bytecodes, getCodeByHash(h)) } + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { t.test.Errorf("Remote side rejected our delivery: %v", err) t.term() } + return nil } func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { var size uint64 + for _, account := range accounts { // The first account might start from a different origin and end sooner var originHash common.Hash if len(origin) > 0 { originHash = common.BytesToHash(origin) } + var limitHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") if len(limit) > 0 { limitHash = common.BytesToHash(limit) } + var ( keys []common.Hash vals [][]byte abort bool ) + for _, entry := range t.storageValues[account] { if size >= max { abort = true break } + if bytes.Compare(entry.k, originHash[:]) < 0 { continue } + keys = append(keys, common.BytesToHash(entry.k)) vals = append(vals, entry.v) size += uint64(32 + len(entry.v)) + if bytes.Compare(entry.k, limitHash[:]) >= 0 { break } } + if len(keys) > 0 { hashes = append(hashes, keys) slots = append(slots, vals) @@ -360,18 +402,22 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm if err := stTrie.Prove(originHash[:], 0, proof); err != nil { t.logger.Error("Could not prove inexistence of origin", "origin", originHash, "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 } @@ -379,30 +425,39 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm // supplies the proof for the last account, even if it is 'complete'. func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { var size uint64 + max = max * 3 / 4 var origin common.Hash if len(bOrigin) > 0 { origin = common.BytesToHash(bOrigin) } + var exit bool + for i, account := range accounts { var keys []common.Hash + var vals [][]byte + for _, entry := range t.storageValues[account] { if bytes.Compare(entry.k, origin[:]) < 0 { exit = true } + keys = append(keys, common.BytesToHash(entry.k)) vals = append(vals, entry.v) + size += uint64(32 + len(entry.v)) if size > max { exit = true } } + if i == len(accounts)-1 { exit = true } + hashes = append(hashes, keys) slots = append(slots, vals) @@ -419,18 +474,22 @@ func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, acco 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 } @@ -468,6 +527,7 @@ func proofHappyStorageRequestHandler(t *testPeer, requestId uint64, root common. t.test.Errorf("Remote side rejected our delivery: %v", err) t.term() } + return nil } @@ -483,11 +543,13 @@ func corruptCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max // Send back the hashes bytecodes = append(bytecodes, h[:]) } + if err := t.remote.OnByteCodes(t, id, bytecodes); 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 } @@ -501,6 +563,7 @@ func cappedCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max t.test.Errorf("Remote side rejected our delivery: %v", err) t.term() } + return nil } @@ -522,11 +585,13 @@ func corruptAccountRequestHandler(t *testPeer, requestId uint64, root common.Has 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 } @@ -536,11 +601,13 @@ func corruptStorageRequestHandler(t *testPeer, requestId uint64, root common.Has 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 } @@ -551,6 +618,7 @@ func noProofStorageRequestHandler(t *testPeer, requestId uint64, root common.Has // Mimic the real-life handler, which drops a peer on errors t.remote.Unregister(t.id) } + return nil } @@ -569,6 +637,7 @@ func TestSyncBloatedProof(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100) source := newTestPeer("source", t, term) source.accountTrie = sourceAccountTrie.Copy() @@ -585,9 +654,11 @@ func TestSyncBloatedProof(t *testing.T) { if bytes.Compare(entry.k, origin[:]) < 0 { continue } + if bytes.Compare(entry.k, limit[:]) > 0 { continue } + keys = append(keys, common.BytesToHash(entry.k)) vals = append(vals, entry.v) } @@ -607,16 +678,20 @@ func TestSyncBloatedProof(t *testing.T) { 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 (as expected)", "error", err) t.term() // This is actually correct, signal to exit the test successfully } + return nil } + syncer := setupSyncer(nodeScheme, source) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err == nil { t.Fatal("No error returned from incomplete/cancelled sync") @@ -625,11 +700,13 @@ func TestSyncBloatedProof(t *testing.T) { func setupSyncer(scheme string, peers ...*testPeer) *Syncer { stateDb := rawdb.NewMemoryDatabase() + syncer := NewSyncer(stateDb, scheme) for _, peer := range peers { syncer.Register(peer) peer.remote = syncer } + return syncer } @@ -646,18 +723,22 @@ func TestSync(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie.Copy() source.accountValues = elems + return source } + syncer := setupSyncer(nodeScheme, mkSource("source")) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -675,19 +756,23 @@ func TestSyncTinyTriePanic(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(1) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie.Copy() source.accountValues = elems + return source } syncer := setupSyncer(nodeScheme, mkSource("source")) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -705,19 +790,23 @@ func TestMultiSync(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie.Copy() source.accountValues = elems + return source } syncer := setupSyncer(nodeScheme, mkSource("sourceA"), mkSource("sourceB")) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -735,6 +824,7 @@ func TestSyncWithStorage(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true, false) mkSource := func(name string) *testPeer { @@ -743,13 +833,16 @@ func TestSyncWithStorage(t *testing.T) { source.accountValues = elems source.setStorageTries(storageTries) source.storageValues = storageElems + return source } syncer := setupSyncer(nodeScheme, mkSource("sourceA")) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -767,6 +860,7 @@ func TestMultiSyncManyUseless(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { @@ -779,12 +873,15 @@ func TestMultiSyncManyUseless(t *testing.T) { if !noAccount { source.accountRequestHandler = emptyRequestAccountRangeFn } + if !noStorage { source.storageRequestHandler = emptyStorageRequestHandler } + if !noTrieNode { source.trieRequestHandler = emptyTrieRequestHandler } + return source } @@ -796,9 +893,11 @@ func TestMultiSyncManyUseless(t *testing.T) { mkSource("noTrie", true, true, false), ) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -814,6 +913,7 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { @@ -826,12 +926,15 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { if !noAccount { source.accountRequestHandler = emptyRequestAccountRangeFn } + if !noStorage { source.storageRequestHandler = emptyStorageRequestHandler } + if !noTrieNode { source.trieRequestHandler = emptyTrieRequestHandler } + return source } @@ -848,9 +951,11 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { syncer.rates.OverrideTTLLimit = time.Millisecond done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -866,6 +971,7 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { @@ -878,12 +984,15 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { if !noAccount { source.accountRequestHandler = nonResponsiveRequestAccountRangeFn } + if !noStorage { source.storageRequestHandler = nonResponsiveStorageRequestHandler } + if !noTrieNode { source.trieRequestHandler = nonResponsiveTrieRequestHandler } + return source } @@ -898,15 +1007,18 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { syncer.rates.OverrideTTLLimit = time.Millisecond done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } func checkStall(t *testing.T, term func()) chan struct{} { testDone := make(chan struct{}) + go func() { select { case <-time.After(time.Minute): // TODO(karalabe): Make tests smaller, this is too much @@ -916,6 +1028,7 @@ func checkStall(t *testing.T, term func()) chan struct{} { return } }() + return testDone } @@ -933,12 +1046,14 @@ func TestSyncBoundaryAccountTrie(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems := makeBoundaryAccountTrie(3000) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie.Copy() source.accountValues = elems + return source } syncer := setupSyncer( @@ -947,9 +1062,11 @@ func TestSyncBoundaryAccountTrie(t *testing.T) { mkSource("peer-b"), ) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -968,6 +1085,7 @@ func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) mkSource := func(name string, slow bool) *testPeer { @@ -978,6 +1096,7 @@ func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { if slow { source.accountRequestHandler = starvingAccountRequestHandler } + return source } @@ -989,9 +1108,11 @@ func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { mkSource("capped", true), ) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -1010,6 +1131,7 @@ func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { @@ -1017,6 +1139,7 @@ func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { source.accountTrie = sourceAccountTrie.Copy() 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% @@ -1029,9 +1152,11 @@ func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { mkSource("corrupt", corruptCodeRequestHandler), ) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -1048,6 +1173,7 @@ func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) mkSource := func(name string, accFn accountHandlerFunc) *testPeer { @@ -1055,6 +1181,7 @@ func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { source.accountTrie = sourceAccountTrie.Copy() 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% @@ -1067,9 +1194,11 @@ func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { mkSource("corrupt", corruptAccountRequestHandler), ) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -1088,6 +1217,7 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { @@ -1095,11 +1225,13 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { source.accountTrie = sourceAccountTrie.Copy() 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( nodeScheme, mkSource("capped", func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { @@ -1108,9 +1240,11 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { }), ) done := checkStall(t, term) + 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 @@ -1122,6 +1256,7 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { if threshold := 100; counter > threshold { t.Logf("Error, expected < %d invocations, got %d", threshold, counter) } + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -1139,6 +1274,7 @@ func TestSyncBoundaryStorageTrie(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(10, 1000, false, true) mkSource := func(name string) *testPeer { @@ -1147,6 +1283,7 @@ func TestSyncBoundaryStorageTrie(t *testing.T) { source.accountValues = elems source.setStorageTries(storageTries) source.storageValues = storageElems + return source } syncer := setupSyncer( @@ -1155,9 +1292,11 @@ func TestSyncBoundaryStorageTrie(t *testing.T) { mkSource("peer-b"), ) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -1176,6 +1315,7 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false, false) mkSource := func(name string, slow bool) *testPeer { @@ -1188,6 +1328,7 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { if slow { source.storageRequestHandler = starvingStorageRequestHandler } + return source } @@ -1197,9 +1338,11 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { mkSource("slow", true), ) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -1218,6 +1361,7 @@ func TestSyncWithStorageAndCorruptPeer(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) mkSource := func(name string, handler storageHandlerFunc) *testPeer { @@ -1227,6 +1371,7 @@ func TestSyncWithStorageAndCorruptPeer(t *testing.T) { source.setStorageTries(storageTries) source.storageValues = storageElems source.storageRequestHandler = handler + return source } @@ -1238,9 +1383,11 @@ func TestSyncWithStorageAndCorruptPeer(t *testing.T) { mkSource("corrupt", corruptStorageRequestHandler), ) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -1257,6 +1404,7 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) mkSource := func(name string, handler storageHandlerFunc) *testPeer { @@ -1266,6 +1414,7 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { source.setStorageTries(storageTries) source.storageValues = storageElems source.storageRequestHandler = handler + return source } syncer := setupSyncer( @@ -1276,9 +1425,11 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { mkSource("corrupt", noProofStorageRequestHandler), ) done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -1289,6 +1440,7 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { // did not mark the account for healing. func TestSyncWithStorageMisbehavingProve(t *testing.T) { t.Parallel() + var ( once sync.Once cancel = make(chan struct{}) @@ -1298,6 +1450,7 @@ func TestSyncWithStorageMisbehavingProve(t *testing.T) { }) } ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorageWithUniqueStorage(10, 30, false) mkSource := func(name string) *testPeer { @@ -1307,12 +1460,15 @@ func TestSyncWithStorageMisbehavingProve(t *testing.T) { source.setStorageTries(storageTries) source.storageValues = storageElems source.storageRequestHandler = proofHappyStorageRequestHandler + return source } + syncer := setupSyncer(nodeScheme, mkSource("sourceA")) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } @@ -1330,6 +1486,7 @@ 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 } @@ -1357,11 +1514,13 @@ func getCodeByHash(hash common.Hash) []byte { if hash == types.EmptyCodeHash { return nil } + for i, h := range codehashes { if h == hash { return []byte{byte(i)} } } + return nil } @@ -1372,6 +1531,7 @@ func makeAccountTrieNoStorage(n int) (string, *trie.Trie, entrySlice) { accTrie = trie.NewEmpty(db) entries entrySlice ) + for i := uint64(1); i <= uint64(n); i++ { value, _ := rlp.EncodeToBytes(&types.StateAccount{ Nonce: i, @@ -1392,6 +1552,7 @@ func makeAccountTrieNoStorage(n int) (string, *trie.Trie, entrySlice) { db.Update(trie.NewWithNodeSet(nodes)) accTrie, _ = trie.New(trie.StateTrieID(root), db) + return db.Scheme(), accTrie, entries } @@ -1408,6 +1569,7 @@ func makeBoundaryAccountTrie(n int) (string, *trie.Trie, entrySlice) { ) // Initialize boundaries var next common.Hash + step := new(big.Int).Sub( new(big.Int).Div( new(big.Int).Exp(common.Big2, common.Big256, nil), @@ -1419,6 +1581,7 @@ func makeBoundaryAccountTrie(n int) (string, *trie.Trie, entrySlice) { if i == accountConcurrency-1 { last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } + boundaries = append(boundaries, last) next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) } @@ -1454,6 +1617,7 @@ func makeBoundaryAccountTrie(n int) (string, *trie.Trie, entrySlice) { db.Update(trie.NewWithNodeSet(nodes)) accTrie, _ = trie.New(trie.StateTrieID(root), db) + return db.Scheme(), accTrie, entries } @@ -1472,6 +1636,7 @@ func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) // Create n accounts in the trie for i := uint64(1); i <= uint64(accounts); i++ { key := key32(i) + codehash := types.EmptyCodeHash.Bytes() if code { codehash = getCodeHash(i) @@ -1504,6 +1669,7 @@ func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) // Re-create tries with new root accTrie, _ = trie.New(trie.StateTrieID(root), db) + for i := uint64(1); i <= uint64(accounts); i++ { key := key32(i) id := trie.StorageTrieID(root, common.BytesToHash(key), storageRoots[common.BytesToHash(key)]) @@ -1528,6 +1694,7 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (strin // Create n accounts in the trie for i := uint64(1); i <= uint64(accounts); i++ { key := key32(i) + codehash := types.EmptyCodeHash.Bytes() if code { codehash = getCodeHash(i) @@ -1544,6 +1711,7 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (strin } else { stRoot, stNodes, stEntries = makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), 0, db) } + nodes.Merge(stNodes) value, _ := rlp.EncodeToBytes(&types.StateAccount{ @@ -1595,7 +1763,9 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (strin // that tries are unique. func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *trie.Database) (common.Hash, *trie.NodeSet, entrySlice) { trie, _ := trie.New(trie.StorageTrieID(common.Hash{}, owner, common.Hash{}), db) + var entries entrySlice + for i := uint64(1); i <= n; i++ { // store 'x' at slot 'x' slotValue := key32(i + seed) @@ -1626,6 +1796,7 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (commo ) // Initialize boundaries var next common.Hash + step := new(big.Int).Sub( new(big.Int).Div( new(big.Int).Exp(common.Big2, common.Big256, nil), @@ -1637,6 +1808,7 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (commo if i == accountConcurrency-1 { last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } + boundaries = append(boundaries, last) next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) } @@ -1672,11 +1844,14 @@ func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { t.Helper() triedb := trie.NewDatabase(rawdb.NewDatabase(db)) + accTrie, err := trie.New(trie.StateTrieID(root), triedb) if err != nil { t.Fatal(err) } + accounts, slots := 0, 0 + accIt := trie.NewIterator(accTrie.NodeIterator(nil)) for accIt.Next() { var acc struct { @@ -1685,29 +1860,36 @@ func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { Root common.Hash CodeHash []byte } + if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { log.Crit("Invalid account encountered during snapshot creation", "err", err) } + accounts++ if acc.Root != types.EmptyRootHash { id := trie.StorageTrieID(root, common.BytesToHash(accIt.Key), acc.Root) + storeTrie, err := trie.NewStateTrie(id, triedb) if err != nil { t.Fatal(err) } + storeIt := trie.NewIterator(storeTrie.NodeIterator(nil)) for storeIt.Next() { slots++ } + if err := storeIt.Err; err != nil { t.Fatal(err) } } } + if err := accIt.Err; err != nil { t.Fatal(err) } + t.Logf("accounts: %d, slots: %d", accounts, slots) } @@ -1735,13 +1917,16 @@ func TestSyncAccountPerformance(t *testing.T) { source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie.Copy() source.accountValues = elems + return source } src := mkSource("source") + syncer := setupSyncer(nodeScheme, src) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) // The trie root will always be requested, since it is added when the snap // sync cycle starts. When popping the queue, we do not look it up again. diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 99701faaeb..d23e6940ff 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -116,13 +116,16 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe if err := ctx.Err(); err != nil { return nil, nil, err } + if current.NumberU64() == 0 { return nil, nil, errors.New("genesis state is missing") } + parent := eth.blockchain.GetBlock(current.ParentHash(), current.NumberU64()-1) if parent == nil { return nil, nil, fmt.Errorf("missing block %v %d", current.ParentHash(), current.NumberU64()-1) } + current = parent statedb, err = state.New(current.Root(), database, nil) @@ -130,6 +133,7 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe break } } + if err != nil { switch err.(type) { case *trie.MissingNodeError: @@ -146,6 +150,7 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe logged time.Time parent common.Hash ) + for current.NumberU64() < origin { if err := ctx.Err(); err != nil { return nil, nil, err @@ -173,6 +178,7 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe return nil, nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w", current.NumberU64(), current.Root().Hex(), err) } + statedb, err = state.New(root, database, nil) if err != nil { return nil, nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err) @@ -180,11 +186,14 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe // Hold the state reference and also drop the parent state // to prevent accumulating too many nodes in memory. database.TrieDB().Reference(root, common.Hash{}) + if parent != (common.Hash{}) { database.TrieDB().Dereference(parent) } + parent = root } + if report { nodes, imgs := database.TrieDB().Size() log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) @@ -210,6 +219,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, if err != nil { return nil, vm.BlockContext{}, nil, nil, err } + if txIndex == 0 && len(block.Transactions()) == 0 { return nil, vm.BlockContext{}, statedb, release, nil } @@ -219,6 +229,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, // Assemble the transaction call message and return if the requested offset msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) if idx == txIndex { return msg, context, statedb, release, nil diff --git a/eth/sync.go b/eth/sync.go index db73d60dad..67cd758ce8 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -110,6 +110,7 @@ func (cs *chainSyncer) loop() { cs.handler.blockFetcher.Start() cs.handler.txFetcher.Start() + defer cs.handler.blockFetcher.Stop() defer cs.handler.txFetcher.Stop() defer cs.handler.downloader.Terminate() @@ -136,6 +137,7 @@ func (cs *chainSyncer) loop() { // potentially flaky. if errors.Is(err, downloader.ErrMergeTransition) && time.Since(cs.warned) > 10*time.Second { log.Warn("Local chain is post-merge, waiting for beacon client sync switch-over...") + cs.warned = time.Now() } case <-cs.force.C: @@ -147,9 +149,11 @@ func (cs *chainSyncer) loop() { // inserts, and these can take a long time to finish. cs.handler.chain.StopInsert() cs.handler.downloader.Terminate() + if cs.doneCh != nil { <-cs.doneCh } + return } } @@ -178,6 +182,7 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { } else if minPeers > cs.handler.maxPeers { minPeers = cs.handler.maxPeers } + if cs.handler.peers.len() < minPeers { return nil } @@ -188,18 +193,23 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { if peer == nil { return nil } + mode, ourTD := cs.modeAndLocalHead() op := peerToSyncOp(mode, peer) + if op.td.Cmp(ourTD) <= 0 { // We seem to be in sync according to the legacy rules. In the merge // world, it can also mean we're stuck on the merge block, waiting for // a beacon client. In the latter case, notify the user. if ttd := cs.handler.chain.Config().TerminalTotalDifficulty; ttd != nil && ourTD.Cmp(ttd) >= 0 && time.Since(cs.warned) > 10*time.Second { log.Warn("Local chain is post-merge, waiting for beacon client sync switch-over...") + cs.warned = time.Now() } + return nil // We're in sync } + return op } @@ -212,29 +222,31 @@ func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) { // Note: Ideally this should never happen with bor, but to be extra // preventive we won't allow it to roll over to snap sync until // we have it working - // Handle full sync mode only head := cs.handler.chain.CurrentBlock() td := cs.handler.chain.GetTd(head.Hash(), head.Number.Uint64()) + return downloader.FullSync, td - // TODO(snap): Uncomment when we have snap sync working - // If we're in snap sync mode, return that directly - // if atomic.LoadUint32(&cs.handler.snapSync) == 1 { - // block := cs.handler.chain.CurrentFastBlock() - // td := cs.handler.chain.GetTd(block.Hash(), block.NumberU64()) - // return downloader.SnapSync, td - // } + // + // if atomic.LoadUint32(&cs.handler.snapSync) == 1 { + // block := cs.handler.chain.CurrentFastBlock() + // td := cs.handler.chain.GetTd(block.Hash(), block.NumberU64()) + // return downloader.SnapSync, td + // } + // // // We are probably in full sync, but we might have rewound to before the // // snap sync pivot, check if we should reenable - // 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.GetTd(block.Hash(), block.NumberU64()) - // return downloader.SnapSync, td - // } - // } + // + // 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.GetTd(block.Hash(), block.NumberU64()) + // return downloader.SnapSync, td + // } + // } + // // // Nope, we're really full syncing // head := cs.handler.chain.CurrentBlock() // td := cs.handler.chain.GetTd(head.Hash(), head.NumberU64()) @@ -272,6 +284,7 @@ func (h *handler) doSync(op *chainSyncOp) error { if err != nil { return err } + if atomic.LoadUint32(&h.snapSync) == 1 { log.Info("Snap sync complete, auto disabling") atomic.StoreUint32(&h.snapSync, 0) @@ -298,5 +311,6 @@ func (h *handler) doSync(op *chainSyncOp) error { h.BroadcastBlock(block, false) } } + return nil } diff --git a/eth/sync_test.go b/eth/sync_test.go index e0e0e0d512..9660427eee 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -66,6 +66,7 @@ func testSnapSyncDisabling(t *testing.T, ethVer uint, snapVer uint) { emptyPeerEth := eth.NewPeer(ethVer, p2p.NewPeer(enode.ID{1}, "", caps), emptyPipeEth, empty.txpool) fullPeerEth := eth.NewPeer(ethVer, p2p.NewPeer(enode.ID{2}, "", caps), fullPipeEth, full.txpool) + defer emptyPeerEth.Close() defer fullPeerEth.Close() @@ -97,6 +98,7 @@ func testSnapSyncDisabling(t *testing.T, ethVer uint, snapVer uint) { if err := empty.handler.doSync(op); err != nil { t.Fatal("sync failed:", err) } + if atomic.LoadUint32(&empty.handler.snapSync) == 1 { t.Fatalf("snap sync not disabled after successful synchronisation") } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index fe9418246d..45d62ece5b 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -131,13 +131,16 @@ func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.H 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 } @@ -154,9 +157,11 @@ func (api *API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*typ if err != nil { return nil, err } + if block == nil { return nil, fmt.Errorf("block #%d not found", number) } + return block, nil } @@ -167,9 +172,11 @@ func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block if err != nil { return nil, err } + if block == nil { return nil, fmt.Errorf("block %s not found", hash.Hex()) } + return block, nil } @@ -183,9 +190,11 @@ func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber if err != nil { return nil, err } + if block.Hash() == hash { return block, nil } + return api.blockByHash(ctx, hash) } @@ -276,10 +285,12 @@ func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, conf if err != nil { return nil, err } + 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) } @@ -324,11 +335,14 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed if config != nil && config.Reexec != nil { reexec = *config.Reexec } + blocks := int(end.NumberU64() - start.NumberU64()) threads := runtime.NumCPU() + if threads > blocks { threads = blocks } + var ( pend = new(sync.WaitGroup) ctx = context.Background() @@ -336,8 +350,10 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed resCh = make(chan *blockTraceTask, threads) tracker = newStateTracker(maximumPendingTraceStates, start.NumberU64()) ) + for th := 0; th < threads; th++ { pend.Add(1) + go func() { defer pend.Done() @@ -377,6 +393,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed if err != nil { task.results[i] = &txTraceResult{Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) + break } // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect @@ -491,6 +508,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed tracker.releaseState(number, release) return } + traced += uint64(len(txs)) } }() @@ -499,6 +517,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed retCh := make(chan *blockTraceResult) go func() { defer close(retCh) + var ( next = start.NumberU64() + 1 done = make(map[uint64]*blockTraceResult) @@ -522,7 +541,9 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed // expected behavior to not waste node resources for a non-active user. retCh <- result } + delete(done, next) + next++ } } @@ -543,6 +564,7 @@ func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, if err != nil { return nil, err } + return api.traceBlock(ctx, block, config) } @@ -553,6 +575,7 @@ func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config * if err != nil { return nil, err } + return api.traceBlock(ctx, block, config) } @@ -563,6 +586,7 @@ func (api *API) TraceBlock(ctx context.Context, blob hexutil.Bytes, config *Trac if err := rlp.Decode(bytes.NewReader(blob), block); err != nil { return nil, fmt.Errorf("could not decode block: %v", err) } + return api.traceBlock(ctx, block, config) } @@ -573,6 +597,7 @@ func (api *API) TraceBlockFromFile(ctx context.Context, file string, config *Tra if err != nil { return nil, fmt.Errorf("could not read file: %v", err) } + return api.TraceBlock(ctx, blob, config) } @@ -584,6 +609,7 @@ func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *Tra if block == nil { return nil, fmt.Errorf("bad block %#x not found", hash) } + return api.traceBlock(ctx, block, config) } @@ -595,6 +621,7 @@ func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, if err != nil { return nil, err } + return api.standardTraceBlockToFile(ctx, block, config) } @@ -632,16 +659,20 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config // Check in the bad blocks block = rawdb.ReadBadBlock(api.backend.ChainDb(), hash) } + if block == nil { return nil, fmt.Errorf("block %#x not found", hash) } + if block.NumberU64() == 0 { return nil, errors.New("genesis is not traceable") } + 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 @@ -667,6 +698,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config if err := ctx.Err(); err != nil { return nil, err } + var ( msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee()) txContext = core.NewEVMTxContext(msg) @@ -704,13 +736,13 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config // N.B: This should never happen while tracing canon blocks, only when tracing bad blocks. return roots, nil } - } // calling IntermediateRoot will internally call Finalize on the state // so any modifications are written to the trie roots = append(roots, statedb.IntermediateRoot(deleteEmptyObjects)) } + return roots, nil } @@ -722,6 +754,7 @@ func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Has if block == nil { return nil, fmt.Errorf("bad block %#x not found", hash) } + return api.standardTraceBlockToFile(ctx, block, config) } @@ -732,7 +765,6 @@ func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Has // One thread runs along and executes txs without tracing enabled to generate their prestate. // Worker threads take the tasks and the prestate and trace them. func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { - if config == nil { config = &TraceConfig{ BorTraceEnabled: defaultBorTraceEnabled, @@ -752,6 +784,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac if err != nil { return nil, err } + reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec @@ -788,14 +821,17 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac results = make([]*txTraceResult, len(txs)) pend sync.WaitGroup ) + threads := runtime.NumCPU() if threads > len(txs) { threads = len(txs) } jobs := make(chan *txTraceTask, threads) + for th := 0; th < threads; th++ { pend.Add(1) + go func() { defer pend.Done() // Fetch and execute the next transaction trace tasks @@ -822,10 +858,12 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac } else { res, err = api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) } + if err != nil { results[task.index] = &txTraceResult{Error: err.Error()} continue } + results[task.index] = &txTraceResult{Result: res} } }() @@ -949,7 +987,6 @@ txloop: if err != nil { return nil, err } - } close(jobs) @@ -986,13 +1023,16 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash) } } + if block.NumberU64() == 0 { return nil, errors.New("genesis is not traceable") } + 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 @@ -1010,10 +1050,12 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block logConfig logger.Config txHash common.Hash ) + if config != nil { logConfig = config.Config txHash = config.TxHash } + logConfig.Debug = true // Execute transaction, either tracing all or just the requested one @@ -1062,6 +1104,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block if err != nil { return nil, err } + dumps = append(dumps, dump.Name()) // Swap out the noop logger to the standard tracer @@ -1087,14 +1130,17 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block } else { // nolint : contextcheck _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), context.Background()) + if writer != nil { writer.Flush() } } + if dump != nil { dump.Close() log.Info("Wrote standard trace", "file", dump.Name()) } + if err != nil { return dumps, err } @@ -1107,6 +1153,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block break } } + return dumps, nil } @@ -1119,6 +1166,7 @@ func (api *API) containsTx(ctx context.Context, block *types.Block, hash common. return true } } + return false } @@ -1146,6 +1194,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * }, nil } } + if err != nil { return nil, err } @@ -1153,10 +1202,12 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * if blockNumber == 0 { return nil, errors.New("genesis is not traceable") } + reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } + block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash) if err != nil { return nil, err @@ -1175,6 +1226,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * TxIndex: int(index), TxHash: hash, } + return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) } @@ -1187,6 +1239,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc err error block *types.Block ) + if hash, ok := blockNrOrHash.Hash(); ok { block, err = api.blockByHash(ctx, hash) } else if number, ok := blockNrOrHash.Number(); ok { @@ -1198,10 +1251,12 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc // of what the next actual block is likely to contain. return nil, errors.New("tracing on top of pending is not supported") } + block, err = api.blockByNumber(ctx, number) } else { return nil, errors.New("invalid arguments; neither block nor hash specified") } + if err != nil { return nil, err } @@ -1238,6 +1293,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc if config != nil { traceConfig = &config.TraceConfig } + return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 8ec442c3eb..1a8b3c2af3 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -81,14 +81,18 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i i SnapshotLimit: 0, TrieDirtyDisabled: true, // Archive mode } + chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, gspec, nil, backend.engine, vm.Config{}, nil, 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 } @@ -100,6 +104,7 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { return b.chain.CurrentHeader(), nil } + return b.chain.GetHeaderByNumber(uint64(number)), nil } @@ -111,6 +116,7 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { return b.chain.GetBlockByNumber(b.chain.CurrentBlock().Number.Uint64()), nil } + return b.chain.GetBlockByNumber(uint64(number)), nil } @@ -173,6 +179,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block if err != nil { return nil, vm.BlockContext{}, nil, nil, errStateNotFound } + if txIndex == 0 && len(block.Transactions()) == 0 { return nil, vm.BlockContext{}, statedb, release, nil } @@ -181,6 +188,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block for idx, tx := range block.Transactions() { msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) + blockContext := core.NewEVMBlockContext(block.Header(), b.chain, nil) if idx == txIndex { return msg, blockContext, statedb, release, nil @@ -191,6 +199,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), context.Background()); err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } @@ -227,6 +236,7 @@ func TestTraceCall(t *testing.T) { defer backend.teardown() api := NewAPI(backend) + var testSuite = []struct { blockNumber rpc.BlockNumber call ethapi.TransactionArgs @@ -316,6 +326,7 @@ func TestTraceCall(t *testing.T) { t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr) continue } + if !reflect.DeepEqual(err, testspec.expectErr) { t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err) } @@ -324,14 +335,17 @@ func TestTraceCall(t *testing.T) { t.Errorf("test %d: expect no error, got %v", i, err) continue } + var have *logger.ExecutionResult if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil { t.Errorf("test %d: failed to unmarshal result %v", i, err) } + var want *logger.ExecutionResult if err := json.Unmarshal([]byte(testspec.expect), &want); err != nil { t.Errorf("test %d: failed to unmarshal result %v", i, err) } + if !reflect.DeepEqual(have, want) { t.Errorf("test %d: result mismatch, want %v, got %v", i, testspec.expect, string(result.(json.RawMessage))) } @@ -364,6 +378,7 @@ func TestTraceTransaction(t *testing.T) { defer backend.chain.Stop() api := NewAPI(backend) + result, err := api.TraceTransaction(context.Background(), target, nil) if err != nil { t.Errorf("Failed to trace transaction %v", err) @@ -450,6 +465,7 @@ func TestTraceBlock(t *testing.T) { want: `[{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, }, } + for i, tc := range testSuite { result, err := api.TraceBlockByNumber(context.Background(), tc.blockNumber, tc.config) if tc.expectErr != nil { @@ -457,17 +473,22 @@ func TestTraceBlock(t *testing.T) { t.Errorf("test %d, want error %v", i, tc.expectErr) continue } + if !reflect.DeepEqual(err, tc.expectErr) { t.Errorf("test %d: error mismatch, want %v, get %v", i, tc.expectErr, err) } + continue } + if err != nil { t.Errorf("test %d, want no error, have %v", i, err) continue } + have, _ := json.Marshal(result) want := tc.want + if string(have) != want { t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(have), want) } @@ -492,7 +513,6 @@ func TestIOdump(t *testing.T) { // Transfer from account[0] to account[1], account[1] to account[2], account[2] to account[3], account[3] to account[4], account[4] to account[0] // value: 1000 wei // fee: 0 wei - for j := 0; j < 5; j++ { tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[(j+1)%5].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[j].key) b.AddTx(tx) @@ -588,11 +608,13 @@ func TestTracingWithOverrides(t *testing.T) { defer backend.chain.Stop() api := NewAPI(backend) randomAccounts := newAccounts(3) + type res struct { Gas int Failed bool ReturnValue string } + var testSuite = []struct { blockNumber rpc.BlockNumber call ethapi.TransactionArgs @@ -852,6 +874,7 @@ func TestTracingWithOverrides(t *testing.T) { want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000055"}`, }, } + for i, tc := range testSuite { result, err := api.TraceCall(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, tc.config) if tc.expectErr != nil { @@ -859,11 +882,14 @@ func TestTracingWithOverrides(t *testing.T) { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) continue } + if !errors.Is(err, tc.expectErr) { t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) } + continue } + if err != nil { t.Errorf("test %d: want no error, have %v", i, err) continue @@ -873,9 +899,11 @@ func TestTracingWithOverrides(t *testing.T) { have res want res ) + resBytes, _ := json.Marshal(result) json.Unmarshal(resBytes, &have) json.Unmarshal([]byte(tc.want), &want) + if !reflect.DeepEqual(have, want) { t.Logf("result: %v\n", string(resBytes)) t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, have, want) @@ -901,6 +929,7 @@ func newAccounts(n int) (accounts Accounts) { accounts = append(accounts, Account{key: key, addr: addr}) } sort.Sort(accounts) + return accounts } @@ -918,10 +947,12 @@ func newStates(keys []common.Hash, vals []common.Hash) *map[common.Hash]common.H if len(keys) != len(vals) { panic("invalid input") } + m := make(map[common.Hash]common.Hash) for i := 0; i < len(keys); i++ { m[keys[i]] = vals[i] } + return &m } @@ -955,6 +986,7 @@ func TestTraceChain(t *testing.T) { for j := 0; j < i+1; j++ { tx, _ := types.SignTx(types.NewTransaction(nonce, accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) b.AddTx(tx) + nonce += 1 } }) diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index fc127809a8..efd35a37ec 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -96,14 +96,17 @@ func TestCallTracerNativeWithLog(t *testing.T) { func testCallTracer(tracerName string, dirPath string, t *testing.T) { isLegacy := strings.HasSuffix(dirPath, "_legacy") + files, err := os.ReadDir(filepath.Join("testdata", dirPath)) if err != nil { t.Fatalf("failed to retrieve tracer test suite: %v", err) } + for _, file := range files { if !strings.HasSuffix(file.Name(), ".json") { continue } + file := file // capture range variable t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { t.Parallel() @@ -118,6 +121,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { } else if err := json.Unmarshal(blob, test); err != nil { t.Fatalf("failed to parse testcase: %v", err) } + if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil { t.Fatalf("failed to parse testcase input: %v", err) } @@ -141,15 +145,19 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { } _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) ) + tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } + evm := vm.NewEVM(blockContext, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) + msg, err := core.TransactionToMessage(tx, signer, nil) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } + vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()), context.Background()) if err != nil { t.Fatalf("failed to execute transaction: %v", err) @@ -165,13 +173,16 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { // This is a tweak to make it deterministic. Can be removed when // we remove the legacy tracer. var x callTrace + json.Unmarshal(res, &x) res, _ = json.Marshal(x) } + want, err := json.Marshal(test.Result) if err != nil { t.Fatalf("failed to marshal test: %v", err) } + if string(want) != string(res) { t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want)) } @@ -179,10 +190,12 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { type simpleResult struct { GasUsed hexutil.Uint64 } + var topCall simpleResult if err := json.Unmarshal(res, &topCall); err != nil { t.Fatalf("failed to unmarshal top calls gasUsed: %v", err) } + if uint64(topCall.GasUsed) != vmRet.UsedGas { t.Fatalf("top call has invalid gasUsed. have: %d want: %d", topCall.GasUsed, vmRet.UsedGas) } @@ -195,20 +208,24 @@ func BenchmarkTracers(b *testing.B) { if err != nil { b.Fatalf("failed to retrieve tracer test suite: %v", err) } + for _, file := range files { if !strings.HasSuffix(file.Name(), ".json") { continue } + file := file // capture range variable b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name())) if err != nil { b.Fatalf("failed to read testcase: %v", err) } + test := new(callTracerTest) if err := json.Unmarshal(blob, test); err != nil { b.Fatalf("failed to parse testcase: %v", err) } + benchTracer("callTracer", test, b) }) } @@ -220,11 +237,14 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { b.Fatalf("failed to parse testcase input: %v", err) } + signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) + msg, err := core.TransactionToMessage(tx, signer, nil) if err != nil { b.Fatalf("failed to prepare transaction for tracing: %v", err) } + origin, _ := signer.Sender(tx) txContext := vm.TxContext{ Origin: origin, @@ -243,6 +263,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { b.ReportAllocs() b.ResetTimer() + for i := 0; i < b.N; i++ { tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil) if err != nil { @@ -256,9 +277,11 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { if _, err = st.TransitionDb(context.Background()); err != nil { b.Fatalf("failed to execute transaction: %v", err) } + if _, err = tracer.GetResult(); err != nil { b.Fatal(err) } + statedb.RevertToSnapshot(snap) } } diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index 6793dd9588..89c980f78c 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -93,6 +93,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { } else if err := json.Unmarshal(blob, test); err != nil { t.Fatalf("failed to parse testcase: %v", err) } + if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil { t.Fatalf("failed to parse testcase input: %v", err) } @@ -116,15 +117,19 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { } _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) ) + tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } + evm := vm.NewEVM(blockContext, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) + msg, err := core.TransactionToMessage(tx, signer, nil) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) if _, err = st.TransitionDb(context.Background()); err != nil { t.Fatalf("failed to execute transaction: %v", err) @@ -140,13 +145,16 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { // This is a tweak to make it deterministic. Can be removed when // we remove the legacy tracer. var x prestateTrace + json.Unmarshal(res, &x) res, _ = json.Marshal(x) } + want, err := json.Marshal(test.Result) if err != nil { t.Fatalf("failed to marshal test: %v", err) } + if string(want) != string(res) { t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want)) } diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index 461c3ad12e..eb16fcf42f 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -413,6 +413,7 @@ func (t *jsTracer) setBuiltinFunctions() { vm.Interrupt(err) return "" } + return hexutil.Encode(b) }) vm.Set("toWord", func(v goja.Value) goja.Value { @@ -422,12 +423,15 @@ func (t *jsTracer) setBuiltinFunctions() { vm.Interrupt(err) return nil } + b = common.BytesToHash(b).Bytes() + res, err := t.toBuf(vm, b) if err != nil { vm.Interrupt(err) return nil } + return res }) vm.Set("toAddress", func(v goja.Value) goja.Value { @@ -436,12 +440,15 @@ func (t *jsTracer) setBuiltinFunctions() { vm.Interrupt(err) return nil } + a = common.BytesToAddress(a).Bytes() + res, err := t.toBuf(vm, a) if err != nil { vm.Interrupt(err) return nil } + return res }) vm.Set("toContract", func(from goja.Value, nonce uint) goja.Value { @@ -450,13 +457,16 @@ func (t *jsTracer) setBuiltinFunctions() { vm.Interrupt(err) return nil } + addr := common.BytesToAddress(a) b := crypto.CreateAddress(addr, uint64(nonce)).Bytes() + res, err := t.toBuf(vm, b) if err != nil { vm.Interrupt(err) return nil } + return res }) vm.Set("toContract2", func(from goja.Value, salt string, initcode goja.Value) goja.Value { @@ -465,20 +475,25 @@ func (t *jsTracer) setBuiltinFunctions() { vm.Interrupt(err) return nil } + addr := common.BytesToAddress(a) + code, err := t.fromBuf(vm, initcode, true) if err != nil { vm.Interrupt(err) return nil } + code = common.CopyBytes(code) codeHash := crypto.Keccak256(code) b := crypto.CreateAddress2(addr, common.HexToHash(salt), codeHash).Bytes() + res, err := t.toBuf(vm, b) if err != nil { vm.Interrupt(err) return nil } + return res }) vm.Set("isPrecompiled", func(v goja.Value) bool { @@ -487,12 +502,14 @@ func (t *jsTracer) setBuiltinFunctions() { vm.Interrupt(err) return false } + addr := common.BytesToAddress(a) for _, p := range t.activePrecompiles { if p == addr { return true } } + return false }) vm.Set("slice", func(slice goja.Value, start, end int) goja.Value { @@ -501,15 +518,18 @@ func (t *jsTracer) setBuiltinFunctions() { vm.Interrupt(err) return nil } + if start < 0 || start > end || end > len(b) { vm.Interrupt(fmt.Sprintf("Tracer accessed out of bound memory: available %d, offset %d, size %d", len(b), start, end-start)) return nil } + res, err := t.toBuf(vm, b[start:end]) if err != nil { vm.Interrupt(err) return nil } + return res }) } diff --git a/eth/tracers/js/internal/tracers/tracers.go b/eth/tracers/js/internal/tracers/tracers.go index 9ca3b08064..23b62e2811 100644 --- a/eth/tracers/js/internal/tracers/tracers.go +++ b/eth/tracers/js/internal/tracers/tracers.go @@ -36,15 +36,19 @@ func Load() (map[string]string, error) { if err != nil { return err } + if d.IsDir() { return nil } + b, err := fs.ReadFile(files, path) if err != nil { return err } + name := camel(strings.TrimSuffix(path, ".js")) assetTracers[name] = string(b) + return nil }) diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 99b689d3a5..d3f7a79cbb 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -68,6 +68,7 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon value = big.NewInt(0) contract = vm.NewContract(account{}, account{}, value, startGas) ) + contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} if contractCode != nil { @@ -80,9 +81,11 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon tracer.CaptureEnd(ret, startGas-contract.Gas, err) // Rest gas assumes no refund tracer.CaptureTxEnd(contract.Gas) + if err != nil { return nil, err } + return tracer.GetResult() } @@ -99,6 +102,7 @@ func TestTracer(t *testing.T) { if err != nil { return nil, err.Error() // Stringify to allow comparison without nil checks } + return ret, "" } for i, tt := range []struct { @@ -165,10 +169,12 @@ func TestTracer(t *testing.T) { func TestHalt(t *testing.T) { timeout := errors.New("stahp") + tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil, nil) if err != nil { t.Fatal(err) } + go func() { time.Sleep(1 * time.Second) tracer.Stop(timeout) @@ -189,8 +195,10 @@ func TestHaltBetweenSteps(t *testing.T) { scope := &vm.ScopeContext{ Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), } + tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0)) tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil) + timeout := errors.New("stahp") tracer.Stop(timeout) tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil) @@ -214,10 +222,12 @@ func TestNoStepExec(t *testing.T) { env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer}) tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0)) tracer.CaptureEnd(nil, 0, nil) + ret, err := tracer.GetResult() if err != nil { t.Fatal(err) } + return ret } for i, tt := range []struct { @@ -241,26 +251,31 @@ func TestIsPrecompile(t *testing.T) { chaincfg.IstanbulBlock = big.NewInt(200) chaincfg.BerlinBlock = big.NewInt(300) txCtx := vm.TxContext{GasPrice: big.NewInt(100000)} + tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil) if err != nil { t.Fatal(err) } blockCtx := vm.BlockContext{BlockNumber: big.NewInt(150)} + res, err := runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg, nil) if err != nil { t.Error(err) } + if string(res) != "false" { t.Errorf("tracer should not consider blake2f as precompile in byzantium") } tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil) blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)} + res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg, nil) if err != nil { t.Error(err) } + if string(res) != "true" { t.Errorf("tracer should consider blake2f as precompile in istanbul") } @@ -280,6 +295,7 @@ func TestEnterExit(t *testing.T) { if err != nil { t.Fatal(err) } + scope := &vm.ScopeContext{ Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), } @@ -290,6 +306,7 @@ func TestEnterExit(t *testing.T) { if err != nil { t.Fatal(err) } + want := `{"enters":1,"exits":1,"enterGas":1000,"gasUsed":400}` if string(have) != want { t.Errorf("Number of invocations of enter() and exit() is wrong. Have %s, want %s\n", have, want) diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 766ee4e4b9..766a800618 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -84,19 +84,23 @@ func (al accessList) equal(other accessList) bool { } } } + return true } // accesslist converts the accesslist to a types.AccessList. func (al accessList) accessList() types.AccessList { acl := make(types.AccessList, 0, len(al)) + for addr, slots := range al { tuple := types.AccessTuple{Address: addr, StorageKeys: []common.Hash{}} for slot := range slots { tuple.StorageKeys = append(tuple.StorageKeys, slot) } + acl = append(acl, tuple) } + return acl } @@ -117,15 +121,19 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi for _, addr := range precompiles { excl[addr] = struct{}{} } + list := newAccessList() + for _, al := range acl { if _, ok := excl[al.Address]; !ok { list.addAddress(al.Address) } + for _, slot := range al.StorageKeys { list.addSlot(al.Address, slot) } } + return &AccessListTracer{ excl: excl, list: list, @@ -140,16 +148,19 @@ func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6 stack := scope.Stack stackData := stack.Data() stackLen := len(stackData) + if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 { slot := common.Hash(stackData[stackLen-1].Bytes32()) a.list.addSlot(scope.Contract.Address(), slot) } + if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 { addr := common.Address(stackData[stackLen-1].Bytes20()) if _, ok := a.excl[addr]; !ok { a.list.addAddress(addr) } } + if (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE) && stackLen >= 5 { addr := common.Address(stackData[stackLen-2].Bytes20()) if _, ok := a.excl[addr]; !ok { diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go index df06a9ee6b..b2fadab931 100644 --- a/eth/tracers/logger/gen_structlog.go +++ b/eth/tracers/logger/gen_structlog.go @@ -32,6 +32,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { OpName string `json:"opName"` ErrorString string `json:"error,omitempty"` } + var enc StructLog enc.Pc = s.Pc enc.Op = s.Op @@ -47,6 +48,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.Err = s.Err enc.OpName = s.OpName() enc.ErrorString = s.ErrorString() + return json.Marshal(&enc) } @@ -66,45 +68,59 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { RefundCounter *uint64 `json:"refund"` Err error `json:"-"` } + var dec StructLog if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Pc != nil { s.Pc = *dec.Pc } + if dec.Op != nil { s.Op = *dec.Op } + if dec.Gas != nil { s.Gas = uint64(*dec.Gas) } + if dec.GasCost != nil { s.GasCost = uint64(*dec.GasCost) } + if dec.Memory != nil { s.Memory = *dec.Memory } + if dec.MemorySize != nil { s.MemorySize = *dec.MemorySize } + if dec.Stack != nil { s.Stack = dec.Stack } + if dec.ReturnData != nil { s.ReturnData = *dec.ReturnData } + if dec.Storage != nil { s.Storage = dec.Storage } + if dec.Depth != nil { s.Depth = *dec.Depth } + if dec.RefundCounter != nil { s.RefundCounter = *dec.RefundCounter } + if dec.Err != nil { s.Err = dec.Err } + return nil } diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index ab10edc53f..5ca08455f6 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -43,6 +43,7 @@ func (s Storage) Copy() Storage { for key, value := range s { cpy[key] = value } + return cpy } @@ -97,6 +98,7 @@ func (s *StructLog) ErrorString() string { if s.Err != nil { return s.Err.Error() } + return "" } @@ -128,6 +130,7 @@ func NewStructLogger(cfg *Config) *StructLogger { if cfg != nil { logger.cfg = *cfg } + return logger } @@ -174,6 +177,7 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s stck[i] = item } } + stackData := stack.Data() stackLen := len(stackData) // Copy a snapshot of the current storage to a new container @@ -190,6 +194,7 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s address = common.Hash(stackData[stackLen-1].Bytes32()) value = l.env.StateDB.GetState(contract.Address(), address) ) + l.storage[contract.Address()][address] = value storage = l.storage[contract.Address()].Copy() } else if op == vm.SSTORE && stackLen >= 2 { @@ -198,10 +203,12 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s value = common.Hash(stackData[stackLen-2].Bytes32()) address = common.Hash(stackData[stackLen-1].Bytes32()) ) + l.storage[contract.Address()][address] = value storage = l.storage[contract.Address()].Copy() } } + var rdata []byte if l.cfg.EnableReturnData { rdata = make([]byte, len(rData)) @@ -221,8 +228,10 @@ func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, s func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { l.output = output l.err = err + if l.cfg.Debug { fmt.Printf("%#x\n", output) + if err != nil { fmt.Printf(" error: %v\n", err) } @@ -284,31 +293,39 @@ func (l *StructLogger) Output() []byte { return l.output } func WriteTrace(writer io.Writer, logs []StructLog) { for _, log := range logs { fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost) + if log.Err != nil { fmt.Fprintf(writer, " ERROR: %v", log.Err) } + fmt.Fprintln(writer) if len(log.Stack) > 0 { fmt.Fprintln(writer, "Stack:") + for i := len(log.Stack) - 1; i >= 0; i-- { fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex()) } } + if len(log.Memory) > 0 { fmt.Fprintln(writer, "Memory:") fmt.Fprint(writer, hex.Dump(log.Memory)) } + if len(log.Storage) > 0 { fmt.Fprintln(writer, "Storage:") + for h, item := range log.Storage { fmt.Fprintf(writer, "%x: %x\n", h, item) } } + if len(log.ReturnData) > 0 { fmt.Fprintln(writer, "ReturnData:") fmt.Fprint(writer, hex.Dump(log.ReturnData)) } + fmt.Fprintln(writer) } } @@ -340,6 +357,7 @@ func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger { if l.cfg == nil { l.cfg = &Config{} } + return l } @@ -364,6 +382,7 @@ func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Addr // CaptureState also tracks SLOAD/SSTORE ops to track storage change. func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { stack := scope.Stack + fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) if !t.cfg.DisableStack { @@ -372,11 +391,14 @@ func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope for _, elem := range stack.Data() { a = append(a, elem.Hex()) } + b := fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) } + fmt.Fprintf(t.out, "%10v |", t.env.StateDB.GetRefund()) fmt.Fprintln(t.out, "") + if err != nil { fmt.Fprintf(t.out, "Error: %v\n", err) } diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index d3d20d82d6..76c5fec5b1 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -39,6 +39,7 @@ func NewJSONLogger(cfg *Config, writer io.Writer) *JSONLogger { if l.cfg == nil { l.cfg = &Config{} } + return l } @@ -69,12 +70,15 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco if l.cfg.EnableMemory { log.Memory = memory.Data() } + if !l.cfg.DisableStack { log.Stack = stack.Data() } + if l.cfg.EnableReturnData { log.ReturnData = rData } + l.encoder.Encode(log) } @@ -85,10 +89,12 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { GasUsed math.HexOrDecimal64 `json:"gasUsed"` Err string `json:"error,omitempty"` } + var errMsg string if err != nil { errMsg = err.Error() } + _ = l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg}) } diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index 4539a01c37..10bd77b0a8 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -59,17 +59,23 @@ func TestStoreCapture(t *testing.T) { env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger}) contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000) ) + contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} + var index common.Hash + logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil) + _, err := env.Interpreter().PreRun(contract, []byte{}, false, nil) if err != nil { t.Fatal(err) } + if len(logger.storage[contract.Address()]) == 0 { t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()])) } + exp := common.BigToHash(big.NewInt(1)) if logger.storage[contract.Address()][index] != exp { t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index]) @@ -105,6 +111,7 @@ func TestStructLogMarshalingOmitEmpty(t *testing.T) { if err != nil { t.Fatal(err) } + if have, want := string(blob), tt.want; have != want { t.Fatalf("mismatched results\n\thave: %v\n\twant: %v", have, want) } diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index eb73c37363..f6dec92ea0 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -70,6 +70,7 @@ func (t *fourByteTracer) isPrecompiled(addr common.Address) bool { return true } } + return false } @@ -97,6 +98,7 @@ func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to comm if t.interrupt.Load() { return } + if len(input) < 4 { return } @@ -109,6 +111,7 @@ func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to comm if t.isPrecompiled(to) { return } + t.store(input[0:4], len(input)-4) } @@ -119,6 +122,7 @@ func (t *fourByteTracer) GetResult() (json.RawMessage, error) { if err != nil { return nil, err } + return res, t.reason } diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index cc608bf631..74a60f2d54 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -134,6 +134,7 @@ func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, e // CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { toCopy := to + t.callstack[0] = callFrame{ Type: vm.CALL, From: from, @@ -228,6 +229,7 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { if t.config.OnlyTopCall { return } + size := len(t.callstack) if size <= 1 { return @@ -265,6 +267,7 @@ func (t *callTracer) GetResult() (json.RawMessage, error) { if err != nil { return nil, err } + return json.RawMessage(res), t.reason } diff --git a/eth/tracers/native/gen_account_json.go b/eth/tracers/native/gen_account_json.go index 4c39cbc38c..56b9fdf281 100644 --- a/eth/tracers/native/gen_account_json.go +++ b/eth/tracers/native/gen_account_json.go @@ -20,11 +20,13 @@ func (a account) MarshalJSON() ([]byte, error) { Nonce uint64 `json:"nonce,omitempty"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"` } + var enc account enc.Balance = (*hexutil.Big)(a.Balance) enc.Code = a.Code enc.Nonce = a.Nonce enc.Storage = a.Storage + return json.Marshal(&enc) } @@ -36,21 +38,27 @@ func (a *account) UnmarshalJSON(input []byte) error { Nonce *uint64 `json:"nonce,omitempty"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"` } + var dec account if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Balance != nil { a.Balance = (*big.Int)(dec.Balance) } + if dec.Code != nil { a.Code = *dec.Code } + if dec.Nonce != nil { a.Nonce = *dec.Nonce } + if dec.Storage != nil { a.Storage = dec.Storage } + return nil } diff --git a/eth/tracers/native/gen_callframe_json.go b/eth/tracers/native/gen_callframe_json.go index c44f38390d..40f5f24362 100644 --- a/eth/tracers/native/gen_callframe_json.go +++ b/eth/tracers/native/gen_callframe_json.go @@ -30,6 +30,7 @@ func (c callFrame) MarshalJSON() ([]byte, error) { Value *hexutil.Big `json:"value,omitempty" rlp:"optional"` TypeString string `json:"type"` } + var enc callFrame0 enc.Type = c.Type enc.From = c.From @@ -44,6 +45,7 @@ func (c callFrame) MarshalJSON() ([]byte, error) { enc.Logs = c.Logs enc.Value = (*hexutil.Big)(c.Value) enc.TypeString = c.TypeString() + return json.Marshal(&enc) } @@ -63,45 +65,59 @@ func (c *callFrame) UnmarshalJSON(input []byte) error { Logs []callLog `json:"logs,omitempty" rlp:"optional"` Value *hexutil.Big `json:"value,omitempty" rlp:"optional"` } + var dec callFrame0 if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Type != nil { c.Type = *dec.Type } + if dec.From != nil { c.From = *dec.From } + if dec.Gas != nil { c.Gas = uint64(*dec.Gas) } + if dec.GasUsed != nil { c.GasUsed = uint64(*dec.GasUsed) } + if dec.To != nil { c.To = dec.To } + if dec.Input != nil { c.Input = *dec.Input } + if dec.Output != nil { c.Output = *dec.Output } + if dec.Error != nil { c.Error = *dec.Error } + if dec.RevertReason != nil { c.RevertReason = *dec.RevertReason } + if dec.Calls != nil { c.Calls = dec.Calls } + if dec.Logs != nil { c.Logs = dec.Logs } + if dec.Value != nil { c.Value = (*big.Int)(dec.Value) } + return nil } diff --git a/eth/tracers/native/gen_flatcallaction_json.go b/eth/tracers/native/gen_flatcallaction_json.go index c075606983..ea00834986 100644 --- a/eth/tracers/native/gen_flatcallaction_json.go +++ b/eth/tracers/native/gen_flatcallaction_json.go @@ -29,6 +29,7 @@ func (f flatCallAction) MarshalJSON() ([]byte, error) { To *common.Address `json:"to,omitempty"` Value *hexutil.Big `json:"value,omitempty"` } + var enc flatCallAction enc.Author = f.Author enc.RewardType = f.RewardType @@ -43,6 +44,7 @@ func (f flatCallAction) MarshalJSON() ([]byte, error) { enc.RefundAddress = f.RefundAddress enc.To = f.To enc.Value = (*hexutil.Big)(f.Value) + return json.Marshal(&enc) } @@ -63,48 +65,63 @@ func (f *flatCallAction) UnmarshalJSON(input []byte) error { To *common.Address `json:"to,omitempty"` Value *hexutil.Big `json:"value,omitempty"` } + var dec flatCallAction if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Author != nil { f.Author = dec.Author } + if dec.RewardType != nil { f.RewardType = *dec.RewardType } + if dec.SelfDestructed != nil { f.SelfDestructed = dec.SelfDestructed } + if dec.Balance != nil { f.Balance = (*big.Int)(dec.Balance) } + if dec.CallType != nil { f.CallType = *dec.CallType } + if dec.CreationMethod != nil { f.CreationMethod = *dec.CreationMethod } + if dec.From != nil { f.From = dec.From } + if dec.Gas != nil { f.Gas = (*uint64)(dec.Gas) } + if dec.Init != nil { f.Init = (*[]byte)(dec.Init) } + if dec.Input != nil { f.Input = (*[]byte)(dec.Input) } + if dec.RefundAddress != nil { f.RefundAddress = dec.RefundAddress } + if dec.To != nil { f.To = dec.To } + if dec.Value != nil { f.Value = (*big.Int)(dec.Value) } + return nil } diff --git a/eth/tracers/native/gen_flatcallresult_json.go b/eth/tracers/native/gen_flatcallresult_json.go index e9fa5e44da..6e5be71190 100644 --- a/eth/tracers/native/gen_flatcallresult_json.go +++ b/eth/tracers/native/gen_flatcallresult_json.go @@ -19,11 +19,13 @@ func (f flatCallResult) MarshalJSON() ([]byte, error) { GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` Output *hexutil.Bytes `json:"output,omitempty"` } + var enc flatCallResult enc.Address = f.Address enc.Code = (*hexutil.Bytes)(f.Code) enc.GasUsed = (*hexutil.Uint64)(f.GasUsed) enc.Output = (*hexutil.Bytes)(f.Output) + return json.Marshal(&enc) } @@ -35,21 +37,27 @@ func (f *flatCallResult) UnmarshalJSON(input []byte) error { GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` Output *hexutil.Bytes `json:"output,omitempty"` } + var dec flatCallResult if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Address != nil { f.Address = dec.Address } + if dec.Code != nil { f.Code = (*[]byte)(dec.Code) } + if dec.GasUsed != nil { f.GasUsed = (*uint64)(dec.GasUsed) } + if dec.Output != nil { f.Output = (*[]byte)(dec.Output) } + return nil } diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 41da5ef66a..4393ab4494 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -141,10 +141,12 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, if t.interrupt.Load() { return } + stack := scope.Stack stackData := stack.Data() stackLen := len(stackData) caller := scope.Contract.Address() + switch { case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): slot := common.Hash(stackData[stackLen-1].Bytes32()) @@ -224,6 +226,7 @@ func (t *prestateTracer) CaptureTxEnd(restGas uint64) { delete(t.pre[addr].Storage, key) } else { modified = true + if newVal != (common.Hash{}) { postAccount.Storage[key] = newVal } @@ -261,9 +264,11 @@ func (t *prestateTracer) GetResult() (json.RawMessage, error) { } else { res, err = json.Marshal(t.pre) } + if err != nil { return nil, err } + return json.RawMessage(res), t.reason } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 61f7961e1f..5990b40e8b 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -38,6 +38,7 @@ func BenchmarkTransactionTrace(b *testing.B) { gas := uint64(1000000) // 1M gas to := common.HexToAddress("0x00000000000000000000000000000000deadbeef") signer := types.LatestSignerForChainID(big.NewInt(1337)) + tx, err := types.SignNewTx(key, signer, &types.LegacyTx{ Nonce: 1, @@ -48,6 +49,7 @@ func BenchmarkTransactionTrace(b *testing.B) { if err != nil { b.Fatal(err) } + txContext := vm.TxContext{ Origin: from, GasPrice: tx.GasPrice(), @@ -89,24 +91,30 @@ func BenchmarkTransactionTrace(b *testing.B) { //EnableReturnData: false, }) evm := vm.NewEVM(blockContext, txContext, statedb, params.AllEthashProtocolChanges, vm.Config{Tracer: tracer}) + msg, err := core.TransactionToMessage(tx, signer, nil) if err != nil { b.Fatalf("failed to prepare transaction for tracing: %v", err) } + b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { snap := statedb.Snapshot() st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + _, err = st.TransitionDb(context.Background()) if err != nil { b.Fatal(err) } + statedb.RevertToSnapshot(snap) + if have, want := len(tracer.StructLogs()), 244752; have != want { b.Fatalf("trace wrong, want %d steps, have %d", want, have) } + tracer.Reset() } } diff --git a/ethclient/bor_ethclient.go b/ethclient/bor_ethclient.go index e03e72d4ea..b169df5736 100644 --- a/ethclient/bor_ethclient.go +++ b/ethclient/bor_ethclient.go @@ -14,15 +14,18 @@ func (ec *Client) GetRootHash(ctx context.Context, startBlockNumber uint64, endB if err := ec.c.CallContext(ctx, &rootHash, "eth_getRootHash", startBlockNumber, endBlockNumber); err != nil { return "", err } + return rootHash, nil } // GetBorBlockReceipt returns bor block receipt func (ec *Client) GetBorBlockReceipt(ctx context.Context, hash common.Hash) (*types.Receipt, error) { var r *types.Receipt + err := ec.c.CallContext(ctx, &r, "eth_getBorBlockReceipt", hash) if err == nil && r == nil { return nil, ethereum.NotFound } + return r, err } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 8158359995..f20cc1662d 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -46,6 +46,7 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) { if err != nil { return nil, err } + return NewClient(c), nil } @@ -63,10 +64,12 @@ func (ec *Client) Close() { // ChainID retrieves the current chain ID for transaction replay protection. func (ec *Client) ChainID(ctx context.Context) (*big.Int, error) { var result hexutil.Big + err := ec.c.CallContext(ctx, &result, "eth_chainId") if err != nil { return nil, err } + return (*big.Int)(&result), err } @@ -80,10 +83,12 @@ func (ec *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Blo func (ec *Client) TransactionReceiptsInBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { var rs []map[string]interface{} + err := ec.c.CallContext(ctx, &rs, "eth_getTransactionReceiptsByBlock", blockNrOrHash) if err != nil { return nil, err } + return rs, err } @@ -100,6 +105,7 @@ func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Bl func (ec *Client) BlockNumber(ctx context.Context) (uint64, error) { var result hexutil.Uint64 err := ec.c.CallContext(ctx, &result, "eth_blockNumber") + return uint64(result), err } @@ -111,6 +117,7 @@ type rpcBlock struct { func (ec *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) { var raw json.RawMessage + err := ec.c.CallContext(ctx, &raw, method, args...) if err != nil { return nil, err @@ -119,10 +126,13 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface } // Decode header and transactions. var head *types.Header + var body rpcBlock + if err := json.Unmarshal(raw, &head); err != nil { return nil, err } + if err := json.Unmarshal(raw, &body); err != nil { return nil, err } @@ -130,9 +140,11 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 { return nil, fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles") } + if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 { return nil, fmt.Errorf("server returned empty uncle list but block header indicates uncles") } + if head.TxHash != types.EmptyRootHash && len(body.Transactions) == 0 { return nil, fmt.Errorf("server returned empty transaction list but block header indicates transactions") } @@ -141,6 +153,7 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface if len(body.UncleHashes) > 0 { uncles = make([]*types.Header, len(body.UncleHashes)) reqs := make([]rpc.BatchElem, len(body.UncleHashes)) + for i := range reqs { reqs[i] = rpc.BatchElem{ Method: "eth_getUncleByBlockHashAndIndex", @@ -148,13 +161,16 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface Result: &uncles[i], } } + if err := ec.c.BatchCallContext(ctx, reqs); err != nil { return nil, err } + for i := range reqs { if reqs[i].Error != nil { return nil, reqs[i].Error } + if uncles[i] == nil { return nil, fmt.Errorf("got null header for uncle %d of block %x", i, body.Hash[:]) } @@ -162,22 +178,27 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface } // Fill the sender cache of transactions in the block. txs := make([]*types.Transaction, len(body.Transactions)) + for i, tx := range body.Transactions { if tx.From != nil { setSenderFromServer(tx.tx, *tx.From, body.Hash) } + txs[i] = tx.tx } + return types.NewBlockWithHeader(head).WithBody(txs, uncles), nil } // HeaderByHash returns the block header with the given hash. func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { var head *types.Header + err := ec.c.CallContext(ctx, &head, "eth_getBlockByHash", hash, false) if err == nil && head == nil { err = ethereum.NotFound } + return head, err } @@ -185,10 +206,12 @@ func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.He // nil, the latest known header is returned. func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { var head *types.Header + err := ec.c.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false) if err == nil && head == nil { err = ethereum.NotFound } + return head, err } @@ -207,12 +230,14 @@ func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error { if err := json.Unmarshal(msg, &tx.tx); err != nil { return err } + return json.Unmarshal(msg, &tx.txExtraInfo) } // TransactionByHash returns the transaction with the given hash. func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { var json *rpcTransaction + err = ec.c.CallContext(ctx, &json, "eth_getTransactionByHash", hash) if err != nil { return nil, false, err @@ -221,9 +246,11 @@ func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx * } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { return nil, false, fmt.Errorf("server returned transaction without signature") } + if json.From != nil && json.BlockHash != nil { setSenderFromServer(json.tx, *json.From, *json.BlockHash) } + return json.tx, json.BlockNumber == nil, nil } @@ -245,12 +272,15 @@ func (ec *Client) TransactionSender(ctx context.Context, tx *types.Transaction, Hash common.Hash From common.Address } + if err = ec.c.CallContext(ctx, &meta, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index)); err != nil { return common.Address{}, err } + if meta.Hash == (common.Hash{}) || meta.Hash != tx.Hash() { return common.Address{}, errors.New("wrong inclusion block/index") } + return meta.From, nil } @@ -258,24 +288,29 @@ func (ec *Client) TransactionSender(ctx context.Context, tx *types.Transaction, func (ec *Client) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { var num hexutil.Uint err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByHash", blockHash) + return uint(num), err } // TransactionInBlock returns a single transaction at index in the given block. func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { var json *rpcTransaction + err := ec.c.CallContext(ctx, &json, "eth_getTransactionByBlockHashAndIndex", blockHash, hexutil.Uint64(index)) if err != nil { return nil, err } + if json == nil { return nil, ethereum.NotFound } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { return nil, fmt.Errorf("server returned transaction without signature") } + if json.From != nil && json.BlockHash != nil { setSenderFromServer(json.tx, *json.From, *json.BlockHash) } + return json.tx, err } @@ -283,12 +318,14 @@ func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash, // Note that the receipt is not available for pending transactions. func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { var r *types.Receipt + err := ec.c.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash) if err == nil { if r == nil { return nil, ethereum.NotFound } } + return r, err } @@ -304,10 +341,12 @@ func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, err if err := json.Unmarshal(raw, &syncing); err == nil { return nil, nil // Not syncing (always false) } + var p *rpcProgress if err := json.Unmarshal(raw, &p); err != nil { return nil, err } + return p.toSyncProgress(), nil } @@ -322,13 +361,16 @@ func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) // NetworkID returns the network ID (also known as the chain ID) for this chain. func (ec *Client) NetworkID(ctx context.Context) (*big.Int, error) { version := new(big.Int) + var ver string if err := ec.c.CallContext(ctx, &ver, "net_version"); err != nil { return nil, err } + if _, ok := version.SetString(ver, 10); !ok { return nil, fmt.Errorf("invalid net_version result %q", ver) } + return version, nil } @@ -337,6 +379,7 @@ func (ec *Client) NetworkID(ctx context.Context) (*big.Int, error) { func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { var result hexutil.Big err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, toBlockNumArg(blockNumber)) + return (*big.Int)(&result), err } @@ -345,6 +388,7 @@ func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNu func (ec *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { var result hexutil.Bytes err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, toBlockNumArg(blockNumber)) + return result, err } @@ -353,6 +397,7 @@ func (ec *Client) StorageAt(ctx context.Context, account common.Address, key com func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { var result hexutil.Bytes err := ec.c.CallContext(ctx, &result, "eth_getCode", account, toBlockNumArg(blockNumber)) + return result, err } @@ -361,6 +406,7 @@ func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumbe func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { var result hexutil.Uint64 err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, toBlockNumArg(blockNumber)) + return uint64(result), err } @@ -369,11 +415,14 @@ func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumb // FilterLogs executes a filter query. func (ec *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { var result []types.Log + arg, err := toFilterArg(q) if err != nil { return nil, err } + err = ec.c.CallContext(ctx, &result, "eth_getLogs", arg) + return result, err } @@ -383,6 +432,7 @@ func (ec *Client) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuer if err != nil { return nil, err } + return ec.c.EthSubscribe(ctx, ch, "logs", arg) } @@ -393,6 +443,7 @@ func toFilterArg(q ethereum.FilterQuery) (interface{}, error) { } if q.BlockHash != nil { arg["blockHash"] = *q.BlockHash + if q.FromBlock != nil || q.ToBlock != nil { return nil, fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock") } @@ -402,8 +453,10 @@ func toFilterArg(q ethereum.FilterQuery) (interface{}, error) { } else { arg["fromBlock"] = toBlockNumArg(q.FromBlock) } + arg["toBlock"] = toBlockNumArg(q.ToBlock) } + return arg, nil } @@ -413,6 +466,7 @@ func toFilterArg(q ethereum.FilterQuery) (interface{}, error) { func (ec *Client) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { var result hexutil.Big err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, "pending") + return (*big.Int)(&result), err } @@ -420,6 +474,7 @@ func (ec *Client) PendingBalanceAt(ctx context.Context, account common.Address) func (ec *Client) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) { var result hexutil.Bytes err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, "pending") + return result, err } @@ -427,6 +482,7 @@ func (ec *Client) PendingStorageAt(ctx context.Context, account common.Address, func (ec *Client) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { var result hexutil.Bytes err := ec.c.CallContext(ctx, &result, "eth_getCode", account, "pending") + return result, err } @@ -435,6 +491,7 @@ func (ec *Client) PendingCodeAt(ctx context.Context, account common.Address) ([] func (ec *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { var result hexutil.Uint64 err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, "pending") + return uint64(result), err } @@ -442,6 +499,7 @@ func (ec *Client) PendingNonceAt(ctx context.Context, account common.Address) (u func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) { var num hexutil.Uint err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByNumber", "pending") + return uint(num), err } @@ -455,10 +513,12 @@ func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) { // blocks might not be available. func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { var hex hexutil.Bytes + err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), toBlockNumArg(blockNumber)) if err != nil { return nil, err } + return hex, nil } @@ -466,10 +526,12 @@ func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockN // the block by block hash instead of block height. func (ec *Client) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { var hex hexutil.Bytes + err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), rpc.BlockNumberOrHashWithHash(blockHash, false)) if err != nil { return nil, err } + return hex, nil } @@ -477,10 +539,12 @@ func (ec *Client) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, // The state seen by the contract call is the pending state. func (ec *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { var hex hexutil.Bytes + err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), "pending") if err != nil { return nil, err } + return hex, nil } @@ -491,6 +555,7 @@ func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) { if err := ec.c.CallContext(ctx, &hex, "eth_gasPrice"); err != nil { return nil, err } + return (*big.Int)(&hex), nil } @@ -501,6 +566,7 @@ func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { if err := ec.c.CallContext(ctx, &hex, "eth_maxPriorityFeePerGas"); err != nil { return nil, err } + return (*big.Int)(&hex), nil } @@ -517,6 +583,7 @@ func (ec *Client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock * if err := ec.c.CallContext(ctx, &res, "eth_feeHistory", hexutil.Uint(blockCount), toBlockNumArg(lastBlock), rewardPercentiles); err != nil { return nil, err } + reward := make([][]*big.Int, len(res.Reward)) for i, r := range res.Reward { reward[i] = make([]*big.Int, len(r)) @@ -524,10 +591,12 @@ func (ec *Client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock * reward[i][j] = (*big.Int)(r) } } + baseFee := make([]*big.Int, len(res.BaseFee)) for i, b := range res.BaseFee { baseFee[i] = (*big.Int)(b) } + return ðereum.FeeHistory{ OldestBlock: (*big.Int)(res.OldestBlock), Reward: reward, @@ -542,10 +611,12 @@ func (ec *Client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock * // but it should provide a basis for setting a reasonable default. func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { var hex hexutil.Uint64 + err := ec.c.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg)) if err != nil { return 0, err } + return uint64(hex), nil } @@ -558,6 +629,7 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er if err != nil { return err } + return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) } @@ -593,15 +665,19 @@ func toCallArg(msg ethereum.CallMsg) interface{} { if len(msg.Data) > 0 { arg["data"] = hexutil.Bytes(msg.Data) } + if msg.Value != nil { arg["value"] = (*hexutil.Big)(msg.Value) } + if msg.Gas != 0 { arg["gas"] = hexutil.Uint64(msg.Gas) } + if msg.GasPrice != nil { arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) } + return arg } @@ -632,6 +708,7 @@ func (p *rpcProgress) toSyncProgress() *ethereum.SyncProgress { if p == nil { return nil } + return ðereum.SyncProgress{ StartingBlock: uint64(p.StartingBlock), CurrentBlock: uint64(p.CurrentBlock), diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 6b688416f4..7e9f9f08df 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -166,6 +166,7 @@ func TestToFilterArg(t *testing.T) { if (testCase.err == nil) != (err == nil) { t.Fatalf("expected error %v but got %v", testCase.err, err) } + if testCase.err != nil { if testCase.err.Error() != err.Error() { t.Fatalf("expected error %v but got %v", testCase.err, err) @@ -237,6 +238,7 @@ func generateTestChain() []*types.Block { generate := func(i int, g *core.BlockGen) { g.OffsetTime(5) g.SetExtra([]byte("test")) + if i == 1 { // Test transactions are included in block #2. g.AddTx(testTx1) @@ -244,56 +246,58 @@ func generateTestChain() []*types.Block { } } _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 2, generate) + return append([]*types.Block{genesis.ToBlock()}, blocks...) } func TestEthClient(t *testing.T) { t.Skip("bor due to burn contract") - // backend, chain := newTestBackend(t) // client, _ := backend.Attach() // defer backend.Close() // defer client.Close() - - // tests := map[string]struct { - // test func(t *testing.T) - // }{ - // "Header": { - // func(t *testing.T) { testHeader(t, chain, client) }, - // }, - // "BalanceAt": { - // func(t *testing.T) { testBalanceAt(t, client) }, - // }, - // "TxInBlockInterrupted": { - // func(t *testing.T) { testTransactionInBlockInterrupted(t, client) }, - // }, - // "ChainID": { - // func(t *testing.T) { testChainID(t, client) }, - // }, - // "GetBlock": { - // func(t *testing.T) { testGetBlock(t, client) }, - // }, - // "StatusFunctions": { - // func(t *testing.T) { testStatusFunctions(t, client) }, - // }, - // "CallContract": { - // func(t *testing.T) { testCallContract(t, client) }, - // }, - // "CallContractAtHash": { - // func(t *testing.T) { testCallContractAtHash(t, client) }, - // }, - // "AtFunctions": { - // func(t *testing.T) { testAtFunctions(t, client) }, - // }, - // "TransactionSender": { - // func(t *testing.T) { testTransactionSender(t, client) }, - // }, - // } - + // + // tests := map[string]struct { + // test func(t *testing.T) + // }{ + // + // "Header": { + // func(t *testing.T) { testHeader(t, chain, client) }, + // }, + // "BalanceAt": { + // func(t *testing.T) { testBalanceAt(t, client) }, + // }, + // "TxInBlockInterrupted": { + // func(t *testing.T) { testTransactionInBlockInterrupted(t, client) }, + // }, + // "ChainID": { + // func(t *testing.T) { testChainID(t, client) }, + // }, + // "GetBlock": { + // func(t *testing.T) { testGetBlock(t, client) }, + // }, + // "StatusFunctions": { + // func(t *testing.T) { testStatusFunctions(t, client) }, + // }, + // "CallContract": { + // func(t *testing.T) { testCallContract(t, client) }, + // }, + // "CallContractAtHash": { + // func(t *testing.T) { testCallContractAtHash(t, client) }, + // }, + // "AtFunctions": { + // func(t *testing.T) { testAtFunctions(t, client) }, + // }, + // "TransactionSender": { + // func(t *testing.T) { testTransactionSender(t, client) }, + // }, + // } + // // t.Parallel() - // for name, tt := range tests { - // t.Run(name, tt.test) - // } + // + // for name, tt := range tests { + // t.Run(name, tt.test) + // } } func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { @@ -319,6 +323,7 @@ func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { for name, tt := range tests { t.Run(name, func(t *testing.T) { ec := NewClient(client) + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() @@ -326,9 +331,11 @@ func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { if !errors.Is(err, tt.wantErr) { t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr) } + if got != nil && got.Number != nil && got.Number.Sign() == 0 { got.Number = big.NewInt(0) // hack to make DeepEqual work } + if !reflect.DeepEqual(got, tt.want) { t.Fatalf("HeaderByNumber(%v)\n = %v\nwant %v", tt.block, got, tt.want) } @@ -368,6 +375,7 @@ func testBalanceAt(t *testing.T, client *rpc.Client) { for name, tt := range tests { t.Run(name, func(t *testing.T) { ec := NewClient(client) + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() @@ -375,6 +383,7 @@ func testBalanceAt(t *testing.T, client *rpc.Client) { if tt.wantErr != nil && (err == nil || err.Error() != tt.wantErr.Error()) { t.Fatalf("BalanceAt(%x, %v) error = %q, want %q", tt.account, tt.block, err, tt.wantErr) } + if got.Cmp(tt.want) != 0 { t.Fatalf("BalanceAt(%x, %v) = %v, want %v", tt.account, tt.block, got, tt.want) } @@ -394,10 +403,12 @@ func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) { // Test tx in block interrupted. ctx, cancel := context.WithCancel(context.Background()) cancel() + tx, err := ec.TransactionInBlock(ctx, block.Hash(), 0) if tx != nil { t.Fatal("transaction should be nil") } + if err == nil || err == ethereum.NotFound { t.Fatal("error should not be nil/notfound") } @@ -410,10 +421,12 @@ func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) { 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) } + if id == nil || id.Cmp(params.AllEthashProtocolChanges.ChainID) != 0 { t.Fatalf("ChainID returned wrong number: %+v", id) } @@ -427,6 +440,7 @@ func testGetBlock(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + if blockNumber != 2 { t.Fatalf("BlockNumber returned wrong number: %d", blockNumber) } @@ -435,6 +449,7 @@ func testGetBlock(t *testing.T, client *rpc.Client) { 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()) } @@ -443,6 +458,7 @@ func testGetBlock(t *testing.T, client *rpc.Client) { 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()) } @@ -451,6 +467,7 @@ func testGetBlock(t *testing.T, client *rpc.Client) { 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()) } @@ -459,6 +476,7 @@ func testGetBlock(t *testing.T, client *rpc.Client) { 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()) } @@ -472,6 +490,7 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + if progress != nil { t.Fatalf("unexpected progress: %v", progress) } @@ -481,6 +500,7 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + if networkID.Cmp(big.NewInt(0)) != 0 { t.Fatalf("unexpected networkID: %v", networkID) } @@ -490,6 +510,7 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + if gasPrice.Cmp(big.NewInt(1000000000)) != 0 { t.Fatalf("unexpected gas price: %v", gasPrice) } @@ -499,6 +520,7 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + if gasTipCap.Cmp(big.NewInt(234375000)) != 0 { t.Fatalf("unexpected gas tip cap: %v", gasTipCap) } @@ -508,6 +530,7 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + want := ðereum.FeeHistory{ OldestBlock: big.NewInt(2), Reward: [][]*big.Int{ @@ -537,13 +560,16 @@ func testCallContractAtHash(t *testing.T, client *rpc.Client) { Gas: 21000, 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) } + block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1)) if err != nil { t.Fatalf("BlockByNumber error: %v", err) @@ -564,10 +590,12 @@ func testCallContract(t *testing.T, client *rpc.Client) { Gas: 21000, 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) } @@ -593,6 +621,7 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + if pending != 1 { t.Fatalf("unexpected pending, wanted 1 got: %v", pending) } @@ -601,10 +630,12 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { 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) } @@ -613,10 +644,12 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { 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) } @@ -625,10 +658,12 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { 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) } @@ -637,10 +672,12 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { 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) } @@ -655,10 +692,12 @@ func testTransactionSender(t *testing.T, client *rpc.Client) { if err != nil { t.Fatal("can't get block 1:", err) } + tx1, err := ec.TransactionInBlock(ctx, block2.Hash(), 0) if err != nil { t.Fatal("can't get tx:", err) } + if tx1.Hash() != testTx1.Hash() { t.Fatalf("wrong tx hash %v, want %v", tx1.Hash(), testTx1.Hash()) } @@ -667,10 +706,12 @@ func testTransactionSender(t *testing.T, client *rpc.Client) { // TransactionSender. Ensure the server is not asked by canceling the context here. canceledCtx, cancel := context.WithCancel(context.Background()) cancel() + sender1, err := ec.TransactionSender(canceledCtx, tx1, block2.Hash(), 0) if err != nil { t.Fatal(err) } + if sender1 != testAddr { t.Fatal("wrong sender:", sender1) } @@ -681,6 +722,7 @@ func testTransactionSender(t *testing.T, client *rpc.Client) { if err != nil { t.Fatal(err) } + if sender2 != testAddr { t.Fatal("wrong sender:", sender2) } @@ -691,12 +733,14 @@ func sendTransaction(ec *Client) error { if err != nil { return err } + nonce, err := ec.PendingNonceAt(context.Background(), testAddr) if err != nil { return err } signer := types.LatestSignerForChainID(chainID) + tx, err := types.SignNewTx(testKey, signer, &types.LegacyTx{ Nonce: nonce, To: &common.Address{2}, @@ -707,5 +751,6 @@ func sendTransaction(ec *Client) error { if err != nil { return err } + return ec.SendTransaction(context.Background(), tx) } diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go index b705d767a3..1df4186bca 100644 --- a/ethclient/gethclient/gethclient.go +++ b/ethclient/gethclient/gethclient.go @@ -52,10 +52,12 @@ func (ec *Client) CreateAccessList(ctx context.Context, msg ethereum.CallMsg) (* Error string `json:"error,omitempty"` GasUsed hexutil.Uint64 `json:"gasUsed"` } + var result accessListResult if err := ec.c.CallContext(ctx, &result, "eth_createAccessList", toCallArg(msg)); err != nil { return nil, 0, "", err } + return result.Accesslist, uint64(result.GasUsed), result.Error, nil } @@ -112,6 +114,7 @@ func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []s Proof: st.Proof, }) } + result := AccountResult{ Address: res.Address, AccountProof: res.AccountProof, @@ -121,6 +124,7 @@ func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []s StorageHash: res.StorageHash, StorageProof: storageResults, } + return &result, err } @@ -140,6 +144,7 @@ func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockN ctx, &hex, "eth_call", toCallArg(msg), toBlockNumArg(blockNumber), overrides, ) + return hex, err } @@ -147,6 +152,7 @@ func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockN func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) { var result debug.GCStats err := ec.c.CallContext(ctx, &result, "debug_gcStats") + return &result, err } @@ -154,6 +160,7 @@ func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) { func (ec *Client) MemStats(ctx context.Context) (*runtime.MemStats, error) { var result runtime.MemStats err := ec.c.CallContext(ctx, &result, "debug_memStats") + return &result, err } @@ -168,6 +175,7 @@ func (ec *Client) SetHead(ctx context.Context, number *big.Int) error { func (ec *Client) GetNodeInfo(ctx context.Context) (*p2p.NodeInfo, error) { var result p2p.NodeInfo err := ec.c.CallContext(ctx, &result, "admin_nodeInfo") + return &result, err } @@ -200,6 +208,7 @@ func toBlockNumArg(number *big.Int) string { if number.Cmp(safe) == 0 { return "safe" } + return hexutil.EncodeBig(number) } @@ -211,15 +220,19 @@ func toCallArg(msg ethereum.CallMsg) interface{} { if len(msg.Data) > 0 { arg["data"] = hexutil.Bytes(msg.Data) } + if msg.Value != nil { arg["value"] = (*hexutil.Big)(msg.Value) } + if msg.Gas != 0 { arg["gas"] = hexutil.Uint64(msg.Gas) } + if msg.GasPrice != nil { arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) } + return arg } diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index 78b42c3369..ed25be8cd4 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -57,6 +57,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { // Create Ethereum Service config := ðconfig.Config{Genesis: genesis} config.Ethash.PowMode = ethash.ModeFake + ethservice, err := eth.New(n, config) if err != nil { t.Fatalf("can't create new ethereum service: %v", err) @@ -73,9 +74,11 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { if err := n.Start(); err != nil { t.Fatalf("can't start test node: %v", err) } + if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil { t.Fatalf("can't import test blocks: %v", err) } + return n, blocks } @@ -93,6 +96,7 @@ func generateTestChain() (*core.Genesis, []*types.Block) { } _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, generate) blocks = append([]*types.Block{genesis.ToBlock()}, blocks...) + return genesis, blocks } @@ -100,10 +104,12 @@ func TestGethClient(t *testing.T) { t.Skip("bor due to burn contract") backend, _ := newTestBackend(t) + client, err := backend.Attach() if err != nil { t.Fatal(err) } + defer backend.Close() defer client.Close() @@ -172,16 +178,20 @@ func testAccessList(t *testing.T, client *rpc.Client) { GasPrice: big.NewInt(765625000), Value: big.NewInt(1), } + al, gas, vmErr, err := ec.CreateAccessList(context.Background(), msg) if err != nil { t.Fatalf("unexpected error: %v", err) } + if vmErr != "" { t.Fatalf("unexpected vm error: %v", vmErr) } + if gas != 21000 { t.Fatalf("unexpected gas used: %v", gas) } + if len(*al) != 0 { t.Fatalf("unexpected length of accesslist: %v", len(*al)) } @@ -194,16 +204,20 @@ func testAccessList(t *testing.T, client *rpc.Client) { Value: big.NewInt(1), Data: common.FromHex("0x608060806080608155fd"), } + al, gas, vmErr, err = ec.CreateAccessList(context.Background(), msg) if err != nil { t.Fatalf("unexpected error: %v", err) } + if vmErr == "" { t.Fatalf("wanted vmErr, got none") } + if gas == 21000 { t.Fatalf("unexpected gas used: %v", gas) } + if len(*al) != 1 || al.StorageKeys() != 1 { t.Fatalf("unexpected length of accesslist: %v", len(*al)) } @@ -211,6 +225,7 @@ func testAccessList(t *testing.T, client *rpc.Client) { if (*al)[0].Address == common.HexToAddress("0x0") { t.Fatalf("unexpected address: %v", (*al)[0].Address) } + if (*al)[0].StorageKeys[0] != common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000081") { t.Fatalf("unexpected storage key: %v", (*al)[0].StorageKeys[0]) } @@ -219,10 +234,12 @@ func testAccessList(t *testing.T, client *rpc.Client) { func testGetProof(t *testing.T, client *rpc.Client) { ec := New(client) ethcl := ethclient.NewClient(client) + result, err := ec.GetProof(context.Background(), testAddr, []string{testSlot.String()}, nil) if err != nil { t.Fatal(err) } + if !bytes.Equal(result.Address[:], testAddr[:]) { t.Fatalf("unexpected address, want: %v got: %v", testAddr, result.Address) } @@ -255,6 +272,7 @@ func testGetProof(t *testing.T, client *rpc.Client) { func testGCStats(t *testing.T, client *rpc.Client) { ec := New(client) + _, err := ec.GCStats(context.Background()) if err != nil { t.Fatal(err) @@ -263,10 +281,12 @@ func testGCStats(t *testing.T, client *rpc.Client) { func testMemStats(t *testing.T, client *rpc.Client) { ec := New(client) + stats, err := ec.MemStats(context.Background()) if err != nil { t.Fatal(err) } + if stats.Alloc == 0 { t.Fatal("Invalid mem stats retrieved") } @@ -274,6 +294,7 @@ func testMemStats(t *testing.T, client *rpc.Client) { func testGetNodeInfo(t *testing.T, client *rpc.Client) { ec := New(client) + info, err := ec.GetNodeInfo(context.Background()) if err != nil { t.Fatal(err) @@ -286,6 +307,7 @@ func testGetNodeInfo(t *testing.T, client *rpc.Client) { func testSetHead(t *testing.T, client *rpc.Client) { ec := New(client) + err := ec.SetHead(context.Background(), big.NewInt(0)) if err != nil { t.Fatal(err) @@ -306,10 +328,12 @@ func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) { // Create transaction tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) signer := types.LatestSignerForChainID(chainID) + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) if err != nil { t.Fatal(err) } + signedTx, err := tx.WithSignature(signer, signature) if err != nil { t.Fatal(err) @@ -384,6 +408,7 @@ func testCallContract(t *testing.T, client *rpc.Client) { } mapAcc := make(map[common.Address]OverrideAccount) mapAcc[testAddr] = override + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/ethclient/signer.go b/ethclient/signer.go index f827d4eb56..be502f9220 100644 --- a/ethclient/signer.go +++ b/ethclient/signer.go @@ -48,6 +48,7 @@ func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) if s.addr == (common.Address{}) { return common.Address{}, errNotCached } + return s.addr, nil } diff --git a/ethdb/batch.go b/ethdb/batch.go index 541f40c838..3e442f7267 100644 --- a/ethdb/batch.go +++ b/ethdb/batch.go @@ -62,6 +62,7 @@ func (b HookedBatch) Put(key []byte, value []byte) error { if b.OnPut != nil { b.OnPut(key, value) } + return b.Batch.Put(key, value) } @@ -70,5 +71,6 @@ func (b HookedBatch) Delete(key []byte) error { if b.OnDelete != nil { b.OnDelete(key) } + return b.Batch.Delete(key) } diff --git a/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go index 911c53c562..1bfa8f00a0 100644 --- a/ethdb/dbtest/testsuite.go +++ b/ethdb/dbtest/testsuite.go @@ -111,20 +111,26 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { t.Errorf("test %d: prefix=%q more items than expected: checking idx=%d (key %q), expecting len=%d", i, tt.prefix, idx, it.Key(), len(tt.order)) break } + if !bytes.Equal(it.Key(), []byte(tt.order[idx])) { t.Errorf("test %d: item %d: key mismatch: have %s, want %s", i, idx, string(it.Key()), tt.order[idx]) } + if !bytes.Equal(it.Value(), []byte(tt.content[tt.order[idx]])) { t.Errorf("test %d: item %d: value mismatch: have %s, want %s", i, idx, string(it.Value()), tt.content[tt.order[idx]]) } + idx++ } + if err := it.Error(); err != nil { t.Errorf("test %d: iteration failed: %v", i, err) } + if idx != len(tt.order) { t.Errorf("test %d: iteration terminated prematurely: have %d, want %d", i, idx, len(tt.order)) } + db.Close() } }) @@ -145,9 +151,11 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { { it := db.NewIterator(nil, nil) got, want := iterateKeys(it), keys + if err := it.Error(); err != nil { t.Fatal(err) } + if !reflect.DeepEqual(got, want) { t.Errorf("Iterator: got: %s; want: %s", got, want) } @@ -156,9 +164,11 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { { it := db.NewIterator([]byte("1"), nil) got, want := iterateKeys(it), []string{"1", "10", "11", "12"} + if err := it.Error(); err != nil { t.Fatal(err) } + if !reflect.DeepEqual(got, want) { t.Errorf("IteratorWith(1,nil): got: %s; want: %s", got, want) } @@ -167,9 +177,11 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { { it := db.NewIterator([]byte("5"), nil) got, want := iterateKeys(it), []string{} + if err := it.Error(); err != nil { t.Fatal(err) } + if !reflect.DeepEqual(got, want) { t.Errorf("IteratorWith(5,nil): got: %s; want: %s", got, want) } @@ -178,9 +190,11 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { { it := db.NewIterator(nil, []byte("2")) got, want := iterateKeys(it), []string{"2", "20", "21", "22", "3", "4", "6"} + if err := it.Error(); err != nil { t.Fatal(err) } + if !reflect.DeepEqual(got, want) { t.Errorf("IteratorWith(nil,2): got: %s; want: %s", got, want) } @@ -189,9 +203,11 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { { it := db.NewIterator(nil, []byte("5")) got, want := iterateKeys(it), []string{"6"} + if err := it.Error(); err != nil { t.Fatal(err) } + if !reflect.DeepEqual(got, want) { t.Errorf("IteratorWith(nil,5): got: %s; want: %s", got, want) } @@ -293,6 +309,7 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { want := []string{"1", "2", "3", "4"} b := db.NewBatch() + for _, k := range want { if err := b.Put([]byte(k), nil); err != nil { t.Fatal(err) @@ -324,15 +341,18 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { for k, v := range initial { db.Put([]byte(k), []byte(v)) } + snapshot, err := db.NewSnapshot() if err != nil { t.Fatal(err) } + for k, v := range initial { got, err := snapshot.Get([]byte(k)) if err != nil { t.Fatal(err) } + if !bytes.Equal(got, []byte(v)) { t.Fatalf("Unexpected value want: %v, got %v", v, got) } @@ -345,30 +365,37 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { insert = map[string]string{"k5": "v5-b"} delete = map[string]string{"k2": ""} ) + for k, v := range update { db.Put([]byte(k), []byte(v)) } + for k, v := range insert { db.Put([]byte(k), []byte(v)) } + for k := range delete { db.Delete([]byte(k)) } + for k, v := range initial { got, err := snapshot.Get([]byte(k)) if err != nil { t.Fatal(err) } + if !bytes.Equal(got, []byte(v)) { t.Fatalf("Unexpected value want: %v, got %v", v, got) } } + for k := range insert { got, err := snapshot.Get([]byte(k)) if err == nil || len(got) != 0 { t.Fatal("Unexpected value") } } + for k := range delete { got, err := snapshot.Get([]byte(k)) if err != nil || len(got) == 0 { @@ -402,6 +429,7 @@ func BenchDatabaseSuite(b *testing.B, New func() ethdb.KeyValueStore) { _ = db.Put(keys[i], vals[i]) } } + b.Run("WriteSorted", func(b *testing.B) { benchWrite(b, sKeys, sVals) }) @@ -426,6 +454,7 @@ func BenchDatabaseSuite(b *testing.B, New func() ethdb.KeyValueStore) { _, _ = db.Get(keys[i]) } } + b.Run("ReadSorted", func(b *testing.B) { benchRead(b, sKeys, sVals) }) @@ -451,6 +480,7 @@ func BenchDatabaseSuite(b *testing.B, New func() ethdb.KeyValueStore) { } it.Release() } + b.Run("IterationSorted", func(b *testing.B) { benchIteration(b, sKeys, sVals) }) @@ -472,8 +502,10 @@ func BenchDatabaseSuite(b *testing.B, New func() ethdb.KeyValueStore) { for i := 0; i < len(keys); i++ { _ = batch.Put(keys[i], vals[i]) } + _ = batch.Write() } + b.Run("BenchWriteSorted", func(b *testing.B) { benchBatchWrite(b, sKeys, sVals) }) @@ -490,6 +522,7 @@ func iterateKeys(it ethdb.Iterator) []string { } sort.Strings(keys) it.Release() + return keys } diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index ce13659d9d..6c81f15f75 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -91,6 +91,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( if cache < minCache { cache = minCache } + if handles < minHandles { handles = minHandles } @@ -98,6 +99,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( options.OpenFilesCacheCapacity = handles options.BlockCacheCapacity = cache / 2 * opt.MiB options.WriteBuffer = cache / 4 * opt.MiB // Two of these are used internally + if readonly { options.ReadOnly = true } @@ -111,10 +113,12 @@ func NewCustom(file string, namespace string, customize func(options *opt.Option options := configureOptions(customize) logger := log.New("database", file) 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 @@ -122,6 +126,7 @@ func NewCustom(file string, namespace string, customize func(options *opt.Option if _, corrupted := err.(*errors.ErrCorrupted); corrupted { db, err = leveldb.RecoverFile(file, nil) } + if err != nil { return nil, err } @@ -148,6 +153,7 @@ func NewCustom(file string, namespace string, customize func(options *opt.Option // Start up the metrics gathering and return go ldb.meter(metricsGatheringInterval) + return ldb, nil } @@ -162,6 +168,7 @@ func configureOptions(customizeFn func(*opt.Options)) *opt.Options { if customizeFn != nil { customizeFn(options) } + return options } @@ -174,11 +181,14 @@ func (db *Database) Close() error { if db.quitChan != nil { errc := make(chan error) db.quitChan <- errc + if err := <-errc; err != nil { db.log.Error("Metrics collection failed", "err", err) } + db.quitChan = nil } + return db.db.Close() } @@ -193,6 +203,7 @@ func (db *Database) Get(key []byte) ([]byte, error) { if err != nil { return nil, err } + return dat, nil } @@ -240,6 +251,7 @@ func (db *Database) NewSnapshot() (ethdb.Snapshot, error) { if err != nil { return nil, err } + return &snapshot{db: snap}, nil } @@ -312,6 +324,7 @@ func (db *Database) meter(refresh time.Duration) { if err != nil { db.log.Error("Failed to read database stats", "err", err) merr = err + continue } // Find the compaction table, skip the header @@ -319,29 +332,37 @@ func (db *Database) meter(refresh time.Duration) { for len(lines) > 0 && strings.TrimSpace(lines[0]) != "Compactions" { lines = lines[1:] } + if len(lines) <= 3 { db.log.Error("Compaction leveldbTable not found") + merr = errors.New("compaction leveldbTable not found") + continue } + lines = lines[3:] // Iterate over all the leveldbTable rows, and accumulate the entries for j := 0; j < len(compactions[i%2]); j++ { compactions[i%2][j] = 0 } + for _, line := range lines { parts := strings.Split(line, "|") if len(parts) != 6 { break } + for idx, counter := range parts[2:] { value, err := strconv.ParseFloat(strings.TrimSpace(counter), 64) if err != nil { db.log.Error("Compaction entry parsing failed", "err", err) merr = err + continue } + compactions[i%2][idx] += value } } @@ -349,12 +370,15 @@ func (db *Database) meter(refresh time.Duration) { if db.diskSizeGauge != nil { db.diskSizeGauge.Update(int64(compactions[i%2][0] * 1024 * 1024)) } + if db.compTimeMeter != nil { db.compTimeMeter.Mark(int64((compactions[i%2][1] - compactions[(i-1)%2][1]) * 1000 * 1000 * 1000)) } + if db.compReadMeter != nil { db.compReadMeter.Mark(int64((compactions[i%2][2] - compactions[(i-1)%2][2]) * 1024 * 1024)) } + if db.compWriteMeter != nil { db.compWriteMeter.Mark(int64((compactions[i%2][3] - compactions[(i-1)%2][3]) * 1024 * 1024)) } @@ -363,28 +387,37 @@ func (db *Database) meter(refresh time.Duration) { if err != nil { db.log.Error("Failed to read database write delay statistic", "err", err) merr = err + continue } + var ( delayN int64 delayDuration string duration time.Duration paused bool ) + if n, err := fmt.Sscanf(writedelay, "DelayN:%d Delay:%s Paused:%t", &delayN, &delayDuration, &paused); n != 3 || err != nil { db.log.Error("Write delay statistic not found") + merr = err + continue } + duration, err = time.ParseDuration(delayDuration) if err != nil { db.log.Error("Failed to parse delay duration", "err", err) merr = err + continue } + if db.writeDelayNMeter != nil { db.writeDelayNMeter.Mark(delayN - delaystats[0]) } + if db.writeDelayMeter != nil { db.writeDelayMeter.Mark(duration.Nanoseconds() - delaystats[1]) } @@ -393,8 +426,10 @@ func (db *Database) meter(refresh time.Duration) { if paused && delayN-delaystats[0] == 0 && duration.Nanoseconds()-delaystats[1] == 0 && time.Now().After(lastWritePaused.Add(degradationWarnInterval)) { db.log.Warn("Database compacting, degraded performance") + lastWritePaused = time.Now() } + delaystats[0], delaystats[1] = delayN, duration.Nanoseconds() // Retrieve the database iostats. @@ -402,37 +437,51 @@ func (db *Database) meter(refresh time.Duration) { if err != nil { db.log.Error("Failed to read database iostats", "err", err) merr = err + continue } + var nRead, nWrite float64 + parts := strings.Split(ioStats, " ") if len(parts) < 2 { db.log.Error("Bad syntax of ioStats", "ioStats", ioStats) merr = fmt.Errorf("bad syntax of ioStats %s", ioStats) + continue } + if n, err := fmt.Sscanf(parts[0], "Read(MB):%f", &nRead); n != 1 || err != nil { db.log.Error("Bad syntax of read entry", "entry", parts[0]) + merr = err + continue } + if n, err := fmt.Sscanf(parts[1], "Write(MB):%f", &nWrite); n != 1 || err != nil { db.log.Error("Bad syntax of write entry", "entry", parts[1]) + merr = err + continue } + if db.diskReadMeter != nil { db.diskReadMeter.Mark(int64((nRead - iostats[0]) * 1024 * 1024)) } + if db.diskWriteMeter != nil { db.diskWriteMeter.Mark(int64((nWrite - iostats[1]) * 1024 * 1024)) } + iostats[0], iostats[1] = nRead, nWrite compCount, err := db.db.GetProperty("leveldb.compcount") if err != nil { db.log.Error("Failed to read database iostats", "err", err) merr = err + continue } @@ -442,11 +491,15 @@ func (db *Database) meter(refresh time.Duration) { nonLevel0Comp uint32 seekComp uint32 ) + if n, err := fmt.Sscanf(compCount, "MemComp:%d Level0Comp:%d NonLevel0Comp:%d SeekComp:%d", &memComp, &level0Comp, &nonLevel0Comp, &seekComp); n != 4 || err != nil { db.log.Error("Compaction count statistic not found") + merr = err + continue } + db.memCompGauge.Update(int64(memComp)) db.level0CompGauge.Update(int64(level0Comp)) db.nonlevel0CompGauge.Update(int64(nonLevel0Comp)) @@ -480,6 +533,7 @@ type batch struct { func (b *batch) Put(key, value []byte) error { b.b.Put(key, value) b.size += len(key) + len(value) + return nil } @@ -487,6 +541,7 @@ func (b *batch) Put(key, value []byte) error { func (b *batch) Delete(key []byte) error { b.b.Delete(key) b.size += len(key) + return nil } @@ -523,6 +578,7 @@ func (r *replayer) Put(key, value []byte) { if r.failure != nil { return } + r.failure = r.writer.Put(key, value) } @@ -532,6 +588,7 @@ func (r *replayer) Delete(key []byte) { if r.failure != nil { return } + r.failure = r.writer.Delete(key) } @@ -541,6 +598,7 @@ func (r *replayer) Delete(key []byte) { func bytesPrefixRange(prefix, start []byte) *util.Range { r := util.BytesPrefix(prefix) r.Start = append(r.Start, start...) + return r } diff --git a/ethdb/leveldb/leveldb_test.go b/ethdb/leveldb/leveldb_test.go index d8c6386016..e02fbf0e2c 100644 --- a/ethdb/leveldb/leveldb_test.go +++ b/ethdb/leveldb/leveldb_test.go @@ -32,6 +32,7 @@ func TestLevelDB(t *testing.T) { if err != nil { t.Fatal(err) } + return &Database{ db: db, } @@ -45,6 +46,7 @@ func BenchmarkLevelDB(b *testing.B) { if err != nil { b.Fatal(err) } + return &Database{ db: db, } diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index 08799425c6..76a8da4f9f 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -72,6 +72,7 @@ func (db *Database) Close() error { defer db.lock.Unlock() db.db = nil + return nil } @@ -83,7 +84,9 @@ func (db *Database) Has(key []byte) (bool, error) { if db.db == nil { return false, errMemorydbClosed } + _, ok := db.db[string(key)] + return ok, nil } @@ -95,9 +98,11 @@ func (db *Database) Get(key []byte) ([]byte, error) { if db.db == nil { return nil, errMemorydbClosed } + if entry, ok := db.db[string(key)]; ok { return common.CopyBytes(entry), nil } + return nil, errMemorydbNotFound } @@ -109,7 +114,9 @@ func (db *Database) Put(key []byte, value []byte) error { if db.db == nil { return errMemorydbClosed } + db.db[string(key)] = common.CopyBytes(value) + return nil } @@ -121,7 +128,9 @@ func (db *Database) Delete(key []byte) error { if db.db == nil { return errMemorydbClosed } + delete(db.db, string(key)) + return nil } @@ -159,15 +168,18 @@ func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { if !strings.HasPrefix(key, pr) { continue } + if key >= st { keys = append(keys, key) } } // Sort the items and retrieve the associated values sort.Strings(keys) + for _, key := range keys { values = append(values, db.db[key]) } + return &iterator{ index: -1, keys: keys, @@ -224,6 +236,7 @@ type batch struct { func (b *batch) Put(key, value []byte) error { b.writes = append(b.writes, keyvalue{common.CopyBytes(key), common.CopyBytes(value), false}) b.size += len(key) + len(value) + return nil } @@ -231,6 +244,7 @@ func (b *batch) Put(key, value []byte) error { func (b *batch) Delete(key []byte) error { b.writes = append(b.writes, keyvalue{common.CopyBytes(key), nil, true}) b.size += len(key) + return nil } @@ -249,8 +263,10 @@ func (b *batch) Write() error { delete(b.db.db, string(keyvalue.key)) continue } + b.db.db[string(keyvalue.key)] = keyvalue.value } + return nil } @@ -267,12 +283,15 @@ func (b *batch) Replay(w ethdb.KeyValueWriter) error { if err := w.Delete(keyvalue.key); err != nil { return err } + continue } + if err := w.Put(keyvalue.key, keyvalue.value); err != nil { return err } } + return nil } @@ -350,6 +369,7 @@ func newSnapshot(db *Database) *snapshot { for key, val := range db.db { copied[key] = common.CopyBytes(val) } + return &snapshot{db: copied} } @@ -362,7 +382,9 @@ func (snap *snapshot) Has(key []byte) (bool, error) { if snap.db == nil { return false, errSnapshotReleased } + _, ok := snap.db[string(key)] + return ok, nil } @@ -375,9 +397,11 @@ func (snap *snapshot) Get(key []byte) ([]byte, error) { if snap.db == nil { return nil, errSnapshotReleased } + if entry, ok := snap.db[string(key)]; ok { return common.CopyBytes(entry), nil } + return nil, errMemorydbNotFound } diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index 0e53506672..895b695fe3 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -98,6 +98,7 @@ func (d *Database) onCompactionBegin(info pebble.CompactionInfo) { } else { d.nonLevel0Comp.Add(1) } + d.activeComp++ } @@ -107,6 +108,7 @@ func (d *Database) onCompactionEnd(info pebble.CompactionInfo) { } else if d.activeComp == 0 { panic("should not happen") } + d.activeComp-- } diff --git a/ethdb/pebble/pebble_test.go b/ethdb/pebble/pebble_test.go index 785dea17bc..120cf09ac7 100644 --- a/ethdb/pebble/pebble_test.go +++ b/ethdb/pebble/pebble_test.go @@ -40,6 +40,7 @@ func TestPebbleDB(t *testing.T) { if err != nil { t.Fatal(err) } + return &Database{ db: db, } @@ -55,6 +56,7 @@ func BenchmarkPebbleDB(b *testing.B) { if err != nil { b.Fatal(err) } + return &Database{ db: db, } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index f92004486f..28e7ad603f 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -183,12 +183,14 @@ func parseEthstatsURL(url string) (parts []string, err error) { if hostIndex == -1 || hostIndex == len(url)-1 { return nil, err } + preHost, host := url[:hostIndex], url[hostIndex+1:] passIndex := strings.LastIndex(preHost, ":") if passIndex == -1 { return []string{preHost, "", host}, nil } + nodename, pass := preHost[:passIndex], "" if passIndex != len(preHost)-1 { pass = preHost[passIndex+1:] @@ -203,6 +205,7 @@ func New(node *node.Node, backend backend, engine consensus.Engine, url string) if err != nil { return err } + ethstats := &Service{ backend: backend, engine: engine, @@ -215,6 +218,7 @@ func New(node *node.Node, backend backend, engine consensus.Engine, url string) } node.RegisterLifecycle(ethstats) + return nil } @@ -227,9 +231,11 @@ func (s *Service) Start() error { s.txSub = s.backend.SubscribeNewTxsEvent(txEventCh) chain2HeadCh := make(chan core.Chain2HeadEvent, chain2HeadChanSize) s.chain2headSub = s.backend.SubscribeChain2HeadEvent(chain2HeadCh) + go s.loop(chainHeadCh, chain2HeadCh, txEventCh) log.Info("Stats daemon started") + return nil } @@ -238,6 +244,7 @@ func (s *Service) Stop() error { s.headSub.Unsubscribe() s.txSub.Unsubscribe() log.Info("Stats daemon stopped") + return nil } @@ -251,6 +258,7 @@ func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, chain2HeadCh chan c txCh = make(chan struct{}, 1) head2Ch = make(chan core.Chain2HeadEvent, 100) ) + go func() { var lastTx mclock.AbsTime @@ -315,20 +323,25 @@ func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, chain2HeadCh chan c conn *connWrapper err error ) + dialer := websocket.Dialer{HandshakeTimeout: 5 * time.Second} header := make(http.Header) header.Set("origin", "http://localhost") + for _, url := range urls { c, _, e := dialer.Dial(url, header) err = e + if err == nil { conn = newConnectionWrapper(c) break } } + if err != nil { log.Warn("Stats server unreachable", "err", err) errTimer.Reset(10 * time.Second) + continue } // Authenticate the client with the server @@ -336,8 +349,10 @@ func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, chain2HeadCh chan c log.Warn("Stats login failed", "err", err) conn.Close() errTimer.Reset(10 * time.Second) + continue } + go s.readLoop(conn) // Send the initial stats so our node looks decent from the get go @@ -345,6 +360,7 @@ func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, chain2HeadCh chan c log.Warn("Initial stats report failed", "err", err) conn.Close() errTimer.Reset(0) + continue } // Keep sending status updates until the connection breaks @@ -356,6 +372,7 @@ func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, chain2HeadCh chan c fullReport.Stop() // Make sure the connection is closed conn.Close() + return case <-fullReport.C: @@ -370,6 +387,7 @@ func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, chain2HeadCh chan c if err = s.reportBlock(conn, head); err != nil { log.Warn("Block stats report failed", "err", err) } + if err = s.reportPending(conn); err != nil { log.Warn("Post-block transaction stats report failed", "err", err) } @@ -416,6 +434,7 @@ func (s *Service) readLoop(conn *connWrapper) { log.Warn("Failed to respond to system ping message", "err", err) return } + continue } // Not a system ping, try to decode an actual state message @@ -424,11 +443,14 @@ func (s *Service) readLoop(conn *connWrapper) { log.Warn("Failed to decode stats server message", "err", err) return } + log.Trace("Received message from stats server", "msg", msg) + if len(msg["emit"]) == 0 { log.Warn("Stats server sent non-broadcast", "msg", msg) return } + command, ok := msg["emit"][0].(string) if !ok { log.Warn("Invalid stats server message type", "type", msg["emit"][0]) @@ -456,8 +478,10 @@ func (s *Service) readLoop(conn *connWrapper) { case s.histCh <- nil: // Treat it as an no indexes request default: } + continue } + list, ok := request["list"].([]interface{}) if !ok { log.Warn("Invalid stats history block list", "list", request["list"]) @@ -465,12 +489,14 @@ func (s *Service) readLoop(conn *connWrapper) { } // Convert the block number list to an integer list numbers := make([]uint64, len(list)) + for i, num := range list { n, ok := num.(float64) if !ok { log.Warn("Invalid stats history block number", "number", num) return } + numbers[i] = uint64(n) } select { @@ -516,12 +542,14 @@ func (s *Service) login(conn *connWrapper) error { 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.(*ethproto.NodeInfo).Network) } else { network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network) } + auth := &authMsg{ ID: s.node, Info: nodeInfo{ @@ -539,6 +567,7 @@ func (s *Service) login(conn *connWrapper) error { }, Secret: s.pass, } + login := map[string][]interface{}{ "emit": {"hello", auth}, } @@ -550,6 +579,7 @@ func (s *Service) login(conn *connWrapper) error { if err := conn.ReadJSON(&ack); err != nil || len(ack["emit"]) != 1 || ack["emit"][0] != "ready" { return errors.New("unauthorized") } + return nil } @@ -560,15 +590,19 @@ func (s *Service) report(conn *connWrapper) error { if err := s.reportLatency(conn); err != nil { return err } + if err := s.reportBlock(conn, nil); err != nil { return err } + if err := s.reportPending(conn); err != nil { return err } + if err := s.reportStats(conn); err != nil { return err } + return nil } @@ -595,6 +629,7 @@ func (s *Service) reportLatency(conn *connWrapper) error { // Ping timeout, abort return errors.New("ping timed out") } + latency := strconv.Itoa(int((time.Since(start) / time.Duration(2)).Nanoseconds() / 1000000)) // Send back the measured latency @@ -606,6 +641,7 @@ func (s *Service) reportLatency(conn *connWrapper) error { "latency": latency, }}, } + return conn.WriteJSON(stats) } @@ -639,6 +675,7 @@ func (s uncleStats) MarshalJSON() ([]byte, error) { if uncles := ([]*types.Header)(s); len(uncles) > 0 { return json.Marshal(uncles) } + return []byte("[]"), nil } @@ -657,6 +694,7 @@ func (s *Service) reportBlock(conn *connWrapper, block *types.Block) error { report := map[string][]interface{}{ "emit": {"block", stats}, } + return conn.WriteJSON(report) } @@ -677,6 +715,7 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { if block == nil { block = fullBackend.CurrentBlock() } + header = block.Header() td = fullBackend.GetTd(context.Background(), header.Hash()) @@ -684,6 +723,7 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { for i, tx := range block.Transactions() { txs[i].Hash = tx.Hash() } + uncles = block.Uncles() } else { // Light nodes would need on-demand lookups for transactions/uncles, skip @@ -692,6 +732,7 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { } else { header = s.backend.CurrentHeader() } + td = s.backend.GetTd(context.Background(), header.Hash()) txs = []txStats{} } @@ -727,16 +768,19 @@ func (s *Service) reportHistory(conn *connWrapper, list []uint64) error { } else { // No indexes requested, send back the top ones head := s.backend.CurrentHeader().Number.Int64() + start := head - historyUpdateRange + 1 if start < 0 { start = 0 } + for i := uint64(start); i <= uint64(head); i++ { indexes = append(indexes, i) } } // Gather the batch of blocks to report history := make([]*blockStats, len(indexes)) + for i, number := range indexes { fullBackend, ok := s.backend.(fullNodeBackend) // Retrieve the next block if it's known to us @@ -755,6 +799,7 @@ func (s *Service) reportHistory(conn *connWrapper, list []uint64) error { } // Ran out of blocks, cut the report short and send history = history[len(history)-i:] + break } // Assemble the history report and send it to the server @@ -763,6 +808,7 @@ func (s *Service) reportHistory(conn *connWrapper, list []uint64) error { } else { log.Trace("No history to send to stats server") } + stats := map[string]interface{}{ "id": s.node, "history": history, @@ -770,6 +816,7 @@ func (s *Service) reportHistory(conn *connWrapper, list []uint64) error { report := map[string][]interface{}{ "emit": {"history", stats}, } + return conn.WriteJSON(report) } @@ -795,6 +842,7 @@ func (s *Service) reportPending(conn *connWrapper) error { report := map[string][]interface{}{ "emit": {"pending", stats}, } + return conn.WriteJSON(report) } @@ -810,6 +858,7 @@ func createStub(b *types.Block) *blockStub { ParentHash: b.ParentHash().String(), Number: b.NumberU64(), } + return s } @@ -827,6 +876,7 @@ func (s *Service) reportChain2Head(conn *connWrapper, chain2HeadData *core.Chain for _, block := range chain2HeadData.NewChain { chainHeadEvent.NewChain = append(chainHeadEvent.NewChain, createStub(block)) } + for _, block := range chain2HeadData.OldChain { chainHeadEvent.OldChain = append(chainHeadEvent.OldChain, createStub(block)) } @@ -838,6 +888,7 @@ func (s *Service) reportChain2Head(conn *connWrapper, chain2HeadData *core.Chain report := map[string][]interface{}{ "emit": {"headEvent", stats}, } + return conn.WriteJSON(report) } @@ -872,6 +923,7 @@ func (s *Service) reportStats(conn *connWrapper) error { syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock price, _ := fullBackend.SuggestGasTipCap(context.Background()) + gasprice = int(price.Uint64()) if basefee := fullBackend.CurrentHeader().BaseFee; basefee != nil { gasprice += int(basefee.Uint64()) @@ -898,5 +950,6 @@ func (s *Service) reportStats(conn *connWrapper) error { report := map[string][]interface{}{ "emit": {"stats", stats}, } + return conn.WriteJSON(report) } diff --git a/ethstats/ethstats_test.go b/ethstats/ethstats_test.go index 60322f7654..2237470fca 100644 --- a/ethstats/ethstats_test.go +++ b/ethstats/ethstats_test.go @@ -61,6 +61,7 @@ func TestParseEthstatsURL(t *testing.T) { if err != nil { t.Fatal(err) } + node, pass, host := parts[0], parts[1], parts[2] // unquote because the value provided will be used as a CLI flag value, so unescaped quotes will be removed @@ -72,9 +73,11 @@ func TestParseEthstatsURL(t *testing.T) { if node != c.node { t.Errorf("case=%d mismatch node value, got: %v ,want: %v", i, node, c.node) } + if pass != c.pass { t.Errorf("case=%d mismatch pass value, got: %v ,want: %v", i, pass, c.pass) } + if host != c.host { t.Errorf("case=%d mismatch host value, got: %v ,want: %v", i, host, c.host) } diff --git a/event/event.go b/event/event.go index ce1b03d523..785fddd73b 100644 --- a/event/event.go +++ b/event/event.go @@ -54,6 +54,7 @@ func (mux *TypeMux) Subscribe(types ...interface{}) *TypeMuxSubscription { sub := newsub(mux) mux.mutex.Lock() defer mux.mutex.Unlock() + if mux.stopped { // set the status to closed so that calling Unsubscribe after this // call will short circuit. @@ -63,18 +64,22 @@ func (mux *TypeMux) Subscribe(types ...interface{}) *TypeMuxSubscription { if mux.subm == nil { mux.subm = make(map[reflect.Type][]*TypeMuxSubscription) } + for _, t := range types { rtyp := reflect.TypeOf(t) oldsubs := mux.subm[rtyp] + if find(oldsubs, sub) != -1 { panic(fmt.Sprintf("event: duplicate type %s in Subscribe", rtyp)) } + subs := make([]*TypeMuxSubscription, len(oldsubs)+1) copy(subs, oldsubs) subs[len(oldsubs)] = sub mux.subm[rtyp] = subs } } + return sub } @@ -86,16 +91,20 @@ func (mux *TypeMux) Post(ev interface{}) error { Data: ev, } rtyp := reflect.TypeOf(ev) + mux.mutex.RLock() if mux.stopped { mux.mutex.RUnlock() return ErrMuxClosed } + subs := mux.subm[rtyp] mux.mutex.RUnlock() + for _, sub := range subs { sub.deliver(event) } + return nil } @@ -105,11 +114,13 @@ func (mux *TypeMux) Post(ev interface{}) error { func (mux *TypeMux) Stop() { mux.mutex.Lock() defer mux.mutex.Unlock() + for _, subs := range mux.subm { for _, sub := range subs { sub.closewait() } } + mux.subm = nil mux.stopped = true } @@ -117,6 +128,7 @@ func (mux *TypeMux) Stop() { func (mux *TypeMux) del(s *TypeMuxSubscription) { mux.mutex.Lock() defer mux.mutex.Unlock() + for typ, subs := range mux.subm { if pos := find(subs, s); pos >= 0 { if len(subs) == 1 { @@ -134,6 +146,7 @@ func find(slice []*TypeMuxSubscription, item *TypeMuxSubscription) int { return i } } + return -1 } @@ -141,6 +154,7 @@ func posdelete(slice []*TypeMuxSubscription, pos int) []*TypeMuxSubscription { news := make([]*TypeMuxSubscription, len(slice)-1) copy(news[:pos], slice[:pos]) copy(news[pos:], slice[pos+1:]) + return news } @@ -162,6 +176,7 @@ type TypeMuxSubscription struct { func newsub(mux *TypeMux) *TypeMuxSubscription { c := make(chan *TypeMuxEvent) + return &TypeMuxSubscription{ mux: mux, created: time.Now(), @@ -183,15 +198,18 @@ func (s *TypeMuxSubscription) Unsubscribe() { func (s *TypeMuxSubscription) Closed() bool { s.closeMu.Lock() defer s.closeMu.Unlock() + return s.closed } func (s *TypeMuxSubscription) closewait() { s.closeMu.Lock() defer s.closeMu.Unlock() + if s.closed { return } + close(s.closing) s.closed = true diff --git a/event/event_test.go b/event/event_test.go index 84b37eca3b..0774400529 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -28,6 +28,7 @@ type testEvent int func TestSubCloseUnsub(t *testing.T) { // the point of this test is **not** to panic var mux TypeMux + mux.Stop() sub := mux.Subscribe(0) sub.Unsubscribe() @@ -38,11 +39,13 @@ func TestSub(t *testing.T) { defer mux.Stop() sub := mux.Subscribe(testEvent(0)) + go func() { if err := mux.Post(testEvent(5)); err != nil { t.Errorf("Post returned unexpected error: %v", err) } }() + ev := <-sub.Chan() if ev.Data.(testEvent) != testEvent(5) { @@ -59,6 +62,7 @@ func TestMuxErrorAfterStop(t *testing.T) { if _, isopen := <-sub.Chan(); isopen { t.Errorf("subscription channel was not closed") } + if err := mux.Post(testEvent(0)); err != ErrMuxClosed { t.Errorf("Post error mismatch, got: %s, expected: %s", err, ErrMuxClosed) } @@ -70,6 +74,7 @@ func TestUnsubscribeUnblockPost(t *testing.T) { sub := mux.Subscribe(testEvent(0)) unblocked := make(chan bool) + go func() { mux.Post(testEvent(5)) unblocked <- true @@ -114,6 +119,7 @@ func TestMuxConcurrent(t *testing.T) { } sub := func(i int) { time.Sleep(time.Duration(rand.Intn(99)) * time.Millisecond) + sub := mux.Subscribe(testEvent(0)) <-sub.Chan() sub.Unsubscribe() @@ -123,6 +129,7 @@ func TestMuxConcurrent(t *testing.T) { go poster() go poster() go poster() + nsubs := 1000 for i := 0; i < nsubs; i++ { go sub(i) @@ -133,6 +140,7 @@ func TestMuxConcurrent(t *testing.T) { for i := 0; i < nsubs; i++ { counts[<-recv]++ } + for i, count := range counts { if count != 1 { t.Errorf("receiver %d called %d times, expected only 1 call", i, count) @@ -154,14 +162,19 @@ func BenchmarkPost1000(b *testing.B) { subscribed, done sync.WaitGroup nsubs = 1000 ) + subscribed.Add(nsubs) done.Add(nsubs) + for i := 0; i < nsubs; i++ { go func() { s := mux.Subscribe(testEvent(0)) + subscribed.Done() + for range s.Chan() { } + done.Done() }() } @@ -169,6 +182,7 @@ func BenchmarkPost1000(b *testing.B) { // The actual benchmark. b.ResetTimer() + for i := 0; i < b.N; i++ { mux.Post(testEvent(0)) } @@ -186,13 +200,16 @@ func BenchmarkPostConcurrent(b *testing.B) { emptySubscriber(mux) var wg sync.WaitGroup + poster := func() { for i := 0; i < b.N; i++ { mux.Post(testEvent(0)) } wg.Done() } + wg.Add(5) + for i := 0; i < 5; i++ { go poster() } @@ -203,7 +220,9 @@ func BenchmarkPostConcurrent(b *testing.B) { func BenchmarkChanSend(b *testing.B) { c := make(chan interface{}) defer close(c) + closed := make(chan struct{}) + go func() { for range c { } diff --git a/event/example_feed_test.go b/event/example_feed_test.go index 9b5ad50df5..e0d76680bb 100644 --- a/event/example_feed_test.go +++ b/event/example_feed_test.go @@ -26,6 +26,7 @@ func ExampleFeed_acknowledgedEvents() { // This example shows how the return value of Send can be used for request/reply // interaction between event consumers and producers. var feed event.Feed + type ackedEvent struct { i int ack chan<- struct{} @@ -34,11 +35,14 @@ func ExampleFeed_acknowledgedEvents() { // Consumers wait for events on the feed and acknowledge processing. done := make(chan struct{}) defer close(done) + for i := 0; i < 3; i++ { ch := make(chan ackedEvent, 100) sub := feed.Subscribe(ch) + go func() { defer sub.Unsubscribe() + for { select { case ev := <-ch: @@ -56,6 +60,7 @@ func ExampleFeed_acknowledgedEvents() { for i := 0; i < 3; i++ { acksignal := make(chan struct{}) n := feed.Send(ackedEvent{i, acksignal}) + for ack := 0; ack < n; ack++ { <-acksignal } diff --git a/event/example_scope_test.go b/event/example_scope_test.go index 825a8deeac..17314e2d1e 100644 --- a/event/example_scope_test.go +++ b/event/example_scope_test.go @@ -34,12 +34,14 @@ type mulServer struct{ results event.Feed } func (s *divServer) do(a, b int) int { r := a / b s.results.Send(r) + return r } func (s *mulServer) do(a, b int) int { r := a * b s.results.Send(r) + return r } @@ -93,12 +95,15 @@ func ExampleSubscriptionScope() { // Run a subscriber in the background. divsub := app.SubscribeResults('/', divs) mulsub := app.SubscribeResults('*', muls) + wg.Add(1) + go func() { defer wg.Done() defer fmt.Println("subscriber exited") defer divsub.Unsubscribe() defer mulsub.Unsubscribe() + for { select { case result := <-divs: diff --git a/event/example_subscription_test.go b/event/example_subscription_test.go index 5c76b55d98..a3e3a20860 100644 --- a/event/example_subscription_test.go +++ b/event/example_subscription_test.go @@ -34,6 +34,7 @@ func ExampleNewSubscription() { return nil } } + return nil }) @@ -41,6 +42,7 @@ func ExampleNewSubscription() { // Note that Unsubscribe waits until the producer has shut down. for i := range ch { fmt.Println(i) + if i == 4 { sub.Unsubscribe() break diff --git a/event/example_test.go b/event/example_test.go index 29938e8539..d11c3b5272 100644 --- a/event/example_test.go +++ b/event/example_test.go @@ -20,7 +20,9 @@ import "fmt" func ExampleTypeMux() { type someEvent struct{ I int } + type otherEvent struct{ S string } + type yetAnotherEvent struct{ X, Y int } var mux TypeMux @@ -28,10 +30,12 @@ func ExampleTypeMux() { // Start a subscriber. done := make(chan struct{}) sub := mux.Subscribe(someEvent{}, otherEvent{}) + go func() { for event := range sub.Chan() { fmt.Printf("Received: %#v\n", event.Data) } + fmt.Println("done") close(done) }() diff --git a/event/feed.go b/event/feed.go index 33dafe5886..21ba2fca85 100644 --- a/event/feed.go +++ b/event/feed.go @@ -73,14 +73,17 @@ func (f *Feed) Subscribe(channel interface{}) Subscription { f.once.Do(f.init) chanval := reflect.ValueOf(channel) + chantyp := chanval.Type() if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 { panic(errBadChannel) } + sub := &feedSub{feed: f, channel: chanval, err: make(chan error, 1)} f.mu.Lock() defer f.mu.Unlock() + if !f.typecheck(chantyp.Elem()) { panic(feedTypeError{op: "Subscribe", got: chantyp, want: reflect.ChanOf(reflect.SendDir, f.etype)}) } @@ -88,6 +91,7 @@ func (f *Feed) Subscribe(channel interface{}) Subscription { // The next Send will add it to f.sendCases. cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval} f.inbox = append(f.inbox, cas) + return sub } @@ -97,6 +101,7 @@ func (f *Feed) typecheck(typ reflect.Type) bool { f.etype = typ return true } + return f.etype == typ } @@ -104,11 +109,14 @@ func (f *Feed) remove(sub *feedSub) { // Delete from inbox first, which covers channels // that have not been added to f.sendCases yet. ch := sub.channel.Interface() + f.mu.Lock() + index := f.inbox.find(ch) if index != -1 { f.inbox = f.inbox.delete(index) f.mu.Unlock() + return } f.mu.Unlock() @@ -152,6 +160,7 @@ func (f *Feed) Send(value interface{}) (nsent int) { // of sendCases. When a send succeeds, the corresponding case moves to the end of // 'cases' and it shrinks by one element. cases := f.sendCases + for { // Fast path: try sending without blocking before adding to the select set. // This should usually succeed if subscribers are fast enough and have free @@ -163,6 +172,7 @@ func (f *Feed) Send(value interface{}) (nsent int) { i-- } } + if len(cases) == firstSubSendCase { break } @@ -171,6 +181,7 @@ func (f *Feed) Send(value interface{}) (nsent int) { if chosen == 0 /* <-f.removeSub */ { index := f.sendCases.find(recv.Interface()) f.sendCases = f.sendCases.delete(index) + if index >= 0 && index < len(cases) { // Shrink 'cases' too because the removed case was still active. cases = f.sendCases[:len(cases)-1] @@ -186,6 +197,7 @@ func (f *Feed) Send(value interface{}) (nsent int) { f.sendCases[i].Send = reflect.Value{} } f.sendLock <- struct{}{} + return nsent } @@ -216,6 +228,7 @@ func (cs caseList) find(channel interface{}) int { return i } } + return -1 } @@ -228,6 +241,7 @@ func (cs caseList) delete(index int) caseList { func (cs caseList) deactivate(index int) caseList { last := len(cs) - 1 cs[index], cs[last] = cs[last], cs[index] + return cs[:last] } diff --git a/event/feed_test.go b/event/feed_test.go index cdf29fdd73..d47d2825fc 100644 --- a/event/feed_test.go +++ b/event/feed_test.go @@ -27,7 +27,9 @@ import ( func TestFeedPanics(t *testing.T) { { var f Feed + 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) @@ -35,8 +37,10 @@ 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(0)} if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil { t.Error(err) @@ -44,7 +48,9 @@ func TestFeedPanics(t *testing.T) { } { var f Feed + 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) @@ -52,12 +58,14 @@ func TestFeedPanics(t *testing.T) { } { var f Feed + if err := checkPanic(errBadChannel, func() { f.Subscribe(make(<-chan int)) }); err != nil { t.Error(err) } } { var f Feed + if err := checkPanic(errBadChannel, func() { f.Subscribe(0) }); err != nil { t.Error(err) } @@ -74,17 +82,21 @@ func checkPanic(want error, fn func()) (err error) { } }() fn() + return nil } func TestFeed(t *testing.T) { var feed Feed + var done, subscribed sync.WaitGroup + subscriber := func(i int) { defer done.Done() subchan := make(chan int) sub := feed.Subscribe(subchan) + timeout := time.NewTimer(2 * time.Second) defer timeout.Stop() subscribed.Done() @@ -110,18 +122,23 @@ func TestFeed(t *testing.T) { } const n = 1000 + done.Add(n) subscribed.Add(n) + for i := 0; i < n; i++ { go subscriber(i) } subscribed.Wait() + if nsent := feed.Send(1); nsent != n { t.Errorf("first send delivered %d times, want %d", nsent, n) } + if nsent := feed.Send(2); nsent != 0 { t.Errorf("second send delivered %d times, want 0", nsent) } + done.Wait() } @@ -134,10 +151,12 @@ func TestFeedSubscribeSameChannel(t *testing.T) { sub2 = feed.Subscribe(ch) _ = feed.Subscribe(ch) ) + expectSends := func(value, n int) { if nsent := feed.Send(value); nsent != n { t.Errorf("send delivered %d times, want %d", nsent, n) } + done.Done() } expectRecv := func(wantValue, n int) { @@ -149,6 +168,7 @@ func TestFeedSubscribeSameChannel(t *testing.T) { } done.Add(1) + go expectSends(1, 3) expectRecv(1, 3) done.Wait() @@ -156,6 +176,7 @@ func TestFeedSubscribeSameChannel(t *testing.T) { sub1.Unsubscribe() done.Add(1) + go expectSends(2, 2) expectRecv(2, 2) done.Wait() @@ -163,6 +184,7 @@ func TestFeedSubscribeSameChannel(t *testing.T) { sub2.Unsubscribe() done.Add(1) + go expectSends(3, 1) expectRecv(3, 1) done.Wait() @@ -176,10 +198,12 @@ func TestFeedSubscribeBlockedPost(t *testing.T) { ch2 = make(chan int) wg sync.WaitGroup ) + defer wg.Wait() feed.Subscribe(ch1) wg.Add(nsends) + for i := 0; i < nsends; i++ { go func() { feed.Send(99) @@ -211,12 +235,14 @@ func TestFeedUnsubscribeBlockedPost(t *testing.T) { bsub = feed.Subscribe(bchan) wg sync.WaitGroup ) + for i := range chans { chans[i] = make(chan int, nsends) } // Queue up some Sends. None of these can make progress while bchan isn't read. wg.Add(nsends) + for i := 0; i < nsends; i++ { go func() { feed.Send(99) @@ -247,9 +273,11 @@ func TestFeedUnsubscribeSentChan(t *testing.T) { sub2 = feed.Subscribe(ch2) wg sync.WaitGroup ) + defer sub2.Unsubscribe() wg.Add(1) + go func() { feed.Send(0) wg.Done() @@ -267,6 +295,7 @@ func TestFeedUnsubscribeSentChan(t *testing.T) { // Send again. This should send to ch2 only, so the wait group will unblock // as soon as a value is received on ch2. wg.Add(1) + go func() { feed.Send(0) wg.Done() @@ -284,9 +313,11 @@ func TestFeedUnsubscribeFromInbox(t *testing.T) { sub2 = feed.Subscribe(ch1) sub3 = feed.Subscribe(ch2) ) + if len(feed.inbox) != 3 { t.Errorf("inbox length != 3 after subscribe") } + if len(feed.sendCases) != 1 { t.Errorf("sendCases is non-empty after unsubscribe") } @@ -294,9 +325,11 @@ func TestFeedUnsubscribeFromInbox(t *testing.T) { sub1.Unsubscribe() sub2.Unsubscribe() sub3.Unsubscribe() + if len(feed.inbox) != 0 { t.Errorf("inbox is non-empty after unsubscribe") } + if len(feed.sendCases) != 1 { t.Errorf("sendCases is non-empty after unsubscribe") } @@ -308,21 +341,26 @@ func BenchmarkFeedSend1000(b *testing.B) { feed Feed nsubs = 1000 ) + subscriber := func(ch <-chan int) { for i := 0; i < b.N; i++ { <-ch } done.Done() } + done.Add(nsubs) + for i := 0; i < nsubs; i++ { ch := make(chan int, 200) feed.Subscribe(ch) + go subscriber(ch) } // The actual benchmark. b.ResetTimer() + for i := 0; i < b.N; i++ { if feed.Send(i) != nsubs { panic("wrong number of sends") diff --git a/event/subscription.go b/event/subscription.go index 6c62874719..7cb6d1d925 100644 --- a/event/subscription.go +++ b/event/subscription.go @@ -53,13 +53,16 @@ func NewSubscription(producer func(<-chan struct{}) error) Subscription { err := producer(s.unsub) s.mu.Lock() defer s.mu.Unlock() + if !s.unsubscribed { if err != nil { s.err <- err } + s.unsubscribed = true } }() + return s } @@ -76,6 +79,7 @@ func (s *funcSub) Unsubscribe() { s.mu.Unlock() return } + s.unsubscribed = true close(s.unsub) s.mu.Unlock() @@ -123,6 +127,7 @@ func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscriptio unsub: make(chan struct{}), } go s.loop() + return s } @@ -154,12 +159,14 @@ func (s *resubscribeSub) Err() <-chan error { func (s *resubscribeSub) loop() { defer close(s.err) + var done bool for !done { sub := s.subscribe() if sub == nil { break } + done = s.waitForError(sub) sub.Unsubscribe() } @@ -167,10 +174,13 @@ func (s *resubscribeSub) loop() { func (s *resubscribeSub) subscribe() Subscription { subscribed := make(chan error) + var sub Subscription + for { s.lastTry = mclock.Now() ctx, cancel := context.WithCancel(context.Background()) + go func() { rsub, err := s.fn(ctx, s.lastSubErr) sub = rsub @@ -179,10 +189,12 @@ func (s *resubscribeSub) subscribe() Subscription { select { case err := <-subscribed: cancel() + if err == nil { if sub == nil { panic("event: ResubscribeFunc returned nil subscription and no error") } + return sub } // Subscribing failed, wait before launching the next try. @@ -192,6 +204,7 @@ func (s *resubscribeSub) subscribe() Subscription { case <-s.unsub: cancel() <-subscribed // avoid leaking the s.fn goroutine. + return nil } } @@ -252,14 +265,18 @@ type scopeSub struct { func (sc *SubscriptionScope) Track(s Subscription) Subscription { sc.mu.Lock() defer sc.mu.Unlock() + if sc.closed { return nil } + if sc.subs == nil { sc.subs = make(map[*scopeSub]struct{}) } + ss := &scopeSub{sc, s} sc.subs[ss] = struct{}{} + return ss } @@ -268,13 +285,16 @@ func (sc *SubscriptionScope) Track(s Subscription) Subscription { func (sc *SubscriptionScope) Close() { sc.mu.Lock() defer sc.mu.Unlock() + if sc.closed { return } + sc.closed = true for s := range sc.subs { s.s.Unsubscribe() } + sc.subs = nil } @@ -283,6 +303,7 @@ func (sc *SubscriptionScope) Close() { func (sc *SubscriptionScope) Count() int { sc.mu.Lock() defer sc.mu.Unlock() + return len(sc.subs) } diff --git a/event/subscription_test.go b/event/subscription_test.go index ba081705c4..9d792c3633 100644 --- a/event/subscription_test.go +++ b/event/subscription_test.go @@ -39,6 +39,7 @@ func subscribeInts(max, fail int, c chan<- int) Subscription { return nil } } + return nil }) } @@ -71,6 +72,7 @@ loop: if err != nil { t.Fatal("got non-nil error after Unsubscribe") } + if ok { t.Fatal("channel still open after Unsubscribe") } @@ -80,6 +82,7 @@ func TestResubscribe(t *testing.T) { t.Parallel() var i int + nfails := 6 sub := Resubscribe(100*time.Millisecond, func(ctx context.Context) (Subscription, error) { // fmt.Printf("call #%d @ %v\n", i, time.Now()) @@ -88,14 +91,18 @@ func TestResubscribe(t *testing.T) { // Delay the second failure a bit to reset the resubscribe interval. time.Sleep(200 * time.Millisecond) } + if i < nfails { return nil, errors.New("oops") } + sub := NewSubscription(func(unsubscribed <-chan struct{}) error { return nil }) + return sub, nil }) <-sub.Err() + if i != nfails { t.Fatalf("resubscribe function called %d times, want %d times", i, nfails) } @@ -112,10 +119,12 @@ func TestResubscribeAbort(t *testing.T) { case <-time.After(2 * time.Second): done <- errors.New("context given to resubscribe function not canceled within 2s") } + return nil, nil }) sub.Unsubscribe() + if err := <-done; err != nil { t.Fatal(err) } @@ -125,14 +134,17 @@ 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 { @@ -141,10 +153,12 @@ func TestResubscribeWithErrorHandler(t *testing.T) { return nil } }) + return sub, nil }) <-sub.Err() + if i != nfails { t.Fatalf("resubscribe function called %d times, want %d times", i, nfails) } diff --git a/graphql/graphiql.go b/graphql/graphiql.go index 576a0cbe95..7663463f9a 100644 --- a/graphql/graphiql.go +++ b/graphql/graphiql.go @@ -44,6 +44,7 @@ func respond(w http.ResponseWriter, body []byte, code int) { func errorJSON(msg string) []byte { buf := bytes.Buffer{} fmt.Fprintf(&buf, `{"error": "%s"}`, msg) + return buf.Bytes() } @@ -52,6 +53,7 @@ func (h GraphiQL) ServeHTTP(w http.ResponseWriter, r *http.Request) { respond(w, errorJSON("only GET requests are supported"), http.StatusMethodNotAllowed) return } + w.Header().Set("Content-Type", "text/html") w.Write(graphiql) } diff --git a/graphql/graphql.go b/graphql/graphql.go index f1a9a9440d..1929164548 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -62,6 +62,7 @@ func (b *Long) UnmarshalGraphQL(input interface{}) error { //} else { value, err := strconv.ParseInt(input, 10, 64) *b = Long(value) + return err //} case int32: @@ -73,6 +74,7 @@ func (b *Long) UnmarshalGraphQL(input interface{}) error { default: err = fmt.Errorf("unexpected type %T for Long", input) } + return err } @@ -98,10 +100,12 @@ func (a *Account) Balance(ctx context.Context) (hexutil.Big, error) { if err != nil { return hexutil.Big{}, err } + balance := state.GetBalance(a.address) if balance == nil { return hexutil.Big{}, fmt.Errorf("failed to load balance %x", a.address) } + return hexutil.Big(*balance), nil } @@ -112,12 +116,15 @@ func (a *Account) TransactionCount(ctx context.Context) (hexutil.Uint64, error) if err != nil { return 0, err } + return hexutil.Uint64(nonce), nil } + state, err := a.getState(ctx) if err != nil { return 0, err } + return hexutil.Uint64(state.GetNonce(a.address)), nil } @@ -126,6 +133,7 @@ func (a *Account) Code(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } + return state.GetCode(a.address), nil } @@ -134,6 +142,7 @@ func (a *Account) Storage(ctx context.Context, args struct{ Slot common.Hash }) if err != nil { return common.Hash{}, err } + return state.GetState(a.address, args.Slot), nil } @@ -232,6 +241,7 @@ func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) { if err != nil || tx == nil { return hexutil.Bytes{}, err } + return tx.Data(), nil } @@ -240,6 +250,7 @@ func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) { if err != nil || tx == nil { return 0, err } + return hexutil.Uint64(tx.Gas()), nil } @@ -248,6 +259,7 @@ func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) { if err != nil || tx == nil { return hexutil.Big{}, err } + switch tx.Type() { case types.AccessListTxType: return hexutil.Big(*tx.GasPrice()), nil @@ -258,6 +270,7 @@ func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) { return (hexutil.Big)(*math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee.ToInt()), tx.GasFeeCap())), nil } } + return hexutil.Big(*tx.GasPrice()), nil default: return hexutil.Big(*tx.GasPrice()), nil @@ -278,9 +291,11 @@ func (t *Transaction) EffectiveGasPrice(ctx context.Context) (*hexutil.Big, erro if err != nil || header == nil { return nil, err } + if header.BaseFee == nil { return (*hexutil.Big)(tx.GasPrice()), nil } + return (*hexutil.Big)(math.BigMin(new(big.Int).Add(tx.GasTipCap(), header.BaseFee), tx.GasFeeCap())), nil } @@ -289,6 +304,7 @@ func (t *Transaction) MaxFeePerGas(ctx context.Context) (*hexutil.Big, error) { if err != nil || tx == nil { return nil, err } + switch tx.Type() { case types.AccessListTxType: return nil, nil @@ -304,6 +320,7 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, e if err != nil || tx == nil { return nil, err } + switch tx.Type() { case types.AccessListTxType: return nil, nil @@ -328,6 +345,7 @@ func (t *Transaction) EffectiveTip(ctx context.Context) (*hexutil.Big, error) { if err != nil || header == nil { return nil, err } + if header.BaseFee == nil { return (*hexutil.Big)(tx.GasPrice()), nil } @@ -336,6 +354,7 @@ func (t *Transaction) EffectiveTip(ctx context.Context) (*hexutil.Big, error) { if err != nil { return nil, err } + return (*hexutil.Big)(tip), nil } @@ -344,9 +363,11 @@ func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) { if err != nil || tx == nil { return hexutil.Big{}, err } + if tx.Value() == nil { return hexutil.Big{}, fmt.Errorf("invalid transaction value %x", t.hash) } + return hexutil.Big(*tx.Value()), nil } @@ -355,6 +376,7 @@ func (t *Transaction) Nonce(ctx context.Context) (hexutil.Uint64, error) { if err != nil || tx == nil { return 0, err } + return hexutil.Uint64(tx.Nonce()), nil } @@ -363,10 +385,12 @@ func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) (*Account, e if err != nil || tx == nil { return nil, err } + to := tx.To() if to == nil { return nil, nil } + return &Account{ r: t.r, address: *to, @@ -382,6 +406,7 @@ func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, signer := types.LatestSigner(t.r.backend.ChainConfig()) from, _ := types.Sender(signer, tx) + return &Account{ r: t.r, address: from, @@ -407,7 +432,9 @@ func (t *Transaction) Index(ctx context.Context) (*int32, error) { if block == nil { return nil, nil } + index := int32(t.index) + return &index, nil } @@ -426,6 +453,7 @@ func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) { if err != nil { return nil, err } + return receipts[t.index], nil } @@ -434,10 +462,13 @@ func (t *Transaction) Status(ctx context.Context) (*Long, error) { if err != nil || receipt == nil { return nil, err } + if len(receipt.PostState) != 0 { return nil, nil } + ret := Long(receipt.Status) + return &ret, nil } @@ -446,7 +477,9 @@ func (t *Transaction) GasUsed(ctx context.Context) (*Long, error) { if err != nil || receipt == nil { return nil, err } + ret := Long(receipt.GasUsed) + return &ret, nil } @@ -455,7 +488,9 @@ func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*Long, error) { if err != nil || receipt == nil { return nil, err } + ret := Long(receipt.CumulativeGasUsed) + return &ret, nil } @@ -464,6 +499,7 @@ func (t *Transaction) CreatedContract(ctx context.Context, args BlockNumberArgs) if err != nil || receipt == nil || receipt.ContractAddress == (common.Address{}) { return nil, err } + return &Account{ r: t.r, address: receipt.ContractAddress, @@ -503,6 +539,7 @@ func (t *Transaction) getLogs(ctx context.Context, hash common.Hash) (*[]*Log, e if err != nil { return nil, err } + var ret []*Log // Select tx logs from all block logs ix := sort.Search(len(logs), func(i int) bool { return uint64(logs[i].TxIndex) >= t.index }) @@ -514,6 +551,7 @@ func (t *Transaction) getLogs(ctx context.Context, hash common.Hash) (*[]*Log, e }) ix++ } + return &ret, nil } @@ -522,7 +560,9 @@ func (t *Transaction) Type(ctx context.Context) (*int32, error) { if err != nil { return nil, err } + txType := int32(tx.Type()) + return &txType, nil } @@ -531,14 +571,17 @@ func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) { if err != nil || tx == nil { return nil, err } + accessList := tx.AccessList() ret := make([]*AccessTuple, 0, len(accessList)) + for _, al := range accessList { ret = append(ret, &AccessTuple{ address: al.Address, storageKeys: al.StorageKeys, }) } + return &ret, nil } @@ -547,7 +590,9 @@ func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) { if err != nil || tx == nil { return hexutil.Big{}, err } + _, r, _ := tx.RawSignatureValues() + return hexutil.Big(*r), nil } @@ -556,7 +601,9 @@ func (t *Transaction) S(ctx context.Context) (hexutil.Big, error) { if err != nil || tx == nil { return hexutil.Big{}, err } + _, _, s := tx.RawSignatureValues() + return hexutil.Big(*s), nil } @@ -565,7 +612,9 @@ func (t *Transaction) V(ctx context.Context) (hexutil.Big, error) { if err != nil || tx == nil { return hexutil.Big{}, err } + v, _, _ := tx.RawSignatureValues() + return hexutil.Big(*v), nil } @@ -608,13 +657,16 @@ type Block struct { func (b *Block) resolve(ctx context.Context) (*types.Block, error) { b.mu.Lock() defer b.mu.Unlock() + if b.block != nil { return b.block, nil } + if b.numberOrHash == nil { latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) b.numberOrHash = &latest } + var err error b.block, err = b.r.backend.BlockByNumberOrHash(ctx, *b.numberOrHash) @@ -624,6 +676,7 @@ func (b *Block) resolve(ctx context.Context) (*types.Block, error) { b.header = b.block.Header() } } + return b.block, err } @@ -637,9 +690,11 @@ func (b *Block) resolveHeader(ctx context.Context) (*types.Header, error) { if b.header != nil { return b.header, nil } + if b.numberOrHash == nil && b.hash == (common.Hash{}) { return nil, errBlockInvariant } + var err error b.header, err = b.r.backend.HeaderByNumberOrHash(ctx, *b.numberOrHash) @@ -687,6 +742,7 @@ func (b *Block) Number(ctx context.Context) (Long, error) { func (b *Block) Hash(ctx context.Context) (common.Hash, error) { b.mu.Lock() defer b.mu.Unlock() + return b.hash, nil } @@ -695,6 +751,7 @@ func (b *Block) GasLimit(ctx context.Context) (Long, error) { if err != nil { return 0, err } + return Long(header.GasLimit), nil } @@ -703,6 +760,7 @@ func (b *Block) GasUsed(ctx context.Context) (Long, error) { if err != nil { return 0, err } + return Long(header.GasUsed), nil } @@ -711,9 +769,11 @@ func (b *Block) BaseFeePerGas(ctx context.Context) (*hexutil.Big, error) { if err != nil { return nil, err } + if header.BaseFee == nil { return nil, nil } + return (*hexutil.Big)(header.BaseFee), nil } @@ -730,7 +790,9 @@ func (b *Block) NextBaseFeePerGas(ctx context.Context) (*hexutil.Big, error) { return nil, nil } } + nextBaseFee := misc.CalcBaseFee(chaincfg, header) + return (*hexutil.Big)(nextBaseFee), nil } @@ -738,6 +800,7 @@ func (b *Block) Parent(ctx context.Context) (*Block, error) { if _, err := b.resolveHeader(ctx); err != nil { return nil, err } + if b.header == nil || b.header.Number.Uint64() < 1 { return nil, nil } @@ -750,6 +813,7 @@ func (b *Block) Parent(ctx context.Context) (*Block, error) { BlockHash: &hash, } ) + return &Block{ r: b.r, numberOrHash: &numOrHash, @@ -762,6 +826,7 @@ func (b *Block) Difficulty(ctx context.Context) (hexutil.Big, error) { if err != nil { return hexutil.Big{}, err } + return hexutil.Big(*header.Difficulty), nil } @@ -770,6 +835,7 @@ func (b *Block) Timestamp(ctx context.Context) (hexutil.Uint64, error) { if err != nil { return 0, err } + return hexutil.Uint64(header.Time), nil } @@ -778,6 +844,7 @@ func (b *Block) Nonce(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } + return header.Nonce[:], nil } @@ -786,6 +853,7 @@ func (b *Block) MixHash(ctx context.Context) (common.Hash, error) { if err != nil { return common.Hash{}, err } + return header.MixDigest, nil } @@ -794,6 +862,7 @@ func (b *Block) TransactionsRoot(ctx context.Context) (common.Hash, error) { if err != nil { return common.Hash{}, err } + return header.TxHash, nil } @@ -802,6 +871,7 @@ func (b *Block) StateRoot(ctx context.Context) (common.Hash, error) { if err != nil { return common.Hash{}, err } + return header.Root, nil } @@ -810,6 +880,7 @@ func (b *Block) ReceiptsRoot(ctx context.Context) (common.Hash, error) { if err != nil { return common.Hash{}, err } + return header.ReceiptHash, nil } @@ -818,6 +889,7 @@ func (b *Block) OmmerHash(ctx context.Context) (common.Hash, error) { if err != nil { return common.Hash{}, err } + return header.UncleHash, nil } @@ -826,7 +898,9 @@ func (b *Block) OmmerCount(ctx context.Context) (*int32, error) { if err != nil || block == nil { return nil, err } + count := int32(len(block.Uncles())) + return &count, err } @@ -835,7 +909,9 @@ func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) { if err != nil || block == nil { return nil, err } + ret := make([]*Block, 0, len(block.Uncles())) + for _, uncle := range block.Uncles() { blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false) ret = append(ret, &Block{ @@ -845,6 +921,7 @@ func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) { hash: uncle.Hash(), }) } + return &ret, nil } @@ -853,6 +930,7 @@ func (b *Block) ExtraData(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } + return header.Extra, nil } @@ -861,6 +939,7 @@ func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } + return header.Bloom.Bytes(), nil } @@ -874,6 +953,7 @@ func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) { if td == nil { return hexutil.Big{}, fmt.Errorf("total difficulty not found %x", hash) } + return hexutil.Big(*td), nil } @@ -910,6 +990,7 @@ func (a BlockNumberArgs) NumberOr(current rpc.BlockNumberOrHash) rpc.BlockNumber blockNr := rpc.BlockNumber(*a.Block) return rpc.BlockNumberOrHashWithNumber(blockNr) } + return current } @@ -924,6 +1005,7 @@ func (b *Block) Miner(ctx context.Context, args BlockNumberArgs) (*Account, erro if err != nil { return nil, err } + return &Account{ r: b.r, address: header.Coinbase, @@ -936,7 +1018,9 @@ func (b *Block) TransactionCount(ctx context.Context) (*int32, error) { if err != nil || block == nil { return nil, err } + count := int32(len(block.Transactions())) + return &count, err } @@ -945,6 +1029,7 @@ func (b *Block) Transactions(ctx context.Context) (*[]*Transaction, error) { if err != nil || block == nil { return nil, err } + ret := make([]*Transaction, 0, len(block.Transactions())) for i, tx := range block.Transactions() { ret = append(ret, &Transaction{ @@ -955,6 +1040,7 @@ func (b *Block) Transactions(ctx context.Context) (*[]*Transaction, error) { index: uint64(i), }) } + return &ret, nil } @@ -963,11 +1049,14 @@ func (b *Block) TransactionAt(ctx context.Context, args struct{ Index int32 }) ( if err != nil || block == nil { return nil, err } + txs := block.Transactions() if args.Index < 0 || int(args.Index) >= len(txs) { return nil, nil } + tx := txs[args.Index] + return &Transaction{ r: b.r, hash: tx.Hash(), @@ -982,12 +1071,15 @@ func (b *Block) OmmerAt(ctx context.Context, args struct{ Index int32 }) (*Block if err != nil || block == nil { return nil, err } + uncles := block.Uncles() if args.Index < 0 || int(args.Index) >= len(uncles) { return nil, nil } + uncle := uncles[args.Index] blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false) + return &Block{ r: b.r, numberOrHash: &blockNumberOrHash, @@ -1022,6 +1114,7 @@ func runFilter(ctx context.Context, r *Resolver, filter *filters.Filter) ([]*Log if err != nil || logs == nil { return nil, err } + ret := make([]*Log, 0, len(logs)) for _, log := range logs { ret = append(ret, &Log{ @@ -1030,6 +1123,7 @@ func runFilter(ctx context.Context, r *Resolver, filter *filters.Filter) ([]*Log log: log, }) } + return ret, nil } @@ -1038,6 +1132,7 @@ func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteri if args.Filter.Addresses != nil { addresses = *args.Filter.Addresses } + var topics [][]common.Hash if args.Filter.Topics != nil { topics = *args.Filter.Topics @@ -1103,6 +1198,7 @@ func (b *Block) Call(ctx context.Context, args struct { if err != nil { return nil, err } + status := Long(1) if result.Failed() { status = 0 @@ -1136,6 +1232,7 @@ func (p *Pending) Transactions(ctx context.Context) (*[]*Transaction, error) { if err != nil { return nil, err } + ret := make([]*Transaction, 0, len(txs)) for i, tx := range txs { ret = append(ret, &Transaction{ @@ -1145,6 +1242,7 @@ func (p *Pending) Transactions(ctx context.Context) (*[]*Transaction, error) { index: uint64(i), }) } + return &ret, nil } @@ -1152,6 +1250,7 @@ func (p *Pending) Account(ctx context.Context, args struct { Address common.Address }) *Account { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + return &Account{ r: p.r, address: args.Address, @@ -1163,10 +1262,12 @@ func (p *Pending) Call(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (*CallResult, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap()) if err != nil { return nil, err } + status := Long(1) if result.Failed() { status = 0 @@ -1184,6 +1285,7 @@ func (p *Pending) EstimateGas(ctx context.Context, args struct { }) (Long, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) gas, err := ethapi.DoEstimateGas(ctx, p.r.backend, args.Data, pendingBlockNr, p.r.backend.RPCGasCap()) + return Long(gas), err } @@ -1198,10 +1300,12 @@ func (r *Resolver) Block(ctx context.Context, args struct { Hash *common.Hash }) (*Block, error) { var numberOrHash rpc.BlockNumberOrHash + if args.Number != nil { if *args.Number < 0 { return nil, nil } + number := rpc.BlockNumber(*args.Number) numberOrHash = rpc.BlockNumberOrHashWithNumber(number) } else if args.Hash != nil { @@ -1223,6 +1327,7 @@ func (r *Resolver) Block(ctx context.Context, args struct { } else if h == nil { return nil, nil } + return block, nil } @@ -1238,10 +1343,13 @@ func (r *Resolver) Blocks(ctx context.Context, args struct { } else { to = rpc.BlockNumber(r.backend.CurrentBlock().Number.Int64()) } + if to < from { return []*Block{}, nil } + ret := make([]*Block, 0, to-from+1) + for i := from; i <= to; i++ { numberOrHash := rpc.BlockNumberOrHashWithNumber(i) block := &Block{ @@ -1258,8 +1366,10 @@ func (r *Resolver) Blocks(ctx context.Context, args struct { // Blocks after must be non-existent too, break. break } + ret = append(ret, block) } + return ret, nil } @@ -1279,6 +1389,7 @@ func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Has } else if t == nil { return nil, nil } + return tx, nil } @@ -1287,7 +1398,9 @@ func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hex if err := tx.UnmarshalBinary(args.Data); err != nil { return common.Hash{}, err } + hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx) + return hash, err } @@ -1317,14 +1430,17 @@ func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria if args.Filter.FromBlock != nil { begin = int64(*args.Filter.FromBlock) } + end := rpc.LatestBlockNumber.Int64() if args.Filter.ToBlock != nil { end = int64(*args.Filter.ToBlock) } + var addresses []common.Address if args.Filter.Addresses != nil { addresses = *args.Filter.Addresses } + var topics [][]common.Hash if args.Filter.Topics != nil { topics = *args.Filter.Topics @@ -1340,9 +1456,11 @@ func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) { if err != nil { return hexutil.Big{}, err } + if head := r.backend.CurrentHeader(); head.BaseFee != nil { tipcap.Add(tipcap, head.BaseFee) } + return (hexutil.Big)(*tipcap), nil } @@ -1351,6 +1469,7 @@ func (r *Resolver) MaxPriorityFeePerGas(ctx context.Context) (hexutil.Big, error if err != nil { return hexutil.Big{}, err } + return (hexutil.Big)(*tipcap), nil } diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 93c69904de..447fb01b97 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -47,11 +47,14 @@ func TestBuildSchema(t *testing.T) { // Copy config conf := node.DefaultConfig conf.DataDir = ddir + stack, err := node.New(&conf) defer stack.Close() + if err != nil { t.Fatalf("could not create new node: %v", err) } + defer stack.Close() // Make sure the schema can be parsed and matched up to the object model. if _, err := newHandler(stack, nil, nil, []string{}, []string{}); err != nil { @@ -160,12 +163,15 @@ func TestGraphQLBlockSerialization(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) resp.Body.Close() + 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) } @@ -203,6 +209,7 @@ func TestGraphQLBlockSerializationEIP2718(t *testing.T) { signer := types.LatestSigner(genesis.Config) newGQLService(t, stack, genesis, 1, func(i int, gen *core.BlockGen) { gen.SetCoinbase(common.Address{1}) + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ Nonce: uint64(0), To: &dad, @@ -248,12 +255,15 @@ func TestGraphQLBlockSerializationEIP2718(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) resp.Body.Close() + 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) } @@ -264,10 +274,13 @@ func TestGraphQLBlockSerializationEIP2718(t *testing.T) { func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { stack := createNode(t) defer stack.Close() + if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) } + body := strings.NewReader(`{"query": "{block{number}}","variables": null}`) + resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", body) if err != nil { t.Fatalf("could not post: %v", err) @@ -414,6 +427,7 @@ func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlock TrieTimeout: 60 * time.Minute, SnapshotCache: 5, } + ethBackend, err := eth.New(stack, ethConf) if err != nil { t.Fatalf("could not create eth backend: %v", err) @@ -421,12 +435,14 @@ func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlock // Create some blocks and import them chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(), ethash.NewFaker(), ethBackend.ChainDb(), genBlocks, genfunc) + _, err = ethBackend.BlockChain().InsertChain(chain) if err != nil { t.Fatalf("could not create import blocks: %v", err) } // Set up handler filterSystem := filters.NewFilterSystem(ethBackend.APIBackend, filters.Config{}) + handler, err := newHandler(stack, ethBackend.APIBackend, filterSystem, []string{}, []string{}) if err != nil { t.Fatalf("could not create graphql service: %v", err) diff --git a/graphql/service.go b/graphql/service.go index d9053a9507..c98de90de7 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -42,6 +42,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 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 @@ -67,6 +68,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { response := &graphql.Response{ Errors: []*gqlErrors.QueryError{{Message: "request timed out"}}, } + responseJSON, err := json.Marshal(response) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -81,6 +83,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("content-type", "application/json") w.Header().Set("content-length", strconv.Itoa(len(responseJSON))) w.Write(responseJSON) + if flush, ok := w.(http.Flusher); ok { flush.Flush() } @@ -97,9 +100,11 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 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) }) @@ -120,6 +125,7 @@ func newHandler(stack *node.Node, backend ethapi.Backend, filterSystem *filters. if err != nil { return nil, err } + h := handler{Schema: s} handler := node.NewHTTPHandlerStack(h, cors, vhosts, nil) diff --git a/internal/build/archive.go b/internal/build/archive.go index c16246070e..33b94ad102 100644 --- a/internal/build/archive.go +++ b/internal/build/archive.go @@ -59,18 +59,23 @@ func AddFile(a Archive, file string) error { if err != nil { return err } + defer fd.Close() + fi, err := fd.Stat() if err != nil { return err } + w, err := a.Header(fi) if err != nil { return err } + if _, err := io.Copy(w, fd); err != nil { return err } + return nil } @@ -88,20 +93,26 @@ func WriteArchive(name string, files []string) (err error) { os.Remove(name) } }() + archive, basename := NewArchive(archfd) if archive == nil { return fmt.Errorf("unknown archive extension") } + fmt.Println(name) + if err := archive.Directory(basename); err != nil { return err } + for _, file := range files { fmt.Println(" +", filepath.Base(file)) + if err := AddFile(archive, file); err != nil { return err } } + return archive.Close() } @@ -125,12 +136,15 @@ func (a *ZipArchive) Header(fi os.FileInfo) (io.Writer, error) { if err != nil { return nil, fmt.Errorf("can't make zip header: %v", err) } + head.Name = a.dir + head.Name head.Method = zip.Deflate + w, err := a.zipw.CreateHeader(head) if err != nil { return nil, fmt.Errorf("can't add zip header: %v", err) } + return w, nil } @@ -138,6 +152,7 @@ func (a *ZipArchive) Close() error { if err := a.zipw.Close(); err != nil { return err } + return a.file.Close() } @@ -151,11 +166,13 @@ type TarballArchive struct { func NewTarballArchive(w io.WriteCloser) Archive { gzw := gzip.NewWriter(w) tarw := tar.NewWriter(gzw) + return &TarballArchive{"", tarw, gzw, w} } func (a *TarballArchive) Directory(name string) error { a.dir = name + "/" + return a.tarw.WriteHeader(&tar.Header{ Name: a.dir, Mode: 0755, @@ -169,10 +186,12 @@ func (a *TarballArchive) Header(fi os.FileInfo) (io.Writer, error) { if err != nil { return nil, fmt.Errorf("can't make tar header: %v", err) } + head.Name = a.dir + head.Name if err := a.tarw.WriteHeader(head); err != nil { return nil, fmt.Errorf("can't add tar header: %v", err) } + return a.tarw, nil } @@ -180,9 +199,11 @@ func (a *TarballArchive) Close() error { if err := a.tarw.Close(); err != nil { return err } + if err := a.gzw.Close(); err != nil { return err } + return a.file.Close() } @@ -213,6 +234,7 @@ func extractTarball(ar io.Reader, dest string) error { defer gzr.Close() tr := tar.NewReader(gzr) + for { // Move to the next file header. header, err := tr.Next() @@ -220,12 +242,14 @@ func extractTarball(ar io.Reader, dest string) error { if err == io.EOF { return nil } + return err } // We only care about regular files, directory modes // and special file types are not supported. if header.Typeflag == tar.TypeReg { armode := header.FileInfo().Mode() + err := extractFile(header.Name, armode, tr, dest) if err != nil { return fmt.Errorf("extract %s: %v", header.Name, err) @@ -240,6 +264,7 @@ func extractZip(ar *os.File, dest string) error { if err != nil { return err } + zr, err := zip.NewReader(ar, info.Size()) if err != nil { return err @@ -254,12 +279,15 @@ func extractZip(ar *os.File, dest string) error { if err != nil { return err } + err = extractFile(zf.Name, zf.Mode(), data, dest) data.Close() + if err != nil { return fmt.Errorf("extract %s: %v", zf.Name, err) } } + return nil } @@ -281,10 +309,13 @@ func extractFile(arpath string, armode os.FileMode, data io.Reader, dest string) if err != nil { return err } + if _, err := io.Copy(file, data); err != nil { file.Close() os.Remove(target) + return err } + return file.Close() } diff --git a/internal/build/azure.go b/internal/build/azure.go index 9d1c4f300a..94564d9106 100644 --- a/internal/build/azure.go +++ b/internal/build/azure.go @@ -48,7 +48,9 @@ func AzureBlobstoreUpload(path string, name string, config AzureBlobstoreConfig) if err != nil { return err } + u := fmt.Sprintf("https://%s.blob.core.windows.net/%s", config.Account, config.Container) + container, err := azblob.NewContainerClientWithSharedKey(u, credential, nil) if err != nil { return err @@ -62,6 +64,7 @@ func AzureBlobstoreUpload(path string, name string, config AzureBlobstoreConfig) blockblob := container.NewBlockBlobClient(name) _, err = blockblob.Upload(context.Background(), in, nil) + return err } @@ -72,20 +75,26 @@ func AzureBlobstoreList(config AzureBlobstoreConfig) ([]*azblob.BlobItemInternal if err != nil { return nil, err } + u := fmt.Sprintf("https://%s.blob.core.windows.net/%s", config.Account, config.Container) + container, err := azblob.NewContainerClientWithSharedKey(u, credential, nil) if err != nil { return nil, err } + var maxResults int32 = 5000 pager := container.ListBlobsFlat(&azblob.ContainerListBlobFlatSegmentOptions{ Maxresults: &maxResults, }) + var allBlobs []*azblob.BlobItemInternal + for pager.NextPage(context.Background()) { res := pager.PageResponse() allBlobs = append(allBlobs, res.ContainerListBlobFlatSegmentResult.Segment.BlobItems...) } + return allBlobs, pager.Err() } @@ -96,6 +105,7 @@ func AzureBlobstoreDelete(config AzureBlobstoreConfig, blobs []*azblob.BlobItemI for _, blob := range blobs { fmt.Printf("would delete %s (%s) from %s/%s\n", *blob.Name, blob.Properties.LastModified, config.Account, config.Container) } + return nil } // Create an authenticated client against the Azure cloud @@ -103,7 +113,9 @@ func AzureBlobstoreDelete(config AzureBlobstoreConfig, blobs []*azblob.BlobItemI if err != nil { return err } + u := fmt.Sprintf("https://%s.blob.core.windows.net/%s", config.Account, config.Container) + container, err := azblob.NewContainerClientWithSharedKey(u, credential, nil) if err != nil { return err @@ -114,7 +126,9 @@ func AzureBlobstoreDelete(config AzureBlobstoreConfig, blobs []*azblob.BlobItemI if _, err := blockblob.Delete(context.Background(), &azblob.DeleteBlobOptions{}); err != nil { return err } + fmt.Printf("deleted %s (%s)\n", *blob.Name, blob.Properties.LastModified) } + return nil } diff --git a/internal/build/download.go b/internal/build/download.go index 903d0308df..d4605a9019 100644 --- a/internal/build/download.go +++ b/internal/build/download.go @@ -40,6 +40,7 @@ func MustLoadChecksums(file string) *ChecksumDB { if err != nil { log.Fatal("can't load checksum file: " + err.Error()) } + return &ChecksumDB{strings.Split(string(content), "\n")} } @@ -55,10 +56,12 @@ func (db *ChecksumDB) Verify(path string) error { if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil { return err } + fileHash := hex.EncodeToString(h.Sum(nil)) if !db.findHash(filepath.Base(path), fileHash) { return fmt.Errorf("invalid file hash %s for %s", fileHash, filepath.Base(path)) } + return nil } @@ -69,6 +72,7 @@ func (db *ChecksumDB) findHash(basename, hash string) bool { return true } } + return false } @@ -78,6 +82,7 @@ func (db *ChecksumDB) DownloadFile(url, dstPath string) error { fmt.Printf("%s is up-to-date\n", dstPath) return nil } + fmt.Printf("%s is stale\n", dstPath) fmt.Printf("downloading from %s\n", url) @@ -87,17 +92,22 @@ func (db *ChecksumDB) DownloadFile(url, dstPath string) error { } else if resp.StatusCode != http.StatusOK { return fmt.Errorf("download error: status %d", resp.StatusCode) } + defer resp.Body.Close() + if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil { return err } + fd, err := os.OpenFile(dstPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { return err } + dst := newDownloadWriter(fd, resp.ContentLength) _, err = io.Copy(dst, resp.Body) dst.Close() + if err != nil { return err } @@ -127,13 +137,16 @@ func (w *downloadWriter) Write(buf []byte) (int, error) { // Report progress. w.written += int64(n) pct := w.written * 10 / w.size * 10 + if pct != w.lastpct { if w.lastpct != 0 { fmt.Print("...") } + fmt.Print(pct, "%") w.lastpct = pct } + return n, err } @@ -141,10 +154,13 @@ func (w *downloadWriter) Close() error { if w.lastpct > 0 { fmt.Println() // Finish the progress line. } + flushErr := w.dstBuf.Flush() closeErr := w.file.Close() + if flushErr != nil { return flushErr } + return closeErr } diff --git a/internal/build/env.go b/internal/build/env.go index d70c0d50a4..0cd272f33c 100644 --- a/internal/build/env.go +++ b/internal/build/env.go @@ -61,6 +61,7 @@ func Env() Environment { if commit == "" { commit = os.Getenv("TRAVIS_COMMIT") } + return Environment{ CI: true, Name: "travis", @@ -78,6 +79,7 @@ func Env() Environment { if commit == "" { commit = os.Getenv("APPVEYOR_REPO_COMMIT") } + return Environment{ CI: true, Name: "appveyor", @@ -110,20 +112,25 @@ func LocalEnv() Environment { if commit := commitRe.FindString(head); commit != "" && env.Commit == "" { env.Commit = commit } + return env } + if env.Commit == "" { env.Commit = readGitFile(head) } + env.Date = getDate(env.Commit) if env.Branch == "" { if head != "HEAD" { env.Branch = strings.TrimPrefix(head, "refs/heads/") } } + if info, err := os.Stat(".git/objects"); err == nil && info.IsDir() && env.Tag == "" { env.Tag = firstLine(RunGit("tag", "-l", "--points-at", "HEAD")) } + return env } @@ -135,14 +142,17 @@ func getDate(commit string) string { if commit == "" { return "" } + out := RunGit("show", "-s", "--format=%ct", commit) if out == "" { return "" } + date, err := strconv.ParseInt(strings.TrimSpace(out), 10, 64) if err != nil { panic(fmt.Sprintf("failed to parse git commit date: %v", err)) } + return time.Unix(date, 0).Format("20060102") } @@ -150,23 +160,30 @@ func applyEnvFlags(env Environment) Environment { if !flag.Parsed() { panic("you need to call flag.Parse before Env or LocalEnv") } + if *GitCommitFlag != "" { env.Commit = *GitCommitFlag } + if *GitBranchFlag != "" { env.Branch = *GitBranchFlag } + if *GitTagFlag != "" { env.Tag = *GitTagFlag } + if *BuildnumFlag != "" { env.Buildnum = *BuildnumFlag } + if *PullRequestFlag { env.IsPullRequest = true } + if *CronJobFlag { env.IsCronJob = true } + return env } diff --git a/internal/build/gotool.go b/internal/build/gotool.go index 4a2a969c41..c808ecf20e 100644 --- a/internal/build/gotool.go +++ b/internal/build/gotool.go @@ -44,6 +44,7 @@ func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd { tool.Env = append(tool.Env, "CGO_ENABLED=1") tool.Env = append(tool.Env, "GOARCH="+g.GOARCH) } + if g.GOOS != "" && g.GOOS != runtime.GOOS { tool.Env = append(tool.Env, "GOOS="+g.GOOS) } @@ -53,6 +54,7 @@ func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd { } else if os.Getenv("CC") != "" { tool.Env = append(tool.Env, "CC="+os.Getenv("CC")) } + return tool } @@ -66,6 +68,7 @@ func (g *GoToolchain) Install(gobin string, args ...string) *exec.Cmd { if !filepath.IsAbs(gobin) { panic("GOBIN must be an absolute path") } + tool := g.goTool("install") tool.Env = append(tool.Env, "GOBIN="+gobin) tool.Args = append(tool.Args, "-mod=readonly") @@ -78,6 +81,7 @@ func (g *GoToolchain) Install(gobin string, args ...string) *exec.Cmd { pathTool := g.goTool("env", "GOPATH") output, _ := pathTool.Output() tool.Env = append(tool.Env, "GOPATH="+string(output)) + return tool } @@ -93,14 +97,17 @@ func (g *GoToolchain) goTool(command string, args ...string) *exec.Cmd { // Forward environment variables to the tool, but skip compiler target settings. // TODO: what about GOARM? skip := map[string]struct{}{"GOROOT": {}, "GOARCH": {}, "GOOS": {}, "GOBIN": {}, "CC": {}} + for _, e := range os.Environ() { if i := strings.IndexByte(e, '='); i >= 0 { if _, ok := skip[e[:i]]; ok { continue } } + tool.Env = append(tool.Env, e) } + return tool } @@ -122,18 +129,22 @@ func DownloadGo(csdb *ChecksumDB, version string) string { // For Arm architecture, GOARCH includes ISA version. os := runtime.GOOS + arch := runtime.GOARCH if arch == "arm" { arch = "armv6l" } + file := fmt.Sprintf("go%s.%s-%s", version, os, arch) if os == "windows" { file += ".zip" } else { file += ".tar.gz" } + url := "https://golang.org/dl/" + file dst := filepath.Join(ucache, file) + if err := csdb.DownloadFile(url, dst); err != nil { log.Fatal(err) } @@ -142,9 +153,11 @@ func DownloadGo(csdb *ChecksumDB, version string) string { if err := ExtractArchive(dst, godir); err != nil { log.Fatal(err) } + goroot, err := filepath.Abs(filepath.Join(godir, "go")) if err != nil { log.Fatal(err) } + return goroot } diff --git a/internal/build/pgp.go b/internal/build/pgp.go index c7d0d23397..781c31ff53 100644 --- a/internal/build/pgp.go +++ b/internal/build/pgp.go @@ -38,6 +38,7 @@ func PGPSignFile(input string, output string, pgpkey string) error { if err != nil { return err } + if len(keys) != 1 { return fmt.Errorf("key count mismatch: have %d, want %d", len(keys), 1) } @@ -64,8 +65,10 @@ func PGPKeyID(pgpkey string) (string, error) { if err != nil { return "", err } + if len(keys) != 1 { return "", fmt.Errorf("key count mismatch: have %d, want %d", len(keys), 1) } + return keys[0].PrimaryKey.KeyIdString(), nil } diff --git a/internal/build/util.go b/internal/build/util.go index c21e2e8f9c..6fdd4df01f 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -41,9 +41,11 @@ var DryRunFlag = flag.Bool("n", false, "dry run, don't execute commands") // any error. func MustRun(cmd *exec.Cmd) { fmt.Println(">>>", printArgs(cmd.Args)) + if !*DryRunFlag { cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { log.Fatal(err) } @@ -78,18 +80,24 @@ var warnedAboutGit bool // The command must complete successfully. func RunGit(args ...string) string { cmd := exec.Command("git", args...) + var stdout, stderr bytes.Buffer + cmd.Stdout, cmd.Stderr = &stdout, &stderr if err := cmd.Run(); err != nil { if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound { if !warnedAboutGit { log.Println("Warning: can't find 'git' in PATH") + warnedAboutGit = true } + return "" } + log.Fatal(strings.Join(cmd.Args, " "), ": ", err, "\n", stderr.String()) } + return strings.TrimSpace(stdout.String()) } @@ -99,6 +107,7 @@ func readGitFile(file string) string { if err != nil { return "" } + return strings.TrimSpace(string(content)) } @@ -118,13 +127,16 @@ func render(tpl *template.Template, outputFile string, outputPerm os.FileMode, x if err := os.MkdirAll(filepath.Dir(outputFile), 0755); err != nil { log.Fatal(err) } + out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_EXCL, outputPerm) if err != nil { log.Fatal(err) } + if err := tpl.Execute(out, x); err != nil { log.Fatal(err) } + if err := out.Close(); err != nil { log.Fatal(err) } @@ -136,11 +148,14 @@ func render(tpl *template.Template, outputFile string, outputPerm os.FileMode, x func UploadSFTP(identityFile, host, dir string, files []string) error { sftp := exec.Command("sftp") sftp.Stderr = os.Stderr + if identityFile != "" { sftp.Args = append(sftp.Args, "-i", identityFile) } + sftp.Args = append(sftp.Args, host) fmt.Println(">>>", printArgs(sftp.Args)) + if *DryRunFlag { return nil } @@ -149,17 +164,21 @@ func UploadSFTP(identityFile, host, dir string, files []string) error { if err != nil { return fmt.Errorf("can't create stdin pipe for sftp: %v", err) } + stdout, err := sftp.StdoutPipe() if err != nil { return fmt.Errorf("can't create stdout pipe for sftp: %v", err) } + if err := sftp.Start(); err != nil { return err } + in := io.MultiWriter(stdin, os.Stdout) for _, f := range files { fmt.Fprintln(in, "put", f, path.Join(dir, filepath.Base(f))) } + fmt.Fprintln(in, "exit") // Some issue with the PPA sftp server makes it so the server does not // respond properly to a 'bye', 'exit' or 'quit' from the client. @@ -169,25 +188,32 @@ func UploadSFTP(identityFile, host, dir string, files []string) error { // https://github.com/kolban-google/sftp-gcs/issues/23 // https://github.com/mscdex/ssh2/pull/1111 aborted := false + go func() { scanner := bufio.NewScanner(stdout) for scanner.Scan() { txt := scanner.Text() fmt.Println(txt) + if txt == "sftp> exit" { // Give it .5 seconds to exit (server might be fixed), then // hard kill it from the outside time.Sleep(500 * time.Millisecond) + aborted = true + sftp.Process.Kill() } } }() stdin.Close() + err = sftp.Wait() + if aborted { return nil } + return err } @@ -200,19 +226,24 @@ func FindMainPackages(dir string) []string { if err != nil { log.Fatal(err) } + for _, cmd := range cmds { pkgdir := filepath.Join(dir, cmd.Name()) + pkgs, err := parser.ParseDir(token.NewFileSet(), pkgdir, nil, parser.PackageClauseOnly) if err != nil { log.Fatal(err) } + for name := range pkgs { if name == "main" { path := "./" + filepath.ToSlash(pkgdir) commands = append(commands, path) + break } } } + return commands } diff --git a/internal/cli/bootnode.go b/internal/cli/bootnode.go index e6c8b33665..8051046a09 100644 --- a/internal/cli/bootnode.go +++ b/internal/cli/bootnode.go @@ -170,6 +170,7 @@ func (b *BootnodeCommand) Run(args []string) int { b.UI.Error(fmt.Sprintf("could not generate key: %v", err)) return 1 } + if b.saveKey != "" { path := b.saveKey diff --git a/internal/cli/debug.go b/internal/cli/debug.go index e15be6d1f9..9b3d224699 100644 --- a/internal/cli/debug.go +++ b/internal/cli/debug.go @@ -234,6 +234,7 @@ func tarCZF(archive string, src, target string) error { if err != nil { return err } + if !fi.Mode().IsRegular() { return nil } @@ -248,6 +249,7 @@ func tarCZF(archive string, src, target string) error { if target != "" { path = filepath.Join([]string{target, path}...) } + path = strings.TrimPrefix(path, string(filepath.Separator)) header.Name = path @@ -267,6 +269,7 @@ func tarCZF(archive string, src, target string) error { } f.Close() + return nil }) } diff --git a/internal/cli/debug_pprof.go b/internal/cli/debug_pprof.go index 6bbe664baa..ddb4520e9c 100644 --- a/internal/cli/debug_pprof.go +++ b/internal/cli/debug_pprof.go @@ -154,6 +154,7 @@ func (d *DebugPprofCommand) Run(args []string) int { d.UI.Output(fmt.Sprintf("Failed to get status: %v", err)) return 1 } + if err := dEnv.writeJSON("status.json", statusResp); err != nil { d.UI.Error(err.Error()) return 1 diff --git a/internal/cli/flagset/flagset.go b/internal/cli/flagset/flagset.go index 74249df395..25b7b4c3c3 100644 --- a/internal/cli/flagset/flagset.go +++ b/internal/cli/flagset/flagset.go @@ -38,6 +38,7 @@ func (f *Flagset) Help() string { str := "Options:\n\n" items := []string{} + for _, item := range f.flags { if item.Default != nil { items = append(items, fmt.Sprintf(" -%s\n %s (default: %v)", item.Name, item.Usage, item.Default)) @@ -152,6 +153,7 @@ func (f *Flagset) StringFlag(b *StringFlag) { Default: b.Default, }) } + f.set.StringVar(b.Value, b.Name, b.Default, b.Usage) } @@ -287,6 +289,7 @@ func (f *Flagset) SliceStringFlag(s *SliceStringFlag) { Default: strings.Join(s.Default, ","), }) } + f.set.Var(s, s.Name, s.Usage) } @@ -367,6 +370,7 @@ func (f *Flagset) MapStringFlag(m *MapStringFlag) { Default: formatMapString(m.Default), }) } + f.set.Var(m, m.Name, m.Usage) } diff --git a/internal/cli/removedb.go b/internal/cli/removedb.go index 224dae95d5..ce0a9f4f19 100644 --- a/internal/cli/removedb.go +++ b/internal/cli/removedb.go @@ -136,6 +136,7 @@ func confirmAndRemoveDB(ui cli.Ui, database string, kind string) { if !info.IsDir() { return os.Remove(path) } + return filepath.SkipDir }) diff --git a/internal/cli/server/chains/utils.go b/internal/cli/server/chains/utils.go index 5f2f761ea4..860ff5b6ee 100644 --- a/internal/cli/server/chains/utils.go +++ b/internal/cli/server/chains/utils.go @@ -16,12 +16,15 @@ func readPrealloc(filename string) core.GenesisAlloc { if err != nil { panic(fmt.Sprintf("Could not open genesis preallocation for %s: %v", filename, err)) } + defer f.Close() decoder := json.NewDecoder(f) ga := make(core.GenesisAlloc) + err = decoder.Decode(&ga) if err != nil { panic(fmt.Sprintf("Could not parse genesis preallocation for %s: %v", filename, err)) } + return ga } diff --git a/internal/cli/server/command.go b/internal/cli/server/command.go index 881b2489a5..4d7ab6f108 100644 --- a/internal/cli/server/command.go +++ b/internal/cli/server/command.go @@ -76,6 +76,7 @@ func (c *Command) extractFlags(args []string) error { // read if config file is provided, this will overwrite the cli flags, if provided if c.configFile != "" { log.Warn("Config File provided, this will overwrite the cli flags", "path", c.configFile) + cfg, err := readConfigFile(c.configFile) if err != nil { c.UI.Error(err.Error()) @@ -83,6 +84,7 @@ func (c *Command) extractFlags(args []string) error { return err } + if err := config.Merge(cfg); err != nil { c.UI.Error(err.Error()) c.config = &config @@ -103,12 +105,14 @@ func (c *Command) extractFlags(args []string) error { } } else { tempFlag := 0 + for _, val := range args { if (strings.HasPrefix(val, "-verbosity") || strings.HasPrefix(val, "--verbosity")) && config.LogLevel != "" { tempFlag = 1 break } } + if tempFlag == 1 { log.Warn("Both, verbosity and log-level flags are provided, log-level will be deprecated soon. Use verbosity only.", "using", config.Verbosity) } else if tempFlag == 0 && config.LogLevel != "" { @@ -144,6 +148,7 @@ func (c *Command) Run(args []string) int { c.UI.Error(err.Error()) return 1 } + c.srv = srv return c.handleSignals() @@ -172,6 +177,7 @@ func (c *Command) handleSignals() int { return 0 } } + return 1 } diff --git a/internal/cli/server/config.go b/internal/cli/server/config.go index 4e8553ec62..e1e6614ed1 100644 --- a/internal/cli/server/config.go +++ b/internal/cli/server/config.go @@ -1037,6 +1037,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* // RequiredBlocks { n.RequiredBlocks = map[uint64]common.Hash{} + for k, v := range c.RequiredBlocks { number, err := strconv.ParseUint(k, 0, 64) if err != nil { @@ -1125,6 +1126,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* n.NoPruning = true if !n.Preimages { n.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") } default: @@ -1441,12 +1443,14 @@ func MakeDatabaseHandles(max int) (int, error) { func parseBootnodes(urls []string) ([]*enode.Node, error) { dst := []*enode.Node{} + for _, url := range urls { if url != "" { node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { return nil, fmt.Errorf("invalid bootstrap url '%s': %v", url, err) } + dst = append(dst, node) } } diff --git a/internal/cli/server/config_legacy_test.go b/internal/cli/server/config_legacy_test.go index 29cefdd7bf..8ac61b094f 100644 --- a/internal/cli/server/config_legacy_test.go +++ b/internal/cli/server/config_legacy_test.go @@ -9,7 +9,6 @@ import ( ) func TestConfigLegacy(t *testing.T) { - readFile := func(path string) { expectedConfig, err := readLegacyConfig(path) assert.NoError(t, err) diff --git a/internal/cli/server/pprof/pprof.go b/internal/cli/server/pprof/pprof.go index 98ba90a9a1..7360ec2619 100644 --- a/internal/cli/server/pprof/pprof.go +++ b/internal/cli/server/pprof/pprof.go @@ -38,6 +38,7 @@ func Profile(profile string, debug, gc int) ([]byte, map[string]string, error) { headers["Content-Type"] = "application/octet-stream" headers["Content-Disposition"] = fmt.Sprintf(`attachment; filename="%s"`, profile) } + return buf.Bytes(), headers, nil } diff --git a/internal/cli/server/proto/server.pb.go b/internal/cli/server/proto/server.pb.go index 2a4919c72e..01071cfd19 100644 --- a/internal/cli/server/proto/server.pb.go +++ b/internal/cli/server/proto/server.pb.go @@ -46,6 +46,7 @@ var ( func (x DebugPprofRequest_Type) Enum() *DebugPprofRequest_Type { p := new(DebugPprofRequest_Type) *p = x + return p } @@ -80,6 +81,7 @@ type TraceRequest struct { func (x *TraceRequest) Reset() { *x = TraceRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -95,13 +97,16 @@ func (*TraceRequest) ProtoMessage() {} func (x *TraceRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -114,6 +119,7 @@ func (x *TraceRequest) GetNumber() int64 { if x != nil { return x.Number } + return 0 } @@ -125,6 +131,7 @@ type TraceResponse struct { func (x *TraceResponse) Reset() { *x = TraceResponse{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -140,13 +147,16 @@ func (*TraceResponse) ProtoMessage() {} func (x *TraceResponse) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -163,6 +173,7 @@ type ChainWatchRequest struct { func (x *ChainWatchRequest) Reset() { *x = ChainWatchRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -178,13 +189,16 @@ func (*ChainWatchRequest) ProtoMessage() {} func (x *ChainWatchRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -205,6 +219,7 @@ type ChainWatchResponse struct { func (x *ChainWatchResponse) Reset() { *x = ChainWatchResponse{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -220,13 +235,16 @@ func (*ChainWatchResponse) ProtoMessage() {} func (x *ChainWatchResponse) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -239,6 +257,7 @@ func (x *ChainWatchResponse) GetOldchain() []*BlockStub { if x != nil { return x.Oldchain } + return nil } @@ -246,6 +265,7 @@ func (x *ChainWatchResponse) GetNewchain() []*BlockStub { if x != nil { return x.Newchain } + return nil } @@ -253,6 +273,7 @@ func (x *ChainWatchResponse) GetType() string { if x != nil { return x.Type } + return "" } @@ -267,6 +288,7 @@ type BlockStub struct { func (x *BlockStub) Reset() { *x = BlockStub{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -282,13 +304,16 @@ func (*BlockStub) ProtoMessage() {} func (x *BlockStub) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -301,6 +326,7 @@ func (x *BlockStub) GetHash() string { if x != nil { return x.Hash } + return "" } @@ -308,6 +334,7 @@ func (x *BlockStub) GetNumber() uint64 { if x != nil { return x.Number } + return 0 } @@ -322,6 +349,7 @@ type PeersAddRequest struct { func (x *PeersAddRequest) Reset() { *x = PeersAddRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -337,13 +365,16 @@ func (*PeersAddRequest) ProtoMessage() {} func (x *PeersAddRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -356,6 +387,7 @@ func (x *PeersAddRequest) GetEnode() string { if x != nil { return x.Enode } + return "" } @@ -363,6 +395,7 @@ func (x *PeersAddRequest) GetTrusted() bool { if x != nil { return x.Trusted } + return false } @@ -374,6 +407,7 @@ type PeersAddResponse struct { func (x *PeersAddResponse) Reset() { *x = PeersAddResponse{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -389,13 +423,16 @@ func (*PeersAddResponse) ProtoMessage() {} func (x *PeersAddResponse) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -415,6 +452,7 @@ type PeersRemoveRequest struct { func (x *PeersRemoveRequest) Reset() { *x = PeersRemoveRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -430,13 +468,16 @@ func (*PeersRemoveRequest) ProtoMessage() {} func (x *PeersRemoveRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -449,6 +490,7 @@ func (x *PeersRemoveRequest) GetEnode() string { if x != nil { return x.Enode } + return "" } @@ -456,6 +498,7 @@ func (x *PeersRemoveRequest) GetTrusted() bool { if x != nil { return x.Trusted } + return false } @@ -467,6 +510,7 @@ type PeersRemoveResponse struct { func (x *PeersRemoveResponse) Reset() { *x = PeersRemoveResponse{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -482,13 +526,16 @@ func (*PeersRemoveResponse) ProtoMessage() {} func (x *PeersRemoveResponse) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -505,6 +552,7 @@ type PeersListRequest struct { func (x *PeersListRequest) Reset() { *x = PeersListRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -520,13 +568,16 @@ func (*PeersListRequest) ProtoMessage() {} func (x *PeersListRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -545,6 +596,7 @@ type PeersListResponse struct { func (x *PeersListResponse) Reset() { *x = PeersListResponse{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -560,13 +612,16 @@ func (*PeersListResponse) ProtoMessage() {} func (x *PeersListResponse) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -579,6 +634,7 @@ func (x *PeersListResponse) GetPeers() []*Peer { if x != nil { return x.Peers } + return nil } @@ -592,6 +648,7 @@ type PeersStatusRequest struct { func (x *PeersStatusRequest) Reset() { *x = PeersStatusRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -607,13 +664,16 @@ func (*PeersStatusRequest) ProtoMessage() {} func (x *PeersStatusRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -626,6 +686,7 @@ func (x *PeersStatusRequest) GetEnode() string { if x != nil { return x.Enode } + return "" } @@ -639,6 +700,7 @@ type PeersStatusResponse struct { func (x *PeersStatusResponse) Reset() { *x = PeersStatusResponse{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -654,13 +716,16 @@ func (*PeersStatusResponse) ProtoMessage() {} func (x *PeersStatusResponse) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -673,6 +738,7 @@ func (x *PeersStatusResponse) GetPeer() *Peer { if x != nil { return x.Peer } + return nil } @@ -692,6 +758,7 @@ type Peer struct { func (x *Peer) Reset() { *x = Peer{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -707,13 +774,16 @@ func (*Peer) ProtoMessage() {} func (x *Peer) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -726,6 +796,7 @@ func (x *Peer) GetId() string { if x != nil { return x.Id } + return "" } @@ -733,6 +804,7 @@ func (x *Peer) GetEnode() string { if x != nil { return x.Enode } + return "" } @@ -740,6 +812,7 @@ func (x *Peer) GetEnr() string { if x != nil { return x.Enr } + return "" } @@ -747,6 +820,7 @@ func (x *Peer) GetCaps() []string { if x != nil { return x.Caps } + return nil } @@ -754,6 +828,7 @@ func (x *Peer) GetName() string { if x != nil { return x.Name } + return "" } @@ -761,6 +836,7 @@ func (x *Peer) GetTrusted() bool { if x != nil { return x.Trusted } + return false } @@ -768,6 +844,7 @@ func (x *Peer) GetStatic() bool { if x != nil { return x.Static } + return false } @@ -781,6 +858,7 @@ type ChainSetHeadRequest struct { func (x *ChainSetHeadRequest) Reset() { *x = ChainSetHeadRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -796,13 +874,16 @@ func (*ChainSetHeadRequest) ProtoMessage() {} func (x *ChainSetHeadRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -815,6 +896,7 @@ func (x *ChainSetHeadRequest) GetNumber() uint64 { if x != nil { return x.Number } + return 0 } @@ -826,6 +908,7 @@ type ChainSetHeadResponse struct { func (x *ChainSetHeadResponse) Reset() { *x = ChainSetHeadResponse{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -841,13 +924,16 @@ func (*ChainSetHeadResponse) ProtoMessage() {} func (x *ChainSetHeadResponse) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -866,6 +952,7 @@ type StatusRequest struct { func (x *StatusRequest) Reset() { *x = StatusRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -881,13 +968,16 @@ func (*StatusRequest) ProtoMessage() {} func (x *StatusRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -900,6 +990,7 @@ func (x *StatusRequest) GetWait() bool { if x != nil { return x.Wait } + return false } @@ -918,6 +1009,7 @@ type StatusResponse struct { func (x *StatusResponse) Reset() { *x = StatusResponse{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -933,13 +1025,16 @@ func (*StatusResponse) ProtoMessage() {} func (x *StatusResponse) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -952,6 +1047,7 @@ func (x *StatusResponse) GetCurrentBlock() *Header { if x != nil { return x.CurrentBlock } + return nil } @@ -959,6 +1055,7 @@ func (x *StatusResponse) GetCurrentHeader() *Header { if x != nil { return x.CurrentHeader } + return nil } @@ -966,6 +1063,7 @@ func (x *StatusResponse) GetNumPeers() int64 { if x != nil { return x.NumPeers } + return 0 } @@ -973,6 +1071,7 @@ func (x *StatusResponse) GetSyncMode() string { if x != nil { return x.SyncMode } + return "" } @@ -980,6 +1079,7 @@ func (x *StatusResponse) GetSyncing() *StatusResponse_Syncing { if x != nil { return x.Syncing } + return nil } @@ -987,6 +1087,7 @@ func (x *StatusResponse) GetForks() []*StatusResponse_Fork { if x != nil { return x.Forks } + return nil } @@ -1001,6 +1102,7 @@ type Header struct { func (x *Header) Reset() { *x = Header{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1016,13 +1118,16 @@ func (*Header) ProtoMessage() {} func (x *Header) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -1035,6 +1140,7 @@ func (x *Header) GetHash() string { if x != nil { return x.Hash } + return "" } @@ -1042,6 +1148,7 @@ func (x *Header) GetNumber() uint64 { if x != nil { return x.Number } + return 0 } @@ -1057,6 +1164,7 @@ type DebugPprofRequest struct { func (x *DebugPprofRequest) Reset() { *x = DebugPprofRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1072,13 +1180,16 @@ func (*DebugPprofRequest) ProtoMessage() {} func (x *DebugPprofRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -1091,6 +1202,7 @@ func (x *DebugPprofRequest) GetType() DebugPprofRequest_Type { if x != nil { return x.Type } + return DebugPprofRequest_LOOKUP } @@ -1098,6 +1210,7 @@ func (x *DebugPprofRequest) GetProfile() string { if x != nil { return x.Profile } + return "" } @@ -1105,6 +1218,7 @@ func (x *DebugPprofRequest) GetSeconds() int64 { if x != nil { return x.Seconds } + return 0 } @@ -1118,6 +1232,7 @@ type DebugBlockRequest struct { func (x *DebugBlockRequest) Reset() { *x = DebugBlockRequest{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1133,13 +1248,16 @@ func (*DebugBlockRequest) ProtoMessage() {} func (x *DebugBlockRequest) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -1152,6 +1270,7 @@ func (x *DebugBlockRequest) GetNumber() int64 { if x != nil { return x.Number } + return 0 } @@ -1170,6 +1289,7 @@ type DebugFileResponse struct { func (x *DebugFileResponse) Reset() { *x = DebugFileResponse{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1185,13 +1305,16 @@ func (*DebugFileResponse) ProtoMessage() {} func (x *DebugFileResponse) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -1204,6 +1327,7 @@ func (m *DebugFileResponse) GetEvent() isDebugFileResponse_Event { if m != nil { return m.Event } + return nil } @@ -1211,6 +1335,7 @@ func (x *DebugFileResponse) GetOpen() *DebugFileResponse_Open { if x, ok := x.GetEvent().(*DebugFileResponse_Open_); ok { return x.Open } + return nil } @@ -1218,6 +1343,7 @@ func (x *DebugFileResponse) GetInput() *DebugFileResponse_Input { if x, ok := x.GetEvent().(*DebugFileResponse_Input_); ok { return x.Input } + return nil } @@ -1225,6 +1351,7 @@ func (x *DebugFileResponse) GetEof() *emptypb.Empty { if x, ok := x.GetEvent().(*DebugFileResponse_Eof); ok { return x.Eof } + return nil } @@ -1262,6 +1389,7 @@ type StatusResponse_Fork struct { func (x *StatusResponse_Fork) Reset() { *x = StatusResponse_Fork{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1277,13 +1405,16 @@ func (*StatusResponse_Fork) ProtoMessage() {} func (x *StatusResponse_Fork) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -1296,6 +1427,7 @@ func (x *StatusResponse_Fork) GetName() string { if x != nil { return x.Name } + return "" } @@ -1303,6 +1435,7 @@ func (x *StatusResponse_Fork) GetBlock() int64 { if x != nil { return x.Block } + return 0 } @@ -1310,6 +1443,7 @@ func (x *StatusResponse_Fork) GetDisabled() bool { if x != nil { return x.Disabled } + return false } @@ -1325,6 +1459,7 @@ type StatusResponse_Syncing struct { func (x *StatusResponse_Syncing) Reset() { *x = StatusResponse_Syncing{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1340,13 +1475,16 @@ func (*StatusResponse_Syncing) ProtoMessage() {} func (x *StatusResponse_Syncing) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -1359,6 +1497,7 @@ func (x *StatusResponse_Syncing) GetStartingBlock() int64 { if x != nil { return x.StartingBlock } + return 0 } @@ -1366,6 +1505,7 @@ func (x *StatusResponse_Syncing) GetHighestBlock() int64 { if x != nil { return x.HighestBlock } + return 0 } @@ -1373,6 +1513,7 @@ func (x *StatusResponse_Syncing) GetCurrentBlock() int64 { if x != nil { return x.CurrentBlock } + return 0 } @@ -1386,6 +1527,7 @@ type DebugFileResponse_Open struct { func (x *DebugFileResponse_Open) Reset() { *x = DebugFileResponse_Open{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1401,13 +1543,16 @@ func (*DebugFileResponse_Open) ProtoMessage() {} func (x *DebugFileResponse_Open) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -1420,6 +1565,7 @@ func (x *DebugFileResponse_Open) GetHeaders() map[string]string { if x != nil { return x.Headers } + return nil } @@ -1433,6 +1579,7 @@ type DebugFileResponse_Input struct { func (x *DebugFileResponse_Input) Reset() { *x = DebugFileResponse_Input{} + if protoimpl.UnsafeEnabled { mi := &file_internal_cli_server_proto_server_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1448,13 +1595,16 @@ func (*DebugFileResponse_Input) ProtoMessage() {} func (x *DebugFileResponse_Input) ProtoReflect() protoreflect.Message { mi := &file_internal_cli_server_proto_server_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } + return ms } + return mi.MessageOf(x) } @@ -1467,6 +1617,7 @@ func (x *DebugFileResponse_Input) GetData() []byte { if x != nil { return x.Data } + return nil } @@ -1654,6 +1805,7 @@ func file_internal_cli_server_proto_server_proto_rawDescGZIP() []byte { file_internal_cli_server_proto_server_proto_rawDescOnce.Do(func() { file_internal_cli_server_proto_server_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_cli_server_proto_server_proto_rawDescData) }) + return file_internal_cli_server_proto_server_proto_rawDescData } @@ -1734,6 +1886,7 @@ func file_internal_cli_server_proto_server_proto_init() { if File_internal_cli_server_proto_server_proto != nil { return } + if !protoimpl.UnsafeEnabled { file_internal_cli_server_proto_server_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TraceRequest); i { @@ -2048,12 +2201,15 @@ func file_internal_cli_server_proto_server_proto_init() { } } } + file_internal_cli_server_proto_server_proto_msgTypes[21].OneofWrappers = []interface{}{ (*DebugFileResponse_Open_)(nil), (*DebugFileResponse_Input_)(nil), (*DebugFileResponse_Eof)(nil), } + type x struct{} + out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), diff --git a/internal/cli/server/proto/server_grpc.pb.go b/internal/cli/server/proto/server_grpc.pb.go index a34b6fe557..3d74a5fde6 100644 --- a/internal/cli/server/proto/server_grpc.pb.go +++ b/internal/cli/server/proto/server_grpc.pb.go @@ -43,55 +43,67 @@ func NewBorClient(cc grpc.ClientConnInterface) BorClient { func (c *borClient) PeersAdd(ctx context.Context, in *PeersAddRequest, opts ...grpc.CallOption) (*PeersAddResponse, error) { out := new(PeersAddResponse) + err := c.cc.Invoke(ctx, "/proto.Bor/PeersAdd", in, out, opts...) if err != nil { return nil, err } + return out, nil } func (c *borClient) PeersRemove(ctx context.Context, in *PeersRemoveRequest, opts ...grpc.CallOption) (*PeersRemoveResponse, error) { out := new(PeersRemoveResponse) + err := c.cc.Invoke(ctx, "/proto.Bor/PeersRemove", in, out, opts...) if err != nil { return nil, err } + return out, nil } func (c *borClient) PeersList(ctx context.Context, in *PeersListRequest, opts ...grpc.CallOption) (*PeersListResponse, error) { out := new(PeersListResponse) + err := c.cc.Invoke(ctx, "/proto.Bor/PeersList", in, out, opts...) if err != nil { return nil, err } + return out, nil } func (c *borClient) PeersStatus(ctx context.Context, in *PeersStatusRequest, opts ...grpc.CallOption) (*PeersStatusResponse, error) { out := new(PeersStatusResponse) + err := c.cc.Invoke(ctx, "/proto.Bor/PeersStatus", in, out, opts...) if err != nil { return nil, err } + return out, nil } func (c *borClient) ChainSetHead(ctx context.Context, in *ChainSetHeadRequest, opts ...grpc.CallOption) (*ChainSetHeadResponse, error) { out := new(ChainSetHeadResponse) + err := c.cc.Invoke(ctx, "/proto.Bor/ChainSetHead", in, out, opts...) if err != nil { return nil, err } + return out, nil } func (c *borClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { out := new(StatusResponse) + err := c.cc.Invoke(ctx, "/proto.Bor/Status", in, out, opts...) if err != nil { return nil, err } + return out, nil } @@ -100,13 +112,16 @@ func (c *borClient) ChainWatch(ctx context.Context, in *ChainWatchRequest, opts if err != nil { return nil, err } + x := &borChainWatchClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } + if err := x.ClientStream.CloseSend(); err != nil { return nil, err } + return x, nil } @@ -124,6 +139,7 @@ func (x *borChainWatchClient) Recv() (*ChainWatchResponse, error) { if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } + return m, nil } @@ -132,13 +148,16 @@ func (c *borClient) DebugPprof(ctx context.Context, in *DebugPprofRequest, opts if err != nil { return nil, err } + x := &borDebugPprofClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } + if err := x.ClientStream.CloseSend(); err != nil { return nil, err } + return x, nil } @@ -156,6 +175,7 @@ func (x *borDebugPprofClient) Recv() (*DebugFileResponse, error) { if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } + return m, nil } @@ -164,13 +184,16 @@ func (c *borClient) DebugBlock(ctx context.Context, in *DebugBlockRequest, opts if err != nil { return nil, err } + x := &borDebugBlockClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } + if err := x.ClientStream.CloseSend(); err != nil { return nil, err } + return x, nil } @@ -188,6 +211,7 @@ func (x *borDebugBlockClient) Recv() (*DebugFileResponse, error) { if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } + return m, nil } @@ -256,9 +280,11 @@ func _Bor_PeersAdd_Handler(srv interface{}, ctx context.Context, dec func(interf if err := dec(in); err != nil { return nil, err } + if interceptor == nil { return srv.(BorServer).PeersAdd(ctx, in) } + info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/proto.Bor/PeersAdd", @@ -266,6 +292,7 @@ func _Bor_PeersAdd_Handler(srv interface{}, ctx context.Context, dec func(interf handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BorServer).PeersAdd(ctx, req.(*PeersAddRequest)) } + return interceptor(ctx, in, info, handler) } @@ -274,9 +301,11 @@ func _Bor_PeersRemove_Handler(srv interface{}, ctx context.Context, dec func(int if err := dec(in); err != nil { return nil, err } + if interceptor == nil { return srv.(BorServer).PeersRemove(ctx, in) } + info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/proto.Bor/PeersRemove", @@ -284,6 +313,7 @@ func _Bor_PeersRemove_Handler(srv interface{}, ctx context.Context, dec func(int handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BorServer).PeersRemove(ctx, req.(*PeersRemoveRequest)) } + return interceptor(ctx, in, info, handler) } @@ -292,9 +322,11 @@ func _Bor_PeersList_Handler(srv interface{}, ctx context.Context, dec func(inter if err := dec(in); err != nil { return nil, err } + if interceptor == nil { return srv.(BorServer).PeersList(ctx, in) } + info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/proto.Bor/PeersList", @@ -302,6 +334,7 @@ func _Bor_PeersList_Handler(srv interface{}, ctx context.Context, dec func(inter handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BorServer).PeersList(ctx, req.(*PeersListRequest)) } + return interceptor(ctx, in, info, handler) } @@ -310,9 +343,11 @@ func _Bor_PeersStatus_Handler(srv interface{}, ctx context.Context, dec func(int if err := dec(in); err != nil { return nil, err } + if interceptor == nil { return srv.(BorServer).PeersStatus(ctx, in) } + info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/proto.Bor/PeersStatus", @@ -320,6 +355,7 @@ func _Bor_PeersStatus_Handler(srv interface{}, ctx context.Context, dec func(int handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BorServer).PeersStatus(ctx, req.(*PeersStatusRequest)) } + return interceptor(ctx, in, info, handler) } @@ -328,9 +364,11 @@ func _Bor_ChainSetHead_Handler(srv interface{}, ctx context.Context, dec func(in if err := dec(in); err != nil { return nil, err } + if interceptor == nil { return srv.(BorServer).ChainSetHead(ctx, in) } + info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/proto.Bor/ChainSetHead", @@ -338,6 +376,7 @@ func _Bor_ChainSetHead_Handler(srv interface{}, ctx context.Context, dec func(in handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BorServer).ChainSetHead(ctx, req.(*ChainSetHeadRequest)) } + return interceptor(ctx, in, info, handler) } @@ -346,9 +385,11 @@ func _Bor_Status_Handler(srv interface{}, ctx context.Context, dec func(interfac if err := dec(in); err != nil { return nil, err } + if interceptor == nil { return srv.(BorServer).Status(ctx, in) } + info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/proto.Bor/Status", @@ -356,6 +397,7 @@ func _Bor_Status_Handler(srv interface{}, ctx context.Context, dec func(interfac handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BorServer).Status(ctx, req.(*StatusRequest)) } + return interceptor(ctx, in, info, handler) } @@ -364,6 +406,7 @@ func _Bor_ChainWatch_Handler(srv interface{}, stream grpc.ServerStream) error { if err := stream.RecvMsg(m); err != nil { return err } + return srv.(BorServer).ChainWatch(m, &borChainWatchServer{stream}) } @@ -385,6 +428,7 @@ func _Bor_DebugPprof_Handler(srv interface{}, stream grpc.ServerStream) error { if err := stream.RecvMsg(m); err != nil { return err } + return srv.(BorServer).DebugPprof(m, &borDebugPprofServer{stream}) } @@ -406,6 +450,7 @@ func _Bor_DebugBlock_Handler(srv interface{}, stream grpc.ServerStream) error { if err := stream.RecvMsg(m); err != nil { return err } + return srv.(BorServer).DebugBlock(m, &borDebugBlockServer{stream}) } diff --git a/internal/cli/server/server.go b/internal/cli/server/server.go index 9a3d6ab316..171b7e167e 100644 --- a/internal/cli/server/server.go +++ b/internal/cli/server/server.go @@ -220,6 +220,7 @@ func NewServer(config *Config, opts ...serverOption) (*Server, error) { cli = c } } + if cli != nil { wallet, err := accountManager.Find(accounts.Account{Address: eb}) if wallet == nil || err != nil { @@ -228,6 +229,7 @@ func NewServer(config *Config, opts ...serverOption) (*Server, error) { } cli.Authorize(eb, wallet.SignData) + authorized = true } @@ -240,6 +242,7 @@ func NewServer(config *Config, opts ...serverOption) (*Server, error) { } bor.Authorize(eb, wallet.SignData) + authorized = true } } @@ -349,6 +352,7 @@ func (s *Server) setupMetrics(config *TelemetryConfig, serviceName string) error go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Database, cfg.Username, cfg.Password, "geth.", tags) } + if v2Enabled { log.Info("Enabling metrics export to InfluxDB (v2)") @@ -360,7 +364,6 @@ func (s *Server) setupMetrics(config *TelemetryConfig, serviceName string) error go metrics.CollectProcessMetrics(3 * time.Second) if config.PrometheusAddr != "" { - prometheusMux := http.NewServeMux() prometheusMux.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry)) @@ -377,7 +380,6 @@ func (s *Server) setupMetrics(config *TelemetryConfig, serviceName string) error }() log.Info("Enabling metrics export to prometheus", "path", fmt.Sprintf("http://%s/debug/metrics/prometheus", config.PrometheusAddr)) - } if config.OpenCollectorEndpoint != "" { @@ -475,6 +477,7 @@ func setupLogger(logLevel string, loggingInfo LoggingConfig) { if usecolor { output = colorable.NewColorableStderr() } + ostream = log.StreamHandler(output, log.TerminalFormat(usecolor)) } diff --git a/internal/cli/server/server_test.go b/internal/cli/server/server_test.go index 5cea044df4..ab14c31a5d 100644 --- a/internal/cli/server/server_test.go +++ b/internal/cli/server/server_test.go @@ -56,6 +56,7 @@ func TestServer_DeveloperMode(t *testing.T) { currBlock := server.backend.BlockChain().CurrentBlock().Number.Int64() expected := blockNumber + i + 1 + if res := assert.Equal(t, currBlock, expected); res == false { break } diff --git a/internal/cli/server/service.go b/internal/cli/server/service.go index 402deb7a33..bf72a0e848 100644 --- a/internal/cli/server/service.go +++ b/internal/cli/server/service.go @@ -101,12 +101,14 @@ func (s *Server) PeersAdd(ctx context.Context, req *proto.PeersAddRequest) (*pro if err != nil { return nil, fmt.Errorf("invalid enode: %v", err) } + srv := s.node.Server() if req.Trusted { srv.AddTrustedPeer(node) } else { srv.AddPeer(node) } + return &proto.PeersAddResponse{}, nil } @@ -115,12 +117,14 @@ func (s *Server) PeersRemove(ctx context.Context, req *proto.PeersRemoveRequest) if err != nil { return nil, fmt.Errorf("invalid enode: %v", err) } + srv := s.node.Server() if req.Trusted { srv.RemoveTrustedPeer(node) } else { srv.RemovePeer(node) } + return &proto.PeersRemoveResponse{}, nil } @@ -131,23 +135,28 @@ func (s *Server) PeersList(ctx context.Context, req *proto.PeersListRequest) (*p for _, p := range peers { resp.Peers = append(resp.Peers, peerInfoToPeer(p)) } + return resp, nil } func (s *Server) PeersStatus(ctx context.Context, req *proto.PeersStatusRequest) (*proto.PeersStatusResponse, error) { var peerInfo *p2p.PeerInfo + for _, p := range s.node.Server().PeersInfo() { if strings.HasPrefix(p.ID, req.Enode) { if peerInfo != nil { return nil, fmt.Errorf("more than one peer with the same prefix") } + peerInfo = p } } + resp := &proto.PeersStatusResponse{} if peerInfo != nil { resp.Peer = peerInfoToPeer(peerInfo) } + return resp, nil } @@ -188,6 +197,7 @@ func (s *Server) Status(ctx context.Context, in *proto.StatusRequest) (*proto.St } else { break } + i++ } } @@ -207,6 +217,7 @@ func (s *Server) Status(ctx context.Context, in *proto.StatusRequest) (*proto.St }, Forks: gatherForks(s.config.chain.Genesis.Config, s.config.chain.Genesis.Config.Bor), } + return resp, nil } @@ -260,12 +271,14 @@ func gatherForks(configList ...interface{}) []*proto.StatusResponse_Fork { skip := "DAOForkBlock" conf := reflect.ValueOf(config).Elem() + for i := 0; i < kind.NumField(); i++ { // Fetch the next field and skip non-fork rules field := kind.Field(i) if strings.Contains(field.Name, skip) { continue } + if !strings.HasSuffix(field.Name, "Block") { continue } @@ -275,6 +288,7 @@ func gatherForks(configList ...interface{}) []*proto.StatusResponse_Fork { } val := conf.Field(i) + switch field.Type.Kind() { case bigIntT: rule := val.Interface().(*big.Int) @@ -293,11 +307,11 @@ func gatherForks(configList ...interface{}) []*proto.StatusResponse_Fork { forks = append(forks, fork) } } + return forks } func convertBlockToBlockStub(blocks []*types.Block) []*proto.BlockStub { - var blockStubs []*proto.BlockStub for _, block := range blocks { @@ -312,10 +326,10 @@ func convertBlockToBlockStub(blocks []*types.Block) []*proto.BlockStub { } func (s *Server) ChainWatch(req *proto.ChainWatchRequest, reply proto.Bor_ChainWatchServer) error { - chain2HeadChanSize := 10 chain2HeadCh := make(chan core.Chain2HeadEvent, chain2HeadChanSize) + headSub := s.backend.APIBackend.SubscribeChain2HeadEvent(chain2HeadCh) defer headSub.Unsubscribe() diff --git a/internal/cli/server/service_test.go b/internal/cli/server/service_test.go index 86cf68e75e..47e8013e57 100644 --- a/internal/cli/server/service_test.go +++ b/internal/cli/server/service_test.go @@ -14,9 +14,11 @@ func TestGatherBlocks(t *testing.T) { ABlock *big.Int BBlock *big.Int } + type d struct { DBlock uint64 } + val := &c{ BBlock: new(big.Int).SetInt64(1), } diff --git a/internal/cmdtest/test_cmd.go b/internal/cmdtest/test_cmd.go index fd7a4a8b7f..fc2634ad87 100644 --- a/internal/cmdtest/test_cmd.go +++ b/internal/cmdtest/test_cmd.go @@ -68,13 +68,16 @@ func (tt *TestCmd) Run(name string, args ...string) { Stderr: tt.stderr, } stdout, err := tt.cmd.StdoutPipe() + if err != nil { tt.Fatal(err) } + tt.stdout = bufio.NewReader(stdout) if tt.stdin, err = tt.cmd.StdinPipe(); err != nil { tt.Fatal(err) } + if err := tt.cmd.Start(); err != nil { tt.Fatal(err) } @@ -93,6 +96,7 @@ func (tt *TestCmd) SetTemplateFunc(name string, fn interface{}) { if tt.Func == nil { tt.Func = make(map[string]interface{}) } + tt.Func[name] = fn } @@ -105,6 +109,7 @@ func (tt *TestCmd) Expect(tplsource string) { // Generate the expected output by running the template. tpl := template.Must(template.New("").Funcs(tt.Func).Parse(tplsource)) wantbuf := new(bytes.Buffer) + if err := tpl.Execute(wantbuf, tt.Data); err != nil { panic(err) } @@ -114,20 +119,25 @@ func (tt *TestCmd) Expect(tplsource string) { if err := tt.matchExactOutput(want); err != nil { tt.Fatal(err) } + tt.Logf("Matched stdout text:\n%s", want) } // Output reads all output from stdout, and returns the data. func (tt *TestCmd) Output() []byte { var buf []byte + tt.withKillTimeout(func() { buf, _ = io.ReadAll(tt.stdout) }) + return buf } func (tt *TestCmd) matchExactOutput(want []byte) error { buf := make([]byte, len(want)) n := 0 + tt.withKillTimeout(func() { n, _ = io.ReadFull(tt.stdout, buf) }) + buf = buf[:n] if n < len(want) || !bytes.Equal(buf, want) { // Grab any additional buffered output in case of mismatch @@ -141,11 +151,13 @@ func (tt *TestCmd) matchExactOutput(want []byte) error { buf[:i], buf[i:n], want) } } + if n < len(want) { return fmt.Errorf("not enough output, got until ◊:\n---------------- (stdout text)\n%s\n---------------- (expected text)\n%s◊%s", buf, want[:n], want[n:]) } } + return nil } @@ -157,24 +169,31 @@ func (tt *TestCmd) matchExactOutput(want []byte) error { // after ExpectRegexp. func (tt *TestCmd) ExpectRegexp(regex string) (*regexp.Regexp, []string) { regex = strings.TrimPrefix(regex, "\n") + var ( re = regexp.MustCompile(regex) rtee = &runeTee{in: tt.stdout} matches []int ) + tt.withKillTimeout(func() { matches = re.FindReaderSubmatchIndex(rtee) }) + output := rtee.buf.Bytes() if matches == nil { tt.Fatalf("Output did not match:\n---------------- (stdout text)\n%s\n---------------- (regular expression)\n%s", output, regex) return re, nil } + tt.Logf("Matched stdout text:\n%s", output) + var submatches []string + for i := 0; i < len(matches); i += 2 { submatch := string(output[matches[i]:matches[i+1]]) submatches = append(submatches, submatch) } + return re, submatches } @@ -182,13 +201,16 @@ func (tt *TestCmd) ExpectRegexp(regex string) (*regexp.Regexp, []string) { // printing any additional text on stdout. func (tt *TestCmd) ExpectExit() { var output []byte + tt.withKillTimeout(func() { output, _ = io.ReadAll(tt.stdout) }) tt.WaitExit() + if tt.Cleanup != nil { tt.Cleanup() } + if len(output) > 0 { tt.Errorf("Unmatched stdout text:\n%s", output) } @@ -213,6 +235,7 @@ func (tt *TestCmd) ExitStatus() int { } } } + return 0 } @@ -222,6 +245,7 @@ func (tt *TestCmd) ExitStatus() int { func (tt *TestCmd) StderrText() string { tt.stderr.mu.Lock() defer tt.stderr.mu.Unlock() + return tt.stderr.buf.String() } @@ -231,6 +255,7 @@ func (tt *TestCmd) CloseStdin() { func (tt *TestCmd) Kill() { tt.cmd.Process.Kill() + if tt.Cleanup != nil { tt.Cleanup() } @@ -261,9 +286,11 @@ func (tl *testlogger) Write(b []byte) (n int, err error) { tl.t.Logf("(stderr:%v) %s", tl.name, line) } } + tl.mu.Lock() tl.buf.Write(b) tl.mu.Unlock() + return len(b), err } @@ -280,6 +307,7 @@ type runeTee struct { func (rtee *runeTee) Read(b []byte) (n int, err error) { n, err = rtee.in.Read(b) rtee.buf.Write(b[:n]) + return n, err } @@ -288,6 +316,7 @@ func (rtee *runeTee) ReadRune() (r rune, size int, err error) { if err == nil { rtee.buf.WriteRune(r) } + return r, size, err } @@ -296,5 +325,6 @@ func (rtee *runeTee) ReadByte() (b byte, err error) { if err == nil { rtee.buf.WriteByte(b) } + return b, err } diff --git a/internal/debug/api.go b/internal/debug/api.go index 42d0fa15ed..7301b106c0 100644 --- a/internal/debug/api.go +++ b/internal/debug/api.go @@ -75,6 +75,7 @@ func (*HandlerT) BacktraceAt(location string) error { func (*HandlerT) MemStats() *runtime.MemStats { s := new(runtime.MemStats) runtime.ReadMemStats(s) + return s } @@ -82,6 +83,7 @@ func (*HandlerT) MemStats() *runtime.MemStats { func (*HandlerT) GcStats() *debug.GCStats { s := new(debug.GCStats) debug.ReadGCStats(s) + return s } @@ -91,8 +93,10 @@ func (h *HandlerT) CpuProfile(file string, nsec uint) error { if err := h.StartCPUProfile(file); err != nil { return err } + time.Sleep(time.Duration(nsec) * time.Second) h.StopCPUProfile() + return nil } @@ -100,20 +104,25 @@ func (h *HandlerT) CpuProfile(file string, nsec uint) error { func (h *HandlerT) StartCPUProfile(file string) error { h.mu.Lock() defer h.mu.Unlock() + if h.cpuW != nil { return errors.New("CPU profiling already in progress") } + f, err := os.Create(expandHome(file)) if err != nil { return err } + if err := pprof.StartCPUProfile(f); err != nil { f.Close() return err } + h.cpuW = f h.cpuFile = file log.Info("CPU profiling started", "dump", h.cpuFile) + return nil } @@ -122,13 +131,16 @@ func (h *HandlerT) StopCPUProfile() error { h.mu.Lock() defer h.mu.Unlock() pprof.StopCPUProfile() + if h.cpuW == nil { return errors.New("CPU profiling not in progress") } + log.Info("Done writing CPU profile", "dump", h.cpuFile) h.cpuW.Close() h.cpuW = nil h.cpuFile = "" + return nil } @@ -138,8 +150,10 @@ func (h *HandlerT) GoTrace(file string, nsec uint) error { if err := h.StartGoTrace(file); err != nil { return err } + time.Sleep(time.Duration(nsec) * time.Second) h.StopGoTrace() + return nil } @@ -149,7 +163,9 @@ func (h *HandlerT) GoTrace(file string, nsec uint) error { func (*HandlerT) BlockProfile(file string, nsec uint) error { runtime.SetBlockProfileRate(1) time.Sleep(time.Duration(nsec) * time.Second) + defer runtime.SetBlockProfileRate(0) + return writeProfile("block", file) } @@ -170,7 +186,9 @@ func (*HandlerT) WriteBlockProfile(file string) error { func (*HandlerT) MutexProfile(file string, nsec uint) error { runtime.SetMutexProfileFraction(1) time.Sleep(time.Duration(nsec) * time.Second) + defer runtime.SetMutexProfileFraction(0) + return writeProfile("mutex", file) } @@ -229,6 +247,7 @@ func (*HandlerT) Stacks(filter *string) string { } } } + return buf.String() } @@ -246,11 +265,14 @@ func (*HandlerT) SetGCPercent(v int) int { func writeProfile(name, file string) error { p := pprof.Lookup(name) log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file) + f, err := os.Create(expandHome(file)) if err != nil { return err } + defer f.Close() + return p.WriteTo(f, 0) } @@ -264,9 +286,11 @@ func expandHome(p string) string { home = usr.HomeDir } } + if home != "" { p = home + p[1:] } } + return filepath.Clean(p) } diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 7c13251eaf..ec7a07a46f 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -198,6 +198,7 @@ func Setup(ctx *cli.Context) error { output = io.Writer(os.Stderr) logFmtFlag = ctx.String(logFormatFlag.Name) ) + switch { case ctx.Bool(logjsonFlag.Name): // Retain backwards compatibility with `--log.json` flag if `--log.format` not set @@ -212,28 +213,33 @@ func Setup(ctx *cli.Context) error { if useColor { output = colorable.NewColorableStderr() } + logfmt = log.TerminalFormat(useColor) default: // Unknown log format specified return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name)) } + var ( stdHandler = log.StreamHandler(output, logfmt) ostream = stdHandler logFile = ctx.String(logFileFlag.Name) rotation = ctx.Bool(logRotateFlag.Name) ) + if len(logFile) > 0 { if err := validateLogLocation(filepath.Dir(logFile)); err != nil { return fmt.Errorf("failed to initiatilize file logger: %v", err) } } + context := []interface{}{"rotate", rotation} if len(logFmtFlag) > 0 { context = append(context, "format", logFmtFlag) } else { context = append(context, "format", "terminal") } + if rotation { // Lumberjack uses -lumberjack.log in is.TempDir() if empty. // so typically /tmp/geth-lumberjack.log on linux @@ -242,6 +248,7 @@ func Setup(ctx *cli.Context) error { } else { context = append(context, "location", filepath.Join(os.TempDir(), "geth-lumberjack.log")) } + ostream = log.MultiHandler(log.StreamHandler(&lumberjack.Logger{ Filename: logFile, MaxSize: ctx.Int(logMaxSizeMBsFlag.Name), @@ -254,14 +261,17 @@ func Setup(ctx *cli.Context) error { return err } else { ostream = log.MultiHandler(logOutputStream, stdHandler) + context = append(context, "location", logFile) } } + glogger.SetHandler(ostream) // logging verbosity := ctx.Int(verbosityFlag.Name) glogger.Verbosity(log.Lvl(verbosity)) + vmodule := ctx.String(logVmoduleFlag.Name) if vmodule == "" { // Retain backwards compatibility with `--vmodule` flag if `--log.vmodule` not set @@ -270,12 +280,14 @@ func Setup(ctx *cli.Context) error { defer log.Warn("The flag '--vmodule' is deprecated, please use '--log.vmodule' instead") } } + glogger.Vmodule(vmodule) debug := ctx.Bool(debugFlag.Name) if ctx.IsSet(debugFlag.Name) { debug = ctx.Bool(debugFlag.Name) } + log.PrintOrigins(debug) backtrace := ctx.String(backtraceAtFlag.Name) @@ -318,9 +330,11 @@ func Setup(ctx *cli.Context) error { address := fmt.Sprintf("%s:%d", "0.0.0.0", 7071) StartPProf(address, !ctx.IsSet("metrics.addr")) } + if len(logFile) > 0 || rotation { log.Info("Logging configured", context...) } + return nil } @@ -330,8 +344,10 @@ func StartPProf(address string, withMetrics bool) { if withMetrics { exp.Exp(metrics.DefaultRegistry) } + http.Handle("/memsize/", http.StripPrefix("/memsize", &Memsize)) log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) + go func() { if err := http.ListenAndServe(address, nil); err != nil { log.Error("Failure in running pprof server", "err", err) @@ -344,6 +360,7 @@ func StartPProf(address string, withMetrics bool) { func Exit() { Handler.StopCPUProfile() Handler.StopGoTrace() + if closer, ok := logOutputStream.(io.Closer); ok { closer.Close() } @@ -360,5 +377,6 @@ func validateLogLocation(path string) error { } else { f.Close() } + return os.Remove(tmp) } diff --git a/internal/debug/trace.go b/internal/debug/trace.go index a273e4a9db..f565608c33 100644 --- a/internal/debug/trace.go +++ b/internal/debug/trace.go @@ -31,20 +31,25 @@ import ( func (h *HandlerT) StartGoTrace(file string) error { h.mu.Lock() defer h.mu.Unlock() + if h.traceW != nil { return errors.New("trace already in progress") } + f, err := os.Create(expandHome(file)) if err != nil { return err } + if err := trace.Start(f); err != nil { f.Close() return err } + h.traceW = f h.traceFile = file log.Info("Go tracing started", "dump", h.traceFile) + return nil } @@ -53,12 +58,15 @@ func (h *HandlerT) StopGoTrace() error { h.mu.Lock() defer h.mu.Unlock() trace.Stop() + if h.traceW == nil { return errors.New("trace not in progress") } + log.Info("Done writing Go trace", "dump", h.traceFile) h.traceW.Close() h.traceW = nil h.traceFile = "" + return nil } diff --git a/internal/ethapi/addrlock.go b/internal/ethapi/addrlock.go index 61ddff688c..7ff5459437 100644 --- a/internal/ethapi/addrlock.go +++ b/internal/ethapi/addrlock.go @@ -31,12 +31,15 @@ type AddrLocker struct { func (l *AddrLocker) lock(address common.Address) *sync.Mutex { l.mu.Lock() defer l.mu.Unlock() + if l.locks == nil { l.locks = make(map[common.Address]*sync.Mutex) } + if _, ok := l.locks[address]; !ok { l.locks[address] = new(sync.Mutex) } + return l.locks[address] } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 265ed53bb2..7c3d67bd24 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -69,9 +69,11 @@ func (s *EthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) { if err != nil { return nil, err } + if head := s.b.CurrentHeader(); head.BaseFee != nil { tipcap.Add(tipcap, head.BaseFee) } + return (*hexutil.Big)(tipcap), err } @@ -81,6 +83,7 @@ func (s *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, e if err != nil { return nil, err } + return (*hexutil.Big)(tipcap), err } @@ -97,6 +100,7 @@ func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecim if err != nil { return nil, err } + results := &feeHistoryResult{ OldestBlock: (*hexutil.Big)(oldest), GasUsedRatio: gasUsed, @@ -110,12 +114,14 @@ func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecim } } } + if baseFee != nil { results.BaseFee = make([]*hexutil.Big, len(baseFee)) for i, v := range baseFee { results.BaseFee[i] = (*hexutil.Big)(v) } } + return results, nil } @@ -177,6 +183,7 @@ func (s *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction { for _, tx := range txs { dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()) } + content["pending"][account.Hex()] = dump } // Flatten the queued transactions @@ -185,8 +192,10 @@ func (s *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction { for _, tx := range txs { dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()) } + content["queued"][account.Hex()] = dump } + return content } @@ -201,6 +210,7 @@ func (s *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCT for _, tx := range pending { dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()) } + content["pending"] = dump // Build the queued transactions @@ -208,6 +218,7 @@ func (s *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCT for _, tx := range queue { dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()) } + content["queued"] = dump return content @@ -216,6 +227,7 @@ func (s *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCT // Status returns the number of pending and queued transaction in the pool. func (s *TxPoolAPI) Status() map[string]hexutil.Uint { pending, queue := s.b.Stats() + return map[string]hexutil.Uint{ "pending": hexutil.Uint(pending), "queued": hexutil.Uint(queue), @@ -244,6 +256,7 @@ func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string { for _, tx := range txs { dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx) } + content["pending"][account.Hex()] = dump } // Flatten the queued transactions @@ -252,8 +265,10 @@ func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string { for _, tx := range txs { dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx) } + content["queued"][account.Hex()] = dump } + return content } @@ -308,6 +323,7 @@ type rawWallet struct { // ListWallets will return a list of wallets this node manages. func (s *PersonalAccountAPI) ListWallets() []rawWallet { wallets := make([]rawWallet, 0) // return [] instead of nil if empty + for _, wallet := range s.am.Wallets() { status, failure := wallet.Status() @@ -319,8 +335,10 @@ func (s *PersonalAccountAPI) ListWallets() []rawWallet { if failure != nil { raw.Failure = failure.Error() } + wallets = append(wallets, raw) } + return wallets } @@ -333,10 +351,12 @@ func (s *PersonalAccountAPI) OpenWallet(url string, passphrase *string) error { if err != nil { return err } + pass := "" if passphrase != nil { pass = *passphrase } + return wallet.Open(pass) } @@ -347,13 +367,16 @@ func (s *PersonalAccountAPI) DeriveAccount(url string, path string, pin *bool) ( if err != nil { return accounts.Account{}, err } + derivPath, err := accounts.ParseDerivationPath(path) if err != nil { return accounts.Account{}, err } + if pin == nil { pin = new(bool) } + return wallet.Derive(derivPath, *pin) } @@ -363,13 +386,16 @@ func (s *PersonalAccountAPI) NewAccount(password string) (common.Address, error) if err != nil { return common.Address{}, err } + acc, err := ks.NewAccount(password) if err == nil { log.Info("Your new key was generated", "address", acc.Address) log.Warn("Please backup your key file!", "path", acc.URL.Path) log.Warn("Please remember your password!") + return acc.Address, nil } + return common.Address{}, err } @@ -378,6 +404,7 @@ func fetchKeystore(am *accounts.Manager) (*keystore.KeyStore, error) { if ks := am.Backends(keystore.KeyStoreType); len(ks) > 0 { return ks[0].(*keystore.KeyStore), nil } + return nil, errors.New("local keystore not used") } @@ -388,11 +415,14 @@ func (s *PersonalAccountAPI) ImportRawKey(privkey string, password string) (comm if err != nil { return common.Address{}, err } + ks, err := fetchKeystore(s.am) if err != nil { return common.Address{}, err } + acc, err := ks.ImportECDSA(key, password) + return acc.Address, err } @@ -408,6 +438,7 @@ func (s *PersonalAccountAPI) UnlockAccount(ctx context.Context, addr common.Addr } const max = uint64(time.Duration(math.MaxInt64) / time.Second) + var d time.Duration if duration == nil { d = 300 * time.Second @@ -416,14 +447,17 @@ func (s *PersonalAccountAPI) UnlockAccount(ctx context.Context, addr common.Addr } else { d = time.Duration(*duration) * time.Second } + ks, err := fetchKeystore(s.am) if err != nil { return false, err } + err = ks.TimedUnlock(accounts.Account{Address: addr}, password, d) if err != nil { log.Warn("Failed account unlock attempt", "address", addr, "err", err) } + return err == nil, err } @@ -432,6 +466,7 @@ func (s *PersonalAccountAPI) LockAccount(addr common.Address) bool { if ks, err := fetchKeystore(s.am); err == nil { return ks.Lock(addr) == nil } + return false } @@ -441,6 +476,7 @@ func (s *PersonalAccountAPI) LockAccount(addr common.Address) bool { func (s *PersonalAccountAPI) signTransaction(ctx context.Context, args *TransactionArgs, passwd string) (*types.Transaction, error) { // Look up the wallet containing the requested signer account := accounts.Account{Address: args.from()} + wallet, err := s.am.Find(account) if err != nil { return nil, err @@ -465,11 +501,13 @@ func (s *PersonalAccountAPI) SendTransaction(ctx context.Context, args Transacti s.nonceLock.LockAddr(args.from()) defer s.nonceLock.UnlockAddr(args.from()) } + signed, err := s.signTransaction(ctx, &args, passwd) if err != nil { log.Warn("Failed transaction send attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err) return common.Hash{}, err } + return SubmitTransaction(ctx, s.b, signed) } @@ -483,12 +521,15 @@ func (s *PersonalAccountAPI) SignTransaction(ctx context.Context, args Transacti if args.From == nil { return nil, fmt.Errorf("sender not specified") } + if args.Gas == nil { return nil, fmt.Errorf("gas not specified") } + if args.GasPrice == nil && (args.MaxFeePerGas == nil || args.MaxPriorityFeePerGas == nil) { return nil, fmt.Errorf("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas") } + if args.Nonce == nil { return nil, fmt.Errorf("nonce not specified") } @@ -497,15 +538,18 @@ func (s *PersonalAccountAPI) SignTransaction(ctx context.Context, args Transacti if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { return nil, err } + signed, err := s.signTransaction(ctx, &args, passwd) if err != nil { log.Warn("Failed transaction sign attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err) return nil, err } + data, err := signed.MarshalBinary() if err != nil { return nil, err } + return &SignTransactionResult{data, signed}, nil } @@ -532,7 +576,9 @@ func (s *PersonalAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr log.Warn("Failed data sign attempt", "address", addr, "err", err) return nil, err } + signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + return signature, nil } @@ -550,15 +596,18 @@ func (s *PersonalAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.By if len(sig) != crypto.SignatureLength { return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength) } + if sig[crypto.RecoveryIDOffset] != 27 && sig[crypto.RecoveryIDOffset] != 28 { return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") } + sig[crypto.RecoveryIDOffset] -= 27 // Transform yellow paper V from 27/28 to 0/1 rpk, err := crypto.SigToPub(accounts.TextHash(data), sig) if err != nil { return common.Address{}, err } + return crypto.PubkeyToAddress(*rpk), nil } @@ -637,6 +686,7 @@ func (s *BlockChainAPI) GetTransactionReceiptsByBlock(ctx context.Context, block borReceipt := rawdb.ReadBorReceipt(s.b.ChainDb(), block.Hash(), block.NumberU64(), s.b.ChainConfig()) if borReceipt != nil { receipts = append(receipts, borReceipt) + txHash = types.GetDerivedBorTxHash(types.BorReceiptKey(block.Number().Uint64(), block.Hash())) if txHash != (common.Hash{}) { borTx, _, _, _, _ := s.b.GetBorBlockTransactionWithBlockHash(ctx, txHash, block.Hash()) @@ -649,12 +699,16 @@ func (s *BlockChainAPI) GetTransactionReceiptsByBlock(ctx context.Context, block } txReceipts := make([]map[string]interface{}, 0, len(txs)) + for idx, receipt := range receipts { tx := txs[idx] + var signer types.Signer = types.FrontierSigner{} + if tx.Protected() { signer = types.NewEIP155Signer(tx.ChainId()) } + from, _ := types.Sender(signer, tx) fields := map[string]interface{}{ @@ -678,6 +732,7 @@ func (s *BlockChainAPI) GetTransactionReceiptsByBlock(ctx context.Context, block } else { fields["status"] = hexutil.Uint(receipt.Status) } + if receipt.Logs == nil { fields["logs"] = [][]*types.Log{} } @@ -721,6 +776,7 @@ func (s *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, if state == nil || err != nil { return nil, err } + return (*hexutil.Big)(state.GetBalance(address)), state.Error() } @@ -747,10 +803,12 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st if state == nil || err != nil { return nil, err } + storageTrie, err := state.StorageTrie(address) if err != nil { return nil, err } + storageHash := types.EmptyRootHash codeHash := state.GetCodeHash(address) storageProof := make([]StorageResult, len(storageKeys)) @@ -769,11 +827,13 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st if err != nil { return nil, err } + if storageTrie != nil { proof, storageError := state.GetStorageProof(address, key) if storageError != nil { return nil, storageError } + storageProof[i] = StorageResult{hexKey, (*hexutil.Big)(state.GetState(address, key).Big()), toHexSlice(proof)} } else { storageProof[i] = StorageResult{hexKey, &hexutil.Big{}, []string{}} @@ -803,16 +863,20 @@ func decodeHash(s string) (common.Hash, error) { if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { s = s[2:] } + if (len(s) & 1) > 0 { s = "0" + s } + b, err := hex.DecodeString(s) if err != nil { return common.Hash{}, fmt.Errorf("hex string invalid") } + if len(b) > 32 { return common.Hash{}, fmt.Errorf("hex string too long, want at most 32 bytes") } + return common.BytesToHash(b), nil } @@ -823,14 +887,17 @@ func (s *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockN header, err := s.b.HeaderByNumber(ctx, number) if header != nil && err == nil { response := s.rpcMarshalHeader(ctx, header) + if number == rpc.PendingBlockNumber { // Pending header need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { response[field] = nil } } + return response, err } + return nil, err } @@ -840,6 +907,7 @@ func (s *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) m if header != nil { return s.rpcMarshalHeader(ctx, header) } + return nil } @@ -866,6 +934,7 @@ func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNu return response, err } + return nil, err } @@ -879,8 +948,10 @@ func (s *BlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fu if err == nil && response != nil { return s.appendRPCMarshalBorTransaction(ctx, block, response, fullTx), err } + return response, err } + return nil, err } @@ -893,9 +964,12 @@ func (s *BlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, block log.Debug("Requested uncle not found", "number", blockNr, "hash", block.Hash(), "index", index) return nil, nil } + block = types.NewBlockWithHeader(uncles[index]) + return s.rpcMarshalBlock(ctx, block, false, false) } + return nil, err } @@ -908,9 +982,12 @@ func (s *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHa log.Debug("Requested uncle not found", "number", block.Number(), "hash", blockHash, "index", index) return nil, nil } + block = types.NewBlockWithHeader(uncles[index]) + return s.rpcMarshalBlock(ctx, block, false, false) } + return nil, err } @@ -920,6 +997,7 @@ func (s *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr n := hexutil.Uint(len(block.Uncles())) return &n } + return nil } @@ -929,6 +1007,7 @@ func (s *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash n := hexutil.Uint(len(block.Uncles())) return &n } + return nil } @@ -938,7 +1017,9 @@ func (s *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blo if state == nil || err != nil { return nil, err } + code := state.GetCode(address) + return code, state.Error() } @@ -950,11 +1031,14 @@ func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address if state == nil || err != nil { return nil, err } + key, err := decodeHash(hexKey) if err != nil { return nil, fmt.Errorf("unable to decode storage key: %s", err) } + res := state.GetState(address, key) + return res[:], state.Error() } @@ -980,6 +1064,7 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { if diff == nil { return nil } + for addr, account := range *diff { // Override account nonce. if account.Nonce != nil { @@ -993,6 +1078,7 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { if account.Balance != nil { state.SetBalance(addr, (*big.Int)(*account.Balance)) } + if account.State != nil && account.StateDiff != nil { return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) } @@ -1011,6 +1097,7 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { // By using finalize, the overrides are semantically behaving as // if they were created in a transaction just before the tracing occur. state.Finalise(false) + return nil } @@ -1030,24 +1117,31 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) { if diff == nil { return } + if diff.Number != nil { blockCtx.BlockNumber = diff.Number.ToInt() } + if diff.Difficulty != nil { blockCtx.Difficulty = diff.Difficulty.ToInt() } + if diff.Time != nil { blockCtx.Time = uint64(*diff.Time) } + if diff.GasLimit != nil { blockCtx.GasLimit = uint64(*diff.GasLimit) } + if diff.Coinbase != nil { blockCtx.Coinbase = *diff.Coinbase } + if diff.Random != nil { blockCtx.Random = diff.Random } + if diff.BaseFee != nil { blockCtx.BaseFee = diff.BaseFee.ToInt() } @@ -1104,6 +1198,7 @@ func doCallWithState(ctx context.Context, b Backend, args TransactionArgs, heade if err != nil { return nil, err } + evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}) if err != nil { return nil, err @@ -1127,18 +1222,22 @@ func doCallWithState(ctx context.Context, b Backend, args TransactionArgs, heade if evm.Cancelled() { return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) } + if err != nil { return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.GasLimit) } + return result, nil } func newRevertError(result *core.ExecutionResult) *revertError { reason, errUnpack := abi.UnpackRevert(result.Revert()) err := errors.New("execution reverted") + if errUnpack == nil { err = fmt.Errorf("execution reverted: %v", reason) } + return &revertError{ error: err, reason: hexutil.Encode(result.Revert()), @@ -1196,6 +1295,7 @@ func (s *BlockChainAPI) CallWithState(ctx context.Context, args TransactionArgs, if len(result.Revert()) > 0 { return nil, newRevertError(result) } + return result.Return(), result.Err } @@ -1219,13 +1319,16 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if err != nil { return 0, err } + if block == nil { return 0, errors.New("block not found") } + hi = block.GasLimit() } // Normalize the max fee per gas the call is willing to spend. var feeCap *big.Int + if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } else if args.GasPrice != nil { @@ -1241,14 +1344,18 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if err != nil { return 0, err } + balance := state.GetBalance(*args.From) // from can't be nil + available := new(big.Int).Set(balance) if args.Value != nil { if args.Value.ToInt().Cmp(available) >= 0 { return 0, core.ErrInsufficientFundsForTransfer } + available.Sub(available, args.Value.ToInt()) } + allowance := new(big.Int).Div(available, feeCap) // If the allowance is larger than maximum uint64, skip checking @@ -1257,8 +1364,10 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if transfer == nil { transfer = new(hexutil.Big) } + log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, "sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance) + hi = allowance.Uint64() } } @@ -1267,6 +1376,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr log.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) hi = gasCap } + cap = hi // Create a helper to check if a gas allowance results in an executable transaction @@ -1278,8 +1388,10 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit } + return true, nil, err // Bail out } + return result.Failed(), result, nil } // Execute the binary search and hone in on an executable gas limit @@ -1293,6 +1405,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if err != nil { return 0, err } + if failed { lo = mid } else { @@ -1305,17 +1418,20 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if err != nil { return 0, err } + if failed { if result != nil && result.Err != vm.ErrOutOfGas { if len(result.Revert()) > 0 { return 0, newRevertError(result) } + return 0, result.Err } // Otherwise, the specified gas cap is too low return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) } } + return hexutil.Uint64(hi), nil } @@ -1326,6 +1442,7 @@ func (s *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, b if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } + return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) } @@ -1365,28 +1482,35 @@ func FormatLogs(logs []logger.StructLog) []StructLogRes { Depth: trace.Depth, Error: trace.ErrorString(), } + if trace.Stack != nil { stack := make([]string, len(trace.Stack)) for i, stackValue := range trace.Stack { stack[i] = stackValue.Hex() } + formatted[index].Stack = &stack } + if trace.Memory != nil { memory := make([]string, 0, (len(trace.Memory)+31)/32) for i := 0; i+32 <= len(trace.Memory); i += 32 { memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) } + formatted[index].Memory = &memory } + if trace.Storage != nil { storage := make(map[string]string) for i, storageValue := range trace.Storage { storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) } + formatted[index].Storage = &storage } } + return formatted } @@ -1439,25 +1563,32 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param return newRPCTransactionFromBlockHash(block, tx.Hash(), config, db), nil } } + txs := block.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 := block.Uncles() uncleHashes := make([]common.Hash, len(uncles)) + for i, uncle := range uncles { uncleHashes[i] = uncle.Hash() } + fields["uncles"] = uncleHashes if block.Header().WithdrawalsHash != nil { fields["withdrawals"] = block.Withdrawals() } + return fields, nil } @@ -1466,6 +1597,7 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param func (s *BlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} { fields := RPCMarshalHeader(header) fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, header.Hash())) + return fields } @@ -1476,9 +1608,11 @@ func (s *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inc if err != nil { return nil, err } + if inclTx { fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, b.Hash())) } + return fields, err } @@ -1511,6 +1645,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber signer := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber)) from, _ := types.Sender(signer, tx) v, r, s := tx.RawSignatureValues() + result := &RPCTransaction{ Type: hexutil.Uint64(tx.Type()), From: from, @@ -1530,6 +1665,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } + switch tx.Type() { case types.LegacyTxType: // if a legacy transaction has an EIP-155 chain id, include it explicitly @@ -1555,17 +1691,21 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) } } + return result } // NewRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation func NewRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction { var baseFee *big.Int + blockNumber := uint64(0) + if current != nil { baseFee = misc.CalcBaseFee(config, current) blockNumber = current.Number.Uint64() } + return newRPCTransaction(tx, common.Hash{}, blockNumber, 0, baseFee, config) } @@ -1613,7 +1753,9 @@ func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.By if index >= uint64(len(txs)) { return nil } + blob, _ := txs[index].MarshalBinary() + return blob } @@ -1624,6 +1766,7 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash, config *pa return newRPCTransactionFromBlockIndex(b, uint64(idx), config, db) } } + return nil } @@ -1643,14 +1786,17 @@ func (s *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionAr if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } + acl, gasUsed, vmerr, err := AccessList(ctx, s.b, bNrOrHash, args) if err != nil { return nil, err } + result := &accessListResult{Accesslist: &acl, GasUsed: hexutil.Uint64(gasUsed)} if vmerr != nil { result.Error = vmerr.Error() } + return result, nil } @@ -1673,12 +1819,14 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH if err := args.setDefaults(ctx, b); err != nil { return nil, 0, nil, err } + var to common.Address if args.To != nil { to = *args.To } else { to = crypto.CreateAddress(args.from(), uint64(*args.Nonce)) } + isPostMerge := header.Difficulty.Cmp(common.Big0) == 0 // Retrieve the precompiles since they don't need to be added to the access list precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time)) @@ -1688,6 +1836,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH if args.AccessList != nil { prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles) } + for { // Retrieve the current access list to expand accessList := prevTracer.AccessList() @@ -1697,6 +1846,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH statedb := db.Copy() // Set the accesslist to the last al args.AccessList = &accessList + msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee) if err != nil { return nil, 0, nil, err @@ -1705,6 +1855,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH // Apply the transaction with the access list tracer tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) config := vm.Config{Tracer: tracer, NoBaseFee: true} + vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config) if err != nil { return nil, 0, nil, err @@ -1763,8 +1914,10 @@ func (s *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, b if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil { txs, _ := s.getAllBlockTransactions(ctx, block) n := hexutil.Uint(len(txs)) + return &n } + return nil } @@ -1773,8 +1926,10 @@ func (s *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blo if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil { txs, _ := s.getAllBlockTransactions(ctx, block) n := hexutil.Uint(len(txs)) + return &n } + return nil } @@ -1783,6 +1938,7 @@ func (s *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil { return newRPCTransactionFromBlockIndex(block, uint64(index), s.b.ChainConfig(), s.b.ChainDb()) } + return nil } @@ -1791,6 +1947,7 @@ func (s *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil { return newRPCTransactionFromBlockIndex(block, uint64(index), s.b.ChainConfig(), s.b.ChainDb()) } + return nil } @@ -1799,6 +1956,7 @@ func (s *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Cont if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil { return newRPCRawTransactionFromBlockIndex(block, uint64(index)) } + return nil } @@ -1807,6 +1965,7 @@ func (s *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Contex if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil { return newRPCRawTransactionFromBlockIndex(block, uint64(index)) } + return nil } @@ -1818,6 +1977,7 @@ func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common if err != nil { return nil, err } + return (*hexutil.Uint64)(&nonce), nil } // Resolve block number and use its state to ask for the nonce @@ -1825,7 +1985,9 @@ func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common if state == nil || err != nil { return nil, err } + nonce := state.GetNonce(address) + return (*hexutil.Uint64)(&nonce), state.Error() } @@ -1836,7 +1998,6 @@ func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.H // Try to return an already finalized transaction tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash) if err != nil { - return nil, err } // fetch bor block tx if necessary @@ -1853,6 +2014,7 @@ func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.H if err != nil { return nil, err } + resultTx := newRPCTransaction(tx, blockHash, blockNumber, index, header.BaseFee, s.b.ChainConfig()) if borTx { @@ -1879,6 +2041,7 @@ func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash commo if err != nil { return nil, err } + if tx == nil { if tx = s.b.GetPoolTransaction(hash); tx == nil { // Transaction not found anywhere, abort @@ -1913,9 +2076,11 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. if err != nil { return nil, err } + if uint64(len(receipts)) <= index { return nil, nil } + receipt = receipts[index] } @@ -1946,6 +2111,7 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. } else { fields["status"] = hexutil.Uint(receipt.Status) } + if receipt.Logs == nil { fields["logs"] = []*types.Log{} } @@ -1954,6 +2120,7 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. if receipt.ContractAddress != (common.Address{}) { fields["contractAddress"] = receipt.ContractAddress } + return fields, nil } @@ -1977,10 +2144,12 @@ 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 } @@ -1998,6 +2167,7 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c } else { log.Info("Submitted transaction", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "recipient", tx.To(), "value", tx.Value()) } + return tx.Hash(), nil } @@ -2030,6 +2200,7 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr if err != nil { return common.Hash{}, err } + return SubmitTransaction(ctx, s.b, signed) } @@ -2043,10 +2214,12 @@ func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionAr } // Assemble the transaction and obtain rlp tx := args.toTransaction() + data, err := tx.MarshalBinary() if err != nil { return nil, err } + return &SignTransactionResult{data, tx}, nil } @@ -2057,6 +2230,7 @@ func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.B if err := tx.UnmarshalBinary(input); err != nil { return common.Hash{}, err } + return SubmitTransaction(ctx, s.b, tx) } @@ -2082,6 +2256,7 @@ func (s *TransactionAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil. if err == nil { signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper } + return signature, err } @@ -2098,12 +2273,15 @@ func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionAr if args.Gas == nil { return nil, fmt.Errorf("gas not specified") } + if args.GasPrice == nil && (args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil) { return nil, fmt.Errorf("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas") } + if args.Nonce == nil { return nil, fmt.Errorf("nonce not specified") } + if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } @@ -2112,14 +2290,17 @@ func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionAr if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { return nil, err } + signed, err := s.sign(args.from(), tx) if err != nil { return nil, err } + data, err := signed.MarshalBinary() if err != nil { return nil, err } + return &SignTransactionResult{data, signed}, nil } @@ -2130,20 +2311,25 @@ func (s *TransactionAPI) PendingTransactions() ([]*RPCTransaction, error) { if err != nil { return nil, err } + accounts := make(map[common.Address]struct{}) + for _, wallet := range s.b.AccountManager().Wallets() { for _, account := range wallet.Accounts() { accounts[account.Address] = struct{}{} } } + curHeader := s.b.CurrentHeader() transactions := make([]*RPCTransaction, 0, len(pending)) + for _, tx := range pending { from, _ := types.Sender(s.signer, tx) if _, exists := accounts[from]; exists { transactions = append(transactions, NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())) } } + return transactions, nil } @@ -2153,9 +2339,11 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g if sendArgs.Nonce == nil { return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec") } + if err := sendArgs.setDefaults(ctx, s.b); err != nil { return common.Hash{}, err } + matchTx := sendArgs.toTransaction() // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. @@ -2163,10 +2351,12 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g if gasPrice != nil { price = gasPrice.ToInt() } + var gas = matchTx.Gas() if gasLimit != nil { gas = uint64(*gasLimit) } + if err := checkTxFee(price, gas, s.b.RPCTxFeeCap()); err != nil { return common.Hash{}, err } @@ -2175,6 +2365,7 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g if err != nil { return common.Hash{}, err } + for _, p := range pending { wantSigHash := s.signer.Hash(matchTx) pFrom, err := types.Sender(s.signer, p) @@ -2182,24 +2373,30 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g if err != nil && (s.b.UnprotectedAllowed() && err == types.ErrInvalidChainId) { err = nil } + 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 } + if gasLimit != nil && *gasLimit != 0 { sendArgs.Gas = gasLimit } + signedTx, err := s.sign(sendArgs.from(), sendArgs.toTransaction()) if err != nil { return common.Hash{}, err } + if err = s.b.SendTx(ctx, signedTx); err != nil { return common.Hash{}, err } + return signedTx.Hash(), nil } } + return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash()) } @@ -2224,12 +2421,15 @@ func (api *DebugAPI) GetRawHeader(ctx context.Context, blockNrOrHash rpc.BlockNu if err != nil { return nil, err } + hash = block.Hash() } + header, _ := api.b.HeaderByHash(ctx, hash) if header == nil { return nil, fmt.Errorf("header #%d not found", hash) } + return rlp.EncodeToBytes(header) } @@ -2243,12 +2443,15 @@ func (api *DebugAPI) GetRawBlock(ctx context.Context, blockNrOrHash rpc.BlockNum if err != nil { return nil, err } + hash = block.Hash() } + block, _ := api.b.BlockByHash(ctx, hash) if block == nil { return nil, fmt.Errorf("block #%d not found", hash) } + return rlp.EncodeToBytes(block) } @@ -2262,20 +2465,26 @@ func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.Block if err != nil { return nil, err } + hash = block.Hash() } + receipts, err := api.b.GetReceipts(ctx, hash) if err != nil { return nil, err } + result := make([]hexutil.Bytes, len(receipts)) + for i, receipt := range receipts { b, err := receipt.MarshalBinary() if err != nil { return nil, err } + result[i] = b } + return result, nil } @@ -2286,12 +2495,14 @@ func (s *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hex if err != nil { return nil, err } + if tx == nil { if tx = s.b.GetPoolTransaction(hash); tx == nil { // Transaction not found anywhere, abort return nil, nil } } + return tx.MarshalBinary() } @@ -2301,6 +2512,7 @@ func (api *DebugAPI) PrintBlock(ctx context.Context, number uint64) (string, err if block == nil { return "", fmt.Errorf("block #%d not found", number) } + return spew.Sdump(block), nil } @@ -2310,6 +2522,7 @@ func (api *DebugAPI) SeedHash(ctx context.Context, number uint64) (string, error if block == nil { return "", fmt.Errorf("block #%d not found", number) } + return fmt.Sprintf("%#x", ethash.SeedHash(number)), nil } @@ -2320,6 +2533,7 @@ func (api *DebugAPI) ChaindbProperty(property string) (string, error) { } else if !strings.HasPrefix(property, "leveldb.") { property = "leveldb." + property } + return api.b.ChainDb().Stat(property) } @@ -2328,11 +2542,13 @@ func (api *DebugAPI) ChaindbProperty(property string) (string, error) { func (api *DebugAPI) ChaindbCompact() error { for b := byte(0); b < 255; b++ { log.Info("Compacting chain database", "range", fmt.Sprintf("0x%0.2X-0x%0.2X", b, b+1)) + if err := api.b.ChainDb().Compact([]byte{b}, []byte{b + 1}); err != nil { log.Error("Database compaction failed", "err", err) return err } } + return nil } @@ -2400,11 +2616,14 @@ func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { if cap == 0 { return nil } + feeEth := new(big.Float).Quo(new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))), new(big.Float).SetInt(big.NewInt(params.Ether))) + feeFloat, _ := feeEth.Float64() if feeFloat > cap { return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap) } + return nil } @@ -2414,5 +2633,6 @@ func toHexSlice(b [][]byte) []string { for i := range b { r[i] = hexutil.Encode(b[i]) } + return r } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 762dc8337d..9dacacc3be 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -34,9 +34,12 @@ func TestTransaction_RoundTripRpcJSON(t *testing.T) { key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") tests = allTransactionTypes(common.Address{0xde, 0xad}, config) ) + t.Parallel() + for i, tt := range tests { var tx2 types.Transaction + tx, err := types.SignNewTx(key, signer, tt) if err != nil { t.Fatalf("test %d: signing failed: %v", i, err) diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index a6ac0aa768..57399dde8b 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -113,6 +113,7 @@ type Backend interface { func GetAPIs(apiBackend Backend) []rpc.API { nonceLock := new(AddrLocker) + return []rpc.API{ { Namespace: "eth", diff --git a/internal/ethapi/bor_api.go b/internal/ethapi/bor_api.go index 73b1ac8a30..aaf5733f61 100644 --- a/internal/ethapi/bor_api.go +++ b/internal/ethapi/bor_api.go @@ -13,6 +13,7 @@ func (s *BlockChainAPI) GetRootHash(ctx context.Context, starBlockNr uint64, end if err != nil { return "", err } + return root, nil } @@ -27,9 +28,11 @@ func (s *BlockChainAPI) GetBorBlockReceipt(ctx context.Context, hash common.Hash func (s *BlockChainAPI) appendRPCMarshalBorTransaction(ctx context.Context, block *types.Block, fields map[string]interface{}, fullTx bool) map[string]interface{} { if block != nil { txHash := types.GetDerivedBorTxHash(types.BorReceiptKey(block.Number().Uint64(), block.Hash())) + borTx, blockHash, blockNumber, txIndex, _ := s.b.GetBorBlockTransactionWithBlockHash(ctx, txHash, block.Hash()) if borTx != nil { formattedTxs := fields["transactions"].([]interface{}) + if fullTx { marshalledTx := newRPCTransaction(borTx, blockHash, blockNumber, txIndex, block.BaseFee(), s.b.ChainConfig()) // newRPCTransaction calculates hash based on RLP of the transaction data. @@ -41,5 +44,6 @@ func (s *BlockChainAPI) appendRPCMarshalBorTransaction(ctx context.Context, bloc } } } + return fields } diff --git a/internal/ethapi/dbapi.go b/internal/ethapi/dbapi.go index 33fda936dc..d53d8a48a4 100644 --- a/internal/ethapi/dbapi.go +++ b/internal/ethapi/dbapi.go @@ -27,6 +27,7 @@ func (api *DebugAPI) DbGet(key string) (hexutil.Bytes, error) { if err != nil { return nil, err } + return api.b.ChainDb().Get(blob) } diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 7d09488539..fb5ef1dea6 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -60,6 +60,7 @@ func (args *TransactionArgs) from() common.Address { if args.From == nil { return common.Address{} } + return *args.From } @@ -81,6 +82,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { if err := args.setFeeDefaults(ctx, b); err != nil { return err } + if args.Value == nil { args.Value = new(hexutil.Big) } @@ -138,6 +140,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { } else { args.ChainID = (*hexutil.Big)(want) } + return nil } @@ -156,6 +159,7 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) erro if args.GasPrice == nil && args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas) } + return nil } // Now attempt to fill in default value depending on whether London is active or not. @@ -174,8 +178,10 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) erro if err != nil { return err } + args.GasPrice = (*hexutil.Big)(price) } + return nil } @@ -187,6 +193,7 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ if err != nil { return err } + args.MaxPriorityFeePerGas = (*hexutil.Big)(tip) } // Set maxFeePerGas if it is missing. @@ -292,6 +299,7 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (* if args.AccessList != nil { accessList = *args.AccessList } + msg := &core.Message{ From: addr, To: args.To, @@ -304,6 +312,7 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (* AccessList: accessList, SkipAccountChecks: true, } + return msg, nil } @@ -318,6 +327,7 @@ func (args *TransactionArgs) toTransaction() *types.Transaction { if args.AccessList != nil { al = *args.AccessList } + data = &types.DynamicFeeTx{ To: args.To, ChainID: (*big.Int)(args.ChainID), diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index ae8fa97f72..b8785a6773 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -189,13 +189,16 @@ func TestSetFeeDefaults(t *testing.T) { } ctx := context.Background() + for i, test := range tests { if test.isLondon { b.activateLondon() } else { b.deactivateLondon() } + got := test.in + err := got.setFeeDefaults(ctx, b) if err != nil && err.Error() == test.err.Error() { // Test threw expected error. @@ -203,6 +206,7 @@ func TestSetFeeDefaults(t *testing.T) { } else if err != nil { t.Fatalf("test %d (%s): unexpected error: %s", i, test.name, err) } + if !reflect.DeepEqual(got, test.want) { t.Fatalf("test %d (%s): did not fill defaults as expected: (got: %v, want: %v)", i, test.name, got, test.want) } @@ -231,6 +235,7 @@ func newBackendMock() *backendMock { BerlinBlock: big.NewInt(0), LondonBlock: big.NewInt(1000), } + return &backendMock{ current: &types.Header{ Difficulty: big.NewInt(10000000000), diff --git a/internal/flags/flags.go b/internal/flags/flags.go index edcdccdd03..a70e46239d 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -83,6 +83,7 @@ func (f *DirectoryFlag) Apply(set *flag.FlagSet) error { eachName(f, func(name string) { set.Var(&f.Value, f.Name, f.Usage) }) + return nil } @@ -109,6 +110,7 @@ func (f *DirectoryFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } + return f.GetValue() } @@ -126,7 +128,9 @@ func (v textMarshalerVal) String() string { if v.v == nil { return "" } + text, _ := v.v.MarshalText() + return string(text) } @@ -169,6 +173,7 @@ func (f *TextMarshalerFlag) Apply(set *flag.FlagSet) error { eachName(f, func(name string) { set.Var(textMarshalerVal{f.Value}, f.Name, f.Usage) }) + return nil } @@ -195,6 +200,7 @@ func (f *TextMarshalerFlag) GetValue() string { if err != nil { return "(ERR: " + err.Error() + ")" } + return string(t) } @@ -202,6 +208,7 @@ func (f *TextMarshalerFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } + return f.GetValue() } @@ -211,6 +218,7 @@ func GlobalTextMarshaler(ctx *cli.Context, name string) TextMarshaler { if val == nil { return nil } + return val.(textMarshalerVal).v } @@ -278,6 +286,7 @@ func (f *BigFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } + return f.GetValue() } @@ -288,6 +297,7 @@ func (b *bigValue) String() string { if b == nil { return "" } + return (*big.Int)(b).String() } @@ -296,7 +306,9 @@ func (b *bigValue) Set(s string) error { if !ok { return errors.New("invalid integer syntax") } + *b = (bigValue)(*intVal) + return nil } @@ -306,6 +318,7 @@ func GlobalBig(ctx *cli.Context, name string) *big.Int { if val == nil { return nil } + return (*big.Int)(val.(*bigValue)) } @@ -319,11 +332,13 @@ func expandPath(p string) string { if strings.HasPrefix(p, `\\.\pipe`) { return p } + if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { if home := HomeDir(); home != "" { p = home + p[1:] } } + return filepath.Clean(os.ExpandEnv(p)) } @@ -331,9 +346,11 @@ func HomeDir() string { if home := os.Getenv("HOME"); home != "" { return home } + if usr, err := user.Current(); err == nil { return usr.HomeDir } + return "" } diff --git a/internal/flags/flags_test.go b/internal/flags/flags_test.go index 3fe92c84cc..a74d7e8c49 100644 --- a/internal/flags/flags_test.go +++ b/internal/flags/flags_test.go @@ -26,6 +26,7 @@ func TestPathExpansion(t *testing.T) { t.Parallel() user, _ := user.Current() + var tests map[string]string if runtime.GOOS == "windows" { @@ -53,6 +54,7 @@ func TestPathExpansion(t *testing.T) { } t.Setenv(`DDDXXX`, `/tmp`) + for test, expected := range tests { got := expandPath(test) if got != expected { diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go index 1ce7156ba0..8d7b153fe4 100644 --- a/internal/flags/helpers.go +++ b/internal/flags/helpers.go @@ -38,6 +38,7 @@ func NewApp(usage string) *cli.App { MigrateGlobalFlags(ctx) return nil } + return app } @@ -47,6 +48,7 @@ func Merge(groups ...[]cli.Flag) []cli.Flag { for _, group := range groups { ret = append(ret, group...) } + return ret } @@ -74,7 +76,9 @@ func MigrateGlobalFlags(ctx *cli.Context) { if _, ok := migrationApplied[cmd]; ok { continue } + migrationApplied[cmd] = struct{}{} + fn(cmd) iterate(cmd.Subcommands, fn) } @@ -98,11 +102,13 @@ func doMigrateFlags(ctx *cli.Context) { // Figure out if there are any aliases of commands. If there are, we want // to ignore them when iterating over the flags. var aliases = make(map[string]bool) + for _, fl := range ctx.Command.Flags { for _, alias := range fl.Names()[1:] { aliases[alias] = true } } + for _, name := range ctx.FlagNames() { for _, parent := range ctx.Lineage()[1:] { if parent.IsSet(name) { @@ -123,6 +129,7 @@ func doMigrateFlags(ctx *cli.Context) { } else { ctx.Set(name, parent.String(name)) } + break } } @@ -142,6 +149,7 @@ func FlagString(f cli.Flag) string { needsPlaceholder := df.TakesValue() placeholder := "" + if needsPlaceholder { placeholder = "value" } @@ -155,6 +163,7 @@ func FlagString(f cli.Flag) string { usage := strings.TrimSpace(df.GetUsage()) envHint := strings.TrimSpace(cli.FlagEnvHinter(df.GetEnvVars(), "")) + if len(envHint) > 0 { usage += " " + envHint } @@ -169,6 +178,7 @@ func pad(s string, length int) string { if len(s) < length { s += strings.Repeat(" ", length-len(s)) } + return s } @@ -185,30 +195,38 @@ func wordWrap(s string, width int) string { for { sp := strings.IndexByte(s, ' ') + var word string + if sp == -1 { word = s } else { word = s[:sp] } + wlen := len(word) + over := lineLength+wlen >= width if over { output.WriteByte('\n') + lineLength = 0 } else { if lineLength != 0 { output.WriteByte(' ') + lineLength++ } } output.WriteString(word) + lineLength += wlen if sp == -1 { break } + s = s[wlen+1:] } diff --git a/internal/guide/guide_test.go b/internal/guide/guide_test.go index f682daac91..64557e6f39 100644 --- a/internal/guide/guide_test.go +++ b/internal/guide/guide_test.go @@ -70,6 +70,7 @@ func TestAccountManagement(t *testing.T) { if err != nil { t.Fatalf("Failed to create signer account: %v", err) } + tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil) chain := big.NewInt(1) @@ -81,9 +82,11 @@ func TestAccountManagement(t *testing.T) { if err := ks.Unlock(signer, "Signer password"); err != nil { t.Fatalf("Failed to unlock account: %v", err) } + if _, err := ks.SignTx(signer, tx, chain); err != nil { t.Fatalf("Failed to sign with unlocked account: %v", err) } + if err := ks.Lock(signer.Address); err != nil { t.Fatalf("Failed to lock account: %v", err) } @@ -91,6 +94,7 @@ func TestAccountManagement(t *testing.T) { if err := ks.TimedUnlock(signer, "Signer password", time.Second); err != nil { t.Fatalf("Failed to time unlock account: %v", err) } + if _, err := ks.SignTx(signer, tx, chain); err != nil { t.Fatalf("Failed to sign with time unlocked account: %v", err) } diff --git a/internal/jsre/completion.go b/internal/jsre/completion.go index 844a0532fd..6673833296 100644 --- a/internal/jsre/completion.go +++ b/internal/jsre/completion.go @@ -31,9 +31,11 @@ var numerical = regexp.MustCompile(`^(NaN|-?((\d*\.\d+|\d+)([Ee][+-]?\d+)?|Infin // evaluated, callers need to make sure that evaluating line does not have side effects. func (jsre *JSRE) CompleteKeywords(line string) []string { var results []string + jsre.Do(func(vm *goja.Runtime) { results = getCompletions(vm, line) }) + return results } @@ -46,14 +48,17 @@ func getCompletions(vm *goja.Runtime, line string) (results []string) { // Find the right-most fully named object in the line. e.g. if line = "x.y.z" // and "x.y" is an object, obj will reference "x.y". obj := vm.GlobalObject() + for i := 0; i < len(parts)-1; i++ { if numerical.MatchString(parts[i]) { return nil } + v := obj.Get(parts[i]) if v == nil || goja.IsNull(v) || goja.IsUndefined(v) { return nil // No object was found } + obj = v.ToObject(vm) } @@ -61,6 +66,7 @@ func getCompletions(vm *goja.Runtime, line string) (results []string) { // Example: if line = "x.y.z" and "x.y" exists and has keys "zebu", "zebra" // and "platypus", then "x.y.zebu" and "x.y.zebra" will be added to results. prefix := parts[len(parts)-1] + iterOwnAndConstructorKeys(vm, obj, func(k string) { if strings.HasPrefix(k, prefix) { if len(parts) == 1 { @@ -89,5 +95,6 @@ func getCompletions(vm *goja.Runtime, line string) (results []string) { } sort.Strings(results) + return results } diff --git a/internal/jsre/completion_test.go b/internal/jsre/completion_test.go index 953bc5026d..9178195797 100644 --- a/internal/jsre/completion_test.go +++ b/internal/jsre/completion_test.go @@ -84,6 +84,7 @@ func TestCompleteKeywords(t *testing.T) { }, }, } + for _, test := range tests { cs := re.CompleteKeywords(test.input) if !reflect.DeepEqual(cs, test.want) { diff --git a/internal/jsre/jsre.go b/internal/jsre/jsre.go index f6e21d2ef7..a6562ed544 100644 --- a/internal/jsre/jsre.go +++ b/internal/jsre/jsre.go @@ -80,6 +80,7 @@ func New(assetPath string, output io.Writer) *JSRE { go re.runEventLoop() re.Set("loadScript", MakeCallback(re.vm, re.loadScript)) re.Set("inspect", re.prettyPrintJS) + return re } @@ -87,11 +88,13 @@ func New(assetPath string, output io.Writer) *JSRE { func randomSource() *rand.Rand { bytes := make([]byte, 8) seed := time.Now().UnixNano() + if _, err := crand.Read(bytes); err == nil { seed = int64(binary.LittleEndian.Uint64(bytes)) } src := rand.NewSource(seed) + return rand.New(src) } @@ -118,6 +121,7 @@ func (re *JSRE) runEventLoop() { if 0 >= delay { delay = 1 } + timer := &jsTimer{ duration: time.Duration(delay) * time.Millisecond, call: call, @@ -148,8 +152,10 @@ func (re *JSRE) runEventLoop() { timer.timer.Stop() delete(registry, timer) } + return goja.Undefined() } + re.vm.Set("_setTimeout", setTimeout) re.vm.Set("_setInterval", setInterval) re.vm.RunString(`var setTimeout = function(args) { @@ -258,6 +264,7 @@ func (re *JSRE) Exec(file string) error { if err != nil { return err } + return re.Compile(file, string(code)) } @@ -280,6 +287,7 @@ func MakeCallback(vm *goja.Runtime, fn func(Call) (goja.Value, error)) goja.Valu if err != nil { panic(vm.NewGoError(err)) } + return result }) } @@ -293,6 +301,7 @@ func (re *JSRE) Evaluate(code string, w io.Writer) { } else { prettyPrint(vm, val, w) } + fmt.Fprintln(w) }) } @@ -321,13 +330,16 @@ func (re *JSRE) loadScript(call Call) (goja.Value, error) { file := call.Argument(0).ToString().String() file = common.AbsolutePath(re.assetPath, file) source, err := os.ReadFile(file) + if err != nil { return nil, fmt.Errorf("could not read file %s: %v", file, err) } + value, err := compileAndRun(re.vm, file, string(source)) if err != nil { return nil, fmt.Errorf("error while compiling or running script: %v", err) } + return value, nil } @@ -336,5 +348,6 @@ func compileAndRun(vm *goja.Runtime, filename string, src string) (goja.Value, e if err != nil { return goja.Null(), err } + return vm.RunProgram(script) } diff --git a/internal/jsre/jsre_test.go b/internal/jsre/jsre_test.go index 9e34eb2362..d1d83d0975 100644 --- a/internal/jsre/jsre_test.go +++ b/internal/jsre/jsre_test.go @@ -48,6 +48,7 @@ func newWithTestJS(t *testing.T, testjs string) *JSRE { t.Fatal("cannot create test.js:", err) } } + jsre := New(dir, os.Stdout) return jsre @@ -60,18 +61,23 @@ func TestExec(t *testing.T) { if err != nil { t.Errorf("expected no error, got %v", err) } + val, err := jsre.Run("msg") if err != nil { t.Errorf("expected no error, got %v", err) } + if val.ExportType().Kind() != reflect.String { t.Errorf("expected string value, got %v", val) } + exp := "testMsg" got := val.ToString().String() + if exp != got { t.Errorf("expected '%v', got '%v'", exp, got) } + jsre.Stop(false) } @@ -82,19 +88,25 @@ func TestNatto(t *testing.T) { if err != nil { t.Fatalf("expected no error, got %v", err) } + time.Sleep(100 * time.Millisecond) + val, err := jsre.Run("msg") if err != nil { t.Fatalf("expected no error, got %v", err) } + if val.ExportType().Kind() != reflect.String { t.Fatalf("expected string value, got %v", val) } + exp := "testMsg" got := val.ToString().String() + if exp != got { t.Fatalf("expected '%v', got '%v'", exp, got) } + jsre.Stop(false) } @@ -117,17 +129,22 @@ func TestLoadScript(t *testing.T) { if err != nil { t.Errorf("expected no error, got %v", err) } + val, err := jsre.Run("msg") if err != nil { t.Errorf("expected no error, got %v", err) } + if val.ExportType().Kind() != reflect.String { t.Errorf("expected string value, got %v", val) } + exp := "testMsg" got := val.ToString().String() + if exp != got { t.Errorf("expected '%v', got '%v'", exp, got) } + jsre.Stop(false) } diff --git a/internal/jsre/pretty.go b/internal/jsre/pretty.go index bd772b4927..392566fd2d 100644 --- a/internal/jsre/pretty.go +++ b/internal/jsre/pretty.go @@ -63,6 +63,7 @@ func prettyError(vm *goja.Runtime, err error, w io.Writer) { if gojaErr, ok := err.(*goja.Exception); ok { failure = gojaErr.String() } + fmt.Fprint(w, ErrorColor("%s", failure)) } @@ -71,6 +72,7 @@ func (re *JSRE) prettyPrintJS(call goja.FunctionCall) goja.Value { prettyPrint(re.vm, v, re.output) fmt.Fprintln(re.output) } + return goja.Undefined() } @@ -88,7 +90,9 @@ func (ctx ppctx) printValue(v goja.Value, level int, inArray bool) { fmt.Fprint(ctx.w, SpecialColor(v.String())) return } + kind := v.ExportType().Kind() + switch { case kind == reflect.Bool: fmt.Fprint(ctx.w, SpecialColor("%t", v.ToBoolean())) @@ -114,6 +118,7 @@ func SafeGet(obj *goja.Object, key string) (ret goja.Value) { ret = goja.Undefined() } }() + ret = obj.Get(key) return ret @@ -123,21 +128,26 @@ func (ctx ppctx) printObject(obj *goja.Object, level int, inArray bool) { switch obj.ClassName() { case "Array", "GoArray": lv := obj.Get("length") + len := lv.ToInteger() if len == 0 { fmt.Fprintf(ctx.w, "[]") return } + if level > maxPrettyPrintLevel { fmt.Fprint(ctx.w, "[...]") return } + fmt.Fprint(ctx.w, "[") + for i := int64(0); i < len; i++ { el := obj.Get(strconv.FormatInt(i, 10)) if el != nil { ctx.printValue(el, level+1, true) } + if i < len-1 { fmt.Fprintf(ctx.w, ", ") } @@ -156,23 +166,30 @@ func (ctx ppctx) printObject(obj *goja.Object, level int, inArray bool) { fmt.Fprint(ctx.w, "{}") return } + if level > maxPrettyPrintLevel { fmt.Fprint(ctx.w, "{...}") return } + fmt.Fprintln(ctx.w, "{") + for i, k := range keys { v := SafeGet(obj, k) fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k) ctx.printValue(v, level+1, false) + if i < len(keys)-1 { fmt.Fprintf(ctx.w, ",") } + fmt.Fprintln(ctx.w) } + if inArray { level-- } + fmt.Fprintf(ctx.w, "%s}", ctx.indent(level)) case "Function": @@ -199,10 +216,12 @@ func (ctx ppctx) fields(obj *goja.Object) []string { vals, methods []string seen = make(map[string]bool) ) + add := func(k string) { if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") { return } + seen[k] = true key := SafeGet(obj, k) @@ -223,15 +242,19 @@ func (ctx ppctx) fields(obj *goja.Object) []string { iterOwnAndConstructorKeys(ctx.vm, obj, add) sort.Strings(vals) sort.Strings(methods) + return append(vals, methods...) } func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) { seen := make(map[string]bool) + iterOwnKeys(vm, obj, func(prop string) { seen[prop] = true + f(prop) }) + if cp := constructorPrototype(vm, obj); cp != nil { iterOwnKeys(vm, cp, func(prop string) { if !seen[prop] { @@ -243,14 +266,17 @@ func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string func iterOwnKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) { Object := vm.Get("Object").ToObject(vm) + getOwnPropertyNames, isFunc := goja.AssertFunction(Object.Get("getOwnPropertyNames")) if !isFunc { panic(vm.ToValue("Object.getOwnPropertyNames isn't a function")) } + rv, err := getOwnPropertyNames(goja.Null(), obj) if err != nil { panic(vm.ToValue(fmt.Sprintf("Error getting object properties: %v", err))) } + gv := rv.Export() switch gv := gv.(type) { case []interface{}: @@ -278,12 +304,16 @@ func (ctx ppctx) isBigNumber(v *goja.Object) bool { if BigNumber == nil { return false } + prototype := BigNumber.Get("prototype").ToObject(ctx.vm) + isPrototypeOf, callable := goja.AssertFunction(prototype.Get("isPrototypeOf")) if !callable { return false } + bv, _ := isPrototypeOf(prototype, v) + return bv.ToBoolean() } @@ -297,5 +327,6 @@ func constructorPrototype(vm *goja.Runtime, obj *goja.Object) *goja.Object { return v.ToObject(vm) } } + return nil } diff --git a/internal/shutdowncheck/shutdown_tracker.go b/internal/shutdowncheck/shutdown_tracker.go index c95b4f02f4..6b16ff5b65 100644 --- a/internal/shutdowncheck/shutdown_tracker.go +++ b/internal/shutdowncheck/shutdown_tracker.go @@ -52,6 +52,7 @@ func (t *ShutdownTracker) MarkStartup() { 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, @@ -65,6 +66,7 @@ func (t *ShutdownTracker) Start() { go func() { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() + for { select { case <-ticker.C: diff --git a/internal/syncx/mutex.go b/internal/syncx/mutex.go index 96a21986c6..20da235587 100644 --- a/internal/syncx/mutex.go +++ b/internal/syncx/mutex.go @@ -26,6 +26,7 @@ type ClosableMutex struct { func NewClosableMutex() *ClosableMutex { ch := make(chan struct{}, 1) ch <- struct{}{} + return &ClosableMutex{ch} } @@ -60,5 +61,6 @@ func (cm *ClosableMutex) Close() { if !ok { panic("Close of already-closed ClosableMutex") } + close(cm.ch) } diff --git a/internal/testlog/testlog.go b/internal/testlog/testlog.go index 93d6f27086..50f4112900 100644 --- a/internal/testlog/testlog.go +++ b/internal/testlog/testlog.go @@ -77,6 +77,7 @@ func Logger(t *testing.T, level log.Lvl) log.Logger { h: &bufHandler{fmt: log.TerminalFormat(false)}, } l.l.SetHandler(log.LvlFilterHandler(level, l.h)) + return l } @@ -143,9 +144,11 @@ func (l *logger) SetHandler(h log.Handler) { // flush writes all buffered messages and clears the buffer. func (l *logger) flush() { l.t.Helper() + for _, r := range l.h.buf { l.t.Logf("%s", l.h.fmt.Format(r)) } + l.h.buf = nil } diff --git a/internal/utesting/utesting.go b/internal/utesting/utesting.go index ee99794c64..6520dfc564 100644 --- a/internal/utesting/utesting.go +++ b/internal/utesting/utesting.go @@ -48,15 +48,18 @@ type Result struct { // MatchTests returns the tests whose name matches a regular expression. func MatchTests(tests []Test, expr string) []Test { var results []Test + re, err := regexp.Compile(expr) if err != nil { return nil } + for _, test := range tests { if re.MatchString(test.Name) { results = append(results, test) } } + return results } @@ -66,9 +69,11 @@ func RunTests(tests []Test, report io.Writer) []Result { if report == nil { report = io.Discard } + results := run(tests, newConsoleOutput(report)) fails := CountFailures(results) fmt.Fprintf(report, "%v/%v tests passed.\n", len(tests)-fails, len(tests)) + return results } @@ -80,11 +85,13 @@ func RunTAP(tests []Test, report io.Writer) []Result { func run(tests []Test, output testOutput) []Result { var results = make([]Result, len(tests)) + for i, test := range tests { buffer := new(bytes.Buffer) logOutput := io.MultiWriter(buffer, output) output.testStart(test.Name) + start := time.Now() results[i].Name = test.Name results[i].Failed = runTest(test, logOutput) @@ -92,6 +99,7 @@ func run(tests []Test, output testOutput) []Result { results[i].Output = buffer.String() output.testResult(results[i]) } + return results } @@ -130,12 +138,14 @@ func (c *consoleOutput) Write(b []byte) (int, error) { fmt.Fprintln(c.out, "-- RUN", c.curTest) c.wroteHeader = true } + return c.indented.Write(b) } // testResult prints the final test result line. func (c *consoleOutput) testResult(r Result) { c.indented.flush() + pd := r.Duration.Truncate(100 * time.Microsecond) if r.Failed { fmt.Fprintf(c.out, "-- FAIL %s (%v)\n", r.Name, pd) @@ -153,6 +163,7 @@ type tapOutput struct { func newTAP(out io.Writer, numTests int) *tapOutput { fmt.Fprintf(out, "1..%d\n", numTests) + return &tapOutput{ out: out, indented: newIndentWriter("# ", out), @@ -173,6 +184,7 @@ func (t *tapOutput) testResult(r Result) { if r.Failed { status = "not ok" } + fmt.Fprintln(t.out, status, t.counter, r.Name) t.indented.Write([]byte(r.Output)) t.indented.flush() @@ -195,6 +207,7 @@ func (w *indentWriter) Write(b []byte) (n int, err error) { if _, err = io.WriteString(w.out, w.indent); err != nil { return n, err } + w.inLine = true } @@ -202,18 +215,22 @@ func (w *indentWriter) Write(b []byte) (n int, err error) { if end == -1 { nn, err := w.out.Write(b) n += nn + return n, err } line := b[:end+1] nn, err := w.out.Write(line) n += nn + if err != nil { return n, err } + b = b[end+1:] w.inLine = false } + return n, err } @@ -228,11 +245,13 @@ func (w *indentWriter) flush() { // CountFailures returns the number of failed tests in the result slice. func CountFailures(rr []Result) int { count := 0 + for _, r := range rr { if r.Failed { count++ } } + return count } @@ -240,12 +259,14 @@ func CountFailures(rr []Result) int { func Run(test Test) (bool, string) { output := new(bytes.Buffer) failed := runTest(test, output) + return failed, output.String() } func runTest(test Test, output io.Writer) bool { t := &T{output: output} done := make(chan struct{}) + go func() { defer close(done) defer func() { @@ -259,6 +280,7 @@ func runTest(test Test, output io.Writer) bool { test.Fn(t) }() <-done + return t.failed } @@ -291,6 +313,7 @@ func (t *T) Fail() { func (t *T) Failed() bool { t.mu.Lock() defer t.mu.Unlock() + return t.failed } @@ -307,9 +330,11 @@ func (t *T) Log(vs ...interface{}) { func (t *T) Logf(format string, vs ...interface{}) { t.mu.Lock() defer t.mu.Unlock() + if len(format) == 0 || format[len(format)-1] != '\n' { format += "\n" } + fmt.Fprintf(t.output, format, vs...) } diff --git a/internal/utesting/utesting_test.go b/internal/utesting/utesting_test.go index 31c7911c52..b9a7568dc3 100644 --- a/internal/utesting/utesting_test.go +++ b/internal/utesting/utesting_test.go @@ -48,9 +48,11 @@ func TestTest(t *testing.T) { if results[0].Failed || results[0].Output != "" { t.Fatalf("wrong result for successful test: %#v", results[0]) } + if !results[1].Failed || results[1].Output != "output\nfailed\n" { t.Fatalf("wrong result for failing test: %#v", results[1]) } + if !results[2].Failed || !strings.HasPrefix(results[2].Output, "panic: oh no\n") { t.Fatalf("wrong result for panicking test: %#v", results[2]) } @@ -91,6 +93,7 @@ var outputTests = []Test{ func TestOutput(t *testing.T) { var buf bytes.Buffer + RunTests(outputTests, &buf) want := regexp.MustCompile(` @@ -117,6 +120,7 @@ $`[1:]) func TestOutputTAP(t *testing.T) { var buf bytes.Buffer + RunTAP(outputTests, &buf) want := ` diff --git a/les/api.go b/les/api.go index 3b21b635ac..d863ae5205 100644 --- a/les/api.go +++ b/les/api.go @@ -53,6 +53,7 @@ func parseNode(node string) (enode.ID, error) { if id, err := enode.ParseID(node); err == nil { return id, nil } + if node, err := enode.Parse(enode.ValidSchemes, node); err == nil { return node.ID(), nil } else { @@ -68,12 +69,14 @@ func (api *LightServerAPI) ServerInfo() map[string]interface{} { _, res["totalCapacity"] = api.server.clientPool.Limits() _, res["totalConnectedCapacity"] = api.server.clientPool.Active() res["priorityConnectedCapacity"] = 0 //TODO connect when token sale module is added + return res } // ClientInfo returns information about clients listed in the ids list or matching the given tags func (api *LightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[string]interface{} { var ids []enode.ID + for _, node := range nodes { if id, err := parseNode(node); err == nil { ids = append(ids, id) @@ -81,9 +84,11 @@ func (api *LightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[string]in } res := make(map[enode.ID]map[string]interface{}) + if len(ids) == 0 { ids = api.server.peers.ids() } + for _, id := range ids { if peer := api.server.peers.peer(id); peer != nil { res[id] = api.clientInfo(peer, peer.balance) @@ -93,6 +98,7 @@ func (api *LightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[string]in }) } } + return res } @@ -105,10 +111,12 @@ func (api *LightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[string]in func (api *LightServerAPI) PriorityClientInfo(start, stop enode.ID, maxCount int) map[enode.ID]map[string]interface{} { res := make(map[enode.ID]map[string]interface{}) ids := api.server.clientPool.GetPosBalanceIDs(start, stop, maxCount+1) + if len(ids) > maxCount { res[ids[maxCount]] = make(map[string]interface{}) ids = ids[:maxCount] } + for _, id := range ids { if peer := api.server.peers.peer(id); peer != nil { res[id] = api.clientInfo(peer, peer.balance) @@ -118,6 +126,7 @@ func (api *LightServerAPI) PriorityClientInfo(start, stop enode.ID, maxCount int }) } } + return res } @@ -135,6 +144,7 @@ func (api *LightServerAPI) clientInfo(peer *clientPeer, balance vfs.ReadOnlyBala info["capacity"] = peer.getCapacity() info["pricing/negBalance"] = nb } + return info } @@ -142,6 +152,7 @@ func (api *LightServerAPI) clientInfo(peer *clientPeer, balance vfs.ReadOnlyBala // or the default parameters applicable to clients connected in the future func (api *LightServerAPI) setParams(params map[string]interface{}, client *clientPeer, posFactors, negFactors *vfs.PriceFactors) (updateFactors bool, err error) { defParams := client == nil + for name, value := range params { errValue := func() error { return fmt.Errorf("invalid value for parameter '%s'", name) @@ -182,10 +193,12 @@ func (api *LightServerAPI) setParams(params map[string]interface{}, client *clie err = fmt.Errorf("invalid client parameter '%s'", name) } } + if err != nil { return } } + return } @@ -193,17 +206,22 @@ func (api *LightServerAPI) setParams(params map[string]interface{}, client *clie // or all connected clients if the list is empty func (api *LightServerAPI) SetClientParams(nodes []string, params map[string]interface{}) error { var err error + for _, node := range nodes { var id enode.ID + if id, err = parseNode(node); err != nil { return err } + if peer := api.server.peers.peer(id); peer != nil { posFactors, negFactors := peer.balance.GetPriceFactors() update, e := api.setParams(params, peer, &posFactors, &negFactors) + if update { peer.balance.SetPriceFactors(posFactors, negFactors) } + if e != nil { err = e } @@ -211,6 +229,7 @@ func (api *LightServerAPI) SetClientParams(nodes []string, params map[string]int err = fmt.Errorf("client %064x is not connected", id) } } + return err } @@ -220,6 +239,7 @@ func (api *LightServerAPI) SetDefaultParams(params map[string]interface{}) error if update { api.server.clientPool.SetDefaultFactors(api.defaultPosFactors, api.defaultNegFactors) } + return err } @@ -231,7 +251,9 @@ func (api *LightServerAPI) SetConnectedBias(bias time.Duration) error { if bias < time.Duration(0) { return fmt.Errorf("bias illegal: %v less than 0", bias) } + api.server.clientPool.SetConnectedBias(bias) + return nil } @@ -239,12 +261,15 @@ func (api *LightServerAPI) SetConnectedBias(bias time.Duration) error { // the balance before and after the operation func (api *LightServerAPI) AddBalance(node string, amount int64) (balance [2]uint64, err error) { var id enode.ID + if id, err = parseNode(node); err != nil { return } + api.server.clientPool.BalanceOperation(id, "", func(nb vfs.AtomicBalanceOperator) { balance[0], balance[1], err = nb.AddBalance(amount) }) + return } @@ -256,20 +281,24 @@ func (api *LightServerAPI) AddBalance(node string, amount int64) (balance [2]uin // Therefore a controlled total measurement time is achievable in multiple passes. func (api *LightServerAPI) Benchmark(setups []map[string]interface{}, passCount, length int) ([]map[string]interface{}, error) { benchmarks := make([]requestBenchmark, len(setups)) + for i, setup := range setups { if t, ok := setup["type"].(string); ok { getInt := func(field string, def int) int { if value, ok := setup[field].(float64); ok { return int(value) } + return def } getBool := func(field string, def bool) bool { if value, ok := setup[field].(bool); ok { return value } + return def } + switch t { case "header": benchmarks[i] = &benchmarkBlockHeaders{ @@ -307,8 +336,10 @@ func (api *LightServerAPI) Benchmark(setups []map[string]interface{}, passCount, return nil, errUnknownBenchmarkType } } + rs := api.server.handler.runBenchmark(benchmarks, passCount, time.Millisecond*time.Duration(length)) result := make([]map[string]interface{}, len(setups)) + for i, r := range rs { res := make(map[string]interface{}) if r.err == nil { @@ -319,8 +350,10 @@ func (api *LightServerAPI) Benchmark(setups []map[string]interface{}, passCount, } else { res["error"] = r.err.Error() } + result[i] = res } + return result, nil } @@ -342,9 +375,11 @@ func (api *DebugAPI) FreezeClient(node string) error { id enode.ID err error ) + if id, err = parseNode(node); err != nil { return err } + if peer := api.server.peers.peer(id); peer != nil { peer.freeze() return nil @@ -373,12 +408,15 @@ func NewLightAPI(backend *lesCommons) *LightAPI { // result[3], 32 bytes hex encoded latest section bloom trie root hash func (api *LightAPI) LatestCheckpoint() ([4]string, error) { var res [4]string + cp := api.backend.latestLocalCheckpoint() if cp.Empty() { return res, errNoCheckpoint } + res[0] = hexutil.EncodeUint64(cp.SectionIndex) res[1], res[2], res[3] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex() + return res, nil } @@ -391,11 +429,14 @@ func (api *LightAPI) LatestCheckpoint() ([4]string, error) { // result[2], 32 bytes hex encoded latest section bloom trie root hash func (api *LightAPI) GetCheckpoint(index uint64) ([3]string, error) { var res [3]string + cp := api.backend.localCheckpoint(index) if cp.Empty() { return res, errNoCheckpoint } + res[0], res[1], res[2] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex() + return res, nil } @@ -404,5 +445,6 @@ func (api *LightAPI) GetCheckpointContractAddress() (string, error) { if api.backend.oracle == nil { return "", errNotActivated } + return api.backend.oracle.Contract().ContractAddr().Hex(), nil } diff --git a/les/api_backend.go b/les/api_backend.go index 43d2756ff5..b27df0498c 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -68,9 +68,11 @@ func (b *LesApiBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb if number == rpc.PendingBlockNumber { return b.eth.blockchain.CurrentHeader(), nil } + if number == rpc.LatestBlockNumber { return b.eth.blockchain.CurrentHeader(), nil } + return b.eth.blockchain.GetHeaderByNumberOdr(ctx, uint64(number)) } @@ -78,19 +80,24 @@ func (b *LesApiBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash if blockNr, ok := blockNrOrHash.Number(); ok { return b.HeaderByNumber(ctx, blockNr) } + if hash, ok := blockNrOrHash.Hash(); ok { header, err := b.HeaderByHash(ctx, hash) if err != nil { return nil, err } + if header == nil { return nil, errors.New("header for hash not found") } + if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { return nil, errors.New("hash is not currently canonical") } + return header, nil } + return nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -103,6 +110,7 @@ func (b *LesApiBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe if header == nil || err != nil { return nil, err } + return b.BlockByHash(ctx, header.Hash()) } @@ -114,19 +122,24 @@ func (b *LesApiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r if blockNr, ok := blockNrOrHash.Number(); ok { return b.BlockByNumber(ctx, blockNr) } + if hash, ok := blockNrOrHash.Hash(); ok { block, err := b.BlockByHash(ctx, hash) if err != nil { return nil, err } + if block == nil { return nil, errors.New("header found, but block body is missing") } + if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(block.NumberU64()) != hash { return nil, errors.New("hash is not currently canonical") } + return block, nil } + return nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -143,9 +156,11 @@ func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B if err != nil { return nil, nil, err } + if header == nil { return nil, nil, errors.New("header not found") } + return light.NewState(ctx, header, b.eth.odr), header, nil } @@ -153,16 +168,20 @@ func (b *LesApiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockN if blockNr, ok := blockNrOrHash.Number(); ok { return b.StateAndHeaderByNumber(ctx, blockNr) } + if hash, ok := blockNrOrHash.Hash(); ok { header := b.eth.blockchain.GetHeaderByHash(hash) if header == nil { return nil, nil, errors.New("header for hash not found") } + if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { return nil, nil, errors.New("hash is not currently canonical") } + return light.NewState(ctx, header, b.eth.odr), header, nil } + return nil, nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -170,6 +189,7 @@ func (b *LesApiBackend) GetReceipts(ctx context.Context, hash common.Hash) (type if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { return light.GetBlockReceipts(ctx, b.eth.odr, hash, *number) } + return nil, nil } @@ -181,6 +201,7 @@ func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { return b.eth.blockchain.GetTdOdr(ctx, hash, *number) } + return nil } @@ -188,8 +209,10 @@ func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *st if vmConfig == nil { vmConfig = new(vm.Config) } + txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(header, b.eth.blockchain, nil) + return vm.NewEVM(context, txContext, state, b.eth.chainConfig, *vmConfig), state.Error, nil } @@ -312,7 +335,9 @@ func (b *LesApiBackend) BloomStatus() (uint64, uint64) { if b.eth.bloomIndexer == nil { return 0, 0 } + sections, _, _ := b.eth.bloomIndexer.Sections() + return params.BloomBitsBlocksClient, sections } diff --git a/les/api_test.go b/les/api_test.go index db680da0bf..16f451edae 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -98,6 +98,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { if testServerDataDir == "" { return } + for !testSim(t, 1, clientCount, []string{testServerDataDir}, nil, func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool { if len(servers) != 1 { t.Fatalf("Invalid number of servers: %d", len(servers)) @@ -308,32 +309,42 @@ func getHead(ctx context.Context, t *testing.T, client *rpc.Client) (uint64, com if err := client.CallContext(ctx, &res, "eth_getBlockByNumber", "latest", false); err != nil { t.Fatalf("Failed to obtain head block: %v", err) } + numStr, ok := res["number"].(string) if !ok { t.Fatalf("RPC block number field invalid") } + num, err := hexutil.DecodeUint64(numStr) if err != nil { t.Fatalf("Failed to decode RPC block number: %v", err) } + hashStr, ok := res["hash"].(string) if !ok { t.Fatalf("RPC block number field invalid") } + hash := common.HexToHash(hashStr) + return num, hash } func testRequest(ctx context.Context, t *testing.T, client *rpc.Client) bool { var res string + var addr common.Address + crand.Read(addr[:]) + c, cancel := context.WithTimeout(ctx, time.Second*12) defer cancel() + err := client.CallContext(c, &res, "eth_getBalance", addr, "latest") if err != nil { t.Log("request error:", err) } + return err == nil } @@ -346,6 +357,7 @@ func freezeClient(ctx context.Context, t *testing.T, server *rpc.Client, clientI func setCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID, cap uint64) { params := make(map[string]interface{}) params["capacity"] = cap + if err := server.CallContext(ctx, nil, "les_setClientParams", []enode.ID{clientID}, []string{}, params); err != nil { t.Fatalf("Failed to set client capacity: %v", err) } @@ -356,18 +368,22 @@ func getCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID if err := server.CallContext(ctx, &res, "les_clientInfo", []enode.ID{clientID}, []string{}); err != nil { t.Fatalf("Failed to get client info: %v", err) } + info, ok := res[clientID] if !ok { t.Fatalf("Missing client info") } + v, ok := info["capacity"] if !ok { t.Fatalf("Missing field in client info: capacity") } + vv, ok := v.(float64) if !ok { t.Fatalf("Failed to decode capacity field") } + return uint64(vv) } @@ -376,19 +392,23 @@ func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (min if err := server.CallContext(ctx, &res, "les_serverInfo"); err != nil { t.Fatalf("Failed to query server info: %v", err) } + decode := func(s string) uint64 { v, ok := res[s] if !ok { t.Fatalf("Missing field in server info: %s", s) } + vv, ok := v.(float64) if !ok { t.Fatalf("Failed to decode server info field: %s", s) } + return uint64(vv) } minCap = decode("minimumCapacity") totalCap = decode("totalCapacity") + return } @@ -402,6 +422,7 @@ func NewNetwork() (*simulations.Network, func(), error) { if err != nil { return nil, adapterTeardown, err } + defaultService := "streamer" net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ ID: "0", @@ -411,11 +432,13 @@ func NewNetwork() (*simulations.Network, func(), error) { adapterTeardown() net.Shutdown() } + return net, teardown, nil } func NewAdapter(adapterType string, services adapters.LifecycleConstructors) (adapter adapters.NodeAdapter, teardown func(), err error) { teardown = func() {} + switch adapterType { case "sim": adapter = adapters.NewSimAdapter(services) @@ -426,6 +449,7 @@ func NewAdapter(adapterType string, services adapters.LifecycleConstructors) (ad if err0 != nil { return nil, teardown, err0 } + teardown = func() { os.RemoveAll(baseDir) } adapter = adapters.NewExecAdapter(baseDir) /*case "docker": @@ -436,16 +460,20 @@ func NewAdapter(adapterType string, services adapters.LifecycleConstructors) (ad default: return nil, teardown, errors.New("adapter needs to be one of sim, socket, exec, docker") } + return adapter, teardown, nil } func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []string, test func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool) bool { net, teardown, err := NewNetwork() defer teardown() + if err != nil { t.Fatalf("Failed to create network: %v", err) } + timeout := 1800 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -455,26 +483,32 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] for i := range clients { clientconf := adapters.RandomNodeConfig() clientconf.Lifecycles = []string{"lesclient"} + if len(clientDir) == clientCount { clientconf.DataDir = clientDir[i] } + client, err := net.NewNodeWithConfig(clientconf) if err != nil { t.Fatalf("Failed to create client: %v", err) } + clients[i] = client } for i := range servers { serverconf := adapters.RandomNodeConfig() serverconf.Lifecycles = []string{"lesserver"} + if len(serverDir) == serverCount { serverconf.DataDir = serverDir[i] } + server, err := net.NewNodeWithConfig(serverconf) if err != nil { t.Fatalf("Failed to create server: %v", err) } + servers[i] = server } @@ -483,6 +517,7 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] t.Fatalf("Failed to start client node: %v", err) } } + for _, server := range servers { if err := net.Start(server.ID()); err != nil { t.Fatalf("Failed to start server node: %v", err) @@ -496,6 +531,7 @@ func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.L config := ethconfig.Defaults config.SyncMode = (ethdownloader.SyncMode)(downloader.LightSync) config.Ethash.PowMode = ethash.ModeFake + return New(stack, &config) } @@ -504,13 +540,16 @@ func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.L config.SyncMode = (ethdownloader.SyncMode)(downloader.FullSync) config.LightServ = testServerCapacity config.LightPeers = testMaxClients + ethereum, err := eth.New(stack, &config) if err != nil { return nil, err } + _, err = NewLesServer(stack, ethereum, &config) if err != nil { return nil, err } + return ethereum, nil } diff --git a/les/benchmark.go b/les/benchmark.go index 95563a21aa..3bfb8e79b4 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -58,18 +58,22 @@ func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error { d := int64(b.amount-1) * int64(b.skip+1) b.offset = 0 b.randMax = h.blockchain.CurrentHeader().Number.Int64() + 1 - d + if b.randMax < 0 { return fmt.Errorf("chain is too short") } + if b.reverse { b.offset = d } + if b.byHash { b.hashes = make([]common.Hash, count) for i := range b.hashes { b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(b.offset+rand.Int63n(b.randMax))) } } + return nil } @@ -77,6 +81,7 @@ 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) } + return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse) } @@ -89,9 +94,11 @@ type benchmarkBodiesOrReceipts struct { func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error { randMax := h.blockchain.CurrentHeader().Number.Int64() + 1 b.hashes = make([]common.Hash, count) + for i := range b.hashes { b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(rand.Int63n(randMax))) } + return nil } @@ -99,6 +106,7 @@ func (b *benchmarkBodiesOrReceipts) request(peer *serverPeer, index int) error { if b.receipts { return peer.requestReceipts(0, []common.Hash{b.hashes[index]}) } + return peer.requestBodies(0, []common.Hash{b.hashes[index]}) } @@ -116,9 +124,11 @@ func (b *benchmarkProofsOrCode) init(h *serverHandler, count int) error { func (b *benchmarkProofsOrCode) request(peer *serverPeer, index int) error { key := make([]byte, 32) crand.Read(key) + if b.code { return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccKey: key}}) } + return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}}) } @@ -136,9 +146,11 @@ func (b *benchmarkHelperTrie) init(h *serverHandler, count int) error { b.sectionCount, _, _ = h.server.chtIndexer.Sections() b.headNum = b.sectionCount*params.CHTFrequency - 1 } + if b.sectionCount == 0 { return fmt.Errorf("no processed sections available") } + return nil } @@ -147,6 +159,7 @@ func (b *benchmarkHelperTrie) request(peer *serverPeer, index int) error { if b.bloom { bitIdx := uint16(rand.Intn(2048)) + for i := range reqs { key := make([]byte, 10) binary.BigEndian.PutUint16(key[:2], bitIdx) @@ -178,12 +191,15 @@ func (b *benchmarkTxSend) init(h *serverHandler, count int) error { for i := range b.txs { data := make([]byte, txSizeCostLimit) crand.Read(data) + tx, err := types.SignTx(types.NewTransaction(0, addr, new(big.Int), 0, new(big.Int), data), signer, key) if err != nil { panic(err) } + b.txs[i] = tx } + return nil } @@ -201,7 +217,9 @@ func (b *benchmarkTxStatus) init(h *serverHandler, count int) error { func (b *benchmarkTxStatus) request(peer *serverPeer, index int) error { var hash common.Hash + crand.Read(hash[:]) + return peer.requestTxStatus(0, []common.Hash{hash}) } @@ -221,10 +239,13 @@ func (h *serverHandler) runBenchmark(benchmarks []requestBenchmark, passCount in for i, b := range benchmarks { setup[i] = &benchmarkSetup{req: b} } + for i := 0; i < passCount; i++ { log.Info("Running benchmark", "pass", i+1, "total", passCount) + todo := make([]*benchmarkSetup, len(benchmarks)) copy(todo, setup) + for len(todo) > 0 { // select a random element index := rand.Intn(len(todo)) @@ -238,6 +259,7 @@ func (h *serverHandler) runBenchmark(benchmarks []requestBenchmark, passCount in if next.totalTime > 0 { count = int(uint64(next.totalCount) * uint64(targetTime) / uint64(next.totalTime)) } + if err := h.measure(next, count); err != nil { next.err = err } @@ -251,6 +273,7 @@ func (h *serverHandler) runBenchmark(benchmarks []requestBenchmark, passCount in s.avgTime = s.totalTime / time.Duration(s.totalCount) } } + return setup } @@ -269,6 +292,7 @@ func (m *meteredPipe) WriteMsg(msg p2p.Msg) error { if msg.Size > m.maxSize { m.maxSize = msg.Size } + return m.rw.WriteMsg(msg) } @@ -278,7 +302,9 @@ func (h *serverHandler) measure(setup *benchmarkSetup, count int) error { clientPipe, serverPipe := p2p.MsgPipe() clientMeteredPipe := &meteredPipe{rw: clientPipe} serverMeteredPipe := &meteredPipe{rw: serverPipe} + var id enode.ID + crand.Read(id[:]) peer1 := newServerPeer(lpv2, NetworkId, false, p2p.NewPeer(id, "client", nil), clientMeteredPipe) @@ -286,11 +312,14 @@ func (h *serverHandler) measure(setup *benchmarkSetup, count int) error { peer2.announceType = announceTypeNone peer2.fcCosts = make(requestCostTable) c := &requestCosts{} + for code := range requests { peer2.fcCosts[code] = c } + peer2.fcParams = flowcontrol.ServerParams{BufLimit: 1, MinRecharge: 1} peer2.fcClient = flowcontrol.NewClientNode(h.server.fcManager, peer2.fcParams) + defer peer2.fcClient.Disconnect() if err := setup.req.init(h, count); err != nil { @@ -323,7 +352,9 @@ func (h *serverHandler) measure(setup *benchmarkSetup, count int) error { errCh <- err return } + var i interface{} + msg.Decode(&i) } // at this point we can be sure that the other two @@ -338,6 +369,7 @@ func (h *serverHandler) measure(setup *benchmarkSetup, count int) error { case <-h.closeCh: clientPipe.Close() serverPipe.Close() + return fmt.Errorf("Benchmark cancelled") } @@ -345,7 +377,9 @@ func (h *serverHandler) measure(setup *benchmarkSetup, count int) error { setup.totalCount += count setup.maxInSize = clientMeteredPipe.maxSize setup.maxOutSize = serverMeteredPipe.maxSize + clientPipe.Close() serverPipe.Close() + return nil } diff --git a/les/bloombits.go b/les/bloombits.go index a98524ce2e..6b381e1a02 100644 --- a/les/bloombits.go +++ b/les/bloombits.go @@ -47,6 +47,7 @@ func (eth *LightEthereum) startBloomHandlers(sectionSize uint64) { for i := 0; i < bloomServiceThreads; i++ { go func() { defer eth.wg.Done() + for { select { case <-eth.closeCh: @@ -56,6 +57,7 @@ func (eth *LightEthereum) startBloomHandlers(sectionSize uint64) { task := <-request task.Bitsets = make([][]byte, len(task.Sections)) compVectors, err := light.GetBloomBits(task.Context, eth.odr, task.Bit, task.Sections) + if err == nil { for i := range task.Sections { if blob, err := bitutil.DecompressBytes(compVectors[i], int(sectionSize/8)); err == nil { diff --git a/les/catalyst/api.go b/les/catalyst/api.go index e21c58c5fe..05b03a4243 100644 --- a/les/catalyst/api.go +++ b/les/catalyst/api.go @@ -40,6 +40,7 @@ func Register(stack *node.Node, backend *les.LightEthereum) error { Authenticated: true, }, }) + return nil } @@ -53,6 +54,7 @@ func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI { if les.BlockChain().Config().TerminalTotalDifficulty == nil { log.Warn("Catalyst started without valid total difficulty") } + return &ConsensusAPI{les: les} } @@ -75,6 +77,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads engine.ForkchoiceStateV1, pay log.Warn("Forkchoice requested update to zero hash") return engine.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? } + if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil { if header := api.les.BlockChain().GetHeaderByHash(heads.HeadBlockHash); header == nil { // TODO (MariusVanDerWijden) trigger sync @@ -94,9 +97,11 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads engine.ForkchoiceStateV1, pay if err := api.setCanonical(heads.HeadBlockHash); err != nil { return engine.STATUS_INVALID, err } + if payloadAttributes != nil { return engine.STATUS_INVALID, errors.New("not supported") } + return api.validForkChoiceResponse(), nil } @@ -111,6 +116,7 @@ func (api *ConsensusAPI) ExecutePayloadV1(params engine.ExecutableData) (engine. if err != nil { return api.invalid(), err } + if !api.les.BlockChain().HasHeader(block.ParentHash(), block.NumberU64()-1) { /* TODO (MariusVanDerWijden) reenable once sync is merged @@ -121,21 +127,27 @@ func (api *ConsensusAPI) ExecutePayloadV1(params engine.ExecutableData) (engine. // TODO (MariusVanDerWijden) we should return nil here not empty hash return engine.PayloadStatusV1{Status: engine.SYNCING, LatestValidHash: nil}, nil } + parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash) if parent == nil { return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash) } + td := api.les.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1) ttd := api.les.BlockChain().Config().TerminalTotalDifficulty + if td.Cmp(ttd) < 0 { return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd) } + if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil { return api.invalid(), err } + if merger := api.les.Merger(); !merger.TDDReached() { merger.ReachTTD() } + hash := block.Hash() return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil @@ -165,10 +177,12 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { if header == nil { return errors.New("unknown header") } + td := api.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) if td != nil && td.Cmp(api.les.BlockChain().Config().TerminalTotalDifficulty) < 0 { return errors.New("invalid ttd") } + return nil } @@ -180,6 +194,7 @@ func (api *ConsensusAPI) setCanonical(newHead common.Hash) error { if headHeader.Hash() == newHead { return nil } + newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead) if newHeadHeader == nil { return errors.New("unknown header") @@ -192,6 +207,7 @@ func (api *ConsensusAPI) setCanonical(newHead common.Hash) error { if merger := api.les.Merger(); !merger.PoSFinalized() { merger.FinalizePoS() } + return nil } diff --git a/les/catalyst/api_test.go b/les/catalyst/api_test.go index fe3c798cd7..cf1d727237 100644 --- a/les/catalyst/api_test.go +++ b/les/catalyst/api_test.go @@ -63,6 +63,7 @@ func generatePreMergeChain(pre, post int) (*core.Genesis, []*types.Header, []*ty totalDifficulty.Add(totalDifficulty, b.Difficulty()) preHeaders = append(preHeaders, b.Header()) } + config.TerminalTotalDifficulty = totalDifficulty // Post-merge blocks postBlocks, _ := core.GenerateChain(genesis.Config, @@ -81,6 +82,7 @@ func generatePreMergeChain(pre, post int) (*core.Genesis, []*types.Header, []*ty func TestSetHeadBeforeTotalDifficulty(t *testing.T) { genesis, headers, blocks, _, _ := generatePreMergeChain(10, 0) + n, lesService := startLesService(t, genesis, headers) defer n.Close() @@ -90,6 +92,7 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) { SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err == nil { t.Errorf("fork choice updated before total terminal difficulty should fail") } @@ -99,6 +102,7 @@ func TestExecutePayloadV1(t *testing.T) { genesis, headers, _, _, postBlocks := generatePreMergeChain(10, 2) n, lesService := startLesService(t, genesis, headers) lesService.Merger().ReachTTD() + defer n.Close() api := NewConsensusAPI(lesService) @@ -107,6 +111,7 @@ func TestExecutePayloadV1(t *testing.T) { SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { t.Errorf("Failed to update head %v", err) } @@ -151,6 +156,7 @@ func TestExecutePayloadV1(t *testing.T) { if err != nil { t.Errorf("Failed to execute payload %v", err) } + headHeader := api.les.BlockChain().CurrentHeader() if headHeader.Number.Uint64() != fakeBlock.NumberU64()-1 { t.Fatal("Unexpected chain head update") @@ -164,6 +170,7 @@ func TestExecutePayloadV1(t *testing.T) { if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { t.Fatal("Failed to update head") } + headHeader = api.les.BlockChain().CurrentHeader() if headHeader.Number.Uint64() != fakeBlock.NumberU64() { t.Fatal("Failed to update chain head") @@ -224,6 +231,7 @@ func startLesService(t *testing.T, genesis *core.Genesis, headers []*types.Heade if err != nil { t.Fatal("can't create node:", err) } + ethcfg := ðconfig.Config{ Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}, @@ -232,17 +240,21 @@ func startLesService(t *testing.T, genesis *core.Genesis, headers []*types.Heade TrieCleanCache: 256, LightPeers: 10, } + lesService, err := les.New(n, ethcfg) if err != nil { t.Fatal("can't create eth service:", err) } + if err := n.Start(); err != nil { t.Fatal("can't start node:", err) } + if _, err := lesService.BlockChain().InsertHeaderChain(headers, 0); err != nil { n.Close() t.Fatal("can't import test headers:", err) } + return n, lesService } @@ -251,5 +263,6 @@ func encodeTransactions(txs []*types.Transaction) [][]byte { for i, tx := range txs { enc[i], _ = tx.MarshalBinary() } + return enc } diff --git a/les/checkpointoracle/oracle.go b/les/checkpointoracle/oracle.go index 6ad1ea2938..4304080c7b 100644 --- a/les/checkpointoracle/oracle.go +++ b/les/checkpointoracle/oracle.go @@ -65,10 +65,12 @@ func (oracle *CheckpointOracle) Start(backend bind.ContractBackend) { log.Error("Oracle contract binding failed", "err", err) return } + if !atomic.CompareAndSwapInt32(&oracle.running, 0, 1) { log.Error("Already bound and listening to registrar") return } + oracle.contract = contract } @@ -87,6 +89,7 @@ func (oracle *CheckpointOracle) Contract() *checkpointoracle.CheckpointOracle { func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, uint64) { oracle.checkMu.Lock() defer oracle.checkMu.Unlock() + if time.Since(oracle.lastCheckTime) < 1*time.Minute { return oracle.lastCheckPoint, oracle.lastCheckPointHeight } @@ -94,11 +97,14 @@ func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, u // Retrieve the latest checkpoint from the contract, abort if empty latest, hash, height, err := oracle.contract.Contract().GetLatestCheckpoint(nil) oracle.lastCheckTime = time.Now() + if err != nil || (latest == 0 && hash == [32]byte{}) { oracle.lastCheckPointHeight = 0 oracle.lastCheckPoint = nil + return oracle.lastCheckPoint, oracle.lastCheckPointHeight } + local := oracle.getLocal(latest) // The following scenarios may occur: @@ -111,8 +117,10 @@ func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, u if local.HashEqual(hash) { oracle.lastCheckPointHeight = height.Uint64() oracle.lastCheckPoint = &local + return oracle.lastCheckPoint, oracle.lastCheckPointHeight } + return nil, 0 } @@ -123,10 +131,12 @@ func (oracle *CheckpointOracle) VerifySigners(index uint64, hash [32]byte, signa if len(signatures) < int(oracle.config.Threshold) { return false, nil } + var ( signers []common.Address checked = make(map[common.Address]struct{}) ) + for i := 0; i < len(signatures); i++ { if len(signatures[i]) != 65 { continue @@ -145,15 +155,20 @@ func (oracle *CheckpointOracle) VerifySigners(index uint64, hash [32]byte, signa binary.BigEndian.PutUint64(buf, index) data := append([]byte{0x19, 0x00}, append(oracle.config.Address.Bytes(), append(buf, hash[:]...)...)...) signatures[i][64] -= 27 // Transform V from 27/28 to 0/1 according to the yellow paper for verification. + pubkey, err := crypto.Ecrecover(crypto.Keccak256(data), signatures[i]) if err != nil { return false, nil } + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + if _, exist := checked[signer]; exist { continue } + for _, s := range oracle.config.Signers { if s == signer { signers = append(signers, signer) @@ -161,10 +176,12 @@ func (oracle *CheckpointOracle) VerifySigners(index uint64, hash [32]byte, signa } } } + threshold := oracle.config.Threshold if uint64(len(signers)) < threshold { log.Warn("Not enough signers to approve checkpoint", "signers", len(signers), "threshold", threshold) return false, nil } + return true, signers } diff --git a/les/client.go b/les/client.go index 204a7567ab..cec1d9c15a 100644 --- a/les/client.go +++ b/les/client.go @@ -19,6 +19,7 @@ package les import ( "fmt" + "github.com/ethereum/go-ethereum/eth/downloader/whitelist" "strings" "time" @@ -89,6 +90,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if err != nil { return nil, err } + lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/lesclient/", false) if err != nil { return nil, err @@ -145,6 +147,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if leth.udpEnabled { 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) @@ -165,6 +168,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine, checkpoint, whitelist.NewService(10)); err != nil { return nil, err } + leth.chainReader = leth.blockchain leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay) @@ -188,14 +192,17 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { } else { leth.blockchain.SetHead(compat.RewindToBlock) } + rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, leth, nil} + gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice } + leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, checkpoint, leth) @@ -222,12 +229,16 @@ func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.R if !s.udpEnabled { return nil } + 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 } @@ -236,9 +247,11 @@ 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.udpEnabled { return 0 } + if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 { s.serverPool.Persist(n) } else { @@ -250,8 +263,11 @@ func (s *LightEthereum) vfxVersion(n *enode.Node) uint { 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 } @@ -264,18 +280,24 @@ func (s *LightEthereum) prenegQuery(n *enode.Node) int { } 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 } @@ -306,6 +328,7 @@ func (s *LightDummyAPI) Mining() bool { func (s *LightEthereum) APIs() []rpc.API { apis := ethapi.GetAPIs(s.ApiBackend) apis = append(apis, s.engine.APIs(s.BlockChain().HeaderChain())...) + return append(apis, []rpc.API{ { Namespace: "eth", @@ -344,6 +367,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { if p := s.peers.peer(id.String()); p != nil { return p.Info() } + return nil }, s.serverPoolIterator) } @@ -358,12 +382,15 @@ func (s *LightEthereum) Start() error { if s.udpEnabled && s.p2pServer.DiscV5 == nil { s.udpEnabled = false + log.Error("Discovery v5 is not initialized") } + discovery, err := s.setupDiscovery() if err != nil { return err } + s.serverPool.AddSource(discovery) s.serverPool.Start() // Start bloom request workers. @@ -398,5 +425,6 @@ func (s *LightEthereum) Stop() error { s.lesDb.Close() s.wg.Wait() log.Info("Light ethereum stopped") + return nil } diff --git a/les/client_handler.go b/les/client_handler.go index cce99d41dc..dd2a887c1b 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -61,21 +61,27 @@ func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.T backend: backend, closeCh: make(chan struct{}), } + if ulcServers != nil { ulc, err := newULC(ulcServers, ulcFraction) if err != nil { log.Error("Failed to initialize ultra light client") } + handler.ulc = ulc + log.Info("Enable ultra light client mode") } + var height uint64 if checkpoint != nil { height = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1 } + handler.fetcher = newLightFetcher(backend.blockchain, backend.engine, backend.peers, handler.ulc, backend.chainDb, backend.reqDist, handler.synchronise) handler.downloader = downloader.New(height, backend.chainDb, backend.eventMux, nil, backend.blockchain, handler.removePeer) handler.backend.peers.subscribe((*downloaderPeerNotify)(handler)) + return handler } @@ -96,11 +102,14 @@ func (h *clientHandler) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) if h.ulc != nil { trusted = h.ulc.trusted(p.ID()) } + peer := newServerPeer(int(version), h.backend.config.NetworkId, trusted, p, newMeteredMsgWriter(rw, int(version))) defer peer.close() h.wg.Add(1) + defer h.wg.Done() err := h.handle(peer, false) + return err } @@ -108,6 +117,7 @@ func (h *clientHandler) handle(p *serverPeer, noInitAnnounce bool) error { if h.backend.peers.len() >= h.backend.config.LightPeers && !p.Peer.Info().Network.Trusted { return p2p.DiscTooManyPeers } + p.Log().Debug("Light Ethereum peer connected", "name", p.Name()) // Execute the LES handshake @@ -121,6 +131,7 @@ func (h *clientHandler) handle(p *serverPeer, noInitAnnounce bool) error { 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()) @@ -159,6 +170,7 @@ func (h *clientHandler) handle(p *serverPeer, noInitAnnounce bool) error { if err := h.handleMsg(p); err != nil { p.Log().Debug("Light Ethereum message handling failed", "err", err) p.fcServer.DumpLogs() + return err } } @@ -172,6 +184,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { if err != nil { return err } + p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size) if msg.Size > ProtocolMaxMsgSize { @@ -185,17 +198,21 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { switch { case msg.Code == AnnounceMsg: p.Log().Trace("Received announce message") + var req announceData if err := msg.Decode(&req); err != nil { return errResp(ErrDecode, "%v: %v", msg, err) } + if err := req.sanityCheck(); err != nil { return err } + update, size := req.Update.decode() if p.rejectUpdate(size) { return errResp(ErrRequestRejected, "") } + p.updateFlowControl(update) p.updateVtParams() @@ -203,13 +220,16 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { if p.announceType == announceTypeNone { return errResp(ErrUnexpectedResponse, "") } + if p.announceType == announceTypeSigned { if err := req.checkSignature(p.ID(), update); err != nil { p.Log().Trace("Invalid announcement signature", "err", err) return err } + p.Log().Trace("Valid announcement signature") } + p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth) // Update peer head information first and then notify the announcement @@ -222,13 +242,16 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } case msg.Code == BlockHeadersMsg: p.Log().Trace("Received block header response message") + var resp struct { ReqID, BV uint64 Headers []*types.Header } + if err := msg.Decode(&resp); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } + headers := resp.Headers p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) @@ -246,6 +269,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { 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) @@ -254,13 +278,16 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } case msg.Code == BlockBodiesMsg: p.Log().Trace("Received block bodies response") + var resp struct { ReqID, BV uint64 Data []*types.Body } + if err := msg.Decode(&resp); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } + p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ @@ -270,13 +297,16 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } case msg.Code == CodeMsg: p.Log().Trace("Received code response") + var resp struct { ReqID, BV uint64 Data [][]byte } + if err := msg.Decode(&resp); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } + p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ @@ -286,13 +316,16 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } case msg.Code == ReceiptsMsg: p.Log().Trace("Received receipts response") + var resp struct { ReqID, BV uint64 Receipts []types.Receipts } + if err := msg.Decode(&resp); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } + p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ @@ -302,13 +335,16 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } case msg.Code == ProofsV2Msg: p.Log().Trace("Received les/2 proofs response") + var resp struct { ReqID, BV uint64 Data light.NodeList } + if err := msg.Decode(&resp); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } + p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ @@ -318,13 +354,16 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } case msg.Code == HelperTrieProofsMsg: p.Log().Trace("Received helper trie proof response") + var resp struct { ReqID, BV uint64 Data HelperTrieResps } + if err := msg.Decode(&resp); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } + p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ @@ -334,13 +373,16 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } case msg.Code == TxStatusMsg: p.Log().Trace("Received tx status response") + var resp struct { ReqID, BV uint64 Status []light.TxStatus } + if err := msg.Decode(&resp); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } + p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ @@ -357,6 +399,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { if err := msg.Decode(&bv); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } + p.fcServer.ResumeFreeze(bv) p.unfreeze() p.Log().Debug("Service resumed") @@ -372,6 +415,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } } } + return nil } @@ -405,10 +449,12 @@ func (pc *peerConnection) RequestHeadersByHash(origin common.Hash, amount int, s return func() { peer.requestHeadersByHash(reqID, origin, amount, skip, reverse) } }, } + _, ok := <-pc.handler.backend.reqDist.queue(rq) if !ok { return light.ErrNoPeers } + return nil } @@ -429,10 +475,12 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip return func() { peer.requestHeadersByNumber(reqID, origin, amount, skip, reverse) } }, } + _, ok := <-pc.handler.backend.reqDist.queue(rq) if !ok { return light.ErrNoPeers } + return nil } @@ -455,7 +503,9 @@ func (pc *peerConnection) RetrieveSingleHeaderByNumber(context context.Context, 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 @@ -469,6 +519,7 @@ func (pc *peerConnection) RetrieveSingleHeaderByNumber(context context.Context, }, nil); err != nil { return nil, err } + return header, nil } diff --git a/les/commons.go b/les/commons.go index 860175c8ca..c0dc2a3a5e 100644 --- a/les/commons.go +++ b/les/commons.go @@ -74,6 +74,7 @@ type NodeInfo struct { // makeProtocols creates protocol descriptors for the given LES versions. func (c *lesCommons) makeProtocols(versions []uint, runPeer func(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error, peerInfo func(id enode.ID) interface{}, dialCandidates enode.Iterator) []p2p.Protocol { protos := make([]p2p.Protocol, len(versions)) + for i, version := range versions { version := version protos[i] = p2p.Protocol{ @@ -88,6 +89,7 @@ func (c *lesCommons) makeProtocols(versions []uint, runPeer func(version uint, p DialCandidates: dialCandidates, } } + return protos } @@ -95,6 +97,7 @@ func (c *lesCommons) makeProtocols(versions []uint, runPeer func(version uint, p func (c *lesCommons) nodeInfo() interface{} { head := c.chainReader.CurrentHeader() hash := head.Hash() + return &NodeInfo{ Network: c.config.NetworkId, Difficulty: rawdb.ReadTd(c.chainDb, hash, head.Number.Uint64()), @@ -115,10 +118,12 @@ func (c *lesCommons) latestLocalCheckpoint() params.TrustedCheckpoint { if sections > sections2 { sections = sections2 } + if sections == 0 { // No checkpoint information can be provided. return params.TrustedCheckpoint{} } + return c.localCheckpoint(sections - 1) } @@ -129,6 +134,7 @@ func (c *lesCommons) latestLocalCheckpoint() params.TrustedCheckpoint { // not the stable checkpoint registered in the registrar contract. func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint { sectionHead := c.chtIndexer.SectionHead(index) + return params.TrustedCheckpoint{ SectionIndex: index, SectionHead: sectionHead, @@ -144,18 +150,22 @@ func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig // Try loading default config. config = params.CheckpointOracles[genesis] } + if config == nil { log.Info("Checkpoint oracle is not enabled") return nil } + if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold { 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 oracle", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold) + return oracle } diff --git a/les/costtracker.go b/les/costtracker.go index 43e32a5b2d..da87ff25b3 100644 --- a/les/costtracker.go +++ b/les/costtracker.go @@ -145,28 +145,36 @@ func newCostTracker(db ethdb.Database, config *ethconfig.Config) (*costTracker, reqInfoCh: make(chan reqInfo, 100), utilTarget: utilTarget, } + if config.LightIngress > 0 { ct.inSizeFactor = utilTarget / float64(config.LightIngress) } + if config.LightEgress > 0 { ct.outSizeFactor = utilTarget / float64(config.LightEgress) } + if makeCostStats { ct.stats = make(map[uint64][]uint64) for code := range reqAvgTimeCost { ct.stats[code] = make([]uint64, 10) } } + ct.gfLoop() + costList := ct.makeCostList(ct.globalFactor() * 1.25) for _, c := range costList { amount := minBufferReqAmount[c.MsgCode] + cost := c.BaseCost + amount*c.ReqCost if cost > ct.minBufLimit { ct.minBufLimit = cost } } + ct.minBufLimit *= uint64(minBufferMultiplier) + return ct, (ct.minBufLimit-1)/bufLimitRatio + 1 } @@ -175,6 +183,7 @@ func (ct *costTracker) stop() { stopCh := make(chan struct{}) ct.stopCh <- stopCh <-stopCh + if makeCostStats { ct.printStats() } @@ -186,19 +195,25 @@ func (ct *costTracker) makeCostList(globalFactor float64) RequestCostList { maxCost := func(avgTimeCost, inSize, outSize uint64) uint64 { cost := avgTimeCost * maxCostFactor inSizeCost := uint64(float64(inSize) * ct.inSizeFactor * globalFactor) + if inSizeCost > cost { cost = inSizeCost } + outSizeCost := uint64(float64(outSize) * ct.outSizeFactor * globalFactor) if outSizeCost > cost { cost = outSizeCost } + return cost } + var list RequestCostList + for code, data := range reqAvgTimeCost { baseCost := maxCost(data.baseCost, reqMaxInSize[code].baseCost, reqMaxOutSize[code].baseCost) reqCost := maxCost(data.reqCost, reqMaxInSize[code].reqCost, reqMaxOutSize[code].reqCost) + if ct.minBufLimit != 0 { // if minBufLimit is set then always enforce maximum request cost <= minBufLimit maxCost := baseCost + reqCost*minBufferReqAmount[code] @@ -215,6 +230,7 @@ func (ct *costTracker) makeCostList(globalFactor float64) RequestCostList { ReqCost: reqCost, }) } + return list } @@ -254,6 +270,7 @@ func (ct *costTracker) gfLoop() { if len(data) == 8 { gfLog = math.Float64frombits(binary.BigEndian.Uint64(data[:])) } + ct.factor = math.Exp(gfLog) factor, totalRecharge = ct.factor, ct.utilTarget*ct.factor @@ -264,10 +281,12 @@ func (ct *costTracker) gfLoop() { go func() { saveCostFactor := func() { var data [8]byte + binary.BigEndian.PutUint64(data[:], math.Float64bits(gfLog)) ct.db.Put([]byte(gfDbKey), data[:]) log.Debug("global cost factor saved", "value", factor) } + saveTicker := time.NewTicker(time.Minute * 10) defer saveTicker.Stop() @@ -306,6 +325,7 @@ func (ct *costTracker) gfLoop() { if r.msgCode == SendTxV2Msg || r.msgCode == GetTxStatusMsg { continue } + requestServedMeter.Mark(int64(r.servingTime)) requestServedTimer.Update(time.Duration(r.servingTime)) requestEstimatedMeter.Mark(int64(r.avgTimeCost / factor)) @@ -319,6 +339,7 @@ func (ct *costTracker) gfLoop() { // calculate factor correction until now, based on previous values var gfCorr float64 + max := recentTime if recentAvg > max { max = recentAvg @@ -355,16 +376,19 @@ func (ct *costTracker) gfLoop() { ct.factor = factor ch := ct.totalRechargeCh ct.gfLock.Unlock() + if ch != nil { select { case ct.totalRechargeCh <- uint64(totalRecharge): default: } } + globalFactorGauge.Update(int64(1000 * factor)) log.Debug("global cost factor updated", "factor", factor) } } + recentServedGauge.Update(int64(recentTime)) recentEstimatedGauge.Update(int64(recentAvg)) @@ -374,6 +398,7 @@ func (ct *costTracker) gfLoop() { case stopCh := <-ct.stopCh: saveCostFactor() close(stopCh) + return } } @@ -404,6 +429,7 @@ func (ct *costTracker) subscribeTotalRecharge(ch chan uint64) uint64 { defer ct.gfLock.Unlock() ct.totalRechargeCh = ch + return uint64(ct.factor * ct.utilTarget) } @@ -416,9 +442,11 @@ func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) { case ct.reqInfoCh <- reqInfo{float64(avgTimeCost), float64(servingTime), code}: default: } + if makeCostStats { realCost <<= 4 l := 0 + for l < 9 && realCost > avgTimeCost { l++ realCost >>= 1 @@ -438,13 +466,16 @@ func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) { func (ct *costTracker) realCost(servingTime uint64, inSize, outSize uint32) uint64 { cost := float64(servingTime) inSizeCost := float64(inSize) * ct.inSizeFactor + if inSizeCost > cost { cost = inSizeCost } + outSizeCost := float64(outSize) * ct.outSizeFactor if outSizeCost > cost { cost = outSizeCost } + return uint64(cost * ct.globalFactor()) } @@ -453,6 +484,7 @@ func (ct *costTracker) printStats() { if ct.stats == nil { return } + for code, arr := range ct.stats { log.Info("Request cost statistics", "code", code, "1/16", arr[0], "1/8", arr[1], "1/4", arr[2], "1/2", arr[3], "1", arr[4], "2", arr[5], "4", arr[6], "8", arr[7], "16", arr[8], ">16", arr[9]) } @@ -484,6 +516,7 @@ func (table requestCostTable) getMaxCost(code, amount uint64) uint64 { // decode converts a cost list to a cost table func (list RequestCostList) decode(protocolLength uint64) requestCostTable { table := make(requestCostTable) + for _, e := range list { if e.MsgCode < protocolLength { table[e.MsgCode] = &requestCosts{ @@ -492,19 +525,23 @@ func (list RequestCostList) decode(protocolLength uint64) requestCostTable { } } } + return table } // testCostList returns a dummy request cost list used by tests func testCostList(testCost uint64) RequestCostList { cl := make(RequestCostList, len(reqAvgTimeCost)) + var max uint64 for code := range reqAvgTimeCost { if code > max { max = code } } + i := 0 + for code := uint64(0); code <= max; code++ { if _, ok := reqAvgTimeCost[code]; ok { cl[i].MsgCode = code @@ -513,5 +550,6 @@ func testCostList(testCost uint64) RequestCostList { i++ } } + return cl } diff --git a/les/distributor.go b/les/distributor.go index a0319c67f7..5860860013 100644 --- a/les/distributor.go +++ b/les/distributor.go @@ -85,8 +85,11 @@ func newRequestDistributor(peers *serverPeerSet, clock mclock.Clock) *requestDis if peers != nil { peers.subscribe(d) } + d.wg.Add(1) + go d.loop() + return d } @@ -124,10 +127,12 @@ var ( // main event loop func (d *requestDistributor) loop() { defer d.wg.Done() + for { select { case <-d.closeCh: d.lock.Lock() + elem := d.reqQueue.Front() for elem != nil { req := elem.Value.(*distReq) @@ -136,6 +141,7 @@ func (d *requestDistributor) loop() { elem = elem.Next() } d.lock.Unlock() + return case <-d.loopChn: d.lock.Lock() @@ -192,6 +198,7 @@ func selectPeerWeight(i interface{}) uint64 { func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { checkedPeers := make(map[distPeer]struct{}) elem := d.reqQueue.Front() + var ( bestWait time.Duration sel *utils.WeightedRandomSelect @@ -205,36 +212,45 @@ func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { req := elem.Value.(*distReq) canSend := false now := d.clock.Now() + if req.waitForPeers > now { canSend = true + wait := time.Duration(req.waitForPeers - now) if bestWait == 0 || wait < bestWait { bestWait = wait } } + for peer := range d.peers { if _, ok := checkedPeers[peer]; !ok && peer.canQueue() && req.canSend(peer) { canSend = true cost := req.getCost(peer) + wait, bufRemain := peer.waitBefore(cost) if wait == 0 { if sel == nil { sel = utils.NewWeightedRandomSelect(selectPeerWeight) } + sel.Update(selectPeerItem{peer: peer, req: req, weight: uint64(bufRemain*1000000) + 1}) } else { if bestWait == 0 || wait < bestWait { bestWait = wait } } + checkedPeers[peer] = struct{}{} } } + next := elem.Next() + if !canSend && elem == d.reqQueue.Front() { close(req.sentChn) d.remove(req) } + elem = next } @@ -242,6 +258,7 @@ func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { c := sel.Choose().(selectPeerItem) return c.peer, c.req, 0 } + return nil, nil, bestWait } @@ -270,6 +287,7 @@ func (d *requestDistributor) queue(r *distReq) chan distPeer { for before.Value.(*distReq).reqOrder < r.reqOrder { before = before.Next() } + r.element = d.reqQueue.InsertBefore(r, before) } @@ -279,6 +297,7 @@ func (d *requestDistributor) queue(r *distReq) chan distPeer { } r.sentChn = make(chan distPeer, 1) + return r.sentChn } @@ -295,6 +314,7 @@ func (d *requestDistributor) cancel(r *distReq) bool { close(r.sentChn) d.remove(r) + return true } diff --git a/les/distributor_test.go b/les/distributor_test.go index 9a93dba145..e7b10c4947 100644 --- a/les/distributor_test.go +++ b/les/distributor_test.go @@ -59,19 +59,24 @@ func (p *testDistPeer) send(r *testDistReq) { func (p *testDistPeer) worker(t *testing.T, checkOrder bool, stop chan struct{}) { var last uint64 + for { wait := time.Millisecond + p.lock.Lock() if len(p.sent) > 0 { rq := p.sent[0] wait = time.Duration(rq.procTime) p.sumCost -= rq.cost + if checkOrder { if rq.order <= last { t.Errorf("Requests processed in wrong order") } + last = rq.order } + p.sent = p.sent[1:] } p.lock.Unlock() @@ -95,9 +100,11 @@ func (p *testDistPeer) waitBefore(cost uint64) (time.Duration, float64) { p.lock.RLock() sumCost := p.sumCost + cost p.lock.RUnlock() + if sumCost < testDistBufLimit { return 0, float64(testDistBufLimit-sumCost) / float64(testDistBufLimit) } + return time.Duration(sumCost - testDistBufLimit), 0 } @@ -123,6 +130,7 @@ func testRequestDistributor(t *testing.T, resend bool) { defer close(stop) dist := newRequestDistributor(nil, &mclock.System{}) + var peers [testDistPeerCount]*testDistPeer for i := range peers { peers[i] = &testDistPeer{} @@ -144,6 +152,7 @@ func testRequestDistributor(t *testing.T, resend bool) { order: uint64(i), canSendTo: make(map[*testDistPeer]struct{}), } + for _, peer := range peers { if rand.Intn(2) != 0 { rq.canSendTo[peer] = struct{}{} @@ -151,21 +160,25 @@ func testRequestDistributor(t *testing.T, resend bool) { } wg.Add(1) + req := &distReq{ getCost: rq.getCost, canSend: rq.canSend, request: rq.request, } chn := dist.queue(req) + go func() { cnt := 1 if resend && len(rq.canSendTo) != 0 { cnt = rand.Intn(testDistMaxResendCount) + 1 } + for i := 0; i < cnt; i++ { if i != 0 { chn = dist.queue(req) } + p := <-chn if p == nil { if len(rq.canSendTo) != 0 { @@ -180,6 +193,7 @@ func testRequestDistributor(t *testing.T, resend bool) { } wg.Done() }() + if rand.Intn(1000) == 0 { time.Sleep(time.Duration(rand.Intn(5000000))) } diff --git a/les/downloader/downloader.go b/les/downloader/downloader.go index a6ebf1d2a5..b5af68004a 100644 --- a/les/downloader/downloader.go +++ b/les/downloader/downloader.go @@ -209,6 +209,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain Bl if lightchain == nil { lightchain = chain } + dl := &Downloader{ stateDB: stateDb, mux: mux, @@ -234,6 +235,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain Bl trackStateReq: make(chan *stateReq), } go dl.stateFetcher() + return dl } @@ -251,6 +253,7 @@ func (d *Downloader) Progress() ethereum.SyncProgress { current := uint64(0) mode := d.getMode() + switch { case d.blockchain != nil && mode == FullSync: current = d.blockchain.CurrentBlock().NumberU64() @@ -261,6 +264,7 @@ func (d *Downloader) Progress() ethereum.SyncProgress { default: log.Error("Unknown downloader chain/mode combo", "light", d.lightchain != nil, "full", d.blockchain != nil, "mode", mode) } + return ethereum.SyncProgress{ StartingBlock: d.syncStatsChainOrigin, CurrentBlock: current, @@ -285,11 +289,14 @@ func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { } else { logger = log.New("peer", id[:8]) } + 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) return err } + return nil } @@ -310,11 +317,14 @@ func (d *Downloader) UnregisterPeer(id string) error { } else { logger = log.New("peer", id[:8]) } + logger.Trace("Unregistering sync peer") + if err := d.peers.Unregister(id); err != nil { logger.Error("Failed to unregister sync peer", "err", err) return err } + d.queue.Revoke(id) return nil @@ -329,10 +339,12 @@ 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) { log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err) + if d.dropPeer == nil { // The dropPeer method is nil when `--copydb` is used for a local copy. // Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored @@ -340,9 +352,12 @@ func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode } else { d.dropPeer(id) } + return err } + log.Warn("Synchronisation failed, retrying", "err", err) + return err } @@ -375,9 +390,12 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests snapshots.Disable() } + 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 @@ -390,6 +408,7 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode default: } } + for _, ch := range []chan dataPack{d.headerCh, d.bodyCh, d.receiptCh} { for empty := false; !empty; { select { @@ -399,6 +418,7 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode } } } + for empty := false; !empty; { select { case <-d.headerProcCh: @@ -422,6 +442,7 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode if p == nil { return errUnknownPeer } + return d.syncWithPeer(p, hash, td) } @@ -433,6 +454,7 @@ func (d *Downloader) getMode() SyncMode { // specified peer and head hash. func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.Int) (err error) { d.mux.Post(StartEvent{}) + defer func() { // reset on error if err != nil { @@ -442,12 +464,15 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I d.mux.Post(DoneEvent{latest}) } }() + if p.version < eth.ETH66 { return fmt.Errorf("%w: advertized %d < required %d", errTooOld, p.version, eth.ETH66) } + mode := d.getMode() log.Debug("Synchronising with the network", "peer", p.id, "eth", p.version, "head", hash, "td", td, "mode", mode) + defer func(start time.Time) { log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start))) }(time.Now()) @@ -457,6 +482,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I if err != nil { return err } + if mode == FastSync && pivot == nil { // If no pivot block was returned, the head is below the min full block // threshold (i.e. new chain). In that case we won't really fast sync @@ -464,16 +490,19 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I // nil panics on an access. pivot = d.blockchain.CurrentBlock().Header() } + height := latest.Number.Uint64() origin, err := d.findAncestor(p, latest) if err != nil { return err } + d.syncStatsLock.Lock() if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin { d.syncStatsChainOrigin = origin } + d.syncStatsChainHeight = height d.syncStatsLock.Unlock() @@ -491,10 +520,12 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I rawdb.WriteLastPivotNumber(d.stateDB, pivotNumber) } } + d.committed = 1 if mode == FastSync && pivot.Number.Uint64() != 0 { d.committed = 0 } + if mode == FastSync { // Set the ancient data limitation. // If we are running fast sync, all block data older than ancientLimit will be @@ -517,12 +548,14 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I } else { d.ancientLimit = 0 } + frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. // If a part of blockchain data has already been written into active store, // disable the ancient style insertion explicitly. if origin >= frozen && frozen != 0 { d.ancientLimit = 0 + log.Info("Disabling direct-ancient mode", "origin", origin, "ancient", frozen-1) } else if d.ancientLimit > 0 { log.Debug("Enabling direct-ancient mode", "ancient", d.ancientLimit) @@ -536,15 +569,18 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I } // Initiate the sync using a concurrent header and content retrieval algorithm d.queue.Prepare(origin+1, mode) + if d.syncInitHook != nil { d.syncInitHook(origin, height) } + fetchers := []func() error{ func() error { return d.fetchHeaders(p, origin+1) }, // Headers are always retrieved func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and fast sync func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync func() error { return d.processHeaders(origin+1, td) }, } + if mode == FastSync { d.pivotLock.Lock() d.pivotHeader = pivot @@ -554,6 +590,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I } else if mode == FullSync { fetchers = append(fetchers, d.processFullSyncContent) } + return d.spawnSync(fetchers) } @@ -562,12 +599,15 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I func (d *Downloader) spawnSync(fetchers []func() error) error { errc := make(chan error, len(fetchers)) d.cancelWg.Add(len(fetchers)) + for _, fn := range fetchers { fn := fn + go func() { defer d.cancelWg.Done(); errc <- fn() }() } // Wait for the first error, then terminate the others. var err error + for i := 0; i < len(fetchers); i++ { if i == len(fetchers)-1 { // Close the queue when all fetchers have exited. @@ -575,12 +615,14 @@ func (d *Downloader) spawnSync(fetchers []func() error) error { // it has processed the queue. d.queue.Close() } + if err = <-errc; err != nil && err != errCanceled { break } } d.queue.Close() d.Cancel() + return err } @@ -629,18 +671,22 @@ func (d *Downloader) Terminate() { // a remote peer. func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *types.Header, err error) { p.log.Debug("Retrieving remote chain head") + mode := d.getMode() // Request the advertised remote head block and wait for the response latest, _ := p.peer.Head() + fetch := 1 if mode == FastSync { fetch = 2 // head + pivot headers } + go p.peer.RequestHeadersByHash(latest, fetch, fsMinFullBlocks-1, true) ttl := d.peers.rates.TargetTimeout() timeout := time.After(ttl) + for { select { case <-d.cancelCh: @@ -664,11 +710,14 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty if (mode == FastSync || mode == LightSync) && head.Number.Uint64() < d.checkpoint { return nil, nil, fmt.Errorf("%w: remote head %d below checkpoint %d", errUnsyncedPeer, head.Number, d.checkpoint) } + if len(headers) == 1 { if mode == FastSync && head.Number.Uint64() > uint64(fsMinFullBlocks) { return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer) } + p.log.Debug("Remote head identified, no pivot", "number", head.Number, "hash", head.Hash()) + return head, nil, nil } // At this point we have 2 headers in total and the first is the @@ -677,6 +726,7 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty if pivot.Number.Uint64() != head.Number.Uint64()-uint64(fsMinFullBlocks) { return nil, nil, fmt.Errorf("%w: remote pivot %d != requested %d", errInvalidChain, pivot.Number, head.Number.Uint64()-uint64(fsMinFullBlocks)) } + return head, pivot, nil case <-timeout: @@ -720,11 +770,14 @@ func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, ui if requestBottom < 0 { requestBottom = 0 } + totalSpan := requestHead - requestBottom + span := 1 + totalSpan/MaxCount if span < 2 { span = 2 } + if span > 16 { span = 16 } @@ -733,14 +786,18 @@ func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, ui if count > MaxCount { count = MaxCount } + if count < 2 { count = 2 } + from = requestHead - (count-1)*span if from < 0 { from = 0 } + max := from + (count-1)*span + return int64(from), count, span - 1, uint64(max) } @@ -756,6 +813,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) localHeight uint64 remoteHeight = remoteHeader.Number.Uint64() ) + mode := d.getMode() switch mode { case FullSync: @@ -765,6 +823,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) default: localHeight = d.lightchain.CurrentHeader().Number.Uint64() } + p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight) // Recap floor value for binary search @@ -772,6 +831,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) if d.getMode() == LightSync { maxForkAncestry = lightMaxForkAncestry } + if localHeight >= maxForkAncestry { // We're above the max reorg threshold, find the earliest fork point floor = int64(localHeight - maxForkAncestry) @@ -787,6 +847,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) if floor >= int64(d.genesis)-1 { break } + header = d.lightchain.GetHeaderByHash(header.ParentHash) } } @@ -812,6 +873,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) if err != nil { return 0, err } + return ancestor, nil } @@ -854,6 +916,7 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re } // Check if a common ancestor was found finished = true + for i := len(headers) - 1; i >= 0; i-- { // Skip any headers that underflow/overflow our requested set if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > max { @@ -864,6 +927,7 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re n := headers[i].Number.Uint64() var known bool + switch mode { case FullSync: known = d.blockchain.HasBlock(h, n) @@ -872,6 +936,7 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re default: known = d.lightchain.HasHeader(h, n) } + if known { number, hash = n, h break @@ -893,9 +958,12 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re p.log.Warn("Ancestor below allowance", "number", number, "hash", hash, "allowance", floor) return 0, errInvalidAncestor } + p.log.Debug("Found common ancestor", "number", number, "hash", hash) + return number, nil } + return 0, errNoAncestorFound } @@ -907,6 +975,7 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, if floor > 0 { start = uint64(floor) } + p.log.Trace("Binary searching for common ancestor", "start", start, "end", end) for start+1 < end { @@ -936,6 +1005,7 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, p.log.Warn("Multiple headers for single request", "headers", len(headers)) return 0, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers)) } + arrived = true // Modify the search interval based on the response @@ -943,6 +1013,7 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, n := headers[0].Number.Uint64() var known bool + switch mode { case FullSync: known = d.blockchain.HasBlock(h, n) @@ -951,15 +1022,18 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, default: known = d.lightchain.HasHeader(h, n) } + if !known { end = check break } + header := d.lightchain.GetHeaderByHash(h) // Independent of sync mode, header surely exists if header.Number.Uint64() != check { p.log.Warn("Received non requested header", "number", header.Number, "hash", header.Hash(), "request", check) return 0, fmt.Errorf("%w: non-requested header (%d)", errBadPeer, header.Number) } + start = check hash = h @@ -978,7 +1052,9 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, p.log.Warn("Ancestor below allowance", "number", start, "hash", hash, "allowance", floor) return 0, errInvalidAncestor } + p.log.Debug("Found common ancestor", "number", start, "hash", hash) + return start, nil } @@ -1000,9 +1076,11 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { request := time.Now() // time of the last skeleton fetch request timeout := time.NewTimer(0) // timer to dump a non-responsive active peer <-timeout.C // timeout channel should be initially empty + defer timeout.Stop() var ttl time.Duration + getHeaders := func(from uint64) { request = time.Now() @@ -1036,6 +1114,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { getHeaders(from) mode := d.getMode() + for { select { case <-d.cancelCh: @@ -1047,6 +1126,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { log.Debug("Received skeleton from incorrect peer", "peer", packet.PeerId()) break } + headerReqTimer.UpdateSince(request) timeout.Stop() @@ -1068,10 +1148,12 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { log.Warn("Peer sent invalid next pivot", "have", have, "want", want) return fmt.Errorf("%w: next pivot number %d != requested %d", errInvalidChain, have, want) } + if have, want := headers[1].Number.Uint64(), pivot+2*uint64(fsMinFullBlocks)-8; have != want { log.Warn("Peer sent invalid pivot confirmer", "have", have, "want", want) return fmt.Errorf("%w: next pivot confirmer number %d != requested %d", errInvalidChain, have, want) } + log.Warn("Pivot seemingly stale, moving", "old", pivot, "new", headers[0].Number) pivot = headers[0].Number.Uint64() @@ -1084,14 +1166,19 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { // the state syncer will be downloading. rawdb.WriteLastPivotNumber(d.stateDB, pivot) } + pivoting = false + getHeaders(from) + continue } // If the skeleton's finished, pull any remaining head headers directly from the origin if skeleton && packet.Items() == 0 { skeleton = false + getHeaders(from) + continue } // If no more headers are inbound, notify the content fetchers and return @@ -1116,6 +1203,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { return errCanceled } } + headers := packet.(*headerPack).headers // If we received a skeleton batch, resolve internals concurrently @@ -1125,6 +1213,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { p.log.Debug("Skeleton chain invalid", "err", err) return fmt.Errorf("%w: %v", errInvalidChain, err) } + headers = filled[proced:] from += uint64(proced) } else { @@ -1154,6 +1243,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { if delay > n { delay = n } + headers = headers[:n-delay] } } @@ -1166,6 +1256,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { case <-d.cancelCh: return errCanceled } + from += uint64(len(headers)) // If we're still skeleton filling fast sync, check pivot staleness @@ -1210,6 +1301,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { case d.headerProcCh <- nil: case <-d.cancelCh: } + return fmt.Errorf("%w: header request timed out", errBadPeer) } } @@ -1243,6 +1335,7 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ( p.SetHeadersIdle(accepted, deliveryTime) } ) + err := d.fetchParts(d.headerCh, deliver, d.queue.headerContCh, expire, d.queue.PendingHeaders, d.queue.InFlightHeaders, reserve, nil, fetch, d.queue.CancelHeaders, capacity, d.peers.HeaderIdlePeers, setIdle, "headers") @@ -1250,6 +1343,7 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ( log.Debug("Skeleton fill terminated", "err", err) filled, proced := d.queue.RetrieveHeaders() + return filled, proced, err } @@ -1269,11 +1363,13 @@ func (d *Downloader) fetchBodies(from uint64) error { capacity = func(p *peerConnection) int { return p.BlockCapacity(d.peers.rates.TargetRoundTrip()) } setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetBodiesIdle(accepted, deliveryTime) } ) + err := d.fetchParts(d.bodyCh, deliver, d.bodyWakeCh, expire, d.queue.PendingBlocks, d.queue.InFlightBlocks, d.queue.ReserveBodies, d.bodyFetchHook, fetch, d.queue.CancelBodies, capacity, d.peers.BodyIdlePeers, setIdle, "bodies") log.Debug("Block body download terminated", "err", err) + return err } @@ -1295,11 +1391,13 @@ func (d *Downloader) fetchReceipts(from uint64) error { p.SetReceiptsIdle(accepted, deliveryTime) } ) + err := d.fetchParts(d.receiptCh, deliver, d.receiptWakeCh, expire, d.queue.PendingReceipts, d.queue.InFlightReceipts, d.queue.ReserveReceipts, d.receiptFetchHook, fetch, d.queue.CancelReceipts, capacity, d.peers.ReceiptIdlePeers, setIdle, "receipts") log.Debug("Transaction receipt download terminated", "err", err) + return err } @@ -1340,6 +1438,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) // Prepare the queue and fetch block parts until the block header fetcher's done finished := false + for { select { case <-d.cancelCh: @@ -1442,12 +1541,14 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) log.Debug("Data fetching completed", "type", kind) return nil } + break } // Send a download request to all idle peers, until throttled progressed, throttled, running := false, false, inFlight() idles, total := idle() pendCount := pending() + for _, peer := range idles { // Short circuit if throttling activated if throttled { @@ -1464,13 +1565,17 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) if progress { progressed = true } + if throttle { throttled = true + throttleCounter.Inc(1) } + if request == nil { continue } + if request.From > 0 { peer.log.Trace("Requesting new batch of data", "type", kind, "from", request.From) } else { @@ -1480,6 +1585,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) if fetchHook != nil { fetchHook(request.Headers) } + if err := fetch(peer, request); err != nil { // Although we could try and make an attempt to fix this, this error really // means that we've double allocated a fetch task to a peer. If that is the @@ -1488,6 +1594,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) // a much bigger issue. panic(fmt.Sprintf("%v: %s fetch assignment failed", peer, kind)) } + running = true } // Make sure that we have peers available for fetching. If all peers have been tried @@ -1509,6 +1616,7 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error { rollbackErr error mode = d.getMode() ) + defer func() { if rollback > 0 { lastHeader, lastFastBlock, lastBlock := d.lightchain.CurrentHeader().Number, common.Big0, common.Big0 @@ -1516,15 +1624,18 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error { lastFastBlock = d.blockchain.CurrentFastBlock().Number() lastBlock = d.blockchain.CurrentBlock().Number() } + if err := d.lightchain.SetHead(rollback - 1); err != nil { // -1 to target the parent of the first uncertain block // We're already unwinding the stack, only print the error to make it more visible log.Error("Failed to roll back chain segment", "head", rollback-1, "err", err) } + curFastBlock, curBlock := common.Big0, common.Big0 if mode != LightSync { curFastBlock = d.blockchain.CurrentFastBlock().Number() curBlock = d.blockchain.CurrentBlock().Number() } + log.Warn("Rolled back chain segment", "header", fmt.Sprintf("%d->%d", lastHeader, d.lightchain.CurrentHeader().Number), "fast", fmt.Sprintf("%d->%d", lastFastBlock, curFastBlock), @@ -1583,10 +1694,12 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error { } // Disable any rollback and return rollback = 0 + return nil } // Otherwise split the chunk of headers into batches and process them gotHeaders = true + for len(headers) > 0 { // Terminate if something failed in between processing chunks select { @@ -1600,6 +1713,7 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error { if limit > len(headers) { limit = len(headers) } + chunk := headers[:limit] // In case of header only syncing, validate the chunk immediately @@ -1617,6 +1731,7 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error { if chunk[len(chunk)-1].Number.Uint64()+uint64(fsHeaderForceVerify) > pivot { frequency = 1 } + if n, err := d.lightchain.InsertHeaderChain(chunk, frequency); err != nil { rollbackErr = err @@ -1624,7 +1739,9 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error { if (mode == FastSync || frequency > 1) && n > 0 && rollback == 0 { rollback = chunk[0].Number.Uint64() } + log.Warn("Invalid header encountered", "number", chunk[n].Number, "hash", chunk[n].Hash(), "parent", chunk[n].ParentHash, "err", err) + return fmt.Errorf("%w: %v", errInvalidChain, err) } // All verifications passed, track all headers within the allotted limits @@ -1655,6 +1772,7 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error { return fmt.Errorf("%w: stale headers", errBadPeer) } } + headers = headers[limit:] origin += uint64(limit) } @@ -1683,9 +1801,11 @@ func (d *Downloader) processFullSyncContent() error { if len(results) == 0 { return nil } + if d.chainInsertHook != nil { d.chainInsertHook(results) } + if err := d.importBlockResults(results); err != nil { return err } @@ -1708,10 +1828,12 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error { "firstnum", first.Number, "firsthash", first.Hash(), "lastnum", last.Number, "lasthash", last.Hash(), ) + blocks := make([]*types.Block, len(results)) for i, result := range results { blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) } + if index, err := d.blockchain.InsertChain(blocks); err != nil { if index < len(results) { log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) @@ -1722,8 +1844,10 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error { // of the blocks delivered from the downloader, and the indexing will be off. log.Debug("Downloaded item processing failed on sidechain import", "index", index, "err", err) } + return fmt.Errorf("%w: %v", errInvalidChain, err) } + return nil } @@ -1756,6 +1880,7 @@ func (d *Downloader) processFastSyncContent() error { oldPivot *fetchResult // Locked in pivot block, might change eventually oldTail []*fetchResult // Downloaded content after the pivot ) + for { // Wait for the next batch of downloaded data to be available, and if the pivot // block became stale, move the goalpost @@ -1773,6 +1898,7 @@ func (d *Downloader) processFastSyncContent() error { default: } } + if d.chainInsertHook != nil { d.chainInsertHook(results) } @@ -1815,10 +1941,12 @@ func (d *Downloader) processFastSyncContent() error { rawdb.WriteLastPivotNumber(d.stateDB, pivot.Number.Uint64()) } } + P, beforeP, afterP := splitAroundPivot(pivot.Number.Uint64(), results) if err := d.commitFastSyncData(beforeP, sync); err != nil { return err } + if P != nil { // If new pivot block found, cancel old state retrieval and restart if oldPivot != P { @@ -1826,6 +1954,7 @@ func (d *Downloader) processFastSyncContent() error { sync = d.syncState(P.Header.Root) go closeOnErr(sync) + oldPivot = P } // Wait for completion, occasionally checking for pivot staleness @@ -1834,9 +1963,11 @@ func (d *Downloader) processFastSyncContent() error { if sync.err != nil { return sync.err } + if err := d.commitPivotBlock(P); err != nil { return err } + oldPivot = nil case <-time.After(time.Second): @@ -1855,6 +1986,7 @@ func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, bef if len(results) == 0 { return nil, nil, nil } + if lastNum := results[len(results)-1].Header.Number.Uint64(); lastNum < pivot { // the pivot is somewhere in the future return nil, results, nil @@ -1862,6 +1994,7 @@ func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, bef // This can also be optimized, but only happens very seldom for _, result := range results { num := result.Header.Number.Uint64() + switch { case num < pivot: before = append(before, result) @@ -1871,6 +2004,7 @@ func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, bef after = append(after, result) } } + return p, before, after } @@ -1894,16 +2028,20 @@ func (d *Downloader) commitFastSyncData(results []*fetchResult, stateSync *state "firstnum", first.Number, "firsthash", first.Hash(), "lastnumn", last.Number, "lasthash", last.Hash(), ) + blocks := make([]*types.Block, len(results)) receipts := make([]types.Receipts, len(results)) + for i, result := range results { blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) receipts[i] = result.Receipts } + if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil { log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) return fmt.Errorf("%w: %v", errInvalidChain, err) } + return nil } @@ -1915,10 +2053,13 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error { if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{result.Receipts}, d.ancientLimit); err != nil { return err } + if err := d.blockchain.FastSyncCommitHead(block.Hash()); err != nil { return err } + atomic.StoreInt32(&d.committed, 1) + return nil } @@ -1952,6 +2093,7 @@ func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) erro if err != nil { return err } + return d.SnapSyncer.OnAccounts(peer, packet.ID, hashes, accounts, packet.Proof) case *snap.StorageRangesPacket: @@ -1973,6 +2115,7 @@ func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) erro 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() { if err != nil { dropMeter.Mark(int64(packet.Items())) @@ -1982,6 +2125,7 @@ func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dro d.cancelLock.RLock() cancel := d.cancelCh d.cancelLock.RUnlock() + if cancel == nil { return errNoSyncActive } diff --git a/les/downloader/downloader_test.go b/les/downloader/downloader_test.go index 1704d3e743..0e4d8fcab1 100644 --- a/les/downloader/downloader_test.go +++ b/les/downloader/downloader_test.go @@ -90,6 +90,7 @@ func newTester() *downloadTester { tester.stateDb.Put(testGenesis.Root().Bytes(), []byte{0x00}) tester.downloader = New(0, tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer) + return tester } @@ -118,6 +119,7 @@ func (dl *downloadTester) sync(id string, td *big.Int, mode SyncMode) error { // Downloader is still accepting packets, can block a peer up panic("downloader active post sync cycle") // panic will be caught by tester } + return err } @@ -139,7 +141,9 @@ func (dl *downloadTester) HasFastBlock(hash common.Hash, number uint64) bool { if _, ok := dl.ancientReceipts[hash]; ok { return true } + _, ok := dl.ownReceipts[hash] + return ok } @@ -147,6 +151,7 @@ func (dl *downloadTester) HasFastBlock(hash common.Hash, number uint64) bool { func (dl *downloadTester) GetHeaderByHash(hash common.Hash) *types.Header { dl.lock.RLock() defer dl.lock.RUnlock() + return dl.getHeaderByHash(hash) } @@ -157,6 +162,7 @@ func (dl *downloadTester) getHeaderByHash(hash common.Hash) *types.Header { if header != nil { return header } + return dl.ownHeaders[hash] } @@ -169,6 +175,7 @@ func (dl *downloadTester) GetBlockByHash(hash common.Hash) *types.Block { if block != nil { return block } + return dl.ownBlocks[hash] } @@ -181,10 +188,12 @@ func (dl *downloadTester) CurrentHeader() *types.Header { if header := dl.ancientHeaders[dl.ownHashes[i]]; header != nil { return header } + if header := dl.ownHeaders[dl.ownHashes[i]]; header != nil { return header } } + return dl.genesis.Header() } @@ -198,14 +207,17 @@ func (dl *downloadTester) CurrentBlock() *types.Block { if _, err := dl.stateDb.Get(block.Root().Bytes()); err == nil { return block } + return block } + if block := dl.ownBlocks[dl.ownHashes[i]]; block != nil { if _, err := dl.stateDb.Get(block.Root().Bytes()); err == nil { return block } } } + return dl.genesis } @@ -218,10 +230,12 @@ func (dl *downloadTester) CurrentFastBlock() *types.Block { if block := dl.ancientBlocks[dl.ownHashes[i]]; block != nil { return block } + if block := dl.ownBlocks[dl.ownHashes[i]]; block != nil { return block } } + return dl.genesis } @@ -232,6 +246,7 @@ func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error { _, err := trie.NewStateTrie(trie.StateTrieID(block.Root()), trie.NewDatabase(dl.stateDb)) return err } + return fmt.Errorf("non existent block: %x", hash[:4]) } @@ -250,6 +265,7 @@ func (dl *downloadTester) getTd(hash common.Hash) *big.Int { if td := dl.ancientChainTd[hash]; td != nil { return td } + return dl.ownChainTd[hash] } @@ -261,14 +277,19 @@ func (dl *downloadTester) InsertHeaderChain(headers []*types.Header, checkFreq i if dl.getHeaderByHash(headers[0].ParentHash) == nil { return 0, fmt.Errorf("InsertHeaderChain: unknown parent at first position, parent of number %d", headers[0].Number) } + var hashes []common.Hash + for i := 1; i < len(headers); i++ { hash := headers[i-1].Hash() + if headers[i].ParentHash != headers[i-1].Hash() { return i, fmt.Errorf("non-contiguous import at position %d", i) } + hashes = append(hashes, hash) } + hashes = append(hashes, headers[len(headers)-1].Hash()) // Do a full insert if pre-checks passed for i, header := range headers { @@ -276,16 +297,19 @@ func (dl *downloadTester) InsertHeaderChain(headers []*types.Header, checkFreq i if dl.getHeaderByHash(hash) != nil { continue } + if dl.getHeaderByHash(header.ParentHash) == nil { // This _should_ be impossible, due to precheck and induction return i, fmt.Errorf("InsertHeaderChain: unknown parent at position %d", i) } + dl.ownHashes = append(dl.ownHashes, hash) dl.ownHeaders[hash] = header td := dl.getTd(header.ParentHash) dl.ownChainTd[hash] = new(big.Int).Add(td, header.Difficulty) } + return len(headers), nil } @@ -293,22 +317,26 @@ func (dl *downloadTester) InsertHeaderChain(headers []*types.Header, checkFreq i func (dl *downloadTester) InsertChain(blocks types.Blocks) (i int, err error) { dl.lock.Lock() defer dl.lock.Unlock() + for i, block := range blocks { if parent, ok := dl.ownBlocks[block.ParentHash()]; !ok { return i, fmt.Errorf("InsertChain: unknown parent at position %d / %d", i, len(blocks)) } else if _, err := dl.stateDb.Get(parent.Root().Bytes()); err != nil { return i, fmt.Errorf("InsertChain: unknown parent state %x: %v", parent.Root(), err) } + if hdr := dl.getHeaderByHash(block.Hash()); hdr == nil { dl.ownHashes = append(dl.ownHashes, block.Hash()) dl.ownHeaders[block.Hash()] = block.Header() } + dl.ownBlocks[block.Hash()] = block dl.ownReceipts[block.Hash()] = make(types.Receipts, 0) dl.stateDb.Put(block.Root().Bytes(), []byte{0x00}) td := dl.getTd(block.ParentHash()) dl.ownChainTd[block.Hash()] = new(big.Int).Add(td, block.Difficulty()) } + return len(blocks), nil } @@ -321,11 +349,13 @@ func (dl *downloadTester) InsertReceiptChain(blocks types.Blocks, receipts []typ if _, ok := dl.ownHeaders[blocks[i].Hash()]; !ok { return i, errors.New("unknown owner") } + if _, ok := dl.ancientBlocks[blocks[i].ParentHash()]; !ok { if _, ok := dl.ownBlocks[blocks[i].ParentHash()]; !ok { return i, errors.New("InsertReceiptChain: unknown parent") } } + if blocks[i].NumberU64() <= ancientLimit { dl.ancientBlocks[blocks[i].Hash()] = blocks[i] dl.ancientReceipts[blocks[i].Hash()] = receipts[i] @@ -340,6 +370,7 @@ func (dl *downloadTester) InsertReceiptChain(blocks types.Blocks, receipts []typ dl.ownReceipts[blocks[i].Hash()] = receipts[i] } } + return len(blocks), nil } @@ -350,21 +381,25 @@ func (dl *downloadTester) SetHead(head uint64) error { // Find the hash of the head to reset to var hash common.Hash + for h, header := range dl.ownHeaders { if header.Number.Uint64() == head { hash = h } } + for h, header := range dl.ancientHeaders { if header.Number.Uint64() == head { hash = h } } + if hash == (common.Hash{}) { return fmt.Errorf("unknown head to set: %d", head) } // Find the offset in the header chain var offset int + for o, h := range dl.ownHashes { if h == hash { offset = o @@ -383,7 +418,9 @@ func (dl *downloadTester) SetHead(head uint64) error { delete(dl.ancientReceipts, dl.ownHashes[i]) delete(dl.ancientBlocks, dl.ownHashes[i]) } + dl.ownHashes = dl.ownHashes[:offset+1] + return nil } @@ -398,6 +435,7 @@ func (dl *downloadTester) newPeer(id string, version uint, chain *testChain) err peer := &downloadTesterPeer{dl: dl, id: id, chain: chain} dl.peers[id] = peer + return dl.downloader.RegisterPeer(id, version, peer) } @@ -435,6 +473,7 @@ func (dlp *downloadTesterPeer) Head() (common.Hash, *big.Int) { func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { result := dlp.chain.headersByHash(origin, amount, skip, reverse) go dlp.dl.downloader.DeliverHeaders(dlp.id, result) + return nil } @@ -444,6 +483,7 @@ func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount i func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { result := dlp.chain.headersByNumber(origin, amount, skip, reverse) go dlp.dl.downloader.DeliverHeaders(dlp.id, result) + return nil } @@ -453,6 +493,7 @@ func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash) error { txs, uncles := dlp.chain.bodies(hashes) go dlp.dl.downloader.DeliverBodies(dlp.id, txs, uncles) + return nil } @@ -462,6 +503,7 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash) error { func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash) error { receipts := dlp.chain.receipts(hashes) go dlp.dl.downloader.DeliverReceipts(dlp.id, receipts) + return nil } @@ -473,6 +515,7 @@ func (dlp *downloadTesterPeer) RequestNodeData(hashes []common.Hash) error { defer dlp.dl.lock.RUnlock() results := make([][]byte, 0, len(hashes)) + for _, hash := range hashes { if data, err := dlp.dl.peerDb.Get(hash.Bytes()); err == nil { if !dlp.missingStates[hash] { @@ -480,7 +523,9 @@ func (dlp *downloadTesterPeer) RequestNodeData(hashes []common.Hash) error { } } } + go dlp.dl.downloader.DeliverNodeData(dlp.id, results) + return nil } @@ -508,15 +553,19 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng blocks += length - common receipts += length - common } + if tester.downloader.getMode() == LightSync { blocks, receipts = 1, 1 } + if hs := len(tester.ownHeaders) + len(tester.ancientHeaders) - 1; hs != headers { t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers) } + if bs := len(tester.ownBlocks) + len(tester.ancientBlocks) - 1; bs != blocks { t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, blocks) } + if rs := len(tester.ownReceipts) + len(tester.ancientReceipts) - 1; rs != receipts { t.Fatalf("synchronised receipts mismatch: have %v, want %v", rs, receipts) } @@ -540,6 +589,7 @@ func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("peer", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chain.len()) } @@ -550,6 +600,7 @@ func TestThrottling66Fast(t *testing.T) { testThrottling(t, eth.ETH66, FastSync) func testThrottling(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() + tester := newTester() // Create a long block chain to download and the tester @@ -573,11 +624,13 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { tester.lock.RLock() retrieved := len(tester.ownBlocks) tester.lock.RUnlock() + if retrieved >= targetBlocks+1 { break } // Wait a bit for sync to throttle itself var cached, frozen int + for start := time.Now(); time.Since(start) < 3*time.Second; { time.Sleep(25 * time.Millisecond) @@ -605,6 +658,7 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { tester.lock.RLock() retrieved = len(tester.ownBlocks) tester.lock.RUnlock() + if cached != blockCacheMaxItems && cached != blockCacheMaxItems-reorgProtHeaderDelay && retrieved+cached+frozen != targetBlocks+1 && retrieved+cached+frozen != targetBlocks+1-reorgProtHeaderDelay { t.Fatalf("block count mismatch: have %v, want %v (owned %v, blocked %v, target %v)", cached, blockCacheMaxItems, retrieved, frozen, targetBlocks+1) } @@ -617,9 +671,11 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { } // Check that we haven't pulled more blocks than available assertOwnChain(t, tester, targetBlocks+1) + if err := <-errc; err != nil { t.Fatalf("block synchronization failed: %v", err) } + tester.terminate() } @@ -638,18 +694,21 @@ func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { chainA := testChainForkLightA.shorten(testChainBase.len() + 80) chainB := testChainForkLightB.shorten(testChainBase.len() + 80) + tester.newPeer("fork A", protocol, chainA) tester.newPeer("fork B", protocol, chainB) // Synchronise with the peer and make sure all blocks were retrieved if err := tester.sync("fork A", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chainA.len()) // Synchronise with the second peer and make sure that fork is pulled too if err := tester.sync("fork B", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnForkedChain(t, tester, testChainBase.len(), []int{chainA.len(), chainB.len()}) } @@ -667,6 +726,7 @@ func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { chainA := testChainForkLightA.shorten(testChainBase.len() + 80) chainB := testChainForkHeavy.shorten(testChainBase.len() + 80) + tester.newPeer("light", protocol, chainA) tester.newPeer("heavy", protocol, chainB) @@ -674,12 +734,14 @@ func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("light", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chainA.len()) // Synchronise with the second peer and make sure that fork is pulled too if err := tester.sync("heavy", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnForkedChain(t, tester, testChainBase.len(), []int{chainA.len(), chainB.len()}) } @@ -698,6 +760,7 @@ func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { chainA := testChainForkLightA chainB := testChainForkLightB + tester.newPeer("original", protocol, chainA) tester.newPeer("rewriter", protocol, chainB) @@ -705,6 +768,7 @@ func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("original", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chainA.len()) // Synchronise with the second peer and ensure that the fork is rejected to being too old @@ -728,17 +792,20 @@ func TestBoundedHeavyForkedSync66Light(t *testing.T) { func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() + tester := newTester() // Create a long enough forked chain chainA := testChainForkLightA chainB := testChainForkHeavy + tester.newPeer("original", protocol, chainA) // Synchronise with the peer and make sure all blocks were retrieved if err := tester.sync("original", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chainA.len()) tester.newPeer("heavy-rewriter", protocol, chainB) @@ -746,6 +813,7 @@ func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("heavy-rewriter", nil, mode); err != errInvalidAncestor { t.Fatalf("sync failure mismatch: have %v, want %v", err, errInvalidAncestor) } + tester.terminate() } @@ -761,9 +829,11 @@ func TestInactiveDownloader63(t *testing.T) { if err := tester.downloader.DeliverHeaders("bad peer", []*types.Header{}); err != errNoSyncActive { t.Errorf("error mismatch: have %v, want %v", err, errNoSyncActive) } + if err := tester.downloader.DeliverBodies("bad peer", [][]*types.Transaction{}, [][]*types.Header{}); err != errNoSyncActive { t.Errorf("error mismatch: have %v, want %v", err, errNoSyncActive) } + if err := tester.downloader.DeliverReceipts("bad peer", [][]*types.Receipt{}); err != errNoSyncActive { t.Errorf("error mismatch: have %v, want %v", err, errNoSyncActive) } @@ -785,6 +855,7 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) { // Make sure canceling works with a pristine downloader tester.downloader.Cancel() + if !tester.downloader.queue.Idle() { t.Errorf("download queue not idle") } @@ -792,7 +863,9 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("peer", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + tester.downloader.Cancel() + if !tester.downloader.queue.Idle() { t.Errorf("download queue not idle") } @@ -817,9 +890,11 @@ func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { id := fmt.Sprintf("peer #%d", i) tester.newPeer(id, protocol, chain.shorten(chain.len()/(i+1))) } + if err := tester.sync("peer #0", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chain.len()) } @@ -846,6 +921,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chain.len()) // Check that no peers have been dropped off @@ -885,23 +961,28 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("peer", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chain.len()) // Validate the number of block bodies that should have been requested bodiesNeeded, receiptsNeeded := 0, 0 + for _, block := range chain.blockm { if mode != LightSync && block != tester.genesis && (len(block.Transactions()) > 0 || len(block.Uncles()) > 0) { bodiesNeeded++ } } + for _, receipt := range chain.receiptm { if mode == FastSync && len(receipt) > 0 { receiptsNeeded++ } } + if int(bodiesHave) != bodiesNeeded { t.Errorf("body retrieval count mismatch: have %v, want %v", bodiesHave, bodiesNeeded) } + if int(receiptsHave) != receiptsNeeded { t.Errorf("receipt retrieval count mismatch: have %v, want %v", receiptsHave, receiptsNeeded) } @@ -929,9 +1010,11 @@ func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { } // Synchronise with the valid peer and make sure sync succeeds tester.newPeer("valid", protocol, chain) + if err := tester.sync("valid", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chain.len()) } @@ -955,15 +1038,18 @@ func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { delete(brokenChain.blockm, brokenChain.chain[1]) delete(brokenChain.receiptm, brokenChain.chain[1]) tester.newPeer("attack", protocol, brokenChain) + if err := tester.sync("attack", nil, mode); err == nil { t.Fatalf("succeeded attacker synchronisation") } // Synchronise with the valid peer and make sure sync succeeds tester.newPeer("valid", protocol, chain) + if err := tester.sync("valid", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + assertOwnChain(t, tester, chain.len()) } @@ -991,6 +1077,7 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("fast-attack", nil, mode); err == nil { t.Fatalf("succeeded fast attacker synchronisation") } + if head := tester.CurrentHeader().Number.Int64(); int(head) > MaxHeaderFetch { t.Errorf("rollback head mismatch: have %v, want at most %v", head, MaxHeaderFetch) } @@ -1007,9 +1094,11 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("block-attack", nil, mode); err == nil { t.Fatalf("succeeded block attacker synchronisation") } + if head := tester.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) } + if mode == FastSync { if head := tester.CurrentBlock().NumberU64(); head != 0 { t.Errorf("fast sync pivot block #%d not rolled back", head) @@ -1021,18 +1110,22 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { // but already imported pivot block. withholdAttackChain := chain.shorten(chain.len()) tester.newPeer("withhold-attack", protocol, withholdAttackChain) + tester.downloader.syncInitHook = func(uint64, uint64) { for i := missing; i < withholdAttackChain.len(); i++ { delete(withholdAttackChain.headerm, withholdAttackChain.chain[i]) } + tester.downloader.syncInitHook = nil } if err := tester.sync("withhold-attack", nil, mode); err == nil { t.Fatalf("succeeded withholding attacker synchronisation") } + if head := tester.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) } + if mode == FastSync { if head := tester.CurrentBlock().NumberU64(); head != 0 { t.Errorf("fast sync pivot block #%d not rolled back", head) @@ -1044,17 +1137,21 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { // sync. Note, we can't assert anything about the receipts since we won't purge the // database of them, hence we can't use assertOwnChain. tester.newPeer("valid", protocol, chain) + if err := tester.sync("valid", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } + if hs := len(tester.ownHeaders); hs != chain.len() { t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, chain.len()) } + if mode != LightSync { if bs := len(tester.ownBlocks); bs != chain.len() { t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, chain.len()) } } + tester.terminate() } @@ -1077,9 +1174,11 @@ func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { chain := testChainBase.shorten(1) tester.newPeer("attack", protocol, chain) + if err := tester.sync("attack", big.NewInt(1000000), mode); err != errStallingPeer { t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errStallingPeer) } + tester.terminate() } @@ -1113,6 +1212,7 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { // Run the tests and check disconnection status tester := newTester() defer tester.terminate() + chain := testChainBase.shorten(1) for i, tt := range tests { @@ -1121,6 +1221,7 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { if err := tester.newPeer(id, protocol, chain); err != nil { t.Fatalf("test %d: failed to register new peer: %v", i, err) } + if _, ok := tester.peers[id]; !ok { t.Fatalf("test %d: registered peer not found", i) } @@ -1128,6 +1229,7 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { tester.downloader.synchroniseMock = func(string, common.Hash) error { return tt.result } tester.downloader.Synchronise(id, tester.genesis.Hash(), big.NewInt(1000), FullSync) + if _, ok := tester.peers[id]; !ok != tt.drop { t.Errorf("test %d: peer drop mismatch for %v: have %v, want %v", i, tt.result, !ok, tt.drop) } @@ -1145,6 +1247,7 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester := newTester() defer tester.terminate() + chain := testChainBase.shorten(blockCacheMaxItems - 15) // Set a sync init hook to catch progress changes @@ -1153,17 +1256,20 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester.downloader.syncInitHook = func(origin, latest uint64) { starting <- struct{}{} + <-progress } checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) // Synchronise half the blocks and check initial progress tester.newPeer("peer-half", protocol, chain.shorten(chain.len()/2)) + pending := new(sync.WaitGroup) pending.Add(1) go func() { defer pending.Done() + if err := tester.sync("peer-half", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1173,13 +1279,16 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { HighestBlock: uint64(chain.len()/2 - 1), }) progress <- struct{}{} + pending.Wait() // Synchronise all the blocks and check continuation progress tester.newPeer("peer-full", protocol, chain) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("peer-full", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1193,6 +1302,7 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Check final progress after successful sync progress <- struct{}{} + pending.Wait() checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ StartingBlock: uint64(chain.len()/2 - 1), @@ -1225,6 +1335,7 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester := newTester() defer tester.terminate() + chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHeaderFetch) chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHeaderFetch) @@ -1234,16 +1345,20 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester.downloader.syncInitHook = func(origin, latest uint64) { starting <- struct{}{} + <-progress } checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) // Synchronise with one of the forks and check progress tester.newPeer("fork A", protocol, chainA) + pending := new(sync.WaitGroup) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("fork A", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1254,6 +1369,7 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { HighestBlock: uint64(chainA.len() - 1), }) progress <- struct{}{} + pending.Wait() // Simulate a successful sync above the fork @@ -1262,8 +1378,10 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Synchronise with the second fork and check progress resets tester.newPeer("fork B", protocol, chainB) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("fork B", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1277,6 +1395,7 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Check final progress after successful sync progress <- struct{}{} + pending.Wait() checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ StartingBlock: uint64(testChainBase.len()) - 1, @@ -1297,6 +1416,7 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester := newTester() defer tester.terminate() + chain := testChainBase.shorten(blockCacheMaxItems - 15) // Set a sync init hook to catch progress changes @@ -1305,6 +1425,7 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester.downloader.syncInitHook = func(origin, latest uint64) { starting <- struct{}{} + <-progress } checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) @@ -1319,8 +1440,10 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { pending := new(sync.WaitGroup) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("faulty", nil, mode); err == nil { panic("succeeded faulty synchronisation") } @@ -1330,15 +1453,19 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { HighestBlock: uint64(brokenChain.len() - 1), }) progress <- struct{}{} + pending.Wait() + afterFailedSync := tester.downloader.Progress() // Synchronise with a good peer and check that the progress origin remind the same // after a failure tester.newPeer("valid", protocol, chain) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("valid", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1348,6 +1475,7 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Check final progress after successful sync progress <- struct{}{} + pending.Wait() checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ CurrentBlock: uint64(chain.len() - 1), @@ -1366,6 +1494,7 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { tester := newTester() defer tester.terminate() + chain := testChainBase.shorten(blockCacheMaxItems - 15) // Set a sync init hook to catch progress changes @@ -1373,6 +1502,7 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { progress := make(chan struct{}) tester.downloader.syncInitHook = func(origin, latest uint64) { starting <- struct{}{} + <-progress } checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) @@ -1380,6 +1510,7 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Create and sync with an attacker that promises a higher chain than available. brokenChain := chain.shorten(chain.len()) numMissing := 5 + for i := brokenChain.len() - 2; i > brokenChain.len()-numMissing; i-- { delete(brokenChain.headerm, brokenChain.chain[i]) } @@ -1387,8 +1518,10 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { pending := new(sync.WaitGroup) pending.Add(1) + go func() { defer pending.Done() + if err := tester.sync("attack", nil, mode); err == nil { panic("succeeded attacker synchronisation") } @@ -1398,7 +1531,9 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { HighestBlock: uint64(brokenChain.len() - 1), }) progress <- struct{}{} + pending.Wait() + afterFailedSync := tester.downloader.Progress() // Synchronise with a good peer and check that the progress height has been reduced to @@ -1409,6 +1544,7 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { go func() { defer pending.Done() + if err := tester.sync("valid", nil, mode); err != nil { panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) } @@ -1421,6 +1557,7 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Check final progress after successful sync. progress <- struct{}{} + pending.Wait() checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ CurrentBlock: uint64(validChain.len() - 1), @@ -1439,6 +1576,7 @@ func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) { master := newTester() defer master.terminate() + chain := testChainBase.shorten(15) for i := 0; i < 200; i++ { @@ -1455,6 +1593,7 @@ func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) { if err := tester.sync("peer", nil, mode); err != nil { t.Errorf("test %d: sync failed: %v", i, err) } + tester.terminate() } } @@ -1491,6 +1630,7 @@ func (ftp *floodingTestPeer) RequestHeadersByNumber(from uint64, count, skip int // None of the extra deliveries should block. timeout := time.After(60 * time.Second) launched := false + for i := 0; i < cap(deliveriesDone); i++ { select { case <-deliveriesDone: @@ -1501,12 +1641,14 @@ func (ftp *floodingTestPeer) RequestHeadersByNumber(from uint64, count, skip int ftp.peer.RequestHeadersByNumber(from, count, skip, reverse) deliveriesDone <- struct{}{} }() + launched = true } case <-timeout: panic("blocked") } } + return nil } @@ -1549,13 +1691,16 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { } reqs := func(from, count, span int) []int { var r []int + num := from for len(r) < count { r = append(r, num) num += span + 1 } + return r } + for i, tt := range testCases { from, count, span, max := calculateRequestSpan(tt.remoteHeight, tt.localHeight) data := reqs(int(from), count, span) @@ -1563,9 +1708,11 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { if max != uint64(data[len(data)-1]) { t.Errorf("test %d: wrong last value %d != %d", i, data[len(data)-1], max) } + failed := false if len(data) != len(tt.expected) { failed = true + t.Errorf("test %d: length wrong, expected %d got %d", i, len(tt.expected), len(data)) } else { for j, n := range data { @@ -1575,9 +1722,11 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { } } } + if failed { res := strings.ReplaceAll(fmt.Sprint(data), " ", ",") exp := strings.ReplaceAll(fmt.Sprint(tt.expected), " ", ",") + t.Logf("got: %v\n", res) t.Logf("exp: %v\n", exp) t.Errorf("test %d: wrong values", i) @@ -1610,9 +1759,11 @@ func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) { if mode == FastSync || mode == LightSync { expect = errUnsyncedPeer } + if err := tester.sync("peer", nil, mode); !errors.Is(err, expect) { t.Fatalf("block sync error mismatch: have %v, want %v", err, expect) } + if mode == FastSync || mode == LightSync { assertOwnChain(t, tester, 1) } else { diff --git a/les/downloader/modes.go b/les/downloader/modes.go index 3ea14d22d7..3bf4ed2d06 100644 --- a/les/downloader/modes.go +++ b/les/downloader/modes.go @@ -77,5 +77,6 @@ func (mode *SyncMode) UnmarshalText(text []byte) error { default: return fmt.Errorf(`unknown sync mode %q, want "full", "fast" or "light"`, text) } + return nil } diff --git a/les/downloader/peer.go b/les/downloader/peer.go index 92930894ac..c12220b9c9 100644 --- a/les/downloader/peer.go +++ b/les/downloader/peer.go @@ -135,6 +135,7 @@ func (p *peerConnection) FetchHeaders(from uint64, count int) error { if !atomic.CompareAndSwapInt32(&p.headerIdle, 0, 1) { return errAlreadyFetching } + p.headerStarted = time.Now() // Issue the header retrieval request (absolute upwards without gaps) @@ -149,6 +150,7 @@ func (p *peerConnection) FetchBodies(request *fetchRequest) error { if !atomic.CompareAndSwapInt32(&p.blockIdle, 0, 1) { return errAlreadyFetching } + p.blockStarted = time.Now() go func() { @@ -157,6 +159,7 @@ func (p *peerConnection) FetchBodies(request *fetchRequest) error { for _, header := range request.Headers { hashes = append(hashes, header.Hash()) } + p.peer.RequestBodies(hashes) }() @@ -169,6 +172,7 @@ func (p *peerConnection) FetchReceipts(request *fetchRequest) error { if !atomic.CompareAndSwapInt32(&p.receiptIdle, 0, 1) { return errAlreadyFetching } + p.receiptStarted = time.Now() go func() { @@ -177,6 +181,7 @@ func (p *peerConnection) FetchReceipts(request *fetchRequest) error { for _, header := range request.Headers { hashes = append(hashes, header.Hash()) } + p.peer.RequestReceipts(hashes) }() @@ -189,6 +194,7 @@ func (p *peerConnection) FetchNodeData(hashes []common.Hash) error { if !atomic.CompareAndSwapInt32(&p.stateIdle, 0, 1) { return errAlreadyFetching } + p.stateStarted = time.Now() go p.peer.RequestNodeData(hashes) @@ -235,6 +241,7 @@ func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int { if cap > MaxHeaderFetch { cap = MaxHeaderFetch } + return cap } @@ -245,6 +252,7 @@ func (p *peerConnection) BlockCapacity(targetRTT time.Duration) int { if cap > MaxBlockFetch { cap = MaxBlockFetch } + return cap } @@ -255,6 +263,7 @@ func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int { if cap > MaxReceiptFetch { cap = MaxReceiptFetch } + return cap } @@ -265,6 +274,7 @@ func (p *peerConnection) NodeDataCapacity(targetRTT time.Duration) int { if cap > MaxStateFetch { cap = MaxStateFetch } + return cap } @@ -281,6 +291,7 @@ func (p *peerConnection) MarkLacking(hash common.Hash) { break } } + p.lacking[hash] = struct{}{} } @@ -291,6 +302,7 @@ func (p *peerConnection) Lacks(hash common.Hash) bool { defer p.lock.RUnlock() _, ok := p.lacking[hash] + return ok } @@ -348,15 +360,18 @@ func (ps *peerSet) Register(p *peerConnection) error { ps.lock.Unlock() return errAlreadyRegistered } + p.rates = msgrate.NewTracker(ps.rates.MeanCapacities(), ps.rates.MedianRoundTrip()) if err := ps.rates.Track(p.id, p.rates); err != nil { ps.lock.Unlock() return err } + ps.peers[p.id] = p ps.lock.Unlock() ps.newPeerFeed.Send(p) + return nil } @@ -364,16 +379,19 @@ func (ps *peerSet) Register(p *peerConnection) error { // actions to/from that particular entity. func (ps *peerSet) Unregister(id string) error { ps.lock.Lock() + p, ok := ps.peers[id] if !ok { ps.lock.Unlock() return errNotRegistered } + delete(ps.peers, id) ps.rates.Untrack(id) ps.lock.Unlock() ps.peerDropFeed.Send(p) + return nil } @@ -402,6 +420,7 @@ func (ps *peerSet) AllPeers() []*peerConnection { for _, p := range ps.peers { list = append(list, p) } + return list } @@ -469,12 +488,14 @@ func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peer idle = make([]*peerConnection, 0, len(ps.peers)) tps = make([]int, 0, len(ps.peers)) ) + for _, p := range ps.peers { if p.version >= minProtocol && p.version <= maxProtocol { if idleCheck(p) { idle = append(idle, p) tps = append(tps, capacity(p)) } + total++ } } @@ -482,6 +503,7 @@ func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peer // And sort them sortPeers := &peerCapacitySort{idle, tps} sort.Sort(sortPeers) + return sortPeers.p, total } diff --git a/les/downloader/queue.go b/les/downloader/queue.go index 836826a043..fa44e90d31 100644 --- a/les/downloader/queue.go +++ b/les/downloader/queue.go @@ -77,9 +77,11 @@ func newFetchResult(header *types.Header, fastSync bool) *fetchResult { if !header.EmptyBody() { item.pending |= (1 << bodyType) } + if fastSync && !header.EmptyReceipts() { item.pending |= (1 << receiptType) } + return item } @@ -153,6 +155,7 @@ func newQueue(blockCacheLimit int, thresholdInitialSize int) *queue { lock: lock, } q.Reset(blockCacheLimit, thresholdInitialSize) + return q } @@ -297,6 +300,7 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header { // Insert all the headers prioritised by the contained block number inserts := make([]*types.Header, 0, len(headers)) + for _, header := range headers { // Make sure chain order is honoured and preserved throughout hash := header.Hash() @@ -304,6 +308,7 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header { log.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", from) break } + if q.headerHead != (common.Hash{}) && q.headerHead != header.ParentHash { log.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) break @@ -326,10 +331,12 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header { q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64())) } } + inserts = append(inserts, header) q.headerHead = hash from++ } + return inserts } @@ -342,6 +349,7 @@ func (q *queue) Results(block bool) []*fetchResult { if !block && !q.resultCache.HasCompletedItems() { return nil } + closed := false for !closed && !q.resultCache.HasCompletedItems() { // In order to wait on 'active', we need to obtain the lock. @@ -369,12 +377,15 @@ func (q *queue) Results(block bool) []*fetchResult { for _, uncle := range result.Uncles { size += uncle.Size() } + for _, receipt := range result.Receipts { size += receipt.Size() } + for _, tx := range result.Transactions { size += common.StorageSize(tx.Size()) } + q.resultSize = common.StorageSize(blockCacheSizeWeight)*size + (1-common.StorageSize(blockCacheSizeWeight))*q.resultSize } @@ -390,6 +401,7 @@ func (q *queue) Results(block bool) []*fetchResult { info = append(info, "throttle", throttleThreshold) log.Info("Downloader queue stats", info...) } + return results } @@ -440,12 +452,14 @@ func (q *queue) ReserveHeaders(p *peerConnection, count int) *fetchRequest { if send == 0 { return nil } + request := &fetchRequest{ Peer: p, From: send, Time: time.Now(), } q.headerPendPool[p.id] = request + return request } @@ -489,6 +503,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common if taskQueue.Empty() { return nil, false, true } + if _, ok := pendPool[p.id]; ok { return nil, false, false } @@ -497,6 +512,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common skip := make([]*types.Header, 0) progress := false throttled := false + for proc := 0; len(send) < count && !taskQueue.Empty(); proc++ { // the task queue will pop items in order, so the highest prio block // is also the lowest block number. @@ -510,12 +526,18 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common // Don't put back in the task queue, this item has already been // delivered upstream taskQueue.PopItem() + progress = true + delete(taskPool, header.Hash()) + proc = proc - 1 + log.Error("Fetch reservation already delivered", "number", header.Number.Uint64()) + continue } + if throttle { // There are no resultslots available. Leave it in the task queue // However, if there are any left as 'skipped', we should not tell @@ -524,18 +546,22 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common throttled = len(skip) == 0 break } + if err != nil { // this most definitely should _not_ happen log.Warn("Failed to reserve headers", "err", err) // There are no resultslots available. Leave it in the task queue break } + if item.Done(kind) { // If it's a noop, we can skip this task delete(taskPool, header.Hash()) taskQueue.PopItem() + proc = proc - 1 progress = true + continue } // Remove it from the task queue @@ -551,6 +577,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common for _, header := range skip { taskQueue.Push(header, -int64(header.Number.Uint64())) } + if q.resultCache.HasCompletedItems() { // Wake Results, resultCache was modified q.active.Signal() @@ -559,12 +586,14 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common if len(send) == 0 { return nil, progress, throttled } + request := &fetchRequest{ Peer: p, Headers: send, Time: time.Now(), } pendPool[p.id] = request + return request, progress, throttled } @@ -596,9 +625,11 @@ func (q *queue) cancel(request *fetchRequest, taskQueue interface{}, pendPool ma if request.From > 0 { taskQueue.(*prque.Prque[int64, uint64]).Push(request.From, -int64(request.From)) } + for _, header := range request.Headers { taskQueue.(*prque.Prque[int64, *types.Header]).Push(header, -int64(header.Number.Uint64())) } + delete(pendPool, request.Peer.id) } @@ -613,12 +644,15 @@ func (q *queue) Revoke(peerID string) { for _, header := range request.Headers { q.blockTaskQueue.Push(header, -int64(header.Number.Uint64())) } + delete(q.blockPendPool, peerID) } + if request, ok := q.receiptPendPool[peerID]; ok { for _, header := range request.Headers { q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64())) } + delete(q.receiptPendPool, peerID) } } @@ -659,6 +693,7 @@ func (q *queue) ExpireReceipts(timeout time.Duration) map[string]int { func (q *queue) expire(timeout time.Duration, pendPool map[string]*fetchRequest, taskQueue interface{}, timeoutMeter metrics.Meter) map[string]int { // Iterate over the expired requests and return each to the queue expiries := make(map[string]int) + for id, request := range pendPool { if time.Since(request.Time) > timeout { // Update the metrics with the timeout @@ -668,6 +703,7 @@ func (q *queue) expire(timeout time.Duration, pendPool map[string]*fetchRequest, if request.From > 0 { taskQueue.(*prque.Prque[int64, uint64]).Push(request.From, -int64(request.From)) } + for _, header := range request.Headers { taskQueue.(*prque.Prque[int64, *types.Header]).Push(header, -int64(header.Number.Uint64())) } @@ -678,6 +714,7 @@ func (q *queue) expire(timeout time.Duration, pendPool map[string]*fetchRequest, delete(pendPool, id) } } + return expiries } @@ -704,6 +741,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh if request == nil { return 0, errNoFetchesPending } + headerReqTimer.UpdateSince(request.Time) delete(q.headerPendPool, id) @@ -714,24 +752,33 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh if accepted { if headers[0].Number.Uint64() != 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 { logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target) + accepted = false } } + if accepted { parentHash := headers[0].Hash() + for i, header := range headers[1:] { hash := header.Hash() if want := request.From + 1 + uint64(i); header.Number.Uint64() != want { logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want) + accepted = false + break } + if parentHash != header.ParentHash { logger.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) + accepted = false + break } // Set-up parent hash for next round @@ -747,9 +794,11 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh q.headerPeerMiss[id] = make(map[uint64]struct{}) miss = q.headerPeerMiss[id] } + miss[request.From] = struct{}{} q.headerTaskQueue.Push(request.From, -int64(request.From)) + return 0, errors.New("delivery not accepted") } // Clean up a successful fetch and try to deliver any sub-results @@ -760,6 +809,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh for q.headerProced+ready < len(q.headerResults) && q.headerResults[q.headerProced+ready] != nil { ready += MaxHeaderFetch } + if ready > 0 { // Headers are ready for delivery, gather them and push forward (non blocking) process := make([]*types.Header, ready) @@ -776,6 +826,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh if len(q.headerTaskPool) == 0 { q.headerContCh <- false } + return len(headers), nil } @@ -785,14 +836,17 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLists [][]*types.Header) (int, error) { q.lock.Lock() defer q.lock.Unlock() + trieHasher := trie.NewStackTrie(nil) validate := func(index int, header *types.Header) error { if types.DeriveSha(types.Transactions(txLists[index]), trieHasher) != header.TxHash { return errInvalidBody } + if types.CalcUncleHash(uncleLists[index]) != header.UncleHash { return errInvalidBody } + return nil } @@ -801,6 +855,7 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLi result.Uncles = uncleLists[index] result.SetBodyDone() } + return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, bodyReqTimer, len(txLists), validate, reconstruct) } @@ -811,17 +866,20 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLi func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int, error) { q.lock.Lock() defer q.lock.Unlock() + trieHasher := trie.NewStackTrie(nil) validate := func(index int, header *types.Header) error { if types.DeriveSha(types.Receipts(receiptList[index]), trieHasher) != header.ReceiptHash { return errInvalidReceipt } + return nil } reconstruct := func(index int, result *fetchResult) { result.Receipts = receiptList[index] result.SetReceiptsDone() } + return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, receiptReqTimer, len(receiptList), validate, reconstruct) } @@ -840,6 +898,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, if request == nil { return 0, errNoFetchesPending } + reqTimer.UpdateSince(request.Time) delete(pendPool, id) @@ -856,6 +915,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, i int hashes []common.Hash ) + for _, header := range request.Headers { // Short circuit assembly if no more fetch results are found if i >= results { @@ -866,6 +926,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, failure = err break } + hashes = append(hashes, header.Hash()) i++ } @@ -878,10 +939,12 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, // or it was indeed a no-op. This should not happen, but if it does it's // not something to panic about log.Error("Delivery stale", "stale", stale, "number", header.Number.Uint64(), "err", err) + failure = errStaleDelivery } // Clean up a successful fetch delete(taskPool, hashes[accepted]) + accepted++ } // Return all failed or missing fetches to the queue @@ -892,6 +955,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, if accepted > 0 { q.active.Signal() } + if failure == nil { return accepted, nil } @@ -899,6 +963,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, if accepted > 0 { return accepted, fmt.Errorf("partial failure: %v", failure) } + return accepted, fmt.Errorf("%w: %v", failure, errStaleDelivery) } diff --git a/les/downloader/queue_test.go b/les/downloader/queue_test.go index 44b2208595..6fa9ebc3e0 100644 --- a/les/downloader/queue_test.go +++ b/les/downloader/queue_test.go @@ -42,13 +42,16 @@ func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Bloc // Add one tx to every secondblock if !empty && i%2 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) if err != nil { panic(err) } + block.AddTx(tx) } }) + return blocks, receipts } @@ -75,6 +78,7 @@ func (chain *chainData) headers() []*types.Header { for i, b := range chain.blocks { hdrs[i] = b.Header() } + return hdrs } @@ -87,6 +91,7 @@ func dummyPeer(id string) *peerConnection { id: id, lacking: make(map[common.Hash]struct{}), } + return p } @@ -98,16 +103,20 @@ func TestBasics(t *testing.T) { if !q.Idle() { t.Errorf("new queue should be idle") } + q.Prepare(1, FastSync) + if res := q.Results(false); len(res) != 0 { t.Fatal("new queue should have 0 results") } // Schedule a batch of headers q.Schedule(chain.headers(), 1) + if q.Idle() { t.Errorf("queue should not be idle") } + if got, exp := q.PendingBlocks(), chain.Len(); got != exp { t.Errorf("wrong pending block count, got %d, exp %d", got, exp) } @@ -119,6 +128,7 @@ func TestBasics(t *testing.T) { // queue that a certain peer will deliver them for us { peer := dummyPeer("peer-1") + fetchReq, _, throttle := q.ReserveBodies(peer, 50) if !throttle { // queue size is only 10, so throttling should occur @@ -128,13 +138,16 @@ func TestBasics(t *testing.T) { if got, exp := len(fetchReq.Headers), 5; got != exp { t.Fatalf("expected %d requests, got %d", exp, got) } + if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp { 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) } @@ -151,9 +164,11 @@ func TestBasics(t *testing.T) { t.Fatalf("should have no fetches, got %d", len(fetchReq.Headers)) } } + 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) } @@ -161,6 +176,7 @@ func TestBasics(t *testing.T) { // The receipt delivering peer should not be affected // by the throttling of body deliveries peer := dummyPeer("peer-3") + fetchReq, _, throttle := q.ReserveReceipts(peer, 50) if !throttle { // queue size is only 10, so throttling should occur @@ -170,16 +186,20 @@ func TestBasics(t *testing.T) { if got, exp := len(fetchReq.Headers), 5; got != exp { t.Fatalf("expected %d requests, got %d", exp, got) } + if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp { 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-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) } @@ -193,12 +213,15 @@ func TestEmptyBlocks(t *testing.T) { q.Prepare(1, FastSync) // Schedule a batch of headers q.Schedule(emptyChain.headers(), 1) + if q.Idle() { t.Errorf("queue should not be idle") } + if got, exp := q.PendingBlocks(), len(emptyChain.blocks); got != exp { t.Errorf("wrong pending block count, got %d, exp %d", got, exp) } + if got, exp := q.PendingReceipts(), 0; got != exp { t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp) } @@ -221,9 +244,11 @@ 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()) } @@ -236,12 +261,15 @@ func TestEmptyBlocks(t *testing.T) { t.Fatal("there should be no receipt 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) } @@ -258,17 +286,24 @@ func XTestDelivery(t *testing.T) { world.receipts = rec world.chain = blo world.progress(10) + if false { log.Root().SetHandler(log.StdoutHandler) } + q := newQueue(10, 10) + var wg sync.WaitGroup + q.Prepare(1, FastSync) wg.Add(1) + go func() { // deliver headers defer wg.Done() + c := 1 + for { //fmt.Printf("getting headers from %d\n", c) hdrs := world.headers(c) @@ -280,10 +315,13 @@ func XTestDelivery(t *testing.T) { } }() wg.Add(1) + go func() { // collect results defer wg.Done() + tot := 0 + for { res := q.Results(true) tot += len(res) @@ -293,29 +331,38 @@ func XTestDelivery(t *testing.T) { } }() wg.Add(1) + go func() { defer wg.Done() // reserve body fetch i := 4 + for { peer := dummyPeer(fmt.Sprintf("peer-%d", i)) + f, _, _ := q.ReserveBodies(peer, rand.Intn(30)) if f != nil { var emptyList []*types.Header + var txs [][]*types.Transaction + var uncles [][]*types.Header + numToSkip := rand.Intn(len(f.Headers)) for _, hdr := range f.Headers[0 : len(f.Headers)-numToSkip] { txs = append(txs, world.getTransactions(hdr.Number.Uint64())) uncles = append(uncles, emptyList) } + time.Sleep(100 * time.Millisecond) + _, err := q.DeliverBodies(peer.id, txs, uncles) if err != nil { fmt.Printf("delivered %d bodies %v\n", len(txs), err) } } else { i++ + time.Sleep(200 * time.Millisecond) } } @@ -324,6 +371,7 @@ func XTestDelivery(t *testing.T) { defer wg.Done() // reserve receiptfetch peer := dummyPeer("peer-3") + for { f, _, _ := q.ReserveReceipts(peer, rand.Intn(50)) if f != nil { @@ -331,10 +379,12 @@ func XTestDelivery(t *testing.T) { for _, hdr := range f.Headers { rcs = append(rcs, world.getReceipts(hdr.Number.Uint64())) } + _, err := q.DeliverReceipts(peer.id, rcs) if err != nil { fmt.Printf("delivered %d receipts %v\n", len(rcs), err) } + time.Sleep(100 * time.Millisecond) } else { time.Sleep(200 * time.Millisecond) @@ -342,21 +392,26 @@ func XTestDelivery(t *testing.T) { } }() wg.Add(1) + go func() { defer wg.Done() + for i := 0; i < 50; i++ { time.Sleep(300 * time.Millisecond) //world.tick() //fmt.Printf("trying to progress\n") world.progress(rand.Intn(100)) } + for i := 0; i < 50; i++ { time.Sleep(2990 * time.Millisecond) } }() wg.Add(1) + go func() { defer wg.Done() + for { time.Sleep(990 * time.Millisecond) fmt.Printf("world block tip is %d\n", @@ -369,6 +424,7 @@ func XTestDelivery(t *testing.T) { func newNetwork() *network { var l sync.RWMutex + return &network{ cond: sync.NewCond(&l), offset: 1, // block 1 is at blocks[0] @@ -394,6 +450,7 @@ func (n *network) getReceipts(blocknum uint64) types.Receipts { fmt.Printf("Err, got %d exp %d\n", got, blocknum) panic("sd") } + return n.receipts[index] } @@ -415,7 +472,9 @@ func (n *network) progress(numBlocks int) { func (n *network) headers(from int) []*types.Header { numHeaders := 128 + var hdrs []*types.Header + index := from - n.offset for index >= len(n.chain) { @@ -428,11 +487,14 @@ func (n *network) headers(from int) []*types.Header { } n.lock.RLock() defer n.lock.RUnlock() + for i, b := range n.chain[index:] { hdrs = append(hdrs, b.Header()) + if i >= numHeaders { break } } + return hdrs } diff --git a/les/downloader/resultstore.go b/les/downloader/resultstore.go index 7fcade2946..1fa3f8d006 100644 --- a/les/downloader/resultstore.go +++ b/les/downloader/resultstore.go @@ -63,7 +63,9 @@ func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 { if threshold >= limit { threshold = limit } + r.throttleThreshold = threshold + return r.throttleThreshold } @@ -81,14 +83,17 @@ func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, thro defer r.lock.Unlock() var index int + item, index, stale, throttled, err = r.getFetchResult(header.Number.Uint64()) if err != nil || stale || throttled { return stale, throttled, item, err } + if item == nil { item = newFetchResult(header, fastSync) r.items[index] = item } + return stale, throttled, item, err } @@ -101,6 +106,7 @@ func (r *resultStore) GetDeliverySlot(headerNumber uint64) (*fetchResult, bool, defer r.lock.RUnlock() res, _, stale, _, err := r.getFetchResult(headerNumber) + return res, stale, err } @@ -115,12 +121,16 @@ func (r *resultStore) getFetchResult(headerNumber uint64) (item *fetchResult, in err = fmt.Errorf("%w: index allocation went beyond available resultStore space "+ "(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d", errInvalidChain, index, headerNumber, r.resultOffset, len(r.items)) + return nil, index, stale, throttle, err } + if stale { return nil, index, stale, throttle, nil } + item = r.items[index] + return item, index, stale, throttle, nil } @@ -133,9 +143,11 @@ func (r *resultStore) HasCompletedItems() bool { if len(r.items) == 0 { return false } + if item := r.items[0]; item != nil && item.AllDone() { return true } + return false } @@ -147,16 +159,19 @@ func (r *resultStore) countCompleted() int { // We iterate from the already known complete point, and see // if any more has completed since last count index := atomic.LoadInt32(&r.indexIncomplete) + for ; ; index++ { if index >= int32(len(r.items)) { break } + result := r.items[index] if result == nil || !result.AllDone() { break } } atomic.StoreInt32(&r.indexIncomplete, index) + return int(index) } @@ -169,11 +184,13 @@ func (r *resultStore) GetCompleted(limit int) []*fetchResult { if limit > completed { limit = completed } + results := make([]*fetchResult, limit) copy(results, r.items[:limit]) // Delete the results from the cache and clear the tail. copy(r.items, r.items[limit:]) + for i := len(r.items) - limit; i < len(r.items); i++ { r.items[i] = nil } diff --git a/les/downloader/statesync.go b/les/downloader/statesync.go index b85e2a4e2c..5c18521f39 100644 --- a/les/downloader/statesync.go +++ b/les/downloader/statesync.go @@ -73,6 +73,7 @@ func (d *Downloader) syncState(root common.Hash) *stateSync { s.err = errCancelStateFetch close(s.done) } + return s } @@ -101,6 +102,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { finished []*stateReq // Completed or failed requests timeout = make(chan *stateReq) // Timed out active requests ) + log.Trace("State sync starting", "root", s.root) defer func() { @@ -111,11 +113,14 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { req.peer.SetNodeDataIdle(int(req.nItems), time.Now()) } }() + go s.run() + defer s.Cancel() // Listen for peer departure events to cancel assigned tasks peerDrop := make(chan *peerConnection, 1024) + peerSub := s.d.peers.SubscribePeerDrops(peerDrop) defer peerSub.Unsubscribe() @@ -125,6 +130,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { deliverReq *stateReq deliverReqCh chan *stateReq ) + if len(finished) > 0 { deliverReq = finished[0] deliverReqCh = s.deliver @@ -161,6 +167,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { req.delivered = time.Now() finished = append(finished, req) + delete(active, pack.PeerId()) // Handle dropped peer connections: @@ -176,6 +183,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { req.delivered = time.Now() finished = append(finished, req) + delete(active, p.id) // Handle timed-out requests: @@ -186,6 +194,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { if active[req.peer.id] != req { continue } + req.delivered = time.Now() // Move the timed out data back into the download queue finished = append(finished, req) @@ -221,6 +230,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { // are marked as idle and de facto _are_ idle. func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []*stateReq, timeout chan *stateReq, peerDrop chan *peerConnection) { log.Trace("State sync spinning down", "active", len(active), "finished", len(finished)) + for len(active) > 0 { var ( req *stateReq @@ -239,9 +249,11 @@ func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []* case req = <-timeout: reason = "timeout" } + if req == nil { continue } + req.peer.log.Trace("State peer marked idle (spindown)", "req.items", int(req.nItems), "reason", reason) req.timer.Stop() delete(active, req.peer.id) @@ -317,11 +329,13 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync { // finish. func (s *stateSync) run() { 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) } @@ -336,6 +350,7 @@ func (s *stateSync) Cancel() error { s.cancelOnce.Do(func() { close(s.cancel) }) + return s.Wait() } @@ -348,6 +363,7 @@ func (s *stateSync) Cancel() error { func (s *stateSync) loop() (err error) { // Listen for new peer events to assign tasks to them newPeer := make(chan *peerConnection, 1024) + peerSub := s.d.peers.SubscribeNewPeers(newPeer) defer peerSub.Unsubscribe() defer func() { @@ -362,6 +378,7 @@ func (s *stateSync) loop() (err error) { if err = s.commit(false); err != nil { return err } + s.assignTasks() // Tasks assigned, wait for something to happen select { @@ -377,10 +394,12 @@ func (s *stateSync) loop() (err error) { case req := <-s.deliver: // Response, disconnect or timeout triggered, drop the peer if stalling log.Trace("Received node data response", "peer", req.peer.id, "count", len(req.response), "dropped", req.dropped, "timeout", !req.dropped && req.timedOut()) + if req.nItems <= 2 && !req.dropped && req.timedOut() { // 2 items are the minimum requested, if even that times out, we've no use of // this peer at the moment. log.Warn("Stalling state sync, dropping peer", "peer", req.peer.id) + if s.d.dropPeer == nil { // The dropPeer method is nil when `--copydb` is used for a local copy. // Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored @@ -402,12 +421,14 @@ func (s *stateSync) loop() (err error) { // Process all the received blobs and check for stale delivery delivered, err := s.process(req) req.peer.SetNodeDataIdle(delivered, req.delivered) + if err != nil { log.Warn("Node data write error", "err", err) return err } } } + return nil } @@ -415,17 +436,22 @@ func (s *stateSync) commit(force bool) error { if !force && s.bytesUncommitted < ethdb.IdealBatchSize { return nil } + start := time.Now() + b := s.d.stateDB.NewBatch() if err := s.sched.Commit(b); err != nil { return err } + if err := b.Write(); err != nil { return fmt.Errorf("DB write error: %v", err) } + s.updateStats(s.numUncommitted, 0, 0, time.Since(start)) s.numUncommitted = 0 s.bytesUncommitted = 0 + return nil } @@ -467,6 +493,7 @@ func (s *stateSync) fillTasks(n int, req *stateReq) (nodes []common.Hash, paths attempts: make(map[string]struct{}), } } + for _, hash := range codes { s.codeTasks[hash] = &codeTask{ attempts: make(map[string]struct{}), @@ -493,8 +520,10 @@ func (s *stateSync) fillTasks(n int, req *stateReq) (nodes []common.Hash, paths } // Assign the request to this peer t.attempts[req.peer.id] = struct{}{} + codes = append(codes, hash) req.codeTasks[hash] = t + delete(s.codeTasks, hash) } @@ -517,7 +546,9 @@ func (s *stateSync) fillTasks(n int, req *stateReq) (nodes []common.Hash, paths delete(s.trieTasks, path) } + req.nItems = uint16(len(nodes) + len(codes)) + return nodes, paths, codes } @@ -569,6 +600,7 @@ func (s *stateSync) process(req *stateReq) (int, error) { // Missing item, place into the retry queue. s.trieTasks[path] = task } + for hash, task := range req.codeTasks { // If the node did deliver something, missing items may be due to a protocol // limit or a previous timeout + delayed delivery. Both cases should permit @@ -584,6 +616,7 @@ func (s *stateSync) process(req *stateReq) (int, error) { // Missing item, place into the retry queue. s.codeTasks[hash] = task } + return successful, nil } @@ -596,6 +629,7 @@ func (s *stateSync) process(req *stateReq) (int, error) { // be fetched again. func (s *stateSync) processNodeData(nodeTasks map[string]*trieTask, codeTasks map[common.Hash]*codeTask, blob []byte) (common.Hash, error) { var hash common.Hash + s.keccak.Reset() s.keccak.Write(blob) s.keccak.Read(hash[:]) diff --git a/les/downloader/testchain_test.go b/les/downloader/testchain_test.go index 400eec94e7..90216cd45f 100644 --- a/les/downloader/testchain_test.go +++ b/les/downloader/testchain_test.go @@ -51,8 +51,11 @@ var testChainForkLightA, testChainForkLightB, testChainForkHeavy *testChain func init() { var forkLen = int(fullMaxForkAncestry + 50) + var wg sync.WaitGroup + wg.Add(3) + go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }() go func() { testChainForkLightB = testChainBase.makeFork(forkLen, false, 2); wg.Done() }() go func() { testChainForkHeavy = testChainBase.makeFork(forkLen, true, 3); wg.Done() }() @@ -77,6 +80,7 @@ func newTestChain(length int, genesis *types.Block) *testChain { tc.tdm[tc.genesis.Hash()] = tc.genesis.Difficulty() tc.blockm[tc.genesis.Hash()] = tc.genesis tc.generate(length-1, 0, genesis, false) + return tc } @@ -84,6 +88,7 @@ func newTestChain(length int, genesis *types.Block) *testChain { func (tc *testChain) makeFork(length int, heavy bool, seed byte) *testChain { fork := tc.copy(tc.len() + length) fork.generate(length, seed, tc.headBlock(), heavy) + return fork } @@ -93,6 +98,7 @@ func (tc *testChain) shorten(length int) *testChain { if length > tc.len() { panic(fmt.Errorf("can't shorten test chain to %d blocks, it's only %d blocks long", length, tc.len())) } + return tc.copy(length) } @@ -104,6 +110,7 @@ func (tc *testChain) copy(newlen int) *testChain { receiptm: make(map[common.Hash][]*types.Receipt, newlen), tdm: make(map[common.Hash]*big.Int, newlen), } + for i := 0; i < len(tc.chain) && i < newlen; i++ { hash := tc.chain[i] cpy.chain = append(cpy.chain, tc.chain[i]) @@ -112,6 +119,7 @@ func (tc *testChain) copy(newlen int) *testChain { cpy.headerm[hash] = tc.headerm[hash] cpy.receiptm[hash] = tc.receiptm[hash] } + return cpy } @@ -122,7 +130,6 @@ func (tc *testChain) copy(newlen int) *testChain { func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) { // start := time.Now() // defer func() { fmt.Printf("test chain generated in %v\n", time.Since(start)) }() - blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) { block.SetCoinbase(common.Address{seed}) // If a heavy chain is requested, delay blocks to raise difficulty @@ -132,10 +139,12 @@ func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) // Include transactions to the miner to make blocks more interesting. if parent == tc.genesis && i%22 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) if err != nil { panic(err) } + block.AddTx(tx) } // if the block number is a multiple of 5, add a bonus uncle to the block @@ -198,17 +207,20 @@ func (tc *testChain) headersByNumber(origin uint64, amount int, skip int, revers } } } + return result } // receipts returns the receipts of the given block hashes. func (tc *testChain) receipts(hashes []common.Hash) [][]*types.Receipt { results := make([][]*types.Receipt, 0, len(hashes)) + for _, hash := range hashes { if receipt, ok := tc.receiptm[hash]; ok { results = append(results, receipt) } } + return results } @@ -216,12 +228,14 @@ func (tc *testChain) receipts(hashes []common.Hash) [][]*types.Receipt { func (tc *testChain) bodies(hashes []common.Hash) ([][]*types.Transaction, [][]*types.Header) { transactions := make([][]*types.Transaction, 0, len(hashes)) uncles := make([][]*types.Header, 0, len(hashes)) + for _, hash := range hashes { if block, ok := tc.blockm[hash]; ok { transactions = append(transactions, block.Transactions()) uncles = append(uncles, block.Uncles()) } } + return transactions, uncles } @@ -231,5 +245,6 @@ func (tc *testChain) hashToNumber(target common.Hash) (uint64, bool) { return uint64(num), true } } + return 0, false } diff --git a/les/downloader/types.go b/les/downloader/types.go index ff70bfa0e3..f7127e1e50 100644 --- a/les/downloader/types.go +++ b/les/downloader/types.go @@ -54,6 +54,7 @@ func (p *bodyPack) Items() int { if len(p.transactions) <= len(p.uncles) { return len(p.transactions) } + return len(p.uncles) } func (p *bodyPack) Stats() string { return fmt.Sprintf("%d:%d", len(p.transactions), len(p.uncles)) } diff --git a/les/enr_entry.go b/les/enr_entry.go index 307313fb10..65207a6979 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -47,10 +47,12 @@ func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { // 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) } @@ -61,12 +63,15 @@ func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { 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/fetcher.go b/les/fetcher.go index 200d58fa98..29ed898c2d 100644 --- a/les/fetcher.go +++ b/les/fetcher.go @@ -86,6 +86,7 @@ func (fp *fetcherPeer) addAnno(anno *announce) { if _, exist := fp.announces[hash]; exist { return } + fp.announces[hash] = anno fp.fifo = append(fp.fifo, hash) @@ -106,21 +107,26 @@ func (fp *fetcherPeer) forwardAnno(td *big.Int) []*announce { cutset int evicted []*announce ) + for ; cutset < len(fp.fifo); cutset++ { anno := fp.announces[fp.fifo[cutset]] if anno == nil { continue // In theory it should never ever happen } + if anno.data.Td.Cmp(td) > 0 { break } + evicted = append(evicted, anno) delete(fp.announces, anno.data.Hash) } + if cutset > 0 { copy(fp.fifo, fp.fifo[cutset:]) fp.fifo = fp.fifo[:len(fp.fifo)-cutset] } + return evicted } @@ -171,6 +177,7 @@ func newLightFetcher(chain *light.LightChain, engine consensus.Engine, peers *se if ulc != nil { checkFreq = 0 } + return chain.InsertHeaderChain(headers, checkFreq) } f := &lightFetcher{ @@ -189,12 +196,14 @@ func newLightFetcher(chain *light.LightChain, engine consensus.Engine, peers *se closeCh: make(chan struct{}), } peers.subscribe(f) + return f } func (f *lightFetcher) start() { f.wg.Add(1) f.fetcher.Start() + go f.mainloop() } @@ -274,6 +283,7 @@ func (f *lightFetcher) mainloop() { ) defer requestTimer.Stop() + sub := f.chain.SubscribeChainHeadEvent(headCh) defer sub.Unsubscribe() @@ -290,6 +300,7 @@ func (f *lightFetcher) mainloop() { agreed []enode.ID trusted bool ) + f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool { if anno := p.announces[hash]; anno != nil && anno.trust && anno.data.Number == number { agreed = append(agreed, id) @@ -298,10 +309,13 @@ func (f *lightFetcher) mainloop() { return false // abort iteration } } + return true }) + return trusted, agreed } + for { select { case anno := <-f.announceCh: @@ -318,14 +332,17 @@ func (f *lightFetcher) mainloop() { if peer.latest != nil && data.Td.Cmp(peer.latest.Td) <= 0 { f.peerset.unregister(peerid.String()) log.Debug("Non-monotonic td", "peer", peerid, "current", data.Td, "previous", peer.latest.Td) + continue } + peer.latest = data // Filter out any stale announce, the local chain is ahead of announce if localTd != nil && data.Td.Cmp(localTd) <= 0 { continue } + peer.addAnno(anno) // If we are not syncing, try to trigger a single retrieval or re-sync @@ -337,10 +354,13 @@ func (f *lightFetcher) mainloop() { // in both cases, so resync is necessary. if data.Number > localHead.Number.Uint64()+syncInterval || data.ReorgDepth > 0 { syncing = true + go f.startSync(peerid) log.Debug("Trigger light sync", "peer", peerid, "local", localHead.Number, "localhash", localHead.Hash(), "remote", data.Number, "remotehash", data.Hash) + continue } + f.fetcher.Notify(peerid.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(peerid), nil) log.Debug("Trigger header retrieval", "peer", peerid, "number", data.Number, "hash", data.Hash) } @@ -352,10 +372,13 @@ func (f *lightFetcher) mainloop() { if trusted && !syncing { if data.Number > localHead.Number.Uint64()+syncInterval || data.ReorgDepth > 0 { syncing = true + go f.startSync(peerid) log.Debug("Trigger trusted light sync", "local", localHead.Number, "localhash", localHead.Hash(), "remote", data.Number, "remotehash", data.Hash) + continue } + p := agreed[rand.Intn(len(agreed))] f.fetcher.Notify(p.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(p), nil) log.Debug("Trigger trusted header retrieval", "number", data.Number, "hash", data.Hash) @@ -376,6 +399,7 @@ func (f *lightFetcher) mainloop() { log.Debug("Request timeout", "peer", request.peerid, "reqid", reqid) } } + f.rescheduleTimer(fetching, requestTimer) case resp := <-f.deliverCh: @@ -390,11 +414,14 @@ func (f *lightFetcher) mainloop() { if len(resp.headers) != 1 { f.peerset.unregister(req.peerid.String()) log.Debug("Deliver more than requested", "peer", req.peerid, "reqid", req.reqid) + continue } + if resp.headers[0].Hash() != req.hash { f.peerset.unregister(req.peerid.String()) log.Debug("Deliver invalid header", "peer", req.peerid, "reqid", req.reqid) + continue } resp.remain <- f.fetcher.FilterHeaders(resp.peerid.String(), resp.headers, time.Now()) @@ -408,10 +435,12 @@ func (f *lightFetcher) mainloop() { if syncing { continue } + reset(ev.Block.Header()) // Clean stale announcements from les-servers. var droplist []enode.ID + f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool { removed := p.forwardAnno(localTd) for _, anno := range removed { @@ -428,12 +457,15 @@ func (f *lightFetcher) mainloop() { } } } + return true }) + for _, id := range droplist { f.peerset.unregister(id.String()) log.Debug("Kicked out peer for invalid announcement") } + if f.newHeadHook != nil { f.newHeadHook(localHead) } @@ -453,12 +485,15 @@ func (f *lightFetcher) mainloop() { if ancestor == nil { ancestor = f.chain.Genesis().Header() } + var untrusted []common.Hash + for head.Number.Cmp(ancestor.Number) > 0 { hash, number := head.Hash(), head.Number.Uint64() if trusted, _ := trustedHeader(hash, number); trusted { break } + untrusted = append(untrusted, hash) head = f.chain.GetHeader(head.ParentHash, number-1) @@ -466,6 +501,7 @@ func (f *lightFetcher) mainloop() { break // all the synced headers will be dropped } } + if len(untrusted) > 0 { for i, j := 0, len(untrusted)-1; i < j; i, j = i+1, j-1 { untrusted[i], untrusted[j] = untrusted[j], untrusted[i] @@ -475,9 +511,11 @@ func (f *lightFetcher) mainloop() { } // Reset local status. reset(f.chain.CurrentHeader()) + if f.newHeadHook != nil { f.newHeadHook(localHead) } + log.Debug("light sync finished", "number", localHead.Number, "hash", localHead.Hash()) case <-f.closeCh: @@ -526,6 +564,7 @@ func (f *lightFetcher) requestHeaderByHash(peerid enode.ID) func(common.Hash) er }, } f.reqDist.queue(req) + return nil } } @@ -540,6 +579,7 @@ func (f *lightFetcher) startSync(id enode.ID) { if peer == nil || peer.onlyAnnounce { return } + f.synchronise(peer) } @@ -551,6 +591,7 @@ func (f *lightFetcher) deliverHeaders(peer *serverPeer, reqid uint64, headers [] case <-f.closeCh: return nil } + return <-remain } @@ -568,5 +609,6 @@ func (f *lightFetcher) rescheduleTimer(requests map[uint64]*request, timer *time earliest = req.sendAt } } + timer.Reset(blockDelayTimeout - time.Since(earliest)) } diff --git a/les/fetcher/block_fetcher.go b/les/fetcher/block_fetcher.go index 085ecb2d66..7f6bac89fa 100644 --- a/les/fetcher/block_fetcher.go +++ b/les/fetcher/block_fetcher.go @@ -143,6 +143,7 @@ func (inject *blockOrHeaderInject) number() uint64 { if inject.header != nil { return inject.header.Number.Uint64() } + return inject.block.NumberU64() } @@ -151,6 +152,7 @@ func (inject *blockOrHeaderInject) hash() common.Hash { if inject.header != nil { return inject.header.Hash() } + return inject.block.Hash() } @@ -338,8 +340,10 @@ func (f *BlockFetcher) loop() { fetchTimer = time.NewTimer(0) completeTimer = time.NewTimer(0) ) + <-fetchTimer.C // clear out the channel <-completeTimer.C + defer fetchTimer.Stop() defer completeTimer.Stop() @@ -352,8 +356,10 @@ func (f *BlockFetcher) loop() { } // Import any queued blocks that could potentially fit height := f.chainHeight() + for !f.queue.Empty() { op := f.queue.PopItem() + hash := op.hash() if f.queueChangeHook != nil { f.queueChangeHook(hash, false) @@ -362,9 +368,11 @@ func (f *BlockFetcher) loop() { number := op.number() if number > height+1 { f.queue.Push(op, -int64(number)) + if f.queueChangeHook != nil { f.queueChangeHook(hash, true) } + break } // Otherwise if fresh and still unknown, try and import @@ -372,6 +380,7 @@ func (f *BlockFetcher) loop() { f.forgetBlock(hash) continue } + if f.light { f.importHeaders(op.origin, op.header) } else { @@ -392,6 +401,7 @@ func (f *BlockFetcher) loop() { if count > hashLimit { log.Debug("Peer exceeded outstanding announces", "peer", notification.origin, "limit", hashLimit) blockAnnounceDOSMeter.Mark(1) + break } // If we have a valid block number, check that it's potentially useful @@ -399,6 +409,7 @@ func (f *BlockFetcher) loop() { if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist { log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist) blockAnnounceDropMeter.Mark(1) + break } } @@ -406,14 +417,18 @@ func (f *BlockFetcher) loop() { if _, ok := f.fetching[notification.hash]; ok { break } + if _, ok := f.completing[notification.hash]; ok { break } + f.announces[notification.origin] = count f.announced[notification.hash] = append(f.announced[notification.hash], notification) + if f.announceChangeHook != nil && len(f.announced[notification.hash]) == 1 { f.announceChangeHook(notification.hash, true) } + if len(f.announced) == 1 { f.rescheduleFetch(fetchTimer) } @@ -427,6 +442,7 @@ func (f *BlockFetcher) loop() { if f.light { continue } + f.enqueue(op.origin, nil, op.block) case hash := <-f.done: @@ -445,9 +461,11 @@ func (f *BlockFetcher) loop() { if f.light { timeout = 0 } + if time.Since(announces[0].time) > timeout { // Pick a random peer to retrieve from, reset all others announce := announces[rand.Intn(len(announces))] + f.forgetHash(hash) // If the block still didn't arrive, queue for fetching @@ -467,6 +485,7 @@ func (f *BlockFetcher) loop() { if f.fetchingHook != nil { f.fetchingHook(hashes) } + for _, hash := range hashes { headerFetchMeter.Mark(1) fetchHeader(hash) // Suboptimal, but protocol doesn't allow batch header retrievals @@ -483,6 +502,7 @@ func (f *BlockFetcher) loop() { for hash, announces := range f.fetched { // Pick a random peer to retrieve from, reset all others announce := announces[rand.Intn(len(announces))] + f.forgetHash(hash) // If the block still didn't arrive, queue for completion @@ -499,7 +519,9 @@ func (f *BlockFetcher) loop() { if f.completingHook != nil { f.completingHook(hashes) } + bodyFetchMeter.Mark(int64(len(hashes))) + go f.completing[hashes[0]].fetchBodies(hashes) } // Schedule the next fetch if blocks are still pending @@ -520,6 +542,7 @@ func (f *BlockFetcher) loop() { // Split the batch of headers into unknown ones (to return to the caller), // known incomplete ones (requiring body retrievals) and completed blocks. unknown, incomplete, complete, lightHeaders := []*types.Header{}, []*blockAnnounce{}, []*types.Block{}, []*blockAnnounce{} + for _, header := range task.headers { hash := header.Hash() @@ -530,6 +553,7 @@ func (f *BlockFetcher) loop() { log.Trace("Invalid block number fetched", "peer", announce.origin, "hash", header.Hash(), "announced", announce.number, "provided", header.Number) f.dropPeer(announce.origin) f.forgetHash(hash) + continue } // Collect all headers only if we are running in light @@ -539,7 +563,9 @@ func (f *BlockFetcher) loop() { announce.header = header lightHeaders = append(lightHeaders, announce) } + f.forgetHash(hash) + continue } // Only keep if not imported by other means @@ -556,6 +582,7 @@ func (f *BlockFetcher) loop() { complete = append(complete, block) f.completing[hash] = announce + continue } // Otherwise add to the list of blocks needing completion @@ -569,6 +596,7 @@ func (f *BlockFetcher) loop() { unknown = append(unknown, header) } } + headerFilterOutMeter.Mark(int64(len(unknown))) select { case filter <- &headerFilterTask{headers: unknown, time: task.time}: @@ -581,6 +609,7 @@ func (f *BlockFetcher) loop() { if _, ok := f.completing[hash]; ok { continue } + f.fetched[hash] = append(f.fetched[hash], announce) if len(f.fetched) == 1 { f.rescheduleComplete(completeTimer) @@ -606,6 +635,7 @@ func (f *BlockFetcher) loop() { return } bodyFilterInMeter.Mark(int64(len(task.transactions))) + blocks := []*types.Block{} // abort early if there's nothing explicitly requested if len(f.completing) > 0 { @@ -616,24 +646,30 @@ func (f *BlockFetcher) loop() { uncleHash common.Hash // calculated lazily and reused txnHash common.Hash // calculated lazily and reused ) + for hash, announce := range f.completing { if f.queued[hash] != nil || announce.origin != task.peer { continue } + if uncleHash == (common.Hash{}) { uncleHash = types.CalcUncleHash(task.uncles[i]) } + if uncleHash != announce.header.UncleHash { continue } + if txnHash == (common.Hash{}) { txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), trie.NewStackTrie(nil)) } + if txnHash != announce.header.TxHash { continue } // Mark the body matched, reassemble if still unknown matched = true + if f.getBlock(hash) == nil { block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i]) block.ReceivedAt = task.time @@ -642,14 +678,17 @@ func (f *BlockFetcher) loop() { f.forgetHash(hash) } } + if matched { task.transactions = append(task.transactions[:i], task.transactions[i+1:]...) task.uncles = append(task.uncles[:i], task.uncles[i+1:]...) i-- + continue } } } + bodyFilterOutMeter.Mark(int64(len(task.transactions))) select { case filter <- task: @@ -685,6 +724,7 @@ func (f *BlockFetcher) rescheduleFetch(fetch *time.Timer) { earliest = announces[0].time } } + fetch.Reset(arriveTimeout - time.Since(earliest)) } @@ -701,6 +741,7 @@ func (f *BlockFetcher) rescheduleComplete(complete *time.Timer) { earliest = announces[0].time } } + complete.Reset(gatherSlack - time.Since(earliest)) } @@ -711,6 +752,7 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B hash common.Hash number uint64 ) + if header != nil { hash, number = header.Hash(), header.Number.Uint64() } else { @@ -722,6 +764,7 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B log.Debug("Discarded delivered header or block, exceeded allowance", "peer", peer, "number", number, "hash", hash, "limit", blockLimit) blockBroadcastDOSMeter.Mark(1) f.forgetHash(hash) + return } // Discard any past or too distant blocks @@ -729,6 +772,7 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B log.Debug("Discarded delivered header or block, too far away", "peer", peer, "number", number, "hash", hash, "distance", dist) blockBroadcastDropMeter.Mark(1) f.forgetHash(hash) + return } // Schedule the block for future importing @@ -739,12 +783,15 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B } else { op.block = block } + f.queues[peer] = count f.queued[hash] = op f.queue.Push(op, -int64(number)) + if f.queueChangeHook != nil { f.queueChangeHook(hash, true) } + log.Debug("Queued delivered header or block", "peer", peer, "number", number, "hash", hash, "queued", f.queue.Size()) } } @@ -768,6 +815,7 @@ func (f *BlockFetcher) importHeaders(peer string, header *types.Header) { if err := f.verifyHeader(header); err != nil && err != consensus.ErrFutureBlock { log.Debug("Propagated header verification failed", "peer", peer, "number", header.Number, "hash", hash, "err", err) f.dropPeer(peer) + return } // Run the actual import and log any issues @@ -790,6 +838,7 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { // Run the import on a new thread log.Debug("Importing propagated block", "peer", peer, "number", block.Number(), "hash", hash) + go func() { defer func() { f.done <- hash }() @@ -804,6 +853,7 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { case nil: // All ok, quickly propagate to our peers blockBroadcastOutTimer.UpdateSince(block.ReceivedAt) + go f.broadcastBlock(block, true) case consensus.ErrFutureBlock: @@ -813,6 +863,7 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { // Something went very wrong, drop the peer log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err) f.dropPeer(peer) + return } // Run the actual import and log any issues @@ -822,6 +873,7 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { } // If import succeeded, broadcast the block blockAnnounceOutTimer.UpdateSince(block.ReceivedAt) + go f.broadcastBlock(block, false) // Invoke the testing hook if needed @@ -842,7 +894,9 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) { delete(f.announces, announce.origin) } } + delete(f.announced, hash) + if f.announceChangeHook != nil { f.announceChangeHook(hash, false) } @@ -853,6 +907,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) { if f.announces[announce.origin] <= 0 { delete(f.announces, announce.origin) } + delete(f.fetching, hash) } @@ -863,6 +918,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) { delete(f.announces, announce.origin) } } + delete(f.fetched, hash) // Remove any pending completions and decrement the DOS counters @@ -871,6 +927,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) { if f.announces[announce.origin] <= 0 { delete(f.announces, announce.origin) } + delete(f.completing, hash) } } @@ -883,6 +940,7 @@ func (f *BlockFetcher) forgetBlock(hash common.Hash) { if f.queues[insert.origin] == 0 { delete(f.queues, insert.origin) } + delete(f.queued, hash) } } diff --git a/les/fetcher/block_fetcher_test.go b/les/fetcher/block_fetcher_test.go index caff7a3b35..4013353932 100644 --- a/les/fetcher/block_fetcher_test.go +++ b/les/fetcher/block_fetcher_test.go @@ -58,10 +58,12 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common // If the block number is multiple of 3, send a bonus transaction to the miner if parent == genesis && i%3 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) if err != nil { panic(err) } + block.AddTx(tx) } // If the block number is a multiple of 5, add a bonus uncle to the block @@ -73,10 +75,12 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common hashes[len(hashes)-1] = parent.Hash() blockm := make(map[common.Hash]*types.Block, n+1) blockm[parent.Hash()] = parent + for i, b := range blocks { hashes[len(hashes)-i-2] = b.Hash() blockm[b.Hash()] = b } + return hashes, blockm } @@ -139,6 +143,7 @@ func (f *fetcherTester) chainHeight() uint64 { if f.fetcher.light { return f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64() } + return f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() } @@ -160,6 +165,7 @@ func (f *fetcherTester) insertHeaders(headers []*types.Header) (int, error) { f.hashes = append(f.hashes, header.Hash()) f.headers[header.Hash()] = header } + return 0, nil } @@ -181,6 +187,7 @@ func (f *fetcherTester) insertChain(blocks types.Blocks) (int, error) { f.hashes = append(f.hashes, block.Hash()) f.blocks[block.Hash()] = block } + return 0, nil } @@ -334,6 +341,7 @@ func testSequentialAnnouncements(t *testing.T, light bool) { // Iteratively announce blocks until all are imported imported := make(chan interface{}) + tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { if light { if header == nil { @@ -383,6 +391,7 @@ func testConcurrentAnnouncements(t *testing.T, light bool) { } // Iteratively announce blocks until all are imported imported := make(chan interface{}) + tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { if light { if header == nil { @@ -408,6 +417,7 @@ func testConcurrentAnnouncements(t *testing.T, light bool) { if int(counter) != targetBlocks { t.Fatalf("retrieval count mismatch: have %v, want %v", counter, targetBlocks) } + verifyChainHeight(t, tester, uint64(len(hashes)-1)) } @@ -428,9 +438,11 @@ func testOverlappingAnnouncements(t *testing.T, light bool) { // Iteratively announce blocks, but overlap them continuously overlap := 16 imported := make(chan interface{}, len(hashes)-1) + for i := 0; i < overlap; i++ { imported <- nil } + tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { if light { if header == nil { @@ -481,8 +493,10 @@ func testPendingDeduplication(t *testing.T, light bool) { time.Sleep(delay) headerFetcher(hash) }() + return nil } + checkNonExist := func() bool { return tester.getBlock(hashes[0]) == nil } @@ -502,6 +516,7 @@ func testPendingDeduplication(t *testing.T, light bool) { if int(counter) != 1 { t.Fatalf("retrieval count mismatch: have %v, want %v", counter, 1) } + verifyChainHeight(t, tester, 1) } @@ -535,6 +550,7 @@ func testRandomArrivalImport(t *testing.T, light bool) { imported <- block } } + for i := len(hashes) - 1; i >= 0; i-- { if i != skip { tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) @@ -634,12 +650,14 @@ func TestDistantPropagationDiscarding(t *testing.T) { // Ensure that a block with a lower number than the threshold is discarded tester.fetcher.Enqueue("lower", blocks[hashes[low]]) time.Sleep(10 * time.Millisecond) + if !tester.fetcher.queue.Empty() { t.Fatalf("fetcher queued stale block") } // Ensure that a block with a higher number than the threshold is discarded tester.fetcher.Enqueue("higher", blocks[hashes[high]]) time.Sleep(10 * time.Millisecond) + if !tester.fetcher.queue.Empty() { t.Fatalf("fetcher queued future block") } @@ -722,6 +740,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { announced <- nil } tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher) + verifyAnnounce := func() { for i := 0; i < 2; i++ { select { @@ -742,6 +761,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { if !dropped { t.Fatalf("peer with invalid numbered announcement not dropped") } + goodHeaderFetcher := tester.makeHeaderFetcher("good", blocks, -gatherSlack) goodBodyFetcher := tester.makeBodyFetcher("good", blocks, 0) // Make sure a good announcement passes without a drop @@ -756,6 +776,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { if dropped { t.Fatalf("peer with valid numbered announcement dropped") } + verifyImportDone(t, imported) } @@ -830,8 +851,10 @@ func TestHashMemoryExhaustionAttack(t *testing.T) { if i < maxQueueDist { tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher) } + tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher) } + if count := atomic.LoadInt32(&announces); count != hashLimit+maxQueueDist { t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist) } @@ -866,6 +889,7 @@ func TestBlockMemoryExhaustionAttack(t *testing.T) { targetBlocks := hashLimit + 2*maxQueueDist hashes, blocks := makeChain(targetBlocks, 0, genesis) attack := make(map[common.Hash]*types.Block) + for i := byte(0); len(attack) < blockLimit+2*maxQueueDist; i++ { hashes, blocks := makeChain(maxQueueDist-1, i, unknownBlock) for _, hash := range hashes[:maxQueueDist-2] { @@ -876,7 +900,9 @@ func TestBlockMemoryExhaustionAttack(t *testing.T) { for _, block := range attack { tester.fetcher.Enqueue("attacker", block) } + time.Sleep(200 * time.Millisecond) + if queued := atomic.LoadInt32(&enqueued); queued != blockLimit { t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit) } @@ -885,6 +911,7 @@ func TestBlockMemoryExhaustionAttack(t *testing.T) { tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-3-i]]) } time.Sleep(100 * time.Millisecond) + if queued := atomic.LoadInt32(&enqueued); queued != blockLimit+maxQueueDist-1 { t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit+maxQueueDist-1) } diff --git a/les/fetcher_test.go b/les/fetcher_test.go index d5a50eeef0..1035894426 100644 --- a/les/fetcher_test.go +++ b/les/fetcher_test.go @@ -73,6 +73,7 @@ func testSequentialAnnouncements(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + s, c, teardown := newClientServerEnv(t, netconfig) defer teardown() @@ -82,10 +83,12 @@ func testSequentialAnnouncements(t *testing.T, protocol int) { if err != nil { t.Fatalf("Failed to create peer pair %v", err) } + importCh := make(chan interface{}) c.handler.fetcher.newHeadHook = func(header *types.Header) { importCh <- header } + for i := uint64(1); i <= s.backend.Blockchain().CurrentHeader().Number.Uint64(); i++ { header := s.backend.Blockchain().GetHeaderByNumber(i) hash, number := header.Hash(), header.Number.Uint64() @@ -95,6 +98,7 @@ func testSequentialAnnouncements(t *testing.T, protocol int) { if p1.cpeer.announceType == announceTypeSigned { announce.sign(s.handler.server.privateKey) } + p1.cpeer.sendAnnounce(announce) verifyImportEvent(t, importCh, true) } @@ -111,6 +115,7 @@ func testGappedAnnouncements(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + s, c, teardown := newClientServerEnv(t, netconfig) defer teardown() @@ -120,6 +125,7 @@ func testGappedAnnouncements(t *testing.T, protocol int) { if err != nil { t.Fatalf("Failed to create peer pair %v", err) } + done := make(chan *types.Header, 1) c.handler.fetcher.newHeadHook = func(header *types.Header) { done <- header } @@ -133,6 +139,7 @@ func testGappedAnnouncements(t *testing.T, protocol int) { if peer.cpeer.announceType == announceTypeSigned { announce.sign(s.handler.server.privateKey) } + peer.cpeer.sendAnnounce(announce) <-done // Wait syncing @@ -186,12 +193,14 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { ids = append(ids, n.String()) } } + netconfig := testnetConfig{ protocol: protocol, nopruning: true, ulcServers: ids, ulcFraction: 60, } + _, c, teardown := newClientServerEnv(t, netconfig) defer teardown() defer func() { @@ -217,8 +226,10 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { if err != nil { t.Fatalf("connect server and client failed, err %s", err) } + cpeers = append(cpeers, cp) } + newHead := make(chan *types.Header, 1) c.handler.fetcher.newHeadHook = func(header *types.Header) { newHead <- header } @@ -232,15 +243,19 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { // Sign the announcement if necessary. announce := announceData{hash, number, td, 0, nil} p := cpeers[j] + if p.announceType == announceTypeSigned { announce.sign(servers[j].handler.server.privateKey) } + p.sendAnnounce(announce) } } + if callback != nil { callback() } + verifyChainHeight(t, c.handler.fetcher, expected) } check([]uint64{1}, 1, func() { <-newHead }) // Sequential announcements @@ -258,6 +273,7 @@ func testInvalidAnnounces(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + s, c, teardown := newClientServerEnv(t, netconfig) defer teardown() @@ -267,6 +283,7 @@ func testInvalidAnnounces(t *testing.T, protocol int) { if err != nil { t.Fatalf("Failed to create peer pair %v", err) } + done := make(chan *types.Header, 1) c.handler.fetcher.newHeadHook = func(header *types.Header) { done <- header } @@ -280,6 +297,7 @@ func testInvalidAnnounces(t *testing.T, protocol int) { if peer.cpeer.announceType == announceTypeSigned { announce.sign(s.handler.server.privateKey) } + peer.cpeer.sendAnnounce(announce) <-done // Wait syncing diff --git a/les/flowcontrol/control.go b/les/flowcontrol/control.go index 76a241fa5a..3b2bedb064 100644 --- a/les/flowcontrol/control.go +++ b/les/flowcontrol/control.go @@ -82,7 +82,9 @@ func NewClientNode(cm *ClientManager, params ServerParams) *ClientNode { if keepLogs > 0 { node.log = newLogger(keepLogs) } + cm.connect(node) + return node } @@ -103,13 +105,16 @@ func (node *ClientNode) BufferStatus() (uint64, uint64) { if !node.connected { return 0, 0 } + now := node.cm.clock.Now() node.update(now) node.cm.updateBuffer(node, 0, now) + bv := node.bufValue if bv < 0 { bv = 0 } + return uint64(bv), node.params.BufLimit } @@ -154,13 +159,16 @@ func (node *ClientNode) recalcBV(now mclock.AbsTime) { if now < node.lastTime { dt = 0 } + node.bufValue += int64(node.params.MinRecharge * dt / uint64(fcTimeConst)) if node.bufValue > int64(node.params.BufLimit) { node.bufValue = int64(node.params.BufLimit) } + if node.log != nil { node.log.add(now, fmt.Sprintf("updated bv=%d MRR=%d BufLimit=%d", node.bufValue, node.params.MinRecharge, node.params.BufLimit)) } + node.lastTime = now } @@ -171,6 +179,7 @@ func (node *ClientNode) UpdateParams(params ServerParams) { now := node.cm.clock.Now() node.update(now) + if params.MinRecharge >= node.params.MinRecharge { node.updateSchedule = nil node.updateParams(params, now) @@ -179,9 +188,11 @@ func (node *ClientNode) UpdateParams(params ServerParams) { if params.MinRecharge >= s.params.MinRecharge { s.params = params node.updateSchedule = node.updateSchedule[:i+1] + return } } + node.updateSchedule = append(node.updateSchedule, scheduledUpdate{time: now.Add(DecParamDelay), params: params}) } } @@ -194,6 +205,7 @@ func (node *ClientNode) updateParams(params ServerParams, now mclock.AbsTime) { } else if node.bufValue > int64(params.BufLimit) { node.bufValue = int64(params.BufLimit) } + node.cm.updateParams(node, params, now) } @@ -206,19 +218,25 @@ func (node *ClientNode) AcceptRequest(reqID, index, maxCost uint64) (accepted bo now := node.cm.clock.Now() node.update(now) + if int64(maxCost) > node.bufValue { if node.log != nil { node.log.add(now, fmt.Sprintf("rejected reqID=%d bv=%d maxCost=%d", reqID, node.bufValue, maxCost)) node.log.dump(now) } + return false, maxCost - uint64(node.bufValue), 0 } + node.bufValue -= int64(maxCost) node.sumCost += maxCost + if node.log != nil { node.log.add(now, fmt.Sprintf("accepted reqID=%d bv=%d maxCost=%d sumCost=%d", reqID, node.bufValue, maxCost, node.sumCost)) } + node.accepted[index] = node.sumCost + return true, 0, node.cm.accepted(node, maxCost, now) } @@ -230,14 +248,18 @@ func (node *ClientNode) RequestProcessed(reqID, index, maxCost, realCost uint64) now := node.cm.clock.Now() node.update(now) node.cm.processed(node, maxCost, realCost, now) + bv := node.bufValue + int64(node.sumCost-node.accepted[index]) if node.log != nil { node.log.add(now, fmt.Sprintf("processed reqID=%d bv=%d maxCost=%d realCost=%d sumCost=%d oldSumCost=%d reportedBV=%d", reqID, node.bufValue, maxCost, realCost, node.sumCost, node.accepted[index], bv)) } + delete(node.accepted, index) + if bv < 0 { return 0 } + return uint64(bv) } @@ -268,6 +290,7 @@ func NewServerNode(params ServerParams, clock mclock.Clock) *ServerNode { if keepLogs > 0 { node.log = newLogger(keepLogs) } + return node } @@ -277,6 +300,7 @@ func (node *ServerNode) UpdateParams(params ServerParams) { defer node.lock.Unlock() node.recalcBLE(mclock.Now()) + if params.BufLimit > node.params.BufLimit { node.bufEstimate += params.BufLimit - node.params.BufLimit } else { @@ -284,6 +308,7 @@ func (node *ServerNode) UpdateParams(params ServerParams) { node.bufEstimate = params.BufLimit } } + node.params = params } @@ -293,14 +318,17 @@ func (node *ServerNode) recalcBLE(now mclock.AbsTime) { if now < node.lastTime { return } + if node.bufRecharge { dt := uint64(now - node.lastTime) + node.bufEstimate += node.params.MinRecharge * dt / uint64(fcTimeConst) if node.bufEstimate >= node.params.BufLimit { node.bufEstimate = node.params.BufLimit node.bufRecharge = false } } + node.lastTime = now if node.log != nil { node.log.add(now, fmt.Sprintf("updated bufEst=%d MRR=%d BufLimit=%d", node.bufEstimate, node.params.MinRecharge, node.params.BufLimit)) @@ -320,23 +348,29 @@ func (node *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) { 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) if maxCost > node.params.BufLimit { maxCost = node.params.BufLimit } + if node.bufEstimate >= maxCost { relBuf := float64(node.bufEstimate-maxCost) / float64(node.params.BufLimit) if node.log != nil { node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d true relBuf=%f", node.bufEstimate, maxCost, relBuf)) } + return 0, relBuf } + timeLeft := time.Duration((maxCost - node.bufEstimate) * uint64(fcTimeConst) / node.params.MinRecharge) if node.log != nil { node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d false timeLeft=%v", node.bufEstimate, maxCost, timeLeft)) } + return timeLeft, 0 } @@ -356,10 +390,13 @@ func (node *ServerNode) QueuedRequest(reqID, maxCost uint64) { node.bufEstimate -= maxCost } else { log.Error("Queued request with insufficient buffer estimate") + node.bufEstimate = 0 } + node.sumCost += maxCost node.pending[reqID] = node.sumCost + if node.log != nil { node.log.add(now, fmt.Sprintf("queued reqID=%d bufEst=%d maxCost=%d sumCost=%d", reqID, node.bufEstimate, maxCost, node.sumCost)) } @@ -373,19 +410,24 @@ func (node *ServerNode) ReceivedReply(reqID, bv uint64) { now := node.clock.Now() node.recalcBLE(now) + if bv > node.params.BufLimit { bv = node.params.BufLimit } + sc, ok := node.pending[reqID] if !ok { return } + delete(node.pending, reqID) cc := node.sumCost - sc newEstimate := uint64(0) + if bv > cc { newEstimate = bv - cc } + if newEstimate > node.bufEstimate { // Note: we never reduce the buffer estimate based on the reported value because // this can only happen because of the delayed delivery of the latest reply. @@ -395,6 +437,7 @@ func (node *ServerNode) ReceivedReply(reqID, bv uint64) { node.bufRecharge = node.bufEstimate < node.params.BufLimit node.lastTime = now + if node.log != nil { node.log.add(now, fmt.Sprintf("received reqID=%d bufEst=%d reportedBv=%d sumCost=%d oldSumCost=%d", reqID, node.bufEstimate, bv, node.sumCost, sc)) } @@ -409,14 +452,18 @@ func (node *ServerNode) ResumeFreeze(bv uint64) { for reqID := range node.pending { delete(node.pending, reqID) } + now := node.clock.Now() node.recalcBLE(now) + if bv > node.params.BufLimit { bv = node.params.BufLimit } + node.bufEstimate = bv node.bufRecharge = node.bufEstimate < node.params.BufLimit node.lastTime = now + if node.log != nil { node.log.add(now, fmt.Sprintf("unfreeze bv=%d sumCost=%d", bv, node.sumCost)) } diff --git a/les/flowcontrol/logger.go b/les/flowcontrol/logger.go index 428d7fbf22..de75c10474 100644 --- a/les/flowcontrol/logger.go +++ b/les/flowcontrol/logger.go @@ -50,8 +50,10 @@ func (l *logger) add(now mclock.AbsTime, event string) { keepAfter := now - mclock.AbsTime(l.keep) for l.delPtr < l.writePtr && l.events[l.delPtr].time <= keepAfter { delete(l.events, l.delPtr) + l.delPtr++ } + l.events[l.writePtr] = logEvent{now, event} l.writePtr++ } diff --git a/les/flowcontrol/manager.go b/les/flowcontrol/manager.go index b7cc9bd903..250f389a01 100644 --- a/les/flowcontrol/manager.go +++ b/les/flowcontrol/manager.go @@ -115,6 +115,7 @@ func NewClientManager(curve PieceWiseLinear, clock mclock.Clock) *ClientManager if curve != nil { cm.SetRechargeCurve(curve) } + go func() { // regularly recalculate and update total capacity for { @@ -129,6 +130,7 @@ func NewClientManager(curve PieceWiseLinear, clock mclock.Clock) *ClientManager } } }() + return cm } @@ -146,6 +148,7 @@ func (cm *ClientManager) SetRechargeCurve(curve PieceWiseLinear) { now := cm.clock.Now() cm.updateRecharge(now) + cm.curve = curve if len(curve) > 0 { cm.totalRecharge = curve[len(curve)-1].Y @@ -162,10 +165,13 @@ func (cm *ClientManager) SetCapacityLimits(min, max, raiseThreshold uint64) { if min < 1 { min = 1 } + cm.minLogTotalCap = math.Log(float64(min)) + if max < 1 { max = 1 } + cm.maxLogTotalCap = math.Log(float64(max)) cm.logTotalCap = cm.maxLogTotalCap cm.capacityRaiseThreshold = raiseThreshold @@ -180,9 +186,11 @@ func (cm *ClientManager) connect(node *ClientNode) { now := cm.clock.Now() cm.updateRecharge(now) + node.corrBufValue = int64(node.params.BufLimit) node.rcLastIntValue = cm.rcLastIntValue node.queueIndex = -1 + cm.updateTotalCapacity(now, true) cm.totalConnected += node.params.MinRecharge cm.updateRaiseLimit() @@ -210,6 +218,7 @@ func (cm *ClientManager) accepted(node *ClientNode, maxCost uint64, now mclock.A cm.updateNodeRc(node, -int64(maxCost), &node.params, now) rcTime := (node.params.BufLimit - uint64(node.corrBufValue)) * FixedPointMultiplier / node.params.MinRecharge + return -int64(now) - int64(rcTime) } @@ -221,6 +230,7 @@ func (cm *ClientManager) processed(node *ClientNode, maxCost, realCost uint64, n if realCost > maxCost { realCost = maxCost } + cm.updateBuffer(node, int64(maxCost-realCost), now) } @@ -231,10 +241,12 @@ func (cm *ClientManager) updateBuffer(node *ClientNode, add int64, now mclock.Ab defer cm.lock.Unlock() cm.updateNodeRc(node, add, &node.params, now) + if node.corrBufValue > node.bufValue { if node.log != nil { node.log.add(now, fmt.Sprintf("corrected bv=%d oldBv=%d", node.corrBufValue, node.bufValue)) } + node.bufValue = node.corrBufValue } } @@ -258,14 +270,18 @@ func (cm *ClientManager) updateRaiseLimit() { cm.logTotalCapRaiseLimit = 0 return } + limit := float64(cm.totalConnected + cm.capacityRaiseThreshold) limit2 := float64(cm.totalConnected) * capacityRaiseThresholdRatio + if limit2 > limit { limit = limit2 } + if limit < 1 { limit = 1 } + cm.logTotalCapRaiseLimit = math.Log(limit) } @@ -281,12 +297,15 @@ func (cm *ClientManager) updateRecharge(now mclock.AbsTime) { if sumRecharge > cm.totalRecharge { sumRecharge = cm.totalRecharge } + bonusRatio := float64(1) v := cm.curve.ValueAt(sumRecharge) s := float64(sumRecharge) + if v > s && s > 0 { bonusRatio = v / s } + dt := now - lastUpdate // fetch the client that finishes first rcqNode := cm.rcQueue.PopItem() // if sumRecharge > 0 then the queue cannot be empty @@ -297,14 +316,17 @@ func (cm *ClientManager) updateRecharge(now mclock.AbsTime) { // to current bonusRatio and return cm.addToQueue(rcqNode) cm.rcLastIntValue += int64(bonusRatio * float64(dt)) + return } + lastUpdate += dtNext // finished recharging, update corrBufValue and sumRecharge if necessary and do next step if rcqNode.corrBufValue < int64(rcqNode.params.BufLimit) { rcqNode.corrBufValue = int64(rcqNode.params.BufLimit) cm.sumRecharge -= rcqNode.params.MinRecharge } + cm.rcLastIntValue = rcqNode.rcFullIntValue } } @@ -314,12 +336,15 @@ func (cm *ClientManager) addToQueue(node *ClientNode) { cm.priorityOffset += 0x4000000000000000 // recreate priority queue with new offset to avoid overflow; should happen very rarely newRcQueue := prque.New[int64, *ClientNode](func(a *ClientNode, i int) { a.queueIndex = i }) + for cm.rcQueue.Size() > 0 { n := cm.rcQueue.PopItem() newRcQueue.Push(n, cm.priorityOffset-n.rcFullIntValue) } + cm.rcQueue = newRcQueue } + cm.rcQueue.Push(node, cm.priorityOffset-node.rcFullIntValue) } @@ -327,36 +352,47 @@ func (cm *ClientManager) addToQueue(node *ClientNode) { // It also adds or removes the rcQueue entry and updates ServerParams and sumRecharge if necessary. func (cm *ClientManager) updateNodeRc(node *ClientNode, bvc int64, params *ServerParams, now mclock.AbsTime) { cm.updateRecharge(now) + wasFull := true if node.corrBufValue != int64(node.params.BufLimit) { wasFull = false + node.corrBufValue += (cm.rcLastIntValue - node.rcLastIntValue) * int64(node.params.MinRecharge) / FixedPointMultiplier if node.corrBufValue > int64(node.params.BufLimit) { node.corrBufValue = int64(node.params.BufLimit) } + node.rcLastIntValue = cm.rcLastIntValue } + node.corrBufValue += bvc diff := int64(params.BufLimit - node.params.BufLimit) + if diff > 0 { node.corrBufValue += diff } + isFull := false + if node.corrBufValue >= int64(params.BufLimit) { node.corrBufValue = int64(params.BufLimit) isFull = true } + if !wasFull { cm.sumRecharge -= node.params.MinRecharge } + if params != &node.params { node.params = *params } + if !isFull { cm.sumRecharge += node.params.MinRecharge if node.queueIndex != -1 { cm.rcQueue.Remove(node.queueIndex) } + node.rcLastIntValue = cm.rcLastIntValue node.rcFullIntValue = cm.rcLastIntValue + (int64(node.params.BufLimit)-node.corrBufValue)*FixedPointMultiplier/int64(node.params.MinRecharge) cm.addToQueue(node) @@ -372,12 +408,15 @@ func (cm *ClientManager) reduceTotalCapacity(frozenCap uint64) { if frozenCap < cm.totalConnected { ratio = float64(frozenCap) / float64(cm.totalConnected) } + now := cm.clock.Now() cm.updateTotalCapacity(now, false) + cm.logTotalCap -= capacityDropFactor * ratio if cm.logTotalCap < cm.minLogTotalCap { cm.logTotalCap = cm.minLogTotalCap } + cm.updateTotalCapacity(now, true) } @@ -397,9 +436,11 @@ func (cm *ClientManager) updateTotalCapacity(now mclock.AbsTime, refresh bool) { cm.logTotalCap = cm.logTotalCapRaiseLimit } } + if cm.logTotalCap > cm.maxLogTotalCap { cm.logTotalCap = cm.maxLogTotalCap } + if refresh { cm.refreshCapacity() } @@ -412,6 +453,7 @@ func (cm *ClientManager) refreshCapacity() { if totalCapacity >= cm.totalCapacity*0.999 && totalCapacity <= cm.totalCapacity*1.001 { return } + cm.totalCapacity = totalCapacity if cm.totalCapacityCh != nil { select { @@ -428,6 +470,7 @@ func (cm *ClientManager) SubscribeTotalCapacity(ch chan uint64) uint64 { defer cm.lock.Unlock() cm.totalCapacityCh = ch + return uint64(cm.totalCapacity) } @@ -437,10 +480,12 @@ type PieceWiseLinear []struct{ X, Y uint64 } // ValueAt returns the curve's value at a given point func (pwl PieceWiseLinear) ValueAt(x uint64) float64 { l := 0 + h := len(pwl) if h == 0 { return 0 } + for h != l { m := (l + h) / 2 if x > pwl[m].X { @@ -449,17 +494,21 @@ func (pwl PieceWiseLinear) ValueAt(x uint64) float64 { h = m } } + if l == 0 { return float64(pwl[0].Y) } + l-- if h == len(pwl) { return float64(pwl[l].Y) } + dx := pwl[h].X - pwl[l].X if dx < 1 { return float64(pwl[l].Y) } + return float64(pwl[l].Y) + float64(pwl[h].Y-pwl[l].Y)*float64(x-pwl[l].X)/float64(dx) } @@ -470,7 +519,9 @@ func (pwl PieceWiseLinear) Valid() bool { if i.X < lastX { return false } + lastX = i.X } + return true } diff --git a/les/flowcontrol/manager_test.go b/les/flowcontrol/manager_test.go index 059bbbec3d..9b320872ee 100644 --- a/les/flowcontrol/manager_test.go +++ b/les/flowcontrol/manager_test.go @@ -60,20 +60,25 @@ func testConstantTotalCapacity(t *testing.T, nodeCount, maxCapacityNodes, random clock := &mclock.Simulated{} nodes := make([]*testNode, nodeCount) + var totalCapacity uint64 + for i := range nodes { nodes[i] = &testNode{capacity: uint64(50000 + rand.Intn(100000))} totalCapacity += nodes[i].capacity } + m := NewClientManager(PieceWiseLinear{{0, totalCapacity}}, clock) if priorityOverflow { // provoke a situation where rcLastUpdate overflow needs to be handled m.rcLastIntValue = math.MaxInt64 - 10000000000 } + for _, n := range nodes { n.bufLimit = n.capacity * 6000 n.node = NewClientNode(m, ServerParams{BufLimit: n.bufLimit, MinRecharge: n.capacity}) } + maxNodes := make([]int, maxCapacityNodes) for i := range maxNodes { // we don't care if some indexes are selected multiple times @@ -82,18 +87,21 @@ func testConstantTotalCapacity(t *testing.T, nodeCount, maxCapacityNodes, random } var sendCount int + for i := 0; i < testLength; i++ { now := clock.Now() for _, idx := range maxNodes { for nodes[idx].send(t, now) { } } + if rand.Intn(testLength) < maxCapacityNodes*3 { maxNodes[rand.Intn(maxCapacityNodes)] = rand.Intn(nodeCount) } sendCount += randomSend failCount := randomSend * 10 + for sendCount > 0 && failCount > 0 { if nodes[rand.Intn(nodeCount)].send(t, now) { sendCount-- @@ -108,6 +116,7 @@ func testConstantTotalCapacity(t *testing.T, nodeCount, maxCapacityNodes, random for _, n := range nodes { totalCost += n.totalCost } + ratio := float64(totalCost) / float64(totalCapacity) / testLength if ratio < 0.98 || ratio > 1.02 { t.Errorf("totalCost/totalCapacity/testLength ratio incorrect (expected: 1, got: %f)", ratio) @@ -118,15 +127,20 @@ func (n *testNode) send(t *testing.T, now mclock.AbsTime) bool { if now < n.waitUntil { return false } + n.index++ if ok, _, _ := n.node.AcceptRequest(0, n.index, testMaxCost); !ok { t.Fatalf("Rejected request after expected waiting time has passed") } + rcost := uint64(rand.Int63n(testMaxCost)) + bv := n.node.RequestProcessed(0, n.index, testMaxCost, rcost) if bv < testMaxCost { n.waitUntil = now + mclock.AbsTime((testMaxCost-bv)*1001000/n.capacity) } + n.totalCost += rcost + return true } diff --git a/les/handler_test.go b/les/handler_test.go index aaf257376d..4aa880e734 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -44,6 +44,7 @@ func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{} ReqID, BV uint64 Data interface{} } + return p2p.ExpectMsg(r, msgcode, resp{reqID, bv, data}) } @@ -58,11 +59,13 @@ func testGetBlockHeaders(t *testing.T, protocol int) { 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 @@ -168,6 +171,7 @@ func testGetBlockHeaders(t *testing.T, protocol int) { } // Run each of the tests and verify the results against the chain var reqID uint64 + for i, tt := range tests { // Collect the headers to expect in the response var headers []*types.Header @@ -178,6 +182,7 @@ func testGetBlockHeaders(t *testing.T, protocol int) { reqID++ 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) } @@ -195,6 +200,7 @@ func testGetBlockBodies(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -232,10 +238,13 @@ func testGetBlockBodies(t *testing.T, protocol int) { } // Run each of the tests and verify the results against the chain var reqID uint64 + for i, tt := range tests { // Collect the hashes to request, and the response to expect var hashes []common.Hash + seen := make(map[int64]bool) + var bodies []*types.Body for j := 0; j < tt.random; j++ { @@ -246,24 +255,30 @@ func testGetBlockBodies(t *testing.T, protocol int) { block := bc.GetBlockByNumber(uint64(num)) hashes = append(hashes, block.Hash()) + if len(bodies) < tt.expected { bodies = append(bodies, &types.Body{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 := bc.GetBlockByHash(hash) bodies = append(bodies, &types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()}) } } + reqID++ // Send the hash request and verify the response 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) } @@ -282,6 +297,7 @@ func testGetCode(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -291,6 +307,7 @@ func testGetCode(t *testing.T, protocol int) { bc := server.handler.blockchain var codereqs []*CodeReq + var codes [][]byte for i := uint64(0); i <= bc.CurrentBlock().Number.Uint64(); i++ { @@ -300,12 +317,14 @@ func testGetCode(t *testing.T, protocol int) { AccKey: crypto.Keccak256(testContractAddr[:]), } codereqs = append(codereqs, req) + if i >= testContractDeployed { codes = append(codes, testContractCodeDeployed) } } sendRequest(rawPeer.app, GetCodeMsg, 42, codereqs) + if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, codes); err != nil { t.Errorf("codes mismatch: %v", err) } @@ -322,6 +341,7 @@ func testGetStaleCode(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -336,6 +356,7 @@ func testGetStaleCode(t *testing.T, protocol int) { AccKey: crypto.Keccak256(testContractAddr[:]), } sendRequest(rawPeer.app, GetCodeMsg, 42, []*CodeReq{req}) + if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, expected); err != nil { t.Errorf("codes mismatch: %v", err) } @@ -357,6 +378,7 @@ func testGetReceipt(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -367,6 +389,7 @@ func testGetReceipt(t *testing.T, protocol int) { // Collect the hashes to request, and the response to expect var receipts []types.Receipts + var hashes []common.Hash for i := uint64(0); i <= bc.CurrentBlock().Number.Uint64(); i++ { @@ -377,6 +400,7 @@ func testGetReceipt(t *testing.T, protocol int) { } // Send the hash request and verify the response sendRequest(rawPeer.app, GetReceiptsMsg, 42, hashes) + if err := expectResponse(rawPeer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil { t.Errorf("receipts mismatch: %v", err) } @@ -394,6 +418,7 @@ func testGetProofs(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -403,6 +428,7 @@ func testGetProofs(t *testing.T, protocol int) { bc := server.handler.blockchain var proofreqs []ProofReq + proofsV2 := light.NewNodeSet() accounts := []common.Address{bankAddr, userAddr1, userAddr2, signerAddr, {}} @@ -417,11 +443,13 @@ func testGetProofs(t *testing.T, protocol int) { Key: crypto.Keccak256(acc[:]), } proofreqs = append(proofreqs, req) + trie.Prove(crypto.Keccak256(acc[:]), 0, proofsV2) } } // Send the proof request and verify the response sendRequest(rawPeer.app, GetProofsV2Msg, 42, proofreqs) + if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil { t.Errorf("proofs mismatch: %v", err) } @@ -438,6 +466,7 @@ func testGetStaleProof(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -451,6 +480,7 @@ func testGetStaleProof(t *testing.T, protocol int) { header = bc.GetHeaderByNumber(number) account = crypto.Keccak256(userAddr1.Bytes()) ) + req := &ProofReq{ BHash: header.Hash(), Key: account, @@ -458,12 +488,14 @@ func testGetStaleProof(t *testing.T, protocol int) { sendRequest(rawPeer.app, GetProofsV2Msg, 42, []*ProofReq{req}) var expected []rlp.RawValue + if wantOK { proofsV2 := light.NewNodeSet() t, _ := trie.New(trie.StateTrieID(header.Root), trie.NewDatabase(server.db)) t.Prove(account, 0, proofsV2) expected = proofsV2.NodeList() } + if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil { t.Errorf("codes mismatch: %v", err) } @@ -497,6 +529,7 @@ func testGetCHTProofs(t *testing.T, protocol int) { nopruning: true, } ) + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -527,6 +560,7 @@ func testGetCHTProofs(t *testing.T, protocol int) { }} // Send the proof request and verify the response sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requestsV2) + if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil { t.Errorf("proofs mismatch: %v", err) } @@ -556,6 +590,7 @@ func testGetBloombitsProofs(t *testing.T, protocol int) { nopruning: true, } ) + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -578,6 +613,7 @@ func testGetBloombitsProofs(t *testing.T, protocol int) { TrieIdx: 0, Key: key, }} + var proofs HelperTrieResps root := light.GetBloomTrieRoot(server.db, 0, bc.GetHeaderByNumber(config.BloomTrieSize-1).Hash()) @@ -586,6 +622,7 @@ func testGetBloombitsProofs(t *testing.T, protocol int) { // Send the proof request and verify the response 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) } @@ -601,6 +638,7 @@ func testTransactionStatus(t *testing.T, protocol int) { protocol: protocol, nopruning: true, } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -620,6 +658,7 @@ func testTransactionStatus(t *testing.T, protocol int) { } else { sendRequest(rawPeer.app, GetTxStatusMsg, reqID, []common.Hash{tx.Hash()}) } + if err := expectResponse(rawPeer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil { t.Error("transaction status mismatch", err) } @@ -656,8 +695,10 @@ func testTransactionStatus(t *testing.T, protocol int) { if pending, _ := server.handler.txpool.Stats(); pending == 1 { break } + time.Sleep(100 * time.Millisecond) } + if pending, _ := server.handler.txpool.Stats(); pending != 1 { t.Fatalf("pending count mismatch: have %d, want 1", pending) } @@ -682,8 +723,10 @@ func testTransactionStatus(t *testing.T, protocol int) { if pending, _ := server.handler.txpool.Stats(); pending == 3 { break } + time.Sleep(100 * time.Millisecond) } + if pending, _ := server.handler.txpool.Stats(); pending != 3 { t.Fatalf("pending count mismatch: have %d, want 3", pending) } @@ -705,6 +748,7 @@ func testStopResume(t *testing.T, protocol int) { simClock: true, nopruning: true, } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -719,15 +763,18 @@ func testStopResume(t *testing.T, protocol int) { expBuf = testBufLimit testCost = testBufLimit / 10 ) + header := server.handler.blockchain.CurrentHeader() req := func() { reqID++ 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(rawPeer.app, BlockHeadersMsg, reqID, expBuf, []*types.Header{header}); err != nil { t.Errorf("expected response and failed: %v", err) @@ -737,8 +784,10 @@ func testStopResume(t *testing.T, protocol int) { c := i for c > 0 { req() + c-- } + if err := p2p.ExpectMsg(rawPeer.app, StopMsg, nil); err != nil { t.Errorf("expected StopMsg and failed: %v", err) } diff --git a/les/metrics.go b/les/metrics.go index 07d3133c95..df8923e29d 100644 --- a/les/metrics.go +++ b/les/metrics.go @@ -123,6 +123,7 @@ func newMeteredMsgWriter(rw p2p.MsgReadWriter, version int) p2p.MsgReadWriter { if !metrics.Enabled { return rw } + return &meteredMsgReadWriter{MsgReadWriter: rw, version: version} } diff --git a/les/odr.go b/les/odr.go index 558cf4f8d0..c9c2396649 100644 --- a/les/odr.go +++ b/les/odr.go @@ -112,9 +112,11 @@ 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] } @@ -140,13 +142,17 @@ func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequ 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 } @@ -155,6 +161,7 @@ func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequ 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} @@ -170,6 +177,7 @@ func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequ }, } ) + 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 } @@ -184,15 +192,19 @@ func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequ if status.Status == txpool.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 } @@ -227,12 +239,15 @@ func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err erro if err != nil { return } + requestRTT.Update(time.Duration(mclock.Now() - 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 { return err } + req.StoreResult(odr.db) + return nil } diff --git a/les/odr_requests.go b/les/odr_requests.go index d8b094b727..d8ca5da842 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -103,22 +103,27 @@ func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error { if msg.MsgType != MsgBlockBodies { return errInvalidMessageType } + bodies := msg.Obj.([]*types.Body) if len(bodies) != 1 { return errInvalidEntryCount } + body := bodies[0] // Retrieve our stored header and validate block content against it if r.Header == nil { r.Header = rawdb.ReadHeader(db, r.Hash, r.Number) } + if r.Header == nil { return errHeaderUnavailable } + if r.Header.TxHash != types.DeriveSha(types.Transactions(body.Transactions), trie.NewStackTrie(nil)) { return errTxHashMismatch } + if r.Header.UncleHash != types.CalcUncleHash(body.Uncles) { return errUncleHashMismatch } @@ -127,7 +132,9 @@ func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error { if err != nil { return err } + r.Rlp = data + return nil } @@ -161,24 +168,29 @@ func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error { if msg.MsgType != MsgReceipts { return errInvalidMessageType } + receipts := msg.Obj.([]types.Receipts) if len(receipts) != 1 { return errInvalidEntryCount } + receipt := receipts[0] // Retrieve our stored header and validate receipt content against it if r.Header == nil { r.Header = rawdb.ReadHeader(db, r.Hash, r.Number) } + if r.Header == nil { return errHeaderUnavailable } + if r.Header.ReceiptHash != types.DeriveSha(receipt, trie.NewStackTrie(nil)) { return errReceiptHashMismatch } // Validations passed, store and return r.Receipts = receipt + return nil } @@ -210,6 +222,7 @@ func (r *TrieRequest) Request(reqID uint64, peer *serverPeer) error { AccKey: r.Id.AccKey, Key: r.Key, } + return peer.requestProofs(reqID, []ProofReq{req}) } @@ -222,9 +235,11 @@ func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error { if msg.MsgType != MsgProofsV2 { return errInvalidMessageType } + proofs := msg.Obj.(light.NodeList) // Verify the proof and store if checks out nodeSet := proofs.NodeSet() + reads := &readTraceDB{db: nodeSet} if _, err := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil { return fmt.Errorf("merkle proof verification failed: %v", err) @@ -233,7 +248,9 @@ func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error { if len(reads.reads) != nodeSet.KeyCount() { return errUselessNodes } + r.Proof = nodeSet + return nil } @@ -263,6 +280,7 @@ func (r *CodeRequest) Request(reqID uint64, peer *serverPeer) error { BHash: r.Id.BlockHash, AccKey: r.Id.AccKey, } + return peer.requestCode(reqID, []CodeReq{req}) } @@ -276,17 +294,21 @@ func (r *CodeRequest) Validate(db ethdb.Database, msg *Msg) error { if msg.MsgType != MsgCode { return errInvalidMessageType } + reply := msg.Obj.([][]byte) if len(reply) != 1 { return errInvalidEntryCount } + data := reply[0] // Verify the data and store if checks out if hash := crypto.Keccak256Hash(data); r.Hash != hash { return errDataHashMismatch } + r.Data = data + return nil } @@ -332,7 +354,9 @@ func (r *ChtRequest) CanSend(peer *serverPeer) bool { // Request sends an ODR request to the LES network (implementation of LesOdrRequest) func (r *ChtRequest) Request(reqID uint64, peer *serverPeer) error { peer.Log().Debug("Requesting CHT", "cht", r.ChtNum, "block", r.BlockNum) + var encNum [8]byte + binary.BigEndian.PutUint64(encNum[:], r.BlockNum) req := HelperTrieReq{ Type: htCanonical, @@ -340,6 +364,7 @@ func (r *ChtRequest) Request(reqID uint64, peer *serverPeer) error { Key: encNum[:], AuxReq: htAuxHeader, } + return peer.requestHelperTrieProofs(reqID, []HelperTrieReq{req}) } @@ -352,15 +377,19 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { if msg.MsgType != MsgHelperTrieProofs { return errInvalidMessageType } + resp := msg.Obj.(HelperTrieResps) if len(resp.AuxData) != 1 { return errInvalidEntryCount } + nodeSet := resp.Proofs.NodeSet() + headerEnc := resp.AuxData[0] if len(headerEnc) == 0 { return errHeaderUnavailable } + header := new(types.Header) if err := rlp.DecodeBytes(headerEnc, header); err != nil { return errHeaderUnavailable @@ -370,22 +399,28 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { node light.ChtNode 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 } + 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 } @@ -393,6 +428,7 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { r.Header = header r.Proof = nodeSet r.Td = node.Td + return nil } @@ -417,6 +453,7 @@ func (r *BloomRequest) CanSend(peer *serverPeer) bool { if peer.version < lpv2 { return false } + return peer.headInfo.Number >= r.Config.BloomTrieConfirms && r.BloomTrieNum <= (peer.headInfo.Number-r.Config.BloomTrieConfirms)/r.Config.BloomTrieSize } @@ -426,6 +463,7 @@ func (r *BloomRequest) Request(reqID uint64, peer *serverPeer) error { reqs := make([]HelperTrieReq, len(r.SectionIndexList)) var encNumber [10]byte + binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx)) for i, sectionIdx := range r.SectionIndexList { @@ -436,6 +474,7 @@ func (r *BloomRequest) Request(reqID uint64, peer *serverPeer) error { Key: common.CopyBytes(encNumber[:]), } } + return peer.requestHelperTrieProofs(reqID, reqs) } @@ -449,6 +488,7 @@ func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error { if msg.MsgType != MsgHelperTrieProofs { return errInvalidMessageType } + resps := msg.Obj.(HelperTrieResps) proofs := resps.Proofs nodeSet := proofs.NodeSet() @@ -458,21 +498,26 @@ func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error { // Verify the proofs var encNumber [10]byte + binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx)) for i, idx := range r.SectionIndexList { binary.BigEndian.PutUint64(encNumber[2:], idx) + value, err := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads) if err != nil { return err } + r.BloomBits[i] = value } if len(reads.reads) != nodeSet.KeyCount() { return errUselessNodes } + r.Proofs = nodeSet + return nil } @@ -505,11 +550,14 @@ func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error { if msg.MsgType != MsgTxStatus { return errInvalidMessageType } + status := msg.Obj.([]light.TxStatus) if len(status) != len(r.Hashes) { return errInvalidEntryCount } + r.Status = status + return nil } @@ -525,7 +573,9 @@ func (db *readTraceDB) Get(k []byte) ([]byte, error) { if db.reads == nil { db.reads = make(map[string]struct{}) } + db.reads[string(k)] = struct{}{} + return db.db.Get(k) } diff --git a/les/odr_test.go b/les/odr_test.go index d68b6641d9..584b5a7414 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -53,10 +53,13 @@ func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainCon } else { block, _ = lc.GetBlockByHash(ctx, bhash) } + if block == nil { return nil } + rlp, _ := rlp.EncodeToBytes(block) + return rlp } @@ -66,6 +69,7 @@ func TestOdrGetReceiptsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetReceipt 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 + if bc != nil { if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { receipts = rawdb.ReadReceipts(db, bhash, *number, config) @@ -75,10 +79,13 @@ func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.Chain receipts, _ = light.GetBlockReceipts(ctx, lc.Odr(), bhash, *number) } } + if receipts == nil { return nil } + rlp, _ := rlp.EncodeToBytes(receipts) + return rlp } @@ -95,6 +102,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon st *state.StateDB err error ) + for _, addr := range acc { if bc != nil { header := bc.GetHeaderByHash(bhash) @@ -103,12 +111,14 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon header := lc.GetHeaderByHash(bhash) st = light.NewState(ctx, header, lc.Odr()) } + if err == nil { bal := st.GetBalance(addr) rlp, _ := rlp.EncodeToBytes(bal) res = append(res, rlp...) } } + return res } @@ -120,8 +130,10 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000") var res []byte + for i := 0; i < 3; i++ { data[35] = byte(i) + if bc != nil { header := bc.GetHeaderByHash(bhash) statedb, err := state.New(header.Root, bc.StateCache(), nil) @@ -178,6 +190,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai } } } + return res } @@ -187,6 +200,7 @@ 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 + if bc != nil { block := bc.GetBlockByHash(bhash) txs = block.Transactions() @@ -194,8 +208,10 @@ func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainCon if block, _ := lc.GetBlockByHash(ctx, bhash); block != nil { btxs := block.Transactions() txs = make(types.Transactions, len(btxs)) + for i, tx := range btxs { var err error + txs[i], _, _, _, err = light.GetTransaction(ctx, lc.Odr(), tx.Hash()) if err != nil { return nil @@ -203,7 +219,9 @@ func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainCon } } } + rlp, _ := rlp.EncodeToBytes(txs) + return rlp } @@ -216,6 +234,7 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od connect: true, nopruning: true, } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -240,13 +259,16 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od // for travis to make the action. ctx, cancel := context.WithTimeout(context.Background(), time.Second) b2 := fn(ctx, client.db, client.handler.backend.chainConfig, nil, client.handler.backend.blockchain, bhash) + cancel() eq := bytes.Equal(b1, b2) exp := i < expFail + if exp && !eq { t.Fatalf("odr mismatch: have %x, want %x", b2, b1) } + if !exp && eq { t.Fatalf("unexpected odr match") } @@ -284,6 +306,7 @@ func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { nopruning: true, } ) + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -303,6 +326,7 @@ func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { 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 @@ -328,19 +352,24 @@ func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { 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 @@ -353,9 +382,11 @@ func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { Index: intraIndex[hash], } } + data, _ := rlp.EncodeToBytes(stats) reply := &reply{peer.app, TxStatusMsg, r.ReqID, data} reply.send(testBufLimit) + return nil } @@ -408,11 +439,13 @@ func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { results: []light.TxStatus{{}, {}}, }, } + for _, testspec := range testspecs { // Create a bunch of server peers with different tx history var ( closeFns []func() ) + for i := 0; i < testspec.peers; i++ { peer, closePeer, _ := client.newRawPeer(t, fmt.Sprintf("server-%d", i), protocol, testspec.txLookups[i]) closeFns = append(closeFns, closePeer) @@ -426,6 +459,7 @@ func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { // 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() @@ -451,5 +485,6 @@ func randomHash() 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 df2d27fa40..dce720f956 100644 --- a/les/peer.go +++ b/les/peer.go @@ -86,23 +86,29 @@ type keyValueMap map[string]rlp.RawValue func (l keyValueList) add(key string, val interface{}) keyValueList { var entry keyValueEntry entry.Key = key + if val == nil { val = uint64(0) } + enc, err := rlp.EncodeToBytes(val) if err == nil { entry.Value = enc } + return append(l, entry) } func (l keyValueList) decode() (keyValueMap, uint64) { m := make(keyValueMap) + var size uint64 + for _, entry := range l { m[entry.Key] = entry.Value size += uint64(len(entry.Key)) + uint64(len(entry.Value)) + 8 } + return m, size } @@ -111,9 +117,11 @@ func (m keyValueMap) get(key string, val interface{}) error { if !ok { return errResp(ErrMissingKey, "%s", key) } + if val == nil { return nil } + return rlp.DecodeBytes(enc, val) } @@ -222,10 +230,12 @@ func (p *peerCommons) sendReceiveHandshake(sendList keyValueList) (keyValueList, errc <- err return } + if msg.Code != StatusMsg { errc <- errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) return } + if msg.Size > ProtocolMaxMsgSize { errc <- errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) return @@ -237,8 +247,10 @@ func (p *peerCommons) sendReceiveHandshake(sendList keyValueList) (keyValueList, } errc <- nil }() + timeout := time.NewTimer(handshakeTimeout) defer timeout.Stop() + for i := 0; i < 2; i++ { select { case err := <-errc: @@ -249,6 +261,7 @@ func (p *peerCommons) sendReceiveHandshake(sendList keyValueList) (keyValueList, return nil, p2p.DiscReadTimeout } } + return recvList, nil } @@ -287,27 +300,35 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g if err != nil { return err } + recv, size := recvList.decode() if size > allowedUpdateBytes { return errResp(ErrRequestRejected, "") } + var rGenesis common.Hash + var rVersion, rNetwork uint64 if err := recv.get("protocolVersion", &rVersion); err != nil { return err } + if err := recv.get("networkId", &rNetwork); err != nil { return err } + if err := recv.get("genesisHash", &rGenesis); err != nil { return err } + if rGenesis != genesis { return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", rGenesis[:8], genesis[:8]) } + if rNetwork != p.network { return errResp(ErrNetworkIdMismatch, "%d (!= %d)", rNetwork, p.network) } + if int(rVersion) != p.version { return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version) } @@ -317,13 +338,16 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g 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) } + return nil } @@ -396,7 +420,9 @@ func (p *serverPeer) rejectUpdate(size uint64) bool { p.updateCount = 0 } } + p.updateCount += size + return p.updateCount > allowedUpdateBytes } @@ -421,6 +447,7 @@ func sendRequest(w p2p.MsgWriter, msgcode, reqID uint64, data interface{}) error ReqID uint64 Data interface{} } + return p2p.Send(w, msgcode, &req{reqID, data}) } @@ -484,10 +511,12 @@ func (p *serverPeer) requestTxStatus(reqID uint64, txHashes []common.Hash) error // sendTxs creates a reply with a batch of transactions to be added to the remote transaction pool. func (p *serverPeer) sendTxs(reqID uint64, amount int, txs rlp.RawValue) error { p.Log().Debug("Sending batch of transactions", "amount", amount, "size", len(txs)) + sizeFactor := (len(txs) + txSizeCostLimit/2) / txSizeCostLimit if sizeFactor > amount { amount = sizeFactor } + return p.sendRequest(SendTxV2Msg, reqID, txs, amount) } @@ -506,10 +535,12 @@ func (p *serverPeer) getRequestCost(msgcode uint64, amount int) uint64 { if costs == nil { return 0 } + cost := costs.baseCost + costs.reqCost*uint64(amount) if cost > p.fcParams.BufLimit { cost = p.fcParams.BufLimit } + return cost } @@ -523,14 +554,18 @@ func (p *serverPeer) getTxRelayCost(amount, size int) uint64 { if costs == nil { return 0 } + cost := costs.baseCost + costs.reqCost*uint64(amount) sizeCost := costs.baseCost + costs.reqCost*uint64(size)/txSizeCostLimit + if sizeCost > cost { cost = sizeCost } + if cost > p.fcParams.BufLimit { cost = p.fcParams.BufLimit } + return cost } @@ -542,7 +577,9 @@ func (p *serverPeer) HasBlock(hash common.Hash, number uint64, hasState bool) bo if p.hasBlockHook != nil { return p.hasBlockHook(hash, number, hasState) } + head := p.headInfo.Number + var since, recent uint64 if hasState { since = p.stateSince @@ -551,6 +588,7 @@ func (p *serverPeer) HasBlock(hash common.Hash, number uint64, hasState bool) bo since = p.chainSince recent = p.chainRecent } + return head >= number && number >= since && (recent == 0 || number+recent+4 > head) } @@ -567,6 +605,7 @@ func (p *serverPeer) updateFlowControl(update keyValueMap) { p.fcParams = params p.fcServer.UpdateParams(params) } + var MRC RequestCostList if update.get("flowControl/MRC", &MRC) == nil { costUpdate := MRC.decode(ProtocolLengths[uint(p.version)]) @@ -598,6 +637,7 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter if p.trusted { p.announceType = announceTypeSigned } + *lists = (*lists).add("announceType", p.announceType) }, func(recv keyValueMap) error { var ( @@ -605,36 +645,46 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter rNum uint64 rTd *big.Int ) + if err := recv.get("headTd", &rTd); err != nil { return err } + if err := recv.get("headHash", &rHash); err != nil { return err } + if err := recv.get("headNum", &rNum); err != nil { return err } + p.headInfo = blockInfo{Hash: rHash, Number: rNum, Td: rTd} if recv.get("serveChainSince", &p.chainSince) != nil { p.onlyAnnounce = true } + if recv.get("serveRecentChain", &p.chainRecent) != nil { p.chainRecent = 0 } + if recv.get("serveStateSince", &p.stateSince) != nil { p.onlyAnnounce = true } + if recv.get("serveRecentState", &p.stateRecent) != nil { p.stateRecent = 0 } + 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 } + p.txHistory = uint64(recentTx) } else { // The weak assumption is held here that legacy les server(les2,3) @@ -642,6 +692,7 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter // versions is disabled if the transaction is unindexed. p.txHistory = txIndexUnlimited } + if p.onlyAnnounce && !p.trusted { return errResp(ErrUselessPeer, "peer cannot serve requests") } @@ -650,13 +701,16 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter if err := recv.get("flowControl/BL", &sParams.BufLimit); err != nil { return err } + if err := recv.get("flowControl/MRR", &sParams.MinRecharge); err != nil { return err } + var MRC RequestCostList if err := recv.get("flowControl/MRC", &MRC); err != nil { return err } + p.fcParams = sParams p.fcServer = flowcontrol.NewServerNode(sParams, &mclock.System{}) p.fcCosts = MRC.decode(ProtocolLengths[uint(p.version)]) @@ -671,6 +725,7 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter } } } + return nil }) } @@ -679,6 +734,7 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter // references should be removed upon disconnection by setValueTracker(nil, nil). func (p *serverPeer) setValueTracker(nvt *vfc.NodeValueTracker) { p.vtLock.Lock() + p.nodeValueTracker = nvt if nvt != nil { p.sentReqs = make(map[uint64]sentReqEntry) @@ -696,7 +752,9 @@ func (p *serverPeer) updateVtParams() { if p.nodeValueTracker == nil { return } + reqCosts := make([]uint64, len(requestList)) + for code, costs := range p.fcCosts { if m, ok := requestMapping[uint32(code)]; ok { reqCosts[m.first] = costs.baseCost + costs.reqCost @@ -705,6 +763,7 @@ func (p *serverPeer) updateVtParams() { } } } + p.nodeValueTracker.UpdateCosts(reqCosts) } @@ -730,17 +789,21 @@ func (p *serverPeer) answeredRequest(id uint64) { p.vtLock.Unlock() return } + e, ok := p.sentReqs[id] delete(p.sentReqs, id) nvt := p.nodeValueTracker p.vtLock.Unlock() + if !ok { return } + var ( vtReqs [2]vfc.ServedRequest reqCount int ) + m := requestMapping[e.reqType] if m.rest == -1 || e.amount <= 1 { reqCount = 1 @@ -750,6 +813,7 @@ func (p *serverPeer) answeredRequest(id uint64) { 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) nvt.Served(vtReqs[:reqCount], dt) } @@ -810,6 +874,7 @@ func (p *clientPeer) FreeClientId() string { return addr.IP.String() } } + return p.id } @@ -833,23 +898,29 @@ func (p *clientPeer) freeze() { // its frozen status permanently atomic.StoreUint32(&p.frozen, 1) p.Peer.Disconnect(p2p.DiscUselessPeer) + return } + if atomic.SwapUint32(&p.frozen, 1) == 0 { go func() { p.sendStop() time.Sleep(freezeTimeBase + time.Duration(rand.Int63n(int64(freezeTimeRandom)))) + for { bufValue, bufLimit := p.fcClient.BufferStatus() if bufLimit == 0 { return } + if bufValue <= bufLimit/8 { time.Sleep(freezeCheckPeriod) continue } + atomic.StoreUint32(&p.frozen, 0) p.sendResume(bufValue) + return } }() @@ -871,6 +942,7 @@ func (r *reply) send(bv uint64) error { ReqID, BV uint64 Data rlp.RawValue } + return p2p.Send(r.w, r.msgcode, &resp{r.reqID, bv, r.data}) } @@ -954,15 +1026,18 @@ func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) { if newCap != p.fcParams.MinRecharge { p.fcParams = flowcontrol.ServerParams{MinRecharge: newCap, BufLimit: newCap * bufLimitRatio} p.fcClient.UpdateParams(p.fcParams) + var kvList keyValueList kvList = kvList.add("flowControl/MRR", newCap) kvList = kvList.add("flowControl/BL", newCap*bufLimitRatio) + p.queueSend(func() { p.sendAnnounce(announceData{Update: kvList}) }) } if p.capacity == 0 && newCap != 0 { p.sendLastAnnounce() } + p.capacity = newCap } @@ -985,12 +1060,14 @@ func (p *clientPeer) sendLastAnnounce() { if p.lastAnnounce.Td == nil { return } + if p.headInfo.Td == nil || p.lastAnnounce.Td.Cmp(p.headInfo.Td) > 0 { if !p.queueSend(func() { p.sendAnnounce(p.lastAnnounce) }) { p.Log().Debug("Dropped announcement because queue is full", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash) } else { p.Log().Debug("Sent announcement", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash) } + p.headInfo = blockInfo{Hash: p.lastAnnounce.Hash, Number: p.lastAnnounce.Number, Td: p.lastAnnounce.Td} } } @@ -1006,15 +1083,18 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge 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} + return p.handshake(td, head, headNum, genesis, forkID, forkFilter, func(lists *keyValueList) { // Add some information which services server can offer. if !server.config.UltraLightOnlyAnnounce { @@ -1028,12 +1108,15 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge 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) @@ -1043,6 +1126,7 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge } else { costList = server.costTracker.makeCostList(server.costTracker.globalFactor()) } + *lists = (*lists).add("flowControl/MRC", costList) p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)]) p.fcParams = server.defParams @@ -1066,6 +1150,7 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge p.announceType = announceTypeSimple } } + return nil }) } @@ -1079,6 +1164,7 @@ func (p *clientPeer) bumpInvalid() { func (p *clientPeer) getInvalid() uint64 { p.invalidLock.RLock() defer p.invalidLock.RUnlock() + return p.invalidCount.Value(mclock.Now()) } @@ -1132,13 +1218,16 @@ func (ps *serverPeerSet) register(peer *serverPeer) error { if ps.closed { return errClosed } + if _, exist := ps.peers[peer.id]; exist { return errAlreadyRegistered } + ps.peers[peer.id] = peer for _, sub := range ps.subscribers { sub.registerPeer(peer) } + return nil } @@ -1153,11 +1242,15 @@ func (ps *serverPeerSet) unregister(id string) error { if !ok { return errNotRegistered } + delete(ps.peers, id) + for _, sub := range ps.subscribers { sub.unregisterPeer(p) } + p.Peer.Disconnect(p2p.DiscRequested) + return nil } @@ -1170,6 +1263,7 @@ func (ps *serverPeerSet) ids() []string { for id := range ps.peers { ids = append(ids, id) } + return ids } @@ -1198,6 +1292,7 @@ func (ps *serverPeerSet) allPeers() []*serverPeer { for _, p := range ps.peers { list = append(list, p) } + return list } @@ -1210,6 +1305,7 @@ func (ps *serverPeerSet) close() { for _, p := range ps.peers { p.Disconnect(p2p.DiscQuitting) } + ps.closed = true } @@ -1238,11 +1334,14 @@ func (ps *clientPeerSet) register(peer *clientPeer) error { if ps.closed { return errClosed } + if _, exist := ps.peers[peer.ID()]; exist { return errAlreadyRegistered } + ps.peers[peer.ID()] = peer ps.announceOrStore(peer) + return nil } @@ -1257,8 +1356,10 @@ func (ps *clientPeerSet) unregister(id enode.ID) error { if !ok { return errNotRegistered } + delete(ps.peers, id) p.Peer.Disconnect(p2p.DiscRequested) + return nil } @@ -1271,6 +1372,7 @@ func (ps *clientPeerSet) ids() []enode.ID { for id := range ps.peers { ids = append(ids, id) } + return ids } @@ -1305,6 +1407,7 @@ func (ps *clientPeerSet) announceOrStore(p *clientPeer) { if ps.lastAnnounce.Td == nil { return } + switch p.announceType { case announceTypeSimple: p.announceOrStore(ps.lastAnnounce) @@ -1313,6 +1416,7 @@ func (ps *clientPeerSet) announceOrStore(p *clientPeer) { ps.signedAnnounce = ps.lastAnnounce ps.signedAnnounce.sign(ps.privateKey) } + p.announceOrStore(ps.signedAnnounce) } } @@ -1326,6 +1430,7 @@ func (ps *clientPeerSet) close() { for _, p := range ps.peers { p.Peer.Disconnect(p2p.DiscQuitting) } + ps.closed = true } @@ -1351,10 +1456,13 @@ func (s *serverSet) register(peer *clientPeer) error { if s.closed { return errClosed } + if _, exist := s.set[peer.id]; exist { return errAlreadyRegistered } + s.set[peer.id] = peer + return nil } @@ -1365,11 +1473,14 @@ func (s *serverSet) unregister(peer *clientPeer) error { if s.closed { return errClosed } + if _, exist := s.set[peer.id]; !exist { return errNotRegistered } + delete(s.set, peer.id) peer.Peer.Disconnect(p2p.DiscQuitting) + return nil } @@ -1380,5 +1491,6 @@ func (s *serverSet) close() { for _, p := range s.set { p.Peer.Disconnect(p2p.DiscQuitting) } + s.closed = true } diff --git a/les/peer_test.go b/les/peer_test.go index 021d5cb594..10c5d2f484 100644 --- a/les/peer_test.go +++ b/les/peer_test.go @@ -58,8 +58,10 @@ func TestPeerSubscription(t *testing.T) { if len(given) == 0 && len(expect) == 0 { return } + sort.Strings(given) sort.Strings(expect) + if !reflect.DeepEqual(given, expect) { t.Fatalf("all peer ids mismatch, want %v, given %v", expect, given) } @@ -76,6 +78,7 @@ func TestPeerSubscription(t *testing.T) { case <-time.NewTimer(10 * time.Millisecond).C: } } + checkIds([]string{}) sub := newTestServerPeerSub() @@ -83,6 +86,7 @@ func TestPeerSubscription(t *testing.T) { // Generate a random id and create the peer var id enode.ID + rand.Read(id[:]) peer := newServerPeer(2, NetworkId, false, p2p.NewPeer(id, "name", nil), nil) peers.register(peer) @@ -109,6 +113,7 @@ func TestHandshake(t *testing.T) { // Generate a random id and create the peer var id enode.ID + rand.Read(id[:]) peer1 := newClientPeer(2, NetworkId, p2p.NewPeer(id, "name", nil), net) diff --git a/les/protocol.go b/les/protocol.go index dced7039e4..032e85f668 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -175,6 +175,7 @@ var ( // service vector indices. func init() { requestMapping = make(map[uint32]reqMapping) + for code, req := range requests { cost := reqAvgTimeCost[code] rm := reqMapping{len(requestList), -1} @@ -183,6 +184,7 @@ func init() { InitAmount: req.refBasketFirst, InitValue: float64(cost.baseCost + cost.reqCost), }) + if req.refBasketRest != 0 { rm.rest = len(requestList) requestList = append(requestList, vfc.RequestInfo{ @@ -191,6 +193,7 @@ func init() { InitValue: float64(cost.reqCost), }) } + requestMapping[uint32(code)] = rm } } @@ -253,6 +256,7 @@ func (a *announceData) sanityCheck() error { if tdlen := a.Td.BitLen(); tdlen > 100 { return fmt.Errorf("too large block TD: bitlen %d", tdlen) } + return nil } @@ -269,14 +273,18 @@ func (a *announceData) checkSignature(id enode.ID, update keyValueMap) error { if err := update.get("sign", &sig); err != nil { return err } + rlp, _ := rlp.EncodeToBytes(blockInfo{a.Hash, a.Number, a.Td}) + recPubkey, err := crypto.SigToPub(crypto.Keccak256(rlp), sig) if err != nil { return err } + if id == enode.PubkeyToIDV4(recPubkey) { return nil } + return errors.New("wrong signature") } @@ -298,9 +306,11 @@ 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) } @@ -308,6 +318,7 @@ func (hn *hashOrNumber) EncodeRLP(w io.Writer) error { // into either a block hash or a block number. func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { _, size, err := s.Kind() + switch { case err != nil: return err diff --git a/les/pruner.go b/les/pruner.go index d115a61a70..df7755acef 100644 --- a/les/pruner.go +++ b/les/pruner.go @@ -42,7 +42,9 @@ func newPruner(db ethdb.Database, indexers ...*core.ChainIndexer) *pruner { closeCh: make(chan struct{}), } pruner.wg.Add(1) + go pruner.loop() + return pruner } @@ -70,6 +72,7 @@ func (p *pruner) loop() { // pruning operations can be silently ignored. pruning := func() { min := uint64(math.MaxUint64) + for _, indexer := range p.indexers { sections, _, _ := indexer.Sections() if sections < min { @@ -80,14 +83,17 @@ func (p *pruner) loop() { if min < 2 || len(p.indexers) == 0 { return } + for _, indexer := range p.indexers { if err := indexer.Prune(min - 2); err != nil { log.Debug("Failed to prune historical data", "err", err) return } } + p.db.Compact(nil, nil) // Compact entire database, ensure all removed data are deleted. } + for { pruning() select { diff --git a/les/pruner_test.go b/les/pruner_test.go index 1672414937..41c391e0bb 100644 --- a/les/pruner_test.go +++ b/les/pruner_test.go @@ -47,6 +47,7 @@ func TestLightPruner(t *testing.T) { connect: true, } ) + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -57,6 +58,7 @@ func TestLightPruner(t *testing.T) { defer it.Release() var next = from + for it.Next() { number := resolve(it.Key(), it.Value()) if number == nil || *number < from { @@ -64,15 +66,18 @@ func TestLightPruner(t *testing.T) { } else if *number > to { return true } + if exist { if *number != next { return false } + next++ } else { return false } } + return true } // checkPruned checks and ensures the stale chain data has been pruned. @@ -184,6 +189,7 @@ func TestLightPruner(t *testing.T) { }, }, } + for _, c := range cases { for i := c.from; i <= c.to; i++ { if !c.method(i) { diff --git a/les/request_test.go b/les/request_test.go index 9b52e6bd86..4470d03fb6 100644 --- a/les/request_test.go +++ b/les/request_test.go @@ -60,6 +60,7 @@ func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) ligh if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { return &light.TrieRequest{Id: light.StateTrieID(rawdb.ReadHeader(db, bhash, *number)), Key: testBankSecureTrieKey} } + return nil } @@ -72,12 +73,15 @@ func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrReq if number != nil { return nil } + header := rawdb.ReadHeader(db, bhash, *number) if header.Number.Uint64() < testContractDeployed { return nil } + sti := light.StateTrieID(header) ci := light.StorageTrieID(sti, crypto.Keccak256Hash(testContractAddr[:]), common.Hash{}) + return &light.CodeRequest{Id: ci, Hash: crypto.Keccak256Hash(testContractCodeDeployed)} } @@ -90,6 +94,7 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) { connect: true, nopruning: true, } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -106,13 +111,16 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) { ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) err := client.handler.backend.odr.Retrieve(ctx, req) + cancel() got := err == nil exp := i < expFail + if exp && !got { t.Errorf("object retrieval failed") } + if !exp && got { t.Errorf("unexpected object retrieval success") } diff --git a/les/retrieve.go b/les/retrieve.go index 307af04212..8395313175 100644 --- a/les/retrieve.go +++ b/les/retrieve.go @@ -112,6 +112,7 @@ func (rm *retrieveManager) retrieve(ctx context.Context, reqID uint64, req *dist case <-shutdown: sentReq.stop(fmt.Errorf("client is shutting down")) } + return sentReq.getError() } @@ -134,6 +135,7 @@ func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc r.lock.RLock() _, sent := r.sentTo[p] r.lock.RUnlock() + return !sent && canSend(p) } @@ -143,13 +145,16 @@ func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc r.lock.Lock() r.sentTo[p] = sentReqToPeer{delivered: false, frozen: false, event: make(chan int, 1)} r.lock.Unlock() + return request(p) } + rm.lock.Lock() rm.sentReqs[reqID] = r rm.lock.Unlock() go r.retrieveLoop() + return r } @@ -159,6 +164,7 @@ func (rm *retrieveManager) requested(reqId uint64) bool { defer rm.lock.RUnlock() _, ok := rm.sentReqs[reqId] + return ok } @@ -171,6 +177,7 @@ func (rm *retrieveManager) deliver(peer distPeer, msg *Msg) error { if ok { return req.deliver(peer, msg) } + return errResp(ErrUnexpectedResponse, "reqID = %v", msg.ReqID) } @@ -209,6 +216,7 @@ func (r *sentReq) stateRequesting() reqStateFn { select { case ev := <-r.eventsCh: r.update(ev) + switch ev.event { case rpSent: if ev.peer == nil { @@ -226,6 +234,7 @@ func (r *sentReq) stateRequesting() reqStateFn { // last request timed out, try asking a new peer go r.tryRequest() r.lastReqQueued = true + return r.stateRequesting case rpDeliveredInvalid, rpNotDelivered: // if it was the last sent request (set to nil by update) then start a new one @@ -233,11 +242,13 @@ func (r *sentReq) stateRequesting() reqStateFn { go r.tryRequest() r.lastReqQueued = true } + return r.stateRequesting case rpDeliveredValid: r.stop(nil) return r.stateStopped } + return r.stateRequesting case <-r.stopCh: return r.stateStopped @@ -252,17 +263,22 @@ func (r *sentReq) stateNoMorePeers() reqStateFn { case <-time.After(retryQueue): go r.tryRequest() r.lastReqQueued = true + return r.stateRequesting case ev := <-r.eventsCh: r.update(ev) + if ev.event == rpDeliveredValid { r.stop(nil) return r.stateStopped } + if r.waiting() { return r.stateNoMorePeers } + r.stop(light.ErrNoPeers) + return nil case <-r.stopCh: return r.stateStopped @@ -275,6 +291,7 @@ func (r *sentReq) stateStopped() reqStateFn { for r.waiting() { r.update(<-r.eventsCh) } + return nil } @@ -309,6 +326,7 @@ func (r *sentReq) waiting() bool { // messages to the request's event channel. func (r *sentReq) tryRequest() { sent := r.rm.dist.queue(r.req) + var p distPeer select { case p = <-sent: @@ -321,6 +339,7 @@ func (r *sentReq) tryRequest() { } r.eventsCh <- reqPeerEvent{rpSent, p} + if p == nil { return } @@ -330,6 +349,7 @@ func (r *sentReq) tryRequest() { r.lock.RLock() s, ok := r.sentTo[p] r.lock.RUnlock() + if !ok { panic(nil) } @@ -338,6 +358,7 @@ func (r *sentReq) tryRequest() { pp, ok := p.(*serverPeer) if hrto && ok { pp.Log().Debug("Request timed out hard") + if r.rm.peers != nil { r.rm.peers.unregister(pp.id) } @@ -352,6 +373,7 @@ func (r *sentReq) tryRequest() { r.lock.Unlock() } r.eventsCh <- reqPeerEvent{event, p} + return case <-time.After(r.rm.softRequestTimeout()): r.eventsCh <- reqPeerEvent{rpSoftTimeout, p} @@ -380,19 +402,24 @@ func (r *sentReq) deliver(peer distPeer, msg *Msg) error { if !ok || s.delivered { return errResp(ErrUnexpectedResponse, "reqID = %v", msg.ReqID) } + if s.frozen { return nil } + valid := r.validate(peer, msg) == nil r.sentTo[peer] = sentReqToPeer{delivered: true, frozen: false, event: s.event} + if valid { s.event <- rpDeliveredValid } else { s.event <- rpDeliveredInvalid } + if !valid { return errResp(ErrInvalidResponse, "reqID = %v", msg.ReqID) } + return nil } diff --git a/les/server.go b/les/server.go index 06bbc30fb0..713f84ea55 100644 --- a/les/server.go +++ b/les/server.go @@ -88,6 +88,7 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les if threads < 4 { threads = 4 } + srv := &LesServer{ lesCommons: lesCommons{ genesis: e.BlockChain().Genesis().Hash(), @@ -111,10 +112,12 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les threadsIdle: threads, p2pSrv: node.Server(), } + 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) @@ -134,9 +137,11 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les // possible while the actually used server capacity does not exceed the limits totalRecharge := srv.costTracker.totalRecharge() srv.maxCapacity = srv.minCapacity * uint64(srv.config.LightPeers) + if totalRecharge > srv.maxCapacity { srv.maxCapacity = totalRecharge } + srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) srv.clientPool = vfs.NewClientPool(lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, issync) srv.clientPool.Start() @@ -148,11 +153,13 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les log.Info("Loaded latest checkpoint", "section", checkpoint.SectionIndex, "head", checkpoint.SectionHead, "chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot) } + srv.chtIndexer.Start(e.BlockChain()) node.RegisterProtocols(srv.Protocols()) node.RegisterAPIs(srv.APIs()) node.RegisterLifecycle(srv) + return srv, nil } @@ -178,6 +185,7 @@ func (s *LesServer) Protocols() []p2p.Protocol { if p := s.peers.peer(id); p != nil { return p.Info() } + return nil }, nil) // Add "les" ENR entries. @@ -186,6 +194,7 @@ func (s *LesServer) Protocols() []p2p.Protocol { VfxVersion: 1, }} } + return ps } @@ -195,10 +204,13 @@ func (s *LesServer) Start() error { s.peers.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 } @@ -207,14 +219,17 @@ func (s *LesServer) Stop() error { close(s.closeCh) s.clientPool.Stop() + if s.serverset != nil { s.serverset.close() } + s.peers.close() s.fcManager.Stop() s.costTracker.stop() s.handler.stop() s.servingQueue.stop() + if s.vfluxServer != nil { s.vfluxServer.Stop() } @@ -223,9 +238,11 @@ func (s *LesServer) Stop() error { if s.chtIndexer != nil { s.chtIndexer.Close() } + if s.lesDb != nil { s.lesDb.Close() } + s.wg.Wait() log.Info("Les server stopped") @@ -239,6 +256,7 @@ func (s *LesServer) capacityManagement() { defer s.wg.Done() processCh := make(chan bool, 100) + sub := s.handler.blockchain.SubscribeBlockProcessingEvent(processCh) defer sub.Unsubscribe() @@ -254,6 +272,7 @@ func (s *LesServer) capacityManagement() { freePeers uint64 blockProcess mclock.AbsTime ) + updateRecharge := func() { if busy { s.servingQueue.setThreads(s.threadsBusy) @@ -273,17 +292,21 @@ func (s *LesServer) capacityManagement() { } else { blockProcessingTimer.Update(time.Duration(mclock.Now() - blockProcess)) } + updateRecharge() case totalRecharge = <-totalRechargeCh: totalRechargeGauge.Update(int64(totalRecharge)) updateRecharge() case totalCapacity = <-totalCapacityCh: totalCapacityGauge.Update(int64(totalCapacity)) + newFreePeers := totalCapacity / s.minCapacity if newFreePeers < freePeers && newFreePeers < uint64(s.config.LightPeers) { log.Warn("Reduced free peer connections", "from", freePeers, "to", newFreePeers) } + freePeers = newFreePeers + s.clientPool.SetLimits(uint64(s.config.LightPeers), totalCapacity) case <-s.closeCh: return diff --git a/les/server_handler.go b/les/server_handler.go index dc545e4b6b..6ae5d9c9f7 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -84,6 +84,7 @@ func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb et closeCh: make(chan struct{}), synced: synced, } + return handler } @@ -104,7 +105,9 @@ func (h *serverHandler) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) peer := newClientPeer(int(version), h.server.config.NetworkId, p, newMeteredMsgWriter(rw, int(version))) defer peer.close() h.wg.Add(1) + defer h.wg.Done() + return h.handle(peer) } @@ -119,6 +122,7 @@ func (h *serverHandler) handle(p *clientPeer) error { td = h.blockchain.GetTd(hash, number) forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis().Hash(), number, head.Time) ) + 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 @@ -128,8 +132,10 @@ func (h *serverHandler) handle(p *clientPeer) error { if err := h.server.serverset.register(p); err != nil { return err } + _, err := p.rw.ReadMsg() h.server.serverset.unregister(p) + return err } // Setup flow control mechanism for the peer @@ -147,11 +153,14 @@ func (h *serverHandler) handle(p *clientPeer) error { if err := h.server.peers.register(p); err != nil { return err } + if p.balance = h.server.clientPool.Register(p); p.balance == nil { h.server.peers.unregister(p.ID()) p.Log().Debug("Client pool already closed") + return p2p.DiscRequested } + p.connectedAt = mclock.Now() var wg sync.WaitGroup // Wait group used to track all in-flight task routines. @@ -175,6 +184,7 @@ func (h *serverHandler) handle(p *clientPeer) error { return err default: } + if err := h.handleMsg(p, &wg); err != nil { p.Log().Debug("Light Ethereum message handling failed", "err", err) return err @@ -196,12 +206,15 @@ func (h *serverHandler) beforeHandle(p *clientPeer, reqID, responseCount uint64, 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 @@ -211,12 +224,15 @@ func (h *serverHandler) beforeHandle(p *clientPeer, reqID, responseCount uint64, 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 } @@ -226,6 +242,7 @@ func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, if reply != nil { task.done() } + p.responseLock.Lock() defer p.responseLock.Unlock() @@ -233,6 +250,7 @@ func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, 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. @@ -240,6 +258,7 @@ func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, if reply != nil { replySize = reply.size() } + var realCost uint64 if h.server.costTracker.testing { realCost = maxCost // Assign a fake cost for testing purpose @@ -249,7 +268,9 @@ func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, 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) @@ -274,6 +295,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { if err != nil { return err } + p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size) // Discard large message which exceeds the limitation. @@ -289,8 +311,10 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { if !ok { p.Log().Trace("Received invalid message", "code", msg.Code) clientErrorMeter.Mark(1) + return errResp(ErrInvalidMsgCode, "%v", msg.Code) } + p.Log().Trace("Received " + req.Name) // Decode the p2p message, resolve the concrete handler for it. @@ -299,10 +323,12 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { 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 @@ -312,7 +338,9 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { if task == nil { return nil } + wg.Add(1) + go func() { defer wg.Done() @@ -324,6 +352,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { if reply != nil { size = reply.size() } + req.OutPacketsMeter.Mark(1) req.OutTrafficMeter.Mark(int64(size)) req.ServingTimeMeter.Update(time.Duration(task.servingTime)) @@ -335,6 +364,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { clientErrorMeter.Mark(1) return errTooManyInvalidRequest } + return nil } @@ -369,10 +399,12 @@ func getAccount(triedb *trie.Database, root, hash common.Hash) (types.StateAccou if err != nil { return types.StateAccount{}, err } + var acc types.StateAccount if err = rlp.DecodeBytes(blob, &acc); err != nil { return types.StateAccount{}, err } + return acc, nil } @@ -382,6 +414,7 @@ func (h *serverHandler) GetHelperTrie(typ uint, index uint64) *trie.Trie { root common.Hash prefix string ) + switch typ { case htCanonical: sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.ChtSize-1) @@ -390,11 +423,13 @@ func (h *serverHandler) GetHelperTrie(typ uint, index uint64) *trie.Trie { sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.BloomTrieSize-1) root, prefix = light.GetBloomTrieRoot(h.chainDb, index, sectionHead), string(rawdb.BloomTrieTablePrefix) } + if root == (common.Hash{}) { return nil } trie, _ := trie.New(trie.TrieID(root), trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) + return trie } @@ -406,6 +441,7 @@ func (h *serverHandler) broadcastLoop() { defer h.wg.Done() headCh := make(chan core.ChainHeadEvent, 10) + headSub := h.blockchain.SubscribeChainHeadEvent(headCh) defer headSub.Unsubscribe() @@ -413,22 +449,27 @@ func (h *serverHandler) broadcastLoop() { lastHead = h.blockchain.CurrentHeader() lastTd = common.Big0 ) + for { select { case ev := <-headCh: header := ev.Block.Header() hash, number := header.Hash(), header.Number.Uint64() + td := h.blockchain.GetTd(hash, number) if td == nil || td.Cmp(lastTd) <= 0 { continue } + var reorg uint64 + if lastHead != nil { // If a setHead has been performed, the common ancestor can be nil. if ancestor := rawdb.FindCommonAncestor(h.chainDb, header, lastHead); ancestor != nil { reorg = lastHead.Number.Uint64() - ancestor.Number.Uint64() } } + lastHead, lastTd = header, td log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg) h.server.peers.broadcast(announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}) diff --git a/les/server_requests.go b/les/server_requests.go index f8c070bc37..98bee2724f 100644 --- a/les/server_requests.go +++ b/les/server_requests.go @@ -154,6 +154,7 @@ func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) 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 ( @@ -165,12 +166,14 @@ func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) 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) @@ -183,9 +186,11 @@ func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) } else { origin = bc.GetHeaderByNumber(r.Query.Origin.Number) } + if origin == nil { break } + headers = append(headers, origin) bytes += estHeaderRlpSize @@ -206,14 +211,17 @@ func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) current = origin.Number.Uint64() next = current + r.Query.Skip + 1 ) + if next <= current { infos, _ := json.Marshal(p.Peer.Info()) p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", string(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 { @@ -235,8 +243,10 @@ func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) // 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 } @@ -247,27 +257,34 @@ func handleGetBlockBodies(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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 } @@ -278,12 +295,15 @@ func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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 @@ -293,6 +313,7 @@ func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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 @@ -301,16 +322,20 @@ func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) { if !backend.ArchiveMode() && header.Number.Uint64()+core.DefaultCacheConfig.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) @@ -318,10 +343,12 @@ func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) { } // 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 } @@ -332,16 +359,20 @@ func handleGetReceipts(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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 } @@ -361,6 +392,7 @@ func handleGetReceipts(msg Decoder) (serveRequestFn, uint64, uint64, error) { bytes += len(encoded) } } + return p.replyReceiptsRLP(r.ReqID, receipts) }, r.ReqID, uint64(len(r.Hashes)), nil } @@ -371,6 +403,7 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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 @@ -378,6 +411,7 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { header *types.Header err error ) + bc := backend.BlockChain() nodes := light.NewNodeSet() @@ -392,6 +426,7 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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 @@ -400,8 +435,10 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { if !backend.ArchiveMode() && header.Number.Uint64()+core.DefaultCacheConfig.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 @@ -413,6 +450,7 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { statedb := bc.StateCache() var trie state.Trie + switch len(request.AccKey) { case 0: // No account key specified, open an account trie @@ -427,6 +465,7 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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 } @@ -441,10 +480,12 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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 } @@ -455,6 +496,7 @@ func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, err 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 @@ -463,16 +505,20 @@ func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, err 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 } @@ -485,20 +531,25 @@ func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, err 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 } @@ -509,13 +560,17 @@ func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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) @@ -530,9 +585,11 @@ func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) { stats[i].Error = errs[0].Error() continue } + stats[i] = txStatus(backend, hash) } } + return p.replyTxStatus(r.ReqID, stats) }, r.ReqID, amount, nil } @@ -543,14 +600,18 @@ func handleGetTxStatus(msg Decoder) (serveRequestFn, uint64, uint64, error) { 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 } @@ -569,5 +630,6 @@ func txStatus(b serverBackend, hash common.Hash) light.TxStatus { stat.Lookup = lookup } } + return stat } diff --git a/les/servingqueue.go b/les/servingqueue.go index b4b53d8df5..1b9e44be37 100644 --- a/les/servingqueue.go +++ b/les/servingqueue.go @@ -73,6 +73,7 @@ func (t *servingTask) start() bool { if t.peer.isFrozen() { return false } + t.tokenCh = make(chan runToken, 1) select { case t.sq.queueAddCh <- t: @@ -84,10 +85,13 @@ func (t *servingTask) start() bool { case <-t.sq.quit: return false } + if t.token == nil { return false } + t.servingTime -= uint64(mclock.Now()) + return true } @@ -98,12 +102,14 @@ func (t *servingTask) done() uint64 { close(t.token) diff := t.servingTime - t.timeAdded t.timeAdded = t.servingTime + if t.expTime > diff { t.expTime -= diff atomic.AddUint64(&t.sq.servingTimeDiff, t.expTime) } else { t.expTime = 0 } + return t.servingTime } @@ -113,10 +119,12 @@ func (t *servingTask) done() uint64 { // means the task should be cancelled. func (t *servingTask) waitOrStop() bool { t.done() + if !t.biasAdded { t.priority += t.sq.suspendBias t.biasAdded = true } + return t.start() } @@ -136,8 +144,10 @@ func newServingQueue(suspendBias int64, utilTarget float64) *servingQueue { lastUpdate: mclock.Now(), } sq.wg.Add(2) + go sq.queueLoop() go sq.threadCountLoop() + return sq } @@ -160,6 +170,7 @@ func (sq *servingQueue) newTask(peer *clientPeer, maxTime uint64, priority int64 // without entering the priority queue. func (sq *servingQueue) threadController() { defer sq.wg.Done() + for { token := make(runToken) select { @@ -208,19 +219,24 @@ func (l peerList) Swap(i, j int) { // them until burstTime goes under burstDropLimit or all peers are frozen func (sq *servingQueue) freezePeers() { peerMap := make(map[*clientPeer]*peerTasks) + var peerList peerList + if sq.best != nil { sq.queue.Push(sq.best, sq.best.priority) } + sq.best = nil for sq.queue.Size() > 0 { task := sq.queue.PopItem() tasks := peerMap[task.peer] + if tasks == nil { bufValue, bufLimit := task.peer.fcClient.BufferStatus() if bufLimit < 1 { bufLimit = 1 } + tasks = &peerTasks{ peer: task.peer, priority: float64(bufValue) / float64(bufLimit), // lower value comes first @@ -228,10 +244,12 @@ func (sq *servingQueue) freezePeers() { peerMap[task.peer] = tasks peerList = append(peerList, tasks) } + tasks.list = append(tasks.list, task) tasks.sumTime += task.expTime } sort.Sort(peerList) + drop := true for _, tasks := range peerList { if drop { @@ -240,7 +258,9 @@ func (sq *servingQueue) freezePeers() { sq.queuedTime -= tasks.sumTime sqQueuedGauge.Update(int64(sq.queuedTime)) clientFreezeMeter.Mark(1) + drop = sq.recentTime+sq.queuedTime > sq.burstDropLimit + for _, task := range tasks.list { task.tokenCh <- nil } @@ -250,6 +270,7 @@ func (sq *servingQueue) freezePeers() { } } } + if sq.queue.Size() > 0 { sq.best = sq.queue.PopItem() } @@ -261,9 +282,11 @@ func (sq *servingQueue) updateRecentTime() { now := mclock.Now() dt := now - sq.lastUpdate sq.lastUpdate = now + if dt > 0 { subTime += uint64(float64(dt) * sq.burstDecRate) } + if sq.recentTime > subTime { sq.recentTime -= subTime } else { @@ -281,10 +304,12 @@ func (sq *servingQueue) addTask(task *servingTask) { } else { sq.queue.Push(task, task.priority) } + sq.updateRecentTime() sq.queuedTime += task.expTime sqServedGauge.Update(int64(sq.recentTime)) sqQueuedGauge.Update(int64(sq.queuedTime)) + if sq.recentTime+sq.queuedTime > sq.burstLimit { sq.freezePeers() } @@ -295,6 +320,7 @@ func (sq *servingQueue) addTask(task *servingTask) { // tasks are removed from the queue. func (sq *servingQueue) queueLoop() { defer sq.wg.Done() + for { if sq.best != nil { expTime := sq.best.expTime @@ -307,6 +333,7 @@ func (sq *servingQueue) queueLoop() { sq.recentTime += expTime sqServedGauge.Update(int64(sq.recentTime)) sqQueuedGauge.Update(int64(sq.queuedTime)) + if sq.queue.Size() == 0 { sq.best = nil } else { @@ -330,13 +357,17 @@ func (sq *servingQueue) queueLoop() { // of active thread controller goroutines. func (sq *servingQueue) threadCountLoop() { var threadCountTarget int + defer sq.wg.Done() + for { for threadCountTarget > sq.threadCount { sq.wg.Add(1) go sq.threadController() + sq.threadCount++ } + if threadCountTarget < sq.threadCount { select { case threadCountTarget = <-sq.setThreadsCh: diff --git a/les/state_accessor.go b/les/state_accessor.go index 1cd4017e7a..1d6a70f5e8 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -54,6 +54,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types. if err != nil { return nil, vm.BlockContext{}, nil, nil, err } + if txIndex == 0 && len(block.Transactions()) == 0 { return nil, vm.BlockContext{}, statedb, release, nil } @@ -66,6 +67,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types. blockContext := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil) statedb.SetTxContext(tx.Hash(), idx) + if idx == txIndex { return msg, blockContext, statedb, release, nil } diff --git a/les/sync.go b/les/sync.go index 31cd06ca70..a9e24f3d02 100644 --- a/les/sync.go +++ b/les/sync.go @@ -58,6 +58,7 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error { // Fetch the block header corresponding to the checkpoint registration. wrapPeer := &peerConnection{handler: h, peer: peer} + header, err := wrapPeer.RetrieveSingleHeaderByNumber(ctx, peer.checkpointNumber) if err != nil { return err @@ -67,23 +68,29 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error { if err != nil { return err } + events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, peer.checkpoint.SectionIndex, peer.checkpoint.Hash()) if len(events) == 0 { return errInvalidCheckpoint } + var ( index = events[0].Index hash = events[0].CheckpointHash signatures [][]byte ) + for _, event := range events { signatures = append(signatures, append(event.R[:], append(event.S[:], event.V)...)) } + valid, signers := h.backend.oracle.VerifySigners(index, hash, signatures) if !valid { return errInvalidCheckpoint } + log.Warn("Verified advertised checkpoint", "peer", peer.id, "signers", len(signers)) + return nil } @@ -95,6 +102,7 @@ func (h *clientHandler) synchronise(peer *serverPeer) { } // Make sure the peer's TD is higher than our own. latest := h.backend.blockchain.CurrentHeader() + currentTd := rawdb.ReadTd(h.backend.chainDb, latest.Hash(), latest.Number.Uint64()) if currentTd != nil && peer.Td().Cmp(currentTd) < 0 { return @@ -116,6 +124,7 @@ func (h *clientHandler) synchronise(peer *serverPeer) { local bool checkpoint = &peer.checkpoint ) + if h.checkpoint != nil && h.checkpoint.SectionIndex >= peer.checkpoint.SectionIndex { local, checkpoint = true, h.checkpoint } @@ -136,15 +145,19 @@ func (h *clientHandler) synchronise(peer *serverPeer) { // 3. The checkpoint is local(replaced with local checkpoint) // 4. For some networks the checkpoint syncing is not activated. mode := checkpointSync + switch { case checkpoint.Empty(): mode = lightSync + log.Debug("Disable checkpoint syncing", "reason", "empty checkpoint") 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 local: mode = legacyCheckpointSync + log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded") case h.backend.oracle == nil || !h.backend.oracle.IsRunning(): if h.checkpoint == nil { @@ -153,6 +166,7 @@ func (h *clientHandler) synchronise(peer *serverPeer) { checkpoint = h.checkpoint mode = legacyCheckpointSync } + log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated") } @@ -164,16 +178,20 @@ func (h *clientHandler) synchronise(peer *serverPeer) { }() start := time.Now() + if mode == checkpointSync || mode == legacyCheckpointSync { // Validate the advertised checkpoint if mode == checkpointSync { if err := h.validateCheckpoint(peer); err != nil { log.Debug("Failed to validate checkpoint", "reason", err) h.removePeer(peer.id) + return } + h.backend.blockchain.AddTrustedCheckpoint(checkpoint) } + log.Debug("Checkpoint syncing start", "peer", peer.id, "checkpoint", checkpoint.SectionIndex) // Fetch the start point block header. @@ -185,9 +203,11 @@ func (h *clientHandler) synchronise(peer *serverPeer) { // of the latest epoch covered by checkpoint. ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() + if !checkpoint.Empty() && !h.backend.blockchain.SyncCheckpoint(ctx, checkpoint) { log.Debug("Sync checkpoint failed") h.removePeer(peer.id) + return } } @@ -200,5 +220,6 @@ func (h *clientHandler) synchronise(peer *serverPeer) { log.Debug("Synchronise failed", "reason", err) return } + log.Debug("Synchronise finished", "elapsed", common.PrettyDuration(time.Since(start))) } diff --git a/les/sync_test.go b/les/sync_test.go index 3fc2a9c154..03173cda09 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -48,9 +48,11 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { for { cs, _, _ := cIndexer.Sections() bts, _, _ := btIndexer.Sections() + if cs >= 1 && bts >= 1 { break } + time.Sleep(10 * time.Millisecond) } } @@ -61,6 +63,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { indexFn: waitIndexers, nopruning: true, } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -70,6 +73,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { if syncMode == 1 || syncMode == 2 { // Assemble checkpoint 0 s, _, head := server.chtIndexer.Sections() + cp := ¶ms.TrustedCheckpoint{ SectionIndex: 0, SectionHead: head, @@ -88,9 +92,11 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { 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)) + 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() // Wait for the checkpoint registration @@ -100,8 +106,10 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { time.Sleep(10 * time.Millisecond) continue } + break } + expected += 1 } } @@ -120,6 +128,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { if err != nil { t.Fatalf("Failed to connect testing peers %v", err) } + defer peer1.close() defer peer2.close() @@ -128,6 +137,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { if err != nil { t.Error("sync failed", err) } + return case <-time.NewTimer(10 * time.Second).C: t.Error("checkpoint syncing timeout") @@ -144,9 +154,11 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol int) { for { cs, _, _ := cIndexer.Sections() bts, _, _ := btIndexer.Sections() + if cs >= 1 && bts >= 1 { break } + time.Sleep(10 * time.Millisecond) } } @@ -157,6 +169,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol int) { indexFn: waitIndexers, nopruning: true, } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -176,9 +189,11 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol int) { 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)) + 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() // Wait for the checkpoint registration @@ -188,8 +203,10 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol int) { time.Sleep(100 * time.Millisecond) continue } + break } + expected += 1 // Explicitly set the oracle as nil. In normal use case it can happen @@ -226,6 +243,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol int) { if err != nil { t.Error("sync failed", err) } + return case <-time.NewTimer(10 * time.Second).C: t.Error("checkpoint syncing timeout") @@ -241,9 +259,11 @@ func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) { for { cs, _, _ := cIndexer.Sections() bts, _, _ := btIndexer.Sections() + if cs >= 2 && bts >= 2 { break } + time.Sleep(10 * time.Millisecond) } } @@ -254,6 +274,7 @@ func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) { indexFn: waitIndexers, nopruning: true, } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -276,6 +297,7 @@ func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) { expectStart = config.ChtSize - 1 expectEnd = 2*config.ChtSize + config.ChtConfirms ) + client.handler.syncStart = func(header *types.Header) { if header.Number.Uint64() == expectStart { start <- nil @@ -300,6 +322,7 @@ func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) { if err != nil { t.Error("sync failed", err) } + return case <-time.NewTimer(10 * time.Second).C: t.Error("checkpoint syncing timeout") @@ -310,6 +333,7 @@ func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) { if err != nil { t.Error("sync failed", err) } + return case <-time.NewTimer(10 * time.Second).C: t.Error("checkpoint syncing timeout") @@ -325,9 +349,11 @@ func testSyncAll(t *testing.T, protocol int) { for { cs, _, _ := cIndexer.Sections() bts, _, _ := btIndexer.Sections() + if cs >= 2 && bts >= 2 { break } + time.Sleep(10 * time.Millisecond) } } @@ -338,6 +364,7 @@ func testSyncAll(t *testing.T, protocol int) { indexFn: waitIndexers, nopruning: true, } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() @@ -349,6 +376,7 @@ func testSyncAll(t *testing.T, protocol int) { expectStart = uint64(0) expectEnd = 2*config.ChtSize + config.ChtConfirms ) + client.handler.syncStart = func(header *types.Header) { if header.Number.Uint64() == expectStart { start <- nil @@ -373,6 +401,7 @@ func testSyncAll(t *testing.T, protocol int) { if err != nil { t.Error("sync failed", err) } + return case <-time.NewTimer(10 * time.Second).C: t.Error("checkpoint syncing timeout") @@ -383,6 +412,7 @@ func testSyncAll(t *testing.T, protocol int) { 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 206ed59c30..30dcae2dfd 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -112,13 +112,13 @@ func prepare(n int, backend *backends.SimulatedBackend) { ctx = context.Background() signer = types.HomesteadSigner{} ) + 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)) oracleAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) @@ -131,7 +131,6 @@ func prepare(n int, backend *backends.SimulatedBackend) { // Builtin-block // number: 2 // txs: 4 - bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1) @@ -146,6 +145,7 @@ func prepare(n int, backend *backends.SimulatedBackend) { // user1 deploys a test contract tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(params.InitialBaseFee), testContractCode), signer, userKey1) backend.SendTransaction(ctx, tx3) + testContractAddr = crypto.CreateAddress(userAddr1, userNonce1+1) // user1 deploys a event contract @@ -155,7 +155,6 @@ func prepare(n int, backend *backends.SimulatedBackend) { // 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, big.NewInt(params.InitialBaseFee), nil), signer, bankKey) @@ -169,13 +168,13 @@ func prepare(n int, backend *backends.SimulatedBackend) { // Builtin-block // number: 4 // txs: 1 - // invoke test contract bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") tx, _ := types.SignTx(types.NewTransaction(bankNonce, testContractAddr, big.NewInt(0), 100000, big.NewInt(params.InitialBaseFee), data), signer, bankKey) backend.SendTransaction(ctx, tx) } + backend.Commit() } } @@ -188,6 +187,7 @@ func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.Indexer 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]) + return indexers[:] } @@ -203,8 +203,10 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index } oracle *checkpointoracle.CheckpointOracle ) + genesis := gspec.MustCommit(db) chain, _ := light.NewLightChain(odr, gspec.Config, engine, nil, nil) + if indexers != nil { checkpointConfig := ¶ms.CheckpointOracleConfig{ Address: crypto.CreateAddress(bankAddr, 0), @@ -214,6 +216,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index getLocal := func(index uint64) params.TrustedCheckpoint { chtIndexer := indexers[0] sectionHead := chtIndexer.SectionHead(index) + return params.TrustedCheckpoint{ SectionIndex: index, SectionHead: sectionHead, @@ -223,6 +226,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index } oracle = checkpointoracle.New(checkpointConfig, getLocal) } + client := &LightEthereum{ lesCommons: lesCommons{ genesis: genesis.Hash(), @@ -248,7 +252,9 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index if client.oracle != nil { client.oracle.Start(backend) } + client.handler.start() + return client.handler, func() { client.handler.stop() } @@ -264,6 +270,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da } oracle *checkpointoracle.CheckpointOracle ) + genesis := gspec.MustCommit(db) // create a simulation backend and pre-commit several customized block to the database. @@ -273,6 +280,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da txpoolConfig := txpool.DefaultConfig txpoolConfig.Journal = "" txpool := txpool.NewTxPool(txpoolConfig, gspec.Config, simulation.Blockchain()) + if indexers != nil { checkpointConfig := ¶ms.CheckpointOracleConfig{ Address: crypto.CreateAddress(bankAddr, 0), @@ -282,6 +290,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da getLocal := func(index uint64) params.TrustedCheckpoint { chtIndexer := indexers[0] sectionHead := chtIndexer.SectionHead(index) + return params.TrustedCheckpoint{ SectionIndex: index, SectionHead: sectionHead, @@ -291,6 +300,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da } oracle = checkpointoracle.New(checkpointConfig, getLocal) } + server := &LesServer{ lesCommons: lesCommons{ genesis: genesis.Hash(), @@ -315,13 +325,17 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da server.clientPool = vfs.NewClientPool(db, testBufRecharge, defaultConnectedBias, clock, alwaysTrueFn) server.clientPool.Start() server.clientPool.SetLimits(10000, 10000) // Assign enough capacity for clientpool + server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true }) if server.oracle != nil { server.oracle.Start(simulation) } + server.servingQueue.setThreads(4) server.handler.start() + closer := func() { server.Stop() } + return server.handler, simulation, closer } @@ -344,6 +358,7 @@ func (p *testPeer) handshakeWithServer(t *testing.T, td *big.Int, head common.Ha 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)) @@ -351,12 +366,15 @@ func (p *testPeer) handshakeWithServer(t *testing.T, td *big.Int, head common.Ha 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) } @@ -368,6 +386,7 @@ func (p *testPeer) handshakeWithClient(t *testing.T, td *big.Int, head common.Ha if p.speer == nil { t.Fatal("handshake for server peer only") } + var sendList keyValueList sendList = sendList.add("protocolVersion", uint64(p.speer.version)) sendList = sendList.add("networkId", uint64(NetworkId)) @@ -383,13 +402,16 @@ func (p *testPeer) handshakeWithClient(t *testing.T, td *big.Int, head common.Ha 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) } @@ -407,6 +429,7 @@ func newTestPeerPair(name string, version int, server *serverHandler, client *cl // Generate a random id and create the peer var id enode.ID + rand.Read(id[:]) peer1 := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) @@ -415,6 +438,7 @@ func newTestPeerPair(name string, version int, server *serverHandler, client *cl // Start the peer on a new thread errc1 := make(chan error, 1) errc2 := make(chan error, 1) + go func() { select { case <-server.closeCh: @@ -438,11 +462,14 @@ func newTestPeerPair(name string, version int, server *serverHandler, client *cl return nil, nil, fmt.Errorf("failed to establish protocol connection %v", err) default: } + if atomic.LoadUint32(&peer1.serving) == 1 && atomic.LoadUint32(&peer2.serving) == 1 { break } + time.Sleep(50 * time.Millisecond) } + return &testPeer{cpeer: peer1, net: net, app: app}, &testPeer{speer: peer2, net: app, app: net}, nil } @@ -467,11 +494,13 @@ func (client *testClient) newRawPeer(t *testing.T, name string, version int, rec // 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: @@ -479,11 +508,13 @@ func (client *testClient) newRawPeer(t *testing.T, name string, version int, rec case errCh <- client.handler.handle(peer, false): } }() + tp := &testPeer{ app: app, net: net, speer: peer, } + var ( genesis = client.handler.backend.blockchain.Genesis() head = client.handler.backend.blockchain.CurrentHeader() @@ -500,15 +531,19 @@ func (client *testClient) newRawPeer(t *testing.T, name string, version int, rec 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 } @@ -532,11 +567,13 @@ func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*t // 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) // Start the peer on a new thread errCh := make(chan error, 1) + go func() { select { case <-server.handler.closeCh: @@ -544,11 +581,13 @@ func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*t 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() @@ -565,15 +604,19 @@ func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*t return nil, nil, nil default: } + if atomic.LoadUint32(&peer.serving) == 1 { break } + time.Sleep(50 * time.Millisecond) } + closePeer := func() { tp.cpeer.close() tp.close() } + return tp, closePeer, errCh } @@ -595,10 +638,12 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC cdb = rawdb.NewMemoryDatabase() speers = newServerPeerSet() ) + var clock mclock.Clock = &mclock.System{} 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, speers, rm) @@ -621,13 +666,16 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC if config.indexFn != nil { config.indexFn(scIndexer, sbIndexer, sbtIndexer) } + var ( err error speer, cpeer *testPeer ) + if config.connect { done := make(chan struct{}) client.syncEnd = func(_ *types.Header) { close(done) } + cpeer, speer, err = newTestPeerPair("peer", config.protocol, server, client, false) if err != nil { t.Fatalf("Failed to connect testing peers %v", err) @@ -638,6 +686,7 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC t.Fatal("test peer did not connect and sync within 3s") } } + s := &testServer{ clock: clock, backend: b, @@ -664,6 +713,7 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC cpeer.cpeer.close() speer.speer.close() } + ccIndexer.Close() cbIndexer.Close() scIndexer.Close() @@ -673,6 +723,7 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC b.Close() clientClose() } + return s, c, teardown } diff --git a/les/txrelay.go b/les/txrelay.go index 40a51fb76f..64e6dc0a52 100644 --- a/les/txrelay.go +++ b/les/txrelay.go @@ -45,6 +45,7 @@ func newLesTxRelay(ps *serverPeerSet, retriever *retrieveManager) *lesTxRelay { stop: make(chan struct{}), } ps.subscribe(r) + return r } @@ -60,6 +61,7 @@ func (ltrx *lesTxRelay) registerPeer(p *serverPeer) { if p.onlyAnnounce { return } + ltrx.peerList = append(ltrx.peerList, p) } @@ -87,25 +89,31 @@ func (ltrx *lesTxRelay) send(txs types.Transactions, count int) { for _, tx := range txs { hash := tx.Hash() + _, ok := ltrx.txSent[hash] if !ok { ltrx.txSent[hash] = tx ltrx.txPending[hash] = struct{}{} } + if len(ltrx.peerList) > 0 { cnt := count pos := ltrx.peerStartPos + for { peer := ltrx.peerList[pos] sendTo[peer] = append(sendTo[peer], tx) + cnt-- if cnt == 0 { break // sent it to the desired number of peers } + pos++ if pos == len(ltrx.peerList) { pos = 0 } + if pos == ltrx.peerStartPos { break // tried all available peers } @@ -134,6 +142,7 @@ func (ltrx *lesTxRelay) send(txs types.Transactions, count int) { return func() { peer.sendTxs(reqID, len(ll), enc) } }, } + go ltrx.retriever.retrieve(context.Background(), reqID, rq, func(p distPeer, msg *Msg) error { return nil }, ltrx.stop) } } @@ -160,10 +169,12 @@ func (ltrx *lesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback if len(ltrx.txPending) > 0 { txs := make(types.Transactions, len(ltrx.txPending)) i := 0 + for hash := range ltrx.txPending { txs[i] = ltrx.txSent[hash] i++ } + ltrx.send(txs, 1) } } diff --git a/les/ulc.go b/les/ulc.go index b97217e796..6469789664 100644 --- a/les/ulc.go +++ b/les/ulc.go @@ -31,17 +31,21 @@ type ulc struct { // newULC creates and returns an ultra light client instance. func newULC(servers []string, fraction int) (*ulc, error) { keys := make(map[string]bool) + for _, id := range servers { node, err := enode.Parse(enode.ValidSchemes, id) if err != nil { log.Warn("Failed to parse trusted server", "id", id, "err", err) continue } + keys[node.ID().String()] = true } + if len(keys) == 0 { return nil, errors.New("no trusted servers") } + return &ulc{ keys: keys, fraction: fraction, diff --git a/les/ulc_test.go b/les/ulc_test.go index c5923d6f8b..9463bd683f 100644 --- a/les/ulc_test.go +++ b/les/ulc_test.go @@ -63,6 +63,7 @@ func testULCAnnounceThreshold(t *testing.T, protocol int) { {[]int{3, 2, 1}, 67, 1}, {[]int{3, 2, 1}, 100, 1}, } + for _, testcase := range cases { var ( servers []*testServer @@ -70,6 +71,7 @@ func testULCAnnounceThreshold(t *testing.T, protocol int) { nodes []*enode.Node ids []string ) + for i := 0; i < len(testcase.height); i++ { s, n, teardown := newTestServerPeer(t, 0, protocol, nil) @@ -78,18 +80,21 @@ func testULCAnnounceThreshold(t *testing.T, protocol int) { teardowns = append(teardowns, teardown) ids = append(ids, n.String()) } + c, teardown := newTestLightPeer(t, protocol, ids, testcase.threshold) // Connect all servers. for i := 0; i < len(servers); i++ { connect(servers[i].handler, nodes[i].ID(), c.handler, protocol, false) } + for i := 0; i < len(servers); i++ { for j := 0; j < testcase.height[i]; j++ { servers[i].backend.Commit() } } time.Sleep(1500 * time.Millisecond) // Ensure the fetcher has done its work. + head := c.handler.backend.blockchain.CurrentHeader().Number.Uint64() if head != testcase.expect { t.Fatalf("chain height mismatch, want %d, got %d", testcase.expect, head) @@ -97,6 +102,7 @@ func testULCAnnounceThreshold(t *testing.T, protocol int) { // Release all servers and client resources. teardown() + for i := 0; i < len(teardowns); i++ { teardowns[i]() } @@ -108,6 +114,7 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr app, net := p2p.MsgPipe() var id enode.ID + rand.Read(id[:]) peer1 := newServerPeer(protocol, NetworkId, true, p2p.NewPeer(serverId, "", nil), net) // Mark server as trusted @@ -116,6 +123,7 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr // Start the peerLight on a new thread errc1 := make(chan error, 1) errc2 := make(chan error, 1) + go func() { select { case <-server.closeCh: @@ -139,11 +147,14 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr return nil, nil, fmt.Errorf("failed to establish protocol connection %v", err) default: } + if atomic.LoadUint32(&peer1.serving) == 1 && atomic.LoadUint32(&peer2.serving) == 1 { break } + time.Sleep(50 * time.Millisecond) } + return peer1, peer2, nil } @@ -158,11 +169,14 @@ func newTestServerPeer(t *testing.T, blocks int, protocol int, indexFn indexerCa nopruning: true, } s, _, teardown := newClientServerEnv(t, netconfig) + key, err := crypto.GenerateKey() if err != nil { t.Fatal("generate key err:", err) } + s.handler.server.privateKey = key n := enode.NewV4(&key.PublicKey, net.ParseIP("127.0.0.1"), 35000, 35000) + return s, n, teardown } diff --git a/les/utils/exec_queue.go b/les/utils/exec_queue.go index 5942b06ec0..c10a0d4b67 100644 --- a/les/utils/exec_queue.go +++ b/les/utils/exec_queue.go @@ -31,7 +31,9 @@ type ExecQueue struct { func NewExecQueue(capacity int) *ExecQueue { q := &ExecQueue{funcs: make([]func(), 0, capacity)} q.cond = sync.NewCond(&q.mu) + go q.loop() + return q } @@ -49,14 +51,17 @@ func (q *ExecQueue) waitNext(drop bool) (f func()) { // dequeuing so len(q.funcs) includes the function that is running. q.funcs = append(q.funcs[:0], q.funcs[1:]...) } + for !q.isClosed() { if len(q.funcs) > 0 { f = q.funcs[0] break } + q.cond.Wait() } q.mu.Unlock() + return f } @@ -69,18 +74,21 @@ func (q *ExecQueue) CanQueue() bool { q.mu.Lock() ok := !q.isClosed() && len(q.funcs) < cap(q.funcs) q.mu.Unlock() + return ok } // Queue adds a function call to the execution Queue. Returns true if successful. func (q *ExecQueue) Queue(f func()) bool { q.mu.Lock() + ok := !q.isClosed() && len(q.funcs) < cap(q.funcs) if ok { q.funcs = append(q.funcs, f) q.cond.Signal() } q.mu.Unlock() + return ok } diff --git a/les/utils/exec_queue_test.go b/les/utils/exec_queue_test.go index 98601c4486..458099aa74 100644 --- a/les/utils/exec_queue_test.go +++ b/les/utils/exec_queue_test.go @@ -26,6 +26,7 @@ func TestExecQueue(t *testing.T) { execd = make(chan int) testexit = make(chan struct{}) ) + defer q.Quit() defer close(testexit) @@ -38,9 +39,11 @@ func TestExecQueue(t *testing.T) { case <-testexit: } } + if q.CanQueue() != wantOK { t.Fatalf("CanQueue() == %t for %s", !wantOK, state) } + if q.Queue(qf) != wantOK { t.Fatalf("Queue() == %t for %s", !wantOK, state) } @@ -50,6 +53,7 @@ func TestExecQueue(t *testing.T) { check("queue below cap", true) } check("full queue", false) + for i := 0; i < N; i++ { if c := <-execd; c != i { t.Fatal("execution out of order") diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go index 099b61d053..50f09db9ce 100644 --- a/les/utils/expiredvalue.go +++ b/les/utils/expiredvalue.go @@ -77,14 +77,17 @@ func (e ExpiredValue) Value(logOffset Fixed64) uint64 { func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { integer, frac := logOffset.ToUint64(), logOffset.Fraction() factor := frac.Pow2() + base := factor * float64(amount) if integer < e.Exp { base /= math.Pow(2, float64(e.Exp-integer)) } + if integer > e.Exp { e.Base >>= (integer - e.Exp) e.Exp = integer } + if base >= 0 || uint64(-base) <= e.Base { // The conversion from negative float64 to // uint64 is undefined in golang, and doesn't @@ -95,10 +98,13 @@ func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { } else { e.Base -= uint64(-base) } + return amount } + net := int64(-float64(e.Base) / factor) e.Base = 0 + return net } @@ -107,10 +113,12 @@ func (e *ExpiredValue) AddExp(a ExpiredValue) { if e.Exp > a.Exp { a.Base >>= (e.Exp - a.Exp) } + if e.Exp < a.Exp { e.Base >>= (a.Exp - e.Exp) e.Exp = a.Exp } + e.Base += a.Base } @@ -119,10 +127,12 @@ func (e *ExpiredValue) SubExp(a ExpiredValue) { if e.Exp > a.Exp { a.Base >>= (e.Exp - a.Exp) } + if e.Exp < a.Exp { e.Base >>= (a.Exp - e.Exp) e.Exp = a.Exp } + if e.Base > a.Base { e.Base -= a.Base } else { @@ -155,6 +165,7 @@ func (e LinearExpiredValue) Value(now mclock.AbsTime) uint64 { e.Val = 0 } } + return e.Val } @@ -169,13 +180,16 @@ func (e *LinearExpiredValue) Add(amount int64, now mclock.AbsTime) uint64 { } else { e.Val = 0 } + e.Offset = offset } + if amount < 0 && uint64(-amount) > e.Val { e.Val = 0 } else { e.Val = uint64(int64(e.Val) + amount) } + return e.Val } @@ -208,6 +222,7 @@ func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) { if dt > 0 { e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate) } + e.lastUpdate = now e.rate = rate } @@ -230,6 +245,7 @@ func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 { if dt <= 0 { return e.logOffset } + return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate) } diff --git a/les/utils/expiredvalue_test.go b/les/utils/expiredvalue_test.go index 1c751d8cc6..6240b6877c 100644 --- a/les/utils/expiredvalue_test.go +++ b/les/utils/expiredvalue_test.go @@ -34,6 +34,7 @@ func TestValueExpiration(t *testing.T) { {ExpiredValue{Base: 128, Exp: 2}, Uint64ToFixed64(2), 128}, {ExpiredValue{Base: 128, Exp: 2}, Uint64ToFixed64(3), 64}, } + for _, c := range cases { if got := c.input.Value(c.timeOffset); got != c.expect { t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) @@ -69,10 +70,12 @@ func TestValueAddition(t *testing.T) { {ExpiredValue{Base: 128, Exp: 2}, -128, Uint64ToFixed64(1), 128, -128}, {ExpiredValue{Base: 128, Exp: 2}, -128, Uint64ToFixed64(2), 0, -128}, } + for _, c := range cases { if net := c.input.Add(c.addend, c.timeOffset); net != c.expectNet { t.Fatalf("Net amount mismatch, want=%d, got=%d", c.expectNet, net) } + if got := c.input.Value(c.timeOffset); got != c.expect { t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) } @@ -91,8 +94,10 @@ func TestExpiredValueAddition(t *testing.T) { {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 1}, Uint64ToFixed64(0), 384}, {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 128}, } + for _, c := range cases { c.input.AddExp(c.another) + if got := c.input.Value(c.timeOffset); got != c.expect { t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) } @@ -111,8 +116,10 @@ func TestExpiredValueSubtraction(t *testing.T) { {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 128}, {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 64}, } + for _, c := range cases { c.input.SubExp(c.another) + if got := c.input.Value(c.timeOffset); got != c.expect { t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) } @@ -149,6 +156,7 @@ func TestLinearExpiredValue(t *testing.T) { Rate: mclock.AbsTime(1), }, mclock.AbsTime(3), 0}, } + for _, c := range cases { if value := c.value.Value(c.now); value != c.expect { t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, value) @@ -187,6 +195,7 @@ func TestLinearExpiredAddition(t *testing.T) { Rate: mclock.AbsTime(1), }, -2, mclock.AbsTime(2), 0}, } + for _, c := range cases { if value := c.value.Add(c.amount, c.now); value != c.expect { t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, value) diff --git a/les/utils/limiter.go b/les/utils/limiter.go index 84d186efd6..e2ab4daacc 100644 --- a/les/utils/limiter.go +++ b/les/utils/limiter.go @@ -77,6 +77,7 @@ 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) @@ -92,6 +93,7 @@ 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)) @@ -110,14 +112,17 @@ func (ag *addressGroup) remove(nq *nodeQueue) { 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) } @@ -136,7 +141,9 @@ func NewLimiter(sumCostLimit uint) *Limiter { sumCostLimit: sumCostLimit, } l.cond = sync.NewCond(&l.lock) + go l.processLoop() + return l } @@ -147,25 +154,33 @@ func (l *Limiter) selectionWeights(reqCost uint, value float64) (flatWeight, val 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 } @@ -183,14 +198,17 @@ func (l *Limiter) Add(id enode.ID, address string, value float64, reqCost uint) 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) @@ -201,6 +219,7 @@ func (l *Limiter) Add(id enode.ID, address string, value float64, reqCost uint) nq.penaltyCost += reqCost l.update(nq) close(process) + return process } } else { @@ -212,19 +231,24 @@ func (l *Limiter) Add(id enode.ID, address string, value float64, reqCost uint) 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 } @@ -236,10 +260,12 @@ func (l *Limiter) update(nq *nodeQueue) { } 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) } @@ -248,11 +274,13 @@ func (l *Limiter) update(nq *nodeQueue) { // 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) } @@ -261,9 +289,11 @@ func (l *Limiter) addToGroup(nq *nodeQueue, address string) { 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) } @@ -271,9 +301,11 @@ func (l *Limiter) removeFromGroup(nq *nodeQueue) { // selector func (l *Limiter) remove(nq *nodeQueue) { l.removeFromGroup(nq) + if nq.valueWeight != 0 { l.valueSelect.Remove(nq) } + delete(l.nodes, nq.id) } @@ -285,8 +317,10 @@ func (l *Limiter) choose() *nodeQueue { return ag.choose() } } + nq, _ := l.valueSelect.Choose().(*nodeQueue) l.selectAddressNext = true + return nq } @@ -302,19 +336,23 @@ func (l *Limiter) processLoop() { 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 @@ -368,23 +406,29 @@ func (l *Limiter) dropRequests() { 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) @@ -398,6 +442,7 @@ func (l *Limiter) dropRequests() { 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 index c031b21de5..b60870d12a 100644 --- a/les/utils/limiter_test.go +++ b/les/utils/limiter_test.go @@ -58,21 +58,26 @@ func (lt *limTest) request(n *ltNode) { 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} }() @@ -83,8 +88,10 @@ func (lt *limTest) moreRequests(n *ltNode) { if maxStart != 0 { n.lastTotalCost = lt.totalCost } + for n.reqMax > n.runCount && maxStart > 0 { lt.request(n) + maxStart-- } } @@ -92,12 +99,14 @@ func (lt *limTest) moreRequests(n *ltNode) { 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 { @@ -160,41 +169,53 @@ func TestLimiter(t *testing.T) { 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 } @@ -202,5 +223,6 @@ func TestLimiter(t *testing.T) { iterCount *= 2 } } + lt.limiter.Stop() } diff --git a/les/utils/timeutils.go b/les/utils/timeutils.go index 62a4285d15..076b43456f 100644 --- a/les/utils/timeutils.go +++ b/les/utils/timeutils.go @@ -39,6 +39,7 @@ func NewUpdateTimer(clock mclock.Clock, threshold time.Duration) *UpdateTimer { if clock == nil { clock = mclock.System{} } + return &UpdateTimer{ clock: clock, last: clock.Now(), @@ -58,12 +59,15 @@ func (t *UpdateTimer) UpdateAt(at mclock.AbsTime, callback func(diff time.Durati if diff < 0 { diff = 0 } + if diff < t.threshold { return false } + if callback(diff) { t.last = at return true } + return false } diff --git a/les/utils/timeutils_test.go b/les/utils/timeutils_test.go index 431b651435..128e3e150e 100644 --- a/les/utils/timeutils_test.go +++ b/les/utils/timeutils_test.go @@ -28,12 +28,16 @@ func TestUpdateTimer(t *testing.T) { if timer != nil { t.Fatalf("Create update timer with negative threshold") } + sim := &mclock.Simulated{} + timer = NewUpdateTimer(sim, time.Second) if updated := timer.Update(func(diff time.Duration) bool { return true }); updated { t.Fatalf("Update the clock without reaching the threshold") } + sim.Run(time.Second) + if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated { t.Fatalf("Doesn't update the clock when reaching the threshold") } @@ -41,6 +45,7 @@ func TestUpdateTimer(t *testing.T) { if updated := timer.UpdateAt(sim.Now().Add(time.Second), func(diff time.Duration) bool { return true }); !updated { t.Fatalf("Doesn't update the clock when reaching the threshold") } + timer = NewUpdateTimer(sim, 0) if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated { t.Fatalf("Doesn't update the clock without threshold limitaion") diff --git a/les/utils/weighted_select.go b/les/utils/weighted_select.go index 486b00820a..1f747ef566 100644 --- a/les/utils/weighted_select.go +++ b/les/utils/weighted_select.go @@ -60,14 +60,17 @@ func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { 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.sumCost { log.Error("WeightedRandomSelect overflow", "sumCost", w.root.sumCost, "new weight", weight) weight = math.MaxInt64 - w.root.sumCost } } + idx, ok := w.idx[item] if ok { w.root.setWeight(idx, weight) + if weight == 0 { delete(w.idx, item) } @@ -80,6 +83,7 @@ func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { newRoot.weights[0] = w.root.sumCost w.root = newRoot } + w.idx[item] = w.root.insert(item, weight) } } @@ -94,12 +98,15 @@ func (w *WeightedRandomSelect) Choose() WrsItem { if w.root.sumCost == 0 { return nil } + val := uint64(rand.Int63n(int64(w.root.sumCost))) choice, lastWeight := w.root.choose(val) weight := w.wfn(choice) + if weight != lastWeight { w.setWeight(choice, weight) } + if weight >= lastWeight || uint64(rand.Int63n(int64(lastWeight))) < weight { return choice } @@ -125,13 +132,16 @@ func (n *wrsNode) insert(item WrsItem, weight uint64) int { panic(nil) } } + n.itemCnt++ n.sumCost += weight n.weights[branch] += weight + if n.level == 0 { n.items[branch] = item return branch } + var subNode *wrsNode if n.items[branch] == nil { subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1} @@ -139,7 +149,9 @@ func (n *wrsNode) insert(item WrsItem, weight uint64) int { } else { subNode = n.items[branch].(*wrsNode) } + subIdx := subNode.insert(item, weight) + return subNode.maxItems*branch + subIdx } @@ -150,21 +162,26 @@ func (n *wrsNode) setWeight(idx int, weight uint64) uint64 { oldWeight := n.weights[idx] n.weights[idx] = weight diff := weight - oldWeight + n.sumCost += diff if weight == 0 { n.items[idx] = nil n.itemCnt-- } + return diff } + branchItems := n.maxItems / wrsBranches branch := idx / branchItems diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight) n.weights[branch] += diff n.sumCost += diff + if weight == 0 { n.itemCnt-- } + return diff } @@ -175,9 +192,12 @@ func (n *wrsNode) choose(val uint64) (WrsItem, uint64) { if n.level == 0 { return n.items[i].(WrsItem), n.weights[i] } + return n.items[i].(*wrsNode).choose(val) } + val -= w } + panic(nil) } diff --git a/les/utils/weighted_select_test.go b/les/utils/weighted_select_test.go index 3e1c0ad987..e02eaca214 100644 --- a/les/utils/weighted_select_test.go +++ b/les/utils/weighted_select_test.go @@ -29,9 +29,11 @@ type testWrsItem struct { func testWeight(i interface{}) uint64 { t := i.(*testWrsItem) w := *t.widx + if w == -1 || w == t.idx { return uint64(t.idx + 1) } + return 0 } @@ -40,11 +42,14 @@ func TestWeightedRandomSelect(t *testing.T) { s := NewWeightedRandomSelect(testWeight) w := -1 list := make([]testWrsItem, cnt) + for i := range list { list[i] = testWrsItem{idx: i, widx: &w} s.Update(&list[i]) } + w = rand.Intn(cnt) + c := s.Choose() if c == nil { t.Errorf("expected item, got nil") @@ -53,7 +58,9 @@ func TestWeightedRandomSelect(t *testing.T) { t.Errorf("expected another item") } } + w = -2 + if s.Choose() != nil { t.Errorf("expected nil, got item") } diff --git a/les/vflux/client/api.go b/les/vflux/client/api.go index 135273ef96..fa836de93f 100644 --- a/les/vflux/client/api.go +++ b/les/vflux/client/api.go @@ -39,6 +39,7 @@ func parseNodeStr(nodeStr string) (enode.ID, error) { if id, err := enode.ParseID(nodeStr); err == nil { return id, nil } + if node, err := enode.Parse(enode.ValidSchemes, nodeStr); err == nil { return node.ID(), nil } else { @@ -63,9 +64,11 @@ func (api *PrivateClientAPI) Distribution(nodeStr string, normalized bool) (RtDi if !normalized { expFactor = utils.ExpFactor(api.vt.StatsExpirer().LogOffset(mclock.Now())) } + if nodeStr == "" { return api.vt.RtStats().Distribution(normalized, expFactor), nil } + if id, err := parseNodeStr(nodeStr); err == nil { return api.vt.GetNode(id).RtStats().Distribution(normalized, expFactor), nil } else { @@ -84,6 +87,7 @@ func (api *PrivateClientAPI) Timeout(nodeStr string, failRate float64) (float64, if nodeStr == "" { return float64(api.vt.RtStats().Timeout(failRate)) / float64(time.Second), nil } + if id, err := parseNodeStr(nodeStr); err == nil { return float64(api.vt.GetNode(id).RtStats().Timeout(failRate)) / float64(time.Second), nil } else { @@ -96,9 +100,11 @@ func (api *PrivateClientAPI) Timeout(nodeStr string, failRate float64) (float64, func (api *PrivateClientAPI) Value(nodeStr string, timeout float64) (float64, error) { wt := TimeoutWeights(time.Duration(timeout * float64(time.Second))) expFactor := utils.ExpFactor(api.vt.StatsExpirer().LogOffset(mclock.Now())) + if nodeStr == "" { return api.vt.RtStats().Value(wt, expFactor), nil } + if id, err := parseNodeStr(nodeStr); err == nil { return api.vt.GetNode(id).RtStats().Value(wt, expFactor), nil } else { diff --git a/les/vflux/client/fillset.go b/les/vflux/client/fillset.go index 0da850bcac..abd30fc730 100644 --- a/les/vflux/client/fillset.go +++ b/les/vflux/client/fillset.go @@ -52,9 +52,11 @@ func NewFillSet(ns *nodestate.NodeStateMachine, input enode.Iterator, flags node if oldState.Equals(flags) { fs.count-- } + if newState.Equals(flags) { fs.count++ } + if fs.target > fs.count { fs.cond.Signal() } @@ -62,6 +64,7 @@ func NewFillSet(ns *nodestate.NodeStateMachine, input enode.Iterator, flags node }) go fs.readLoop() + return fs } @@ -75,9 +78,11 @@ func (fs *FillSet) readLoop() { } fs.lock.Unlock() + if !fs.input.Next() { return } + fs.ns.SetState(fs.input.Node(), fs.flags, nodestate.Flags{}, 0) } } diff --git a/les/vflux/client/fillset_test.go b/les/vflux/client/fillset_test.go index 652dcf9f62..2b62254a7f 100644 --- a/les/vflux/client/fillset_test.go +++ b/les/vflux/client/fillset_test.go @@ -37,7 +37,9 @@ func (i *testIter) Next() bool { if _, ok := <-i.waitCh; !ok { return false } + i.node = <-i.nodeCh + return true } @@ -51,6 +53,7 @@ func (i *testIter) Close() { func (i *testIter) push() { var id enode.ID + rand.Read(id[:]) i.nodeCh <- enode.SignNull(new(enr.Record), id) } @@ -78,6 +81,7 @@ func TestFillSet(t *testing.T) { if !iter.waiting(time.Second * 10) { t.Fatalf("FillSet not waiting for new nodes") } + if push { iter.push() } diff --git a/les/vflux/client/queueiterator.go b/les/vflux/client/queueiterator.go index ad3f8df5bb..9d6e11c77f 100644 --- a/les/vflux/client/queueiterator.go +++ b/les/vflux/client/queueiterator.go @@ -50,6 +50,7 @@ func NewQueueIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState nodestate.Flags) { oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags) newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags) + if newMatch == oldMatch { return } @@ -65,12 +66,15 @@ func NewQueueIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags if qn.ID() == id { copy(qi.queue[i:len(qi.queue)-1], qi.queue[i+1:]) qi.queue = qi.queue[:len(qi.queue)-1] + break } } } + qi.cond.Signal() }) + return qi } @@ -81,16 +85,20 @@ func (qi *QueueIterator) Next() bool { if qi.waitCallback != nil { qi.waitCallback(true) } + for !qi.closed && len(qi.queue) == 0 { qi.cond.Wait() } + if qi.waitCallback != nil { qi.waitCallback(false) } } + if qi.closed { qi.nextNode = nil qi.lock.Unlock() + return false } // Move to the next node in queue. @@ -103,6 +111,7 @@ func (qi *QueueIterator) Next() bool { qi.queue = qi.queue[:len(qi.queue)-1] } qi.lock.Unlock() + return true } diff --git a/les/vflux/client/queueiterator_test.go b/les/vflux/client/queueiterator_test.go index 400d978e19..985dbddb52 100644 --- a/les/vflux/client/queueiterator_test.go +++ b/les/vflux/client/queueiterator_test.go @@ -42,9 +42,11 @@ func testQueueIterator(t *testing.T, fifo bool) { ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup) qi := NewQueueIterator(ns, sfTest2, sfTest3.Or(sfTest4), fifo, nil) ns.Start() + for i := 1; i <= iterTestNodeCount; i++ { ns.SetState(testNode(i), sfTest1, nodestate.Flags{}, 0) } + next := func() int { ch := make(chan struct{}) go func() { @@ -56,8 +58,10 @@ func testQueueIterator(t *testing.T, fifo bool) { case <-time.After(time.Second * 5): t.Fatalf("Iterator.Next() timeout") } + node := qi.Node() ns.SetState(node, sfTest4, nodestate.Flags{}, 0) + return testNodeIndex(node.ID()) } exp := func(i int) { diff --git a/les/vflux/client/requestbasket.go b/les/vflux/client/requestbasket.go index 55d4b165df..bcd1563acb 100644 --- a/les/vflux/client/requestbasket.go +++ b/les/vflux/client/requestbasket.go @@ -80,8 +80,10 @@ func (b *requestBasket) setExp(exp uint64) { item.value >>= shift b.items[i] = item } + b.exp = exp } + if exp < b.exp { shift := b.exp - exp for i, item := range b.items { @@ -89,6 +91,7 @@ func (b *requestBasket) setExp(exp uint64) { item.value <<= shift b.items[i] = item } + b.exp = exp } } @@ -124,18 +127,23 @@ func (s *serverBasket) transfer(ratio float64) requestBasket { items: make([]basketItem, len(s.basket.items)), exp: s.basket.exp, } + for i, v := range s.basket.items { ta := uint64(float64(v.amount) * ratio) tv := uint64(float64(v.value) * ratio) + if ta > v.amount { ta = v.amount } + if tv > v.value { tv = v.value } + s.basket.items[i] = basketItem{v.amount - ta, v.value - tv} res.items[i] = basketItem{ta, tv} } + return res } @@ -157,18 +165,22 @@ func (r *referenceBasket) add(newBasket requestBasket) { totalCost uint64 totalValue float64 ) + for i, v := range newBasket.items { totalCost += v.value totalValue += float64(v.amount) * r.reqValues[i] } + if totalCost > 0 { // add to reference with scaled values scaleValues := totalValue / float64(totalCost) + for i, v := range newBasket.items { r.basket.items[i].amount += v.amount r.basket.items[i].value += uint64(float64(v.value) * scaleValues) } } + r.updateReqValues() } @@ -192,6 +204,7 @@ func (r *referenceBasket) normalize() { sumAmount += b.amount sumValue += b.value } + add := float64(int64(sumAmount-sumValue)) / float64(sumValue) for i, b := range r.basket.items { b.value += uint64(int64(float64(b.value) * add)) @@ -206,13 +219,16 @@ func (r *referenceBasket) reqValueFactor(costList []uint64) float64 { totalCost float64 totalValue uint64 ) + for i, b := range r.basket.items { totalCost += float64(costList[i]) * float64(b.amount) // use floats to avoid overflow totalValue += b.value } + if totalCost < 1 { return 0 } + return float64(totalValue) * basketFactor / totalCost } @@ -226,10 +242,13 @@ func (b *basketItem) DecodeRLP(s *rlp.Stream) error { var item struct { Amount, Value uint64 } + if err := s.Decode(&item); err != nil { return err } + b.amount, b.value = item.Amount, item.Value + return nil } @@ -244,10 +263,13 @@ func (r *requestBasket) DecodeRLP(s *rlp.Stream) error { Items []basketItem Exp uint64 } + if err := s.Decode(&enc); err != nil { return err } + r.items, r.exp = enc.Items, enc.Exp + return nil } @@ -261,8 +283,11 @@ func (r requestBasket) convertMapping(oldMapping, newMapping []string, initBaske for i, name := range oldMapping { nameMap[name] = i } + rc := requestBasket{items: make([]basketItem, len(newMapping))} + var scale, oldScale, newScale float64 + for i, name := range newMapping { if ii, ok := nameMap[name]; ok { rc.items[i] = r.items[ii] @@ -270,16 +295,19 @@ func (r requestBasket) convertMapping(oldMapping, newMapping []string, initBaske newScale += float64(rc.items[i].amount) * float64(initBasket.items[i].amount) } } + if oldScale > 1e-10 { scale = newScale / oldScale } else { scale = 1 } + for i, name := range newMapping { if _, ok := nameMap[name]; !ok { rc.items[i].amount = uint64(float64(initBasket.items[i].amount) * scale) rc.items[i].value = uint64(float64(initBasket.items[i].value) * scale) } } + return rc } diff --git a/les/vflux/client/requestbasket_test.go b/les/vflux/client/requestbasket_test.go index 7c5f87c618..4c49652935 100644 --- a/les/vflux/client/requestbasket_test.go +++ b/les/vflux/client/requestbasket_test.go @@ -37,9 +37,11 @@ func checkF64(t *testing.T, name string, value, exp, tol float64) { func TestServerBasket(t *testing.T) { var s serverBasket + s.init(2) // add some requests with different request value factors s.updateRvFactor(1) + noexp := utils.ExpirationFactor{Factor: 1} s.add(0, 1000, 10000, noexp) s.add(1, 3000, 60000, noexp) @@ -83,11 +85,13 @@ func TestConvertMapping(t *testing.T) { func TestReqValueFactor(t *testing.T) { var ref referenceBasket + ref.basket = requestBasket{items: make([]basketItem, 4)} for i := range ref.basket.items { ref.basket.items[i].amount = uint64(i+1) * basketFactor ref.basket.items[i].value = uint64(i+1) * basketFactor } + ref.init(4) rvf := ref.reqValueFactor([]uint64{1000, 2000, 3000, 4000}) // expected value is (1000000+2000000+3000000+4000000) / (1*1000+2*2000+3*3000+4*4000) = 10000000/30000 = 333.333 @@ -99,6 +103,7 @@ func TestNormalize(t *testing.T) { // Initialize data for testing valueRange, lower := 1000000, 1000000 ref := referenceBasket{basket: requestBasket{items: make([]basketItem, 10)}} + for i := 0; i < 10; i++ { ref.basket.items[i].amount = uint64(rand.Intn(valueRange) + lower) ref.basket.items[i].value = uint64(rand.Intn(valueRange) + lower) @@ -111,6 +116,7 @@ func TestNormalize(t *testing.T) { sumAmount += ref.basket.items[i].amount sumValue += ref.basket.items[i].value } + var epsilon = 0.01 if float64(sumAmount)*(1+epsilon) < float64(sumValue) || float64(sumAmount)*(1-epsilon) > float64(sumValue) { t.Fatalf("Failed to normalize sumAmount: %d sumValue: %d", sumAmount, sumValue) @@ -120,24 +126,31 @@ func TestNormalize(t *testing.T) { func TestReqValueAdjustment(t *testing.T) { var s1, s2 serverBasket + s1.init(3) s2.init(3) + cost1 := []uint64{30000, 60000, 90000} cost2 := []uint64{100000, 200000, 300000} + var ref referenceBasket + ref.basket = requestBasket{items: make([]basketItem, 3)} for i := range ref.basket.items { ref.basket.items[i].amount = 123 * basketFactor ref.basket.items[i].value = 123 * basketFactor } + ref.init(3) // initial reqValues are expected to be {1, 1, 1} checkF64(t, "reqValues[0]", ref.reqValues[0], 1, 0.01) checkF64(t, "reqValues[1]", ref.reqValues[1], 1, 0.01) checkF64(t, "reqValues[2]", ref.reqValues[2], 1, 0.01) + var logOffset utils.Fixed64 for period := 0; period < 1000; period++ { exp := utils.ExpFactor(logOffset) + s1.updateRvFactor(ref.reqValueFactor(cost1)) s2.updateRvFactor(ref.reqValueFactor(cost2)) // throw in random requests into each basket using their internal pricing @@ -153,6 +166,7 @@ func TestReqValueAdjustment(t *testing.T) { ref.add(s2.transfer(0.1)) ref.normalize() ref.updateReqValues() + logOffset += utils.Float64ToFixed64(0.1) } checkF64(t, "reqValues[0]", ref.reqValues[0], 0.5, 0.01) diff --git a/les/vflux/client/serverpool.go b/les/vflux/client/serverpool.go index 271d6e0224..36ba87ef6a 100644 --- a/les/vflux/client/serverpool.go +++ b/les/vflux/client/serverpool.go @@ -205,6 +205,7 @@ func (s *serverPoolIterator) Next() bool { s.nextFn(s.dialIterator.Node()) return true } + return false } @@ -225,12 +226,15 @@ func (s *ServerPool) AddMetrics( s.suggestedTimeoutGauge = suggestedTimeoutGauge s.totalValueGauge = totalValueGauge s.sessionValueMeter = sessionValueMeter + if serverSelectableGauge != nil { s.ns.AddLogMetrics(sfHasValue, sfDialProcess, "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) } @@ -254,13 +258,17 @@ func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enod // remove query flag if the node is already somewhere in the dial process s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0) } + return } + fails := atomic.LoadUint32(&s.queryFails) + failMax := fails if failMax > maxQueryFails { failMax = maxQueryFails } + if rand.Intn(maxQueryFails*2) < int(failMax) { // skip pre-negotiation with increasing chance, max 50% // this ensures that the client can operate even if UDP is not working at all @@ -268,12 +276,15 @@ func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enod // set canDial before resetting queried so that FillSet will not read more // candidates unnecessarily s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0) + return } + go func() { q := query(n) if q == -1 { atomic.AddUint32(&s.queryFails, 1) + fails++ if fails%warnQueryFails == 0 { // warn if a large number of consecutive queries have failed @@ -282,6 +293,7 @@ func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enod } else { atomic.StoreUint32(&s.queryFails, 0) } + s.ns.Operation(func() { // we are no longer running in the operation that the callback belongs to, start a new one because of setRedialWait if q == 1 { @@ -289,10 +301,12 @@ func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enod } else { s.setRedialWait(n, queryCost, queryWaitStep) } + s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0) }) }() }) + return NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { if waiting { s.fillSet.SetTarget(preNegLimit) @@ -305,11 +319,13 @@ 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() { s.ns.Start() + for _, iter := range s.mixSources { // add sources to mixer at startup because the mixer instantly tries to read them // which should only happen after NodeStateMachine has been started s.mixer.AddSource(iter) } + for _, url := range s.trustedURLs { if node, err := enode.Parse(s.validSchemes, url); err == nil { s.ns.SetState(node, sfAlwaysConnect, nodestate.Flags{}, 0) @@ -317,18 +333,22 @@ func (s *ServerPool) Start() { log.Error("Invalid trusted server URL", "url", url, "error", err) } } + unixTime := s.unixTime() s.ns.Operation(func() { s.ns.ForEach(sfHasValue, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { s.calculateWeight(node) + if n, ok := s.ns.GetField(node, sfiNodeHistory).(nodeHistory); ok && n.redialWaitEnd > unixTime { wait := n.redialWaitEnd - unixTime lastWait := n.redialWaitEnd - n.redialWaitStart + if wait > lastWait { // if the time until expiration is larger than the last suggested // waiting time then the system clock was probably adjusted wait = lastWait } + s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, time.Duration(wait)*time.Second) } }) @@ -341,6 +361,7 @@ func (s *ServerPool) Stop() { if s.fillSet != nil { s.fillSet.Close() } + s.ns.Operation(func() { s.ns.ForEach(sfConnected, nodestate.Flags{}, func(n *enode.Node, state nodestate.Flags) { // recalculate weight of connected nodes in order to update hasValue flag if necessary @@ -356,14 +377,17 @@ func (s *ServerPool) RegisterNode(node *enode.Node) (*NodeValueTracker, error) { if atomic.LoadUint32(&s.started) == 0 { return nil, errors.New("server pool not started yet") } + nvt := s.vt.Register(node.ID()) 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 } @@ -385,6 +409,7 @@ func (s *ServerPool) recalTimeout() { s.timeoutLock.RLock() refreshed := s.timeoutRefreshed s.timeoutLock.RUnlock() + now := s.clock.Now() if refreshed != 0 && time.Duration(now-refreshed) < timeoutRefresh { return @@ -403,9 +428,11 @@ func (s *ServerPool) recalTimeout() { if t := rts.Timeout(0.1); t > timeout { timeout = t } + if t := rts.Timeout(0.5) * 2; t > timeout { timeout = t } + s.timeoutLock.Lock() if s.timeout != timeout { s.timeout = timeout @@ -414,10 +441,12 @@ func (s *ServerPool) recalTimeout() { 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() } @@ -427,6 +456,7 @@ func (s *ServerPool) GetTimeout() time.Duration { s.recalTimeout() s.timeoutLock.RLock() defer s.timeoutLock.RUnlock() + return s.timeout } @@ -436,6 +466,7 @@ func (s *ServerPool) getTimeoutAndWeight() (time.Duration, ResponseTimeWeights) s.recalTimeout() s.timeoutLock.RLock() defer s.timeoutLock.RUnlock() + return s.timeout, s.timeWeights } @@ -446,10 +477,12 @@ func (s *ServerPool) addDialCost(n *nodeHistory, amount int64) uint64 { if amount > 0 { n.dialCost.Add(amount, logOffset) } + totalDialCost := n.dialCost.Value(logOffset) if totalDialCost < dialCost { totalDialCost = dialCost } + return totalDialCost } @@ -459,19 +492,23 @@ func (s *ServerPool) serviceValue(node *enode.Node) (sessionValue, totalValue fl if nvt == nil { return 0, 0 } + currentStats := nvt.RtStats() _, timeWeights := s.getTimeoutAndWeight() expFactor := s.vt.StatsExpFactor() totalValue = currentStats.Value(timeWeights, expFactor) + if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(ResponseTimeStats); ok { diff := currentStats diff.SubStats(&connStats) + sessionValue = diff.Value(timeWeights, expFactor) if s.sessionValueMeter != nil { s.sessionValueMeter.Mark(int64(sessionValue)) } } + return } @@ -489,6 +526,7 @@ func (s *ServerPool) updateWeight(node *enode.Node, totalValue float64, totalDia s.ns.SetFieldSub(node, sfiNodeHistory, nil) s.ns.SetFieldSub(node, sfiLocalAddress, nil) } + s.ns.Persist(node) // saved if node history or hasValue changed } @@ -517,7 +555,9 @@ func (s *ServerPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep // steps because queries are cheaper and therefore we can allow more failed attempts. unixTime := s.unixTime() plannedTimeout := float64(n.redialWaitEnd - n.redialWaitStart) // last planned redialWait timeout - var actualWait float64 // actual waiting time elapsed + + var actualWait float64 // actual waiting time elapsed + if unixTime > n.redialWaitEnd { // the planned timeout has elapsed actualWait = plannedTimeout @@ -541,12 +581,15 @@ func (s *ServerPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep // connection (but never under the minimum) a := totalValue * dialCost * float64(minRedialWait) b := float64(totalDialCost) * sessionValue + if a < b*nextTimeout { nextTimeout = a / b } + if nextTimeout < minRedialWait { nextTimeout = minRedialWait } + wait := time.Duration(float64(time.Second) * nextTimeout) if wait < waitThreshold { n.redialWaitStart = unixTime @@ -596,6 +639,7 @@ func (s *ServerPool) DialNode(n *enode.Node) *enode.Node { n, _ := enode.New(dummyIdentity(n.ID()), enr) return n } + return n } diff --git a/les/vflux/client/serverpool_test.go b/les/vflux/client/serverpool_test.go index f1fd987d7e..6d023f1138 100644 --- a/les/vflux/client/serverpool_test.go +++ b/les/vflux/client/serverpool_test.go @@ -47,6 +47,7 @@ func testNodeIndex(id enode.ID) int { if id[0] != 42 { return -1 } + return int(id[1]) + int(id[2])*256 } @@ -85,6 +86,7 @@ func newServerPoolTest(preNeg, preNegFail bool) *ServerPoolTest { for i := range nodes { nodes[i] = enode.SignNull(&enr.Record{}, testNodeID(i)) } + return &ServerPoolTest{ clock: &mclock.Simulated{}, db: memorydb.New(), @@ -114,6 +116,7 @@ func (s *ServerPoolTest) addTrusted(i int) { func (s *ServerPoolTest) start() { var testQuery QueryFunc + s.queryWg = new(sync.WaitGroup) if s.preNeg { testQuery = func(node *enode.Node) int { @@ -122,11 +125,14 @@ func (s *ServerPoolTest) start() { s.preNegLock.Unlock() return 0 } + s.queryWg.Add(1) + idx := testNodeIndex(node.ID()) n := &s.testNodes[idx] canConnect := !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle s.preNegLock.Unlock() + defer s.queryWg.Done() if s.preNegFail { @@ -134,14 +140,17 @@ func (s *ServerPoolTest) start() { s.beginWait() s.clock.Sleep(time.Second * 5) s.endWait() + return -1 } + 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 @@ -151,11 +160,14 @@ func (s *ServerPoolTest) start() { if canConnect { return 1 } + s.beginWait() s.clock.Sleep(time.Second * 5) s.endWait() + return -1 } + return -1 } } @@ -172,8 +184,10 @@ func (s *ServerPoolTest) start() { s.disconnect = make(map[int][]int) s.sp.Start() s.quit = make(chan chan struct{}) + go func() { last := int32(-1) + for { select { case <-time.After(time.Millisecond * 100): @@ -182,6 +196,7 @@ func (s *ServerPoolTest) start() { // advance clock if test is stuck (might happen in rare cases) s.clock.Run(time.Second) } + last = c case quit := <-s.quit: close(quit) @@ -206,15 +221,18 @@ func (s *ServerPoolTest) stop() { s.preNegLock.Lock() s.stopping = false s.preNegLock.Unlock() + for i := range s.testNodes { n := &s.testNodes[i] if n.connected { n.totalConn += s.cycle } + n.connected = false n.node = nil n.nextConnCycle = 0 } + s.conn, s.servedConn = 0, 0 } @@ -228,15 +246,20 @@ func (s *ServerPoolTest) run() { s.preNegLock.Lock() n.connected = false s.preNegLock.Unlock() + n.node = nil s.conn-- + if n.service { s.servedConn-- } + n.nextConnCycle = s.cycle + n.waitCycles } + delete(s.disconnect, s.cycle) } + if s.conn < spTestTarget { s.dialCount++ s.beginWait() @@ -245,12 +268,14 @@ func (s *ServerPoolTest) run() { dial := s.spi.Node() id := dial.ID() idx := testNodeIndex(id) + n := &s.testNodes[idx] if !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle { s.conn++ if n.service { s.servedConn++ } + n.totalConn -= s.cycle s.preNegLock.Lock() n.connected = true @@ -259,11 +284,13 @@ func (s *ServerPoolTest) run() { s.disconnect[dc] = append(s.disconnect[dc], idx) n.node = dial nv, _ := s.sp.RegisterNode(n.node) + if n.service { nv.Served([]ServedRequest{{ReqType: 0, Amount: 100}}, 0) } } } + s.serviceCycles += s.servedConn s.clock.Run(time.Second) s.preNegLock.Lock() @@ -278,7 +305,9 @@ func (s *ServerPoolTest) setNodes(count, conn, wait int, service, trusted bool) for s.testNodes[idx].connectCycles != 0 || s.testNodes[idx].connected { idx = rand.Intn(spTestNodes) } + res = append(res, idx) + s.preNegLock.Lock() s.testNodes[idx] = spTestNode{ connectCycles: conn, @@ -286,10 +315,12 @@ func (s *ServerPoolTest) setNodes(count, conn, wait int, service, trusted bool) service: service, } s.preNegLock.Unlock() + if trusted { s.addTrusted(idx) } } + return } @@ -299,10 +330,12 @@ func (s *ServerPoolTest) resetNodes() { n.totalConn += s.cycle s.sp.UnregisterNode(n.node) } + s.preNegLock.Lock() s.testNodes[i] = spTestNode{totalConn: n.totalConn} s.preNegLock.Unlock() } + s.conn, s.servedConn = 0, 0 s.disconnect = make(map[int][]int) s.trusted = nil @@ -310,17 +343,21 @@ func (s *ServerPoolTest) resetNodes() { func (s *ServerPoolTest) checkNodes(t *testing.T, nodes []int) { var sum int + for _, idx := range nodes { n := &s.testNodes[idx] if n.connected { n.totalConn += s.cycle } + sum += n.totalConn + n.totalConn = 0 if n.connected { n.totalConn -= s.cycle } } + if sum < spMinTotal || sum > spMaxTotal { t.Errorf("Total connection amount %d outside expected range %d to %d", sum, spMinTotal, spMaxTotal) } @@ -348,6 +385,7 @@ func testServerPoolChangedNodes(t *testing.T, preNeg bool) { s.start() s.run() s.checkNodes(t, nodes) + for i := 0; i < 3; i++ { s.resetNodes() nodes := s.setNodes(100, 200, 200, true, false) diff --git a/les/vflux/client/timestats.go b/les/vflux/client/timestats.go index 7f1ffdbe26..d4df998e35 100644 --- a/les/vflux/client/timestats.go +++ b/les/vflux/client/timestats.go @@ -54,14 +54,17 @@ func TimeToStatScale(d time.Duration) float64 { if d < 0 { return 0 } + r := float64(d) / float64(minResponseTime) if r > 1 { r = math.Log(r) + 1 } + r *= timeStatsLogFactor if r > timeStatLength-1 { return timeStatLength - 1 } + return r } @@ -72,6 +75,7 @@ func StatScaleToTime(r float64) time.Duration { if r > 1 { r = math.Exp(r - 1) } + return time.Duration(r * float64(minResponseTime)) } @@ -89,6 +93,7 @@ func TimeoutWeights(timeout time.Duration) (res ResponseTimeWeights) { res[i] = -1 } } + return } @@ -98,6 +103,7 @@ func (rt *ResponseTimeStats) EncodeRLP(w io.Writer) error { Stats [timeStatLength]uint64 Exp uint64 }{rt.stats, rt.exp} + return rlp.Encode(w, &enc) } @@ -107,10 +113,13 @@ func (rt *ResponseTimeStats) DecodeRLP(s *rlp.Stream) error { Stats [timeStatLength]uint64 Exp uint64 } + if err := s.Decode(&enc); err != nil { return err } + rt.stats, rt.exp = enc.Stats, enc.Exp + return nil } @@ -122,6 +131,7 @@ func (rt *ResponseTimeStats) Add(respTime time.Duration, weight float64, expFact i := int(r) r -= float64(i) rt.stats[i] += uint64(weight * (1 - r)) + if i < timeStatLength-1 { rt.stats[i+1] += uint64(weight * r) } @@ -135,13 +145,16 @@ func (rt *ResponseTimeStats) setExp(exp uint64) { for i, v := range rt.stats { rt.stats[i] = v >> shift } + rt.exp = exp } + if exp < rt.exp { shift := rt.exp - exp for i, v := range rt.stats { rt.stats[i] = v << shift } + rt.exp = exp } } @@ -153,15 +166,18 @@ func (rt ResponseTimeStats) Value(weights ResponseTimeWeights, expFactor utils.E for i, s := range rt.stats { v += float64(s) * weights[i] } + if v < 0 { return 0 } + return expFactor.Value(v, rt.exp) / weightScaleFactor } // AddStats adds the given ResponseTimeStats to the current one. func (rt *ResponseTimeStats) AddStats(s *ResponseTimeStats) { rt.setExp(s.exp) + for i, v := range s.stats { rt.stats[i] += v } @@ -170,6 +186,7 @@ func (rt *ResponseTimeStats) AddStats(s *ResponseTimeStats) { // SubStats subtracts the given ResponseTimeStats from the current one. func (rt *ResponseTimeStats) SubStats(s *ResponseTimeStats) { rt.setExp(s.exp) + for i, v := range s.stats { if v < rt.stats[i] { rt.stats[i] -= v @@ -190,23 +207,29 @@ func (rt ResponseTimeStats) Timeout(failRatio float64) time.Duration { for _, v := range rt.stats { sum += v } + s := uint64(float64(sum) * failRatio) i := timeStatLength - 1 + for i > 0 && s >= rt.stats[i] { s -= rt.stats[i] i-- } + r := float64(i) + 0.5 if rt.stats[i] > 0 { r -= float64(s) / float64(rt.stats[i]) } + if r < 0 { r = 0 } + th := StatScaleToTime(r) if th > maxResponseTime { th = maxResponseTime } + return th } @@ -218,20 +241,24 @@ type RtDistribution [timeStatLength][2]float64 // Distribution returns a RtDistribution, optionally normalized to a sum of 1. func (rt ResponseTimeStats) Distribution(normalized bool, expFactor utils.ExpirationFactor) (res RtDistribution) { var mul float64 + if normalized { var sum uint64 for _, v := range rt.stats { sum += v } + if sum > 0 { mul = 1 / float64(sum) } } else { mul = expFactor.Value(float64(1)/weightScaleFactor, rt.exp) } + for i, v := range rt.stats { res[i][0] = float64(StatScaleToTime(float64(i))) / float64(time.Second) res[i][1] = float64(v) * mul } + return } diff --git a/les/vflux/client/timestats_test.go b/les/vflux/client/timestats_test.go index a28460171e..2f43380eaf 100644 --- a/les/vflux/client/timestats_test.go +++ b/les/vflux/client/timestats_test.go @@ -27,10 +27,12 @@ import ( func TestTransition(t *testing.T) { var epsilon = 0.01 + var cases = []time.Duration{ time.Millisecond, minResponseTime, time.Second, time.Second * 5, maxResponseTime, } + for _, c := range cases { got := StatScaleToTime(TimeToStatScale(c)) if float64(got)*(1+epsilon) < float64(c) || float64(got)*(1-epsilon) > float64(c) { @@ -48,6 +50,7 @@ var maxResponseWeights = TimeoutWeights(maxResponseTime) func TestValue(t *testing.T) { noexp := utils.ExpirationFactor{Factor: 1} + for i := 0; i < 1000; i++ { max := minResponseTime + time.Duration(rand.Int63n(int64(maxResponseTime-minResponseTime))) min := minResponseTime + time.Duration(rand.Int63n(int64(max-minResponseTime))) @@ -59,10 +62,12 @@ func TestValue(t *testing.T) { minx := math.Pi / 2 * float64(min) / float64(timeout) maxx := math.Pi / 2 * float64(max) / float64(timeout) avgWeight := (math.Sin(maxx) - math.Sin(minx)) / (maxx - minx) + expv := 1000 * avgWeight if expv < 0 { expv = 0 } + if value < expv-10 || value > expv+10 { t.Errorf("Value failed (expected %v, got %v)", expv, value) } @@ -75,6 +80,7 @@ func TestAddSubExpire(t *testing.T) { sum1ValueExp, sum2ValueExp float64 logOffset utils.Fixed64 ) + for i := 0; i < 1000; i++ { exp := utils.ExpFactor(logOffset) max := minResponseTime + time.Duration(rand.Int63n(int64(maxResponseTime-minResponseTime))) @@ -82,28 +88,37 @@ func TestAddSubExpire(t *testing.T) { s := makeRangeStats(min, max, 1000, exp) value := s.Value(maxResponseWeights, exp) sum1.AddStats(&s) + sum1ValueExp += value + if rand.Intn(2) == 1 { sum2.AddStats(&s) + sum2ValueExp += value } + logOffset += utils.Float64ToFixed64(0.001 / math.Log(2)) sum1ValueExp -= sum1ValueExp * 0.001 sum2ValueExp -= sum2ValueExp * 0.001 } + exp := utils.ExpFactor(logOffset) + sum1Value := sum1.Value(maxResponseWeights, exp) if sum1Value < sum1ValueExp*0.99 || sum1Value > sum1ValueExp*1.01 { t.Errorf("sum1Value failed (expected %v, got %v)", sum1ValueExp, sum1Value) } + sum2Value := sum2.Value(maxResponseWeights, exp) if sum2Value < sum2ValueExp*0.99 || sum2Value > sum2ValueExp*1.01 { t.Errorf("sum2Value failed (expected %v, got %v)", sum2ValueExp, sum2Value) } + diff := sum1 diff.SubStats(&sum2) diffValue := diff.Value(maxResponseWeights, exp) diffValueExp := sum1ValueExp - sum2ValueExp + if diffValue < diffValueExp*0.99 || diffValue > diffValueExp*1.01 { t.Errorf("diffValue failed (expected %v, got %v)", diffValueExp, diffValue) } @@ -121,6 +136,7 @@ func testTimeoutRange(t *testing.T, min, max time.Duration) { to := s.Timeout(float64(i) / 10) exp := max - (max-min)*time.Duration(i)/10 tol := (max - min) / 50 + if to < exp-tol || to > exp+tol { t.Errorf("Timeout failed (expected %v, got %v)", exp, to) } @@ -129,9 +145,11 @@ func testTimeoutRange(t *testing.T, min, max time.Duration) { func makeRangeStats(min, max time.Duration, amount float64, exp utils.ExpirationFactor) ResponseTimeStats { var s ResponseTimeStats + amount /= 1000 for i := 0; i < 1000; i++ { s.Add(min+(max-min)*time.Duration(i)/999, amount, exp) } + return s } diff --git a/les/vflux/client/valuetracker.go b/les/vflux/client/valuetracker.go index 806d0c7d75..8fddf24fcd 100644 --- a/les/vflux/client/valuetracker.go +++ b/les/vflux/client/valuetracker.go @@ -86,12 +86,15 @@ func (nv *NodeValueTracker) transferStats(now mclock.AbsTime, transferRate float dt := now - nv.lastTransfer nv.lastTransfer = now + if dt < 0 { dt = 0 } + recentRtStats := nv.rtStats recentRtStats.SubStats(&nv.lastRtStats) nv.lastRtStats = nv.rtStats + return nv.basket.transfer(-math.Expm1(-transferRate * float64(dt))), recentRtStats } @@ -110,10 +113,12 @@ func (nv *NodeValueTracker) Served(reqs []ServedRequest, respTime time.Duration) 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) } @@ -183,7 +188,9 @@ func NewValueTracker(db ethdb.KeyValueStore, clock mclock.Clock, reqInfo []Reque sumAmount += req.InitAmount sumValue += req.InitAmount * req.InitValue } + scaleValues := sumAmount * basketFactor / sumValue + for i, req := range reqInfo { mapping[i] = req.Name initRefBasket.items[i].amount = uint64(req.InitAmount * basketFactor) @@ -207,6 +214,7 @@ func NewValueTracker(db ethdb.KeyValueStore, clock mclock.Clock, reqInfo []Reque vt.mappings = [][]string{mapping} vt.currentMapping = 0 } + vt.statsExpirer.SetRate(now, statsExpRate) vt.refBasket.init(vt.reqTypeCount) vt.periodicUpdate() @@ -224,6 +232,7 @@ func NewValueTracker(db ethdb.KeyValueStore, clock mclock.Clock, reqInfo []Reque } } }() + return vt } @@ -249,26 +258,34 @@ func (vt *ValueTracker) loadFromDb(mapping []string) error { if err != nil { return err } + r := bytes.NewReader(enc) + var version uint + if err := rlp.Decode(r, &version); err != nil { log.Error("Decoding value tracker state failed", "err", err) return err } + if version != vtVersion { log.Error("Unknown ValueTracker version", "stored", version, "current", nvtVersion) return fmt.Errorf("Unknown ValueTracker version %d (current version is %d)", version, vtVersion) } + var vte valueTrackerEncV1 if err := rlp.Decode(r, &vte); err != nil { log.Error("Decoding value tracker state failed", "err", err) return err } + logOffset := utils.Fixed64(vte.ExpOffset) dt := time.Now().UnixNano() - int64(vte.SavedAt) + if dt > 0 { logOffset += utils.Float64ToFixed64(float64(dt) * vt.offlineExpRate / math.Log(2)) } + vt.statsExpirer.SetLogOffset(vt.clock.Now(), logOffset) vt.rtStats = vte.RtStats vt.mappings = vte.Mappings @@ -286,10 +303,12 @@ loop: vt.currentMapping = i break } + if vt.currentMapping == -1 { vt.currentMapping = len(vt.mappings) vt.mappings = append(vt.mappings, mapping) } + if int(vte.RefBasketMapping) == vt.currentMapping { vt.refBasket.basket = vte.RefBasket } else { @@ -297,8 +316,10 @@ loop: log.Error("Unknown request basket mapping", "stored", vte.RefBasketMapping, "current", vt.currentMapping) return fmt.Errorf("Unknown request basket mapping %d (current version is %d)", vte.RefBasketMapping, vt.currentMapping) } + vt.refBasket.basket = vte.RefBasket.convertMapping(vt.mappings[vte.RefBasketMapping], mapping, vt.initRefBasket) } + return nil } @@ -312,16 +333,19 @@ func (vt *ValueTracker) saveToDb() { ExpOffset: uint64(vt.statsExpirer.LogOffset(vt.clock.Now())), SavedAt: uint64(time.Now().UnixNano()), } + enc1, err := rlp.EncodeToBytes(uint(vtVersion)) if err != nil { log.Error("Encoding value tracker state failed", "err", err) return } + enc2, err := rlp.EncodeToBytes(&vte) if err != nil { log.Error("Encoding value tracker state failed", "err", err) return } + if err := vt.db.Put(vtKey, append(enc1, enc2...)); err != nil { log.Error("Saving value tracker state failed", "err", err) } @@ -335,9 +359,11 @@ func (vt *ValueTracker) Stop() { <-quit vt.lock.Lock() vt.periodicUpdate() + for id, nv := range vt.connected { vt.saveNode(id, nv) } + vt.connected = nil vt.saveToDb() vt.lock.Unlock() @@ -352,6 +378,7 @@ func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker { // ValueTracker has already been stopped return nil } + nv := vt.loadOrNewNode(id) reqTypeCount := len(vt.refBasket.reqValues) nv.reqCosts = make([]uint64, reqTypeCount) @@ -360,6 +387,7 @@ func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker { nv.basket.init(reqTypeCount) vt.connected[id] = nv + return nv } @@ -389,28 +417,37 @@ func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker { if nv, ok := vt.connected[id]; ok { return nv } + nv := &NodeValueTracker{vt: vt, lastTransfer: vt.clock.Now()} enc, err := vt.db.Get(append(vtNodeKey, id[:]...)) + if err != nil { return nv } + r := bytes.NewReader(enc) + var version uint + if err := rlp.Decode(r, &version); err != nil { log.Error("Failed to decode node value tracker", "id", id, "err", err) return nv } + if version != nvtVersion { log.Error("Unknown NodeValueTracker version", "stored", version, "current", nvtVersion) return nv } + var nve nodeValueTrackerEncV1 if err := rlp.Decode(r, &nve); err != nil { log.Error("Failed to decode node value tracker", "id", id, "err", err) return nv } + nv.rtStats = nve.RtStats nv.lastRtStats = nve.RtStats + if int(nve.ServerBasketMapping) == vt.currentMapping { nv.basket.basket = nve.ServerBasket } else { @@ -418,8 +455,10 @@ func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker { log.Error("Unknown request basket mapping", "stored", nve.ServerBasketMapping, "current", vt.currentMapping) return nv } + nv.basket.basket = nve.ServerBasket.convertMapping(vt.mappings[nve.ServerBasketMapping], vt.mappings[vt.currentMapping], vt.initRefBasket) } + return nv } @@ -435,16 +474,19 @@ func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) { ServerBasketMapping: uint(vt.currentMapping), ServerBasket: nv.basket.basket, } + enc1, err := rlp.EncodeToBytes(uint(nvtVersion)) if err != nil { log.Error("Failed to encode service value information", "id", id, "err", err) return } + enc2, err := rlp.EncodeToBytes(&nve) if err != nil { log.Error("Failed to encode service value information", "id", id, "err", err) return } + if err := vt.db.Put(append(vtNodeKey, id[:]...), append(enc1, enc2...)); err != nil { log.Error("Failed to save service value information", "id", id, "err", err) } @@ -456,6 +498,7 @@ func (vt *ValueTracker) RtStats() ResponseTimeStats { defer vt.lock.Unlock() vt.periodicUpdate() + return vt.rtStats } @@ -473,11 +516,14 @@ func (vt *ValueTracker) periodicUpdate() { vt.refBasket.add(basket) vt.rtStats.AddStats(&rtStats) } + vt.refBasket.normalize() vt.refBasket.updateReqValues() + for _, nv := range vt.connected { nv.updateCosts(nv.reqCosts, vt.refBasket.reqValues, vt.refBasket.reqValueFactor(nv.reqCosts)) } + vt.saveToDb() } @@ -496,11 +542,13 @@ func (vt *ValueTracker) RequestStats() []RequestStatsItem { defer vt.lock.Unlock() vt.periodicUpdate() + res := make([]RequestStatsItem, len(vt.refBasket.basket.items)) for i, item := range vt.refBasket.basket.items { res[i].Name = vt.mappings[vt.currentMapping][i] res[i].ReqAmount = expFactor.Value(float64(item.amount)/basketFactor, vt.refBasket.basket.exp) res[i].ReqValue = vt.refBasket.reqValues[i] } + return res } diff --git a/les/vflux/client/valuetracker_test.go b/les/vflux/client/valuetracker_test.go index 87a337be8d..4ccb054d15 100644 --- a/les/vflux/client/valuetracker_test.go +++ b/les/vflux/client/valuetracker_test.go @@ -43,16 +43,21 @@ func TestValueTracker(t *testing.T) { requestList := make([]RequestInfo, testReqTypes) relPrices := make([]float64, testReqTypes) totalAmount := make([]uint64, testReqTypes) + for i := range requestList { requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1} totalAmount[i] = 1 relPrices[i] = rand.Float64() + 0.1 } + nodes := make([]*NodeValueTracker, testNodeCount) + for round := 0; round < testRounds; round++ { makeRequests := round < testRounds-2 useExpiration := round == testRounds-1 + var expRate float64 + if useExpiration { expRate = math.Log(2) / float64(time.Hour*100) } @@ -61,15 +66,19 @@ func TestValueTracker(t *testing.T) { updateCosts := func(i int) { costList := make([]uint64, testReqTypes) baseCost := rand.Float64()*10000000 + 100000 + for j := range costList { costList[j] = uint64(baseCost * relPrices[j]) } + nodes[i].UpdateCosts(costList) } + for i := range nodes { nodes[i] = vt.Register(enode.ID{byte(i)}) updateCosts(i) } + if makeRequests { for i := 0; i < testReqCount; i++ { reqType := rand.Intn(testReqTypes) @@ -82,18 +91,22 @@ func TestValueTracker(t *testing.T) { } } else { clock.Run(time.Hour * 100) + if useExpiration { for i, a := range totalAmount { totalAmount[i] = a / 2 } } } + vt.Stop() + var sumrp, sumrv float64 for i, rp := range relPrices { sumrp += rp sumrv += vt.refBasket.reqValues[i] } + for i, rp := range relPrices { ratio := vt.refBasket.reqValues[i] * sumrp / (rp * sumrv) if ratio < 0.99 || ratio > 1.01 { @@ -101,11 +114,14 @@ func TestValueTracker(t *testing.T) { break } } + exp := utils.ExpFactor(vt.StatsExpirer().LogOffset(clock.Now())) basketAmount := make([]uint64, testReqTypes) + for i, bi := range vt.refBasket.basket.items { basketAmount[i] += uint64(exp.Value(float64(bi.amount), vt.refBasket.basket.exp)) } + if makeRequests { // if we did not make requests in this round then we expect all amounts to be // in the reference basket @@ -115,18 +131,23 @@ func TestValueTracker(t *testing.T) { } } } + for i, a := range basketAmount { amount := a / basketFactor if amount+10 < totalAmount[i] || amount > totalAmount[i]+10 { t.Errorf("totalAmount[%d] mismatch in round %d (expected %d, got %d)", i, round, totalAmount[i], amount) } } + var sumValue float64 + for _, node := range nodes { s := node.RtStats() sumValue += s.Value(maxResponseWeights, exp) } + s := vt.RtStats() + mainValue := s.Value(maxResponseWeights, exp) if sumValue < mainValue-10 || sumValue > mainValue+10 { t.Errorf("Main rtStats value does not match sum of node rtStats values in round %d (main %v, sum %v)", round, mainValue, sumValue) diff --git a/les/vflux/client/wrsiterator.go b/les/vflux/client/wrsiterator.go index 1b37cba6e5..fba0fd42bd 100644 --- a/les/vflux/client/wrsiterator.go +++ b/les/vflux/client/wrsiterator.go @@ -45,7 +45,9 @@ func NewWrsIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags n if n == nil { return 0 } + wt, _ := ns.GetField(n, weightField).(uint64) + return wt } @@ -67,6 +69,7 @@ func NewWrsIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags n ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState nodestate.Flags) { oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags) newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags) + if newMatch == oldMatch { return } @@ -80,6 +83,7 @@ func NewWrsIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags n w.lock.Unlock() w.cond.Signal() }) + return w } @@ -97,6 +101,7 @@ func (w *WrsIterator) chooseNode() *enode.Node { for !w.closed && w.wrs.IsEmpty() { w.cond.Wait() } + if w.closed { return nil } @@ -106,6 +111,7 @@ func (w *WrsIterator) chooseNode() *enode.Node { if c := w.wrs.Choose(); c != nil { id := c.(enode.ID) w.wrs.Remove(id) + return w.ns.GetNode(id) } } @@ -123,5 +129,6 @@ func (w *WrsIterator) Close() { func (w *WrsIterator) Node() *enode.Node { w.lock.Lock() defer w.lock.Unlock() + return w.nextNode } diff --git a/les/vflux/client/wrsiterator_test.go b/les/vflux/client/wrsiterator_test.go index 77bb5ee0ca..2eb8c7c5b4 100644 --- a/les/vflux/client/wrsiterator_test.go +++ b/les/vflux/client/wrsiterator_test.go @@ -40,10 +40,12 @@ func TestWrsIterator(t *testing.T) { ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup) w := NewWrsIterator(ns, sfTest2, sfTest3.Or(sfTest4), sfiTestWeight) ns.Start() + for i := 1; i <= iterTestNodeCount; i++ { ns.SetState(testNode(i), sfTest1, nodestate.Flags{}, 0) ns.SetField(testNode(i), sfiTestWeight, uint64(1)) } + next := func() int { ch := make(chan struct{}) go func() { @@ -55,8 +57,10 @@ func TestWrsIterator(t *testing.T) { case <-time.After(time.Second * 5): t.Fatalf("Iterator.Next() timeout") } + node := w.Node() ns.SetState(node, sfTest4, nodestate.Flags{}, 0) + return testNodeIndex(node.ID()) } set := make(map[int]bool) @@ -66,6 +70,7 @@ func TestWrsIterator(t *testing.T) { if !set[n] { t.Errorf("Item returned by iterator not in the expected set (got %d)", n) } + delete(set, n) } } @@ -73,31 +78,39 @@ func TestWrsIterator(t *testing.T) { ns.SetState(testNode(1), sfTest2, nodestate.Flags{}, 0) ns.SetState(testNode(2), sfTest2, nodestate.Flags{}, 0) ns.SetState(testNode(3), sfTest2, nodestate.Flags{}, 0) + set[1] = true set[2] = true set[3] = true + expset() ns.SetState(testNode(4), sfTest2, nodestate.Flags{}, 0) ns.SetState(testNode(5), sfTest2.Or(sfTest3), nodestate.Flags{}, 0) ns.SetState(testNode(6), sfTest2, nodestate.Flags{}, 0) + set[4] = true set[6] = true + expset() ns.SetField(testNode(2), sfiTestWeight, uint64(0)) ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0) ns.SetState(testNode(2), nodestate.Flags{}, sfTest4, 0) ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0) + set[1] = true set[3] = true + expset() ns.SetField(testNode(2), sfiTestWeight, uint64(1)) ns.SetState(testNode(2), nodestate.Flags{}, sfTest2, 0) ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0) ns.SetState(testNode(2), sfTest2, sfTest4, 0) ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0) + set[1] = true set[2] = true set[3] = true + expset() ns.Stop() } diff --git a/les/vflux/requests.go b/les/vflux/requests.go index 5abae2f537..13ee773edd 100644 --- a/les/vflux/requests.go +++ b/les/vflux/requests.go @@ -60,11 +60,13 @@ func (r *Requests) Add(service, name string, val interface{}) (int, error) { if err != nil { return -1, err } + *r = append(*r, Request{ Service: service, Name: name, Params: enc, }) + return len(*r) - 1, nil } @@ -73,6 +75,7 @@ func (r Replies) Get(i int, val interface{}) error { if i < 0 || i >= len(r) { return ErrNoReply } + return rlp.DecodeBytes(r[i], val) } @@ -102,6 +105,7 @@ func (i *IntOrInf) BigInt() *big.Int { case IntMinusInf: panic(nil) } + return &big.Int{} // invalid type decodes to 0 value } @@ -113,6 +117,7 @@ func (i *IntOrInf) Inf() int { case IntMinusInf: return -1 } + return 0 // invalid type decodes to 0 value } @@ -136,6 +141,7 @@ func (i *IntOrInf) Int64() int64 { case IntMinusInf: return math.MinInt64 } + return 0 // invalid type decodes to 0 value } diff --git a/les/vflux/server/balance.go b/les/vflux/server/balance.go index b09f7bb501..f4164ae468 100644 --- a/les/vflux/server/balance.go +++ b/les/vflux/server/balance.go @@ -136,25 +136,31 @@ func (b *balance) addValue(now mclock.AbsTime, amount int64, pos bool, force boo val utils.ExpiredValue offset utils.Fixed64 ) + if pos { offset, val = b.posExp.LogOffset(now), b.pos } else { offset, val = b.negExp.LogOffset(now), b.neg } + old := val.Value(offset) if amount > 0 && (amount > maxBalance || old > maxBalance-uint64(amount)) { if !force { return old, 0, 0, errBalanceOverflow } + val = utils.ExpiredValue{} amount = maxBalance } + net := val.Add(amount, offset) + if pos { b.pos = val } else { b.neg = val } + return old, val.Value(offset), net, nil } @@ -164,11 +170,14 @@ func (b *balance) setValue(now mclock.AbsTime, pos uint64, neg uint64) error { if pos > maxBalance || neg > maxBalance { return errBalanceOverflow } + var pb, nb utils.ExpiredValue + pb.Add(int64(pos), b.posExp.LogOffset(now)) nb.Add(int64(neg), b.negExp.LogOffset(now)) b.pos = pb b.neg = nb + return nil } @@ -187,6 +196,7 @@ func (n *nodeBalance) GetBalance() (uint64, uint64) { now := n.bt.clock.Now() n.updateBalance(now) + return n.balance.posValue(now), n.balance.negValue(now) } @@ -198,6 +208,7 @@ func (n *nodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) { now := n.bt.clock.Now() n.updateBalance(now) + return n.balance.pos, n.balance.neg } @@ -217,13 +228,17 @@ func (n *nodeBalance) AddBalance(amount int64) (uint64, uint64, error) { // Operation with holding the lock n.bt.updateTotalBalance(n, func() bool { n.updateBalance(now) + if old, new, _, err = n.balance.addValue(now, amount, true, false); err != nil { return false } + callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus() n.storeBalance(true, false) + return true }) + if err != nil { return old, old, err } @@ -231,6 +246,7 @@ func (n *nodeBalance) AddBalance(amount int64) (uint64, uint64, error) { for _, cb := range callbacks { cb() } + if n.setFlags { if setPriority { n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0) @@ -238,6 +254,7 @@ func (n *nodeBalance) AddBalance(amount int64) (uint64, uint64, error) { // Note: priority flag is automatically removed by the zero priority callback if necessary n.signalPriorityUpdate() } + return old, new, nil } @@ -252,17 +269,21 @@ func (n *nodeBalance) SetBalance(pos, neg uint64) error { // Operation with holding the lock n.bt.updateTotalBalance(n, func() bool { n.updateBalance(now) + if err := n.balance.setValue(now, pos, neg); err != nil { return false } + callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus() n.storeBalance(true, true) + return true }) // Operation without holding the lock for _, cb := range callbacks { cb() } + if n.setFlags { if setPriority { n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0) @@ -270,6 +291,7 @@ func (n *nodeBalance) SetBalance(pos, neg uint64) error { // Note: priority flag is automatically removed by the zero priority callback if necessary n.signalPriorityUpdate() } + return nil } @@ -282,7 +304,9 @@ func (n *nodeBalance) RequestServed(cost uint64) (newBalance uint64) { fcost = float64(cost) now = n.bt.clock.Now() ) + n.updateBalance(now) + if !n.balance.pos.IsZero() { posCost := -int64(fcost * n.posFactor.RequestFactor) if posCost == 0 { @@ -290,19 +314,24 @@ func (n *nodeBalance) RequestServed(cost uint64) (newBalance uint64) { newBalance = n.balance.posValue(now) } else { var net int64 + _, newBalance, net, _ = n.balance.addValue(now, posCost, true, false) if posCost == net { fcost = 0 } else { fcost *= 1 - float64(net)/float64(posCost) } + check = true } } + if fcost > 0 && n.negFactor.RequestFactor != 0 { n.balance.addValue(now, int64(fcost*n.negFactor.RequestFactor), false, false) + check = true } + n.sumReqCost += cost var callbacks []func() @@ -318,6 +347,7 @@ func (n *nodeBalance) RequestServed(cost uint64) (newBalance uint64) { } }) } + return } @@ -328,6 +358,7 @@ func (n *nodeBalance) priority(capacity uint64) int64 { now := n.bt.clock.Now() n.updateBalance(now) + return n.balanceToPriority(now, n.balance, capacity) } @@ -347,17 +378,22 @@ func (n *nodeBalance) estimatePriority(capacity uint64, addBalance int64, future if addBalance != 0 { b.addValue(now, addBalance, true, true) } + 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.Add(future), bias, capacity, 0) } + pri := n.balanceToPriority(now, b, capacity) // Ensure that biased estimates are always lower than actual priorities, even if // the bias is very small. @@ -367,9 +403,11 @@ func (n *nodeBalance) estimatePriority(capacity uint64, addBalance int64, future if pri >= current { pri = current - 1 } + if update { n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate) } + return pri } @@ -382,6 +420,7 @@ func (n *nodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) { n.posFactor, n.negFactor = posFactor, negFactor callbacks := n.checkCallbacks(now) n.lock.Unlock() + if callbacks != nil { n.bt.ns.Operation(func() { for _, cb := range callbacks { @@ -405,8 +444,10 @@ func (n *nodeBalance) activate() { if n.active { return false } + n.active = true n.lastUpdate = n.bt.clock.Now() + return true }) } @@ -417,13 +458,17 @@ func (n *nodeBalance) deactivate() { if !n.active { return false } + n.updateBalance(n.bt.clock.Now()) + if n.updateEvent != nil { n.updateEvent.Stop() n.updateEvent = nil } + n.storeBalance(true, true) n.active = false + return true }) } @@ -441,6 +486,7 @@ func (n *nodeBalance) storeBalance(pos, neg bool) { if pos { n.bt.storeBalance(n.node.ID().Bytes(), false, n.balance.pos) } + if neg { n.bt.storeBalance([]byte(n.connAddress), true, n.balance.neg) } @@ -453,14 +499,17 @@ func (n *nodeBalance) storeBalance(pos, neg bool) { // Note 2: the callback function runs inside a NodeStateMachine operation func (n *nodeBalance) addCallback(id int, threshold int64, callback func()) { n.removeCallback(id) + idx := 0 for idx < n.callbackCount && threshold > n.callbacks[idx].threshold { idx++ } + for i := n.callbackCount - 1; i >= idx; i-- { n.callbackIndex[n.callbacks[i].id]++ n.callbacks[i+1] = n.callbacks[i] } + n.callbackCount++ n.callbackIndex[id] = idx n.callbacks[idx] = balanceCallback{id, threshold, callback} @@ -476,12 +525,15 @@ func (n *nodeBalance) removeCallback(id int) bool { if idx == -1 { return false } + n.callbackIndex[id] = -1 for i := idx; i < n.callbackCount-1; i++ { n.callbackIndex[n.callbacks[i+1].id]-- n.callbacks[i] = n.callbacks[i+1] } + n.callbackCount-- + return true } @@ -492,6 +544,7 @@ func (n *nodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) { if n.callbackCount == 0 || n.capacity == 0 { return } + pri := n.balanceToPriority(now, n.balance, n.capacity) for n.callbackCount != 0 && n.callbacks[n.callbackCount-1].threshold >= pri { n.callbackCount-- @@ -499,6 +552,7 @@ func (n *nodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) { callbacks = append(callbacks, n.callbacks[n.callbackCount].callback) } n.scheduleCheck(now) + return } @@ -510,8 +564,10 @@ func (n *nodeBalance) scheduleCheck(now mclock.AbsTime) { if !ok { n.nextUpdate = 0 n.updateAfter(0) + return } + if n.nextUpdate == 0 || n.nextUpdate > now.Add(d) { if d > time.Second { // Note: if the scheduled update is not in the very near future then we @@ -520,6 +576,7 @@ func (n *nodeBalance) scheduleCheck(now mclock.AbsTime) { // brings the expected firing time a little bit closer. d = ((d - time.Second) * 7 / 8) + time.Second } + n.nextUpdate = now.Add(d) n.updateAfter(d) } @@ -537,6 +594,7 @@ func (n *nodeBalance) updateAfter(dt time.Duration) { } else { n.updateEvent = n.bt.clock.AfterFunc(dt, func() { var callbacks []func() + n.lock.Lock() if n.callbackCount != 0 { now := n.bt.clock.Now() @@ -544,6 +602,7 @@ func (n *nodeBalance) updateAfter(dt time.Duration) { callbacks = n.checkCallbacks(now) } n.lock.Unlock() + if callbacks != nil { n.bt.ns.Operation(func() { for _, cb := range callbacks { @@ -563,6 +622,7 @@ func (n *nodeBalance) balanceExhausted() { n.storeBalance(true, false) n.hasPriority = false n.lock.Unlock() + if n.setFlags { n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.priorityFlag, 0) } @@ -575,8 +635,10 @@ func (n *nodeBalance) checkPriorityStatus() bool { if !n.hasPriority && !n.balance.pos.IsZero() { n.hasPriority = true n.addCallback(balanceCallbackZero, 0, func() { n.balanceExhausted() }) + return true } + return false } @@ -597,6 +659,7 @@ func (n *nodeBalance) setCapacity(capacity uint64) { n.capacity = capacity callbacks := n.checkCallbacks(now) n.lock.Unlock() + for _, cb := range callbacks { cb() } @@ -610,6 +673,7 @@ func (n *nodeBalance) balanceToPriority(now mclock.AbsTime, b balance, capacity if pos > 0 { return int64(pos / capacity) } + return -int64(b.negValue(now)) } @@ -620,6 +684,7 @@ func (n *nodeBalance) priorityToBalance(priority int64, capacity uint64) (uint64 if priority > 0 { return uint64(priority) * n.capacity, 0 } + return 0, uint64(-priority) } @@ -632,20 +697,24 @@ func (n *nodeBalance) reducedBalance(b balance, start mclock.AbsTime, dt time.Du at = start.Add(dt / 2) dtf = float64(dt) ) + if !b.pos.IsZero() { factor := n.posFactor.connectionPrice(capacity, avgReqCost) diff := -int64(dtf * factor) _, _, net, _ := b.addValue(at, diff, true, false) + if net == diff { dtf = 0 } else { dtf += float64(net) / factor } } + if dtf > 0 { factor := n.negFactor.connectionPrice(capacity, avgReqCost) b.addValue(at, int64(dtf*factor), false, false) } + return b } @@ -662,16 +731,20 @@ func (n *nodeBalance) timeUntil(priority int64) (time.Duration, bool) { targetPos, targetNeg = n.priorityToBalance(priority, n.capacity) diffTime float64 ) + if pos > 0 { timePrice := n.posFactor.connectionPrice(n.capacity, 0) if timePrice < 1e-100 { return 0, false } + if targetPos > 0 { if targetPos > pos { return 0, true } + diffTime = float64(pos-targetPos) / timePrice + return time.Duration(diffTime), true } else { diffTime = float64(pos) / timePrice @@ -681,13 +754,16 @@ func (n *nodeBalance) timeUntil(priority int64) (time.Duration, bool) { return 0, true } } + neg := n.balance.negValue(now) if targetNeg > neg { timePrice := n.negFactor.connectionPrice(n.capacity, 0) if timePrice < 1e-100 { return 0, false } + diffTime += float64(targetNeg-neg) / timePrice } + return time.Duration(diffTime), true } diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go index 12a24a09fa..e1e366c7cb 100644 --- a/les/vflux/server/balance_test.go +++ b/les/vflux/server/balance_test.go @@ -57,17 +57,22 @@ func newBalanceTestSetup(db ethdb.KeyValueStore, posExp, negExp utils.ValueExpir setup.clientField = setup.setup.NewField("balanceTestClient", reflect.TypeOf(balanceTestClient{})) ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) + if posExp == nil { posExp = zeroExpirer{} } + if negExp == nil { negExp = zeroExpirer{} } + if db == nil { db = memorydb.New() } + bt := newBalanceTracker(ns, setup, db, clock, posExp, negExp) ns.Start() + return &balanceTestSetup{ clock: clock, db: db, @@ -80,10 +85,13 @@ func newBalanceTestSetup(db ethdb.KeyValueStore, posExp, negExp utils.ValueExpir func (b *balanceTestSetup) newNode(capacity uint64) *nodeBalance { node := enode.SignNull(&enr.Record{}, enode.ID{}) b.ns.SetField(node, b.setup.clientField, balanceTestClient{}) + if capacity != 0 { b.ns.SetField(node, b.setup.capacityField, capacity) } + n, _ := b.ns.GetField(node, b.setup.balanceField).(*nodeBalance) + return n } @@ -91,6 +99,7 @@ func (b *balanceTestSetup) setBalance(node *nodeBalance, pos, neg uint64) (err e b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) { err = balance.SetBalance(pos, neg) }) + return } @@ -98,6 +107,7 @@ func (b *balanceTestSetup) addBalance(node *nodeBalance, add int64) (old, new ui b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) { old, new, err = balance.AddBalance(add) }) + return } @@ -111,6 +121,7 @@ func TestAddBalance(t *testing.T) { defer b.stop() node := b.newNode(1000) + var inputs = []struct { delta int64 expect [2]uint64 @@ -123,19 +134,23 @@ func TestAddBalance(t *testing.T) { {1, [2]uint64{0, 1}, 1, false}, {maxBalance, [2]uint64{0, 0}, 0, true}, } + for _, i := range inputs { old, new, err := b.addBalance(node, i.delta) if i.expectErr { if err == nil { t.Fatalf("Expect get error but nil") } + continue } else if err != nil { t.Fatalf("Expect get no error but %v", err) } + if old != i.expect[0] || new != i.expect[1] { t.Fatalf("Positive balance mismatch, got %v -> %v", old, new) } + if b.bt.TotalTokenAmount() != i.total { t.Fatalf("Total positive balance mismatch, want %v, got %v", i.total, b.bt.TotalTokenAmount()) } @@ -154,12 +169,15 @@ func TestSetBalance(t *testing.T) { {0, 1000}, {1000, 1000}, } + for _, i := range inputs { b.setBalance(node, i.pos, i.neg) + pos, neg := node.GetBalance() if pos != i.pos { t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos) } + if neg != i.neg { t.Fatalf("Negative balance mismatch, want %v, got %v", i.neg, neg) } @@ -184,22 +202,28 @@ func TestBalanceTimeCost(t *testing.T) { {time.Second * 59, 0, 0}, {time.Second, 0, uint64(time.Second)}, } + for _, i := range inputs { b.clock.Run(i.runTime) + if pos, _ := node.GetBalance(); pos != i.expPos { t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) } + if _, neg := node.GetBalance(); neg != i.expNeg { t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) } } b.setBalance(node, uint64(time.Minute), 0) // Refill 1 minute time allowance + for _, i := range inputs { b.clock.Run(i.runTime) + if pos, _ := node.GetBalance(); pos != i.expPos { t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) } + if _, neg := node.GetBalance(); neg != i.expNeg { t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) } @@ -213,6 +237,7 @@ func TestBalanceReqCost(t *testing.T) { node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) b.setBalance(node, uint64(time.Minute), 0) // 1 minute time serving time allowance + var inputs = []struct { reqCost uint64 expPos uint64 @@ -223,11 +248,14 @@ func TestBalanceReqCost(t *testing.T) { {uint64(time.Second * 59), 0, 0}, {uint64(time.Second), 0, uint64(time.Second)}, } + for _, i := range inputs { node.RequestServed(i.reqCost) + if pos, _ := node.GetBalance(); pos != i.expPos { t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) } + if _, neg := node.GetBalance(); neg != i.expNeg { t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) } @@ -250,8 +278,10 @@ func TestBalanceToPriority(t *testing.T) { {0, 0, 0}, {0, 1000, -1000}, } + for _, i := range inputs { b.setBalance(node, i.pos, i.neg) + priority := node.priority(1000) if priority != i.priority { t.Fatalf("priority mismatch, want %v, got %v", i.priority, priority) @@ -265,6 +295,7 @@ func TestEstimatedPriority(t *testing.T) { node := b.newNode(1000000000) node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) b.setBalance(node, uint64(time.Minute), 0) + var inputs = []struct { runTime time.Duration // time cost futureTime time.Duration // diff of future time @@ -288,9 +319,11 @@ func TestEstimatedPriority(t *testing.T) { // 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec. {0, time.Minute, 0, -int64(time.Minute) - int64(time.Second)*120/29}, } + for _, i := range inputs { b.clock.Run(i.runTime) node.RequestServed(i.reqCost) + 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) @@ -300,10 +333,12 @@ func TestEstimatedPriority(t *testing.T) { func TestPositiveBalanceCounting(t *testing.T) { t.Parallel() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() var nodes []*nodeBalance + for i := 0; i < 100; i += 1 { node := b.newNode(1000000) node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) @@ -312,11 +347,13 @@ func TestPositiveBalanceCounting(t *testing.T) { // Allocate service token var sum uint64 + for i := 0; i < 100; i += 1 { amount := int64(rand.Intn(100) + 100) b.addBalance(nodes[i], amount) sum += uint64(amount) } + if b.bt.TotalTokenAmount() != sum { t.Fatalf("Invalid token amount") } @@ -327,14 +364,17 @@ func TestPositiveBalanceCounting(t *testing.T) { b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1)) } } + if b.bt.TotalTokenAmount() != sum { t.Fatalf("Invalid token amount") } + for i := 0; i < 100; i += 1 { if rand.Intn(2) == 0 { b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1)) } } + if b.bt.TotalTokenAmount() != sum { t.Fatalf("Invalid token amount") } @@ -354,7 +394,9 @@ func TestCallbackChecking(t *testing.T) { {0, time.Second}, {-int64(time.Second), 2 * time.Second}, } + b.setBalance(node, uint64(time.Second), 0) + for _, i := range inputs { diff, _ := node.timeUntil(i.priority) if diff != i.expDiff { @@ -370,6 +412,7 @@ func TestCallback(t *testing.T) { node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) callCh := make(chan struct{}, 1) + b.setBalance(node, uint64(time.Minute), 0) node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) @@ -395,6 +438,7 @@ func TestCallback(t *testing.T) { func TestBalancePersistence(t *testing.T) { posExp := &utils.Expirer{} negExp := &utils.Expirer{} + posExp.SetRate(0, math.Log(2)/float64(time.Hour*2)) // halves every two hours negExp.SetRate(0, math.Log(2)/float64(time.Hour)) // halves every hour setup := newBalanceTestSetup(nil, posExp, negExp) @@ -404,6 +448,7 @@ func TestBalancePersistence(t *testing.T) { 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) } @@ -416,7 +461,9 @@ func TestBalancePersistence(t *testing.T) { } expTotal(0) + balance := setup.newNode(0) + expTotal(0) setup.setBalance(balance, 16000000000, 16000000000) exp(balance, 16000000000, 16000000000) @@ -429,7 +476,9 @@ func TestBalancePersistence(t *testing.T) { // Test the functionalities after restart setup = newBalanceTestSetup(setup.db, posExp, negExp) + expTotal(8000000000) + balance = setup.newNode(0) exp(balance, 8000000000, 4000000000) expTotal(8000000000) diff --git a/les/vflux/server/balance_tracker.go b/les/vflux/server/balance_tracker.go index 9695e79638..3a9333218d 100644 --- a/les/vflux/server/balance_tracker.go +++ b/les/vflux/server/balance_tracker.go @@ -90,12 +90,15 @@ func newBalanceTracker(ns *nodestate.NodeStateMachine, setup *serverSetup, db et ov, _ := oldValue.(uint64) nv, _ := newValue.(uint64) + if ov == 0 && nv != 0 { n.activate() } + if nv != 0 { n.setCapacity(nv) } + if ov != 0 && nv == 0 { n.deactivate() } @@ -104,6 +107,7 @@ func newBalanceTracker(ns *nodestate.NodeStateMachine, setup *serverSetup, db et type peer interface { FreeClientId() string } + if newValue != nil { n := bt.newNodeBalance(node, newValue.(peer).FreeClientId(), true) bt.lock.Lock() @@ -112,9 +116,11 @@ func newBalanceTracker(ns *nodestate.NodeStateMachine, setup *serverSetup, db et ns.SetFieldSub(node, bt.setup.balanceField, n) } else { ns.SetStateSub(node, nodestate.Flags{}, bt.setup.priorityFlag, 0) + if b, _ := ns.GetField(node, bt.setup.balanceField).(*nodeBalance); b != nil { b.deactivate() } + ns.SetFieldSub(node, bt.setup.balanceField, nil) } }) @@ -135,6 +141,7 @@ func newBalanceTracker(ns *nodestate.NodeStateMachine, setup *serverSetup, db et } } }() + return bt } @@ -167,10 +174,13 @@ func (bt *balanceTracker) TotalTokenAmount() uint64 { bt.active.AddExp(pos) } }) + return true }) + total := bt.active total.AddExp(bt.inactive) + return total.Value(bt.posExp.LogOffset(bt.clock.Now())) } @@ -195,11 +205,13 @@ func (bt *balanceTracker) SetExpirationTCs(pos, neg uint64) { bt.posExpTC, bt.negExpTC = pos, neg now := bt.clock.Now() + if pos > 0 { bt.posExp.SetRate(now, 1/float64(pos*uint64(time.Second))) } else { bt.posExp.SetRate(now, 0) } + if neg > 0 { bt.negExp.SetRate(now, 1/float64(neg*uint64(time.Second))) } else { @@ -224,10 +236,12 @@ func (bt *balanceTracker) BalanceOperation(id enode.ID, connAddress string, cb f if node := bt.ns.GetNode(id); node != nil { nb, _ = bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance) } + if nb == nil { node := enode.SignNull(&enr.Record{}, id) nb = bt.newNodeBalance(node, connAddress, false) } + cb(nb) }) } @@ -239,6 +253,7 @@ func (bt *balanceTracker) BalanceOperation(id enode.ID, connAddress string, cb f func (bt *balanceTracker) newNodeBalance(node *enode.Node, connAddress string, setFlags bool) *nodeBalance { pb := bt.ndb.getOrNewBalance(node.ID().Bytes(), false) nb := bt.ndb.getOrNewBalance([]byte(connAddress), true) + n := &nodeBalance{ bt: bt, node: node, @@ -251,9 +266,11 @@ func (bt *balanceTracker) newNodeBalance(node *enode.Node, connAddress string, s for i := range n.callbackIndex { n.callbackIndex[i] = -1 } + if setFlags && n.checkPriorityStatus() { n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0) } + return n } @@ -272,6 +289,7 @@ func (bt *balanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.E if neg { return b.Value(bt.negExp.LogOffset(now)) <= negThreshold } + return b.Value(bt.posExp.LogOffset(now)) <= posThreshold } @@ -284,14 +302,17 @@ func (bt *balanceTracker) updateTotalBalance(n *nodeBalance, callback func() boo defer n.lock.Unlock() original, active := n.balance.pos, n.active + if !callback() { return } + if active { bt.active.SubExp(original) } else { bt.inactive.SubExp(original) } + if n.active { bt.active.AddExp(n.balance.pos) } else { diff --git a/les/vflux/server/clientdb.go b/les/vflux/server/clientdb.go index a39cbec36a..9a76778b42 100644 --- a/les/vflux/server/clientdb.go +++ b/les/vflux/server/clientdb.go @@ -75,7 +75,9 @@ func newNodeDB(db ethdb.KeyValueStore, clock mclock.Clock) *nodeDB { closeCh: make(chan struct{}), } binary.BigEndian.PutUint16(ndb.verbuf[:], uint16(nodeDBVersion)) + go ndb.expirer() + return ndb } @@ -88,6 +90,7 @@ func (db *nodeDB) getPrefix(neg bool) []byte { if neg { prefix = negativeBalancePrefix } + return append(db.verbuf[:], prefix...) } @@ -96,12 +99,15 @@ func (db *nodeDB) key(id []byte, neg bool) []byte { if neg { prefix = negativeBalancePrefix } + if len(prefix)+len(db.verbuf)+len(id) > len(db.auxbuf) { db.auxbuf = append(db.auxbuf, make([]byte, len(prefix)+len(db.verbuf)+len(id)-len(db.auxbuf))...) } + copy(db.auxbuf[:len(db.verbuf)], db.verbuf[:]) copy(db.auxbuf[len(db.verbuf):len(db.verbuf)+len(prefix)], prefix) copy(db.auxbuf[len(prefix)+len(db.verbuf):len(prefix)+len(db.verbuf)+len(id)], id) + return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)] } @@ -110,11 +116,13 @@ func (db *nodeDB) getExpiration() (utils.Fixed64, utils.Fixed64) { if err != nil || len(blob) != 16 { return 0, 0 } + return utils.Fixed64(binary.BigEndian.Uint64(blob[:8])), utils.Fixed64(binary.BigEndian.Uint64(blob[8:16])) } func (db *nodeDB) setExpiration(pos, neg utils.Fixed64) { var buff [16]byte + binary.BigEndian.PutUint64(buff[:8], uint64(pos)) binary.BigEndian.PutUint64(buff[8:16], uint64(neg)) db.db.Put(append(db.verbuf[:], expirationKey...), buff[:16]) @@ -122,29 +130,36 @@ func (db *nodeDB) setExpiration(pos, neg utils.Fixed64) { func (db *nodeDB) getOrNewBalance(id []byte, neg bool) utils.ExpiredValue { key := db.key(id, neg) + item, exist := db.cache.Get(string(key)) if exist { return item } var b utils.ExpiredValue + enc, err := db.db.Get(key) if err != nil || len(enc) == 0 { return b } + if err := rlp.DecodeBytes(enc, &b); err != nil { log.Crit("Failed to decode positive balance", "err", err) } + db.cache.Add(string(key), b) + return b } func (db *nodeDB) setBalance(id []byte, neg bool, b utils.ExpiredValue) { key := db.key(id, neg) + enc, err := rlp.EncodeToBytes(&(b)) if err != nil { log.Crit("Failed to encode positive balance", "err", err) } + db.db.Put(key, enc) db.cache.Add(string(key), b) } @@ -161,6 +176,7 @@ func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result [ if maxCount <= 0 { return } + prefix := db.getPrefix(false) keylen := len(prefix) + len(enode.ID{}) @@ -169,18 +185,23 @@ func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result [ for it.Next() { var id enode.ID + if len(it.Key()) != keylen { return } + copy(id[:], it.Key()[keylen-len(id):]) + if bytes.Compare(id.Bytes(), stop.Bytes()) >= 0 { return } + result = append(result, id) if len(result) == maxCount { return } } + return } @@ -194,15 +215,18 @@ func (db *nodeDB) forEachBalance(neg bool, callback func(id enode.ID, balance ut for it.Next() { var id enode.ID + if len(it.Key()) != keylen { return } + copy(id[:], it.Key()[keylen-len(id):]) var b utils.ExpiredValue if err := rlp.DecodeBytes(it.Value(), &b); err != nil { continue } + if !callback(id, b) { return } @@ -228,16 +252,20 @@ func (db *nodeDB) expireNodes() { deleted int start = time.Now() ) + for _, neg := range []bool{false, true} { iter := db.db.NewIterator(db.getPrefix(neg), nil) for iter.Next() { visited++ + var balance utils.ExpiredValue if err := rlp.DecodeBytes(iter.Value(), &balance); err != nil { log.Crit("Failed to decode negative balance", "err", err) } + if db.evictCallBack != nil && db.evictCallBack(db.clock.Now(), neg, balance) { deleted++ + db.db.Delete(iter.Key()) } } @@ -246,5 +274,6 @@ func (db *nodeDB) expireNodes() { if db.cleanupHook != nil { db.cleanupHook() } + log.Debug("Expire nodes", "visited", visited, "deleted", deleted, "elapsed", common.PrettyDuration(time.Since(start))) } diff --git a/les/vflux/server/clientdb_test.go b/les/vflux/server/clientdb_test.go index 353d84aead..52f1713463 100644 --- a/les/vflux/server/clientdb_test.go +++ b/les/vflux/server/clientdb_test.go @@ -46,34 +46,42 @@ func TestNodeDB(t *testing.T) { {enode.ID{}, "127.0.0.1", expval(100), false}, {enode.ID{}, "127.0.0.1", expval(200), false}, } + for _, c := range cases { if c.positive { ndb.setBalance(c.id.Bytes(), false, c.balance) + if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, c.balance) { t.Fatalf("Positive balance mismatch, want %v, got %v", c.balance, pb) } } else { ndb.setBalance([]byte(c.ip), true, c.balance) + if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, c.balance) { t.Fatalf("Negative balance mismatch, want %v, got %v", c.balance, nb) } } } + for _, c := range cases { if c.positive { ndb.delBalance(c.id.Bytes(), false) + if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, utils.ExpiredValue{}) { t.Fatalf("Positive balance mismatch, want %v, got %v", utils.ExpiredValue{}, pb) } } else { ndb.delBalance([]byte(c.ip), true) + if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, utils.ExpiredValue{}) { t.Fatalf("Negative balance mismatch, want %v, got %v", utils.ExpiredValue{}, nb) } } } + posExp, negExp := utils.Fixed64(1000), utils.Fixed64(2000) ndb.setExpiration(posExp, negExp) + if pos, neg := ndb.getExpiration(); pos != posExp || neg != negExp { t.Fatalf("Expiration mismatch, want %v / %v, got %v / %v", posExp, negExp, pos, neg) } @@ -89,11 +97,13 @@ func TestNodeDBExpiration(t *testing.T) { iterated int done = make(chan struct{}, 1) ) + callback := func(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { iterated += 1 return true } clock := &mclock.Simulated{} + ndb := newNodeDB(rawdb.NewMemoryDatabase(), clock) defer ndb.close() ndb.evictCallBack = callback @@ -114,9 +124,11 @@ func TestNodeDBExpiration(t *testing.T) { {[]byte("127.0.0.3"), true, expval(1)}, {[]byte("127.0.0.4"), true, expval(1)}, } + for _, c := range cases { ndb.setBalance(c.id, c.neg, c.balance) } + clock.WaitForTimers(1) clock.Run(time.Hour + time.Minute) select { @@ -124,6 +136,7 @@ func TestNodeDBExpiration(t *testing.T) { case <-time.NewTimer(time.Second).C: t.Fatalf("timeout") } + if iterated != 8 { t.Fatalf("Failed to evict useless balances, want %v, got %d", 8, iterated) } @@ -131,6 +144,7 @@ func TestNodeDBExpiration(t *testing.T) { for _, c := range cases { ndb.setBalance(c.id, c.neg, c.balance) } + clock.WaitForTimers(1) clock.Run(time.Hour + time.Minute) select { @@ -138,6 +152,7 @@ func TestNodeDBExpiration(t *testing.T) { case <-time.NewTimer(time.Second).C: t.Fatalf("timeout") } + if iterated != 16 { t.Fatalf("Failed to evict useless balances, want %v, got %d", 16, iterated) } diff --git a/les/vflux/server/clientpool.go b/les/vflux/server/clientpool.go index a525f86368..cc363bfb4c 100644 --- a/les/vflux/server/clientpool.go +++ b/les/vflux/server/clientpool.go @@ -102,11 +102,14 @@ func NewClientPool(balanceDb ethdb.KeyValueStore, minCap uint64, connectedBias t if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok { timeout = c.InactiveAllowance() } + ns.AddTimeout(node, setup.inactiveFlag, timeout) } + if oldState.Equals(setup.inactiveFlag) && newState.Equals(setup.inactiveFlag.Or(setup.priorityFlag)) { ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0) // priority gained; remove timeout } + if newState.Equals(setup.activeFlag) { // active with no priority; limit capacity to minCap cap, _ := ns.GetField(node, setup.capacityField).(uint64) @@ -114,6 +117,7 @@ func NewClientPool(balanceDb ethdb.KeyValueStore, minCap uint64, connectedBias t cp.requestCapacity(node, minCap, minCap, 0) } } + if newState.Equals(nodestate.Flags{}) { if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok { c.Disconnect() @@ -133,20 +137,25 @@ func NewClientPool(balanceDb ethdb.KeyValueStore, minCap uint64, connectedBias t if oldState.IsEmpty() && !newState.IsEmpty() { clientConnectedMeter.Mark(1) } + if !oldState.IsEmpty() && newState.IsEmpty() { clientDisconnectedMeter.Mark(1) } + if oldState.HasNone(cp.setup.activeFlag) && oldState.HasAll(cp.setup.activeFlag) { clientActivatedMeter.Mark(1) } + if oldState.HasAll(cp.setup.activeFlag) && oldState.HasNone(cp.setup.activeFlag) { clientDeactivatedMeter.Mark(1) } + activeCount, activeCap := cp.Active() totalActiveCountGauge.Update(int64(activeCount)) totalActiveCapacityGauge.Update(int64(activeCap)) totalInactiveCountGauge.Update(int64(cp.Inactive())) }) + return cp } @@ -168,6 +177,7 @@ func (cp *ClientPool) Stop() { func (cp *ClientPool) Register(peer clientPeer) ConnectedBalance { cp.ns.SetField(peer.Node(), cp.setup.clientField, peerWrapper{peer}) balance, _ := cp.ns.GetField(peer.Node(), cp.setup.balanceField).(*nodeBalance) + return balance } @@ -200,24 +210,29 @@ func (cp *ClientPool) SetCapacity(node *enode.Node, reqCap uint64, bias time.Dur err = ErrNotConnected return } + capacity, _ = cp.ns.GetField(node, cp.setup.capacityField).(uint64) if capacity == 0 { // if the client is inactive then it has insufficient priority for the minimal capacity // (will be activated automatically with minCap when possible) return } + if reqCap < cp.minCap { // can't request less than minCap; switching between 0 (inactive state) and minCap is // performed by the server automatically as soon as necessary/possible reqCap = cp.minCap } + if reqCap > cp.minCap && cp.ns.GetState(node).HasNone(cp.setup.priorityFlag) { err = ErrNoPriority return } + if reqCap == capacity { return } + if requested { // mark the requested node so that the UpdateCapacity callback can signal // whether the update is the direct result of a SetCapacity call on the given node @@ -228,6 +243,7 @@ func (cp *ClientPool) SetCapacity(node *enode.Node, reqCap uint64, bias time.Dur } var minTarget, maxTarget uint64 + if reqCap > capacity { // Estimate maximum available capacity at the current priority level and request // the estimated amount. @@ -237,12 +253,14 @@ func (cp *ClientPool) SetCapacity(node *enode.Node, reqCap uint64, bias time.Dur // estimation of the maximum available capacity based on the capacity curve we // can limit the number of required iterations. curve := cp.getCapacityCurve().exclude(node.ID()) + maxTarget = curve.maxCapacity(func(capacity uint64) int64 { return balance.estimatePriority(capacity, 0, 0, bias, false) }) if maxTarget < reqCap { return } + maxTarget = reqCap // Specify a narrow target range that allows a limited number of fine step @@ -254,14 +272,17 @@ func (cp *ClientPool) SetCapacity(node *enode.Node, reqCap uint64, bias time.Dur } else { minTarget, maxTarget = reqCap, reqCap } + if newCap := cp.requestCapacity(node, minTarget, maxTarget, bias); newCap >= minTarget && newCap <= maxTarget { capacity = newCap return } // we should be able to find the maximum allowed capacity in a few iterations log.Error("Unable to find maximum allowed capacity") + err = ErrCantFindMaximum }) + return } @@ -273,17 +294,23 @@ func (cp *ClientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []b if rlp.DecodeBytes(data, &req) != nil { return nil } + if l := len(req.AddTokens); l == 0 || l > vflux.CapacityQueryMaxLen { return nil } + result := make(vflux.CapacityQueryReply, len(req.AddTokens)) + if !cp.synced() { capacityQueryZeroMeter.Mark(1) + reply, _ := rlp.EncodeToBytes(&result) + return reply } bias := time.Second * time.Duration(req.Bias) + cp.lock.RLock() if cp.connectedBias > bias { bias = cp.connectedBias @@ -294,14 +321,17 @@ func (cp *ClientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []b curve := cp.getCapacityCurve().exclude(id) cp.BalanceOperation(id, freeID, func(balance AtomicBalanceOperator) { pb, _ := balance.GetBalance() + for i, addTokens := range req.AddTokens { add := addTokens.Int64() result[i] = curve.maxCapacity(func(capacity uint64) int64 { return balance.estimatePriority(capacity, add, 0, bias, false) / int64(capacity) }) + if add <= 0 && uint64(-add) >= pb && result[i] > cp.minCap { result[i] = cp.minCap } + if result[i] < cp.minCap { result[i] = 0 } @@ -313,7 +343,9 @@ func (cp *ClientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []b } else { capacityQueryNonZeroMeter.Mark(1) } + reply, _ := rlp.EncodeToBytes(&result) + return reply } diff --git a/les/vflux/server/clientpool_test.go b/les/vflux/server/clientpool_test.go index f75c70afca..273f8325fb 100644 --- a/les/vflux/server/clientpool_test.go +++ b/les/vflux/server/clientpool_test.go @@ -85,6 +85,7 @@ func (i *poolTestPeer) InactiveAllowance() time.Duration { if i.inactiveAllowed { return time.Second * 10 } + return 0 } @@ -96,6 +97,7 @@ func (i *poolTestPeer) Disconnect() { if i.disconnCh == nil { return } + id := i.node.ID() i.disconnCh <- int(id[0]) + int(id[1])<<8 } @@ -104,6 +106,7 @@ func getBalance(pool *ClientPool, p *poolTestPeer) (pos, neg uint64) { pool.BalanceOperation(p.node.ID(), p.FreeClientId(), func(nb AtomicBalanceOperator) { pos, neg = nb.GetBalance() }) + return } @@ -118,6 +121,7 @@ func checkDiff(a, b uint64) bool { if maxDiff < 1 { maxDiff = 1 } + return a > b+maxDiff || b > a+maxDiff } @@ -143,6 +147,7 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando disconnCh = make(chan int, clientCount) pool = NewClientPool(db, 1, 0, &clock, alwaysTrueFn) ) + pool.Start() pool.SetExpirationTCs(0, 1000) @@ -173,6 +178,7 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando if connected[i] { if randomDisconnect { disconnect(pool, newPoolTestPeer(i, disconnCh)) + connected[i] = false connTicks[i] += tickCounter } @@ -211,15 +217,18 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando if c { connTicks[i] += testClientPoolTicks } + min, max := expMin, expMax if i < paidCount { // expect a higher amount for clients with a positive balance min, max = paidMin, paidMax } + if connTicks[i] < min || connTicks[i] > max { t.Errorf("Total connected time of test node #%d (%d) outside expected range (%d to %d)", i, connTicks[i], min, max) } } + pool.Stop() } @@ -231,6 +240,7 @@ func testPriorityConnect(t *testing.T, pool *ClientPool, p *poolTestPeer, cap ui return } } + if newCap, _ := pool.SetCapacity(p.node, cap, defaultConnectedBias, true); newCap != cap { if expSuccess { t.Fatalf("Failed to raise capacity of paid client") @@ -238,6 +248,7 @@ func testPriorityConnect(t *testing.T, pool *ClientPool, p *poolTestPeer, cap ui return } } + if !expSuccess { t.Fatalf("Should reject high capacity paid client") } @@ -248,8 +259,10 @@ func TestConnectPaidClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetLimits(10, uint64(10)) pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -264,8 +277,10 @@ func TestConnectPaidClientToSmallPool(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -282,8 +297,10 @@ func TestConnectPaidClientToFullPool(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -293,11 +310,14 @@ func TestConnectPaidClientToFullPool(t *testing.T) { connect(pool, newPoolTestPeer(i, nil)) } addBalance(pool, newPoolTestPeer(11, nil).node.ID(), int64(time.Second*2)) // Add low balance to new paid client + if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 { t.Fatalf("Low balance paid client should be rejected") } + clock.Run(time.Second) addBalance(pool, newPoolTestPeer(12, nil).node.ID(), int64(time.Minute*5)) // Add high balance to new paid client + if cap := connect(pool, newPoolTestPeer(12, nil)); cap == 0 { t.Fatalf("High balance paid client should be accepted") } @@ -309,9 +329,11 @@ func TestPaidClientKickedOut(t *testing.T) { db = rawdb.NewMemoryDatabase() kickedCh = make(chan int, 100) ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() pool.SetExpirationTCs(0, 0) + defer pool.Stop() pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -322,9 +344,11 @@ func TestPaidClientKickedOut(t *testing.T) { clock.Run(time.Millisecond) } clock.Run(defaultConnectedBias + time.Second*11) + if cap := connect(pool, newPoolTestPeer(11, kickedCh)); cap == 0 { t.Fatalf("Free client should be accepted") } + clock.Run(0) select { case id := <-kickedCh: @@ -341,14 +365,18 @@ func TestConnectFreeClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetLimits(10, uint64(10)) pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + if cap := connect(pool, newPoolTestPeer(0, nil)); cap == 0 { t.Fatalf("Failed to connect free client") } + testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 2, false) } @@ -357,8 +385,10 @@ func TestConnectFreeClientToFullPool(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -366,15 +396,20 @@ func TestConnectFreeClientToFullPool(t *testing.T) { for i := 0; i < 10; i++ { connect(pool, newPoolTestPeer(i, nil)) } + if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 { t.Fatalf("New free client should be rejected") } + clock.Run(time.Minute) + if cap := connect(pool, newPoolTestPeer(12, nil)); cap != 0 { t.Fatalf("New free client should be rejected") } + clock.Run(time.Millisecond) clock.Run(4 * time.Minute) + if cap := connect(pool, newPoolTestPeer(13, nil)); cap == 0 { t.Fatalf("Old client connects more than 5min should be kicked") } @@ -386,8 +421,10 @@ func TestFreeClientKickedOut(t *testing.T) { db = rawdb.NewMemoryDatabase() kicked = make(chan int, 100) ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -396,9 +433,11 @@ func TestFreeClientKickedOut(t *testing.T) { connect(pool, newPoolTestPeer(i, kicked)) clock.Run(time.Millisecond) } + if cap := connect(pool, newPoolTestPeer(10, kicked)); cap != 0 { t.Fatalf("New free client should be rejected") } + clock.Run(0) select { case <-kicked: @@ -407,6 +446,7 @@ func TestFreeClientKickedOut(t *testing.T) { } disconnect(pool, newPoolTestPeer(10, kicked)) clock.Run(5 * time.Minute) + for i := 0; i < 10; i++ { connect(pool, newPoolTestPeer(i+10, kicked)) } @@ -430,8 +470,10 @@ func TestPositiveBalanceCalculation(t *testing.T) { db = rawdb.NewMemoryDatabase() kicked = make(chan int, 10) ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -441,6 +483,7 @@ func TestPositiveBalanceCalculation(t *testing.T) { clock.Run(time.Minute) disconnect(pool, newPoolTestPeer(0, kicked)) + pb, _ := getBalance(pool, newPoolTestPeer(0, kicked)) if checkDiff(pb, uint64(time.Minute*2)) { t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb) @@ -453,8 +496,10 @@ func TestDowngradePriorityClient(t *testing.T) { db = rawdb.NewMemoryDatabase() kicked = make(chan int, 10) ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -462,21 +507,25 @@ func TestDowngradePriorityClient(t *testing.T) { p := newPoolTestPeer(0, kicked) addBalance(pool, p.node.ID(), int64(time.Minute)) testPriorityConnect(t, pool, p, 10, true) + if p.cap != 10 { t.Fatalf("The capacity of priority peer hasn't been updated, got: %d", p.cap) } clock.Run(time.Minute) // All positive balance should be used up. time.Sleep(300 * time.Millisecond) // Ensure the callback is called + if p.cap != 1 { t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap) } + pb, _ := getBalance(pool, newPoolTestPeer(0, kicked)) if pb != 0 { t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb) } addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute)) + pb, _ = getBalance(pool, newPoolTestPeer(0, kicked)) if checkDiff(pb, uint64(time.Minute)) { t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb) @@ -488,8 +537,10 @@ func TestNegativeBalanceCalculation(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetExpirationTCs(0, 3600) pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 @@ -502,20 +553,24 @@ func TestNegativeBalanceCalculation(t *testing.T) { for i := 0; i < 10; i++ { disconnect(pool, newPoolTestPeer(i, nil)) + _, nb := getBalance(pool, newPoolTestPeer(i, nil)) if nb != 0 { t.Fatalf("Short connection shouldn't be recorded") } } + for i := 0; i < 10; i++ { connect(pool, newPoolTestPeer(i, nil)) } clock.Run(time.Minute) + for i := 0; i < 10; i++ { disconnect(pool, newPoolTestPeer(i, nil)) _, nb := getBalance(pool, newPoolTestPeer(i, nil)) 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) } @@ -527,8 +582,10 @@ func TestInactiveClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) pool.Start() + defer pool.Stop() pool.SetLimits(2, uint64(2)) @@ -538,6 +595,7 @@ func TestInactiveClient(t *testing.T) { p2.inactiveAllowed = true p3 := newPoolTestPeer(3, nil) p3.inactiveAllowed = true + addBalance(pool, p1.node.ID(), 1000*int64(time.Second)) addBalance(pool, p3.node.ID(), 2000*int64(time.Second)) // p1: 1000 p2: 0 p3: 2000 @@ -545,34 +603,43 @@ func TestInactiveClient(t *testing.T) { if p1.cap != 1 { t.Fatalf("Failed to connect peer #1") } + p2.cap = connect(pool, p2) if p2.cap != 1 { t.Fatalf("Failed to connect peer #2") } + p3.cap = connect(pool, p3) if p3.cap != 1 { t.Fatalf("Failed to connect peer #3") } + if p2.cap != 0 { t.Fatalf("Failed to deactivate peer #2") } + addBalance(pool, p2.node.ID(), 3000*int64(time.Second)) // p1: 1000 p2: 3000 p3: 2000 if p2.cap != 1 { t.Fatalf("Failed to activate peer #2") } + if p1.cap != 0 { t.Fatalf("Failed to deactivate peer #1") } + addBalance(pool, p2.node.ID(), -2500*int64(time.Second)) // p1: 1000 p2: 500 p3: 2000 if p1.cap != 1 { t.Fatalf("Failed to activate peer #1") } + if p2.cap != 0 { t.Fatalf("Failed to deactivate peer #2") } + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, 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 @@ -580,9 +647,11 @@ func TestInactiveClient(t *testing.T) { if p4.cap != 1 { t.Fatalf("Failed to activate peer #4") } + if p1.cap != 0 { t.Fatalf("Failed to deactivate peer #1") } + clock.Run(time.Second * 600) // manually trigger a check to avoid a long real-time wait pool.ns.SetState(p1.node, pool.setup.updateFlag, nodestate.Flags{}, 0) @@ -591,15 +660,19 @@ func TestInactiveClient(t *testing.T) { if p1.cap != 1 { t.Fatalf("Failed to activate peer #1") } + if p4.cap != 0 { t.Fatalf("Failed to deactivate peer #4") } + disconnect(pool, p2) disconnect(pool, p4) addBalance(pool, p1.node.ID(), -1000*int64(time.Second)) + if p1.cap != 1 { t.Fatalf("Should not deactivate peer #1") } + if p2.cap != 0 { t.Fatalf("Should not activate peer #2") } diff --git a/les/vflux/server/prioritypool.go b/les/vflux/server/prioritypool.go index 766026a808..e62e4d3e18 100644 --- a/les/vflux/server/prioritypool.go +++ b/les/vflux/server/prioritypool.go @@ -113,6 +113,7 @@ func newPriorityPool(ns *nodestate.NodeStateMachine, setup *serverSetup, clock m if pp.activeBias < time.Duration(1) { pp.activeBias = time.Duration(1) } + pp.activeQueue = prque.NewLazyQueue(activeSetIndex, activePriority, pp.activeMaxPriority, clock, lazyQueueRefresh) ns.SubscribeField(pp.setup.balanceField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { @@ -127,9 +128,11 @@ func newPriorityPool(ns *nodestate.NodeStateMachine, setup *serverSetup, clock m ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0) } else { ns.SetStateSub(node, nodestate.Flags{}, pp.setup.activeFlag.Or(pp.setup.inactiveFlag), 0) + if n, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo); n != nil { pp.disconnectNode(n) } + ns.SetFieldSub(node, pp.setup.capacityField, nil) ns.SetFieldSub(node, pp.setup.queueField, nil) } @@ -139,6 +142,7 @@ func newPriorityPool(ns *nodestate.NodeStateMachine, setup *serverSetup, clock m if oldState.IsEmpty() { pp.connectNode(c) } + if newState.IsEmpty() { pp.disconnectNode(c) } @@ -149,6 +153,7 @@ func newPriorityPool(ns *nodestate.NodeStateMachine, setup *serverSetup, clock m pp.updatePriority(node) } }) + return pp } @@ -164,23 +169,30 @@ func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget u if minTarget < pp.minCap { minTarget = pp.minCap } + if maxTarget < minTarget { maxTarget = minTarget } + if bias < pp.activeBias { bias = pp.activeBias } + c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo) if c == nil { log.Error("requestCapacity called for unknown node", "id", node.ID()) pp.lock.Unlock() + return 0 } + pp.setTempState(c) + if maxTarget > c.capacity { pp.setTempStepDiv(c, pp.fineStepDiv) pp.setTempBias(c, bias) } + pp.setTempCapacity(c, maxTarget) c.minTarget = minTarget pp.removeFromQueues(c) @@ -189,6 +201,7 @@ func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget u updates := pp.finalizeChanges(c.tempCapacity >= minTarget && c.tempCapacity <= maxTarget && c.tempCapacity != c.capacity) pp.lock.Unlock() pp.updateFlags(updates) + return c.capacity } @@ -201,10 +214,12 @@ func (pp *priorityPool) SetLimits(maxCount, maxCap uint64) { pp.maxCount, pp.maxCap = maxCount, maxCap var updates []capUpdate + if dec { pp.enforceLimits() updates = pp.finalizeChanges(true) } + if inc { updates = append(updates, pp.tryActivate(false)...) } @@ -215,10 +230,12 @@ func (pp *priorityPool) SetLimits(maxCount, maxCap uint64) { // setActiveBias sets the bias applied when trying to activate inactive nodes func (pp *priorityPool) setActiveBias(bias time.Duration) { pp.lock.Lock() + pp.activeBias = bias if pp.activeBias < time.Duration(1) { pp.activeBias = time.Duration(1) } + updates := pp.tryActivate(false) pp.lock.Unlock() pp.ns.Operation(func() { pp.updateFlags(updates) }) @@ -264,6 +281,7 @@ func invertPriority(p int64) int64 { if p == math.MinInt64 { return math.MaxInt64 } + return -p } @@ -282,6 +300,7 @@ func (pp *priorityPool) activeMaxPriority(c *ppNodeInfo, until mclock.AbsTime) i if future < 0 { future = 0 } + return invertPriority(c.nodePriority.estimatePriority(c.tempCapacity, 0, future, c.bias, false)) } @@ -295,6 +314,7 @@ func (pp *priorityPool) removeFromQueues(c *ppNodeInfo) { if c.activeIndex >= 0 { pp.activeQueue.Remove(c.activeIndex) } + if c.inactiveIndex >= 0 { pp.inactiveQueue.Remove(c.inactiveIndex) } @@ -305,10 +325,12 @@ func (pp *priorityPool) removeFromQueues(c *ppNodeInfo) { func (pp *priorityPool) connectNode(c *ppNodeInfo) { pp.lock.Lock() pp.activeQueue.Refresh() + if c.connected { pp.lock.Unlock() return } + c.connected = true pp.inactiveQueue.Push(c, pp.inactivePriority(c)) updates := pp.tryActivate(false) @@ -322,14 +344,17 @@ func (pp *priorityPool) connectNode(c *ppNodeInfo) { func (pp *priorityPool) disconnectNode(c *ppNodeInfo) { pp.lock.Lock() pp.activeQueue.Refresh() + if !c.connected { pp.lock.Unlock() return } + c.connected = false pp.removeFromQueues(c) var updates []capUpdate + if c.capacity != 0 { pp.setTempState(c) pp.setTempCapacity(c, 0) @@ -347,6 +372,7 @@ func (pp *priorityPool) setTempState(c *ppNodeInfo) { if c.tempState { return } + c.tempState = true if c.tempCapacity != c.capacity { // should never happen log.Error("tempCapacity != capacity when entering tempState") @@ -364,10 +390,12 @@ func (pp *priorityPool) unsetTempState(c *ppNodeInfo) { if !c.tempState { return } + c.tempState = false if c.tempCapacity != c.capacity { // should never happen log.Error("tempCapacity != capacity when leaving tempState") } + c.minTarget = pp.minCap c.stepDiv = pp.capacityStepDiv c.bias = 0 @@ -381,13 +409,16 @@ func (pp *priorityPool) setTempCapacity(c *ppNodeInfo, cap uint64) { log.Error("Node is not in temporary state") return } + pp.activeCap += cap - c.tempCapacity if c.tempCapacity == 0 { pp.activeCount++ } + if cap == 0 { pp.activeCount-- } + c.tempCapacity = cap } @@ -397,6 +428,7 @@ func (pp *priorityPool) setTempBias(c *ppNodeInfo, bias time.Duration) { log.Error("Node is not in temporary state") return } + c.bias = bias } @@ -406,6 +438,7 @@ func (pp *priorityPool) setTempStepDiv(c *ppNodeInfo, stepDiv uint64) { log.Error("Node is not in temporary state") return } + c.stepDiv = stepDiv } @@ -416,14 +449,18 @@ func (pp *priorityPool) enforceLimits() (*ppNodeInfo, int64) { if pp.activeCap <= pp.maxCap && pp.activeCount <= pp.maxCount { return nil, math.MinInt64 } + var ( lastNode *ppNodeInfo maxActivePriority int64 ) + pp.activeQueue.MultiPop(func(c *ppNodeInfo, priority int64) bool { lastNode = c pp.setTempState(c) + maxActivePriority = priority + if c.tempCapacity == c.minTarget || pp.activeCount > pp.maxCount { pp.setTempCapacity(c, 0) } else { @@ -431,14 +468,18 @@ func (pp *priorityPool) enforceLimits() (*ppNodeInfo, int64) { if sub == 0 { sub = 1 } + if c.tempCapacity-sub < c.minTarget { sub = c.tempCapacity - c.minTarget } + pp.setTempCapacity(c, c.tempCapacity-sub) pp.activeQueue.Push(c) } + return pp.activeCap > pp.maxCap || pp.activeCount > pp.maxCount }) + return lastNode, invertPriority(maxActivePriority) } @@ -450,11 +491,13 @@ func (pp *priorityPool) finalizeChanges(commit bool) (updates []capUpdate) { // always remove and push back in order to update biased priority pp.removeFromQueues(c) oldCapacity := c.capacity + if commit { c.capacity = c.tempCapacity } else { pp.setTempCapacity(c, c.capacity) // revert activeCount/activeCap } + pp.unsetTempState(c) if c.connected { @@ -463,15 +506,18 @@ func (pp *priorityPool) finalizeChanges(commit bool) (updates []capUpdate) { } else { pp.inactiveQueue.Push(c, pp.inactivePriority(c)) } + if c.capacity != oldCapacity { updates = append(updates, capUpdate{c.node, oldCapacity, c.capacity}) } } } + pp.tempState = nil if commit { pp.ccUpdateForced = true } + return } @@ -489,6 +535,7 @@ func (pp *priorityPool) updateFlags(updates []capUpdate) { if f.oldCap == 0 { pp.ns.SetStateSub(f.node, pp.setup.activeFlag, pp.setup.inactiveFlag, 0) } + if f.newCap == 0 { pp.ns.SetStateSub(f.node, pp.setup.inactiveFlag, pp.setup.activeFlag, 0) pp.ns.SetFieldSub(f.node, pp.setup.capacityField, nil) @@ -507,14 +554,18 @@ func (pp *priorityPool) tryActivate(commit bool) []capUpdate { pp.setTempCapacity(c, pp.minCap) pp.activeQueue.Push(c) pp.enforceLimits() + if c.tempCapacity > 0 { commit = true + pp.setTempBias(c, 0) } else { break } } + pp.ccUpdateForced = true + return pp.finalizeChanges(commit) } @@ -524,17 +575,21 @@ func (pp *priorityPool) tryActivate(commit bool) []capUpdate { func (pp *priorityPool) updatePriority(node *enode.Node) { pp.lock.Lock() pp.activeQueue.Refresh() + c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo) if c == nil || !c.connected { pp.lock.Unlock() return } + pp.removeFromQueues(c) + if c.capacity != 0 { pp.activeQueue.Push(c) } else { pp.inactiveQueue.Push(c, pp.inactivePriority(c)) } + updates := pp.tryActivate(false) pp.lock.Unlock() pp.updateFlags(updates) @@ -560,6 +615,7 @@ func (pp *priorityPool) getCapacityCurve() *capacityCurve { 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 @@ -573,12 +629,15 @@ func (pp *priorityPool) getCapacityCurve() *capacityCurve { 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 lastPri := int64(math.MinInt64) + 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 { @@ -587,6 +646,7 @@ func (pp *priorityPool) getCapacityCurve() *capacityCurve { // 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 @@ -597,12 +657,16 @@ func (pp *priorityPool) getCapacityCurve() *capacityCurve { } else { lastPri = cp.nextPri } + 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 @@ -617,6 +681,7 @@ func (pp *priorityPool) getCapacityCurve() *capacityCurve { } // restore original state of the queue pp.finalizeChanges(false) + curve.points = append(curve.points, curvePoint{ freeCap: pp.maxCap, nextPri: math.MaxInt64, @@ -624,6 +689,7 @@ func (pp *priorityPool) getCapacityCurve() *capacityCurve { if curve.excludeFirst { curve.excludeList = curve.index[excludeID] } + return curve } @@ -639,6 +705,7 @@ func (cc *capacityCurve) exclude(id enode.ID) *capacityCurve { excludeList: excludeList, } } + return cc } @@ -648,14 +715,17 @@ func (cc *capacityCurve) getPoint(i int) curvePoint { cp.freeCap = 0 return cp } + for ii := len(cc.excludeList) - 1; ii >= 0; ii-- { ei := cc.excludeList[ii] if ei < i { break } + e1, e2 := cc.points[ei], cc.points[ei+1] cp.freeCap += e2.freeCap - e1.freeCap } + return cp } @@ -668,20 +738,24 @@ func (cc *capacityCurve) maxCapacity(priority func(cap uint64) int64) uint64 { 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 @@ -691,5 +765,6 @@ func (cc *capacityCurve) maxCapacity(priority func(cap uint64) int64) uint64 { maxc = midc - 1 } } + return maxc } diff --git a/les/vflux/server/prioritypool_test.go b/les/vflux/server/prioritypool_test.go index 5152312116..9c429e8289 100644 --- a/les/vflux/server/prioritypool_test.go +++ b/les/vflux/server/prioritypool_test.go @@ -59,26 +59,33 @@ func TestPriorityPool(t *testing.T) { c.cap = newValue.(uint64) } }) + pp := newPriorityPool(ns, setup, clock, testMinCap, 0, testCapacityStepDiv, testCapacityStepDiv) ns.Start() pp.SetLimits(100, 1000000) + clients := make([]*ppTestClient, 100) raise := func(c *ppTestClient) { for { var ok bool + ns.Operation(func() { newCap := c.cap + c.cap/testCapacityStepDiv ok = pp.requestCapacity(c.node, newCap, newCap, 0) == newCap }) + if !ok { return } } } + var sumBalance uint64 + check := func(c *ppTestClient) { expCap := 1000000 * c.balance / sumBalance capTol := expCap / testCapacityToleranceDiv + if c.cap < expCap-capTol || c.cap > expCap+capTol { t.Errorf("Wrong node capacity (expected %d, got %d)", expCap, c.cap) } @@ -105,6 +112,7 @@ func TestPriorityPool(t *testing.T) { sumBalance += c.balance - oldBalance pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0) pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0) + if c.balance > oldBalance { raise(c) } else { @@ -116,6 +124,7 @@ func TestPriorityPool(t *testing.T) { for _, c := range clients { check(c) } + if count%10 == 0 { // test available capacity calculation with capacity curve c = clients[rand.Intn(len(clients))] @@ -127,29 +136,38 @@ func TestPriorityPool(t *testing.T) { expCap := curve.maxCapacity(func(cap uint64) int64 { return int64(c.balance / cap) }) + var ok bool + expFail := expCap + 10 if expFail < testMinCap { expFail = testMinCap } + ns.Operation(func() { ok = pp.requestCapacity(c.node, expFail, expFail, 0) == expFail }) + if ok { t.Errorf("Request for more than expected available capacity succeeded") } + if expCap >= testMinCap { ns.Operation(func() { ok = pp.requestCapacity(c.node, expCap, expCap, 0) == expCap }) + if !ok { t.Errorf("Request for expected available capacity failed") } } + c.balance -= add sumBalance -= add + pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0) pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0) + for _, c := range clients { raise(c) } @@ -168,6 +186,7 @@ func TestCapacityCurve(t *testing.T) { pp := newPriorityPool(ns, setup, clock, 400000, 0, 2, 2) ns.Start() pp.SetLimits(10, 10000000) + clients := make([]*ppTestClient, 10) for i := range clients { @@ -189,7 +208,9 @@ func TestCapacityCurve(t *testing.T) { cap := curve.maxCapacity(func(cap uint64) int64 { return int64(balance / cap) }) + var fail bool + if cap == 0 || expCap == 0 { fail = cap != expCap } else { @@ -197,6 +218,7 @@ func TestCapacityCurve(t *testing.T) { 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) } diff --git a/les/vflux/server/service.go b/les/vflux/server/service.go index 40515f072e..520139666a 100644 --- a/les/vflux/server/service.go +++ b/les/vflux/server/service.go @@ -66,6 +66,7 @@ func (s *Server) Register(b Service, id, desc string) { log.Error("Service ID contains ':'", "id", srv.id) return } + s.lock.Lock() s.services[srv.id] = srv s.lock.Unlock() @@ -89,6 +90,7 @@ func (s *Server) Serve(id enode.ID, address string, requests vflux.Requests) vfl // 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) @@ -97,6 +99,7 @@ func (s *Server) Serve(id enode.ID, address string, requests vflux.Requests) vfl s.lock.Unlock() time.Sleep(s.delayPerRequest * time.Duration(reqLen)) close(ch) + return results } @@ -106,11 +109,14 @@ func (s *Server) ServeEncoded(id enode.ID, addr *net.UDPAddr, req []byte) []byte 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 } diff --git a/les/vflux/server/status.go b/les/vflux/server/status.go index 2d7e25b684..ffa6c381b3 100644 --- a/les/vflux/server/status.go +++ b/les/vflux/server/status.go @@ -55,5 +55,6 @@ func newServerSetup() *serverSetup { setup.inactiveFlag = setup.setup.NewFlag("inactive") setup.capacityField = setup.setup.NewField("capacity", reflect.TypeOf(uint64(0))) setup.queueField = setup.setup.NewField("queue", reflect.TypeOf(&ppNodeInfo{})) + return setup } diff --git a/light/lightchain.go b/light/lightchain.go index 567a28b800..2c4cc04d2e 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -94,18 +94,23 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus. engine: engine, } bc.forker = core.NewForkChoice(bc, nil, checker) + var err error + bc.hc, err = core.NewHeaderChain(odr.Database(), config, bc.engine, bc.getProcInterrupt) if err != nil { return nil, err } + bc.genesisBlock, _ = bc.GetBlockByNumber(NoOdr, 0) if bc.genesisBlock == nil { return nil, core.ErrNoGenesis } + if checkpoint != nil { bc.AddTrustedCheckpoint(checkpoint) } + if err := bc.loadLastState(); err != nil { return nil, err } @@ -117,6 +122,7 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus. log.Info("Chain rewind was successful, resuming normal operation") } } + return bc, nil } @@ -126,13 +132,16 @@ func (lc *LightChain) AddTrustedCheckpoint(cp *params.TrustedCheckpoint) { StoreChtRoot(lc.chainDb, cp.SectionIndex, cp.SectionHead, cp.CHTRoot) lc.odr.ChtIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead) } + if lc.odr.BloomTrieIndexer() != nil { StoreBloomTrieRoot(lc.chainDb, cp.SectionIndex, cp.SectionHead, cp.BloomRoot) lc.odr.BloomTrieIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead) } + if lc.odr.BloomIndexer() != nil { lc.odr.BloomIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead) } + log.Info("Added trusted checkpoint", "block", (cp.SectionIndex+1)*lc.indexerConfig.ChtSize-1, "hash", cp.SectionHead) } @@ -169,6 +178,7 @@ func (lc *LightChain) loadLastState() error { header := lc.hc.CurrentHeader() headerTd := lc.GetTd(header.Hash(), header.Number.Uint64()) log.Info("Loaded most recent local header", "number", header.Number, "hash", header.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(header.Time), 0))) + return nil } @@ -179,6 +189,7 @@ func (lc *LightChain) SetHead(head uint64) error { defer lc.chainmu.Unlock() lc.hc.SetHead(head, nil, nil) + return lc.loadLastState() } @@ -190,6 +201,7 @@ func (lc *LightChain) SetHeadWithTimestamp(timestamp uint64) error { defer lc.chainmu.Unlock() lc.hc.SetHeadWithTimestamp(timestamp, nil, nil) + return lc.loadLastState() } @@ -217,9 +229,11 @@ func (lc *LightChain) ResetWithGenesisBlock(genesis *types.Block) { rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) rawdb.WriteBlock(batch, genesis) rawdb.WriteHeadHeaderHash(batch, genesis.Hash()) + if err := batch.Write(); err != nil { log.Crit("Failed to reset genesis block", "err", err) } + lc.genesisBlock = genesis lc.hc.SetGenesis(lc.genesisBlock.Header()) lc.hc.SetCurrentHeader(lc.genesisBlock.Header()) @@ -246,16 +260,19 @@ func (lc *LightChain) GetBody(ctx context.Context, hash common.Hash) (*types.Bod if cached, ok := lc.bodyCache.Get(hash); ok { return cached, nil } + number := lc.hc.GetBlockNumber(hash) if number == nil { return nil, errors.New("unknown block") } + body, err := GetBody(ctx, lc.odr, hash, *number) if err != nil { return nil, err } // Cache the found body for next time and return lc.bodyCache.Add(hash, body) + return body, nil } @@ -266,16 +283,19 @@ func (lc *LightChain) GetBodyRLP(ctx context.Context, hash common.Hash) (rlp.Raw if cached, ok := lc.bodyRLPCache.Get(hash); ok { return cached, nil } + number := lc.hc.GetBlockNumber(hash) if number == nil { return nil, errors.New("unknown block") } + body, err := GetBodyRLP(ctx, lc.odr, hash, *number) if err != nil { return nil, err } // Cache the found body for next time and return lc.bodyRLPCache.Add(hash, body) + return body, nil } @@ -293,12 +313,14 @@ func (lc *LightChain) GetBlock(ctx context.Context, hash common.Hash, number uin if block, ok := lc.blockCache.Get(hash); ok { return block, nil } + block, err := GetBlock(ctx, lc.odr, hash, number) if err != nil { return nil, err } // Cache the found block for next time and return lc.blockCache.Add(block.Hash(), block) + return block, nil } @@ -309,6 +331,7 @@ func (lc *LightChain) GetBlockByHash(ctx context.Context, hash common.Hash) (*ty if number == nil { return nil, errors.New("unknown block") } + return lc.GetBlock(ctx, hash, *number) } @@ -319,6 +342,7 @@ func (lc *LightChain) GetBlockByNumber(ctx context.Context, number uint64) (*typ if hash == (common.Hash{}) || err != nil { return nil, err } + return lc.GetBlock(ctx, hash, number) } @@ -328,6 +352,7 @@ func (lc *LightChain) Stop() { if !atomic.CompareAndSwapInt32(&lc.running, 0, 1) { return } + close(lc.quit) lc.StopInsert() lc.wg.Wait() @@ -348,6 +373,7 @@ func (lc *LightChain) Rollback(chain []common.Hash) { defer lc.chainmu.Unlock() batch := lc.chainDb.NewBatch() + for i := len(chain) - 1; i >= 0; i-- { hash := chain[i] @@ -360,6 +386,7 @@ func (lc *LightChain) Rollback(chain []common.Hash) { lc.hc.SetCurrentHeader(lc.GetHeader(head.ParentHash, head.Number.Uint64()-1)) } } + if err := batch.Write(); err != nil { log.Crit("Failed to rollback light chain", "error", err) } @@ -379,7 +406,9 @@ func (lc *LightChain) InsertHeader(header *types.Header) error { defer lc.wg.Done() _, err := lc.hc.WriteHeaders(headers) + log.Info("Inserted header", "number", header.Number, "hash", header.Hash()) + return err } @@ -398,6 +427,7 @@ func (lc *LightChain) SetCanonical(header *types.Header) error { lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()}) lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block}) log.Info("Set the chain head", "number", block.Number(), "hash", block.Hash()) + return nil } @@ -416,6 +446,7 @@ func (lc *LightChain) SetChainHead(header *types.Header) error { lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()}) lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block}) log.Info("Set the chain head", "number", block.Number(), "hash", block.Hash()) + return nil } @@ -434,10 +465,13 @@ func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i if len(chain) == 0 { return 0, nil } + if atomic.LoadInt32(&lc.disableCheckFreq) == 1 { checkFreq = 0 } + start := time.Now() + if i, err := lc.hc.ValidateHeaderChain(chain, checkFreq); err != nil { return i, err } @@ -459,6 +493,7 @@ func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i lastHeader = chain[len(chain)-1] block = types.NewBlockWithHeader(lastHeader) ) + switch status { case core.CanonStatTy: lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()}) @@ -466,6 +501,7 @@ func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i case core.SideStatTy: lc.chainSideFeed.Send(core.ChainSideEvent{Block: block}) } + return 0, err } @@ -488,7 +524,9 @@ func (lc *LightChain) GetTdOdr(ctx context.Context, hash common.Hash, number uin if td != nil { return td } + td, _ = GetTd(ctx, lc.odr, hash, number) + return td } @@ -536,6 +574,7 @@ func (lc *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64) ( if header := lc.hc.GetHeaderByNumber(number); header != nil { return header, nil } + return GetHeaderByNumber(ctx, lc.odr, number) } @@ -555,6 +594,7 @@ func (lc *LightChain) SyncCheckpoint(ctx context.Context, checkpoint *params.Tru if clique := lc.hc.Config().Clique; clique != nil { latest -= latest % clique.Epoch // epoch snapshot for clique } + if head >= latest { return true } @@ -569,8 +609,10 @@ func (lc *LightChain) SyncCheckpoint(ctx context.Context, checkpoint *params.Tru rawdb.WriteHeadHeaderHash(lc.chainDb, header.Hash()) lc.hc.SetCurrentHeader(header) } + return true } + return false } diff --git a/light/lightchain_test.go b/light/lightchain_test.go index 076f746eb8..0bb497f45a 100644 --- a/light/lightchain_test.go +++ b/light/lightchain_test.go @@ -43,9 +43,11 @@ func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) [ b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) }) headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { headers[i] = block.Header() } + return headers } @@ -65,6 +67,7 @@ func newCanonical(n int) (ethdb.Database, *LightChain, error) { // Header-only chain requested headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed) _, err := blockchain.InsertHeaderChain(headers, 1) + return db, blockchain, err } @@ -76,10 +79,12 @@ func newTestLightChain() *LightChain { Config: params.TestChainConfig, } gspec.MustCommit(db) + lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker(), nil, nil) if err != nil { panic(err) } + return lc } @@ -94,6 +99,7 @@ func testFork(t *testing.T, LightChain *LightChain, i, n int, comparator func(td var hash1, hash2 common.Hash hash1 = LightChain.GetHeaderByNumber(uint64(i)).Hash() hash2 = LightChain2.GetHeaderByNumber(uint64(i)).Hash() + if hash1 != hash2 { t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1) } @@ -104,11 +110,14 @@ func testFork(t *testing.T, LightChain *LightChain, i, n int, comparator func(td } // Sanity check that the forked chain can be imported into the original var tdPre, tdPost *big.Int + cur := LightChain.CurrentHeader() tdPre = LightChain.GetTd(cur.Hash(), cur.Number.Uint64()) + if err := testHeaderChainImport(headerChainB, LightChain); err != nil { t.Fatalf("failed to import forked header chain: %v", err) } + last := headerChainB[len(headerChainB)-1] tdPost = LightChain.GetTd(last.Hash(), last.Number.Uint64()) // Compare the total difficulties of the chains @@ -130,6 +139,7 @@ func testHeaderChainImport(chain []*types.Header, lightchain *LightChain) error rawdb.WriteHeader(lightchain.chainDb, header) lightchain.chainmu.Unlock() } + return nil } @@ -247,6 +257,7 @@ func TestBrokenHeaderChain(t *testing.T) { func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header { var chain []*types.Header + for i, difficulty := range d { header := &types.Header{ Coinbase: common.Address{seed}, @@ -261,8 +272,10 @@ func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types. } else { header.ParentHash = chain[i-1].Hash() } + chain = append(chain, types.CopyHeader(header)) } + return chain } @@ -322,8 +335,10 @@ func TestBadHeaderHashes(t *testing.T) { // Create a chain, ban a hash and try to import var err error + headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10) core.BadHashes[headers[2].Hash()] = true + if _, err = bc.InsertHeaderChain(headers, 1); !errors.Is(err, core.ErrBannedHash) { t.Errorf("error mismatch: have: %v, want %v", err, core.ErrBannedHash) } @@ -340,10 +355,13 @@ func TestReorgBadHeaderHashes(t *testing.T) { if _, err := bc.InsertHeaderChain(headers, 1); err != nil { t.Fatalf("failed to import headers: %v", err) } + if bc.CurrentHeader().Hash() != headers[3].Hash() { t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash()) } + core.BadHashes[headers[3].Hash()] = true + defer func() { delete(core.BadHashes, headers[3].Hash()) }() // Create a new LightChain and check that it rolled back the state. @@ -351,6 +369,7 @@ func TestReorgBadHeaderHashes(t *testing.T) { if err != nil { t.Fatalf("failed to create new chain manager: %v", err) } + if ncm.CurrentHeader().Hash() != headers[2].Hash() { t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash()) } diff --git a/light/nodeset.go b/light/nodeset.go index 3662596785..4e01b04181 100644 --- a/light/nodeset.go +++ b/light/nodeset.go @@ -51,6 +51,7 @@ func (db *NodeSet) Put(key []byte, value []byte) error { if _, ok := db.nodes[string(key)]; ok { return nil } + keystr := string(key) db.nodes[keystr] = common.CopyBytes(value) @@ -66,6 +67,7 @@ func (db *NodeSet) Delete(key []byte) error { defer db.lock.Unlock() delete(db.nodes, string(key)) + return nil } @@ -77,6 +79,7 @@ func (db *NodeSet) Get(key []byte) ([]byte, error) { if entry, ok := db.nodes[string(key)]; ok { return entry, nil } + return nil, errors.New("not found") } @@ -111,6 +114,7 @@ func (db *NodeSet) NodeList() NodeList { for _, key := range db.order { values = append(values, db.nodes[key]) } + return values } @@ -138,6 +142,7 @@ func (n NodeList) Store(db ethdb.KeyValueWriter) { func (n NodeList) NodeSet() *NodeSet { db := NewNodeSet() n.Store(db) + return db } @@ -158,5 +163,6 @@ func (n NodeList) DataSize() int { for _, node := range n { size += len(node) } + return size } diff --git a/light/odr_test.go b/light/odr_test.go index 76d5384c5f..8f067766c9 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -70,6 +70,7 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { if odr.disable { return ErrOdrDisabled } + switch req := req.(type) { case *BlockRequest: number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash) @@ -86,14 +87,17 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { err error t state.Trie ) + if len(req.Id.AccKey) > 0 { t, err = odr.serverState.OpenStorageTrie(req.Id.StateRoot, common.BytesToHash(req.Id.AccKey), req.Id.Root) } else { t, err = odr.serverState.OpenTrie(req.Id.Root) } + if err != nil { panic(err) } + nodes := NewNodeSet() t.Prove(req.Key, 0, nodes) req.Proof = nodes @@ -101,6 +105,7 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { req.Data = rawdb.ReadCode(odr.sdb, req.Hash) } req.StoreResult(odr.ldb) + return nil } @@ -119,10 +124,13 @@ func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc } else { block, _ = lc.GetBlockByHash(ctx, bhash) } + if block == nil { return nil, nil } + rlp, _ := rlp.EncodeToBytes(block) + return rlp, nil } @@ -130,6 +138,7 @@ func TestOdrGetReceiptsLes2(t *testing.T) { testChainOdr(t, 1, odrGetReceipts) } func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { var receipts types.Receipts + if bc != nil { number := rawdb.ReadHeaderNumber(db, bhash) if number != nil { @@ -141,10 +150,13 @@ func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, *number) } } + if receipts == nil { return nil, nil } + rlp, _ := rlp.EncodeToBytes(receipts) + return rlp, nil } @@ -155,6 +167,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr} var st *state.StateDB + if bc == nil { header := lc.GetHeaderByHash(bhash) st = NewState(ctx, header, lc.Odr()) @@ -164,11 +177,13 @@ func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc } var res []byte + for _, addr := range acc { bal := st.GetBalance(addr) rlp, _ := rlp.EncodeToBytes(bal) res = append(res, rlp...) } + return res, st.Error() } @@ -179,6 +194,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain config := params.TestChainConfig var res []byte + for i := 0; i < 3; i++ { data[35] = byte(i) @@ -187,6 +203,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain header *types.Header chain core.ChainContext ) + if bc == nil { chain = lc header = lc.GetHeaderByHash(bhash) @@ -216,16 +233,19 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain gp := new(core.GasPool).AddGas(math.MaxUint64) // nolint : contextcheck result, _ := core.ApplyMessage(vmenv, msg, gp, context.Background()) + res = append(res, result.Return()...) if st.Error() != nil { return res, st.Error() } } + return res, nil } func testChainGen(i int, block *core.BlockGen) { signer := types.HomesteadSigner{} + switch i { case 0: // In block 1, the test bank sends account #1 some ether. @@ -241,6 +261,7 @@ func testChainGen(i int, block *core.BlockGen) { nonce++ tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, block.BaseFee(), testContractCode), signer, acc1Key) testContractAddr = crypto.CreateAddress(acc1Addr, nonce) + block.AddTx(tx1) block.AddTx(tx2) block.AddTx(tx3) @@ -248,6 +269,7 @@ func testChainGen(i int, block *core.BlockGen) { // Block 3 is empty but was mined by account #2. block.SetCoinbase(acc2Addr) block.SetExtra([]byte("yeehaw")) + data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001") tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey) block.AddTx(tx) @@ -259,6 +281,7 @@ func testChainGen(i int, block *core.BlockGen) { b3 := block.PrevBlock(2).Header() b3.Extra = []byte("foo") block.AddUncle(b3) + data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey) block.AddTx(tx) @@ -278,20 +301,24 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { // Assemble the test environment blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil, nil) _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen) + if _, err := blockchain.InsertChain(gchain); err != nil { t.Fatal(err) } gspec.MustCommit(ldb) odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig} + lightchain, err := NewLightChain(odr, gspec.Config, ethash.NewFullFaker(), nil, nil) if err != nil { t.Fatal(err) } + headers := make([]*types.Header, len(gchain)) for i, block := range gchain { headers[i] = block.Header() } + if _, err := lightchain.InsertHeaderChain(headers, 1); err != nil { t.Fatal(err) } @@ -299,6 +326,7 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { test := func(expFail int) { for i := uint64(0); i <= blockchain.CurrentHeader().Number.Uint64(); i++ { bhash := rawdb.ReadCanonicalHash(sdb, i) + b1, err := fn(NoOdr, sdb, blockchain, nil, bhash) if err != nil { t.Fatalf("error in full-node test for block %d: %v", i, err) @@ -309,6 +337,7 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { exp := i < uint64(expFail) b2, err := fn(ctx, ldb, nil, lightchain, bhash) + if err != nil && exp { t.Errorf("error in ODR test for block %d: %v", i, err) } @@ -322,16 +351,22 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { // expect retrievals to fail (except genesis block) without a les peer t.Log("checking without ODR") + odr.disable = true + test(1) // expect all retrievals to pass with ODR enabled t.Log("checking with ODR") + odr.disable = false + test(len(gchain)) // still expect all retrievals to pass, now data should be cached locally t.Log("checking without ODR, should be cached") + odr.disable = true + test(len(gchain)) } diff --git a/light/odr_util.go b/light/odr_util.go index 786b54132a..6fcfacff39 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -54,6 +54,7 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ if number >= chts*odr.IndexerConfig().ChtSize { return nil, errNoTrustedCht } + r := &ChtRequest{ ChtRoot: GetChtRoot(db, chts-1, chtHead), ChtNum: chts - 1, @@ -63,6 +64,7 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ if err := odr.Retrieve(ctx, r); err != nil { return nil, err } + return r.Header, nil } @@ -72,6 +74,7 @@ func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (commo if hash != (common.Hash{}) { return hash, nil } + header, err := GetHeaderByNumber(ctx, odr, number) if err != nil { return common.Hash{}, err @@ -86,10 +89,12 @@ func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) if td != nil { return td, nil } + header, err := GetHeaderByNumber(ctx, odr, number) if err != nil { return nil, err } + if header.Hash() != hash { return nil, errNonCanonicalHash } @@ -107,13 +112,16 @@ 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 } + return r.Rlp, nil } @@ -124,10 +132,12 @@ func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint6 if err != nil { return nil, err } + body := new(types.Body) if err := rlp.Decode(bytes.NewReader(data), body); err != nil { return nil, err } + return body, nil } @@ -139,6 +149,7 @@ func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint if err != nil { return nil, errNoHeader } + body, err := GetBody(ctx, odr, hash, number) if err != nil { return nil, err @@ -157,13 +168,16 @@ 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 } + receipts = r.Receipts } // If the receipts are incomplete, fill the derived fields @@ -172,14 +186,17 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num if err != nil { return nil, err } + genesis := rawdb.ReadCanonicalHash(odr.Database(), 0) config := rawdb.ReadChainConfig(odr.Database(), genesis) if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.BaseFee(), block.Transactions()); err != nil { return nil, err } + rawdb.WriteReceipts(odr.Database(), hash, number, receipts) } + return receipts, nil } @@ -190,10 +207,12 @@ func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number if err != nil { return nil, err } + logs := make([][]*types.Log, len(receipts)) for i, receipt := range receipts { logs[i] = receipt.Logs } + return logs, nil } @@ -204,11 +223,13 @@ func GetUntrustedBlockLogs(ctx context.Context, odr OdrBackend, header *types.He // Retrieve the potentially incomplete receipts from disk or network hash, number := header.Hash(), header.Number.Uint64() receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number) + if receipts == nil { r := &ReceiptsRequest{Hash: hash, Number: number, Header: header, Untrusted: true} if err := odr.Retrieve(ctx, r); err != nil { return nil, err } + receipts = r.Receipts // Untrusted receipts won't be stored in the database. Therefore // derived fields computation is unnecessary. @@ -218,6 +239,7 @@ func GetUntrustedBlockLogs(ctx context.Context, odr OdrBackend, header *types.He for i, receipt := range receipts { logs[i] = receipt.Logs } + return logs, nil } @@ -230,6 +252,7 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint db = odr.Database() result = make([][]byte, len(sections)) ) + blooms, _, sectionHead := odr.BloomTrieIndexer().Sections() for i, section := range sections { sectionHead := rawdb.ReadCanonicalHash(db, (section+1)*odr.IndexerConfig().BloomSize-1) @@ -244,6 +267,7 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint if section >= blooms { return nil, errNoTrustedBloomTrie } + reqSections = append(reqSections, section) reqIndex = append(reqIndex, i) } @@ -262,9 +286,11 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint if err := odr.Retrieve(ctx, r); err != nil { return nil, err } + for i, idx := range reqIndex { result[idx] = r.BloomBits[i] } + return result, nil } @@ -279,15 +305,18 @@ func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*t if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != txpool.TxStatusIncluded { return nil, common.Hash{}, 0, 0, err } + pos := r.Status[0].Lookup // first ensure that we have the header, otherwise block body retrieval will fail // also verify if this is a canonical block by getting the header by number and checking its hash if header, err := GetHeaderByNumber(ctx, odr, pos.BlockIndex); err != nil || header.Hash() != pos.BlockHash { return nil, common.Hash{}, 0, 0, err } + body, err := GetBody(ctx, odr, pos.BlockHash, pos.BlockIndex) if err != nil || uint64(len(body.Transactions)) <= pos.Index || body.Transactions[pos.Index].Hash() != txHash { return nil, common.Hash{}, 0, 0, err } + return body.Transactions[pos.Index], pos.BlockHash, pos.BlockIndex, pos.Index, nil } diff --git a/light/postprocess.go b/light/postprocess.go index 763ba27529..a660db29b6 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -113,14 +113,17 @@ type ChtNode struct { // GetChtRoot reads the CHT root associated to the given section from the database func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { var encNumber [8]byte + binary.BigEndian.PutUint64(encNumber[:], sectionIdx) data, _ := db.Get(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...)) + return common.BytesToHash(data) } // StoreChtRoot writes the CHT root associated to the given section into the database func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { var encNumber [8]byte + binary.BigEndian.PutUint64(encNumber[:], sectionIdx) db.Put(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) } @@ -147,6 +150,7 @@ func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, dis sectionSize: size, disablePruning: disablePruning, } + return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.ChtIndexTablePrefix)), backend, size, confirms, time.Millisecond*100, "cht") } @@ -155,6 +159,7 @@ func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, dis func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { batch := c.trieTable.NewBatch() r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1, Config: c.odr.IndexerConfig()} + for { err := c.odr.Retrieve(ctx, r) switch err { @@ -181,6 +186,7 @@ func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSecti if section > 0 { root = GetChtRoot(c.diskdb, section-1, lastSectionHead) } + var err error c.trie, err = trie.New(trie.TrieID(root), c.triedb) @@ -190,7 +196,9 @@ func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSecti c.trie, err = trie.New(trie.TrieID(root), c.triedb) } } + c.section = section + return err } @@ -203,9 +211,13 @@ func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) e if td == nil { panic(nil) } + var encNumber [8]byte + binary.BigEndian.PutUint64(encNumber[:], num) + data, _ := rlp.EncodeToBytes(ChtNode{hash, td}) + return c.trie.Update(encNumber[:], data) } @@ -217,12 +229,14 @@ func (c *ChtIndexerBackend) Commit() error { if err := c.triedb.Update(trie.NewWithNodeSet(nodes)); err != nil { return err } + if err := c.triedb.Commit(root, false); err != nil { return err } } // Re-create trie with newly generated root and updated database. var err error + c.trie, err = trie.New(trie.TrieID(root), c.triedb) if err != nil { return err @@ -237,28 +251,36 @@ func (c *ChtIndexerBackend) Commit() error { batch = c.trieTable.NewBatch() t = time.Now() ) + hashes := make(map[common.Hash]struct{}) + if nodes != nil { for _, hash := range nodes.Hashes() { hashes[hash] = struct{}{} } } + for it.Next() { trimmed := bytes.TrimPrefix(it.Key(), rawdb.ChtTablePrefix) if len(trimmed) == common.HashLength { if _, ok := hashes[common.BytesToHash(trimmed)]; !ok { batch.Delete(trimmed) + deleted += 1 } } } + if err := batch.Write(); err != nil { return err } + log.Debug("Prune historical CHT trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t))) } + log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root)) StoreChtRoot(c.diskdb, c.section, c.lastHash, root) + return nil } @@ -269,16 +291,19 @@ func (c *ChtIndexerBackend) Prune(threshold uint64) error { if c.disablePruning { return nil } + t := time.Now() // Always keep genesis header in database. start, end := uint64(1), (threshold+1)*c.sectionSize var batch = c.diskdb.NewBatch() + for { numbers, hashes := rawdb.ReadAllCanonicalHashes(c.diskdb, start, end, 10240) if len(numbers) == 0 { break } + for i := 0; i < len(numbers); i++ { // Keep hash<->number mapping in database otherwise the hash based // API(e.g. GetReceipt, GetLogs) will be broken. @@ -291,32 +316,41 @@ func (c *ChtIndexerBackend) Prune(threshold uint64) error { rawdb.DeleteCanonicalHash(batch, numbers[i]) rawdb.DeleteBlockWithoutNumber(batch, hashes[i], numbers[i]) } + if batch.ValueSize() > ethdb.IdealBatchSize { if err := batch.Write(); err != nil { return err } + batch.Reset() } + start = numbers[len(numbers)-1] + 1 } + if err := batch.Write(); err != nil { return err } + log.Debug("Prune history headers", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(t))) + return nil } // GetBloomTrieRoot reads the BloomTrie root associated to the given section from the database func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { var encNumber [8]byte + binary.BigEndian.PutUint64(encNumber[:], sectionIdx) data, _ := db.Get(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...)) + return common.BytesToHash(data) } // StoreBloomTrieRoot writes the BloomTrie root associated to the given section into the database func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { var encNumber [8]byte + binary.BigEndian.PutUint64(encNumber[:], sectionIdx) db.Put(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) } @@ -349,6 +383,7 @@ func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uin } backend.bloomTrieRatio = size / parentSize backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio) + return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.BloomTrieIndexPrefix)), backend, size, 0, time.Millisecond*100, "bloomtrie") } @@ -356,15 +391,19 @@ func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uin // ODR backend in order to be able to add new entries and calculate subsequent root hashes func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { indexCh := make(chan uint, types.BloomBitLength) + type res struct { nodes *NodeSet err error } + resCh := make(chan res, types.BloomBitLength) + for i := 0; i < 20; i++ { go func() { for bitIndex := range indexCh { r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIndexList: []uint64{section - 1}, Config: b.odr.IndexerConfig()} + for { if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers { // if there are no peers to serve, retry later @@ -383,18 +422,23 @@ func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section } }() } + for i := uint(0); i < types.BloomBitLength; i++ { indexCh <- i } close(indexCh) + batch := b.trieTable.NewBatch() + for i := uint(0); i < types.BloomBitLength; i++ { res := <-resCh if res.err != nil { return res.err } + res.nodes.Store(batch) } + return batch.Write() } @@ -404,7 +448,9 @@ func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, las if section > 0 { root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead) } + var err error + b.trie, err = trie.New(trie.TrieID(root), b.triedb) if err != nil && b.odr != nil { err = b.fetchMissingNodes(ctx, section, root) @@ -412,7 +458,9 @@ func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, las b.trie, err = trie.New(trie.TrieID(root), b.triedb) } } + b.section = section + return err } @@ -422,6 +470,7 @@ func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Hea if (num+1)%b.parentSize == 0 { b.sectionHeads[num/b.parentSize] = header.Hash() } + return nil } @@ -431,20 +480,26 @@ func (b *BloomTrieIndexerBackend) Commit() error { for i := uint(0); i < types.BloomBitLength; i++ { var encKey [10]byte + binary.BigEndian.PutUint16(encKey[0:2], uint16(i)) binary.BigEndian.PutUint64(encKey[2:10], b.section) + var decomp []byte + for j := uint64(0); j < b.bloomTrieRatio; j++ { data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j]) if err != nil { return err } + decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSize/8)) if err2 != nil { return err2 } + decomp = append(decomp, decompData...) } + comp := bitutil.CompressBytes(decomp) decompSize += uint64(len(decomp)) @@ -456,22 +511,26 @@ func (b *BloomTrieIndexerBackend) Commit() error { } else { terr = b.trie.Delete(encKey[:]) } + if terr != nil { return terr } } + root, nodes := b.trie.Commit(false) // Commit trie changes into trie database in case it's not nil. if nodes != nil { if err := b.triedb.Update(trie.NewWithNodeSet(nodes)); err != nil { return err } + if err := b.triedb.Commit(root, false); err != nil { return err } } // Re-create trie with newly generated root and updated database. var err error + b.trie, err = trie.New(trie.TrieID(root), b.triedb) if err != nil { return err @@ -486,26 +545,33 @@ func (b *BloomTrieIndexerBackend) Commit() error { batch = b.trieTable.NewBatch() t = time.Now() ) + hashes := make(map[common.Hash]struct{}) + if nodes != nil { for _, hash := range nodes.Hashes() { hashes[hash] = struct{}{} } } + for it.Next() { trimmed := bytes.TrimPrefix(it.Key(), rawdb.BloomTrieTablePrefix) if len(trimmed) == common.HashLength { if _, ok := hashes[common.BytesToHash(trimmed)]; !ok { batch.Delete(trimmed) + deleted += 1 } } } + if err := batch.Write(); err != nil { return err } + log.Debug("Prune historical bloom trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t))) } + sectionHead := b.sectionHeads[b.bloomTrieRatio-1] StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root) log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize)) @@ -520,10 +586,13 @@ func (b *BloomTrieIndexerBackend) Prune(threshold uint64) error { if b.disablePruning { return nil } + start := time.Now() + for i := uint(0); i < types.BloomBitLength; i++ { rawdb.DeleteBloombits(b.diskdb, i, 0, threshold*b.bloomTrieRatio+b.bloomTrieRatio) } log.Debug("Prune history bloombits", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(start))) + return nil } diff --git a/light/trie.go b/light/trie.go index 38dd6b5c27..ac36f690a2 100644 --- a/light/trie.go +++ b/light/trie.go @@ -65,6 +65,7 @@ func (db *odrDatabase) CopyTrie(t state.Trie) state.Trie { if t.trie != nil { cpy.trie = t.trie.Copy() } + return cpy default: panic(fmt.Errorf("unknown trie type %T", t)) @@ -75,14 +76,17 @@ func (db *odrDatabase) ContractCode(addrHash, codeHash common.Hash) ([]byte, err if codeHash == sha3Nil { return nil, nil } + code := rawdb.ReadCode(db.backend.Database(), codeHash) if len(code) != 0 { return code, nil } + id := *db.id id.AccKey = addrHash[:] req := &CodeRequest{Id: &id, Hash: codeHash} err := db.backend.Retrieve(db.ctx, req) + return req.Data, err } @@ -107,36 +111,45 @@ type odrTrie struct { func (t *odrTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { key = crypto.Keccak256(key) + var res []byte + err := t.do(key, func() (err error) { res, err = t.trie.Get(key) return err }) + return res, err } func (t *odrTrie) GetAccount(address common.Address) (*types.StateAccount, error) { var res types.StateAccount + key := crypto.Keccak256(address.Bytes()) err := t.do(key, func() (err error) { value, err := t.trie.Get(key) if err != nil { return err } + if value == nil { return nil } + return rlp.DecodeBytes(value, &res) }) + return &res, err } func (t *odrTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error { key := crypto.Keccak256(address.Bytes()) + value, err := rlp.EncodeToBytes(acc) if err != nil { return fmt.Errorf("decoding error in account update: %w", err) } + return t.do(key, func() error { return t.trie.Update(key, value) }) @@ -144,6 +157,7 @@ func (t *odrTrie) UpdateAccount(address common.Address, acc *types.StateAccount) func (t *odrTrie) UpdateStorage(_ common.Address, key, value []byte) error { key = crypto.Keccak256(key) + return t.do(key, func() error { return t.trie.Update(key, value) }) @@ -151,6 +165,7 @@ func (t *odrTrie) UpdateStorage(_ common.Address, key, value []byte) error { func (t *odrTrie) DeleteStorage(_ common.Address, key []byte) error { key = crypto.Keccak256(key) + return t.do(key, func() error { return t.trie.Delete(key) }) @@ -159,6 +174,7 @@ func (t *odrTrie) DeleteStorage(_ common.Address, key []byte) error { // TryDeleteAccount abstracts an account deletion from the trie. func (t *odrTrie) DeleteAccount(address common.Address) error { key := crypto.Keccak256(address.Bytes()) + return t.do(key, func() error { return t.trie.Delete(key) }) @@ -168,6 +184,7 @@ func (t *odrTrie) Commit(collectLeaf bool) (common.Hash, *trie.NodeSet) { if t.trie == nil { return t.id.Root, nil } + return t.trie.Commit(collectLeaf) } @@ -175,6 +192,7 @@ func (t *odrTrie) Hash() common.Hash { if t.trie == nil { return t.id.Root } + return t.trie.Hash() } @@ -195,6 +213,7 @@ func (t *odrTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter func (t *odrTrie) do(key []byte, fn func() error) error { for { var err error + if t.trie == nil { var id *trie.ID if len(t.id.AccKey) > 0 { @@ -202,14 +221,18 @@ func (t *odrTrie) do(key []byte, fn func() error) error { } else { id = trie.StateTrieID(t.id.StateRoot) } + t.trie, err = trie.New(id, trie.NewDatabase(t.db.backend.Database())) } + if err == nil { err = fn() } + if _, ok := err.(*trie.MissingNodeError); !ok { return err } + r := &TrieRequest{Id: t.id, Key: key} if err := t.db.backend.Retrieve(t.db.ctx, r); err != nil { return err @@ -234,43 +257,54 @@ func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator { } else { id = trie.StateTrieID(t.id.StateRoot) } + t, err := trie.New(id, trie.NewDatabase(t.db.backend.Database())) if err == nil { it.t.trie = t } + return err }) } + it.do(func() error { it.NodeIterator = it.t.trie.NodeIterator(startkey) return it.NodeIterator.Error() }) + return it } func (it *nodeIterator) Next(descend bool) bool { var ok bool + it.do(func() error { ok = it.NodeIterator.Next(descend) return it.NodeIterator.Error() }) + return ok } // do runs fn and attempts to fill in missing nodes by retrieving. func (it *nodeIterator) do(fn func() error) { var lasthash common.Hash + for { it.err = fn() + missing, ok := it.err.(*trie.MissingNodeError) if !ok { return } + if missing.NodeHash == lasthash { it.err = fmt.Errorf("retrieve loop for trie node %x", missing.NodeHash) return } + lasthash = missing.NodeHash + r := &TrieRequest{Id: it.t.id, Key: nibblesToKey(missing.Path)} if it.err = it.t.db.backend.Retrieve(it.t.db.ctx, r); it.err != nil { return @@ -282,6 +316,7 @@ func (it *nodeIterator) Error() error { if it.err != nil { return it.err } + return it.NodeIterator.Error() } @@ -289,12 +324,15 @@ func nibblesToKey(nib []byte) []byte { if len(nib) > 0 && nib[len(nib)-1] == 0x10 { nib = nib[:len(nib)-1] // drop terminator } + if len(nib)&1 == 1 { nib = append(nib, 0) // make even } + key := make([]byte, len(nib)/2) for bi, ni := 0, 0; ni < len(nib); bi, ni = bi+1, ni+2 { key[bi] = nib[ni]<<4 | nib[ni+1] } + return key } diff --git a/light/trie_test.go b/light/trie_test.go index 477883b886..1caf5c5027 100644 --- a/light/trie_test.go +++ b/light/trie_test.go @@ -43,18 +43,22 @@ func TestNodeIterator(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), } ) + blockchain, _ := core.NewBlockChain(fulldb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil, nil) _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen) + if _, err := blockchain.InsertChain(gchain); err != nil { panic(err) } gspec.MustCommit(lightdb) + ctx := context.Background() odr := &testOdr{sdb: fulldb, ldb: lightdb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig} head := blockchain.CurrentHeader() lightTrie, _ := NewStateDatabase(ctx, head, odr).OpenTrie(head.Root) fullTrie, _ := blockchain.StateCache().OpenTrie(head.Root) + if err := diffTries(fullTrie, lightTrie); err != nil { t.Fatal(err) } @@ -63,15 +67,18 @@ func TestNodeIterator(t *testing.T) { func diffTries(t1, t2 state.Trie) error { i1 := trie.NewIterator(t1.NodeIterator(nil)) i2 := trie.NewIterator(t2.NodeIterator(nil)) + for i1.Next() && i2.Next() { if !bytes.Equal(i1.Key, i2.Key) { spew.Dump(i2) return fmt.Errorf("tries have different keys %x, %x", i1.Key, i2.Key) } + if !bytes.Equal(i1.Value, i2.Value) { return fmt.Errorf("tries differ at key %x", i1.Key) } } + switch { case i1.Err != nil: return fmt.Errorf("full trie iterator error: %v", i1.Err) @@ -82,5 +89,6 @@ func diffTries(t1, t2 state.Trie) error { case i2.Next(): return fmt.Errorf("light trie iterator has more k/v pairs") } + return nil } diff --git a/light/txpool.go b/light/txpool.go index e59dc3e774..b83b244757 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -124,16 +124,20 @@ func (pool *TxPool) currentState(ctx context.Context) *state.StateDB { func (pool *TxPool) GetNonce(ctx context.Context, addr common.Address) (uint64, error) { state := pool.currentState(ctx) nonce := state.GetNonce(addr) + if state.Error() != nil { return 0, state.Error() } + sn, ok := pool.nonce[addr] if ok && sn > nonce { nonce = sn } + if !ok || sn < nonce { pool.nonce[addr] = nonce } + return nonce, nil } @@ -160,6 +164,7 @@ func (txc txStateChanges) getLists() (mined []common.Hash, rollback []common.Has rollback = append(rollback, hash) } } + return } @@ -171,12 +176,14 @@ func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number if len(pool.pending) == 0 { return nil } + block, err := GetBlock(ctx, pool.odr, hash, number) if err != nil { return err } // Gather all the local transaction mined in this block list := pool.mined[hash] + for _, tx := range block.Transactions() { if _, ok := pool.pending[tx.Hash()]; ok { list = append(list, tx) @@ -188,6 +195,7 @@ func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results return err } + rawdb.WriteTxLookupEntriesByBlock(pool.chainDb, block) // Update the transaction pool's state @@ -195,8 +203,10 @@ func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number delete(pool.pending, tx.Hash()) txc.setState(tx.Hash(), true) } + pool.mined[hash] = list } + return nil } @@ -204,15 +214,20 @@ func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number // as rolled back. It also removes any positional lookup entries. func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) { batch := pool.chainDb.NewBatch() + if list, ok := pool.mined[hash]; ok { for _, tx := range list { txHash := tx.Hash() rawdb.DeleteTxLookupEntry(batch, txHash) + pool.pending[txHash] = tx + txc.setState(txHash, false) } + delete(pool.mined, hash) } + batch.Write() } @@ -228,13 +243,16 @@ func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) newh := newHeader // find common ancestor, create list of rolled back and new block hashes var oldHashes, newHashes []common.Hash + for oldh.Hash() != newh.Hash() { if oldh.Number.Uint64() >= newh.Number.Uint64() { oldHashes = append(oldHashes, oldh.Hash()) oldh = pool.chain.GetHeader(oldh.ParentHash, oldh.Number.Uint64()-1) } + if oldh.Number.Uint64() < newh.Number.Uint64() { newHashes = append(newHashes, newh.Hash()) + newh = pool.chain.GetHeader(newh.ParentHash, newh.Number.Uint64()-1) if newh == nil { // happens when CHT syncing, nothing to do @@ -242,6 +260,7 @@ func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) } } } + if oldh.Number.Uint64() < pool.clearIdx { pool.clearIdx = oldh.Number.Uint64() } @@ -249,6 +268,7 @@ func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) for _, hash := range oldHashes { pool.rollbackTxs(hash, txc) } + pool.head = oldh.Hash() // check mined txs of new blocks (array is in reversed order) for i := len(newHashes) - 1; i >= 0; i-- { @@ -256,6 +276,7 @@ func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) if err := pool.checkMinedTxs(ctx, hash, newHeader.Number.Uint64()-uint64(i), txc); err != nil { return txc, err } + pool.head = hash } @@ -270,11 +291,13 @@ func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) for i, tx := range list { hashes[i] = tx.Hash() } + pool.relay.Discard(hashes) delete(pool.mined, hash) } } } + pool.clearIdx = idx2 } @@ -343,6 +366,7 @@ func (pool *TxPool) Stats() (pending int) { defer pool.mu.RUnlock() pending = len(pool.pending) + return } @@ -390,9 +414,11 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error if err != nil { return err } + if tx.Gas() < gas { return core.ErrIntrinsicGas } + return currentState.Error() } @@ -404,6 +430,7 @@ func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error { if pool.pending[hash] != nil { return fmt.Errorf("known transaction (%x)", hash[:4]) } + err := pool.validateTx(ctx, tx) if err != nil { return err @@ -427,6 +454,7 @@ func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error { // Print a log message if low enough level is set log.Debug("Pooled new transaction", "hash", hash, "from", log.Lazy{Fn: func() common.Address { from, _ := types.Sender(pool.signer, tx); return from }}, "to", tx.To()) + return nil } @@ -435,6 +463,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 := tx.MarshalBinary() if err != nil { return err @@ -447,6 +476,7 @@ func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error { pool.relay.Send(types.Transactions{tx}) pool.chainDb.Put(tx.Hash().Bytes(), data) + return nil } @@ -455,6 +485,7 @@ func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error { func (pool *TxPool) AddBatch(ctx context.Context, txs []*types.Transaction) { pool.mu.Lock() defer pool.mu.Unlock() + var sendTx types.Transactions for _, tx := range txs { @@ -462,6 +493,7 @@ func (pool *TxPool) AddBatch(ctx context.Context, txs []*types.Transaction) { sendTx = append(sendTx, tx) } } + if len(sendTx) > 0 { pool.relay.Send(sendTx) } @@ -474,6 +506,7 @@ func (pool *TxPool) GetTransaction(hash common.Hash) *types.Transaction { if tx, ok := pool.pending[hash]; ok { return tx } + return nil } @@ -485,10 +518,12 @@ func (pool *TxPool) GetTransactions() (txs types.Transactions, err error) { txs = make(types.Transactions, len(pool.pending)) i := 0 + for _, tx := range pool.pending { txs[i] = tx i++ } + return txs, nil } @@ -506,6 +541,7 @@ func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common } // There are no queued transactions in a light pool, just return an empty map queued := make(map[common.Address]types.Transactions) + return pending, queued } @@ -522,6 +558,7 @@ func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types. if account != addr { continue } + pending = append(pending, tx) } // There are no queued transactions in a light pool, just return an empty map @@ -534,13 +571,16 @@ func (pool *TxPool) RemoveTransactions(txs types.Transactions) { defer pool.mu.Unlock() var hashes []common.Hash + batch := pool.chainDb.NewBatch() + for _, tx := range txs { hash := tx.Hash() delete(pool.pending, hash) batch.Delete(hash.Bytes()) hashes = append(hashes, hash) } + batch.Write() pool.relay.Discard(hashes) } diff --git a/light/txpool_test.go b/light/txpool_test.go index d815435ff8..203973c0ee 100644 --- a/light/txpool_test.go +++ b/light/txpool_test.go @@ -69,6 +69,7 @@ func minedTx(i int) int { func txPoolTestChainGen(i int, block *core.BlockGen) { s := minedTx(i) + e := minedTx(i + 1) for i := s; i < e; i++ { block.AddTx(testTx[i]) @@ -92,6 +93,7 @@ func TestTxPool(t *testing.T) { // Assemble the test environment blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil, nil) _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), poolTestBlocks, txPoolTestChainGen) + if _, err := blockchain.InsertChain(gchain); err != nil { panic(err) } @@ -106,17 +108,21 @@ func TestTxPool(t *testing.T) { lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), nil, nil) txPermanent = 50 pool := NewTxPool(params.TestChainConfig, lightchain, relay) + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() for ii, block := range gchain { i := ii + 1 s := sentTx(i - 1) + e := sentTx(i) for i := s; i < e; i++ { pool.Add(ctx, testTx[i]) + got := <-relay.send exp := 1 + if got != exp { t.Errorf("relay.Send expected len = %d, got %d", exp, got) } @@ -128,6 +134,7 @@ func TestTxPool(t *testing.T) { got := <-relay.mined exp := minedTx(i) - minedTx(i-1) + if got != exp { t.Errorf("relay.NewHead expected len(mined) = %d, got %d", exp, got) } @@ -136,6 +143,7 @@ func TestTxPool(t *testing.T) { if i > int(txPermanent)+1 { exp = minedTx(i-int(txPermanent)-1) - minedTx(i-int(txPermanent)-2) } + if exp != 0 { got = <-relay.discard if got != exp { diff --git a/log/format.go b/log/format.go index 20314b2144..36bccf9025 100644 --- a/log/format.go +++ b/log/format.go @@ -89,7 +89,9 @@ type TerminalStringer interface { func TerminalFormat(usecolor bool) Format { return FormatFunc(func(r *Record) []byte { msg := escapeMessage(r.Msg) + var color = 0 + if usecolor { switch r.Lvl { case LvlCrit: @@ -109,6 +111,7 @@ func TerminalFormat(usecolor bool) Format { b := &bytes.Buffer{} lvl := r.Lvl.AlignedString() + if atomic.LoadUint32(&locationEnabled) != 0 { // Log origin printing was requested, format the location path and line number location := fmt.Sprintf("%+v", r.Call) @@ -121,6 +124,7 @@ func TerminalFormat(usecolor bool) Format { align = len(location) atomic.StoreUint32(&locationLength, uint32(align)) } + padding := strings.Repeat(" ", align-len(location)) // Assemble and print the log heading @@ -143,6 +147,7 @@ func TerminalFormat(usecolor bool) Format { } // print the keys logfmt style logfmt(b, r.Ctx, color, true) + return b.Bytes() }) } @@ -156,6 +161,7 @@ func LogfmtFormat() Format { common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} buf := &bytes.Buffer{} logfmt(buf, append(common, r.Ctx...), 0, false) + return buf.Bytes() }) } @@ -168,6 +174,7 @@ func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) { k, ok := ctx[i].(string) v := formatLogfmtValue(ctx[i+1], term) + if !ok { k, v = errorKey, formatLogfmtValue(k, term) } else { @@ -187,13 +194,16 @@ func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) { fieldPadding[k] = padding fieldPaddingLock.Unlock() } + if color > 0 { fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, k) } else { buf.WriteString(k) buf.WriteByte('=') } + buf.WriteString(v) + if i < len(ctx)-2 && padding > length { buf.Write(bytes.Repeat([]byte{' '}, padding-length)) } @@ -217,6 +227,7 @@ func JSONFormatOrderedEx(pretty, lineSeparated bool) Format { return json.MarshalIndent(v, "", " ") } } + return FormatFunc(func(r *Record) []byte { props := make(map[string]interface{}) @@ -225,14 +236,17 @@ func JSONFormatOrderedEx(pretty, lineSeparated bool) Format { props[r.KeyNames.Msg] = r.Msg ctx := make([]string, len(r.Ctx)) + for i := 0; i < len(r.Ctx); i += 2 { k, ok := r.Ctx[i].(string) if !ok { props[errorKey] = fmt.Sprintf("%+v is not a string key,", r.Ctx[i]) } + ctx[i] = k ctx[i+1] = formatLogfmtValue(r.Ctx[i+1], true) } + props[r.KeyNames.Ctx] = ctx b, err := jsonMarshal(props) @@ -240,11 +254,14 @@ func JSONFormatOrderedEx(pretty, lineSeparated bool) Format { b, _ = jsonMarshal(map[string]string{ errorKey: err.Error(), }) + return b } + if lineSeparated { b = append(b, '\n') } + return b }) } @@ -272,6 +289,7 @@ func JSONFormatEx(pretty, lineSeparated bool) Format { if !ok { props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) } + props[k] = formatJSONValue(r.Ctx[i+1]) } @@ -280,6 +298,7 @@ func JSONFormatEx(pretty, lineSeparated bool) Format { b, _ = jsonMarshal(map[string]string{ errorKey: err.Error(), }) + return b } @@ -346,6 +365,7 @@ func formatLogfmtValue(value interface{}, term bool) string { if v == nil { return "" } + return formatLogfmtBigInt(v) case *uint256.Int: @@ -357,12 +377,14 @@ func formatLogfmtValue(value interface{}, term bool) string { return formatLogfmtUint256(v) } + if term { if s, ok := value.(TerminalStringer); ok { // Custom terminal stringer provided, use that return escapeString(s.TerminalString()) } } + value = formatShared(value) switch v := value.(type) { case bool: @@ -404,6 +426,7 @@ func FormatLogfmtInt64(n int64) string { if n < 0 { return formatLogfmtUint64(uint64(-n), true) } + return formatLogfmtUint64(uint64(n), false) } @@ -429,6 +452,7 @@ func formatLogfmtUint64(n uint64, neg bool) string { i = maxLength - 1 comma = 0 ) + for ; n > 0; i-- { if comma == 3 { comma = 0 @@ -439,10 +463,12 @@ func formatLogfmtUint64(n uint64, neg bool) string { n /= 10 } } + if neg { out[i] = '-' i-- } + return string(out[i+1:]) } @@ -451,6 +477,7 @@ func formatLogfmtBigInt(n *big.Int) string { if n.IsUint64() { return FormatLogfmtUint64(n.Uint64()) } + if n.IsInt64() { return FormatLogfmtInt64(n.Int64()) } @@ -461,6 +488,7 @@ func formatLogfmtBigInt(n *big.Int) string { comma = 0 i = len(buf) - 1 ) + for j := len(text) - 1; j >= 0; j, i = j-1, i-1 { c := text[j] @@ -471,12 +499,14 @@ func formatLogfmtBigInt(n *big.Int) string { buf[i] = ',' i-- comma = 0 + fallthrough default: buf[i] = c comma++ } } + return string(buf[i+1:]) } @@ -519,6 +549,7 @@ func formatLogfmtUint256(n *uint256.Int) string { // calls strconv.Quote if needed func escapeString(s string) string { needsQuoting := false + for _, r := range s { // We quote everything below " (0x22) and above~ (0x7E), plus equal-sign if r <= '"' || r > '~' || r == '=' { @@ -526,9 +557,11 @@ func escapeString(s string) string { break } } + if !needsQuoting { return s } + return strconv.Quote(s) } diff --git a/log/format_test.go b/log/format_test.go index c161ff7dbd..ba84fe6cd4 100644 --- a/log/format_test.go +++ b/log/format_test.go @@ -107,6 +107,7 @@ var sink string func BenchmarkPrettyInt64Logfmt(b *testing.B) { b.ReportAllocs() + for i := 0; i < b.N; i++ { sink = FormatLogfmtInt64(rand.Int63()) } @@ -114,6 +115,7 @@ func BenchmarkPrettyInt64Logfmt(b *testing.B) { func BenchmarkPrettyUint64Logfmt(b *testing.B) { b.ReportAllocs() + for i := 0; i < b.N; i++ { sink = FormatLogfmtUint64(rand.Uint64()) } diff --git a/log/handler.go b/log/handler.go index 2aaedf73e4..feb60b57fc 100644 --- a/log/handler.go +++ b/log/handler.go @@ -51,6 +51,7 @@ func StreamHandler(wr io.Writer, fmtr Format) Handler { _, err := wr.Write(fmtr.Format(r)) return err }, LvlTrace) + return LazyHandler(SyncHandler(h)) } @@ -59,6 +60,7 @@ func StreamHandler(wr io.Writer, fmtr Format) Handler { // for thread-safe concurrent writes. func SyncHandler(h Handler) Handler { var mu sync.Mutex + return FuncHandler(func(r *Record) error { mu.Lock() defer mu.Unlock() @@ -76,6 +78,7 @@ func FileHandler(path string, fmtr Format) (Handler, error) { if err != nil { return nil, err } + return closingHandler{f, StreamHandler(f, fmtr)}, nil } @@ -136,6 +139,7 @@ func CallerStackHandler(format string, h Handler) Handler { if len(s) > 0 { r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s)) } + return h.Log(r) }, h.Level()) } @@ -157,6 +161,7 @@ func FilterHandler(fn func(r *Record) bool, h Handler) Handler { if fn(r) { return h.Log(r) } + return nil }, h.Level()) } @@ -183,6 +188,7 @@ func MatchFilterHandler(key string, value interface{}, h Handler) Handler { return r.Ctx[i+1] == value } } + return false }, h) } @@ -213,6 +219,7 @@ func MultiHandler(hs ...Handler) Handler { // what to do about failures? h.Log(r) } + return nil }, LvlDebug) } @@ -240,6 +247,7 @@ func FailoverHandler(hs ...Handler) Handler { if err == nil { return nil } + r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) } @@ -282,6 +290,7 @@ func LazyHandler(h Handler) Handler { // go through the values (odd indices) and reassign // the values of any lazy fn to the result of its execution hadErr := false + for i := 1; i < len(r.Ctx); i += 2 { lz, ok := r.Ctx[i].(Lazy) if ok { @@ -293,6 +302,7 @@ func LazyHandler(h Handler) Handler { if cs, ok := v.(stack.CallStack); ok { v = cs.TrimBelow(r.Call).TrimRuntime() } + r.Ctx[i] = v } } @@ -322,14 +332,17 @@ func evaluateLazy(lz Lazy) (interface{}, error) { } value := reflect.ValueOf(lz.Fn) + results := value.Call([]reflect.Value{}) if len(results) == 1 { return results[0].Interface(), nil } + values := make([]interface{}, len(results)) for i, v := range results { values[i] = v.Interface() } + return values, nil } @@ -351,6 +364,7 @@ func must(h Handler, err error) Handler { if err != nil { panic(err) } + return h } diff --git a/log/handler_glog.go b/log/handler_glog.go index 67376d3d41..0e066d49c2 100644 --- a/log/handler_glog.go +++ b/log/handler_glog.go @@ -92,6 +92,7 @@ func (h *GlogHandler) Verbosity(level Lvl) { // sets V to 3 in all files of any packages whose import path contains "foo" func (h *GlogHandler) Vmodule(ruleset string) error { var filter []pattern + for _, rule := range strings.Split(ruleset, ",") { // Empty strings such as from a trailing comma can be ignored if len(rule) == 0 { @@ -102,8 +103,10 @@ func (h *GlogHandler) Vmodule(ruleset string) error { if len(parts) != 2 { return errVmoduleSyntax } + parts[0] = strings.TrimSpace(parts[0]) parts[1] = strings.TrimSpace(parts[1]) + if len(parts[0]) == 0 || len(parts[1]) == 0 { return errVmoduleSyntax } @@ -112,11 +115,13 @@ func (h *GlogHandler) Vmodule(ruleset string) error { if err != nil { return errVmoduleSyntax } + if level <= 0 { continue // Ignore. It's harmless but no point in paying the overhead. } // Compile the rule pattern into a regular expression matcher := ".*" + for _, comp := range strings.Split(parts[0], "/") { if comp == "*" { matcher += "(/.*)?" @@ -124,9 +129,11 @@ func (h *GlogHandler) Vmodule(ruleset string) error { matcher += "/" + regexp.QuoteMeta(comp) } } + if !strings.HasSuffix(parts[0], ".go") { matcher += "/[^/]+\\.go" } + matcher = matcher + "$" re, _ := regexp.Compile(matcher) @@ -154,8 +161,10 @@ func (h *GlogHandler) BacktraceAt(location string) error { if len(parts) != 2 { return errTraceSyntax } + parts[0] = strings.TrimSpace(parts[0]) parts[1] = strings.TrimSpace(parts[1]) + if len(parts[0]) == 0 || len(parts[1]) == 0 { return errTraceSyntax } @@ -163,6 +172,7 @@ func (h *GlogHandler) BacktraceAt(location string) error { if !strings.HasSuffix(parts[0], ".go") { return errTraceSyntax } + if _, err := strconv.Atoi(parts[1]); err != nil { return errTraceSyntax } @@ -225,9 +235,11 @@ func (h *GlogHandler) Log(r *Record) error { } h.lock.Unlock() } + if lvl >= r.Lvl { return h.origin.Log(r) } + return nil } diff --git a/log/logger.go b/log/logger.go index cd076b2a1e..4e4bc6e6ce 100644 --- a/log/logger.go +++ b/log/logger.go @@ -209,6 +209,7 @@ func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) { func (l *logger) New(ctx ...interface{}) Logger { child := &logger{newContext(l.ctx, ctx), new(swapHandler)} child.SetHandler(l.h) + return child } @@ -217,6 +218,7 @@ func newContext(prefix []interface{}, suffix []interface{}) []interface{} { newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) n := copy(newCtx, prefix) copy(newCtx[n:], normalizedSuffix) + return newCtx } @@ -327,6 +329,7 @@ func (c Ctx) toArray() []interface{} { arr := make([]interface{}, len(c)*2) i := 0 + for k, v := range c { arr[i] = k arr[i+1] = v diff --git a/log/syslog.go b/log/syslog.go index cfa7c8cc1b..b9d47f22cd 100644 --- a/log/syslog.go +++ b/log/syslog.go @@ -26,8 +26,10 @@ func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) if err != nil { return nil, err } + h := FuncHandler(func(r *Record) error { var syslogFn = sysWr.Info + switch r.Lvl { case LvlCrit: syslogFn = sysWr.Crit @@ -44,8 +46,10 @@ func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) } s := strings.TrimSpace(string(fmtr.Format(r))) + return syslogFn(s) }, LvlTrace) + return LazyHandler(&closingHandler{sysWr, h}), nil } diff --git a/metrics/counter.go b/metrics/counter.go index 0562061ff8..41e9df2501 100644 --- a/metrics/counter.go +++ b/metrics/counter.go @@ -19,6 +19,7 @@ func GetOrRegisterCounter(name string, r Registry) Counter { if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, NewCounter).(Counter) } @@ -30,6 +31,7 @@ func GetOrRegisterCounterForced(name string, r Registry) Counter { if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, NewCounterForced).(Counter) } @@ -51,10 +53,13 @@ func NewCounterForced() Counter { // NewRegisteredCounter constructs and registers a new StandardCounter. func NewRegisteredCounter(name string, r Registry) Counter { c := NewCounter() + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } @@ -64,10 +69,13 @@ func NewRegisteredCounter(name string, r Registry) Counter { // allow for garbage collection. func NewRegisteredCounterForced(name string, r Registry) Counter { c := NewCounterForced() + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } diff --git a/metrics/counter_test.go b/metrics/counter_test.go index af26ef1548..ecd43de7fb 100644 --- a/metrics/counter_test.go +++ b/metrics/counter_test.go @@ -4,7 +4,9 @@ import "testing" func BenchmarkCounter(b *testing.B) { c := NewCounter() + b.ResetTimer() + for i := 0; i < b.N; i++ { c.Inc(1) } @@ -14,6 +16,7 @@ func TestCounterClear(t *testing.T) { c := NewCounter() c.Inc(1) c.Clear() + if count := c.Count(); count != 0 { t.Errorf("c.Count(): 0 != %v\n", count) } @@ -22,6 +25,7 @@ func TestCounterClear(t *testing.T) { func TestCounterDec1(t *testing.T) { c := NewCounter() c.Dec(1) + if count := c.Count(); count != -1 { t.Errorf("c.Count(): -1 != %v\n", count) } @@ -30,6 +34,7 @@ func TestCounterDec1(t *testing.T) { func TestCounterDec2(t *testing.T) { c := NewCounter() c.Dec(2) + if count := c.Count(); count != -2 { t.Errorf("c.Count(): -2 != %v\n", count) } @@ -38,6 +43,7 @@ func TestCounterDec2(t *testing.T) { func TestCounterInc1(t *testing.T) { c := NewCounter() c.Inc(1) + if count := c.Count(); count != 1 { t.Errorf("c.Count(): 1 != %v\n", count) } @@ -46,6 +52,7 @@ func TestCounterInc1(t *testing.T) { func TestCounterInc2(t *testing.T) { c := NewCounter() c.Inc(2) + if count := c.Count(); count != 2 { t.Errorf("c.Count(): 2 != %v\n", count) } @@ -56,6 +63,7 @@ func TestCounterSnapshot(t *testing.T) { c.Inc(1) snapshot := c.Snapshot() c.Inc(1) + if count := snapshot.Count(); count != 1 { t.Errorf("c.Count(): 1 != %v\n", count) } @@ -71,6 +79,7 @@ func TestCounterZero(t *testing.T) { func TestGetOrRegisterCounter(t *testing.T) { r := NewRegistry() NewRegisteredCounter("foo", r).Inc(47) + if c := GetOrRegisterCounter("foo", r); c.Count() != 47 { t.Fatal(c) } diff --git a/metrics/cpu_enabled.go b/metrics/cpu_enabled.go index 2359028a21..d8ce26710f 100644 --- a/metrics/cpu_enabled.go +++ b/metrics/cpu_enabled.go @@ -32,6 +32,7 @@ func ReadCPUStats(stats *CPUStats) { log.Error("Could not read cpu stats", "err", err) return } + if len(timeStats) == 0 { log.Error("Empty cpu stats") return diff --git a/metrics/debug.go b/metrics/debug.go index de4a2739fe..0dd93d1349 100644 --- a/metrics/debug.go +++ b/metrics/debug.go @@ -38,11 +38,13 @@ func CaptureDebugGCStats(r Registry, d time.Duration) { func CaptureDebugGCStatsOnce(r Registry) { lastGC := gcStats.LastGC t := time.Now() + debug.ReadGCStats(&gcStats) debugMetrics.ReadGCStats.UpdateSince(t) debugMetrics.GCStats.LastGC.Update(gcStats.LastGC.UnixNano()) debugMetrics.GCStats.NumGC.Update(gcStats.NumGC) + if lastGC != gcStats.LastGC && 0 < len(gcStats.Pause) { debugMetrics.GCStats.Pause.Update(int64(gcStats.Pause[0])) } diff --git a/metrics/debug_test.go b/metrics/debug_test.go index 07eb867841..a097c0e2de 100644 --- a/metrics/debug_test.go +++ b/metrics/debug_test.go @@ -11,6 +11,7 @@ func BenchmarkDebugGCStats(b *testing.B) { r := NewRegistry() RegisterDebugGCStats(r) b.ResetTimer() + for i := 0; i < b.N; i++ { CaptureDebugGCStatsOnce(r) } @@ -21,14 +22,22 @@ func TestDebugGCStatsBlocking(t *testing.T) { t.Skipf("skipping TestDebugGCMemStatsBlocking with GOMAXPROCS=%d\n", g) return } + ch := make(chan int) go testDebugGCStatsBlocking(ch) + var gcStats debug.GCStats + t0 := time.Now() + debug.ReadGCStats(&gcStats) + t1 := time.Now() + t.Log("i++ during debug.ReadGCStats:", <-ch) + go testDebugGCStatsBlocking(ch) + d := t1.Sub(t0) t.Log(d) time.Sleep(d) @@ -37,6 +46,7 @@ func TestDebugGCStatsBlocking(t *testing.T) { func testDebugGCStatsBlocking(ch chan int) { i := 0 + for { select { case ch <- i: diff --git a/metrics/ewma.go b/metrics/ewma.go index ed95cba19b..440030a55d 100644 --- a/metrics/ewma.go +++ b/metrics/ewma.go @@ -86,6 +86,7 @@ type StandardEWMA struct { func (a *StandardEWMA) Rate() float64 { a.mutex.Lock() defer a.mutex.Unlock() + return a.rate * float64(time.Second) } @@ -100,8 +101,10 @@ func (a *StandardEWMA) Tick() { count := a.uncounted.Load() a.uncounted.Add(-count) instantRate := float64(count) / float64(5*time.Second) + a.mutex.Lock() defer a.mutex.Unlock() + if a.init { a.rate += a.alpha * (instantRate - a.rate) } else { diff --git a/metrics/ewma_test.go b/metrics/ewma_test.go index 5b24419161..d13b62c190 100644 --- a/metrics/ewma_test.go +++ b/metrics/ewma_test.go @@ -7,7 +7,9 @@ import ( func BenchmarkEWMA(b *testing.B) { a := NewEWMA1() + b.ResetTimer() + for i := 0; i < b.N; i++ { a.Update(1) a.Tick() @@ -18,66 +20,97 @@ func TestEWMA1(t *testing.T) { a := NewEWMA1() a.Update(3) a.Tick() + if rate := a.Rate(); math.Abs(0.6-rate) > epsilon { t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.22072766470286553-rate) > epsilon { t.Errorf("1 minute a.Rate(): 0.22072766470286553 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.08120116994196772-rate) > epsilon { t.Errorf("2 minute a.Rate(): 0.08120116994196772 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.029872241020718428-rate) > epsilon { t.Errorf("3 minute a.Rate(): 0.029872241020718428 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.01098938333324054-rate) > epsilon { t.Errorf("4 minute a.Rate(): 0.01098938333324054 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.004042768199451294-rate) > epsilon { t.Errorf("5 minute a.Rate(): 0.004042768199451294 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.0014872513059998212-rate) > epsilon { t.Errorf("6 minute a.Rate(): 0.0014872513059998212 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.0005471291793327122-rate) > epsilon { t.Errorf("7 minute a.Rate(): 0.0005471291793327122 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.00020127757674150815-rate) > epsilon { t.Errorf("8 minute a.Rate(): 0.00020127757674150815 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(7.404588245200814e-05-rate) > epsilon { t.Errorf("9 minute a.Rate(): 7.404588245200814e-05 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(2.7239957857491083e-05-rate) > epsilon { t.Errorf("10 minute a.Rate(): 2.7239957857491083e-05 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(1.0021020474147462e-05-rate) > epsilon { t.Errorf("11 minute a.Rate(): 1.0021020474147462e-05 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(3.6865274119969525e-06-rate) > epsilon { t.Errorf("12 minute a.Rate(): 3.6865274119969525e-06 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(1.3561976441886433e-06-rate) > epsilon { t.Errorf("13 minute a.Rate(): 1.3561976441886433e-06 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(4.989172314621449e-07-rate) > epsilon { t.Errorf("14 minute a.Rate(): 4.989172314621449e-07 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(1.8354139230109722e-07-rate) > epsilon { t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate) } @@ -87,66 +120,97 @@ func TestEWMA5(t *testing.T) { a := NewEWMA5() a.Update(3) a.Tick() + if rate := a.Rate(); math.Abs(0.6-rate) > epsilon { t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.49123845184678905-rate) > epsilon { t.Errorf("1 minute a.Rate(): 0.49123845184678905 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.4021920276213837-rate) > epsilon { t.Errorf("2 minute a.Rate(): 0.4021920276213837 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.32928698165641596-rate) > epsilon { t.Errorf("3 minute a.Rate(): 0.32928698165641596 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.269597378470333-rate) > epsilon { t.Errorf("4 minute a.Rate(): 0.269597378470333 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.2207276647028654-rate) > epsilon { t.Errorf("5 minute a.Rate(): 0.2207276647028654 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.18071652714732128-rate) > epsilon { t.Errorf("6 minute a.Rate(): 0.18071652714732128 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.14795817836496392-rate) > epsilon { t.Errorf("7 minute a.Rate(): 0.14795817836496392 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.12113791079679326-rate) > epsilon { t.Errorf("8 minute a.Rate(): 0.12113791079679326 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.09917933293295193-rate) > epsilon { t.Errorf("9 minute a.Rate(): 0.09917933293295193 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.08120116994196763-rate) > epsilon { t.Errorf("10 minute a.Rate(): 0.08120116994196763 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.06648189501740036-rate) > epsilon { t.Errorf("11 minute a.Rate(): 0.06648189501740036 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.05443077197364752-rate) > epsilon { t.Errorf("12 minute a.Rate(): 0.05443077197364752 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.04456414692860035-rate) > epsilon { t.Errorf("13 minute a.Rate(): 0.04456414692860035 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.03648603757513079-rate) > epsilon { t.Errorf("14 minute a.Rate(): 0.03648603757513079 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.0298722410207183831020718428-rate) > epsilon { t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate) } @@ -156,66 +220,97 @@ func TestEWMA15(t *testing.T) { a := NewEWMA15() a.Update(3) a.Tick() + if rate := a.Rate(); math.Abs(0.6-rate) > epsilon { t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.5613041910189706-rate) > epsilon { t.Errorf("1 minute a.Rate(): 0.5613041910189706 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.5251039914257684-rate) > epsilon { t.Errorf("2 minute a.Rate(): 0.5251039914257684 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.4912384518467888184678905-rate) > epsilon { t.Errorf("3 minute a.Rate(): 0.4912384518467888184678905 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.459557003018789-rate) > epsilon { t.Errorf("4 minute a.Rate(): 0.459557003018789 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.4299187863442732-rate) > epsilon { t.Errorf("5 minute a.Rate(): 0.4299187863442732 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.4021920276213831-rate) > epsilon { t.Errorf("6 minute a.Rate(): 0.4021920276213831 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.37625345116383313-rate) > epsilon { t.Errorf("7 minute a.Rate(): 0.37625345116383313 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.3519877317060185-rate) > epsilon { t.Errorf("8 minute a.Rate(): 0.3519877317060185 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.3292869816564153165641596-rate) > epsilon { t.Errorf("9 minute a.Rate(): 0.3292869816564153165641596 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.3080502714195546-rate) > epsilon { t.Errorf("10 minute a.Rate(): 0.3080502714195546 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.2881831806538789-rate) > epsilon { t.Errorf("11 minute a.Rate(): 0.2881831806538789 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.26959737847033216-rate) > epsilon { t.Errorf("12 minute a.Rate(): 0.26959737847033216 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.2522102307052083-rate) > epsilon { t.Errorf("13 minute a.Rate(): 0.2522102307052083 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.23594443252115815-rate) > epsilon { t.Errorf("14 minute a.Rate(): 0.23594443252115815 != %v\n", rate) } + elapseMinute(a) + if rate := a.Rate(); math.Abs(0.2207276647028646247028654470286553-rate) > epsilon { t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate) } diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index 2b04eeab27..ac4fe6e859 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -25,12 +25,16 @@ func (exp *exp) expHandler(w http.ResponseWriter, r *http.Request) { // now just run the official expvar handler code (which is not publicly callable, so pasted inline) w.Header().Set("Content-Type", "application/json; charset=utf-8") fmt.Fprintf(w, "{\n") + first := true + expvar.Do(func(kv expvar.KeyValue) { if !first { fmt.Fprintf(w, ",\n") } + first = false + fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) }) fmt.Fprintf(w, "\n}\n") @@ -60,6 +64,7 @@ func Setup(address string) { m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry)) m.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry)) log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address)) + go func() { if err := http.ListenAndServe(address, m); err != nil { log.Error("Failure in running metrics server", "err", err) @@ -69,7 +74,9 @@ func Setup(address string) { func (exp *exp) getInt(name string) *expvar.Int { var v *expvar.Int + exp.expvarLock.Lock() + p := expvar.Get(name) if p != nil { v = p.(*expvar.Int) @@ -78,12 +85,15 @@ func (exp *exp) getInt(name string) *expvar.Int { expvar.Publish(name, v) } exp.expvarLock.Unlock() + return v } func (exp *exp) getFloat(name string) *expvar.Float { var v *expvar.Float + exp.expvarLock.Lock() + p := expvar.Get(name) if p != nil { v = p.(*expvar.Float) @@ -92,6 +102,7 @@ func (exp *exp) getFloat(name string) *expvar.Float { expvar.Publish(name, v) } exp.expvarLock.Unlock() + return v } diff --git a/metrics/gauge.go b/metrics/gauge.go index 88110ae155..9dda898d63 100644 --- a/metrics/gauge.go +++ b/metrics/gauge.go @@ -17,6 +17,7 @@ func GetOrRegisterGauge(name string, r Registry) Gauge { if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, NewGauge).(Gauge) } @@ -32,10 +33,13 @@ func NewGauge() Gauge { // NewRegisteredGauge constructs and registers a new StandardGauge. func NewRegisteredGauge(name string, r Registry) Gauge { c := NewGauge() + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } @@ -44,16 +48,20 @@ func NewFunctionalGauge(f func() int64) Gauge { if !Enabled { return NilGauge{} } + return &FunctionalGauge{value: f} } // NewRegisteredFunctionalGauge constructs and registers a new StandardGauge. func NewRegisteredFunctionalGauge(name string, r Registry, f func() int64) Gauge { c := NewFunctionalGauge(f) + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } diff --git a/metrics/gauge_float64.go b/metrics/gauge_float64.go index 588bf76cba..b6418c74cf 100644 --- a/metrics/gauge_float64.go +++ b/metrics/gauge_float64.go @@ -18,6 +18,7 @@ func GetOrRegisterGaugeFloat64(name string, r Registry) GaugeFloat64 { if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, NewGaugeFloat64()).(GaugeFloat64) } @@ -33,10 +34,13 @@ func NewGaugeFloat64() GaugeFloat64 { // NewRegisteredGaugeFloat64 constructs and registers a new StandardGaugeFloat64. func NewRegisteredGaugeFloat64(name string, r Registry) GaugeFloat64 { c := NewGaugeFloat64() + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } @@ -45,16 +49,20 @@ func NewFunctionalGaugeFloat64(f func() float64) GaugeFloat64 { if !Enabled { return NilGaugeFloat64{} } + return &FunctionalGaugeFloat64{value: f} } // NewRegisteredFunctionalGauge constructs and registers a new StandardGauge. func NewRegisteredFunctionalGaugeFloat64(name string, r Registry, f func() float64) GaugeFloat64 { c := NewFunctionalGaugeFloat64(f) + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } diff --git a/metrics/gauge_float64_test.go b/metrics/gauge_float64_test.go index ea0901644c..0152d6dc66 100644 --- a/metrics/gauge_float64_test.go +++ b/metrics/gauge_float64_test.go @@ -7,7 +7,9 @@ import ( func BenchmarkGaugeFloat64(b *testing.B) { g := NewGaugeFloat64() + b.ResetTimer() + for i := 0; i < b.N; i++ { g.Update(float64(i)) } @@ -38,6 +40,7 @@ func BenchmarkGaugeFloat64Parallel(b *testing.B) { func TestGaugeFloat64(t *testing.T) { g := NewGaugeFloat64() g.Update(47.0) + if v := g.Value(); 47.0 != v { t.Errorf("g.Value(): 47.0 != %v\n", v) } @@ -48,6 +51,7 @@ func TestGaugeFloat64Snapshot(t *testing.T) { g.Update(47.0) snapshot := g.Snapshot() g.Update(float64(0)) + if v := snapshot.Value(); 47.0 != v { t.Errorf("g.Value(): 47.0 != %v\n", v) } @@ -57,6 +61,7 @@ func TestGetOrRegisterGaugeFloat64(t *testing.T) { r := NewRegistry() NewRegisteredGaugeFloat64("foo", r).Update(47.0) t.Logf("registry: %v", r) + if g := GetOrRegisterGaugeFloat64("foo", r); 47.0 != g.Value() { t.Fatal(g) } @@ -64,12 +69,14 @@ func TestGetOrRegisterGaugeFloat64(t *testing.T) { func TestFunctionalGaugeFloat64(t *testing.T) { var counter float64 + fg := NewFunctionalGaugeFloat64(func() float64 { counter++ return counter }) fg.Value() fg.Value() + if counter != 2 { t.Error("counter != 2") } @@ -78,6 +85,7 @@ func TestFunctionalGaugeFloat64(t *testing.T) { func TestGetOrRegisterFunctionalGaugeFloat64(t *testing.T) { r := NewRegistry() NewRegisteredFunctionalGaugeFloat64("foo", r, func() float64 { return 47 }) + if g := GetOrRegisterGaugeFloat64("foo", r); g.Value() != 47 { t.Fatal(g) } diff --git a/metrics/gauge_test.go b/metrics/gauge_test.go index a98fe985d8..6aa0869096 100644 --- a/metrics/gauge_test.go +++ b/metrics/gauge_test.go @@ -7,7 +7,9 @@ import ( func BenchmarkGauge(b *testing.B) { g := NewGauge() + b.ResetTimer() + for i := 0; i < b.N; i++ { g.Update(int64(i)) } @@ -16,6 +18,7 @@ func BenchmarkGauge(b *testing.B) { func TestGauge(t *testing.T) { g := NewGauge() g.Update(int64(47)) + if v := g.Value(); v != 47 { t.Errorf("g.Value(): 47 != %v\n", v) } @@ -26,6 +29,7 @@ func TestGaugeSnapshot(t *testing.T) { g.Update(int64(47)) snapshot := g.Snapshot() g.Update(int64(0)) + if v := snapshot.Value(); v != 47 { t.Errorf("g.Value(): 47 != %v\n", v) } @@ -34,6 +38,7 @@ func TestGaugeSnapshot(t *testing.T) { func TestGetOrRegisterGauge(t *testing.T) { r := NewRegistry() NewRegisteredGauge("foo", r).Update(47) + if g := GetOrRegisterGauge("foo", r); g.Value() != 47 { t.Fatal(g) } @@ -41,12 +46,14 @@ func TestGetOrRegisterGauge(t *testing.T) { func TestFunctionalGauge(t *testing.T) { var counter int64 + fg := NewFunctionalGauge(func() int64 { counter++ return counter }) fg.Value() fg.Value() + if counter != 2 { t.Error("counter != 2") } @@ -55,6 +62,7 @@ func TestFunctionalGauge(t *testing.T) { func TestGetOrRegisterFunctionalGauge(t *testing.T) { r := NewRegistry() NewRegisteredFunctionalGauge("foo", r, func() int64 { return 47 }) + if g := GetOrRegisterGauge("foo", r); g.Value() != 47 { t.Fatal(g) } diff --git a/metrics/graphite.go b/metrics/graphite.go index 29f72b0c41..ccfdabc101 100644 --- a/metrics/graphite.go +++ b/metrics/graphite.go @@ -39,6 +39,7 @@ func Graphite(r Registry, d time.Duration, prefix string, addr *net.TCPAddr) { // but it takes a GraphiteConfig instead. func GraphiteWithConfig(c GraphiteConfig) { log.Printf("WARNING: This go-metrics client has been DEPRECATED! It has been moved to https://github.com/cyberdelia/go-metrics-graphite and will be removed from rcrowley/go-metrics on August 12th 2015") + for range time.Tick(c.FlushInterval) { if err := graphite(&c); nil != err { log.Println(err) @@ -57,12 +58,15 @@ func GraphiteOnce(c GraphiteConfig) error { func graphite(c *GraphiteConfig) error { now := time.Now().Unix() du := float64(c.DurationUnit) + conn, err := net.DialTCP("tcp", nil, c.Addr) if nil != err { return err } + defer conn.Close() w := bufio.NewWriter(conn) + c.Registry.Each(func(name string, i interface{}) { switch metric := i.(type) { case Counter: @@ -81,6 +85,7 @@ func graphite(c *GraphiteConfig) error { fmt.Fprintf(w, "%s.%s.max %d %d\n", c.Prefix, name, h.Max(), now) fmt.Fprintf(w, "%s.%s.mean %.2f %d\n", c.Prefix, name, h.Mean(), now) fmt.Fprintf(w, "%s.%s.std-dev %.2f %d\n", c.Prefix, name, h.StdDev(), now) + for psIdx, psKey := range c.Percentiles { key := strings.Replace(strconv.FormatFloat(psKey*100.0, 'f', -1, 64), ".", "", 1) fmt.Fprintf(w, "%s.%s.%s-percentile %.2f %d\n", c.Prefix, name, key, ps[psIdx], now) @@ -100,10 +105,12 @@ func graphite(c *GraphiteConfig) error { fmt.Fprintf(w, "%s.%s.max %d %d\n", c.Prefix, name, t.Max()/int64(du), now) fmt.Fprintf(w, "%s.%s.mean %.2f %d\n", c.Prefix, name, t.Mean()/du, now) fmt.Fprintf(w, "%s.%s.std-dev %.2f %d\n", c.Prefix, name, t.StdDev()/du, now) + for psIdx, psKey := range c.Percentiles { key := strings.Replace(strconv.FormatFloat(psKey*100.0, 'f', -1, 64), ".", "", 1) fmt.Fprintf(w, "%s.%s.%s-percentile %.2f %d\n", c.Prefix, name, key, ps[psIdx], now) } + fmt.Fprintf(w, "%s.%s.one-minute %.2f %d\n", c.Prefix, name, t.Rate1(), now) fmt.Fprintf(w, "%s.%s.five-minute %.2f %d\n", c.Prefix, name, t.Rate5(), now) fmt.Fprintf(w, "%s.%s.fifteen-minute %.2f %d\n", c.Prefix, name, t.Rate15(), now) @@ -111,5 +118,6 @@ func graphite(c *GraphiteConfig) error { } w.Flush() }) + return nil } diff --git a/metrics/healthcheck.go b/metrics/healthcheck.go index f1ae31e34a..05b6ff01ab 100644 --- a/metrics/healthcheck.go +++ b/metrics/healthcheck.go @@ -14,6 +14,7 @@ func NewHealthcheck(f func(Healthcheck)) Healthcheck { if !Enabled { return NilHealthcheck{} } + return &StandardHealthcheck{nil, f} } diff --git a/metrics/histogram.go b/metrics/histogram.go index 2c54ce8b40..c56fae0f93 100644 --- a/metrics/histogram.go +++ b/metrics/histogram.go @@ -23,6 +23,7 @@ func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram { if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, func() Histogram { return NewHistogram(s) }).(Histogram) } @@ -32,6 +33,7 @@ func GetOrRegisterHistogramLazy(name string, r Registry, s func() Sample) Histog if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, func() Histogram { return NewHistogram(s()) }).(Histogram) } @@ -40,6 +42,7 @@ func NewHistogram(s Sample) Histogram { if !Enabled { return NilHistogram{} } + return &StandardHistogram{sample: s} } @@ -47,10 +50,13 @@ func NewHistogram(s Sample) Histogram { // a Sample. func NewRegisteredHistogram(name string, r Registry, s Sample) Histogram { c := NewHistogram(s) + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } diff --git a/metrics/histogram_test.go b/metrics/histogram_test.go index 7c9f42fcec..0f29a1d7e5 100644 --- a/metrics/histogram_test.go +++ b/metrics/histogram_test.go @@ -4,7 +4,9 @@ import "testing" func BenchmarkHistogram(b *testing.B) { h := NewHistogram(NewUniformSample(100)) + b.ResetTimer() + for i := 0; i < b.N; i++ { h.Update(int64(i)) } @@ -14,6 +16,7 @@ func TestGetOrRegisterHistogram(t *testing.T) { r := NewRegistry() s := NewUniformSample(100) NewRegisteredHistogram("foo", r, s).Update(47) + if h := GetOrRegisterHistogram("foo", r, s); h.Count() != 1 { t.Fatal(h) } @@ -32,25 +35,32 @@ func TestHistogramEmpty(t *testing.T) { if count := h.Count(); count != 0 { t.Errorf("h.Count(): 0 != %v\n", count) } + if min := h.Min(); min != 0 { t.Errorf("h.Min(): 0 != %v\n", min) } + if max := h.Max(); max != 0 { t.Errorf("h.Max(): 0 != %v\n", max) } + if mean := h.Mean(); mean != 0.0 { t.Errorf("h.Mean(): 0.0 != %v\n", mean) } + if stdDev := h.StdDev(); stdDev != 0.0 { t.Errorf("h.StdDev(): 0.0 != %v\n", stdDev) } + ps := h.Percentiles([]float64{0.5, 0.75, 0.99}) if ps[0] != 0.0 { t.Errorf("median: 0.0 != %v\n", ps[0]) } + if ps[1] != 0.0 { t.Errorf("75th percentile: 0.0 != %v\n", ps[1]) } + if ps[2] != 0.0 { t.Errorf("99th percentile: 0.0 != %v\n", ps[2]) } @@ -61,6 +71,7 @@ func TestHistogramSnapshot(t *testing.T) { for i := 1; i <= 10000; i++ { h.Update(int64(i)) } + snapshot := h.Snapshot() h.Update(0) testHistogram10000(t, snapshot) @@ -70,25 +81,32 @@ func testHistogram10000(t *testing.T, h Histogram) { if count := h.Count(); count != 10000 { t.Errorf("h.Count(): 10000 != %v\n", count) } + if min := h.Min(); min != 1 { t.Errorf("h.Min(): 1 != %v\n", min) } + if max := h.Max(); max != 10000 { t.Errorf("h.Max(): 10000 != %v\n", max) } + if mean := h.Mean(); mean != 5000.5 { t.Errorf("h.Mean(): 5000.5 != %v\n", mean) } + if stdDev := h.StdDev(); stdDev != 2886.751331514372 { t.Errorf("h.StdDev(): 2886.751331514372 != %v\n", stdDev) } + ps := h.Percentiles([]float64{0.5, 0.75, 0.99}) if ps[0] != 5000.5 { t.Errorf("median: 5000.5 != %v\n", ps[0]) } + if ps[1] != 7500.75 { t.Errorf("75th percentile: 7500.75 != %v\n", ps[1]) } + if ps[2] != 9900.99 { t.Errorf("99th percentile: 9900.99 != %v\n", ps[2]) } diff --git a/metrics/influxdb/influxdbv1.go b/metrics/influxdb/influxdbv1.go index bee696a8b7..95bd10953e 100644 --- a/metrics/influxdb/influxdbv1.go +++ b/metrics/influxdb/influxdbv1.go @@ -135,10 +135,12 @@ func (r *reporter) send() error { r.reg.Each(func(name string, i interface{}) { now := time.Now() + measurement, fields := readMeter(r.namespace, name, i) if fields == nil { return } + if p, err := client.NewPoint(measurement, r.tags, fields, now); err == nil { bps.AddPoint(p) } diff --git a/metrics/influxdb/influxdbv2.go b/metrics/influxdb/influxdbv2.go index 7984898f32..380887762b 100644 --- a/metrics/influxdb/influxdbv2.go +++ b/metrics/influxdb/influxdbv2.go @@ -77,10 +77,12 @@ func (r *v2Reporter) run() { func (r *v2Reporter) send() { r.reg.Each(func(name string, i interface{}) { now := time.Now() + measurement, fields := readMeter(r.namespace, name, i) if fields == nil { return } + pt := influxdb2.NewPoint(measurement, r.tags, fields, now) r.write.WritePoint(pt) }) diff --git a/metrics/json_test.go b/metrics/json_test.go index f91fe8cfa5..44d20a6225 100644 --- a/metrics/json_test.go +++ b/metrics/json_test.go @@ -12,6 +12,7 @@ func TestRegistryMarshallJSON(t *testing.T) { r := NewRegistry() r.Register("counter", NewCounter()) enc.Encode(r) + if s := b.String(); s != "{\"counter\":{\"count\":0}}\n" { t.Fatalf(s) } @@ -20,8 +21,10 @@ func TestRegistryMarshallJSON(t *testing.T) { func TestRegistryWriteJSONOnce(t *testing.T) { r := NewRegistry() r.Register("counter", NewCounter()) + b := &bytes.Buffer{} WriteJSONOnce(r, b) + if s := b.String(); s != "{\"counter\":{\"count\":0}}\n" { t.Fail() } diff --git a/metrics/librato/client.go b/metrics/librato/client.go index 9d926c8534..23df8623f7 100644 --- a/metrics/librato/client.go +++ b/metrics/librato/client.go @@ -99,7 +99,9 @@ func (c *LibratoClient) PostMetrics(batch Batch) (err error) { if body, err = io.ReadAll(resp.Body); err != nil { body = []byte(fmt.Sprintf("(could not fetch response body for error: %s)", err)) } + err = fmt.Errorf("unable to post to Librato: %d %s %s", resp.StatusCode, resp.Status, string(body)) } + return } diff --git a/metrics/librato/librato.go b/metrics/librato/librato.go index 3d45f4c7be..ae5c08c1bf 100644 --- a/metrics/librato/librato.go +++ b/metrics/librato/librato.go @@ -18,6 +18,7 @@ func translateTimerAttributes(d time.Duration) (attrs map[string]interface{}) { attrs = make(map[string]interface{}) attrs[DisplayTransform] = fmt.Sprintf("x/%d", int64(d)) attrs[DisplayUnitsShort] = string(unitRegexp.Find([]byte(d.String()))) + return } @@ -42,16 +43,21 @@ func Librato(r metrics.Registry, d time.Duration, e string, t string, s string, func (rep *Reporter) Run() { log.Printf("WARNING: This client has been DEPRECATED! It has been moved to https://github.com/mihasya/go-metrics-librato and will be removed from rcrowley/go-metrics on August 5th 2015") + ticker := time.NewTicker(rep.Interval) defer ticker.Stop() + metricsApi := &LibratoClient{rep.Email, rep.Token} + for now := range ticker.C { var metrics Batch + var err error if metrics, err = rep.BuildRequest(now, rep.Registry); err != nil { log.Printf("ERROR constructing librato request body %s", err) continue } + if err := metricsApi.PostMetrics(metrics); err != nil { log.Printf("ERROR sending metrics to librato %s", err) continue @@ -64,19 +70,23 @@ func (rep *Reporter) Run() { func sumSquares(s metrics.Sample) float64 { count := float64(s.Count()) sumSquared := math.Pow(count*s.Mean(), 2) + sumSquares := math.Pow(count*s.StdDev(), 2) + sumSquared/count if math.IsNaN(sumSquares) { return 0.0 } + return sumSquares } func sumSquaresTimer(t metrics.Timer) float64 { count := float64(t.Count()) sumSquared := math.Pow(count*t.Mean(), 2) + sumSquares := math.Pow(count*t.StdDev(), 2) + sumSquared/count if math.IsNaN(sumSquares) { return 0.0 } + return sumSquares } @@ -89,12 +99,15 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B snapshot.Gauges = make([]Measurement, 0) snapshot.Counters = make([]Measurement, 0) histogramGaugeCount := 1 + len(rep.Percentiles) + r.Each(func(name string, metric interface{}) { if rep.Namespace != "" { name = fmt.Sprintf("%s.%s", rep.Namespace, name) } + measurement := Measurement{} measurement[Period] = rep.Interval.Seconds() + switch m := metric.(type) { case metrics.Counter: if m.Count() > 0 { @@ -137,6 +150,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B measurement[Sum] = float64(s.Sum()) measurement[SumSquares] = sumSquares(s) gauges[0] = measurement + for i, p := range rep.Percentiles { gauges[i+1] = Measurement{ Name: fmt.Sprintf("%s.%.2f", measurement[Name], p), @@ -144,6 +158,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B Period: measurement[Period], } } + snapshot.Gauges = append(snapshot.Gauges, gauges...) } case metrics.Meter: @@ -186,6 +201,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B measurement[Name] = name measurement[Value] = float64(m.Count()) snapshot.Counters = append(snapshot.Counters, measurement) + if m.Count() > 0 { libratoName := fmt.Sprintf("%s.%s", name, "timer.mean") gauges := make([]Measurement, histogramGaugeCount) @@ -199,6 +215,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B Period: int64(rep.Interval.Seconds()), Attributes: rep.TimerAttributes, } + for i, p := range rep.Percentiles { gauges[i+1] = Measurement{ Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100), @@ -207,6 +224,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B Attributes: rep.TimerAttributes, } } + snapshot.Gauges = append(snapshot.Gauges, gauges...) snapshot.Gauges = append(snapshot.Gauges, Measurement{ @@ -243,5 +261,6 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B } } }) + return } diff --git a/metrics/log.go b/metrics/log.go index d1ce627a83..a13a75eb50 100644 --- a/metrics/log.go +++ b/metrics/log.go @@ -40,6 +40,7 @@ func LogScaled(r Registry, freq time.Duration, scale time.Duration, l Logger) { case Histogram: h := metric.Snapshot() ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + l.Printf("histogram %s\n", name) l.Printf(" count: %9d\n", h.Count()) l.Printf(" min: %9d\n", h.Min()) @@ -53,6 +54,7 @@ func LogScaled(r Registry, freq time.Duration, scale time.Duration, l Logger) { l.Printf(" 99.9%%: %12.2f\n", ps[4]) case Meter: m := metric.Snapshot() + l.Printf("meter %s\n", name) l.Printf(" count: %9d\n", m.Count()) l.Printf(" 1-min rate: %12.2f\n", m.Rate1()) @@ -62,6 +64,7 @@ func LogScaled(r Registry, freq time.Duration, scale time.Duration, l Logger) { case Timer: t := metric.Snapshot() ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + l.Printf("timer %s\n", name) l.Printf(" count: %9d\n", t.Count()) l.Printf(" min: %12.2f%s\n", float64(t.Min())/du, duSuffix) diff --git a/metrics/meter.go b/metrics/meter.go index e8564d6a5e..9243761504 100644 --- a/metrics/meter.go +++ b/metrics/meter.go @@ -27,6 +27,7 @@ func GetOrRegisterMeter(name string, r Registry) Meter { if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, NewMeter).(Meter) } @@ -38,6 +39,7 @@ func GetOrRegisterMeterForced(name string, r Registry) Meter { if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, NewMeterForced).(Meter) } @@ -47,14 +49,18 @@ func NewMeter() Meter { if !Enabled { return NilMeter{} } + m := newStandardMeter() + arbiter.Lock() defer arbiter.Unlock() + arbiter.meters[m] = struct{}{} if !arbiter.started { arbiter.started = true go arbiter.tick() } + return m } @@ -63,13 +69,16 @@ func NewMeter() Meter { // Be sure to call Stop() once the meter is of no use to allow for garbage collection. func NewMeterForced() Meter { m := newStandardMeter() + arbiter.Lock() defer arbiter.Unlock() + arbiter.meters[m] = struct{}{} if !arbiter.started { arbiter.started = true go arbiter.tick() } + return m } @@ -79,10 +88,13 @@ func NewMeterForced() Meter { // allow for garbage collection. func NewRegisteredMeter(name string, r Registry) Meter { c := NewMeter() + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } @@ -92,10 +104,13 @@ func NewRegisteredMeter(name string, r Registry) Meter { // allow for garbage collection. func NewRegisteredMeterForced(name string, r Registry) Meter { c := NewMeterForced() + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } @@ -198,6 +213,7 @@ func (m *StandardMeter) Count() int64 { m.lock.Lock() defer m.lock.Unlock() m.updateMeter() + return m.snapshot.count } @@ -210,6 +226,7 @@ func (m *StandardMeter) Mark(n int64) { func (m *StandardMeter) Rate1() float64 { m.lock.RLock() defer m.lock.RUnlock() + return m.snapshot.rate1 } @@ -217,6 +234,7 @@ func (m *StandardMeter) Rate1() float64 { func (m *StandardMeter) Rate5() float64 { m.lock.RLock() defer m.lock.RUnlock() + return m.snapshot.rate5 } @@ -224,6 +242,7 @@ func (m *StandardMeter) Rate5() float64 { func (m *StandardMeter) Rate15() float64 { m.lock.RLock() defer m.lock.RUnlock() + return m.snapshot.rate15 } @@ -231,6 +250,7 @@ func (m *StandardMeter) Rate15() float64 { func (m *StandardMeter) RateMean() float64 { m.lock.RLock() defer m.lock.RUnlock() + return m.snapshot.rateMean } @@ -246,6 +266,7 @@ func (m *StandardMeter) Snapshot() Meter { } snapshot.temp.Store(m.snapshot.temp.Load()) m.lock.RUnlock() + return &snapshot } @@ -298,6 +319,7 @@ func (ma *meterArbiter) tick() { func (ma *meterArbiter) tickMeters() { ma.RLock() defer ma.RUnlock() + for meter := range ma.meters { meter.tick() } diff --git a/metrics/meter_test.go b/metrics/meter_test.go index b3f6cb8c0c..61354ddca3 100644 --- a/metrics/meter_test.go +++ b/metrics/meter_test.go @@ -7,7 +7,9 @@ import ( func BenchmarkMeter(b *testing.B) { m := NewMeter() + b.ResetTimer() + for i := 0; i < b.N; i++ { m.Mark(1) } @@ -16,6 +18,7 @@ func BenchmarkMeter(b *testing.B) { func TestGetOrRegisterMeter(t *testing.T) { r := NewRegistry() NewRegisteredMeter("foo", r).Mark(47) + if m := GetOrRegisterMeter("foo", r); m.Count() != 47 { t.Fatal(m.Count()) } @@ -27,13 +30,18 @@ func TestMeterDecay(t *testing.T) { meters: make(map[*StandardMeter]struct{}), } defer ma.ticker.Stop() + m := newStandardMeter() ma.meters[m] = struct{}{} + m.Mark(1) ma.tickMeters() + rateMean := m.RateMean() + time.Sleep(100 * time.Millisecond) ma.tickMeters() + if m.RateMean() >= rateMean { t.Error("m.RateMean() didn't decrease") } @@ -42,6 +50,7 @@ func TestMeterDecay(t *testing.T) { func TestMeterNonzero(t *testing.T) { m := NewMeter() m.Mark(3) + if count := m.Count(); count != 3 { t.Errorf("m.Count(): 3 != %v\n", count) } @@ -50,10 +59,13 @@ func TestMeterNonzero(t *testing.T) { func TestMeterStop(t *testing.T) { l := len(arbiter.meters) m := NewMeter() + if l+1 != len(arbiter.meters) { t.Errorf("arbiter.meters: %d != %d\n", l+1, len(arbiter.meters)) } + m.Stop() + if l != len(arbiter.meters) { t.Errorf("arbiter.meters: %d != %d\n", l, len(arbiter.meters)) } @@ -62,6 +74,7 @@ func TestMeterStop(t *testing.T) { func TestMeterSnapshot(t *testing.T) { m := NewMeter() m.Mark(1) + if snapshot := m.Snapshot(); m.RateMean() != snapshot.RateMean() { t.Fatal(snapshot) } @@ -79,12 +92,15 @@ func TestMeterRepeat(t *testing.T) { for i := 0; i < 101; i++ { m.Mark(int64(i)) } + if count := m.Count(); count != 5050 { t.Errorf("m.Count(): 5050 != %v\n", count) } + for i := 0; i < 101; i++ { m.Mark(int64(i)) } + if count := m.Count(); count != 10100 { t.Errorf("m.Count(): 10100 != %v\n", count) } diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index bc65d37e80..6030cf37fb 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -29,6 +29,7 @@ func BenchmarkMetrics(b *testing.B) { t := NewRegisteredTimer("timer", r) RegisterDebugGCStats(r) b.ResetTimer() + ch := make(chan bool) wgD := &sync.WaitGroup{} @@ -69,6 +70,7 @@ func BenchmarkMetrics(b *testing.B) { wg := &sync.WaitGroup{} wg.Add(FANOUT) + for i := 0; i < FANOUT; i++ { go func(i int) { defer wg.Done() diff --git a/metrics/opentsdb.go b/metrics/opentsdb.go index c9fd2e75d5..d5c76b2a11 100644 --- a/metrics/opentsdb.go +++ b/metrics/opentsdb.go @@ -54,6 +54,7 @@ func getShortHostname() string { shortHostName = host } } + return shortHostName } @@ -61,12 +62,15 @@ func openTSDB(c *OpenTSDBConfig) error { shortHostname := getShortHostname() now := time.Now().Unix() du := float64(c.DurationUnit) + conn, err := net.DialTCP("tcp", nil, c.Addr) if nil != err { return err } + defer conn.Close() w := bufio.NewWriter(conn) + c.Registry.Each(func(name string, i interface{}) { switch metric := i.(type) { case Counter: @@ -117,5 +121,6 @@ func openTSDB(c *OpenTSDBConfig) error { } w.Flush() }) + return nil } diff --git a/metrics/prometheus/collector.go b/metrics/prometheus/collector.go index 20e6ae9054..9a64aac0b5 100644 --- a/metrics/prometheus/collector.go +++ b/metrics/prometheus/collector.go @@ -68,7 +68,9 @@ func (c *collector) addHistogram(name string, m metrics.Histogram) { ps := m.Percentiles(pv) var sum float64 = 0 + c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name))) + for i := range pv { c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i]) sum += ps[i] @@ -88,9 +90,11 @@ func (c *collector) addTimer(name string, m metrics.Timer) { ps := m.Percentiles(pv) c.writeCounter(name, m.Count()) c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name))) + for i := range pv { c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i]) } + c.buff.WriteRune('\n') } @@ -98,8 +102,10 @@ func (c *collector) addResettingTimer(name string, m metrics.ResettingTimer) { if len(m.Values()) <= 0 { return } + ps := m.Percentiles([]float64{50, 95, 99}) val := m.Values() + c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name))) c.writeSummaryPercentile(name, "0.50", ps[0]) c.writeSummaryPercentile(name, "0.95", ps[1]) diff --git a/metrics/prometheus/collector_test.go b/metrics/prometheus/collector_test.go index 2e5075d0f1..63979dc911 100644 --- a/metrics/prometheus/collector_test.go +++ b/metrics/prometheus/collector_test.go @@ -10,6 +10,7 @@ import ( func TestMain(m *testing.M) { metrics.Enabled = true + os.Exit(m.Run()) } diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index d966fa9a86..54f945261c 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -31,6 +31,7 @@ func Handler(reg metrics.Registry) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Gather and pre-sort the metrics to avoid random listings var names []string + reg.Each(func(name string, i interface{}) { names = append(names, name) }) @@ -63,6 +64,7 @@ func Handler(reg metrics.Registry) http.Handler { log.Warn("Unknown Prometheus metric type", "type", fmt.Sprintf("%T", i)) } } + w.Header().Add("Content-Type", "text/plain") w.Header().Add("Content-Length", fmt.Sprint(c.buff.Len())) w.Write(c.buff.Bytes()) diff --git a/metrics/registry.go b/metrics/registry.go index 4c62248351..e4d75275ee 100644 --- a/metrics/registry.go +++ b/metrics/registry.go @@ -73,6 +73,7 @@ func (r *StandardRegistry) Each(f func(string, interface{})) { func (r *StandardRegistry) Get(name string) interface{} { r.mutex.Lock() defer r.mutex.Unlock() + return r.metrics[name] } @@ -83,13 +84,17 @@ func (r *StandardRegistry) Get(name string) interface{} { func (r *StandardRegistry) GetOrRegister(name string, i interface{}) interface{} { r.mutex.Lock() defer r.mutex.Unlock() + if metric, ok := r.metrics[name]; ok { return metric } + if v := reflect.ValueOf(i); v.Kind() == reflect.Func { i = v.Call(nil)[0].Interface() } + r.register(name, i) + return i } @@ -98,6 +103,7 @@ func (r *StandardRegistry) GetOrRegister(name string, i interface{}) interface{} func (r *StandardRegistry) Register(name string, i interface{}) error { r.mutex.Lock() defer r.mutex.Unlock() + return r.register(name, i) } @@ -105,6 +111,7 @@ func (r *StandardRegistry) Register(name string, i interface{}) error { func (r *StandardRegistry) RunHealthchecks() { r.mutex.Lock() defer r.mutex.Unlock() + for _, i := range r.metrics { if h, ok := i.(Healthcheck); ok { h.Check() @@ -115,6 +122,7 @@ func (r *StandardRegistry) RunHealthchecks() { // GetAll metrics in the Registry func (r *StandardRegistry) GetAll() map[string]map[string]interface{} { data := make(map[string]map[string]interface{}) + r.Each(func(name string, i interface{}) { values := make(map[string]interface{}) switch metric := i.(type) { @@ -128,7 +136,9 @@ func (r *StandardRegistry) GetAll() map[string]map[string]interface{} { values["value"] = metric.Value() case Healthcheck: values["error"] = nil + metric.Check() + if err := metric.Error(); nil != err { values["error"] = metric.Error().Error() } @@ -170,8 +180,10 @@ func (r *StandardRegistry) GetAll() map[string]map[string]interface{} { values["15m.rate"] = t.Rate15() values["mean.rate"] = t.RateMean() } + data[name] = values }) + return data } @@ -187,6 +199,7 @@ func (r *StandardRegistry) Unregister(name string) { func (r *StandardRegistry) UnregisterAll() { r.mutex.Lock() defer r.mutex.Unlock() + for name := range r.metrics { r.stop(name) delete(r.metrics, name) @@ -197,20 +210,24 @@ func (r *StandardRegistry) register(name string, i interface{}) error { if _, ok := r.metrics[name]; ok { return DuplicateMetric(name) } + switch i.(type) { case Counter, CounterFloat64, Gauge, GaugeFloat64, Healthcheck, Histogram, Meter, Timer, ResettingTimer: r.metrics[name] = i } + return nil } func (r *StandardRegistry) registered() map[string]interface{} { r.mutex.Lock() defer r.mutex.Unlock() + metrics := make(map[string]interface{}, len(r.metrics)) for name, i := range r.metrics { metrics[name] = i } + return metrics } @@ -269,6 +286,7 @@ func findPrefix(registry Registry, prefix string) (Registry, string) { case *StandardRegistry: return r, prefix } + return nil, "" } diff --git a/metrics/registry_test.go b/metrics/registry_test.go index d277ae5c3e..c04972615a 100644 --- a/metrics/registry_test.go +++ b/metrics/registry_test.go @@ -8,6 +8,7 @@ func BenchmarkRegistry(b *testing.B) { r := NewRegistry() r.Register("foo", NewCounter()) b.ResetTimer() + for i := 0; i < b.N; i++ { r.Each(func(string, interface{}) {}) } @@ -16,22 +17,31 @@ func BenchmarkRegistry(b *testing.B) { func TestRegistry(t *testing.T) { r := NewRegistry() r.Register("foo", NewCounter()) + i := 0 + r.Each(func(name string, iface interface{}) { i++ + if name != "foo" { t.Fatal(name) } + if _, ok := iface.(Counter); !ok { t.Fatal(iface) } }) + if i != 1 { t.Fatal(i) } + r.Unregister("foo") + i = 0 + r.Each(func(string, interface{}) { i++ }) + if i != 0 { t.Fatal(i) } @@ -42,16 +52,21 @@ func TestRegistryDuplicate(t *testing.T) { if err := r.Register("foo", NewCounter()); nil != err { t.Fatal(err) } + if err := r.Register("foo", NewGauge()); nil == err { t.Fatal(err) } + i := 0 + r.Each(func(name string, iface interface{}) { i++ + if _, ok := iface.(Counter); !ok { t.Fatal(iface) } }) + if i != 1 { t.Fatal(i) } @@ -60,10 +75,13 @@ func TestRegistryDuplicate(t *testing.T) { func TestRegistryGet(t *testing.T) { r := NewRegistry() r.Register("foo", NewCounter()) + if count := r.Get("foo").(Counter).Count(); count != 0 { t.Fatal(count) } + r.Get("foo").(Counter).Inc(1) + if count := r.Get("foo").(Counter).Count(); count != 1 { t.Fatal(count) } @@ -74,21 +92,26 @@ func TestRegistryGetOrRegister(t *testing.T) { // First metric wins with GetOrRegister _ = r.GetOrRegister("foo", NewCounter()) + m := r.GetOrRegister("foo", NewGauge()) if _, ok := m.(Counter); !ok { t.Fatal(m) } i := 0 + r.Each(func(name string, iface interface{}) { i++ + if name != "foo" { t.Fatal(name) } + if _, ok := iface.(Counter); !ok { t.Fatal(iface) } }) + if i != 1 { t.Fatal(i) } @@ -99,21 +122,26 @@ func TestRegistryGetOrRegisterWithLazyInstantiation(t *testing.T) { // First metric wins with GetOrRegister _ = r.GetOrRegister("foo", NewCounter) + m := r.GetOrRegister("foo", NewGauge) if _, ok := m.(Counter); !ok { t.Fatal(m) } i := 0 + r.Each(func(name string, iface interface{}) { i++ + if name != "foo" { t.Fatal(name) } + if _, ok := iface.(Counter); !ok { t.Fatal(iface) } }) + if i != 1 { t.Fatal(i) } @@ -125,12 +153,15 @@ func TestRegistryUnregister(t *testing.T) { r.Register("foo", NewCounter()) r.Register("bar", NewMeter()) r.Register("baz", NewTimer()) + if len(arbiter.meters) != l+2 { t.Errorf("arbiter.meters: %d != %d\n", l+2, len(arbiter.meters)) } + r.Unregister("foo") r.Unregister("bar") r.Unregister("baz") + if len(arbiter.meters) != l { t.Errorf("arbiter.meters: %d != %d\n", l+2, len(arbiter.meters)) } @@ -143,12 +174,15 @@ func TestPrefixedChildRegistryGetOrRegister(t *testing.T) { _ = pr.GetOrRegister("foo", NewCounter()) i := 0 + r.Each(func(name string, m interface{}) { i++ + if name != "prefix.foo" { t.Fatal(name) } }) + if i != 1 { t.Fatal(i) } @@ -160,12 +194,15 @@ func TestPrefixedRegistryGetOrRegister(t *testing.T) { _ = r.GetOrRegister("foo", NewCounter()) i := 0 + r.Each(func(name string, m interface{}) { i++ + if name != "prefix.foo" { t.Fatal(name) } }) + if i != 1 { t.Fatal(i) } @@ -176,17 +213,21 @@ func TestPrefixedRegistryRegister(t *testing.T) { err := r.Register("foo", NewCounter()) c := NewCounter() Register("bar", c) + if err != nil { t.Fatal(err.Error()) } i := 0 + r.Each(func(name string, m interface{}) { i++ + if name != "prefix.foo" { t.Fatal(name) } }) + if i != 1 { t.Fatal(i) } @@ -198,12 +239,15 @@ func TestPrefixedRegistryUnregister(t *testing.T) { _ = r.Register("foo", NewCounter()) i := 0 + r.Each(func(name string, m interface{}) { i++ + if name != "prefix.foo" { t.Fatal(name) } }) + if i != 1 { t.Fatal(i) } @@ -211,6 +255,7 @@ func TestPrefixedRegistryUnregister(t *testing.T) { r.Unregister("foo") i = 0 + r.Each(func(name string, m interface{}) { i++ }) @@ -236,6 +281,7 @@ func TestPrefixedChildRegistryGet(t *testing.T) { pr := NewPrefixedChildRegistry(r, "prefix.") name := "foo" pr.Register(name, NewCounter()) + fooCounter := pr.Get(name) if fooCounter == nil { t.Fatal(name) @@ -247,17 +293,21 @@ func TestChildPrefixedRegistryRegister(t *testing.T) { err := r.Register("foo", NewCounter()) c := NewCounter() Register("bar", c) + if err != nil { t.Fatal(err.Error()) } i := 0 + r.Each(func(name string, m interface{}) { i++ + if name != "prefix.foo" { t.Fatal(name) } }) + if i != 1 { t.Fatal(i) } @@ -266,24 +316,30 @@ func TestChildPrefixedRegistryRegister(t *testing.T) { func TestChildPrefixedRegistryOfChildRegister(t *testing.T) { r := NewPrefixedChildRegistry(NewRegistry(), "prefix.") r2 := NewPrefixedChildRegistry(r, "prefix2.") + err := r.Register("foo2", NewCounter()) if err != nil { t.Fatal(err.Error()) } + err = r2.Register("baz", NewCounter()) if err != nil { t.Fatal(err.Error()) } + c := NewCounter() Register("bars", c) i := 0 + r2.Each(func(name string, m interface{}) { i++ + if name != "prefix.prefix2.baz" { t.Fatal(name) } }) + if i != 1 { t.Fatal(i) } @@ -292,14 +348,17 @@ func TestChildPrefixedRegistryOfChildRegister(t *testing.T) { func TestWalkRegistries(t *testing.T) { r := NewPrefixedChildRegistry(NewRegistry(), "prefix.") r2 := NewPrefixedChildRegistry(r, "prefix2.") + err := r.Register("foo2", NewCounter()) if err != nil { t.Fatal(err.Error()) } + err = r2.Register("baz", NewCounter()) if err != nil { t.Fatal(err.Error()) } + c := NewCounter() Register("bars", c) diff --git a/metrics/resetting_sample.go b/metrics/resetting_sample.go index 43c1129cd0..73b9f3c05c 100644 --- a/metrics/resetting_sample.go +++ b/metrics/resetting_sample.go @@ -20,5 +20,6 @@ type resettingSample struct { func (rs *resettingSample) Snapshot() Sample { s := rs.Sample.Snapshot() rs.Sample.Clear() + return s } diff --git a/metrics/resetting_timer.go b/metrics/resetting_timer.go index e5327d3bd3..83d4ec30d6 100644 --- a/metrics/resetting_timer.go +++ b/metrics/resetting_timer.go @@ -27,16 +27,20 @@ func GetOrRegisterResettingTimer(name string, r Registry) ResettingTimer { if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, NewResettingTimer).(ResettingTimer) } // NewRegisteredResettingTimer constructs and registers a new StandardResettingTimer. func NewRegisteredResettingTimer(name string, r Registry) ResettingTimer { c := NewResettingTimer() + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } @@ -45,6 +49,7 @@ func NewResettingTimer() ResettingTimer { if !Enabled { return NilResettingTimer{} } + return &StandardResettingTimer{ values: make([]int64, 0, InitialResettingTimerSliceCap), } @@ -120,6 +125,7 @@ func (t *StandardResettingTimer) Mean() float64 { // Record the duration of the execution of the given function. func (t *StandardResettingTimer) Time(f func()) { ts := time.Now() + f() t.Update(time.Since(ts)) } @@ -195,6 +201,7 @@ func (t *ResettingTimerSnapshot) calc(percentiles []float64) { cumulativeValues := make([]int64, count) cumulativeValues[0] = min + for i := 1; i < count; i++ { cumulativeValues[i] = t.values[i] + cumulativeValues[i-1] } @@ -217,6 +224,7 @@ func (t *ResettingTimerSnapshot) calc(percentiles []float64) { if pct >= 0 && indexOfPerc > 0 { indexOfPerc -= 1 // index offset=0 } + thresholdBoundary = t.values[indexOfPerc] } diff --git a/metrics/runtimehistogram_test.go b/metrics/runtimehistogram_test.go index fad6c697a8..67f6f61357 100644 --- a/metrics/runtimehistogram_test.go +++ b/metrics/runtimehistogram_test.go @@ -79,29 +79,37 @@ func TestRuntimeHistogramStats(t *testing.T) { t.Run(fmt.Sprint(i), func(t *testing.T) { t.Parallel() + s := runtimeHistogramSnapshot(test.h) if v := s.Count(); v != test.Count { t.Errorf("Count() = %v, want %v", v, test.Count) } + if v := s.Min(); v != test.Min { t.Errorf("Min() = %v, want %v", v, test.Min) } + if v := s.Max(); v != test.Max { t.Errorf("Max() = %v, want %v", v, test.Max) } + if v := s.Sum(); v != test.Sum { t.Errorf("Sum() = %v, want %v", v, test.Sum) } + if v := s.Mean(); !approxEqual(v, test.Mean, 0.0001) { t.Errorf("Mean() = %v, want %v", v, test.Mean) } + if v := s.Variance(); !approxEqual(v, test.Variance, 0.0001) { t.Errorf("Variance() = %v, want %v", v, test.Variance) } + if v := s.StdDev(); !approxEqual(v, test.StdDev, 0.0001) { t.Errorf("StdDev() = %v, want %v", v, test.StdDev) } + ps := []float64{.5, .8, .9, .99, .995} if v := s.Percentiles(ps); !reflect.DeepEqual(v, test.Percentiles) { t.Errorf("Percentiles(%v) = %v, want %v", ps, v, test.Percentiles) diff --git a/metrics/sample.go b/metrics/sample.go index 5bf59bb239..a3f2f300c0 100644 --- a/metrics/sample.go +++ b/metrics/sample.go @@ -50,6 +50,7 @@ func NewExpDecaySample(reservoirSize int, alpha float64) Sample { if !Enabled { return NilSample{} } + s := &ExpDecaySample{ alpha: alpha, reservoirSize: reservoirSize, @@ -57,6 +58,7 @@ func NewExpDecaySample(reservoirSize int, alpha float64) Sample { values: newExpDecaySampleHeap(reservoirSize), } s.t1 = s.t0.Add(rescaleThreshold) + return s } @@ -81,6 +83,7 @@ func (s *ExpDecaySample) Clear() { func (s *ExpDecaySample) Count() int64 { s.mutex.Lock() defer s.mutex.Unlock() + return s.count } @@ -116,6 +119,7 @@ func (s *ExpDecaySample) Percentiles(ps []float64) []float64 { func (s *ExpDecaySample) Size() int { s.mutex.Lock() defer s.mutex.Unlock() + return s.values.Size() } @@ -125,9 +129,11 @@ func (s *ExpDecaySample) Snapshot() Sample { defer s.mutex.Unlock() vals := s.values.Values() values := make([]int64, len(vals)) + for i, v := range vals { values[i] = v.v } + return &SampleSnapshot{ count: s.count, values: values, @@ -155,9 +161,11 @@ func (s *ExpDecaySample) Values() []int64 { defer s.mutex.Unlock() vals := s.values.Values() values := make([]int64, len(vals)) + for i, v := range vals { values[i] = v.v } + return values } @@ -171,6 +179,7 @@ func (s *ExpDecaySample) Variance() float64 { func (s *ExpDecaySample) update(t time.Time, v int64) { s.mutex.Lock() defer s.mutex.Unlock() + s.count++ if s.values.Size() == s.reservoirSize { s.values.Pop() @@ -183,16 +192,19 @@ func (s *ExpDecaySample) update(t time.Time, v int64) { } else { f64 = rand.Float64() } + s.values.Push(expDecaySample{ k: math.Exp(t.Sub(s.t0).Seconds()*s.alpha) / f64, v: v, }) + if t.After(s.t1) { values := s.values.Values() t0 := s.t0 s.values.Clear() s.t0 = t s.t1 = s.t0.Add(rescaleThreshold) + for _, v := range values { v.k = v.k * math.Exp(-s.alpha*s.t0.Sub(t0).Seconds()) s.values.Push(v) @@ -252,12 +264,14 @@ func SampleMax(values []int64) int64 { if len(values) == 0 { return 0 } + var max int64 = math.MinInt64 for _, v := range values { if max < v { max = v } } + return max } @@ -266,6 +280,7 @@ func SampleMean(values []int64) float64 { if len(values) == 0 { return 0.0 } + return float64(SampleSum(values)) / float64(len(values)) } @@ -274,12 +289,14 @@ func SampleMin(values []int64) int64 { if len(values) == 0 { return 0 } + var min int64 = math.MaxInt64 for _, v := range values { if min > v { min = v } } + return min } @@ -292,9 +309,11 @@ func SamplePercentile(values int64Slice, p float64) float64 { // int64. func SamplePercentiles(values int64Slice, ps []float64) []float64 { scores := make([]float64, len(ps)) + size := len(values) if size > 0 { sort.Sort(values) + for i, p := range ps { pos := p * float64(size+1) if pos < 1.0 { @@ -308,6 +327,7 @@ func SamplePercentiles(values int64Slice, ps []float64) []float64 { } } } + return scores } @@ -375,6 +395,7 @@ func (*SampleSnapshot) Update(int64) { func (s *SampleSnapshot) Values() []int64 { values := make([]int64, len(s.values)) copy(values, s.values) + return values } @@ -392,6 +413,7 @@ func SampleSum(values []int64) int64 { for _, v := range values { sum += v } + return sum } @@ -400,12 +422,16 @@ func SampleVariance(values []int64) float64 { if len(values) == 0 { return 0.0 } + m := SampleMean(values) + var sum float64 + for _, v := range values { d := float64(v) - m sum += d * d } + return sum / float64(len(values)) } @@ -426,6 +452,7 @@ func NewUniformSample(reservoirSize int) Sample { if !Enabled { return NilSample{} } + return &UniformSample{ reservoirSize: reservoirSize, values: make([]int64, 0, reservoirSize), @@ -451,6 +478,7 @@ func (s *UniformSample) Clear() { func (s *UniformSample) Count() int64 { s.mutex.Lock() defer s.mutex.Unlock() + return s.count } @@ -459,6 +487,7 @@ func (s *UniformSample) Count() int64 { func (s *UniformSample) Max() int64 { s.mutex.Lock() defer s.mutex.Unlock() + return SampleMax(s.values) } @@ -466,6 +495,7 @@ func (s *UniformSample) Max() int64 { func (s *UniformSample) Mean() float64 { s.mutex.Lock() defer s.mutex.Unlock() + return SampleMean(s.values) } @@ -474,6 +504,7 @@ func (s *UniformSample) Mean() float64 { func (s *UniformSample) Min() int64 { s.mutex.Lock() defer s.mutex.Unlock() + return SampleMin(s.values) } @@ -481,6 +512,7 @@ func (s *UniformSample) Min() int64 { func (s *UniformSample) Percentile(p float64) float64 { s.mutex.Lock() defer s.mutex.Unlock() + return SamplePercentile(s.values, p) } @@ -489,6 +521,7 @@ func (s *UniformSample) Percentile(p float64) float64 { func (s *UniformSample) Percentiles(ps []float64) []float64 { s.mutex.Lock() defer s.mutex.Unlock() + return SamplePercentiles(s.values, ps) } @@ -496,6 +529,7 @@ func (s *UniformSample) Percentiles(ps []float64) []float64 { func (s *UniformSample) Size() int { s.mutex.Lock() defer s.mutex.Unlock() + return len(s.values) } @@ -505,6 +539,7 @@ func (s *UniformSample) Snapshot() Sample { defer s.mutex.Unlock() values := make([]int64, len(s.values)) copy(values, s.values) + return &SampleSnapshot{ count: s.count, values: values, @@ -515,6 +550,7 @@ func (s *UniformSample) Snapshot() Sample { func (s *UniformSample) StdDev() float64 { s.mutex.Lock() defer s.mutex.Unlock() + return SampleStdDev(s.values) } @@ -522,6 +558,7 @@ func (s *UniformSample) StdDev() float64 { func (s *UniformSample) Sum() int64 { s.mutex.Lock() defer s.mutex.Unlock() + return SampleSum(s.values) } @@ -529,6 +566,7 @@ func (s *UniformSample) Sum() int64 { func (s *UniformSample) Update(v int64) { s.mutex.Lock() defer s.mutex.Unlock() + s.count++ if len(s.values) < s.reservoirSize { s.values = append(s.values, v) @@ -539,6 +577,7 @@ func (s *UniformSample) Update(v int64) { } else { r = rand.Int63n(s.count) } + if r < int64(len(s.values)) { s.values[int(r)] = v } @@ -551,6 +590,7 @@ func (s *UniformSample) Values() []int64 { defer s.mutex.Unlock() values := make([]int64, len(s.values)) copy(values, s.values) + return values } @@ -558,6 +598,7 @@ func (s *UniformSample) Values() []int64 { func (s *UniformSample) Variance() float64 { s.mutex.Lock() defer s.mutex.Unlock() + return SampleVariance(s.values) } @@ -596,6 +637,7 @@ func (h *expDecaySampleHeap) Pop() expDecaySample { n = len(h.s) s := h.s[n-1] h.s = h.s[0 : n-1] + return s } @@ -613,6 +655,7 @@ func (h *expDecaySampleHeap) up(j int) { if i == j || !(h.s[j].k < h.s[i].k) { break } + h.s[i], h.s[j] = h.s[j], h.s[i] j = i } @@ -624,13 +667,16 @@ func (h *expDecaySampleHeap) down(i, n int) { if j1 >= n || j1 < 0 { // j1 < 0 after int overflow break } + j := j1 // left child if j2 := j1 + 1; j2 < n && !(h.s[j1].k < h.s[j2].k) { j = j2 // = 2*i + 2 // right child } + if !(h.s[j].k < h.s[i].k) { break } + h.s[i], h.s[j] = h.s[j], h.s[i] i = j } diff --git a/metrics/sample_test.go b/metrics/sample_test.go index 3ae128d56f..e46c06876b 100644 --- a/metrics/sample_test.go +++ b/metrics/sample_test.go @@ -18,6 +18,7 @@ func BenchmarkCompute1000(b *testing.B) { s[i] = int64(i) } b.ResetTimer() + for i := 0; i < b.N; i++ { SampleVariance(s) } @@ -28,6 +29,7 @@ func BenchmarkCompute1000000(b *testing.B) { s[i] = int64(i) } b.ResetTimer() + for i := 0; i < b.N; i++ { SampleVariance(s) } @@ -38,6 +40,7 @@ func BenchmarkCopy1000(b *testing.B) { s[i] = int64(i) } b.ResetTimer() + for i := 0; i < b.N; i++ { sCopy := make([]int64, len(s)) copy(sCopy, s) @@ -49,6 +52,7 @@ func BenchmarkCopy1000000(b *testing.B) { s[i] = int64(i) } b.ResetTimer() + for i := 0; i < b.N; i++ { sCopy := make([]int64, len(s)) copy(sCopy, s) @@ -84,15 +88,19 @@ func TestExpDecaySample10(t *testing.T) { for i := 0; i < 10; i++ { s.Update(int64(i)) } + if size := s.Count(); size != 10 { t.Errorf("s.Count(): 10 != %v\n", size) } + if size := s.Size(); size != 10 { t.Errorf("s.Size(): 10 != %v\n", size) } + if l := len(s.Values()); l != 10 { t.Errorf("len(s.Values()): 10 != %v\n", l) } + for _, v := range s.Values() { if v > 10 || v < 0 { t.Errorf("out of range [0, 10): %v\n", v) @@ -105,15 +113,19 @@ func TestExpDecaySample100(t *testing.T) { for i := 0; i < 100; i++ { s.Update(int64(i)) } + if size := s.Count(); size != 100 { t.Errorf("s.Count(): 100 != %v\n", size) } + if size := s.Size(); size != 100 { t.Errorf("s.Size(): 100 != %v\n", size) } + if l := len(s.Values()); l != 100 { t.Errorf("len(s.Values()): 100 != %v\n", l) } + for _, v := range s.Values() { if v > 100 || v < 0 { t.Errorf("out of range [0, 100): %v\n", v) @@ -126,15 +138,19 @@ func TestExpDecaySample1000(t *testing.T) { for i := 0; i < 1000; i++ { s.Update(int64(i)) } + if size := s.Count(); size != 1000 { t.Errorf("s.Count(): 1000 != %v\n", size) } + if size := s.Size(); size != 100 { t.Errorf("s.Size(): 100 != %v\n", size) } + if l := len(s.Values()); l != 100 { t.Errorf("len(s.Values()): 100 != %v\n", l) } + for _, v := range s.Values() { if v > 1000 || v < 0 { t.Errorf("out of range [0, 1000): %v\n", v) @@ -152,14 +168,18 @@ func TestExpDecaySampleNanosecondRegression(t *testing.T) { s.Update(10) } time.Sleep(1 * time.Millisecond) + for i := 0; i < 100; i++ { s.Update(20) } + v := s.Values() avg := float64(0) + for i := 0; i < len(v); i++ { avg += float64(v[i]) } + avg /= float64(len(v)) if avg > 16 || avg < 14 { t.Errorf("out of range [14, 16]: %v\n", avg) @@ -170,6 +190,7 @@ func TestExpDecaySampleRescale(t *testing.T) { s := NewExpDecaySample(2, 0.001).(*ExpDecaySample) s.update(time.Now(), 1) s.update(time.Now().Add(time.Hour+time.Microsecond), 1) + for _, v := range s.values.Values() { if v.k == 0.0 { t.Fatal("v.k == 0.0") @@ -180,9 +201,11 @@ func TestExpDecaySampleRescale(t *testing.T) { func TestExpDecaySampleSnapshot(t *testing.T) { now := time.Now() s := NewExpDecaySample(100, 0.99).(*ExpDecaySample).SetRand(rand.New(rand.NewSource(1))) + for i := 1; i <= 10000; i++ { s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i)) } + snapshot := s.Snapshot() s.Update(1) testExpDecaySampleStatistics(t, snapshot) @@ -191,6 +214,7 @@ func TestExpDecaySampleSnapshot(t *testing.T) { func TestExpDecaySampleStatistics(t *testing.T) { now := time.Now() s := NewExpDecaySample(100, 0.99).(*ExpDecaySample).SetRand(rand.New(rand.NewSource(1))) + for i := 1; i <= 10000; i++ { s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i)) } @@ -202,15 +226,19 @@ func TestUniformSample(t *testing.T) { for i := 0; i < 1000; i++ { s.Update(int64(i)) } + if size := s.Count(); size != 1000 { t.Errorf("s.Count(): 1000 != %v\n", size) } + if size := s.Size(); size != 100 { t.Errorf("s.Size(): 100 != %v\n", size) } + if l := len(s.Values()); l != 100 { t.Errorf("len(s.Values()): 100 != %v\n", l) } + for _, v := range s.Values() { if v > 1000 || v < 0 { t.Errorf("out of range [0, 100): %v\n", v) @@ -221,15 +249,19 @@ func TestUniformSample(t *testing.T) { func TestUniformSampleIncludesTail(t *testing.T) { s := NewUniformSample(100) max := 100 + for i := 0; i < max; i++ { s.Update(int64(i)) } + v := s.Values() sum := 0 exp := (max - 1) * max / 2 + for i := 0; i < len(v); i++ { sum += int(v[i]) } + if exp != sum { t.Errorf("sum: %v != %v\n", exp, sum) } @@ -240,6 +272,7 @@ func TestUniformSampleSnapshot(t *testing.T) { for i := 1; i <= 10000; i++ { s.Update(int64(i)) } + snapshot := s.Snapshot() s.Update(1) testUniformSampleStatistics(t, snapshot) @@ -255,9 +288,12 @@ func TestUniformSampleStatistics(t *testing.T) { func benchmarkSample(b *testing.B, s Sample) { var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) pauseTotalNs := memStats.PauseTotalNs + b.ResetTimer() + for i := 0; i < b.N; i++ { s.Update(1) } @@ -271,25 +307,32 @@ func testExpDecaySampleStatistics(t *testing.T, s Sample) { if count := s.Count(); count != 10000 { t.Errorf("s.Count(): 10000 != %v\n", count) } + if min := s.Min(); min != 107 { t.Errorf("s.Min(): 107 != %v\n", min) } + if max := s.Max(); max != 10000 { t.Errorf("s.Max(): 10000 != %v\n", max) } + if mean := s.Mean(); mean != 4965.98 { t.Errorf("s.Mean(): 4965.98 != %v\n", mean) } + if stdDev := s.StdDev(); stdDev != 2959.825156930727 { t.Errorf("s.StdDev(): 2959.825156930727 != %v\n", stdDev) } + ps := s.Percentiles([]float64{0.5, 0.75, 0.99}) if ps[0] != 4615 { t.Errorf("median: 4615 != %v\n", ps[0]) } + if ps[1] != 7672 { t.Errorf("75th percentile: 7672 != %v\n", ps[1]) } + if ps[2] != 9998.99 { t.Errorf("99th percentile: 9998.99 != %v\n", ps[2]) } @@ -299,25 +342,32 @@ func testUniformSampleStatistics(t *testing.T, s Sample) { if count := s.Count(); count != 10000 { t.Errorf("s.Count(): 10000 != %v\n", count) } + if min := s.Min(); min != 37 { t.Errorf("s.Min(): 37 != %v\n", min) } + if max := s.Max(); max != 9989 { t.Errorf("s.Max(): 9989 != %v\n", max) } + if mean := s.Mean(); mean != 4748.14 { t.Errorf("s.Mean(): 4748.14 != %v\n", mean) } + if stdDev := s.StdDev(); stdDev != 2826.684117548333 { t.Errorf("s.StdDev(): 2826.684117548333 != %v\n", stdDev) } + ps := s.Percentiles([]float64{0.5, 0.75, 0.99}) if ps[0] != 4599 { t.Errorf("median: 4599 != %v\n", ps[0]) } + if ps[1] != 7380.5 { t.Errorf("75th percentile: 7380.5 != %v\n", ps[1]) } + if math.Abs(9986.429999999998-ps[2]) > epsilonPercentile { t.Errorf("99th percentile: 9986.429999999998 != %v\n", ps[2]) } @@ -330,14 +380,18 @@ func TestUniformSampleConcurrentUpdateCount(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } + s := NewUniformSample(100) for i := 0; i < 100; i++ { s.Update(int64(i)) } + quit := make(chan struct{}) + go func() { t := time.NewTicker(10 * time.Millisecond) defer t.Stop() + for { select { case <-t.C: @@ -348,6 +402,7 @@ func TestUniformSampleConcurrentUpdateCount(t *testing.T) { } } }() + for i := 0; i < 1000; i++ { s.Count() time.Sleep(5 * time.Millisecond) diff --git a/metrics/timer.go b/metrics/timer.go index a63c9dfb6c..a7f4fe6d9f 100644 --- a/metrics/timer.go +++ b/metrics/timer.go @@ -35,6 +35,7 @@ func GetOrRegisterTimer(name string, r Registry) Timer { if nil == r { r = DefaultRegistry } + return r.GetOrRegister(name, NewTimer).(Timer) } @@ -44,6 +45,7 @@ func NewCustomTimer(h Histogram, m Meter) Timer { if !Enabled { return NilTimer{} } + return &StandardTimer{ histogram: h, meter: m, @@ -55,10 +57,13 @@ func NewCustomTimer(h Histogram, m Meter) Timer { // allow for garbage collection. func NewRegisteredTimer(name string, r Registry) Timer { c := NewTimer() + if nil == r { r = DefaultRegistry } + r.Register(name, c) + return c } @@ -69,6 +74,7 @@ func NewTimer() Timer { if !Enabled { return NilTimer{} } + return &StandardTimer{ histogram: NewHistogram(NewExpDecaySample(1028, 0.015)), meter: NewMeter(), @@ -197,6 +203,7 @@ func (t *StandardTimer) RateMean() float64 { func (t *StandardTimer) Snapshot() Timer { t.mutex.Lock() defer t.mutex.Unlock() + return &TimerSnapshot{ histogram: t.histogram.Snapshot().(*HistogramSnapshot), meter: t.meter.Snapshot().(*MeterSnapshot), @@ -221,6 +228,7 @@ func (t *StandardTimer) Sum() int64 { // Record the duration of the execution of the given function. func (t *StandardTimer) Time(f func()) { ts := time.Now() + f() t.Update(time.Since(ts)) } diff --git a/metrics/timer_test.go b/metrics/timer_test.go index 903e8e8d49..be4f804973 100644 --- a/metrics/timer_test.go +++ b/metrics/timer_test.go @@ -9,7 +9,9 @@ import ( func BenchmarkTimer(b *testing.B) { tm := NewTimer() + b.ResetTimer() + for i := 0; i < b.N; i++ { tm.Update(1) } @@ -18,6 +20,7 @@ func BenchmarkTimer(b *testing.B) { func TestGetOrRegisterTimer(t *testing.T) { r := NewRegistry() NewRegisteredTimer("foo", r).Update(47) + if tm := GetOrRegisterTimer("foo", r); tm.Count() != 1 { t.Fatal(tm) } @@ -27,6 +30,7 @@ func TestTimerExtremes(t *testing.T) { tm := NewTimer() tm.Update(math.MaxInt64) tm.Update(0) + if stdDev := tm.StdDev(); stdDev != 4.611686018427388e+18 { t.Errorf("tm.StdDev(): 4.611686018427388e+18 != %v\n", stdDev) } @@ -35,10 +39,13 @@ func TestTimerExtremes(t *testing.T) { func TestTimerStop(t *testing.T) { l := len(arbiter.meters) tm := NewTimer() + if l+1 != len(arbiter.meters) { t.Errorf("arbiter.meters: %d != %d\n", l+1, len(arbiter.meters)) } + tm.Stop() + if l != len(arbiter.meters) { t.Errorf("arbiter.meters: %d != %d\n", l, len(arbiter.meters)) } @@ -50,16 +57,19 @@ func TestTimerFunc(t *testing.T) { testStart = time.Now() actualTime time.Duration ) + tm.Time(func() { time.Sleep(50 * time.Millisecond) actualTime = time.Since(testStart) }) + var ( drift = time.Millisecond * 2 measured = time.Duration(tm.Max()) ceil = actualTime + drift floor = actualTime - drift ) + if measured > ceil || measured < floor { t.Errorf("tm.Max(): %v > %v || %v > %v\n", measured, ceil, measured, floor) } @@ -70,37 +80,48 @@ func TestTimerZero(t *testing.T) { if count := tm.Count(); count != 0 { t.Errorf("tm.Count(): 0 != %v\n", count) } + if min := tm.Min(); min != 0 { t.Errorf("tm.Min(): 0 != %v\n", min) } + if max := tm.Max(); max != 0 { t.Errorf("tm.Max(): 0 != %v\n", max) } + if mean := tm.Mean(); mean != 0.0 { t.Errorf("tm.Mean(): 0.0 != %v\n", mean) } + if stdDev := tm.StdDev(); stdDev != 0.0 { t.Errorf("tm.StdDev(): 0.0 != %v\n", stdDev) } + ps := tm.Percentiles([]float64{0.5, 0.75, 0.99}) if ps[0] != 0.0 { t.Errorf("median: 0.0 != %v\n", ps[0]) } + if ps[1] != 0.0 { t.Errorf("75th percentile: 0.0 != %v\n", ps[1]) } + if ps[2] != 0.0 { t.Errorf("99th percentile: 0.0 != %v\n", ps[2]) } + if rate1 := tm.Rate1(); rate1 != 0.0 { t.Errorf("tm.Rate1(): 0.0 != %v\n", rate1) } + if rate5 := tm.Rate5(); rate5 != 0.0 { t.Errorf("tm.Rate5(): 0.0 != %v\n", rate5) } + if rate15 := tm.Rate15(); rate15 != 0.0 { t.Errorf("tm.Rate15(): 0.0 != %v\n", rate15) } + if rateMean := tm.RateMean(); rateMean != 0.0 { t.Errorf("tm.RateMean(): 0.0 != %v\n", rateMean) } diff --git a/metrics/writer.go b/metrics/writer.go index 256fbd14c9..8dcfb8d356 100644 --- a/metrics/writer.go +++ b/metrics/writer.go @@ -19,11 +19,13 @@ func Write(r Registry, d time.Duration, w io.Writer) { // io.Writer. func WriteOnce(r Registry, w io.Writer) { var namedMetrics namedMetricSlice + r.Each(func(name string, i interface{}) { namedMetrics = append(namedMetrics, namedMetric{name, i}) }) sort.Sort(namedMetrics) + for _, namedMetric := range namedMetrics { switch metric := namedMetric.m.(type) { case Counter: @@ -45,6 +47,7 @@ func WriteOnce(r Registry, w io.Writer) { case Histogram: h := metric.Snapshot() ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + fmt.Fprintf(w, "histogram %s\n", namedMetric.name) fmt.Fprintf(w, " count: %9d\n", h.Count()) fmt.Fprintf(w, " min: %9d\n", h.Min()) @@ -58,6 +61,7 @@ func WriteOnce(r Registry, w io.Writer) { fmt.Fprintf(w, " 99.9%%: %12.2f\n", ps[4]) case Meter: m := metric.Snapshot() + fmt.Fprintf(w, "meter %s\n", namedMetric.name) fmt.Fprintf(w, " count: %9d\n", m.Count()) fmt.Fprintf(w, " 1-min rate: %12.2f\n", m.Rate1()) @@ -67,6 +71,7 @@ func WriteOnce(r Registry, w io.Writer) { case Timer: t := metric.Snapshot() ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + fmt.Fprintf(w, "timer %s\n", namedMetric.name) fmt.Fprintf(w, " count: %9d\n", t.Count()) fmt.Fprintf(w, " min: %9d\n", t.Min()) diff --git a/metrics/writer_test.go b/metrics/writer_test.go index 1aacc28712..a879e99d09 100644 --- a/metrics/writer_test.go +++ b/metrics/writer_test.go @@ -14,6 +14,7 @@ func TestMetricsSorting(t *testing.T) { } sort.Sort(namedMetrics) + for i, name := range []string{"bbb", "fff", "ggg", "zzz"} { if namedMetrics[i].name != name { t.Fail() diff --git a/miner/miner.go b/miner/miner.go index 801a9f4cc7..49e450a2e4 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -45,15 +45,15 @@ type Backend interface { // Config is the configuration parameters of mining. type Config struct { - Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards - Notify []string `toml:",omitempty"` // HTTP URL list to be notified of new work packages (only useful in ethash). - NotifyFull bool `toml:",omitempty"` // Notify with pending block headers instead of work packages - ExtraData hexutil.Bytes `toml:",omitempty"` // Block extra data set by the miner - GasFloor uint64 // Target gas floor for mined blocks. - GasCeil uint64 // Target gas ceiling for mined blocks. - GasPrice *big.Int // Minimum gas price for mining a transaction - Recommit time.Duration // The time interval for miner to re-create mining work. - Noverify bool // Disable remote mining solution verification(only useful in ethash). + Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards + Notify []string `toml:",omitempty"` // HTTP URL list to be notified of new work packages (only useful in ethash). + NotifyFull bool `toml:",omitempty"` // Notify with pending block headers instead of work packages + ExtraData hexutil.Bytes `toml:",omitempty"` // Block extra data set by the miner + GasFloor uint64 // Target gas floor for mined blocks. + GasCeil uint64 // Target gas ceiling for mined blocks. + GasPrice *big.Int // Minimum gas price for mining a transaction + Recommit time.Duration // The time interval for miner to re-create mining work. + Noverify bool // Disable remote mining solution verification(only useful in ethash). CommitInterruptFlag bool // Interrupt commit when time is up ( default = true) NewPayloadTimeout time.Duration // The maximum time allowance for creating a new payload @@ -96,7 +96,9 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, true), } miner.wg.Add(1) + go miner.update() + return miner } @@ -117,6 +119,7 @@ func (miner *Miner) update() { shouldStart := false canStart := true dlEventCh := events.Chan() + for { select { case ev := <-dlEventCh: @@ -125,23 +128,29 @@ func (miner *Miner) update() { dlEventCh = nil continue } + switch ev.Data.(type) { case downloader.StartEvent: wasMining := miner.Mining() miner.worker.stop() + canStart = false + if wasMining { // Resume mining after sync was finished shouldStart = true + log.Info("Mining aborted due to sync") } case downloader.FailedEvent: canStart = true + if shouldStart { miner.worker.start() } case downloader.DoneEvent: canStart = true + if shouldStart { miner.worker.start() } @@ -152,9 +161,11 @@ func (miner *Miner) update() { if canStart { miner.worker.start() } + shouldStart = true case <-miner.stopCh: shouldStart = false + miner.worker.stop() case <-miner.exitCh: miner.worker.close() @@ -184,6 +195,7 @@ func (miner *Miner) Hashrate() uint64 { if pow, ok := miner.engine.(consensus.PoW); ok { return uint64(pow.Hashrate()) } + return 0 } @@ -191,7 +203,9 @@ func (miner *Miner) SetExtra(extra []byte) error { if uint64(len(extra)) > params.MaximumExtraDataSize { return fmt.Errorf("extra exceeds max length. %d > %v", len(extra), params.MaximumExtraDataSize) } + miner.worker.setExtra(extra) + return nil } diff --git a/miner/miner_test.go b/miner/miner_test.go index ca5e475788..359ed6a81a 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -246,6 +246,7 @@ func TestMinerSetEtherbase(t *testing.T) { coinbase := common.HexToAddress("0xdeedbeef") miner.SetEtherbase(coinbase) + if addr := miner.worker.etherbase(); addr != coinbase { t.Fatalf("Unexpected etherbase want %x got %x", coinbase, addr) } @@ -258,8 +259,10 @@ func waitForMiningState(t *testing.T, m *Miner, mining bool) { t.Helper() var state bool + for i := 0; i < 100; i++ { time.Sleep(10 * time.Millisecond) + if state = m.Mining(); state == mining { return } @@ -275,6 +278,7 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { // Create chainConfig chainDB := rawdb.NewMemoryDatabase() genesis := core.DeveloperGenesisBlock(15, 11_500_000, common.HexToAddress("12345")) + chainConfig, _, err := core.SetupGenesisBlock(chainDB, trie.NewDatabase(chainDB), genesis) if err != nil { t.Fatalf("can't create new chain config: %v", err) @@ -286,6 +290,7 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { if err != nil { t.Fatalf("can't create new chain %v", err) } + statedb, _ := state.New(common.Hash{}, state.NewDatabase(chainDB), nil) blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} @@ -299,9 +304,11 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { bc.Stop() engine.Close() pool.Stop() + if !skipMiner { miner.Close() } } + return miner, mux, cleanup } diff --git a/miner/payload_building.go b/miner/payload_building.go index f84d908e86..9177c77897 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -51,8 +51,11 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID { hasher.Write(args.Random[:]) hasher.Write(args.FeeRecipient[:]) rlp.Encode(hasher, args.Withdrawals) + var out engine.PayloadID + copy(out[:], hasher.Sum(nil)[:8]) + return out } @@ -80,6 +83,7 @@ func newPayload(empty *types.Block, id engine.PayloadID) *Payload { } log.Info("Starting work on payload", "id", payload.id) payload.cond = sync.NewCond(&payload.lock) + return payload } @@ -105,6 +109,7 @@ func (payload *Payload) update(block *types.Block, fees *big.Int, elapsed time.D "txs", len(block.Transactions()), "gas", block.GasUsed(), "fees", feesInEther, "root", block.Root(), "elapsed", common.PrettyDuration(elapsed)) } + payload.cond.Broadcast() // fire signal for notifying full block } @@ -119,9 +124,11 @@ func (payload *Payload) Resolve() *engine.ExecutionPayloadEnvelope { default: close(payload.stop) } + if payload.full != nil { return engine.BlockToExecutableData(payload.full, payload.fullFees) } + return engine.BlockToExecutableData(payload.empty, big.NewInt(0)) } @@ -148,6 +155,7 @@ func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope { } payload.cond.Wait() } + return engine.BlockToExecutableData(payload.full, payload.fullFees) } @@ -181,9 +189,11 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { case <-timer.C: start := time.Now() block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, false) + if err == nil { payload.update(block, fees, time.Since(start)) } + timer.Reset(w.recommit) case <-payload.stop: log.Info("Stopping work on payload", "id", payload.id, "reason", "delivery") @@ -194,5 +204,6 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { } } }() + return payload, nil } diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go index fdbf6668db..6ecfcae49d 100644 --- a/miner/payload_building_test.go +++ b/miner/payload_building_test.go @@ -34,6 +34,7 @@ func TestBuildPayload(t *testing.T) { db = rawdb.NewMemoryDatabase() recipient = common.HexToAddress("0xdeadbeef") ) + w, b, _ := newTestWorker(t, params.TestChainConfig, ethash.NewFaker(), db, 0, false, 0, 0) defer w.close() @@ -44,24 +45,30 @@ func TestBuildPayload(t *testing.T) { Random: common.Hash{}, FeeRecipient: recipient, } + payload, err := w.buildPayload(args) if err != nil { t.Fatalf("Failed to build payload %v", err) } + verify := func(outer *engine.ExecutionPayloadEnvelope, txs int) { payload := outer.ExecutionPayload if payload.ParentHash != b.chain.CurrentBlock().Hash() { t.Fatal("Unexpect parent hash") } + if payload.Random != (common.Hash{}) { t.Fatal("Unexpect random value") } + if payload.Timestamp != timestamp { t.Fatal("Unexpect timestamp") } + if payload.FeeRecipient != recipient { t.Fatal("Unexpect fee recipient") } + if len(payload.Transactions) != txs { t.Fatal("Unexpect transaction set") } @@ -76,6 +83,7 @@ func TestBuildPayload(t *testing.T) { // result should be unchanged dataOne := payload.Resolve() dataTwo := payload.Resolve() + if !reflect.DeepEqual(dataOne, dataTwo) { t.Fatal("Unexpected payload data") } @@ -83,6 +91,7 @@ func TestBuildPayload(t *testing.T) { func TestPayloadId(t *testing.T) { ids := make(map[string]int) + for i, tt := range []*BuildPayloadArgs{ &BuildPayloadArgs{ Parent: common.Hash{1}, @@ -153,6 +162,7 @@ func TestPayloadId(t *testing.T) { if prev, exists := ids[id]; exists { t.Errorf("ID collision, case %d and case %d: id %v", prev, i, id) } + ids[id] = i } } diff --git a/miner/stress/1559/main.go b/miner/stress/1559/main.go index 2e8b78d85e..600f804344 100644 --- a/miner/stress/1559/main.go +++ b/miner/stress/1559/main.go @@ -72,6 +72,7 @@ func main() { nodes []*eth.Ethereum enodes []*enode.Node ) + for i := 0; i < 4; i++ { // Start the node and wait until it's up stack, ethBackend, err := makeMiner(genesis) @@ -94,11 +95,13 @@ func main() { // Iterate over all the nodes and start mining time.Sleep(3 * time.Second) + for _, node := range nodes { if err := node.StartMining(1); err != nil { panic(err) } } + time.Sleep(3 * time.Second) // Start injecting transactions from the faucets like crazy @@ -109,6 +112,7 @@ func main() { // so the new 1559 txs can be created with this signer. signer = types.LatestSignerForChainID(genesis.Config.ChainID) ) + for { // Stop when interrupted. select { @@ -116,6 +120,7 @@ func main() { for _, node := range stacks { node.Close() } + return default: } @@ -134,6 +139,7 @@ func main() { if err := backend.TxPool().AddLocal(tx); err != nil { continue } + nonces[index]++ // Wait if we're too saturated @@ -155,6 +161,7 @@ func makeTransaction(nonce uint64, privKey *ecdsa.PrivateKey, signer types.Signe if err != nil { panic(err) } + return tx } // Generate eip 1559 transaction @@ -163,6 +170,7 @@ func makeTransaction(nonce uint64, privKey *ecdsa.PrivateKey, signer types.Signe // Feecap and feetip are limited to 32 bytes. Offer a sightly // larger buffer for creating both valid and invalid transactions. var buf = make([]byte, 32+5) + crand.Read(buf) gasTipCap := new(big.Int).SetBytes(buf) @@ -173,12 +181,14 @@ func makeTransaction(nonce uint64, privKey *ecdsa.PrivateKey, signer types.Signe } // Generate the feecap, 75% valid feecap and 25% unguaranteed. var gasFeeCap *big.Int + if rand.Intn(4) == 0 { crand.Read(buf) gasFeeCap = new(big.Int).SetBytes(buf) } else { gasFeeCap = new(big.Int).Add(baseFee, gasTipCap) } + return types.MustSignNewTx(privKey, signer, &types.DynamicFeeTx{ ChainID: signer.ChainID(), Nonce: nonce, @@ -212,11 +222,13 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil), } } + if londonBlock.Sign() == 0 { log.Info("Enabled the eip 1559 by default") } else { log.Info("Registered the london fork", "number", londonBlock) } + return genesis } @@ -240,6 +252,7 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { if err != nil { return nil, nil, err } + ethBackend, err := eth.New(stack, ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), @@ -259,6 +272,8 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { if err != nil { return nil, nil, err } + err = stack.Start() + return stack, ethBackend, err } diff --git a/miner/stress/beacon/main.go b/miner/stress/beacon/main.go index 61f894c847..4b4f8eb76a 100644 --- a/miner/stress/beacon/main.go +++ b/miner/stress/beacon/main.go @@ -113,9 +113,11 @@ func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode } else { stack, ethBackend, api, err = makeFullNode(genesis) } + if err != nil { panic(err) } + for stack.Server().NodeInfo().Ports.Listener == 0 { time.Sleep(250 * time.Millisecond) } @@ -123,6 +125,7 @@ func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode for _, n := range enodes { stack.Server().AddPeer(n) } + enode := stack.Server().Self() // Inject the signer key and start sealing with it @@ -137,6 +140,7 @@ func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode if _, err := store.NewAccount(""); err != nil { panic(err) } + return ðNode{ typ: typ, api: api, @@ -152,6 +156,7 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) if n.typ != eth2MiningNode { return nil, errors.New("invalid node type") } + timestamp := uint64(time.Now().Unix()) if timestamp <= parentTimestamp { timestamp = parentTimestamp + 1 @@ -167,12 +172,14 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } + payload, err := n.api.ForkchoiceUpdatedV1(fcState, &payloadAttribute) if err != nil { return nil, err } time.Sleep(time.Second * 5) // give enough time for block creation + return n.api.GetPayloadV1(*payload.PayloadID) } @@ -180,6 +187,7 @@ func (n *ethNode) insertBlock(eb engine.ExecutableData) error { if !eth2types(n.typ) { return errors.New("invalid node type") } + switch n.typ { case eth2NormalNode, eth2MiningNode: newResp, err := n.api.NewPayloadV1(eb) @@ -188,6 +196,7 @@ func (n *ethNode) insertBlock(eb engine.ExecutableData) error { } else if newResp.Status != "VALID" { return errors.New("failed to insert block") } + return nil case eth2LightClient: newResp, err := n.lapi.ExecutePayloadV1(eb) @@ -196,6 +205,7 @@ func (n *ethNode) insertBlock(eb engine.ExecutableData) error { } else if newResp.Status != "VALID" { return errors.New("failed to insert block") } + return nil default: return errors.New("undefined node") @@ -206,6 +216,7 @@ func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed engine.Executab if !eth2types(n.typ) { return errors.New("invalid node type") } + if err := n.insertBlock(ed); err != nil { return err } @@ -220,16 +231,19 @@ func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed engine.Executab SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } + switch n.typ { case eth2NormalNode, eth2MiningNode: if _, err := n.api.ForkchoiceUpdatedV1(fcState, nil); err != nil { return err } + return nil case eth2LightClient: if _, err := n.lapi.ForkchoiceUpdatedV1(fcState, nil); err != nil { return err } + return nil default: return errors.New("undefined node") @@ -260,11 +274,13 @@ func (mgr *nodeManager) createNode(typ nodetype) { func (mgr *nodeManager) getNodes(typ nodetype) []*ethNode { var ret []*ethNode + for _, node := range mgr.nodes { if node.typ == typ { ret = append(ret, node) } } + return ret } @@ -278,6 +294,7 @@ func (mgr *nodeManager) startMining() { func (mgr *nodeManager) shutdown() { close(mgr.close) + for _, node := range mgr.nodes { node.stack.Close() } @@ -287,8 +304,10 @@ func (mgr *nodeManager) run() { if len(mgr.nodes) == 0 { return } + chain := mgr.nodes[0].ethBackend.BlockChain() sink := make(chan core.ChainHeadEvent, 1024) + sub := chain.SubscribeChainHeadEvent(sink) defer sub.Unsubscribe() @@ -297,6 +316,7 @@ func (mgr *nodeManager) run() { parentBlock *types.Block waitFinalise []*types.Block ) + timer := time.NewTimer(0) defer timer.Stop() <-timer.C // discard the initial tick @@ -305,6 +325,7 @@ func (mgr *nodeManager) run() { if transitionDifficulty.Sign() == 0 { transitioned = true parentBlock = mgr.genesisBlock + timer.Reset(blockInterval) log.Info("Enable the transition by default") } @@ -314,17 +335,21 @@ func (mgr *nodeManager) run() { if parentBlock == nil { return } + if len(waitFinalise) == 0 { return } + oldest := waitFinalise[0] if oldest.NumberU64() > parentBlock.NumberU64() { return } + distance := parentBlock.NumberU64() - oldest.NumberU64() if int(distance) < finalizationDist { return } + nodes := mgr.getNodes(eth2MiningNode) nodes = append(nodes, mgr.getNodes(eth2NormalNode)...) //nodes = append(nodes, mgr.getNodes(eth2LightClient)...) @@ -336,7 +361,9 @@ func (mgr *nodeManager) run() { } node.api.ForkchoiceUpdatedV1(fcState, nil) } + log.Info("Finalised eth2 block", "number", oldest.NumberU64(), "hash", oldest.Hash()) + waitFinalise = waitFinalise[1:] } @@ -350,11 +377,14 @@ func (mgr *nodeManager) run() { if transitioned { continue } + td := chain.GetTd(ev.Block.Hash(), ev.Block.NumberU64()) if td.Cmp(transitionDifficulty) < 0 { continue } + transitioned, parentBlock = true, ev.Block + timer.Reset(blockInterval) log.Info("Transition difficulty reached", "td", td, "target", transitionDifficulty, "number", ev.Block.NumberU64(), "hash", ev.Block.Hash()) @@ -363,10 +393,12 @@ func (mgr *nodeManager) run() { if len(producers) == 0 { continue } + hash, timestamp := parentBlock.Hash(), parentBlock.Time() if parentBlock.NumberU64() == 0 { timestamp = uint64(time.Now().Unix()) - uint64(blockIntervalInt) } + ed, err := producers[0].assembleBlock(hash, timestamp) if err != nil { log.Error("Failed to assemble the block", "err", err) @@ -378,14 +410,18 @@ func (mgr *nodeManager) run() { nodes := mgr.getNodes(eth2MiningNode) nodes = append(nodes, mgr.getNodes(eth2NormalNode)...) nodes = append(nodes, mgr.getNodes(eth2LightClient)...) + for _, node := range nodes { if err := node.insertBlockAndSetHead(parentBlock.Header(), *ed); err != nil { log.Error("Failed to insert block", "type", node.typ, "err", err) } } + log.Info("Create and insert eth2 block", "number", ed.Number) + parentBlock = block waitFinalise = append(waitFinalise, block) + timer.Reset(blockInterval) } } @@ -405,6 +441,7 @@ func main() { // Create an Ethash network genesis := makeGenesis(faucets) + manager := newNodeManager(genesis) defer manager.shutdown() @@ -416,14 +453,18 @@ func main() { // Iterate over all the nodes and start mining time.Sleep(3 * time.Second) + if transitionDifficulty.Sign() != 0 { manager.startMining() } + go manager.run() // Start injecting transactions from the faucets like crazy time.Sleep(3 * time.Second) + nonces := make([]uint64, len(faucets)) + for { // Pick a random mining node nodes := manager.getNodes(eth2MiningNode) @@ -436,9 +477,11 @@ func main() { if err != nil { panic(err) } + if err := node.ethBackend.TxPool().AddLocal(tx); err != nil { panic(err) } + nonces[index]++ // Wait if we're too saturated @@ -465,6 +508,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil), } } + return genesis } @@ -488,6 +532,7 @@ func makeFullNode(genesis *core.Genesis) (*node.Node, *eth.Ethereum, *ethcatalys if err != nil { return nil, nil, nil, err } + econfig := ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), @@ -507,15 +552,19 @@ func makeFullNode(genesis *core.Genesis) (*node.Node, *eth.Ethereum, *ethcatalys LightPeers: 10, LightNoSyncServe: true, } + ethBackend, err := eth.New(stack, econfig) if err != nil { return nil, nil, nil, err } + _, err = les.NewLesServer(stack, ethBackend, econfig) if err != nil { log.Crit("Failed to create the LES server", "err", err) } + err = stack.Start() + return stack, ethBackend, ethcatalyst.NewConsensusAPI(ethBackend), err } @@ -539,6 +588,7 @@ func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *lesc if err != nil { return nil, nil, nil, err } + lesBackend, err := les.New(stack, ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), @@ -553,7 +603,9 @@ func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *lesc if err != nil { return nil, nil, nil, err } + err = stack.Start() + return stack, lesBackend, lescatalyst.NewConsensusAPI(lesBackend), err } @@ -561,5 +613,6 @@ func eth2types(typ nodetype) bool { if typ == eth2LightClient || typ == eth2NormalNode || typ == eth2MiningNode { return true } + return false } diff --git a/miner/stress/clique/main.go b/miner/stress/clique/main.go index 74962c6d51..00f3ad1ba0 100644 --- a/miner/stress/clique/main.go +++ b/miner/stress/clique/main.go @@ -53,6 +53,7 @@ func main() { for i := 0; i < len(faucets); i++ { faucets[i], _ = crypto.GenerateKey() } + sealers := make([]*ecdsa.PrivateKey, 4) for i := 0; i < len(sealers); i++ { sealers[i], _ = crypto.GenerateKey() @@ -69,6 +70,7 @@ func main() { nodes []*eth.Ethereum enodes []*enode.Node ) + for _, sealer := range sealers { // Start the node and wait until it's up stack, ethBackend, err := makeSealer(genesis) @@ -91,6 +93,7 @@ func main() { // Inject the signer key and start sealing with it ks := keystore.NewKeyStore(stack.KeyStoreDir(), keystore.LightScryptN, keystore.LightScryptP) + signer, err := ks.ImportECDSA(sealer, "") if err != nil { panic(err) @@ -98,20 +101,24 @@ func main() { if err := ks.Unlock(signer, ""); err != nil { panic(err) } + stack.AccountManager().AddBackend(ks) } // Iterate over all the nodes and start signing on them time.Sleep(3 * time.Second) + for _, node := range nodes { if err := node.StartMining(1); err != nil { panic(err) } } + time.Sleep(3 * time.Second) // Start injecting transactions from the faucet like crazy nonces := make([]uint64, len(faucets)) + for { // Stop when interrupted. select { @@ -119,6 +126,7 @@ func main() { for _, node := range stacks { node.Close() } + return default: } @@ -132,9 +140,11 @@ func main() { if err != nil { panic(err) } + if err := backend.TxPool().AddLocal(tx); err != nil { panic(err) } + nonces[index]++ // Wait if we're too saturated @@ -165,6 +175,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core for i, sealer := range sealers { signers[i] = crypto.PubkeyToAddress(sealer.PublicKey) } + for i := 0; i < len(signers); i++ { for j := i + 1; j < len(signers); j++ { if bytes.Compare(signers[i][:], signers[j][:]) > 0 { @@ -172,6 +183,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core } } } + genesis.ExtraData = make([]byte, 32+len(signers)*common.AddressLength+65) for i, signer := range signers { copy(genesis.ExtraData[32+i*common.AddressLength:], signer[:]) @@ -219,5 +231,6 @@ func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { } err = stack.Start() + return stack, ethBackend, err } diff --git a/miner/stress/ethash/main.go b/miner/stress/ethash/main.go index 6905bf01f1..411362e8a5 100644 --- a/miner/stress/ethash/main.go +++ b/miner/stress/ethash/main.go @@ -67,6 +67,7 @@ func main() { nodes []*eth.Ethereum enodes []*enode.Node ) + for i := 0; i < 4; i++ { // Start the node and wait until it's up stack, ethBackend, err := makeMiner(genesis) @@ -90,15 +91,18 @@ func main() { // Iterate over all the nodes and start mining time.Sleep(3 * time.Second) + for _, node := range nodes { if err := node.StartMining(1); err != nil { panic(err) } } + time.Sleep(3 * time.Second) // Start injecting transactions from the faucets like crazy nonces := make([]uint64, len(faucets)) + for { // Stop when interrupted. select { @@ -106,6 +110,7 @@ func main() { for _, node := range stacks { node.Close() } + return default: } @@ -119,9 +124,11 @@ func main() { if err != nil { panic(err) } + if err := backend.TxPool().AddLocal(tx); err != nil { panic(err) } + nonces[index]++ // Wait if we're too saturated @@ -146,6 +153,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil), } } + return genesis } @@ -169,6 +177,7 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { if err != nil { return nil, nil, err } + ethBackend, err := eth.New(stack, ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), @@ -190,5 +199,6 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { } err = stack.Start() + return stack, ethBackend, err } diff --git a/miner/test_backend.go b/miner/test_backend.go index f9070d6cbc..65f9c39ee0 100644 --- a/miner/test_backend.go +++ b/miner/test_backend.go @@ -442,6 +442,7 @@ func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint, opcodeDelay w.commitWork(ctx, nil, true, time.Now().Unix()) } } + w.newTxs.Add(int32(len(ev.Txs))) // System stopped case <-w.exitCh: @@ -618,7 +619,6 @@ func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32 } tracing.Exec(ctx, "", "worker.SplittingTransactions", func(ctx context.Context, span trace.Span) { - prePendingTime := time.Now() pending := w.eth.TxPool().Pending(ctx, true) @@ -629,6 +629,7 @@ func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32 for _, account := range w.eth.TxPool().Locals() { if txs := remoteTxs[account]; len(txs) > 0 { delete(remoteTxs, account) + localTxs[account] = txs } } diff --git a/miner/unconfirmed.go b/miner/unconfirmed.go index 0489f1ea4a..1e1395a057 100644 --- a/miner/unconfirmed.go +++ b/miner/unconfirmed.go @@ -100,6 +100,7 @@ func (set *unconfirmedBlocks) Shift(height uint64) { } // Block seems to exceed depth allowance, check for canonical status header := set.chain.GetHeaderByNumber(next.index) + switch { case header == nil: log.Warn("Failed to retrieve header of mined block", "number", next.index, "hash", next.hash) @@ -118,6 +119,7 @@ func (set *unconfirmedBlocks) Shift(height uint64) { } } } + if included { log.Info("⑂ block became an uncle", "number", next.index, "hash", next.hash) } else { diff --git a/miner/unconfirmed_test.go b/miner/unconfirmed_test.go index b8fe717320..e398a3d968 100644 --- a/miner/unconfirmed_test.go +++ b/miner/unconfirmed_test.go @@ -39,6 +39,7 @@ func TestUnconfirmedInsertBounds(t *testing.T) { limit := uint(10) pool := newUnconfirmedBlocks(new(noopChainRetriever), limit) + 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++ { @@ -68,12 +69,14 @@ func TestUnconfirmedShifts(t *testing.T) { // Try to shift below the limit and ensure no blocks are dropped pool.Shift(start + uint64(limit) - 1) + if n := pool.blocks.Len(); n != int(limit) { t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit) } // Try to shift half the blocks out and verify remainder pool.Shift(start + uint64(limit) - 1 + uint64(limit/2)) + if n := pool.blocks.Len(); n != int(limit)/2 { t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit/2) } diff --git a/miner/worker.go b/miner/worker.go index 7894ed71f6..29eb7d0f08 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -135,6 +135,7 @@ func (env *environment) copy() *environment { header: types.CopyHeader(env.header), receipts: copyReceipts(env.receipts), } + if env.gasPool != nil { gasPool := *env.gasPool cpy.gasPool = &gasPool @@ -143,10 +144,12 @@ func (env *environment) copy() *environment { // to do the expensive deep copy for them. cpy.txs = make([]*types.Transaction, len(env.txs)) copy(cpy.txs, env.txs) + cpy.uncles = make(map[common.Hash]*types.Header) for hash, uncle := range env.uncles { cpy.uncles[hash] = uncle } + return cpy } @@ -156,6 +159,7 @@ func (env *environment) unclelist() []*types.Header { for _, uncle := range env.uncles { uncles = append(uncles, uncle) } + return uncles } @@ -166,6 +170,7 @@ func (env *environment) discard() { if env.state == nil { return } + env.state.StopPrefetcher() } @@ -358,6 +363,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval) recommit = minRecommitInterval } + worker.recommit = recommit // Sanitize the timeout config for creating payload. @@ -366,9 +372,11 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus log.Warn("Sanitizing new payload timeout to default", "provided", newpayloadTimeout, "updated", DefaultConfig.NewPayloadTimeout) newpayloadTimeout = DefaultConfig.NewPayloadTimeout } + if newpayloadTimeout < time.Millisecond*100 { log.Warn("Low payload timeout may cause high amount of non-full blocks", "provided", newpayloadTimeout, "default", DefaultConfig.NewPayloadTimeout) } + worker.newpayloadTimeout = newpayloadTimeout ctx := tracing.WithTracer(context.Background(), otel.GetTracerProvider().Tracer("MinerWorker")) @@ -384,6 +392,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus if init { worker.startCh <- struct{}{} } + return worker } @@ -398,6 +407,7 @@ func (w *worker) setEtherbase(addr common.Address) { func (w *worker) etherbase() common.Address { w.mu.RLock() defer w.mu.RUnlock() + return w.coinbase } @@ -437,9 +447,11 @@ func (w *worker) pending() (*types.Block, *state.StateDB) { // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() + if w.snapshotState == nil { return nil, nil } + return w.snapshotBlock, w.snapshotState.Copy() } @@ -448,6 +460,7 @@ func (w *worker) pendingBlock() *types.Block { // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() + return w.snapshotBlock } @@ -456,6 +469,7 @@ func (w *worker) pendingBlockAndReceipts() (*types.Block, types.Receipts) { // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() + return w.snapshotBlock, w.snapshotReceipts } @@ -510,6 +524,7 @@ func recalcRecommit(minRecommit, prev time.Duration, target float64, inc bool) t //nolint:gocognit func (w *worker) newWorkLoop(ctx context.Context, recommit time.Duration) { defer w.wg.Done() + var ( interrupt *atomic.Int32 minRecommit = recommit // minimal resubmit interval specified by user. @@ -529,6 +544,7 @@ func (w *worker) newWorkLoop(ctx context.Context, recommit time.Duration) { if interrupt != nil { interrupt.Store(s) } + interrupt = new(atomic.Int32) select { case w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp, ctx: ctx}: @@ -556,12 +572,16 @@ func (w *worker) newWorkLoop(ctx context.Context, recommit time.Duration) { select { case <-w.startCh: clearPending(w.chain.CurrentBlock().Number.Uint64()) + timestamp = time.Now().Unix() + commit(false, commitInterruptNewHead) case head := <-w.chainHeadCh: clearPending(head.Block.NumberU64()) + timestamp = time.Now().Unix() + commit(false, commitInterruptNewHead) case <-timer.C: @@ -573,6 +593,7 @@ func (w *worker) newWorkLoop(ctx context.Context, recommit time.Duration) { timer.Reset(recommit) continue } + commit(true, commitInterruptResubmit) } @@ -582,6 +603,7 @@ func (w *worker) newWorkLoop(ctx context.Context, recommit time.Duration) { log.Warn("Sanitizing miner recommit interval", "provided", interval, "updated", minRecommitInterval) interval = minRecommitInterval } + log.Info("Miner recommit interval update", "from", minRecommit, "to", interval) minRecommit, recommit = interval, interval @@ -649,6 +671,7 @@ func (w *worker) mainLoop(ctx context.Context) { if _, exist := w.localUncles[ev.Block.Hash()]; exist { continue } + if _, exist := w.remoteUncles[ev.Block.Hash()]; exist { continue } @@ -677,6 +700,7 @@ func (w *worker) mainLoop(ctx context.Context) { delete(w.localUncles, hash) } } + for hash, uncle := range w.remoteUncles { if uncle.NumberU64()+staleThreshold <= chainHead.Number.Uint64() { delete(w.remoteUncles, hash) @@ -694,11 +718,14 @@ func (w *worker) mainLoop(ctx context.Context) { if gp := w.current.gasPool; gp != nil && gp.Gas() < params.TxGas { continue } + txs := make(map[common.Address]types.Transactions) + for _, tx := range ev.Txs { acc, _ := types.Sender(w.current.signer, tx) txs[acc] = append(txs[acc], tx) } + txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee)) tcount := w.current.tcount @@ -718,6 +745,7 @@ func (w *worker) mainLoop(ctx context.Context) { w.commitWork(ctx, nil, true, time.Now().Unix()) } } + w.newTxs.Add(int32(len(ev.Txs))) // System stopped @@ -737,6 +765,7 @@ func (w *worker) mainLoop(ctx context.Context) { // push them to consensus engine. func (w *worker) taskLoop() { defer w.wg.Done() + var ( stopCh chan struct{} prev common.Hash @@ -749,6 +778,7 @@ func (w *worker) taskLoop() { stopCh = nil } } + for { select { case task := <-w.taskCh: @@ -762,11 +792,13 @@ func (w *worker) taskLoop() { } // Interrupt previous sealing operation interrupt() + stopCh, prev = make(chan struct{}), sealHash if w.skipSealHook != nil && w.skipSealHook(task) { continue } + w.pendingMu.Lock() w.pendingTasks[sealHash] = task w.pendingMu.Unlock() @@ -788,6 +820,7 @@ func (w *worker) taskLoop() { // and flush relative data to the database. func (w *worker) resultLoop() { defer w.wg.Done() + for { select { case block := <-w.resultCh: @@ -804,6 +837,7 @@ func (w *worker) resultLoop() { if oldBlock != nil { oldBlockAuthor, _ := w.chain.Engine().Author(oldBlock.Header()) newBlockAuthor, _ := w.chain.Engine().Author(block.Header()) + if oldBlockAuthor == newBlockAuthor { log.Info("same block ", "height", block.NumberU64()) continue @@ -814,9 +848,11 @@ func (w *worker) resultLoop() { sealhash = w.engine.SealHash(block.Header()) hash = block.Hash() ) + w.pendingMu.RLock() task, exist := w.pendingTasks[sealhash] w.pendingMu.RUnlock() + if !exist { log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash) continue @@ -827,6 +863,7 @@ func (w *worker) resultLoop() { logs []*types.Log err error ) + tracing.Exec(task.ctx, "", "resultLoop", func(ctx context.Context, span trace.Span) { for i, taskReceipt := range task.receipts { receipt := new(types.Receipt) @@ -841,12 +878,14 @@ func (w *worker) resultLoop() { // Update the block hash in all logs since it is now available and not when the // receipt/log of individual transactions were created. receipt.Logs = make([]*types.Log, len(taskReceipt.Logs)) + for i, taskLog := range taskReceipt.Logs { log := new(types.Log) receipt.Logs[i] = log *log = *taskLog log.BlockHash = hash } + logs = append(logs, receipt.Logs...) } // Commit block and state to database. @@ -869,6 +908,7 @@ func (w *worker) resultLoop() { log.Error("Failed writing block to chain", "err", err) continue } + log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt))) @@ -898,6 +938,7 @@ func (w *worker) makeEnv(parent *types.Header, header *types.Header, coinbase co if err != nil { return nil, err } + state.StartPrefetcher("miner") // Note the passed coinbase may be different with header.Coinbase. @@ -915,11 +956,13 @@ func (w *worker) makeEnv(parent *types.Header, header *types.Header, coinbase co for _, uncle := range ancestor.Uncles() { env.family.Add(uncle.Hash()) } + 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 + return env, nil } @@ -928,20 +971,26 @@ func (w *worker) commitUncle(env *environment, uncle *types.Header) error { if w.isTTDReached(env.header) { return errors.New("ignore uncle for beacon block") } + hash := uncle.Hash() if _, exist := env.uncles[hash]; exist { return errors.New("uncle not unique") } + if env.header.ParentHash == uncle.ParentHash { return errors.New("uncle is sibling") } + if !env.ancestors.Contains(uncle.ParentHash) { return errors.New("uncle's parent unknown") } + if env.family.Contains(hash) { return errors.New("uncle already included") } + env.uncles[hash] = uncle + return nil } @@ -969,12 +1018,15 @@ func (w *worker) commitTransaction(env *environment, tx *types.Transaction, inte // nolint : staticcheck interruptCtx = vm.SetCurrentTxOnContext(interruptCtx, tx.Hash()) + receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig(), interruptCtx) if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp) + return nil, err } + env.txs = append(env.txs, tx) env.receipts = append(env.receipts, receipt) @@ -987,6 +1039,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP if env.gasPool == nil { env.gasPool = new(core.GasPool).AddGas(gasLimit) } + var coalescedLogs []*types.Log var depsMVReadList [][]blockstm.ReadDescriptor @@ -1194,11 +1247,11 @@ mainloop: env.header.TxDependency = nil } } + if !w.isRunning() && len(coalescedLogs) > 0 { // We don't push the pendingLogsEvent while we are sealing. The reason is that // when we are sealing, the worker will regenerate a sealing block every 3 seconds. // In order to avoid pushing the repeated pendingLog, we disable the pending log pushing. - // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined // logs by filling in the block hash when the block was mined by the local miner. This can // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed. @@ -1207,8 +1260,10 @@ mainloop: cpy[i] = new(types.Log) *cpy[i] = *l } + w.pendingLogsFeed.Send(cpy) } + return nil } @@ -1233,11 +1288,13 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { // Find the parent block for sealing task parent := w.chain.CurrentBlock() + if genParams.parentHash != (common.Hash{}) { block := w.chain.GetBlockByHash(genParams.parentHash) if block == nil { return nil, fmt.Errorf("missing parent") } + parent = block.Header() } // Sanity check the timestamp correctness, recap the timestamp @@ -1247,6 +1304,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { if genParams.forceTime { return nil, fmt.Errorf("invalid timestamp, parent %d given %d", parent.Time, timestamp) } + timestamp = parent.Time + 1 } // Construct the sealing block header. @@ -1268,6 +1326,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { // Set baseFee and GasLimit if we are on an EIP-1559 chain if w.chainConfig.IsLondon(header.Number) { header.BaseFee = misc.CalcBaseFeeUint(w.chainConfig, parent).ToBig() + if !w.chainConfig.IsLondon(parent.Number) { parentGasLimit := parent.GasLimit * w.chainConfig.ElasticityMultiplier() header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil) @@ -1281,6 +1340,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { default: log.Error("Failed to prepare header for sealing", "err", err) } + return nil, err } // Could potentially happen if starting to mine in an odd state. @@ -1298,6 +1358,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { if len(env.uncles) == 2 { break } + if err := w.commitUncle(env, uncle.Header()); err != nil { log.Trace("Possible uncle rejected", "hash", hash, "reason", err) } else { @@ -1309,6 +1370,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { commitUncles(w.localUncles) commitUncles(w.remoteUncles) } + return env, nil } @@ -1457,7 +1519,6 @@ func (w *worker) fillTransactions(ctx context.Context, interrupt *atomic.Int32, } tracing.Exec(ctx, "", "worker.SplittingTransactions", func(ctx context.Context, span trace.Span) { - prePendingTime := time.Now() pending := w.eth.TxPool().Pending(ctx, true) @@ -1468,6 +1529,7 @@ func (w *worker) fillTransactions(ctx context.Context, interrupt *atomic.Int32, for _, account := range w.eth.TxPool().Locals() { if txs := remoteTxs[account]; len(txs) > 0 { delete(remoteTxs, account) + localTxs[account] = txs } } @@ -1557,6 +1619,7 @@ func (w *worker) generateWork(ctx context.Context, params *generateParams) (*typ if !params.noTxs { interrupt := new(atomic.Int32) + timer := time.AfterFunc(w.newpayloadTimeout, func() { interrupt.Store(commitInterruptTimeout) }) @@ -1567,10 +1630,12 @@ func (w *worker) generateWork(ctx context.Context, params *generateParams) (*typ log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout)) } } + block, err := w.engine.FinalizeAndAssemble(ctx, w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts, params.withdrawals) if err != nil { return nil, nil, err } + return block, totalFees(block, work.receipts), nil } @@ -1594,6 +1659,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *atomic.Int32, noempt return } } + work, err = w.prepareWork(&generateParams{ timestamp: uint64(timestamp), coinbase: coinbase, @@ -1634,6 +1700,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *atomic.Int32, noempt } // Fill pending transactions from the txpool into the block. err = w.fillTransactions(ctx, interrupt, work, interruptCtx) + switch { case err == nil: // The entire block is filled, decrease resubmit interval in case @@ -1644,6 +1711,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *atomic.Int32, noempt // Notify resubmit loop to increase resubmitting interval if the // interruption is due to frequent commits. gaslimit := work.header.GasLimit + ratio := float64(gaslimit-work.gasPool.Gas()) / float64(gaslimit) if ratio < 0.1 { ratio = 0.1 @@ -1669,6 +1737,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *atomic.Int32, noempt if w.current != nil { w.current.discard() } + w.current = work } @@ -1742,9 +1811,11 @@ func (w *worker) commit(ctx context.Context, env *environment, interval func(), } } } + if update { w.updateSnapshot(env) } + return nil } @@ -1774,6 +1845,7 @@ func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase if result.err != nil { return nil, nil, result.err } + return result.block, result.fees, nil case <-w.exitCh: return nil, nil, errors.New("miner closed") @@ -1790,10 +1862,12 @@ func (w *worker) isTTDReached(header *types.Header) bool { // copyReceipts makes a deep copy of the given receipts. func copyReceipts(receipts []*types.Receipt) []*types.Receipt { result := make([]*types.Receipt, len(receipts)) + for i, l := range receipts { cpy := *l result[i] = &cpy } + return result } @@ -1808,10 +1882,12 @@ func (w *worker) postSideBlock(event core.ChainSideEvent) { // totalFees computes total consumed miner fees in Wei. Block transactions and receipts have to have the same order. func totalFees(block *types.Block, receipts []*types.Receipt) *big.Int { feesWei := new(big.Int) + for i, tx := range block.Transactions() { minerFee, _ := tx.EffectiveGasTip(block.BaseFee()) feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), minerFee)) } + return feesWei } diff --git a/miner/worker_test.go b/miner/worker_test.go index 81be7d6336..7febc7dba1 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -113,32 +113,40 @@ func (b *testWorkerBackend) StateAtBlock(block *types.Block, reexec uint64, base func (b *testWorkerBackend) newRandomUncle() (*types.Block, error) { var parent *types.Block + cur := b.chain.CurrentBlock() if cur.Number.Uint64() == 0 { parent = b.chain.Genesis() } else { parent = b.chain.GetBlockByHash(b.chain.CurrentBlock().ParentHash) } + var err error + blocks, _ := core.GenerateChain(b.chain.Config(), parent, b.chain.Engine(), b.DB, 1, func(i int, gen *core.BlockGen) { var addr = make([]byte, common.AddressLength) + _, err = rand.Read(addr) if err != nil { return } + gen.SetCoinbase(common.BytesToAddress(addr)) }) + return blocks[0], err } func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { var tx *types.Transaction + gasPrice := big.NewInt(10 * params.InitialBaseFee) if creation { tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(TestBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey) } else { tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(TestBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey) } + return tx } @@ -364,6 +372,7 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens time.Sleep(100 * time.Millisecond) } w.start() // Start mining! + for i := 0; i < 2; i += 1 { select { case <-taskCh: @@ -391,11 +400,13 @@ func TestStreamUncleBlock(t *testing.T) { if taskIndex == 2 { have := task.block.Header().UncleHash want := types.CalcUncleHash([]*types.Header{b.uncleBlock.Header()}) + if have != want { t.Errorf("uncle hash mismatch: have %s, want %s", have.Hex(), want.Hex()) } } taskCh <- struct{}{} + taskIndex += 1 } } @@ -450,11 +461,13 @@ func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, en if len(task.receipts) != receiptLen { t.Errorf("receipt number mismatch: have %d, want %d", len(task.receipts), receiptLen) } + if task.state.GetBalance(testUserAddress).Cmp(balance) != 0 { t.Errorf("account balance mismatch: have %d, want %d", task.state.GetBalance(testUserAddress), balance) } } taskCh <- struct{}{} + taskIndex += 1 } } @@ -508,17 +521,20 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co w.fullTaskHook = func() { time.Sleep(100 * time.Millisecond) } + var ( progress = make(chan struct{}, 10) result = make([]float64, 0, 10) index = 0 start atomic.Bool ) + w.resubmitHook = func(minInterval time.Duration, recommitInterval time.Duration) { // Short circuit if interval checking hasn't started. if !start.Load() { return } + var wantMinInterval, wantRecommitInterval time.Duration switch index { @@ -541,9 +557,11 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co if minInterval != wantMinInterval { t.Errorf("resubmit min interval mismatch: have %v, want %v ", minInterval, wantMinInterval) } + if recommitInterval != wantRecommitInterval { t.Errorf("resubmit interval mismatch: have %v, want %v", recommitInterval, wantRecommitInterval) } + result = append(result, float64(recommitInterval.Nanoseconds())) index += 1 progress <- struct{}{} @@ -619,14 +637,17 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co // is even smaller than parent block's. It's OK. t.Logf("Invalid timestamp, want %d, get %d", timestamp, block.Time()) } + if len(block.Uncles()) != 0 { t.Error("Unexpected uncle block") } + _, isClique := engine.(*clique.Clique) if !isClique { if len(block.Extra()) != 2 { t.Error("Unexpected extra field") } + if block.Coinbase() != coinbase { t.Errorf("Unexpected coinbase got %x want %x", block.Coinbase(), coinbase) } @@ -635,18 +656,22 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co t.Error("Unexpected coinbase") } } + if !isClique { if block.MixDigest() != random { t.Error("Unexpected mix digest") } } + if block.Nonce() != 0 { t.Error("Unexpected block nonce") } + if block.NumberU64() != number { t.Errorf("Mismatched block number, want %d got %d", number, block.NumberU64()) } } + var cases = []struct { parent common.Hash coinbase common.Address @@ -702,12 +727,14 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co if err != nil { t.Errorf("Unexpected error %v", err) } + assertBlock(block, c.expectNumber, c.coinbase, c.random) } } // This API should work even when the automatic sealing is enabled w.start() + for _, c := range cases { block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, nil, false) if c.expectErr { @@ -718,6 +745,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co if err != nil { t.Errorf("Unexpected error %v", err) } + assertBlock(block, c.expectNumber, c.coinbase, c.random) } } @@ -1053,6 +1081,7 @@ func BenchmarkBorMiningBlockSTMMetadata(b *testing.B) { if len(deps[0]) != 0 { b.Fatalf("wrong dependency") } + for i := 1; i < block.Transactions().Len(); i++ { if deps[i][0] != uint64(i-1) || len(deps[i]) != 1 { b.Fatalf("wrong dependency") diff --git a/node/api.go b/node/api.go index 4bb4bf4378..14ef1cc546 100644 --- a/node/api.go +++ b/node/api.go @@ -90,7 +90,9 @@ func (api *adminAPI) AddPeer(url string) (bool, error) { if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } + server.AddPeer(node) + return true, nil } @@ -106,7 +108,9 @@ func (api *adminAPI) RemovePeer(url string) (bool, error) { if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } + server.RemovePeer(node) + return true, nil } @@ -117,11 +121,14 @@ func (api *adminAPI) AddTrustedPeer(url string) (bool, error) { if server == nil { return false, ErrNodeStopped } + node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } + server.AddTrustedPeer(node) + return true, nil } @@ -133,11 +140,14 @@ func (api *adminAPI) RemoveTrustedPeer(url string) (bool, error) { if server == nil { return false, ErrNodeStopped } + node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } + server.RemoveTrustedPeer(node) + return true, nil } @@ -155,10 +165,12 @@ func (api *adminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) if !supported { return nil, rpc.ErrNotificationsUnsupported } + rpcSub := notifier.CreateSubscription() go func() { events := make(chan *p2p.PeerEvent) + sub := server.SubscribeEvents(events) defer sub.Unsubscribe() @@ -190,8 +202,10 @@ func (api *adminAPI) StartHTTP(host *string, port *int, cors *string, apis *stri if api.node.config.HTTPHost != "" { h = api.node.config.HTTPHost } + host = &h } + if port == nil { port = &api.node.config.HTTPPort } @@ -208,12 +222,14 @@ func (api *adminAPI) StartHTTP(host *string, port *int, cors *string, apis *stri config.CorsAllowedOrigins = append(config.CorsAllowedOrigins, strings.TrimSpace(origin)) } } + if vhosts != nil { config.Vhosts = nil for _, vhost := range strings.Split(*host, ",") { config.Vhosts = append(config.Vhosts, strings.TrimSpace(vhost)) } } + if apis != nil { config.Modules = nil for _, m := range strings.Split(*apis, ",") { @@ -224,12 +240,15 @@ func (api *adminAPI) StartHTTP(host *string, port *int, cors *string, apis *stri if err := api.node.http.setListenAddr(*host, *port); err != nil { return false, err } + if err := api.node.http.enableRPC(api.node.rpcAPIs, config); err != nil { return false, err } + if err := api.node.http.start(); err != nil { return false, err } + return true, nil } @@ -264,8 +283,10 @@ func (api *adminAPI) StartWS(host *string, port *int, allowedOrigins *string, ap if api.node.config.WSHost != "" { h = api.node.config.WSHost } + host = &h } + if port == nil { port = &api.node.config.WSPort } @@ -282,6 +303,7 @@ func (api *adminAPI) StartWS(host *string, port *int, allowedOrigins *string, ap config.Modules = append(config.Modules, strings.TrimSpace(m)) } } + if allowedOrigins != nil { config.Origins = nil for _, origin := range strings.Split(*allowedOrigins, ",") { @@ -294,14 +316,18 @@ func (api *adminAPI) StartWS(host *string, port *int, allowedOrigins *string, ap if err := server.setListenAddr(*host, *port); err != nil { return false, err } + openApis, _ := api.node.getAPIs() if err := server.enableWS(openApis, config); err != nil { return false, err } + if err := server.start(); err != nil { return false, err } + api.node.http.log.Info("WebSocket endpoint opened", "url", api.node.WSEndpoint()) + return true, nil } @@ -309,6 +335,7 @@ func (api *adminAPI) StartWS(host *string, port *int, allowedOrigins *string, ap func (api *adminAPI) StopWS() (bool, error) { api.node.http.stopWS() api.node.ws.stop() + return true, nil } @@ -319,6 +346,7 @@ func (api *adminAPI) Peers() ([]*p2p.PeerInfo, error) { if server == nil { return nil, ErrNodeStopped } + return server.PeersInfo(), nil } @@ -329,6 +357,7 @@ func (api *adminAPI) NodeInfo() (*p2p.NodeInfo, error) { if server == nil { return nil, ErrNodeStopped } + return server.NodeInfo(), nil } diff --git a/node/api_test.go b/node/api_test.go index 7a967afdbf..d6e84dc4c8 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -307,15 +307,19 @@ func TestStartRPC(t *testing.T) { handlersAvailable := checkBodyOK(baseURL + "/test") rpcAvailable := checkRPC(baseURL) wsAvailable := checkRPC(strings.Replace(baseURL, "http://", "ws://", 1)) + if reachable != test.wantReachable { t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable)) } + if handlersAvailable != test.wantHandlers { t.Errorf("RegisterHandler handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers)) } + if rpcAvailable != test.wantRPC { t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC)) } + if wsAvailable != test.wantWS { t.Errorf("WS RPC %savailable, want it %savailable", not(wsAvailable), not(test.wantWS)) } @@ -329,11 +333,14 @@ func checkReachable(rawurl string) bool { if err != nil { panic(err) } + conn, err := net.Dial("tcp", u.Host) if err != nil { return false } + conn.Close() + return true } @@ -348,10 +355,12 @@ func checkBodyOK(url string) bool { if resp.StatusCode != 200 { return false } + buf := make([]byte, 2) if _, err = io.ReadFull(resp.Body, buf); err != nil { return false } + return bytes.Equal(buf, []byte("OK")) } @@ -364,6 +373,7 @@ func checkRPC(url string) bool { defer c.Close() _, err = c.SupportedModules() + return err == nil } @@ -375,5 +385,6 @@ func not(ok bool) string { if ok { return "" } + return "not " } diff --git a/node/config.go b/node/config.go index e2eb270fd5..f3fd882e50 100644 --- a/node/config.go +++ b/node/config.go @@ -232,6 +232,7 @@ func (c *Config) IPCEndpoint() string { if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) { return c.IPCPath } + return `\\.\pipe\` + c.IPCPath } // Resolve names into the data directory full paths otherwise @@ -239,8 +240,10 @@ func (c *Config) IPCEndpoint() string { if c.DataDir == "" { return filepath.Join(os.TempDir(), c.IPCPath) } + return filepath.Join(c.DataDir, c.IPCPath) } + return c.IPCPath } @@ -249,6 +252,7 @@ func (c *Config) NodeDB() string { if c.DataDir == "" { return "" // ephemeral } + return c.ResolvePath(datadirNodeDatabase) } @@ -260,7 +264,9 @@ func DefaultIPCEndpoint(clientIdentifier string) string { panic("empty executable name") } } + config := &Config{DataDir: DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"} + return config.IPCEndpoint() } @@ -270,6 +276,7 @@ func (c *Config) HTTPEndpoint() string { if c.HTTPHost == "" { return "" } + return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) } @@ -285,6 +292,7 @@ func (c *Config) WSEndpoint() string { if c.WSHost == "" { return "" } + return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort) } @@ -307,14 +315,18 @@ func (c *Config) NodeName() string { if name == "geth" || name == "geth-testnet" { name = "Geth" } + if c.UserIdent != "" { name += "/" + c.UserIdent } + if c.Version != "" { name += "/v" + c.Version } + name += "/" + runtime.GOOS + "-" + runtime.GOARCH name += "/" + runtime.Version() + return name } @@ -324,8 +336,10 @@ func (c *Config) name() string { if progname == "" { panic("empty executable name, set Config.Name") } + return progname } + return c.Name } @@ -343,6 +357,7 @@ func (c *Config) ResolvePath(path string) string { if filepath.IsAbs(path) { return path } + if c.DataDir == "" { return "" } @@ -353,14 +368,18 @@ func (c *Config) ResolvePath(path string) string { if c.name() == "geth" { oldpath = filepath.Join(c.DataDir, path) } + if oldpath != "" && common.FileExist(oldpath) { if warn && !c.oldGethResourceWarning { c.oldGethResourceWarning = true + log.Warn("Using deprecated resource file, please move this file to the 'geth' subdirectory of datadir.", "file", oldpath) } + return oldpath } } + return filepath.Join(c.instanceDir(), path) } @@ -381,9 +400,11 @@ func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node { if c.DataDir == "" { return nil } + if _, err := os.Stat(path); err != nil { return nil } + c.warnOnce(w, "Found deprecated node list file %s, please use the TOML config file instead.", path) // Load the nodes from the config file. @@ -394,17 +415,21 @@ func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node { } // Interpret the list as a discovery node array var nodes []*enode.Node + for _, url := range nodelist { if url == "" { continue } + node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err)) continue } + nodes = append(nodes, node) } + return nodes } @@ -412,6 +437,7 @@ func (c *Config) instanceDir() string { if c.DataDir == "" { return "" } + return filepath.Join(c.DataDir, c.name()) } @@ -429,6 +455,7 @@ func (c *Config) NodeKey() *ecdsa.PrivateKey { if err != nil { log.Crit(fmt.Sprintf("Failed to generate ephemeral node key: %v", err)) } + return key } @@ -441,15 +468,18 @@ func (c *Config) NodeKey() *ecdsa.PrivateKey { if err != nil { log.Crit(fmt.Sprintf("Failed to generate node key: %v", err)) } + instanceDir := filepath.Join(c.DataDir, c.name()) if err := os.MkdirAll(instanceDir, 0700); err != nil { log.Error(fmt.Sprintf("Failed to persist node key: %v", err)) return key } + keyfile = filepath.Join(instanceDir, datadirPrivateKey) if err := crypto.SaveECDSA(keyfile, key); err != nil { log.Error(fmt.Sprintf("Failed to persist node key: %v", err)) } + return key } @@ -466,13 +496,16 @@ func (c *Config) checkLegacyFile(path string) { if c.DataDir == "" { return } + if _, err := os.Stat(path); err != nil { return } + logger := c.Logger if logger == nil { logger = log.Root() } + switch fname := filepath.Base(path); fname { case "static-nodes.json": logger.Error("The static-nodes.json file is deprecated and ignored. Use P2P.StaticNodes in config.toml instead.") @@ -490,6 +523,7 @@ func (c *Config) KeyDirConfig() (string, error) { keydir string err error ) + switch { case filepath.IsAbs(c.KeyStoreDir): keydir = c.KeyStoreDir @@ -502,6 +536,7 @@ func (c *Config) KeyDirConfig() (string, error) { case c.KeyStoreDir != "": keydir, err = filepath.Abs(c.KeyStoreDir) } + return keydir, err } @@ -512,7 +547,9 @@ func getKeyStoreDir(conf *Config) (string, bool, error) { if err != nil { return "", false, err } + isEphemeral := false + if keydir == "" { // There is no datadir. keydir, err = os.MkdirTemp("", "go-ethereum-keystore") @@ -522,6 +559,7 @@ func getKeyStoreDir(conf *Config) (string, bool, error) { if err != nil { return "", false, err } + if err := os.MkdirAll(keydir, 0700); err != nil { return "", false, err } @@ -538,10 +576,13 @@ func (c *Config) warnOnce(w *bool, format string, args ...interface{}) { if *w { return } + l := c.Logger if l == nil { l = log.Root() } + l.Warn(fmt.Sprintf(format, args...)) + *w = true } diff --git a/node/config_test.go b/node/config_test.go index e8af8ddcd8..d2607b37cb 100644 --- a/node/config_test.go +++ b/node/config_test.go @@ -37,18 +37,22 @@ func TestDatadirCreation(t *testing.T) { if err != nil { t.Fatalf("failed to create stack with existing datadir: %v", err) } + if err := node.Close(); err != nil { t.Fatalf("failed to close node: %v", err) } // Generate a long non-existing datadir path and check that it gets created by a node dir = filepath.Join(dir, "a", "b", "c", "d", "e", "f") + node, err = New(&Config{DataDir: dir}) if err != nil { t.Fatalf("failed to create stack with creatable datadir: %v", err) } + if err := node.Close(); err != nil { t.Fatalf("failed to close node: %v", err) } + if _, err := os.Stat(dir); err != nil { t.Fatalf("freshly created datadir not accessible: %v", err) } @@ -57,12 +61,14 @@ func TestDatadirCreation(t *testing.T) { if err != nil { t.Fatalf("failed to create temporary file: %v", err) } + defer func() { file.Close() os.Remove(file.Name()) }() dir = filepath.Join(file.Name(), "invalid/path") + _, err = New(&Config{DataDir: dir}) if err == nil { t.Fatalf("protocol stack created with an invalid datadir") @@ -90,6 +96,7 @@ func TestIPCPathResolution(t *testing.T) { {"data", "geth.ipc", true, `\\.\pipe\geth.ipc`}, {"data", `\\.\pipe\geth.ipc`, true, `\\.\pipe\geth.ipc`}, } + for i, test := range tests { // Only run when platform/test match if (runtime.GOOS == "windows") == test.Windows { @@ -113,8 +120,10 @@ func TestNodeKeyPersistency(t *testing.T) { if err != nil { t.Fatalf("failed to generate one-shot node key: %v", err) } + config := &Config{Name: "unit-test", DataDir: dir, P2P: p2p.Config{PrivateKey: key}} config.NodeKey() + if _, err := os.Stat(keyfile); err == nil { t.Fatalf("one-shot node key persisted to data directory") } @@ -122,12 +131,15 @@ func TestNodeKeyPersistency(t *testing.T) { // Configure a node with no preset key and ensure it is persisted this time config = &Config{Name: "unit-test", DataDir: dir} config.NodeKey() + if _, err := os.Stat(keyfile); err != nil { t.Fatalf("node key not persisted to data directory: %v", err) } + if _, err = crypto.LoadECDSA(keyfile); err != nil { t.Fatalf("failed to load freshly persisted node key: %v", err) } + blob1, err := os.ReadFile(keyfile) if err != nil { t.Fatalf("failed to read freshly persisted node key: %v", err) @@ -136,10 +148,12 @@ func TestNodeKeyPersistency(t *testing.T) { // Configure a new node and ensure the previously persisted key is loaded config = &Config{Name: "unit-test", DataDir: dir} config.NodeKey() + blob2, err := os.ReadFile(keyfile) if err != nil { t.Fatalf("failed to read previously persisted node key: %v", err) } + if !bytes.Equal(blob1, blob2) { t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1) } @@ -147,6 +161,7 @@ func TestNodeKeyPersistency(t *testing.T) { // Configure ephemeral node and ensure no key is dumped locally config = &Config{Name: "unit-test", DataDir: ""} config.NodeKey() + if _, err := os.Stat(filepath.Join(".", "unit-test", datadirPrivateKey)); err == nil { t.Fatalf("ephemeral node key persisted to disk") } diff --git a/node/defaults.go b/node/defaults.go index 8cdef92815..637013b3d6 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -84,9 +84,11 @@ func DefaultDataDir() string { // is non-empty, use it, otherwise DTRT and check %LOCALAPPDATA%. fallback := filepath.Join(home, "AppData", "Roaming", "Ethereum") appdata := windowsAppData() + if appdata == "" || isNonEmptyDir(fallback) { return fallback } + return filepath.Join(appdata, "Ethereum") default: return filepath.Join(home, ".ethereum") @@ -104,6 +106,7 @@ func windowsAppData() string { // other issues. panic("environment variable LocalAppData is undefined") } + return v } @@ -112,8 +115,10 @@ func isNonEmptyDir(dir string) bool { if err != nil { return false } + names, _ := f.Readdir(1) f.Close() + return len(names) > 0 } @@ -121,8 +126,10 @@ func homeDir() string { if home := os.Getenv("HOME"); home != "" { return home } + if usr, err := user.Current(); err == nil { return usr.HomeDir } + return "" } diff --git a/node/endpoints.go b/node/endpoints.go index 14c12fd1f1..36e0923ebb 100644 --- a/node/endpoints.go +++ b/node/endpoints.go @@ -32,6 +32,7 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http. listener net.Listener err error ) + if listener, err = net.Listen("tcp", endpoint); err != nil { return nil, nil, err } @@ -46,6 +47,7 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http. IdleTimeout: timeouts.IdleTimeout, } go httpSrv.Serve(listener) + return httpSrv, listener.Addr(), err } @@ -57,9 +59,11 @@ func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available [ for _, api := range apis { if _, ok := availableSet[api.Namespace]; !ok { availableSet[api.Namespace] = struct{}{} + available = append(available, api.Namespace) } } + for _, name := range modules { if _, ok := availableSet[name]; !ok { if name != rpc.MetadataApi && name != rpc.EngineApi { @@ -67,6 +71,7 @@ func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available [ } } } + return bad, available } @@ -76,14 +81,17 @@ func CheckTimeouts(timeouts *rpc.HTTPTimeouts) { log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadTimeout) timeouts.ReadTimeout = rpc.DefaultHTTPTimeouts.ReadTimeout } + if timeouts.ReadHeaderTimeout < time.Second { log.Warn("Sanitizing invalid HTTP read header timeout", "provided", timeouts.ReadHeaderTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadHeaderTimeout) timeouts.ReadHeaderTimeout = rpc.DefaultHTTPTimeouts.ReadHeaderTimeout } + if timeouts.WriteTimeout < time.Second { log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", rpc.DefaultHTTPTimeouts.WriteTimeout) timeouts.WriteTimeout = rpc.DefaultHTTPTimeouts.WriteTimeout } + if timeouts.IdleTimeout < time.Second { log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", rpc.DefaultHTTPTimeouts.IdleTimeout) timeouts.IdleTimeout = rpc.DefaultHTTPTimeouts.IdleTimeout diff --git a/node/errors.go b/node/errors.go index 67547bf691..1434fa98d3 100644 --- a/node/errors.go +++ b/node/errors.go @@ -36,6 +36,7 @@ func convertFileLockError(err error) error { if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { return ErrDatadirUsed } + return err } diff --git a/node/jwt_auth.go b/node/jwt_auth.go index 5fa23fd5b8..6a2998b443 100644 --- a/node/jwt_auth.go +++ b/node/jwt_auth.go @@ -36,11 +36,14 @@ func NewJWTAuth(jwtsecret [32]byte) rpc.HTTPAuth { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "iat": &jwt.NumericDate{Time: time.Now()}, }) + s, err := token.SignedString(jwtsecret[:]) if err != nil { return fmt.Errorf("failed to create JWT token: %w", err) } + h.Set("Authorization", "Bearer "+s) + return nil } } diff --git a/node/jwt_handler.go b/node/jwt_handler.go index 637ae19686..ad3abda406 100644 --- a/node/jwt_handler.go +++ b/node/jwt_handler.go @@ -47,9 +47,11 @@ func (handler *jwtHandler) ServeHTTP(out http.ResponseWriter, r *http.Request) { strToken string claims jwt.RegisteredClaims ) + if auth := r.Header.Get("Authorization"); strings.HasPrefix(auth, "Bearer ") { strToken = strings.TrimPrefix(auth, "Bearer ") } + if len(strToken) == 0 { http.Error(out, "missing token", http.StatusUnauthorized) return diff --git a/node/node.go b/node/node.go index 9ebc1b0876..071fc4d652 100644 --- a/node/node.go +++ b/node/node.go @@ -80,14 +80,17 @@ func New(conf *Config) (*Node, error) { // Copy config and resolve the datadir so future changes to the current // working directory don't affect the node. confCopy := *conf + conf = &confCopy if conf.DataDir != "" { absdatadir, err := filepath.Abs(conf.DataDir) if err != nil { return nil, err } + conf.DataDir = absdatadir } + if conf.Logger == nil { conf.Logger = log.New() } @@ -97,9 +100,11 @@ func New(conf *Config) (*Node, error) { if strings.ContainsAny(conf.Name, `/\`) { return nil, errors.New(`Config.Name must not contain '/' or '\'`) } + if conf.Name == datadirDefaultKeyStore { return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`) } + if strings.HasSuffix(conf.Name, ".ipc") { return nil, errors.New(`Config.Name cannot end in ".ipc"`) } @@ -124,10 +129,12 @@ func New(conf *Config) (*Node, error) { if err := node.openDataDir(); err != nil { return nil, err } + keyDir, isEphem, err := getKeyStoreDir(conf) if err != nil { return nil, err } + node.keyDir = keyDir node.keyDirTemp = isEphem // Creates an empty AccountManager with no backends. Callers (e.g. cmd/geth) @@ -139,6 +146,7 @@ func New(conf *Config) (*Node, error) { node.server.Config.Name = node.config.NodeName() node.server.Config.Logger = node.log node.config.checkLegacyFiles() + if node.server.Config.NodeDatabase == "" { node.server.Config.NodeDatabase = node.config.NodeDB() } @@ -147,6 +155,7 @@ func New(conf *Config) (*Node, error) { if err := validatePrefix("HTTP", conf.HTTPPathPrefix); err != nil { return nil, err } + if err := validatePrefix("WebSocket", conf.WSPathPrefix); err != nil { return nil, err } @@ -176,6 +185,7 @@ func (n *Node) Start() error { n.lock.Unlock() return ErrNodeStopped } + n.state = runningState // open networking and RPC endpoints err := n.openEndpoints() @@ -190,10 +200,12 @@ func (n *Node) Start() error { } // Start all registered lifecycles. var started []Lifecycle + for _, lifecycle := range lifecycles { if err = lifecycle.Start(); err != nil { break } + started = append(started, lifecycle) } // Check if any lifecycle failed to start. @@ -201,6 +213,7 @@ func (n *Node) Start() error { n.stopServices(started) n.doClose(nil) } + return err } @@ -213,6 +226,7 @@ func (n *Node) Close() error { n.lock.Lock() state := n.state n.lock.Unlock() + switch state { case initializingState: // The node was never started. @@ -223,6 +237,7 @@ func (n *Node) Close() error { if err := n.stopServices(n.lifecycles); err != nil { errs = append(errs, err) } + return n.doClose(errs) case closedState: return ErrNodeStopped @@ -243,6 +258,7 @@ func (n *Node) doClose(errs []error) error { if err := n.accman.Close(); err != nil { errs = append(errs, err) } + if n.keyDirTemp { if err := os.RemoveAll(n.keyDir); err != nil { errs = append(errs, err) @@ -270,6 +286,7 @@ func (n *Node) doClose(errs []error) error { 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) } @@ -279,6 +296,7 @@ func (n *Node) openEndpoints() error { n.stopRPC() n.server.Stop() } + return err } @@ -289,6 +307,7 @@ func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool { return true } } + return false } @@ -299,6 +318,7 @@ func (n *Node) stopServices(running []Lifecycle) error { // Stop running lifecycles in reverse order. failure := &StopError{Services: make(map[reflect.Type]error)} + for i := len(running) - 1; i >= 0; i-- { if err := running[i].Stop(); err != nil { failure.Services[reflect.TypeOf(running[i])] = err @@ -311,6 +331,7 @@ func (n *Node) stopServices(running []Lifecycle) error { if len(failure.Services) > 0 { return failure } + return nil } @@ -332,6 +353,7 @@ func (n *Node) openDataDir() error { } else if !locked { return ErrDatadirUsed } + return nil } @@ -359,7 +381,9 @@ func (n *Node) obtainJWTSecret(cliParam string) ([]byte, error) { log.Info("Loaded JWT secret file", "path", fileName, "crc32", fmt.Sprintf("%#x", crc32.ChecksumIEEE(jwtSecret))) return jwtSecret, nil } + log.Error("Invalid JWT secret", "path", fileName, "length", len(jwtSecret)) + return nil, errors.New("invalid JWT secret") } // Need to generate one @@ -370,10 +394,13 @@ func (n *Node) obtainJWTSecret(cliParam string) ([]byte, error) { log.Info("Generated ephemeral JWT secret", "secret", hexutil.Encode(jwtSecret)) return jwtSecret, nil } + if err := os.WriteFile(fileName, []byte(hexutil.Encode(jwtSecret)), 0600); err != nil { return nil, err } + log.Info("Generated JWT secret", "path", fileName) + return jwtSecret, nil } @@ -383,6 +410,7 @@ func (n *Node) obtainJWTSecret(cliParam string) ([]byte, error) { func (n *Node) startRPC() error { // Filter out personal api var apis []rpc.API + for _, api := range n.rpcAPIs { if api.Namespace == "personal" { if n.config.EnablePersonal { @@ -391,8 +419,10 @@ func (n *Node) startRPC() error { continue } } + apis = append(apis, api) } + if err := n.startInProc(apis); err != nil { return err } @@ -403,6 +433,7 @@ func (n *Node) startRPC() error { return err } } + var ( servers []*httpServer openAPIs, allAPIs = n.getAPIs() @@ -412,6 +443,7 @@ func (n *Node) startRPC() error { if err := server.setListenAddr(n.config.HTTPHost, port); err != nil { return err } + if err := server.enableRPC(openAPIs, httpConfig{ CorsAllowedOrigins: n.config.HTTPCors, Vhosts: n.config.HTTPVirtualHosts, @@ -420,7 +452,9 @@ func (n *Node) startRPC() error { }); err != nil { return err } + servers = append(servers, server) + return nil } @@ -429,6 +463,7 @@ func (n *Node) startRPC() error { if err := server.setListenAddr(n.config.WSHost, port); err != nil { return err } + if err := server.enableWS(openAPIs, wsConfig{ Modules: n.config.WSModules, Origins: n.config.WSOrigins, @@ -438,7 +473,9 @@ func (n *Node) startRPC() error { }); err != nil { return err } + servers = append(servers, server) + return nil } @@ -448,6 +485,7 @@ func (n *Node) startRPC() error { if err := server.setListenAddr(n.config.AuthAddr, port); err != nil { return err } + if err := server.enableRPC(allAPIs, httpConfig{ CorsAllowedOrigins: DefaultAuthCors, Vhosts: n.config.AuthVirtualHosts, @@ -457,12 +495,14 @@ func (n *Node) startRPC() error { }); err != nil { return err } + servers = append(servers, server) // Enable auth via WS server = n.wsServerForPort(port, true) if err := server.setListenAddr(n.config.AuthAddr, port); err != nil { return err } + if err := server.enableWS(allAPIs, wsConfig{ Modules: DefaultAuthModules, Origins: DefaultAuthOrigins, @@ -471,7 +511,9 @@ func (n *Node) startRPC() error { }); err != nil { return err } + servers = append(servers, server) + return nil } @@ -507,6 +549,7 @@ func (n *Node) startRPC() error { if err != nil { return err } + if err := initAuth(n.config.AuthPort, jwtSecret); err != nil { return err } @@ -517,6 +560,7 @@ func (n *Node) startRPC() error { return err } } + return nil } @@ -525,9 +569,11 @@ func (n *Node) wsServerForPort(port int, authenticated bool) *httpServer { if authenticated { httpServer, wsServer = n.httpAuth, n.wsAuth } + if n.config.HTTPHost == "" || httpServer.port == port { return httpServer } + return wsServer } @@ -547,6 +593,7 @@ func (n *Node) startInProc(apis []rpc.API) error { return err } } + return nil } @@ -568,9 +615,11 @@ func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { if n.state != initializingState { panic("can't register lifecycle on running/stopped node") } + if containsLifecycle(n.lifecycles, lifecycle) { panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) } + n.lifecycles = append(n.lifecycles, lifecycle) } @@ -582,6 +631,7 @@ func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { if n.state != initializingState { panic("can't register protocols on running/stopped node") } + n.server.Protocols = append(n.server.Protocols, protocols...) } @@ -593,6 +643,7 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { if n.state != initializingState { panic("can't register APIs on running/stopped node") } + n.rpcAPIs = append(n.rpcAPIs, apis...) } @@ -604,6 +655,7 @@ func (n *Node) getAPIs() (unauthenticated, all []rpc.API) { unauthenticated = append(unauthenticated, api) } } + return unauthenticated, n.rpcAPIs } @@ -636,6 +688,7 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { if n.state == closedState { return nil, ErrNodeStopped } + return n.inprocHandler, nil } @@ -691,6 +744,7 @@ func (n *Node) WSEndpoint() string { if n.http.wsAllowed() { return "ws://" + n.http.listenAddr() + n.http.wsConfig.prefix } + return "ws://" + n.ws.listenAddr() + n.ws.wsConfig.prefix } @@ -704,6 +758,7 @@ func (n *Node) WSAuthEndpoint() string { if n.httpAuth.wsAllowed() { return "ws://" + n.httpAuth.listenAddr() + n.httpAuth.wsConfig.prefix } + return "ws://" + n.wsAuth.listenAddr() + n.wsAuth.wsConfig.prefix } @@ -719,12 +774,15 @@ func (n *Node) EventMux() *event.TypeMux { func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() + if n.state == closedState { return nil, ErrNodeStopped } var db ethdb.Database + var err error + if n.config.DataDir == "" { db = rawdb.NewMemoryDatabase() } else { @@ -741,6 +799,7 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r if err == nil { db = n.wrapDatabase(db) } + return db, err } @@ -752,11 +811,15 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, ancient string, namespace string, readonly bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() + if n.state == closedState { return nil, ErrNodeStopped } + var db ethdb.Database + var err error + if n.config.DataDir == "" { db = rawdb.NewMemoryDatabase() } else { @@ -774,6 +837,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, ancient if err == nil { db = n.wrapDatabase(db) } + return db, err } @@ -790,6 +854,7 @@ func (n *Node) ResolveAncient(name string, ancient string) string { case !filepath.IsAbs(ancient): ancient = n.ResolvePath(ancient) } + return ancient } @@ -805,6 +870,7 @@ func (db *closeTrackingDB) Close() error { db.n.lock.Lock() delete(db.n.databases, db) db.n.lock.Unlock() + return db.Database.Close() } @@ -812,6 +878,7 @@ func (db *closeTrackingDB) Close() error { func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database { wrapper := &closeTrackingDB{db, n} n.databases[wrapper] = struct{}{} + return wrapper } @@ -819,9 +886,11 @@ func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database { func (n *Node) closeDatabases() (errors []error) { for db := range n.databases { delete(n.databases, db) + if err := db.Database.Close(); err != nil { errors = append(errors, err) } } + return errors } diff --git a/node/node_auth_test.go b/node/node_auth_test.go index 043ce30ec1..92eb7988a1 100644 --- a/node/node_auth_test.go +++ b/node/node_auth_test.go @@ -49,6 +49,7 @@ type authTest struct { func (at *authTest) Run(t *testing.T) { ctx := context.Background() + cl, err := rpc.DialOptions(ctx, at.endpoint, rpc.WithHTTPAuth(at.prov)) if at.expectDialFail { if err == nil { @@ -57,11 +58,13 @@ func (at *authTest) Run(t *testing.T) { return } } + if err != nil { t.Fatalf("failed to dial rpc endpoint: %v", err) } var x string + err = cl.CallContext(ctx, &x, "engine_helloWorld") if at.expectCall1Fail { if err == nil { @@ -70,9 +73,11 @@ func (at *authTest) Run(t *testing.T) { return } } + if err != nil { t.Fatalf("failed to call rpc endpoint: %v", err) } + if x != "hello engine" { t.Fatalf("method was silent but did not return expected value: %q", x) } @@ -85,9 +90,11 @@ func (at *authTest) Run(t *testing.T) { return } } + if err != nil { t.Fatalf("failed to call rpc endpoint: %v", err) } + if x != "hello eth" { t.Fatalf("method was silent but did not return expected value: %q", x) } @@ -119,6 +126,7 @@ func TestAuthEndpoints(t *testing.T) { WSModules: []string{"eth", "engine"}, HTTPModules: []string{"eth", "engine"}, } + node, err := New(conf) if err != nil { t.Fatalf("could not create a new node: %v", err) @@ -140,6 +148,7 @@ func TestAuthEndpoints(t *testing.T) { Authenticated: true, }, }) + if err := node.Start(); err != nil { t.Fatalf("failed to start test node: %v", err) } @@ -149,15 +158,18 @@ func TestAuthEndpoints(t *testing.T) { if a, b := node.WSEndpoint(), node.WSAuthEndpoint(); a == b { t.Fatalf("expected ws and auth-ws endpoints to be different, got: %q and %q", a, b) } + if a, b := node.HTTPEndpoint(), node.HTTPAuthEndpoint(); a == b { t.Fatalf("expected http and auth-http endpoints to be different, got: %q and %q", a, b) } goodAuth := NewJWTAuth(secret) + var otherSecret [32]byte if _, err := crand.Read(otherSecret[:]); err != nil { t.Fatalf("failed to create jwt secret: %v", err) } + badAuth := NewJWTAuth(otherSecret) notTooLong := time.Second * 57 @@ -207,22 +219,27 @@ func noneAuth(secret [32]byte) rpc.HTTPAuth { token := jwt.NewWithClaims(jwt.SigningMethodNone, jwt.MapClaims{ "iat": &jwt.NumericDate{Time: time.Now()}, }) + s, err := token.SignedString(secret[:]) if err != nil { return fmt.Errorf("failed to create JWT token: %w", err) } + header.Set("Authorization", "Bearer "+s) + return nil } } func changingAuth(provs ...rpc.HTTPAuth) rpc.HTTPAuth { i := 0 + return func(header http.Header) error { i += 1 if i > len(provs) { i = len(provs) } + return provs[i-1](header) } } @@ -232,11 +249,14 @@ func offsetTimeAuth(secret [32]byte, offset time.Duration) rpc.HTTPAuth { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "iat": &jwt.NumericDate{Time: time.Now().Add(offset)}, }) + s, err := token.SignedString(secret[:]) if err != nil { return fmt.Errorf("failed to create JWT token: %w", err) } + header.Set("Authorization", "Bearer "+s) + return nil } } diff --git a/node/node_example_test.go b/node/node_example_test.go index e45ee49a25..172aab7429 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -50,6 +50,7 @@ func ExampleLifecycle() { if err := stack.Start(); err != nil { log.Fatalf("Failed to start the protocol stack: %v", err) } + if err := stack.Close(); err != nil { log.Fatalf("Failed to stop the protocol stack: %v", err) } diff --git a/node/node_test.go b/node/node_test.go index 1f1c3461a7..dc291f5d91 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -51,6 +51,7 @@ func TestNodeCloseMultipleTimes(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + stack.Close() // Ensure that a stopped node can be stopped again @@ -71,6 +72,7 @@ func TestNodeStartMultipleTimes(t *testing.T) { if err := stack.Start(); err != nil { t.Fatalf("failed to start node: %v", err) } + if err := stack.Start(); err != ErrNodeRunning { t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning) } @@ -78,6 +80,7 @@ func TestNodeStartMultipleTimes(t *testing.T) { if err := stack.Close(); err != nil { t.Fatalf("failed to stop node: %v", err) } + if err := stack.Close(); err != ErrNodeStopped { t.Fatalf("stop failure mismatch: have %v, want %v ", err, ErrNodeStopped) } @@ -93,7 +96,9 @@ func TestNodeUsedDataDir(t *testing.T) { if err != nil { t.Fatalf("failed to create original protocol stack: %v", err) } + defer original.Close() + if err := original.Start(); err != nil { t.Fatalf("failed to start original protocol stack: %v", err) } @@ -156,11 +161,13 @@ func TestNodeCloseClosesDB(t *testing.T) { if err != nil { t.Fatal("can't open DB:", err) } + if err = db.Put([]byte{}, []byte{}); err != nil { t.Fatal("can't Put on open DB:", err) } stack.Close() + if err = db.Put([]byte{}, []byte{}); err == nil { t.Fatal("Put succeeded after node is closed") } @@ -172,7 +179,9 @@ func TestNodeOpenDatabaseFromLifecycleStart(t *testing.T) { defer stack.Close() var db ethdb.Database + var err error + stack.RegisterLifecycle(&InstrumentedService{ startHook: func() { db, err = stack.OpenDatabase("mydb", 0, 0, "", false) @@ -239,10 +248,12 @@ func TestLifecycleLifeCycle(t *testing.T) { if err := stack.Start(); err != nil { t.Fatalf("failed to start protocol stack: %v", err) } + for id := range lifecycles { if !started[id] { t.Fatalf("service %s: freshly started service not running", id) } + if stopped[id] { t.Fatalf("service %s: freshly started service already stopped", id) } @@ -251,6 +262,7 @@ func TestLifecycleLifeCycle(t *testing.T) { if err := stack.Close(); err != nil { t.Fatalf("failed to stop protocol stack: %v", err) } + for id := range lifecycles { if !stopped[id] { t.Fatalf("service %s: freshly terminated service still running", id) @@ -299,10 +311,12 @@ func TestLifecycleStartupError(t *testing.T) { if err := stack.Start(); err != failure { t.Fatalf("stack startup failure mismatch: have %v, want %v", err, failure) } + for id := range lifecycles { if started[id] && !stopped[id] { t.Fatalf("service %s: started but not stopped", id) } + delete(started, id) delete(stopped, id) } @@ -350,10 +364,12 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { if err := stack.Start(); err != nil { t.Fatalf("failed to start protocol stack: %v", err) } + for id := range lifecycles { if !started[id] { t.Fatalf("service %s: service not running", id) } + if stopped[id] { t.Fatalf("service %s: service already stopped", id) } @@ -367,14 +383,17 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { if err.Services[failer] != failure { t.Fatalf("failer termination failure mismatch: have %v, want %v", err.Services[failer], failure) } + if len(err.Services) != 1 { t.Fatalf("failure count mismatch: have %d, want %d", len(err.Services), 1) } } + for id := range lifecycles { if !stopped[id] { t.Fatalf("service %s: service not terminated", id) } + delete(started, id) delete(stopped, id) } @@ -408,10 +427,12 @@ func TestRegisterHandler_Successful(t *testing.T) { // check response resp := doHTTPRequest(t, httpReq) buf := make([]byte, 7) + _, err = io.ReadFull(resp.Body, buf) if err != nil { t.Fatalf("could not read response: %v", err) } + assert.Equal(t, "success", string(buf)) } @@ -440,9 +461,11 @@ func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { if node.WSEndpoint() != ws { t.Fatalf("endpoints should be the same") } + if !checkRPC(ws) { t.Fatalf("ws request failed") } + if !checkRPC(node.HTTPEndpoint()) { t.Fatalf("http request failed") } @@ -454,6 +477,7 @@ func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) { if err != nil { t.Fatal("can't listen:", err) } + port := listener.Addr().(*net.TCPAddr).Port listener.Close() @@ -474,6 +498,7 @@ func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) { if !checkRPC(ws) { t.Fatalf("ws request failed") } + if !checkRPC(node.HTTPEndpoint()) { t.Fatalf("http request failed") } @@ -536,14 +561,18 @@ func TestNodeRPCPrefix(t *testing.T) { 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) }) } @@ -551,12 +580,14 @@ func TestNodeRPCPrefix(t *testing.T) { 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()) } + var resp *http.Response for _, path := range test.wantHTTP { resp = rpcRequest(t, httpBase+path, testMethod) @@ -564,20 +595,25 @@ func (test rpcPrefixTest) check(t *testing.T, node *Node) { t.Errorf("Error: %s: bad status code %d, want 200", path, resp.StatusCode) } } + defer resp.Body.Close() + for _, path := range test.wantNoHTTP { resp = rpcRequest(t, httpBase+path, testMethod) if resp.StatusCode != 404 { t.Errorf("Error: %s: bad status code %d, want 404", path, resp.StatusCode) } } + defer resp.Body.Close() + 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 { @@ -594,15 +630,18 @@ func createNode(t *testing.T, httpPort, wsPort int) *Node { WSPort: wsPort, HTTPTimeouts: rpc.DefaultHTTPTimeouts, } + node, err := New(conf) if err != nil { t.Fatalf("could not create a new node: %v", err) } + return node } func startHTTP(t *testing.T, httpPort, wsPort int) *Node { node := createNode(t, httpPort, wsPort) + err := node.Start() if err != nil { t.Fatalf("could not start http service on node: %v", err) @@ -613,11 +652,14 @@ func startHTTP(t *testing.T, httpPort, wsPort int) *Node { func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { client := http.DefaultClient + resp, err := client.Do(req) if err != nil { t.Fatalf("could not issue a GET request to the given endpoint: %v", err) } + t.Cleanup(func() { resp.Body.Close() }) + return resp } @@ -627,6 +669,7 @@ func containsProtocol(stackProtocols []p2p.Protocol, protocol p2p.Protocol) bool return true } } + return false } @@ -636,5 +679,6 @@ func containsAPI(stackAPIs []rpc.API, api rpc.API) bool { return true } } + return false } diff --git a/node/rpcstack.go b/node/rpcstack.go index 8dafea2fb1..9b7d90e7a0 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -20,6 +20,7 @@ import ( "compress/gzip" "context" "fmt" + "github.com/rs/cors" "io" "net" "net/http" @@ -101,6 +102,7 @@ func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts, rpcBatchLimit uint h.httpHandler.Store((*rpcHandler)(nil)) h.wsHandler.Store((*rpcHandler)(nil)) + return h } @@ -116,6 +118,7 @@ func (h *httpServer) setListenAddr(host string, port int) error { h.host, h.port = host, port h.endpoint = fmt.Sprintf("%s:%d", host, port) + return nil } @@ -127,6 +130,7 @@ func (h *httpServer) listenAddr() string { if h.listener != nil { return h.listener.Addr().String() } + return h.endpoint } @@ -156,8 +160,10 @@ func (h *httpServer) start() error { // configuration so they can be configured another time. h.disableRPC() h.disableWS() + return err } + h.listener = listener go h.server.Serve(listener) @@ -166,6 +172,7 @@ func (h *httpServer) start() error { if h.wsConfig.prefix != "" { url += h.wsConfig.prefix } + h.log.Info("WebSocket enabled", "url", url) } // if server is websocket only, return after logging @@ -185,15 +192,19 @@ func (h *httpServer) start() error { for path := range h.handlerNames { paths = append(paths, path) } + sort.Strings(paths) logged := make(map[string]bool, len(paths)) + for _, path := range paths { name := h.handlerNames[path] if !logged[name] { log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path) + logged[name] = true } } + return nil } @@ -204,6 +215,7 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { if checkPath(r, h.wsConfig.prefix) { ws.ServeHTTP(w, r) } + return } @@ -225,6 +237,7 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } } + w.WriteHeader(http.StatusNotFound) } @@ -243,15 +256,18 @@ 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 } @@ -270,10 +286,12 @@ func (h *httpServer) doStop() { // Shut down the server. httpHandler := h.httpHandler.Load().(*rpcHandler) wsHandler := h.wsHandler.Load().(*rpcHandler) + if httpHandler != nil { h.httpHandler.Store((*rpcHandler)(nil)) httpHandler.server.Stop() } + if wsHandler != nil { h.wsHandler.Store((*rpcHandler)(nil)) wsHandler.server.Stop() @@ -281,6 +299,7 @@ func (h *httpServer) doStop() { ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) defer cancel() + err := h.server.Shutdown(ctx) if err != nil && err == ctx.Err() { h.log.Warn("HTTP server graceful shutdown timed out") @@ -307,14 +326,17 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { // Create RPC server and handler. srv := rpc.NewServer(config.executionPoolSize, config.executionPoolRequestTimeout) srv.SetRPCBatchLimit(h.RPCBatchLimit) + if err := RegisterApis(apis, config.Modules, srv); err != nil { return err } + h.httpConfig = config h.httpHandler.Store(&rpcHandler{ Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts, config.jwtSecret), server: srv, }) + return nil } @@ -325,6 +347,7 @@ func (h *httpServer) disableRPC() bool { h.httpHandler.Store((*rpcHandler)(nil)) handler.server.Stop() } + return handler != nil } @@ -339,14 +362,17 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { // Create RPC server and handler. srv := rpc.NewServer(config.executionPoolSize, config.executionPoolRequestTimeout) srv.SetRPCBatchLimit(h.RPCBatchLimit) + if err := RegisterApis(apis, config.Modules, srv); err != nil { return err } + h.wsConfig = config h.wsHandler.Store(&rpcHandler{ Handler: NewWSHandlerStack(srv.WebsocketHandler(config.Origins), config.jwtSecret), server: srv, }) + return nil } @@ -369,6 +395,7 @@ func (h *httpServer) disableWS() bool { h.wsHandler.Store((*rpcHandler)(nil)) ws.server.Stop() } + return ws != nil } @@ -393,9 +420,11 @@ func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string, jwtSe // Wrap the CORS-handler within a host-handler handler := newCorsHandler(srv, cors) handler = newVHostHandler(vhosts, handler) + if len(jwtSecret) != 0 { handler = newJWTHandler(jwtSecret, handler) } + return newGzipHandler(handler) } @@ -404,6 +433,7 @@ func NewWSHandlerStack(srv http.Handler, jwtSecret []byte) http.Handler { if len(jwtSecret) != 0 { return newJWTHandler(jwtSecret, srv) } + return srv } @@ -412,12 +442,14 @@ func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { if len(allowedOrigins) == 0 { return srv } + c := cors.New(cors.Options{ AllowedOrigins: allowedOrigins, AllowedMethods: []string{http.MethodPost, http.MethodGet}, AllowedHeaders: []string{"*"}, MaxAge: 600, }) + return c.Handler(srv) } @@ -435,6 +467,7 @@ func newVHostHandler(vhosts []string, next http.Handler) http.Handler { for _, allowedHost := range vhosts { vhostMap[strings.ToLower(allowedHost)] = struct{}{} } + return &virtualHostHandler{vhostMap, next} } @@ -445,11 +478,13 @@ func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.next.ServeHTTP(w, r) return } + host, _, err := net.SplitHostPort(r.Host) if err != nil { // Either invalid (too many colons) or no port specified host = r.Host } + if ipAddr := net.ParseIP(host); ipAddr != nil { // It's an IP address, we can serve that h.next.ServeHTTP(w, r) @@ -460,10 +495,12 @@ func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.next.ServeHTTP(w, r) return } + if _, exist := h.vhosts[host]; exist { h.next.ServeHTTP(w, r) return } + http.Error(w, "invalid host specified", http.StatusForbidden) } @@ -490,9 +527,11 @@ func (w *gzipResponseWriter) init() { if w.inited { return } + w.inited = true hdr := w.resp.Header() + length := hdr.Get("content-length") if len(length) > 0 { if n, err := strconv.ParseUint(length, 10, 64); err != nil { @@ -538,12 +577,14 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) { n, err := w.gz.Write(b) w.written += uint64(n) + if w.hasLength && w.written >= w.contentLength { // The HTTP handler has finished writing the entire uncompressed response. Close // the gzip stream to ensure the footer will be seen by the client in case the // response is flushed after this call to write. err = w.gz.Close() } + return n, err } @@ -551,6 +592,7 @@ func (w *gzipResponseWriter) Flush() { if w.gz != nil { w.gz.Flush() } + if f, ok := w.resp.(http.Flusher); ok { f.Flush() } @@ -560,6 +602,7 @@ func (w *gzipResponseWriter) close() { if w.gz == nil { return } + w.gz.Close() gzPool.Put(w.gz) w.gz = nil @@ -600,13 +643,16 @@ func (is *ipcServer) start(apis []rpc.API) error { if is.listener != nil { return nil // already running } + 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) is.listener, is.srv = listener, srv + return nil } @@ -617,10 +663,12 @@ func (is *ipcServer) stop() error { if is.listener == nil { return nil // not running } + err := is.listener.Close() is.srv.Stop() is.listener, is.srv = nil, nil is.log.Info("IPC endpoint closed", "url", is.endpoint) + return err } @@ -643,5 +691,6 @@ func RegisterApis(apis []rpc.API, modules []string, srv *rpc.Server) error { } } } + return nil } diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 72a7973122..ff6e3fddc3 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -90,6 +90,7 @@ func splitAndTrim(input string) (ret []string) { ret = append(ret, r) } } + return ret } @@ -159,17 +160,20 @@ func TestWebsocketOrigins(t *testing.T) { } for _, tc := range tests { srv := createAndStartServer(t, &httpConfig{}, true, &wsConfig{Origins: splitAndTrim(tc.spec)}, nil) + url := fmt.Sprintf("ws://%v", srv.listenAddr()) for _, origin := range tc.expOk { if err := wsRequest(t, url, "Origin", origin); err != nil { t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err) } } + for _, origin := range tc.expFail { if err := wsRequest(t, url, "Origin", origin); err == nil { t.Errorf("spec '%v', origin '%v': expected not to allow, got ok", tc.spec, origin) } } + srv.stop() } } @@ -250,13 +254,17 @@ func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsCon if timeouts == nil { timeouts = &rpc.DefaultHTTPTimeouts } + srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), *timeouts, 100) assert.NoError(t, srv.enableRPC(apis(), *conf)) + if ws { assert.NoError(t, srv.enableWS(nil, *wsConf)) } + assert.NoError(t, srv.setListenAddr("localhost", 0)) assert.NoError(t, srv.start()) + return srv } @@ -270,14 +278,17 @@ func wsRequest(t *testing.T, url string, extraHeaders ...string) error { if len(extraHeaders)%2 != 0 { panic("odd extraHeaders length") } + for i := 0; i < len(extraHeaders); i += 2 { key, value := extraHeaders[i], extraHeaders[i+1] headers.Set(key, value) } + conn, _, err := websocket.DefaultDialer.Dial(url, headers) if conn != nil { conn.Close() } + return err } @@ -286,6 +297,7 @@ func rpcRequest(t *testing.T, url, method string, extraHeaders ...string) *http. t.Helper() body := fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"%s","params":[]}`, method) + return baseRpcRequest(t, url, body, extraHeaders...) } @@ -296,7 +308,9 @@ func batchRpcRequest(t *testing.T, url string, methods []string, extraHeaders .. for i, m := range methods { reqs[i] = fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"%s","params":[]}`, m) } + body := fmt.Sprintf(`[%s]`, strings.Join(reqs, ",")) + return baseRpcRequest(t, url, body, extraHeaders...) } @@ -305,10 +319,12 @@ func baseRpcRequest(t *testing.T, url, bodyStr string, extraHeaders ...string) * // Create the request. body := bytes.NewReader([]byte(bodyStr)) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, body) if err != nil { t.Fatal("could not create http request:", err) } + req.Header.Set("content-type", "application/json") req.Header.Set("accept-encoding", "identity") @@ -316,6 +332,7 @@ func baseRpcRequest(t *testing.T, url, bodyStr string, extraHeaders ...string) * if len(extraHeaders)%2 != 0 { panic("odd extraHeaders length") } + for i := 0; i < len(extraHeaders); i += 2 { key, value := extraHeaders[i], extraHeaders[i+1] if strings.EqualFold(key, "host") { @@ -327,11 +344,14 @@ func baseRpcRequest(t *testing.T, url, bodyStr string, extraHeaders ...string) * // 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) } + t.Cleanup(func() { resp.Body.Close() }) + return resp } @@ -343,11 +363,14 @@ func (testClaim) Valid() error { func TestJWT(t *testing.T) { var secret = []byte("secret") + issueToken := func(secret []byte, method jwt.SigningMethod, input map[string]interface{}) string { if method == nil { method = jwt.SigningMethodHS256 } + ss, _ := jwt.NewWithClaims(method, testClaim(input)).SignedString(secret) + return ss } srv := createAndStartServer(t, &httpConfig{jwtSecret: []byte("secret")}, @@ -383,6 +406,7 @@ func TestJWT(t *testing.T) { if err := wsRequest(t, wsUrl, "Authorization", token); err != nil { t.Errorf("test %d-ws, token '%v': expected ok, got %v", i, token, err) } + token = tokenFn() if resp := rpcRequest(t, htUrl, testMethod, "Authorization", token); resp.StatusCode != 200 { t.Errorf("test %d-http, token '%v': expected ok, got %v", i, token, resp.StatusCode) @@ -443,7 +467,9 @@ func TestJWT(t *testing.T) { return fmt.Sprintf("Bearer \t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) }, } + var resp *http.Response + for i, tokenFn := range expFail { token := tokenFn() if err := wsRequest(t, wsUrl, "Authorization", token); err == nil { @@ -452,10 +478,12 @@ func TestJWT(t *testing.T) { token = tokenFn() resp = rpcRequest(t, htUrl, testMethod, "Authorization", token) + if resp.StatusCode != http.StatusUnauthorized { t.Errorf("tc %d-http, token '%v': expected not to allow, got %v", i, token, resp.StatusCode) } } + defer resp.Body.Close() srv.stop() } @@ -470,6 +498,7 @@ func TestGzipHandler(t *testing.T) { isGzip bool header map[string]string } + tests := []gzipTest{ { name: "Write", @@ -538,37 +567,45 @@ func TestGzipHandler(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() + srv := httptest.NewServer(newGzipHandler(test.handler)) defer srv.Close() cli := &http.Client{ Timeout: 10 * time.Second, } + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL, nil) if err != nil { log.Crit("Can't build request", "url", srv.URL, "err", err) } + resp, err := cli.Do(req) if err != nil { t.Fatal(err) } + defer resp.Body.Close() content, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } + wasGzip := resp.Uncompressed if string(content) != "response" { t.Fatalf("wrong response content %q", content) } + if wasGzip != test.isGzip { t.Fatalf("response gzipped == %t, want %t", wasGzip, test.isGzip) } + if resp.StatusCode != test.status { t.Fatalf("response status == %d, want %d", resp.StatusCode, test.status) } + for name, expectedValue := range test.header { if v := resp.Header.Get(name); v != expectedValue { t.Fatalf("response header %s == %s, want %s", name, v, expectedValue) @@ -594,12 +631,15 @@ func TestHTTPWriteTimeout(t *testing.T) { // Send normal request t.Run("message", func(t *testing.T) { t.Parallel() + resp := rpcRequest(t, url, "test_sleep") defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } + if string(body) != timeoutRes { t.Errorf("wrong response. have %s, want %s", string(body), timeoutRes) } @@ -608,13 +648,17 @@ func TestHTTPWriteTimeout(t *testing.T) { // Batch request t.Run("batch", func(t *testing.T) { t.Parallel() + want := fmt.Sprintf("[%s,%s,%s]", greetRes, timeoutRes, timeoutRes) + resp := batchRpcRequest(t, url, []string{"test_greet", "test_sleep", "test_greet"}) defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } + if string(body) != want { t.Errorf("wrong response. have %s, want %s", string(body), want) } diff --git a/node/utils_test.go b/node/utils_test.go index 681f3a8b28..a283ed6695 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -53,6 +53,7 @@ func (s *InstrumentedService) Start() error { if s.startHook != nil { s.startHook() } + return s.start } @@ -60,6 +61,7 @@ func (s *InstrumentedService) Stop() error { if s.stopHook != nil { s.stopHook() } + return s.stop } @@ -71,6 +73,7 @@ func NewFullService(stack *Node) (*FullService, error) { stack.RegisterProtocols(fs.Protocols()) stack.RegisterAPIs(fs.APIs()) stack.RegisterLifecycle(fs) + return fs, nil } diff --git a/p2p/dial.go b/p2p/dial.go index ff0cd52cdd..fd6f0e6f4a 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -143,18 +143,22 @@ func (cfg dialConfig) withDefaults() dialConfig { if cfg.maxActiveDials == 0 { cfg.maxActiveDials = defaultMaxPendingPeers } + if cfg.log == nil { cfg.log = log.Root() } + if cfg.clock == nil { cfg.clock = mclock.System{} } + if cfg.rand == nil { seedb := make([]byte, 8) crand.Read(seedb) seed := int64(binary.BigEndian.Uint64(seedb)) cfg.rand = mrand.New(mrand.NewSource(seed)) } + return cfg } @@ -177,8 +181,10 @@ func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupF d.lastStatsLog = d.clock.Now() d.ctx, d.cancel = context.WithCancel(context.Background()) d.wg.Add(2) + go d.readNodes(it) go d.loop(it) + return d } @@ -307,9 +313,11 @@ loop: } d.historyTimer.Stop() + for range d.dialing { <-d.doneCh } + d.wg.Done() } @@ -334,9 +342,11 @@ func (d *dialScheduler) logStats() { if d.lastStatsLog.Add(dialStatsLogInterval) > now { return } + if d.dialPeers < dialStatsPeerLimit && d.dialPeers < d.maxDialPeers { d.log.Info("Looking for peers", "peercount", len(d.peers), "tried", d.doneSinceLastLog, "static", len(d.static)) } + d.doneSinceLastLog = 0 d.lastStatsLog = now } @@ -355,6 +365,7 @@ func (d *dialScheduler) rearmHistoryTimer() { func (d *dialScheduler) expireHistory() { d.history.expire(d.clock.Now(), func(hkey string) { var id enode.ID + copy(id[:], hkey) d.updateStaticPool(id) }) @@ -367,7 +378,9 @@ func (d *dialScheduler) freeDialSlots() int { if slots > d.maxActiveDials { slots = d.maxActiveDials } + free := slots - len(d.dialing) + return free } @@ -376,24 +389,30 @@ func (d *dialScheduler) checkDial(n *enode.Node) error { if n.ID() == d.self { return errSelf } + if n.IP() != nil && n.TCP() == 0 { // This check can trigger if a non-TCP node is found // by discovery. If there is no IP, the node is a static // node and the actual endpoint will be resolved later in dialTask. return errNoPort } + if _, ok := d.dialing[n.ID()]; ok { return errAlreadyDialing } + if _, ok := d.peers[n.ID()]; ok { return errAlreadyConnected } + if d.netRestrict != nil && !d.netRestrict.Contains(n.IP()) { return errNetRestrict } + if d.history.contains(string(n.ID().Bytes())) { return errRecentlyDialed } + return nil } @@ -405,6 +424,7 @@ func (d *dialScheduler) startStaticDials(n int) (started int) { d.startDial(task) d.removeFromStaticPool(idx) } + return started } @@ -420,6 +440,7 @@ func (d *dialScheduler) addToStaticPool(task *dialTask) { if task.staticPoolIndex >= 0 { panic("attempt to add task to staticPool twice") } + d.staticPool = append(d.staticPool, task) task.staticPoolIndex = len(d.staticPool) - 1 } @@ -442,6 +463,7 @@ func (d *dialScheduler) startDial(task *dialTask) { hkey := string(task.dest.ID().Bytes()) d.history.add(hkey, d.clock.Now().Add(dialHistoryExpiration)) d.dialing[task.dest.ID()] = task + go func() { task.run(d) d.doneCh <- task @@ -497,26 +519,33 @@ func (t *dialTask) resolve(d *dialScheduler) bool { if d.resolver == nil { return false } + if t.resolveDelay == 0 { t.resolveDelay = initialResolveDelay } + if t.lastResolved > 0 && time.Duration(d.clock.Now()-t.lastResolved) < t.resolveDelay { return false } + resolved := d.resolver.Resolve(t.dest) t.lastResolved = d.clock.Now() + if resolved == nil { t.resolveDelay *= 2 if t.resolveDelay > maxResolveDelay { t.resolveDelay = maxResolveDelay } + d.log.Debug("Resolving node failed", "id", t.dest.ID(), "newdelay", t.resolveDelay) + return false } // The node was found. t.resolveDelay = initialResolveDelay t.dest = resolved d.log.Debug("Resolved node", "id", t.dest.ID(), "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()}) + return true } @@ -527,7 +556,9 @@ func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error { d.log.Trace("Dial error", "id", t.dest.ID(), "addr", nodeAddr(t.dest), "conn", t.flags, "err", cleanupDialErr(err)) return &dialError{err} } + mfd := newMeteredConn(fd, false, &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()}) + return d.setupFunc(mfd, t.flags, dest) } @@ -540,5 +571,6 @@ func cleanupDialErr(err error) error { if netErr, ok := err.(*net.OpError); ok && netErr.Op == "dial" { return netErr.Err } + return err } diff --git a/p2p/dial_test.go b/p2p/dial_test.go index cd8dedff1c..3d35089919 100644 --- a/p2p/dial_test.go +++ b/p2p/dial_test.go @@ -428,12 +428,15 @@ func runDialTest(t *testing.T, config dialConfig, rounds []dialTestRound) { // Set up the dialer. The setup function below runs on the dialTask // goroutine and adds the peer. var dialsched *dialScheduler + setup := func(fd net.Conn, f connFlag, node *enode.Node) error { conn := &conn{flags: f, node: node} dialsched.peerAdded(conn) setupCh <- conn + return nil } + dialsched = newDialScheduler(config, iterator, setup) defer dialsched.stop() @@ -443,33 +446,40 @@ func runDialTest(t *testing.T, config dialConfig, rounds []dialTestRound) { if peers[c.node.ID()] != nil { t.Fatalf("round %d: peer %v already connected", i, c.node.ID()) } + dialsched.peerAdded(c) peers[c.node.ID()] = c } + for _, id := range round.peersRemoved { c := peers[id] if c == nil { t.Fatalf("round %d: can't remove non-existent peer %v", i, id) } + dialsched.peerRemoved(c) } // Init round. t.Logf("round %d (%d peers)", i, len(peers)) resolver.setAnswers(round.wantResolves) + if round.update != nil { round.update(dialsched) } + iterator.addNodes(round.discovered) // Unblock dialTask goroutines. if err := dialer.completeDials(round.succeeded, nil); err != nil { t.Fatalf("round %d: %v", i, err) } + for range round.succeeded { conn := <-setupCh peers[conn.node.ID()] = conn } + if err := dialer.completeDials(round.failed, errors.New("oops")); err != nil { t.Fatalf("round %d: %v", i, err) } @@ -478,6 +488,7 @@ func runDialTest(t *testing.T, config dialConfig, rounds []dialTestRound) { if err := dialer.waitForDials(round.wantNewDials); err != nil { t.Fatalf("round %d: %v", i, err) } + if !resolver.checkCalls() { t.Fatalf("unexpected calls to Resolve: %v", resolver.calls) } @@ -501,6 +512,7 @@ type dialTestIterator struct { func newDialTestIterator() *dialTestIterator { it := &dialTestIterator{} it.cond = sync.NewCond(&it.mu) + return it } @@ -527,12 +539,15 @@ func (it *dialTestIterator) Next() bool { for len(it.buf) == 0 && !it.closed { it.cond.Wait() } + if it.closed { return false } + it.cur = it.buf[0] copy(it.buf[:], it.buf[1:]) it.buf = it.buf[:len(it.buf)-1] + return true } @@ -588,6 +603,7 @@ func (d *dialTestDialer) waitForDials(nodes []*enode.Node) error { for _, n := range nodes { waitset[n.ID()] = n } + timeout := time.NewTimer(1 * time.Second) defer timeout.Stop() @@ -598,9 +614,11 @@ func (d *dialTestDialer) waitForDials(nodes []*enode.Node) error { if !ok { return fmt.Errorf("attempt to dial unexpected node %v", req.n.ID()) } + if !reflect.DeepEqual(req.n, want) { return fmt.Errorf("ENR of dialed node %v does not match test", req.n.ID()) } + delete(waitset, req.n.ID()) d.blocked[req.n.ID()] = req case <-timeout.C: @@ -608,6 +626,7 @@ func (d *dialTestDialer) waitForDials(nodes []*enode.Node) error { for id := range waitset { waitlist = append(waitlist, id) } + return fmt.Errorf("timed out waiting for dials to %v", waitlist) } } @@ -633,6 +652,7 @@ func (d *dialTestDialer) completeDials(ids []enode.ID, err error) error { } req.unblock <- err } + return nil } @@ -660,6 +680,7 @@ func (t *dialTestResolver) checkCalls() bool { return false } } + return true } @@ -668,5 +689,6 @@ func (t *dialTestResolver) Resolve(n *enode.Node) *enode.Node { defer t.mu.Unlock() t.calls = append(t.calls, n.ID()) + return t.answers[n.ID()] } diff --git a/p2p/discover/common.go b/p2p/discover/common.go index c36e8dcc3a..8699daafce 100644 --- a/p2p/discover/common.go +++ b/p2p/discover/common.go @@ -61,12 +61,15 @@ func (cfg Config) withDefaults() Config { if cfg.Log == nil { cfg.Log = log.Root() } + if cfg.ValidSchemes == nil { cfg.ValidSchemes = enode.ValidSchemes } + if cfg.Clock == nil { cfg.Clock = mclock.System{} } + return cfg } @@ -86,5 +89,6 @@ func min(x, y int) int { if x > y { return y } + return x } diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index 55d24ffa50..2e284005a9 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -54,6 +54,7 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l // Don't query further if we hit ourself. // Unlikely to happen often in practice. it.asked[tab.self().ID()] = true + return it } @@ -78,6 +79,7 @@ func (it *lookup) advance() bool { it.replyBuffer = append(it.replyBuffer, n) } } + it.queries-- if len(it.replyBuffer) > 0 { return true @@ -86,14 +88,17 @@ func (it *lookup) advance() bool { it.shutdown() } } + return false } func (it *lookup) shutdown() { for it.queries > 0 { <-it.replyCh + it.queries-- } + it.queryfunc = nil it.replyBuffer = nil } @@ -112,8 +117,10 @@ func (it *lookup) startQueries() bool { if len(closest.entries) == 0 { it.slowdown() } + it.queries = 1 it.replyCh <- closest.entries + return true } @@ -123,6 +130,7 @@ func (it *lookup) startQueries() bool { if !it.asked[n.ID()] { it.asked[n.ID()] = true it.queries++ + go it.query(n, it.replyCh) } } @@ -155,8 +163,10 @@ func (it *lookup) query(n *node, reply chan<- []*node) { dropped := false if fails >= maxFindnodeFailures && it.tab.bucketLen(n.ID()) >= bucketSize/2 { dropped = true + it.tab.delete(n) } + it.tab.log.Trace("FINDNODE failed", "id", n.ID(), "failcount", fails, "dropped", dropped, "err", err) } else if fails > 0 { // Reset failure counter because it counts _consecutive_ failures. @@ -193,6 +203,7 @@ func (it *lookupIterator) Node() *enode.Node { if len(it.buffer) == 0 { return nil } + return unwrapNode(it.buffer[0]) } @@ -207,18 +218,23 @@ func (it *lookupIterator) Next() bool { if it.ctx.Err() != nil { it.lookup = nil it.buffer = nil + return false } + if it.lookup == nil { it.lookup = it.nextLookup(it.ctx) continue } + if !it.lookup.advance() { it.lookup = nil continue } + it.buffer = it.lookup.replyBuffer } + return true } diff --git a/p2p/discover/node.go b/p2p/discover/node.go index 9ffe101ccf..22f8fb0bb6 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -41,8 +41,10 @@ type encPubkey [64]byte func encodePubkey(key *ecdsa.PublicKey) encPubkey { var e encPubkey + math.ReadBits(key.X, e[:len(e)/2]) math.ReadBits(key.Y, e[len(e)/2:]) + return e } @@ -50,13 +52,16 @@ func decodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) { if len(e) != len(encPubkey{}) { return nil, errors.New("wrong size public key data") } + p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)} half := len(e) / 2 p.X.SetBytes(e[:half]) p.Y.SetBytes(e[half:]) + if !p.Curve.IsOnCurve(p.X, p.Y) { return nil, errors.New("invalid curve point") } + return p, nil } @@ -73,6 +78,7 @@ func wrapNodes(ns []*enode.Node) []*node { for i, n := range ns { result[i] = wrapNode(n) } + return result } @@ -85,6 +91,7 @@ func unwrapNodes(ns []*node) []*enode.Node { for i, n := range ns { result[i] = unwrapNode(n) } + return result } diff --git a/p2p/discover/ntp.go b/p2p/discover/ntp.go index 48ceffe95b..0a2f32c1c2 100644 --- a/p2p/discover/ntp.go +++ b/p2p/discover/ntp.go @@ -48,6 +48,7 @@ func checkClockDrift() { if err != nil { return } + if drift < -driftThreshold || drift > driftThreshold { log.Warn(fmt.Sprintf("System clock seems off by %v, which can prevent network connectivity", drift)) log.Warn("Please enable network time synchronisation in system settings.") @@ -76,6 +77,7 @@ func sntpDrift(measurements int) (time.Duration, error) { // Execute each of the measurements drifts := []time.Duration{} + for i := 0; i < measurements+2; i++ { // Dial the NTP server and send the time retrieval request conn, err := net.DialUDP("udp", nil, addr) @@ -85,6 +87,7 @@ func sntpDrift(measurements int) (time.Duration, error) { defer conn.Close() sent := time.Now() + if _, err = conn.Write(request); err != nil { return 0, err } @@ -95,6 +98,7 @@ func sntpDrift(measurements int) (time.Duration, error) { if _, err = conn.Read(reply); err != nil { return 0, err } + elapsed := time.Since(sent) // Reconstruct the time from the reply data @@ -115,5 +119,6 @@ func sntpDrift(measurements int) (time.Duration, error) { for i := 1; i < len(drifts)-1; i++ { drift += drifts[i] } + return drift / time.Duration(measurements), nil } diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 06f02c510c..8dd76fc0a0 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -114,11 +114,13 @@ func newTable(t transport, db *enode.DB, bootnodes []*enode.Node, log log.Logger if err := tab.setFallbackNodes(bootnodes); err != nil { return nil, err } + for i := range tab.buckets { tab.buckets[i] = &bucket{ ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit}, } } + tab.seedRand() tab.loadSeedNodes() @@ -131,6 +133,7 @@ func (tab *Table) self() *enode.Node { func (tab *Table) seedRand() { var b [8]byte + crand.Read(b[:]) tab.mutex.Lock() @@ -144,10 +147,12 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { if !tab.isInitDone() { return 0 } + tab.mutex.Lock() defer tab.mutex.Unlock() var nodes []*enode.Node + for _, b := range &tab.buckets { for _, n := range b.entries { nodes = append(nodes, unwrapNode(n)) @@ -158,6 +163,7 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { j := tab.rand.Intn(len(nodes)) nodes[i], nodes[j] = nodes[j], nodes[i] } + return copy(buf, nodes) } @@ -172,6 +178,7 @@ func (tab *Table) getNode(id enode.ID) *enode.Node { return unwrapNode(e) } } + return nil } @@ -190,7 +197,9 @@ func (tab *Table) setFallbackNodes(nodes []*enode.Node) error { return fmt.Errorf("bad bootstrap node %q: %v", n, err) } } + tab.nursery = wrapNodes(nodes) + return nil } @@ -211,6 +220,7 @@ func (tab *Table) refresh() <-chan struct{} { case <-tab.closeReq: close(done) } + return done } @@ -224,6 +234,7 @@ func (tab *Table) loop() { revalidateDone chan struct{} // where doRevalidate reports completion waiting = []chan struct{}{tab.initDone} // holds waiting callers while doRefresh runs ) + defer refresh.Stop() defer revalidate.Stop() defer copyNodes.Stop() @@ -267,12 +278,15 @@ loop: if refreshDone != nil { <-refreshDone } + for _, ch := range waiting { close(ch) } + if revalidateDone != nil { <-revalidateDone } + close(tab.closed) } @@ -303,6 +317,7 @@ func (tab *Table) doRefresh(done chan struct{}) { func (tab *Table) loadSeedNodes() { seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge)) seeds = append(seeds, tab.nursery...) + for i := range seeds { seed := seeds[i] age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) }} @@ -338,11 +353,13 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { tab.mutex.Lock() defer tab.mutex.Unlock() b := tab.buckets[bi] + if err == nil { // The node responded, move it to the front. last.livenessChecks++ tab.log.Debug("Revalidated node", "b", bi, "id", last.ID(), "checks", last.livenessChecks) tab.bumpInBucket(b, last) + return } // No reply received, pick a replacement or delete the node if there aren't @@ -366,6 +383,7 @@ func (tab *Table) nodeToRevalidate() (n *node, bi int) { return last, bi } } + return nil, 0 } @@ -383,6 +401,7 @@ func (tab *Table) copyLiveNodes() { defer tab.mutex.Unlock() now := time.Now() + for _, b := range &tab.buckets { for _, n := range b.entries { if n.livenessChecks > 0 && now.Sub(n.addedAt) >= seedMinTableTime { @@ -408,9 +427,11 @@ func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) * // is O(tab.len() * nresults). nodes := &nodesByDistance{target: target} liveNodes := &nodesByDistance{target: target} + for _, b := range &tab.buckets { for _, n := range b.entries { nodes.push(n, nresults) + if preferLive && n.livenessChecks > 0 { liveNodes.push(n, nresults) } @@ -420,6 +441,7 @@ func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) * if preferLive && len(liveNodes.entries) > 0 { return liveNodes } + return nodes } @@ -431,6 +453,7 @@ func (tab *Table) len() (n int) { for _, b := range &tab.buckets { n += len(b.entries) } + return n } @@ -452,6 +475,7 @@ func (tab *Table) bucketAtDistance(d int) *bucket { if d <= bucketMinDistance { return tab.buckets[0] } + return tab.buckets[d-bucketMinDistance-1] } @@ -467,16 +491,19 @@ func (tab *Table) addSeenNode(n *node) { tab.mutex.Lock() defer tab.mutex.Unlock() + b := tab.bucket(n.ID()) if contains(b.entries, n.ID()) { // Already in bucket, don't add. return } + if len(b.entries) >= bucketSize { // Bucket full, maybe add as replacement. tab.addReplacement(b, n) return } + if !tab.addIP(b, n.IP()) { // Can't add: IP limit reached. return @@ -484,6 +511,7 @@ func (tab *Table) addSeenNode(n *node) { // Add to end of bucket: b.entries = append(b.entries, n) b.replacements = deleteNode(b.replacements, n) + n.addedAt = time.Now() if tab.nodeAddedHook != nil { tab.nodeAddedHook(n) @@ -503,22 +531,26 @@ func (tab *Table) addVerifiedNode(n *node) { if !tab.isInitDone() { return } + if n.ID() == tab.self().ID() { return } tab.mutex.Lock() defer tab.mutex.Unlock() + b := tab.bucket(n.ID()) if tab.bumpInBucket(b, n) { // Already in bucket, moved to front. return } + if len(b.entries) >= bucketSize { // Bucket full, maybe add as replacement. tab.addReplacement(b, n) return } + if !tab.addIP(b, n.IP()) { // Can't add: IP limit reached. return @@ -526,6 +558,7 @@ func (tab *Table) addVerifiedNode(n *node) { // Add to front of bucket. b.entries, _ = pushNode(b.entries, n, bucketSize) b.replacements = deleteNode(b.replacements, n) + n.addedAt = time.Now() if tab.nodeAddedHook != nil { tab.nodeAddedHook(n) @@ -544,18 +577,23 @@ func (tab *Table) addIP(b *bucket, ip net.IP) bool { if len(ip) == 0 { return false // Nodes without IP cannot be added. } + if netutil.IsLAN(ip) { return true } + if !tab.ips.Add(ip) { tab.log.Debug("IP exceeds table limit", "ip", ip) return false } + if !b.ips.Add(ip) { tab.log.Debug("IP exceeds bucket limit", "ip", ip) tab.ips.Remove(ip) + return false } + return true } @@ -563,6 +601,7 @@ func (tab *Table) removeIP(b *bucket, ip net.IP) { if netutil.IsLAN(ip) { return } + tab.ips.Remove(ip) b.ips.Remove(ip) } @@ -573,10 +612,13 @@ func (tab *Table) addReplacement(b *bucket, n *node) { return // already in list } } + if !tab.addIP(b, n.IP()) { return } + var removed *node + b.replacements, removed = pushNode(b.replacements, n, maxReplacements) if removed != nil { tab.removeIP(b, removed.IP()) @@ -596,10 +638,12 @@ func (tab *Table) replace(b *bucket, last *node) *node { tab.deleteInBucket(b, last) return nil } + r := b.replacements[tab.rand.Intn(len(b.replacements))] b.replacements = deleteNode(b.replacements, r) b.entries[len(b.entries)-1] = r tab.removeIP(b, last.IP()) + return r } @@ -611,6 +655,7 @@ func (tab *Table) bumpInBucket(b *bucket, n *node) bool { if !n.IP().Equal(b.entries[i].IP()) { // Endpoint has changed, ensure that the new IP fits into table limits. tab.removeIP(b, b.entries[i].IP()) + if !tab.addIP(b, n.IP()) { // It doesn't, put the previous one back. tab.addIP(b, b.entries[i].IP()) @@ -620,9 +665,11 @@ func (tab *Table) bumpInBucket(b *bucket, n *node) bool { // Move it to the front. copy(b.entries[1:], b.entries[:i]) b.entries[0] = n + return true } } + return false } @@ -637,6 +684,7 @@ func contains(ns []*node, id enode.ID) bool { return true } } + return false } @@ -645,9 +693,11 @@ func pushNode(list []*node, n *node, max int) ([]*node, *node) { if len(list) < max { list = append(list, nil) } + removed := list[len(list)-1] copy(list[1:], list) list[0] = n + return list, removed } @@ -658,6 +708,7 @@ func deleteNode(list []*node, n *node) []*node { return append(list[:i], list[i+1:]...) } } + return list } @@ -674,6 +725,7 @@ func (h *nodesByDistance) push(n *node, maxElems int) { }) end := len(h.entries) + if len(h.entries) < maxElems { h.entries = append(h.entries, n) } diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 2e8c7908be..703a0feddf 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -50,6 +50,7 @@ func TestTable_pingReplace(t *testing.T) { func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) { transport := newPingRecorder() + tab, db := newTestTable(transport) defer db.Close() defer tab.close() @@ -65,6 +66,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding // its bucket if it is unresponsive. Revalidate again to ensure that transport.dead[last.ID()] = !lastInBucketIsResponding transport.dead[pingSender.ID()] = !newNodeIsResponding + tab.addSeenNode(pingSender) tab.doRevalidate(make(chan struct{}, 1)) tab.doRevalidate(make(chan struct{}, 1)) @@ -76,16 +78,20 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding tab.mutex.Lock() defer tab.mutex.Unlock() + wantSize := bucketSize if !lastInBucketIsResponding && !newNodeIsResponding { wantSize-- } + if l := len(tab.bucket(pingSender.ID()).entries); l != wantSize { t.Errorf("wrong bucket size after bond: got %d, want %d", l, wantSize) } + if found := contains(tab.bucket(pingSender.ID()).entries, last.ID()); found != lastInBucketIsResponding { t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding) } + wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding if found := contains(tab.bucket(pingSender.ID()).entries, pingSender.ID()); found != wantNewEntry { t.Errorf("new entry found: %t, want: %t", found, wantNewEntry) @@ -94,6 +100,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding func TestBucket_bumpNoDuplicates(t *testing.T) { t.Parallel() + cfg := &quick.Config{ MaxCount: 1000, Rand: rand.New(rand.NewSource(time.Now().Unix())), @@ -121,17 +128,23 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { b := &bucket{entries: make([]*node, len(nodes))} copy(b.entries, nodes) + for i, pos := range bumps { tab.bumpInBucket(b, 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 } } + checkIPLimitInvariant(t, tab) + return true } if err := quick.Check(prop, cfg); err != nil { @@ -142,6 +155,7 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { // This checks that the table-wide IP limit is applied correctly. func TestTable_IPLimit(t *testing.T) { transport := newPingRecorder() + tab, db := newTestTable(transport) defer db.Close() defer tab.close() @@ -150,15 +164,18 @@ func TestTable_IPLimit(t *testing.T) { n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)}) tab.addSeenNode(n) } + if tab.len() > tableIPLimit { t.Errorf("too many nodes in table") } + checkIPLimitInvariant(t, tab) } // This checks that the per-bucket IP limit is applied correctly. func TestTable_BucketIPLimit(t *testing.T) { transport := newPingRecorder() + tab, db := newTestTable(transport) defer db.Close() defer tab.close() @@ -168,9 +185,11 @@ func TestTable_BucketIPLimit(t *testing.T) { n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)}) tab.addSeenNode(n) } + if tab.len() > bucketIPLimit { t.Errorf("too many nodes in table") } + checkIPLimitInvariant(t, tab) } @@ -180,11 +199,13 @@ func checkIPLimitInvariant(t *testing.T, tab *Table) { t.Helper() tabset := netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit} + for _, b := range tab.buckets { for _, n := range b.entries { tabset.Add(n.IP()) } } + if tabset.String() != tab.ips.String() { t.Errorf("table IP set is incorrect:\nhave: %v\nwant: %v", tab.ips, tabset) } @@ -196,6 +217,7 @@ func TestTable_findnodeByID(t *testing.T) { test := func(test *closeTest) bool { // for any node table, Target and N transport := newPingRecorder() + tab, db := newTestTable(transport) defer db.Close() defer tab.close() @@ -207,6 +229,7 @@ func TestTable_findnodeByID(t *testing.T) { t.Errorf("result contains duplicates") return false } + if !sortedByDistanceTo(test.Target, result) { t.Errorf("result is not sorted by distance to target") return false @@ -217,6 +240,7 @@ func TestTable_findnodeByID(t *testing.T) { if tlen := tab.len(); tlen < test.N { wantN = tlen } + if len(result) != wantN { t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN) return false @@ -230,16 +254,19 @@ func TestTable_findnodeByID(t *testing.T) { if contains(result, n.ID()) { continue // don't run the check below for nodes in result } + farthestResult := result[len(result)-1].ID() if enode.DistCmp(test.Target, n.ID(), 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 { @@ -257,6 +284,7 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) { } test := func(buf []*enode.Node) bool { transport := newPingRecorder() + tab, db := newTestTable(transport) defer db.Close() defer tab.close() @@ -266,17 +294,21 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) { ld := cfg.Rand.Intn(len(tab.buckets)) fillTable(tab, []*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))}) } + gotN := tab.ReadRandomNodes(buf) if gotN != tab.len() { t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.len()) return false } + if hasDuplicates(wrapNodes(buf[:gotN])) { t.Errorf("result contains duplicates") return false } + return true } + if err := quick.Check(test, cfg); err != nil { t.Error(err) } @@ -295,6 +327,7 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { Target: gen(enode.ID{}, rand).(enode.ID), N: rand.Intn(bucketSize), } + for _, id := range gen([]enode.ID{}, rand).([]enode.ID) { r := new(enr.Record) r.Set(enr.IP(genIP(rand))) @@ -302,12 +335,14 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { n.livenessChecks = 1 t.All = append(t.All, n) } + return reflect.ValueOf(t) } func TestTable_addVerifiedNode(t *testing.T) { tab, db := newTestTable(newPingRecorder()) <-tab.initDone + defer db.Close() defer tab.close() @@ -334,12 +369,14 @@ func TestTable_addVerifiedNode(t *testing.T) { if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, newBcontent) { t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries) } + checkIPLimitInvariant(t, tab) } func TestTable_addSeenNode(t *testing.T) { tab, db := newTestTable(newPingRecorder()) <-tab.initDone + defer db.Close() defer tab.close() @@ -365,6 +402,7 @@ func TestTable_addSeenNode(t *testing.T) { if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) { t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries) } + checkIPLimitInvariant(t, tab) } @@ -374,12 +412,15 @@ func TestTable_revalidateSyncRecord(t *testing.T) { transport := newPingRecorder() tab, db := newTestTable(transport) <-tab.initDone + defer db.Close() defer tab.close() // Insert a node. var r enr.Record + r.Set(enr.IP(net.IP{127, 0, 0, 1})) + id := enode.ID{1} n1 := wrapNode(enode.SignNull(&r, id)) tab.addSeenNode(n1) @@ -390,6 +431,7 @@ func TestTable_revalidateSyncRecord(t *testing.T) { transport.updateRecord(n2) tab.doRevalidate(make(chan struct{}, 1)) + intable := tab.getNode(id) if !reflect.DeepEqual(intable, n2) { t.Fatalf("table contains old record with seq %d, want seq %d", intable.Seq(), n2.Seq()) @@ -462,12 +504,14 @@ func gen(typ interface{}, rand *rand.Rand) interface{} { if !ok { panic(fmt.Sprintf("couldn't generate random value of type %T", typ)) } + return v.Interface() } func genIP(rand *rand.Rand) net.IP { ip := make(net.IP, 4) rand.Read(ip) + return ip } @@ -483,5 +527,6 @@ func newkey() *ecdsa.PrivateKey { if err != nil { panic("couldn't generate key: " + err.Error()) } + return key } diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 77e03ca9e7..7b187e4b98 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -37,21 +37,26 @@ var nullNode *enode.Node func init() { var r enr.Record + r.Set(enr.IP{0, 0, 0, 0}) nullNode = enode.SignNull(&r, enode.ID{}) } func newTestTable(t transport) (*Table, *enode.DB) { db, _ := enode.OpenDB("") + tab, _ := newTable(t, db, nil, log.Root()) go tab.loop() + return tab, db } // nodeAtDistance creates a node for which enode.LogDist(base, n.id) == ld. func nodeAtDistance(base enode.ID, ld int, ip net.IP) *node { var r enr.Record + r.Set(enr.IP(ip)) + return wrapNode(enode.SignNull(&r, idAtDistance(base, ld))) } @@ -61,6 +66,7 @@ func nodesAtDistance(base enode.ID, ld int, n int) []*enode.Node { for i := range results { results[i] = unwrapNode(nodeAtDistance(base, ld, intIP(i))) } + return results } @@ -69,6 +75,7 @@ func nodesToRecords(nodes []*enode.Node) []*enr.Record { for i := range nodes { records[i] = nodes[i].Record() } + return records } @@ -81,14 +88,17 @@ func idAtDistance(a enode.ID, n int) (b enode.ID) { 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 } @@ -100,9 +110,11 @@ func intIP(i int) net.IP { func fillBucket(tab *Table, n *node) (last *node) { ld := enode.LogDist(tab.self().ID(), n.ID()) b := tab.bucket(n.ID()) + for len(b.entries) < bucketSize { b.entries = append(b.entries, nodeAtDistance(tab.self().ID(), ld, intIP(ld))) } + return b.entries[bucketSize-1] } @@ -123,6 +135,7 @@ type pingRecorder struct { func newPingRecorder() *pingRecorder { var r enr.Record + r.Set(enr.IP{0, 0, 0, 0}) n := enode.SignNull(&r, enode.ID{}) @@ -156,9 +169,11 @@ func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) { if t.dead[n.ID()] { return 0, errTimeout } + if t.records[n.ID()] != nil { seq = t.records[n.ID()].Seq() } + return seq, nil } @@ -170,20 +185,25 @@ func (t *pingRecorder) RequestENR(n *enode.Node) (*enode.Node, error) { if t.dead[n.ID()] || t.records[n.ID()] == nil { return nil, errTimeout } + return t.records[n.ID()], nil } func hasDuplicates(slice []*node) bool { seen := make(map[enode.ID]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 } @@ -196,18 +216,23 @@ func checkNodesEqual(got, want []*enode.Node) error { } } } + return nil NotEqual: output := new(bytes.Buffer) fmt.Fprintf(output, "got %d nodes:\n", len(got)) + for _, n := range got { fmt.Fprintf(output, " %v %v\n", n.ID(), n) } + fmt.Fprintf(output, "want %d:\n", len(want)) + for _, n := range want { fmt.Fprintf(output, " %v %v\n", n.ID(), n) } + return errors.New(output.String()) } @@ -233,10 +258,12 @@ func hexEncPrivkey(h string) *ecdsa.PrivateKey { if err != nil { panic(err) } + key, err := crypto.ToECDSA(b) if err != nil { panic(err) } + return key } @@ -246,9 +273,12 @@ func hexEncPubkey(h string) (ret encPubkey) { if err != nil { panic(err) } + if len(b) != len(ret) { panic("invalid length") } + copy(ret[:], b) + return ret } diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go index a00de9ca18..2822649dba 100644 --- a/p2p/discover/v4_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -46,6 +46,7 @@ func TestUDPv4_Lookup(t *testing.T) { resultC := make(chan []*enode.Node, 1) go func() { resultC <- test.udp.LookupPubkey(targetKey) + test.close() }() @@ -54,18 +55,23 @@ func TestUDPv4_Lookup(t *testing.T) { // Verify result nodes. results := <-resultC + t.Logf("results:") + for _, e := range results { t.Logf(" ld=%d, %x", enode.LogDist(lookupTestnet.target.id(), e.ID()), e.ID().Bytes()) } + if len(results) != bucketSize { t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) } + checkLookupResults(t, lookupTestnet, results) } func TestUDPv4_LookupIterator(t *testing.T) { t.Parallel() + test := newUDPTest(t) defer test.close() @@ -74,12 +80,15 @@ func TestUDPv4_LookupIterator(t *testing.T) { for i := range lookupTestnet.dists[256] { bootnodes[i] = wrapNode(lookupTestnet.node(256, i)) } + fillTable(test.table, bootnodes) + go serveTestnet(test, lookupTestnet) // Create the iterator and collect the nodes it yields. iter := test.udp.RandomNodes() seen := make(map[enode.ID]*enode.Node) + for limit := lookupTestnet.len(); iter.Next() && len(seen) < limit; { seen[iter.Node().ID()] = iter.Node() } @@ -90,7 +99,9 @@ func TestUDPv4_LookupIterator(t *testing.T) { for _, n := range seen { results = append(results, n) } + sortByID(results) + want := lookupTestnet.nodes() if err := checkNodesEqual(results, want); err != nil { t.Fatal(err) @@ -101,6 +112,7 @@ func TestUDPv4_LookupIterator(t *testing.T) { // method is called. func TestUDPv4_LookupIteratorClose(t *testing.T) { t.Parallel() + test := newUDPTest(t) defer test.close() @@ -109,7 +121,9 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) { for i := range lookupTestnet.dists[256] { bootnodes[i] = wrapNode(lookupTestnet.node(256, i)) } + fillTable(test.table, bootnodes) + go serveTestnet(test, lookupTestnet) it := test.udp.RandomNodes() @@ -126,9 +140,11 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) { } } t.Logf("iterator returned %d nodes after close", ncalls) + if it.Next() { t.Errorf("Next() == true after close and %d more calls", ncalls) } + if n := it.Node(); n != nil { t.Errorf("iterator returned non-nil node after close and %d more calls", ncalls) } @@ -155,15 +171,19 @@ func serveTestnet(test *udpTest, testnet *preminedTestnet) { func checkLookupResults(t *testing.T, tn *preminedTestnet, results []*enode.Node) { t.Helper() t.Logf("results:") + for _, e := range results { t.Logf(" ld=%d, %x", enode.LogDist(tn.target.id(), e.ID()), e.ID().Bytes()) } + if hasDuplicates(wrapNodes(results)) { t.Errorf("result set contains duplicate entries") } + if !sortedByDistanceTo(tn.target.id(), wrapNodes(results)) { t.Errorf("result set not sorted by distance to target") } + wantNodes := tn.closest(len(results)) if err := checkNodesEqual(results, wantNodes); err != nil { t.Error(err) @@ -240,17 +260,21 @@ func (tn *preminedTestnet) len() int { for _, keys := range tn.dists { n += len(keys) } + return n } func (tn *preminedTestnet) nodes() []*enode.Node { result := make([]*enode.Node, 0, tn.len()) + for dist, keys := range tn.dists { for index := range keys { result = append(result, tn.node(dist, index)) } } + sortByID(result) + return result } @@ -261,6 +285,7 @@ func (tn *preminedTestnet) node(dist, index int) *enode.Node { rec.Set(enr.UDP(5000)) enode.SignV4(rec, key) n, _ := enode.New(enode.ValidSchemes, rec) + return n } @@ -268,6 +293,7 @@ func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.Pr dist := int(addr.IP[1])<<8 + int(addr.IP[2]) index := int(addr.IP[3]) key := tn.dists[dist][index] + return tn.node(dist, index), key } @@ -276,15 +302,18 @@ func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node { for i := range result { result[i] = nodeToRPC(wrapNode(tn.node(dist, i))) } + return result } func (tn *preminedTestnet) neighborsAtDistances(base *enode.Node, distances []uint, elems int) []*enode.Node { var result []*enode.Node + for d := range lookupTestnet.dists { for i := range lookupTestnet.dists[d] { n := lookupTestnet.node(d, i) d := enode.LogDist(base.ID(), n.ID()) + if containsUint(uint(d), distances) { result = append(result, n) if len(result) >= elems { @@ -293,6 +322,7 @@ func (tn *preminedTestnet) neighborsAtDistances(base *enode.Node, distances []ui } } } + return result } @@ -302,9 +332,11 @@ func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) { nodes = append(nodes, tn.node(d, i)) } } + sort.Slice(nodes, func(i, j int) bool { return enode.DistCmp(tn.target.id(), nodes[i].ID(), nodes[j].ID()) < 0 }) + return nodes[:n] } @@ -319,10 +351,12 @@ func (tn *preminedTestnet) mine() { } targetSha := tn.target.id() + found, need := 0, 40 for found < need { k := newkey() ld := enode.LogDist(targetSha, encodePubkey(&k.PublicKey).id()) + if len(tn.dists[ld]) < 8 { tn.dists[ld] = append(tn.dists[ld], k) found++ @@ -332,16 +366,21 @@ func (tn *preminedTestnet) mine() { fmt.Printf("&preminedTestnet{\n") fmt.Printf(" target: hexEncPubkey(\"%x\"),\n", tn.target[:]) fmt.Printf(" dists: [%d][]*ecdsa.PrivateKey{\n", len(tn.dists)) + for ld, ns := range tn.dists { if len(ns) == 0 { continue } + fmt.Printf(" %d: {\n", ld) + for _, key := range ns { fmt.Printf(" hexEncPrivkey(\"%x\"),\n", crypto.FromECDSA(key)) } + fmt.Printf(" },\n") } + fmt.Printf(" },\n") fmt.Printf("}\n") } diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index 67cd2c004c..97d86016ad 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -146,12 +146,15 @@ func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) { if err != nil { return nil, err } + t.tab = tab + go tab.loop() t.wg.Add(2) go t.loop() go t.readLoop(cfg.Unhandled) + return t, nil } @@ -189,6 +192,7 @@ func (t *UDPv4) Resolve(n *enode.Node) *enode.Node { if n.Load(&key) != nil { return n // no secp256k1 key } + result := t.LookupPubkey((*ecdsa.PublicKey)(&key)) for _, rn := range result { if rn.ID() == n.ID() { @@ -197,12 +201,14 @@ func (t *UDPv4) Resolve(n *enode.Node) *enode.Node { } } } + return n } func (t *UDPv4) ourEndpoint() v4wire.Endpoint { n := t.Self() a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()} + return v4wire.NewEndpoint(a, uint16(n.TCP())) } @@ -218,6 +224,7 @@ func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) { if err = <-rm.errc; err == nil { seq = rm.reply.(*v4wire.Pong).ENRSeq } + return seq, err } @@ -225,10 +232,12 @@ func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) { // when the reply arrives. func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher { req := t.makePing(toaddr) + packet, hash, err := v4wire.Encode(t.priv, req) if err != nil { errc := make(chan error, 1) errc <- err + return &replyMatcher{errc: errc} } // Add a matcher for the reply to the pending reply queue. Pongs are matched if they @@ -238,11 +247,13 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r if matched && callback != nil { callback() } + return matched, matched }) // Send the packet. t.localNode.UDPContact(toaddr) t.write(toaddr, toid, req.Name(), packet) + return rm } @@ -263,6 +274,7 @@ func (t *UDPv4) LookupPubkey(key *ecdsa.PublicKey) []*enode.Node { // case and run the bootstrapping logic. <-t.tab.refresh() } + return t.newLookup(t.closeCtx, encodePubkey(key)).run() } @@ -283,7 +295,9 @@ func (t *UDPv4) lookupSelf() []*enode.Node { func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup { var target encPubkey + crand.Read(target[:]) + return t.newLookup(ctx, target) } @@ -293,6 +307,7 @@ func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup { it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) { return t.findnode(n.ID(), n.addr(), ekey) }) + return it } @@ -309,15 +324,19 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubke reply := r.(*v4wire.Neighbors) for _, rn := range reply.Nodes { nreceived++ + n, err := t.nodeFromRPC(toaddr, rn) if err != nil { t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err) continue } + nodes = append(nodes, n) } + return true, nreceived >= bucketSize }) + t.send(toaddr, toid, &v4wire.Findnode{ Target: target, Expiration: uint64(time.Now().Add(expiration).Unix()), @@ -331,6 +350,7 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubke if errors.Is(err, errTimeout) && rm.reply != nil { err = nil } + return nodes, err } @@ -342,6 +362,7 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) { req := &v4wire.ENRRequest{ Expiration: uint64(time.Now().Add(expiration).Unix()), } + packet, hash, err := v4wire.Encode(t.priv, req) if err != nil { return nil, err @@ -355,6 +376,7 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) { }) // Send the packet and wait for the reply. t.write(addr, n.ID(), req.Name(), packet) + if err := <-rm.errc; err != nil { return nil, err } @@ -363,15 +385,19 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) { if err != nil { return nil, err } + if respN.ID() != n.ID() { return nil, fmt.Errorf("invalid ID in response record") } + if respN.Seq() < n.Seq() { return n, nil // response record is older } + if err := netutil.CheckRelayIP(addr.IP, respN.IP()); err != nil { return nil, fmt.Errorf("invalid IP in response record: %v", err) } + return respN, nil } @@ -386,6 +412,7 @@ func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchF case <-t.closeCtx.Done(): ch <- errClosed } + return p } @@ -414,6 +441,7 @@ func (t *UDPv4) loop() { contTimeouts = 0 // number of continuous timeouts to do NTP checks ntpWarnTime = time.Unix(0, 0) ) + <-timeout.C // ignore first timeout defer timeout.Stop() @@ -423,6 +451,7 @@ func (t *UDPv4) loop() { } // Start the timer so it fires when the next pending reply has expired. now := time.Now() + for el := plist.Front(); el != nil; el = el.Next() { nextTimeout = el.Value.(*replyMatcher) if dist := nextTimeout.deadline.Sub(now); dist < 2*respTimeout { @@ -433,9 +462,12 @@ func (t *UDPv4) loop() { // future. These can occur if the system clock jumped // backwards after the deadline was assigned. nextTimeout.errc <- errClockWarp + plist.Remove(el) } + nextTimeout = nil + timeout.Stop() } @@ -447,6 +479,7 @@ func (t *UDPv4) loop() { for el := plist.Front(); el != nil; el = el.Next() { el.Value.(*replyMatcher).errc <- errClosed } + return case p := <-t.addReplyMatcher: @@ -455,6 +488,7 @@ func (t *UDPv4) loop() { case r := <-t.gotreply: var matched bool // whether any replyMatcher considered the reply acceptable. + for el := plist.Front(); el != nil; el = el.Next() { p := el.Value.(*replyMatcher) if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) { @@ -464,6 +498,7 @@ func (t *UDPv4) loop() { // Remove the matcher if callback indicates that all replies have been received. if requestDone { p.errc <- nil + plist.Remove(el) } // Reset the continuous timeout counter (time drift detection) @@ -480,7 +515,9 @@ func (t *UDPv4) loop() { p := el.Value.(*replyMatcher) if now.After(p.deadline) || now.Equal(p.deadline) { p.errc <- errTimeout + plist.Remove(el) + contTimeouts++ } } @@ -488,8 +525,10 @@ func (t *UDPv4) loop() { if contTimeouts > ntpFailureThreshold { if time.Since(ntpWarnTime) >= ntpWarningCooldown { ntpWarnTime = time.Now() + go checkClockDrift() } + contTimeouts = 0 } } @@ -501,23 +540,27 @@ func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]b if err != nil { return hash, err } + return hash, t.write(toaddr, toid, req.Name(), packet) } func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error { _, err := t.conn.WriteToUDP(packet, toaddr) t.log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err) + return err } // readLoop runs in its own goroutine. it handles incoming UDP packets. func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) { defer t.wg.Done() + if unhandled != nil { defer close(unhandled) } buf := make([]byte, maxPacketSize) + for { nbytes, from, err := t.conn.ReadFromUDP(buf) if netutil.IsTemporaryError(err) { @@ -529,8 +572,10 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) { if !errors.Is(err, io.EOF) { t.log.Debug("UDP read error", "err", err) } + return } + if t.handlePacket(from, buf[:nbytes]) != nil && unhandled != nil { select { case unhandled <- ReadPacket{buf[:nbytes], from}: @@ -546,15 +591,20 @@ func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error { t.log.Debug("Bad discv4 packet", "addr", from, "err", err) return err } + packet := t.wrapPacket(rawpacket) fromID := fromKey.ID() + if err == nil && packet.preverify != nil { err = packet.preverify(packet, from, fromID, fromKey) } + t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", from, "err", err) + if err == nil && packet.handle != nil { packet.handle(packet, from, fromID, hash) } + return err } @@ -579,33 +629,41 @@ func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) if rn.UDP <= 1024 { return nil, errLowPort } + if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { return nil, err } + if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) { return nil, errors.New("not contained in netrestrict list") } + key, err := v4wire.DecodePubkey(crypto.S256(), rn.ID) if err != nil { return nil, err } + n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))) err = n.ValidateComplete() + return n, err } func nodeToRPC(n *node) v4wire.Node { var key ecdsa.PublicKey + var ekey v4wire.Pubkey if err := n.Load((*enode.Secp256k1)(&key)); err == nil { ekey = v4wire.EncodePubkey(&key) } + return v4wire.Node{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())} } // wrapPacket returns the handler functions applicable to a packet. func (t *UDPv4) wrapPacket(p v4wire.Packet) *packetHandlerV4 { var h packetHandlerV4 + h.Packet = p switch p.(type) { case *v4wire.Ping: @@ -624,6 +682,7 @@ func (t *UDPv4) wrapPacket(p v4wire.Packet) *packetHandlerV4 { case *v4wire.ENRResponse: h.preverify = t.verifyENRResponse } + return &h } @@ -647,10 +706,13 @@ func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I if err != nil { return err } + if v4wire.Expired(req.Expiration) { return errExpired } + h.senderKey = senderKey + return nil } @@ -688,11 +750,14 @@ func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I if v4wire.Expired(req.Expiration) { return errExpired } + if !t.handleReply(fromID, from.IP, req) { return errUnsolicitedReply } + t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}) t.db.UpdateLastPongReceived(fromID, from.IP, time.Now()) + return nil } @@ -704,6 +769,7 @@ func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno if v4wire.Expired(req.Expiration) { return errExpired } + if !t.checkBond(fromID, from.IP) { // No endpoint proof pong exists, we don't process the packet. This prevents an // attack vector where the discovery protocol could be used to amplify traffic in a @@ -713,6 +779,7 @@ func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno // findnode) to the victim. return errUnknownNode } + return nil } @@ -726,17 +793,21 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno // Send neighbors in chunks with at most maxNeighbors per packet // to stay below the packet size limit. p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())} + var sent bool + for _, n := range closest { if netutil.CheckRelayIP(from.IP, n.IP()) == nil { p.Nodes = append(p.Nodes, nodeToRPC(n)) } + if len(p.Nodes) == v4wire.MaxNeighbors { t.send(from, fromID, &p) p.Nodes = p.Nodes[:0] sent = true } } + if len(p.Nodes) > 0 || !sent { t.send(from, fromID, &p) } @@ -750,9 +821,11 @@ func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID en if v4wire.Expired(req.Expiration) { return errExpired } + if !t.handleReply(fromID, from.IP, h.Packet) { return errUnsolicitedReply } + return nil } @@ -764,9 +837,11 @@ func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID e if v4wire.Expired(req.Expiration) { return errExpired } + if !t.checkBond(fromID, from.IP) { return errUnknownNode } + return nil } @@ -783,5 +858,6 @@ func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID if !t.handleReply(fromID, from.IP, h.Packet) { return errUnsolicitedReply } + return nil } diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 07df858a9d..674686fa8d 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -76,6 +76,7 @@ func newUDPTest(t *testing.T) *udpTest { test.table = test.udp.tab // Wait for initial refresh so the table doesn't send unexpected findnode. <-test.table.initDone + return test } @@ -99,6 +100,7 @@ func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr * if err != nil { test.t.Errorf("%s encode error: %v", data.Name(), err) } + test.sent = append(test.sent, enc) if err = test.udp.handlePacket(addr, enc); err != wantError { test.t.Errorf("error mismatch: got %q, want %q", err, wantError) @@ -117,18 +119,23 @@ func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) { test.t.Error("packet receive error:", err) return false } + p, _, hash, err := v4wire.Decode(dgram.data) if err != nil { test.t.Errorf("sent packet decode error: %v", err) return false } + fn := reflect.ValueOf(validate) + exptype := fn.Type().In(0) if !reflect.TypeOf(p).AssignableTo(exptype) { test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) return false } + fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(hash)}) + return false } @@ -144,11 +151,13 @@ func TestUDPv4_packetErrors(t *testing.T) { func TestUDPv4_pingTimeout(t *testing.T) { t.Parallel() + test := newUDPTest(t) defer test.close() key := newkey() toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} + node := enode.NewV4(&key.PublicKey, toaddr.IP, 0, toaddr.Port) if _, err := test.udp.ping(node); err != errTimeout { t.Error("expected timeout error, got", err) @@ -162,6 +171,7 @@ func (req testPacket) Name() string { return "" } func TestUDPv4_responseTimeouts(t *testing.T) { t.Parallel() + test := newUDPTest(t) defer test.close() @@ -175,6 +185,7 @@ func TestUDPv4_responseTimeouts(t *testing.T) { nilErr = make(chan error, nReqs) // for requests that get a reply timeoutErr = make(chan error, nReqs) // for requests that time out ) + for i := 0; i < nReqs; i++ { // Create a matcher for a random request in udp.loop. Requests // with ptype <= 128 will not get a reply and should time out. @@ -185,19 +196,23 @@ func TestUDPv4_responseTimeouts(t *testing.T) { callback: func(v4wire.Packet) (bool, bool) { return true, true }, } binary.BigEndian.PutUint64(p.from[:], uint64(i)) + if p.ptype <= 128 { p.errc = timeoutErr test.udp.addReplyMatcher <- p + nTimeouts++ } else { p.errc = nilErr test.udp.addReplyMatcher <- p + time.AfterFunc(randomDuration(60*time.Millisecond), func() { if !test.udp.handleReply(p.from, p.ip, testPacket(p.ptype)) { t.Logf("not matched: %v", p) } }) } + time.Sleep(randomDuration(30 * time.Millisecond)) } @@ -207,25 +222,30 @@ func TestUDPv4_responseTimeouts(t *testing.T) { recvDeadline = time.After(20 * time.Second) nTimeoutsRecv, nNil = 0, 0 ) + for i := 0; i < nReqs; i++ { select { case err := <-timeoutErr: if err != errTimeout { t.Fatalf("got non-timeout error on timeoutErr %d: %v", i, err) } + nTimeoutsRecv++ case err := <-nilErr: if err != nil { t.Fatalf("got non-nil error on nilErr %d: %v", i, err) } + nNil++ case <-recvDeadline: t.Fatalf("exceeded recv deadline") } } + if nTimeoutsRecv != nTimeouts { t.Errorf("wrong number of timeout errors received: got %d, want %d", nTimeoutsRecv, nTimeouts) } + if nNil != nReqs-nTimeouts { t.Errorf("wrong number of successful replies: got %d, want %d", nNil, nReqs-nTimeouts) } @@ -233,16 +253,19 @@ func TestUDPv4_responseTimeouts(t *testing.T) { func TestUDPv4_findnodeTimeout(t *testing.T) { t.Parallel() + test := newUDPTest(t) defer test.close() toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} toid := enode.ID{1, 2, 3, 4} target := v4wire.Pubkey{4, 5, 6, 7} + result, err := test.udp.findnode(toid, toaddr, target) if err != errTimeout { t.Error("expected timeout error, got", err) } + if len(result) > 0 { t.Error("expected empty result, got", result) } @@ -257,6 +280,7 @@ func TestUDPv4_findnode(t *testing.T) { // take care not to overflow any bucket. nodes := &nodesByDistance{target: testTarget.ID()} live := make(map[enode.ID]bool) + numCandidates := 2 * bucketSize for i := 0; i < numCandidates; i++ { key := newkey() @@ -267,6 +291,7 @@ func TestUDPv4_findnode(t *testing.T) { n.livenessChecks = 1 live[n.ID()] = true } + nodes.push(n, numCandidates) } fillTable(test.table, nodes.entries) @@ -279,16 +304,19 @@ func TestUDPv4_findnode(t *testing.T) { // check that closest neighbors are returned. expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true) test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp}) + waitNeighbors := func(want []*node) { test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) { if len(p.Nodes) != len(want) { t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) return } + for i, n := range p.Nodes { if n.ID.ID() != want[i].ID() { t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, n, expected.entries[i]) } + if !live[n.ID.ID()] { t.Errorf("result includes dead node %v", n.ID.ID()) } @@ -301,6 +329,7 @@ func TestUDPv4_findnode(t *testing.T) { waitNeighbors(want[:v4wire.MaxNeighbors]) want = want[v4wire.MaxNeighbors:] } + waitNeighbors(want) } @@ -313,8 +342,10 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { // queue a pending findnode request resultc, errc := make(chan []*node, 1), make(chan error, 1) + go func() { rid := encodePubkey(&test.remotekey.PublicKey).id() + ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget) if err != nil && len(ns) == 0 { errc <- err @@ -339,9 +370,11 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")), } rpclist := make([]v4wire.Node, len(list)) + for i := range list { rpclist[i] = nodeToRPC(list[i]) } + test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[:2]}) test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[2:]}) @@ -394,6 +427,7 @@ func TestUDPv4_pingMatchIP(t *testing.T) { func TestUDPv4_successfulPing(t *testing.T) { test := newUDPTest(t) added := make(chan *node, 1) + test.table.nodeAddedHook = func(n *node) { added <- n } defer test.close() @@ -406,6 +440,7 @@ func TestUDPv4_successfulPing(t *testing.T) { if !bytes.Equal(p.ReplyTok, pinghash) { t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash) } + wantTo := v4wire.Endpoint{ // The mirrored UDP address is the UDP packet sender IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), @@ -422,6 +457,7 @@ func TestUDPv4_successfulPing(t *testing.T) { if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) { t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint()) } + wantTo := v4wire.Endpoint{ // The mirrored UDP address is the UDP packet sender. IP: test.remoteaddr.IP, @@ -431,6 +467,7 @@ func TestUDPv4_successfulPing(t *testing.T) { if !reflect.DeepEqual(p.To, wantTo) { t.Errorf("got ping.To %v, want %v", p.To, wantTo) } + test.packetIn(nil, &v4wire.Pong{ReplyTok: hash, Expiration: futureExp}) }) @@ -442,12 +479,15 @@ func TestUDPv4_successfulPing(t *testing.T) { if n.ID() != rid { t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid) } + if !n.IP().Equal(test.remoteaddr.IP) { t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.IP) } + if n.UDP() != test.remoteaddr.Port { t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port) } + if n.TCP() != int(testRemote.TCP) { t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP(), testRemote.TCP) } @@ -478,6 +518,7 @@ func TestUDPv4_EIP868(t *testing.T) { if p.ENRSeq != wantNode.Seq() { t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq, wantNode.Seq()) } + test.packetIn(nil, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash}) }) @@ -488,6 +529,7 @@ func TestUDPv4_EIP868(t *testing.T) { if err != nil { t.Fatalf("invalid record: %v", err) } + if !reflect.DeepEqual(n, wantNode) { t.Fatalf("wrong node in ENRResponse: %v", n) } @@ -502,10 +544,12 @@ func TestUDPv4_smallNetConvergence(t *testing.T) { nodes := make([]*UDPv4, 4) for i := range nodes { var cfg Config + if i > 0 { bn := nodes[0].Self() cfg.Bootnodes = []*enode.Node{bn} } + nodes[i] = startLocalhostV4(t, cfg) defer nodes[i].Close() } @@ -513,11 +557,13 @@ func TestUDPv4_smallNetConvergence(t *testing.T) { // Run through the iterator on all nodes until // they have all found each other. status := make(chan error, len(nodes)) + for i := range nodes { node := nodes[i] go func() { found := make(map[enode.ID]bool, len(nodes)) it := node.RandomNodes() + for it.Next() { found[it.Node().ID()] = true if len(found) == len(nodes) { @@ -532,6 +578,7 @@ func TestUDPv4_smallNetConvergence(t *testing.T) { // Wait for all status reports. timeout := time.NewTimer(30 * time.Second) defer timeout.Stop() + for received := 0; received < len(nodes); { select { case <-timeout.C: @@ -540,6 +587,7 @@ func TestUDPv4_smallNetConvergence(t *testing.T) { } case err := <-status: received++ + if err != nil { t.Error("ERROR:", err) return @@ -569,13 +617,16 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 { if err != nil { t.Fatal(err) } + realaddr := socket.LocalAddr().(*net.UDPAddr) ln.SetStaticIP(realaddr.IP) ln.SetFallbackUDP(realaddr.Port) + udp, err := ListenV4(socket, ln, cfg) if err != nil { t.Fatal(err) } + return udp } @@ -595,6 +646,7 @@ type dgram struct { func newpipe() *dgramPipe { mu := new(sync.Mutex) + return &dgramPipe{ closing: make(chan struct{}), cond: &sync.Cond{L: mu}, @@ -608,11 +660,14 @@ func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) { copy(msg, b) c.mu.Lock() defer c.mu.Unlock() + if c.closed { return 0, errors.New("closed") } + c.queue = append(c.queue, dgram{*to, b}) c.cond.Signal() + return len(b), nil } @@ -625,11 +680,14 @@ func (c *dgramPipe) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) func (c *dgramPipe) Close() error { c.mu.Lock() defer c.mu.Unlock() + if !c.closed { close(c.closing) c.closed = true } + c.cond.Broadcast() + return nil } @@ -642,6 +700,7 @@ func (c *dgramPipe) receive() (dgram, error) { defer c.mu.Unlock() var timedOut bool + timer := time.AfterFunc(3*time.Second, func() { c.mu.Lock() timedOut = true @@ -653,14 +712,18 @@ func (c *dgramPipe) receive() (dgram, error) { for len(c.queue) == 0 && !c.closed && !timedOut { c.cond.Wait() } + if c.closed { return dgram{}, errClosed } + if timedOut { return dgram{}, errTimeout } + p := c.queue[0] copy(c.queue, c.queue[1:]) c.queue = c.queue[:len(c.queue)-1] + return p, nil } diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go index 3935068cd9..7b1f1674fc 100644 --- a/p2p/discover/v4wire/v4wire.go +++ b/p2p/discover/v4wire/v4wire.go @@ -157,6 +157,7 @@ func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint { } else if ip6 := addr.IP.To16(); ip6 != nil { ip = ip6 } + return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} } @@ -211,17 +212,21 @@ func Decode(input []byte) (Packet, Pubkey, []byte, error) { if len(input) < headSize+1 { return nil, Pubkey{}, nil, ErrPacketTooSmall } + hash, sig, sigdata := input[:macSize], input[macSize:headSize], input[headSize:] shouldhash := crypto.Keccak256(input[macSize:]) + if !bytes.Equal(hash, shouldhash) { return nil, Pubkey{}, nil, ErrBadHash } + fromKey, err := recoverNodeKey(crypto.Keccak256(input[headSize:]), sig) if err != nil { return nil, fromKey, hash, err } var req Packet + switch ptype := sigdata[0]; ptype { case PingPacket: req = new(Ping) @@ -238,8 +243,10 @@ func Decode(input []byte) (Packet, Pubkey, []byte, error) { default: return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) } + s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) err = s.Decode(req) + return req, fromKey, hash, err } @@ -248,18 +255,23 @@ func Encode(priv *ecdsa.PrivateKey, req Packet) (packet, hash []byte, err error) b := new(bytes.Buffer) b.Write(headSpace) b.WriteByte(req.Kind()) + if err := rlp.Encode(b, req); err != nil { return nil, nil, err } + packet = b.Bytes() + sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv) if err != nil { return nil, nil, err } + copy(packet[macSize:], sig) // Add the hash to the front. Note: this doesn't protect the packet in any way. hash = crypto.Keccak256(packet[macSize:]) copy(packet, hash) + return packet, hash, nil } @@ -269,15 +281,19 @@ func recoverNodeKey(hash, sig []byte) (key Pubkey, err error) { if err != nil { return key, err } + copy(key[:], pubkey[1:]) + return key, nil } // EncodePubkey encodes a secp256k1 public key. func EncodePubkey(key *ecdsa.PublicKey) Pubkey { var e Pubkey + math.ReadBits(key.X, e[:len(e)/2]) math.ReadBits(key.Y, e[len(e)/2:]) + return e } @@ -287,8 +303,10 @@ func DecodePubkey(curve elliptic.Curve, e Pubkey) (*ecdsa.PublicKey, error) { half := len(e) / 2 p.X.SetBytes(e[:half]) p.Y.SetBytes(e[half:]) + if !p.Curve.IsOnCurve(p.X, p.Y) { return nil, ErrBadPoint } + return p, nil } diff --git a/p2p/discover/v4wire/v4wire_test.go b/p2p/discover/v4wire/v4wire_test.go index 38820f3b48..dbc391b433 100644 --- a/p2p/discover/v4wire/v4wire_test.go +++ b/p2p/discover/v4wire/v4wire_test.go @@ -105,14 +105,17 @@ func TestForwardCompatibility(t *testing.T) { if err != nil { t.Fatalf("invalid hex: %s", test.input) } + packet, nodekey, _, err := Decode(input) if err != nil { t.Errorf("did not accept packet %s\n%v", test.input, err) continue } + if !reflect.DeepEqual(packet, test.wantPacket) { t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket)) } + if nodekey != wantNodeKey { t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey) } @@ -124,9 +127,12 @@ func hexPubkey(h string) (ret Pubkey) { if err != nil { panic(err) } + if len(b) != len(ret) { panic("invalid length") } + copy(ret[:], b) + return ret } diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index 582164b1e4..f4c258cb57 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -129,10 +129,13 @@ func ListenV5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { if err != nil { return nil, err } + go t.tab.loop() t.wg.Add(2) + go t.readLoop() go t.dispatch() + return t, nil } @@ -168,11 +171,14 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { closeCtx: closeCtx, cancelCloseCtx: cancelCloseCtx, } + tab, err := newTable(t, t.db, cfg.Bootnodes, cfg.Log) if err != nil { return nil, err } + t.tab = tab + return t, nil } @@ -214,6 +220,7 @@ func (t *UDPv5) Resolve(n *enode.Node) *enode.Node { return rn } } + return n } @@ -221,6 +228,7 @@ func (t *UDPv5) Resolve(n *enode.Node) *enode.Node { func (t *UDPv5) AllNodes() []*enode.Node { t.tab.mutex.Lock() defer t.tab.mutex.Unlock() + nodes := make([]*enode.Node, 0) for _, b := range &t.tab.buckets { @@ -228,6 +236,7 @@ func (t *UDPv5) AllNodes() []*enode.Node { nodes = append(nodes, unwrapNode(n)) } } + return nodes } @@ -249,6 +258,7 @@ func (t *UDPv5) RegisterTalkHandler(protocol string, handler TalkRequestHandler) // TalkRequest sends a talk request to n and waits for a response. func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]byte, error) { req := &v5wire.TalkRequest{Protocol: protocol, Message: request} + resp := t.call(n, v5wire.TalkResponseMsg, req) defer t.callDone(resp) select { @@ -290,7 +300,9 @@ func (t *UDPv5) lookupSelf() []*enode.Node { func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup { var target enode.ID + crand.Read(target[:]) + return t.newLookup(ctx, target) } @@ -307,17 +319,20 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) { nodes = nodesByDistance{target: target} err error ) + var r []*enode.Node r, err = t.findnode(unwrapNode(destNode), dists) if errors.Is(err, errClosed) { return nil, err } + for _, n := range r { if n.ID() != t.Self().ID() { nodes.push(wrapNode(n), findnodeResultLimit) } } + return nodes.entries, err } @@ -327,14 +342,17 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) { func lookupDistances(target, dest enode.ID) (dists []uint) { td := enode.LogDist(target, dest) dists = append(dists, uint(td)) + for i := 1; len(dists) < lookupRequestLimit; i++ { if td+i <= 256 { dists = append(dists, uint(td+i)) } + if td-i > 0 { dists = append(dists, uint(td-i)) } } + return dists } @@ -358,9 +376,11 @@ func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) { if err != nil { return nil, err } + if len(nodes) != 1 { return nil, fmt.Errorf("%d nodes in response for distance zero", len(nodes)) } + return nodes[0], nil } @@ -379,6 +399,7 @@ func (t *UDPv5) waitForNodes(c *callV5, distances []uint) ([]*enode.Node, error) seen = make(map[enode.ID]struct{}) received, total = 0, -1 ) + for { select { case responseP := <-c.ch: @@ -389,11 +410,14 @@ func (t *UDPv5) waitForNodes(c *callV5, distances []uint) ([]*enode.Node, error) t.log.Debug("Invalid record in "+response.Name(), "id", c.node.ID(), "err", err) continue } + nodes = append(nodes, node) } + if total == -1 { total = min(int(response.RespCount), totalNodesResponseLimit) } + if received++; received == total { return nodes, nil } @@ -409,6 +433,7 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s if err != nil { return nil, err } + if err := netutil.CheckRelayIP(c.node.IP(), node.IP()); err != nil { return nil, err } @@ -416,19 +441,24 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s if t.netrestrict != nil && !t.netrestrict.Contains(node.IP()) { return nil, errors.New("not contained in netrestrict list") } + if c.node.UDP() <= 1024 { return nil, errLowPort } + if distances != nil { nd := enode.LogDist(c.node.ID(), node.ID()) if !containsUint(uint(nd), distances) { return nil, errors.New("does not match any requested distance") } } + if _, ok := seen[node.ID()]; ok { return nil, fmt.Errorf("duplicate record") } + seen[node.ID()] = struct{}{} + return node, nil } @@ -438,6 +468,7 @@ func containsUint(x uint, xs []uint) bool { return true } } + return false } @@ -461,6 +492,7 @@ func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) case <-t.closeCtx.Done(): c.err <- errClosed } + return c } @@ -515,10 +547,12 @@ func (t *UDPv5) dispatch() { case c := <-t.callDoneCh: id := c.node.ID() + active := t.activeCallByNode[id] if active != c { panic("BUG: callDone for inactive call") } + c.timeout.Stop() delete(t.activeCallByAuth, c.nonce) delete(t.activeCallByNode, id) @@ -531,17 +565,22 @@ func (t *UDPv5) dispatch() { case <-t.closeCtx.Done(): close(t.readNextCh) + for id, queue := range t.callQueue { for _, c := range queue { c.err <- errClosed } + delete(t.callQueue, id) } + for id, c := range t.activeCallByNode { c.err <- errClosed + delete(t.activeCallByNode, id) delete(t.activeCallByAuth, c.nonce) } + return } } @@ -552,10 +591,12 @@ func (t *UDPv5) startResponseTimeout(c *callV5) { if c.timeout != nil { c.timeout.Stop() } + var ( timer mclock.Timer done = make(chan struct{}) ) + timer = t.clock.AfterFunc(respTimeoutV5, func() { <-done select { @@ -564,6 +605,7 @@ func (t *UDPv5) startResponseTimeout(c *callV5) { } }) c.timeout = timer + close(done) } @@ -573,8 +615,10 @@ func (t *UDPv5) sendNextCall(id enode.ID) { if len(queue) == 0 || t.activeCallByNode[id] != nil { return } + t.activeCallByNode[id] = queue[0] t.sendCall(t.activeCallByNode[id]) + if len(queue) == 1 { delete(t.callQueue, id) } else { @@ -616,11 +660,13 @@ func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c if err != nil { t.logcontext = append(t.logcontext, "err", err) t.log.Warn(">> "+packet.Name(), t.logcontext...) + return nonce, err } _, err = t.conn.WriteToUDP(enc, toAddr) t.log.Trace(">> "+packet.Name(), t.logcontext...) + return nonce, err } @@ -640,8 +686,10 @@ func (t *UDPv5) readLoop() { if !errors.Is(err, io.EOF) { t.log.Debug("UDP read error", "err", err) } + return } + t.dispatchReadPacket(from, buf[:nbytes]) } } @@ -659,6 +707,7 @@ func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool { // handlePacket decodes and processes an incoming packet from the network. func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error { addr := fromAddr.String() + fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr) if err != nil { if t.unhandled != nil && v5wire.IsInvalidHeader(err) { @@ -670,20 +719,26 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error { return nil } + t.log.Debug("Bad discv5 packet", "id", fromID, "addr", addr, "err", err) + return err } + if fromNode != nil { // Handshake succeeded, add to table. t.tab.addSeenNode(wrapNode(fromNode)) } + if packet.Kind() != v5wire.WhoareyouPacket { // WHOAREYOU logged separately to report errors. t.logcontext = append(t.logcontext[:0], "id", fromID, "addr", addr) t.logcontext = packet.AppendLogInfo(t.logcontext) t.log.Trace("<< "+packet.Name(), t.logcontext...) } + t.handle(packet, fromID, fromAddr) + return nil } @@ -694,16 +749,20 @@ func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5w t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr) return false } + if !fromAddr.IP.Equal(ac.node.IP()) || fromAddr.Port != ac.node.UDP() { t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr) return false } + if p.Kind() != ac.responseType { t.log.Debug(fmt.Sprintf("Wrong discv5 response type %s", p.Name()), "id", fromID, "addr", fromAddr) return false } + t.startResponseTimeout(ac) ac.ch <- p + return true } @@ -712,9 +771,11 @@ func (t *UDPv5) getNode(id enode.ID) *enode.Node { if n := t.tab.getNode(id); n != nil { return n } + if n := t.localNode.Database().Node(id); n != nil { return n } + return nil } @@ -746,10 +807,12 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) { challenge := &v5wire.Whoareyou{Nonce: p.Nonce} crand.Read(challenge.IDNonce[:]) + if n := t.getNode(fromID); n != nil { challenge.Node = n challenge.RecordSeq = n.Seq() } + t.sendResponse(fromID, fromAddr, challenge) } @@ -768,6 +831,7 @@ func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr * // Resend the call that was answered by WHOAREYOU. t.log.Trace("<< "+p.Name(), "id", c.node.ID(), "addr", fromAddr) + c.handshakeCount++ c.challenge = p p.Node = c.node @@ -780,9 +844,11 @@ func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, err if c == nil { return nil, errChallengeNoCall } + if c.handshakeCount > 0 { return nil, errChallengeTwice } + return c, nil } @@ -795,6 +861,7 @@ func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAdd if remoteIP.To4() != nil { remoteIP = remoteIP.To4() } + t.sendResponse(fromID, fromAddr, &v5wire.Pong{ ReqID: p.ReqID, ToIP: remoteIP, @@ -814,6 +881,7 @@ func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *ne // collectTableNodes creates a FINDNODE result set for the given distances. func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node { var nodes []*enode.Node + var processed = make(map[uint]struct{}) for _, dist := range distances { // Reject duplicate / invalid distances. @@ -831,6 +899,7 @@ func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*en bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries) t.tab.mutex.Unlock() } + processed[dist] = struct{}{} // Apply some pre-checks to avoid sending invalid nodes. @@ -839,12 +908,14 @@ func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*en if netutil.CheckRelayIP(rip, n.IP()) != nil { continue } + nodes = append(nodes, n) if len(nodes) >= limit { return nodes } } } + return nodes } @@ -861,6 +932,7 @@ func packNodes(reqid []byte, nodes []*enode.Node) []*v5wire.Nodes { const sizeLimit = 1000 var resp []*v5wire.Nodes + for len(nodes) > 0 { p := &v5wire.Nodes{ReqID: reqid} size := uint64(0) @@ -874,12 +946,14 @@ func packNodes(reqid []byte, nodes []*enode.Node) []*v5wire.Nodes { p.Nodes = append(p.Nodes, r) nodes = nodes[1:] } + resp = append(resp, p) } for _, msg := range resp { msg.RespCount = uint8(len(resp)) } + return resp } @@ -893,6 +967,7 @@ func (t *UDPv5) handleTalkRequest(fromID enode.ID, fromAddr *net.UDPAddr, p *v5w if handler != nil { 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 8d2e0c5244..47b8c6dad7 100644 --- a/p2p/discover/v5_udp_test.go +++ b/p2p/discover/v5_udp_test.go @@ -43,17 +43,23 @@ func TestUDPv5_lookupE2E(t *testing.T) { t.Parallel() const N = 5 + var nodes []*UDPv5 + for i := 0; i < N; i++ { var cfg Config + if len(nodes) > 0 { bn := nodes[0].Self() cfg.Bootnodes = []*enode.Node{bn} } + node := startLocalhostV5(t, cfg) nodes = append(nodes, node) + defer node.Close() } + last := nodes[N-1] target := nodes[rand.Intn(N-2)].Self() @@ -62,6 +68,7 @@ func TestUDPv5_lookupE2E(t *testing.T) { for i := range nodes { expectedResult[i] = nodes[i].Self() } + sort.Slice(expectedResult, func(i, j int) bool { return enode.DistCmp(target.ID(), expectedResult[i].ID(), expectedResult[j].ID()) < 0 }) @@ -92,19 +99,23 @@ func startLocalhostV5(t *testing.T, cfg Config) *UDPv5 { if err != nil { t.Fatal(err) } + realaddr := socket.LocalAddr().(*net.UDPAddr) ln.SetStaticIP(realaddr.IP) ln.Set(enr.UDP(realaddr.Port)) + udp, err := ListenV5(socket, ln, cfg) if err != nil { t.Fatal(err) } + return udp } // This test checks that incoming PING calls are handled correctly. func TestUDPv5_pingHandling(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() @@ -113,6 +124,7 @@ func TestUDPv5_pingHandling(t *testing.T) { if !bytes.Equal(p.ReqID, []byte("foo")) { t.Error("wrong request ID in response:", p.ReqID) } + if p.ENRSeq != test.table.self().Seq() { t.Error("wrong ENR sequence number in response:", p.ENRSeq) } @@ -122,18 +134,22 @@ func TestUDPv5_pingHandling(t *testing.T) { // This test checks that incoming 'unknown' packets trigger the handshake. func TestUDPv5_unknownPacket(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() nonce := v5wire.Nonce{1, 2, 3} check := func(p *v5wire.Whoareyou, wantSeq uint64) { t.Helper() + if p.Nonce != nonce { t.Error("wrong nonce in WHOAREYOU:", p.Nonce, nonce) } + if p.IDNonce == ([16]byte{}) { t.Error("all zero ID nonce") } + if p.RecordSeq != wantSeq { t.Errorf("wrong record seq %d in WHOAREYOU, want %d", p.RecordSeq, wantSeq) } @@ -158,6 +174,7 @@ func TestUDPv5_unknownPacket(t *testing.T) { // This test checks that incoming FINDNODE calls are handled correctly. func TestUDPv5_findnodeHandling(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() @@ -192,6 +209,7 @@ func TestUDPv5_findnodeHandling(t *testing.T) { // This request gets all the distance-249 nodes and some more at 248 because // the bucket at 249 is not full. test.packetIn(&v5wire.Findnode{ReqID: []byte{5}, Distances: []uint{249, 248}}) + var nodes []*enode.Node nodes = append(nodes, nodes249...) nodes = append(nodes, nodes248[:10]...) @@ -209,21 +227,27 @@ func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes if !bytes.Equal(p.ReqID, wantReqID) { test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID) } + if p.RespCount != wantTotal { test.t.Fatalf("wrong total response count %d, want %d", p.RespCount, wantTotal) } + for _, record := range p.Nodes { n, _ := enode.New(enode.ValidSchemesForTesting, record) want := nodeSet[n.ID()] + if want == nil { test.t.Fatalf("unexpected node in response: %v", n) } + if !reflect.DeepEqual(record, want) { test.t.Fatalf("wrong record in response: %v", n) } + delete(nodeSet, n.ID()) } }) + if len(nodeSet) == 0 { return } @@ -233,6 +257,7 @@ func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes // This test checks that outgoing PING calls work. func TestUDPv5_pingCall(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() @@ -245,6 +270,7 @@ func TestUDPv5_pingCall(t *testing.T) { done <- err }() test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {}) + if err := <-done; err != errTimeout { t.Fatalf("want errTimeout, got %q", err) } @@ -257,6 +283,7 @@ func TestUDPv5_pingCall(t *testing.T) { test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID}) }) + if err := <-done; err != nil { t.Fatal(err) } @@ -270,6 +297,7 @@ func TestUDPv5_pingCall(t *testing.T) { wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101} test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID}) }) + if err := <-done; err != errTimeout { t.Fatalf("want errTimeout for reply from wrong IP, got %q", err) } @@ -279,6 +307,7 @@ func TestUDPv5_pingCall(t *testing.T) { // replies are aggregated. func TestUDPv5_findnodeCall(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() @@ -290,6 +319,7 @@ func TestUDPv5_findnodeCall(t *testing.T) { done = make(chan error, 1) response []*enode.Node ) + go func() { var err error response, err = test.udp.findnode(remote, distances) @@ -301,6 +331,7 @@ func TestUDPv5_findnodeCall(t *testing.T) { if !reflect.DeepEqual(p.Distances, distances) { t.Fatalf("wrong distances in request: %v", p.Distances) } + test.packetIn(&v5wire.Nodes{ ReqID: p.ReqID, RespCount: 2, @@ -317,10 +348,10 @@ func TestUDPv5_findnodeCall(t *testing.T) { if err := <-done; err != nil { t.Fatalf("unexpected error: %v", err) } + if !reflect.DeepEqual(response, nodes) { t.Fatalf("wrong nodes in response") } - // TODO: check invalid IPs // TODO: check invalid/unsigned record } @@ -328,11 +359,13 @@ func TestUDPv5_findnodeCall(t *testing.T) { // This test checks that pending calls are re-sent when a handshake happens. func TestUDPv5_callResend(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() remote := test.getNode(test.remotekey, test.remoteaddr).Node() done := make(chan error, 2) + go func() { _, err := test.udp.ping(remote) done <- err @@ -354,9 +387,11 @@ func TestUDPv5_callResend(t *testing.T) { test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { test.packetIn(&v5wire.Pong{ReqID: p.ReqID}) }) + if err := <-done; err != nil { t.Fatalf("unexpected ping error: %v", err) } + if err := <-done; err != nil { t.Fatalf("unexpected ping error: %v", err) } @@ -365,11 +400,13 @@ func TestUDPv5_callResend(t *testing.T) { // This test ensures we don't allow multiple rounds of WHOAREYOU for a single call. func TestUDPv5_multipleHandshakeRounds(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() remote := test.getNode(test.remotekey, test.remoteaddr).Node() done := make(chan error, 1) + go func() { _, err := test.udp.ping(remote) done <- err @@ -383,6 +420,7 @@ func TestUDPv5_multipleHandshakeRounds(t *testing.T) { test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) }) + if err := <-done; err != errTimeout { t.Fatalf("unexpected ping error: %q", err) } @@ -391,6 +429,7 @@ func TestUDPv5_multipleHandshakeRounds(t *testing.T) { // This test checks that calls with n replies may take up to n * respTimeout. func TestUDPv5_callTimeoutReset(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() @@ -401,6 +440,7 @@ func TestUDPv5_callTimeoutReset(t *testing.T) { nodes = nodesAtDistance(remote.ID(), int(distance), 8) done = make(chan error, 1) ) + go func() { _, err := test.udp.findnode(remote, []uint{distance}) done <- err @@ -422,6 +462,7 @@ func TestUDPv5_callTimeoutReset(t *testing.T) { Nodes: nodesToRecords(nodes[4:]), }) }) + if err := <-done; err != nil { t.Fatalf("unexpected error: %q", err) } @@ -430,10 +471,12 @@ func TestUDPv5_callTimeoutReset(t *testing.T) { // This test checks that TALKREQ calls the registered handler function. func TestUDPv5_talkHandling(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() var recvMessage []byte + test.udp.RegisterTalkHandler("test", func(id enode.ID, addr *net.UDPAddr, message []byte) []byte { recvMessage = message return []byte("test response") @@ -449,9 +492,11 @@ func TestUDPv5_talkHandling(t *testing.T) { if !bytes.Equal(p.ReqID, []byte("foo")) { t.Error("wrong request ID in response:", p.ReqID) } + if string(p.Message) != "test response" { t.Errorf("wrong talk response message: %q", p.Message) } + if string(recvMessage) != "test request" { t.Errorf("wrong message received in handler: %q", recvMessage) } @@ -459,6 +504,7 @@ func TestUDPv5_talkHandling(t *testing.T) { // Check that empty response is returned for unregistered protocols. recvMessage = nil + test.packetIn(&v5wire.TalkRequest{ ReqID: []byte("2"), Protocol: "wrong", @@ -468,9 +514,11 @@ func TestUDPv5_talkHandling(t *testing.T) { if !bytes.Equal(p.ReqID, []byte("2")) { t.Error("wrong request ID in response:", p.ReqID) } + if string(p.Message) != "" { t.Errorf("wrong talk response message: %q", p.Message) } + if recvMessage != nil { t.Errorf("handler was called for wrong protocol: %q", recvMessage) } @@ -480,6 +528,7 @@ func TestUDPv5_talkHandling(t *testing.T) { // This test checks that outgoing TALKREQ calls work. func TestUDPv5_talkRequest(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() @@ -492,6 +541,7 @@ func TestUDPv5_talkRequest(t *testing.T) { done <- err }() test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {}) + if err := <-done; err != errTimeout { t.Fatalf("want errTimeout, got %q", err) } @@ -505,14 +555,17 @@ func TestUDPv5_talkRequest(t *testing.T) { if p.Protocol != "test" { t.Errorf("wrong protocol ID in talk request: %q", p.Protocol) } + if string(p.Message) != "test request" { t.Errorf("wrong message talk request: %q", p.Message) } + test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.TalkResponse{ ReqID: p.ReqID, Message: []byte("test response"), }) }) + if err := <-done; err != nil { t.Fatal(err) } @@ -526,6 +579,7 @@ func TestUDPv5_lookupDistances(t *testing.T) { t.Run("target distance of 1", func(t *testing.T) { t.Parallel() + node := nodeAtDistance(lnID, 1, intIP(0)) dists := lookupDistances(lnID, node.ID()) require.Equal(t, []uint{1, 2, 3}, dists) @@ -533,6 +587,7 @@ func TestUDPv5_lookupDistances(t *testing.T) { t.Run("target distance of 2", func(t *testing.T) { t.Parallel() + node := nodeAtDistance(lnID, 2, intIP(0)) dists := lookupDistances(lnID, node.ID()) require.Equal(t, []uint{2, 3, 1}, dists) @@ -540,6 +595,7 @@ func TestUDPv5_lookupDistances(t *testing.T) { t.Run("target distance of 128", func(t *testing.T) { t.Parallel() + node := nodeAtDistance(lnID, 128, intIP(0)) dists := lookupDistances(lnID, node.ID()) require.Equal(t, []uint{128, 129, 127}, dists) @@ -547,6 +603,7 @@ func TestUDPv5_lookupDistances(t *testing.T) { t.Run("target distance of 255", func(t *testing.T) { t.Parallel() + node := nodeAtDistance(lnID, 255, intIP(0)) dists := lookupDistances(lnID, node.ID()) require.Equal(t, []uint{255, 256, 254}, dists) @@ -554,6 +611,7 @@ func TestUDPv5_lookupDistances(t *testing.T) { t.Run("target distance of 256", func(t *testing.T) { t.Parallel() + node := nodeAtDistance(lnID, 256, intIP(0)) dists := lookupDistances(lnID, node.ID()) // nolint:typecheck require.Equal(t, []uint{256, 255, 254}, dists) @@ -586,11 +644,13 @@ func TestUDPv5_lookup(t *testing.T) { resultC := make(chan []*enode.Node, 1) go func() { resultC <- test.udp.Lookup(lookupTestnet.target.id()) + test.close() }() // Answer lookup packets. asked := make(map[enode.ID]bool) + for done := false; !done; { done = test.waitPacketOut(func(p v5wire.Packet, to *net.UDPAddr, _ v5wire.Nonce) { recipient, key := lookupTestnet.nodeByAddr(to) @@ -601,9 +661,11 @@ func TestUDPv5_lookup(t *testing.T) { if asked[recipient.ID()] { t.Error("Asked node", recipient.ID(), "twice") } + asked[recipient.ID()] = true nodes := lookupTestnet.neighborsAtDistances(recipient, p.Distances, 16) t.Logf("Got FINDNODE for %v, returning %d nodes", p.Distances, len(nodes)) + for _, resp := range packNodes(p.ReqID, nodes) { test.packetInFrom(key, to, resp) } @@ -619,7 +681,9 @@ func TestUDPv5_lookup(t *testing.T) { // This test checks the local node can be utilised to set key-values. func TestUDPv5_LocalNode(t *testing.T) { t.Parallel() + var cfg Config + node := startLocalhostV5(t, cfg) defer node.Close() localNd := node.LocalNode() @@ -633,6 +697,7 @@ func TestUDPv5_LocalNode(t *testing.T) { if err := node.Self().Load(enr.WithEntry("testing", &outputVal)); err != nil { t.Errorf("Could not load value from record: %v", err) } + if testVal != outputVal { t.Errorf("Wanted %#x to be retrieved from the record but instead got %#x", testVal, outputVal) } @@ -640,6 +705,7 @@ func TestUDPv5_LocalNode(t *testing.T) { func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) { t.Parallel() + test := newUDPV5Test(t) defer test.close() @@ -660,9 +726,11 @@ func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) { if len(p.ToIP) == net.IPv6len { t.Error("Received untruncated ip address") } + if len(p.ToIP) != net.IPv4len { t.Errorf("Received ip address with incorrect length: %d", len(p.ToIP)) } + if !p.ToIP.Equal(rawIP) { t.Errorf("Received incorrect ip address: wanted %s but received %s", rawIP.String(), p.ToIP.String()) } @@ -700,11 +768,14 @@ type testCodecFrame struct { func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) { c.ctr++ + var authTag v5wire.Nonce + binary.BigEndian.PutUint64(authTag[:], c.ctr) penc, _ := rlp.EncodeToBytes(p) frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc}) + return frame, authTag, err } @@ -713,6 +784,7 @@ func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5 if err != nil { return enode.ID{}, nil, nil, err } + return frame.NodeID, nil, p, nil } @@ -720,6 +792,7 @@ func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Pa if err = rlp.DecodeBytes(input, &frame); err != nil { return frame, nil, fmt.Errorf("invalid frame: %v", err) } + switch frame.Ptype { case v5wire.UnknownPacket: dec := new(v5wire.Unknown) @@ -732,6 +805,7 @@ func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Pa default: p, err = v5wire.DecodeMessage(frame.Ptype, frame.Packet) } + return frame, p, err } @@ -759,6 +833,7 @@ func newUDPV5Test(t *testing.T) *udpV5Test { test.nodesByID[ln.ID()] = ln // Wait for initial refresh so the table doesn't send unexpected findnode. <-test.table.initDone + return test } @@ -774,10 +849,12 @@ func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, pa ln := test.getNode(key, addr) codec := &testCodec{test: test, id: ln.ID()} + enc, _, err := codec.Encode(test.udp.Self().ID(), addr.String(), packet, nil) if err != nil { test.t.Errorf("%s encode error: %v", packet.Name(), err) } + if test.udp.dispatchReadPacket(addr, enc) { <-test.udp.readNextCh // unblock UDPv5.dispatch } @@ -786,6 +863,7 @@ func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, pa // getNode ensures the test knows about a node at the given endpoint. func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode.LocalNode { id := encodePubkey(&key.PublicKey).id() + ln := test.nodesByID[id] if ln == nil { db, _ := enode.OpenDB("") @@ -794,7 +872,9 @@ func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode. ln.Set(enr.UDP(addr.Port)) test.nodesByID[id] = ln } + test.nodesByIP[string(addr.IP)] = ln + return ln } @@ -811,26 +891,33 @@ func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) { if err == errClosed { return true } + if err == errTimeout { test.t.Fatalf("timed out waiting for %v", exptype) return false } + ln := test.nodesByIP[string(dgram.to.IP)] if ln == nil { test.t.Fatalf("attempt to send to non-existing node %v", &dgram.to) return false } + codec := &testCodec{test: test, id: ln.ID()} + frame, p, err := codec.decodeFrame(dgram.data) if err != nil { test.t.Errorf("sent packet decode error: %v", err) return false } + if !reflect.TypeOf(p).AssignableTo(exptype) { test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) return false } + fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(frame.AuthTag)}) + return false } @@ -839,11 +926,13 @@ func (test *udpV5Test) close() { test.udp.Close() test.db.Close() + for id, n := range test.nodesByID { if id != test.udp.Self().ID() { n.Database().Close() } } + if len(test.pipe.queue) != 0 { test.t.Fatalf("%d unmatched UDP packets in queue", len(test.pipe.queue)) } diff --git a/p2p/discover/v5wire/crypto.go b/p2p/discover/v5wire/crypto.go index fc0a0edef5..f478becf34 100644 --- a/p2p/discover/v5wire/crypto.go +++ b/p2p/discover/v5wire/crypto.go @@ -57,6 +57,7 @@ func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) { if len(e) != 33 { return nil, errors.New("wrong size public key data") } + return crypto.DecompressPubkey(e) default: return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name) @@ -70,18 +71,21 @@ func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte h.Write(challenge) h.Write(ephkey) h.Write(destID[:]) + return h.Sum(nil) } // makeIDSignature creates the ID nonce signature. func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) { input := idNonceHash(hash, challenge, ephkey, destID) + switch key.Curve { case crypto.S256(): idsig, err := crypto.Sign(input, key) if err != nil { return nil, err } + return idsig[:len(idsig)-1], nil // remove recovery ID default: return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name) @@ -101,10 +105,12 @@ func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, eph if n.Load(&pubkey) != nil { return errors.New("no secp256k1 public key in record") } + input := idNonceHash(hash, challenge, ephkey, destID) if !crypto.VerifySignature(pubkey, input, sig) { return errInvalidNonceSig } + return nil default: return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme) @@ -116,6 +122,7 @@ type hashFn func() hash.Hash // deriveKeys creates the session keys. func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session { const text = "discovery v5 key agreement" + var info = make([]byte, 0, len(text)+len(n1)+len(n2)) info = append(info, text...) info = append(info, n1[:]...) @@ -125,13 +132,16 @@ func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n if eph == nil { return nil } + kdf := hkdf.New(hash, eph, challenge, info) sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)} kdf.Read(sec.writeKey) kdf.Read(sec.readKey) + for i := range eph { eph[i] = 0 } + return &sec } @@ -141,9 +151,11 @@ func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte { if secX == nil { return nil } + sec := make([]byte, 33) sec[0] = 0x02 | byte(secY.Bit(0)) math.ReadBits(secX, sec[1:]) + return sec } @@ -155,10 +167,12 @@ func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) { if err != nil { panic(fmt.Errorf("can't create block cipher: %v", err)) } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) if err != nil { panic(fmt.Errorf("can't create GCM: %v", err)) } + return aesgcm.Seal(dest, nonce, plaintext, authData), nil } @@ -168,13 +182,17 @@ func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) { if err != nil { return nil, fmt.Errorf("can't create block cipher: %v", err) } + if len(nonce) != gcmNonceSize { return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce)) } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) if err != nil { return nil, fmt.Errorf("can't create GCM: %v", err) } + pt := make([]byte, 0, len(ct)) + return aesgcm.Open(pt, nonce, ct, authData) } diff --git a/p2p/discover/v5wire/crypto_test.go b/p2p/discover/v5wire/crypto_test.go index 72169b4314..18c2597abb 100644 --- a/p2p/discover/v5wire/crypto_test.go +++ b/p2p/discover/v5wire/crypto_test.go @@ -36,6 +36,7 @@ func TestVector_ECDH(t *testing.T) { publicKey = hexPubkey(crypto.S256(), "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231") want = hexutil.MustDecode("0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e") ) + result := ecdh(staticKey, publicKey) check(t, "shared-secret", result, want) } @@ -46,6 +47,7 @@ func TestVector_KDF(t *testing.T) { cdata = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000") net = newHandshakeTest() ) + defer net.close() destKey := &testKeyB.PublicKey @@ -71,10 +73,12 @@ func TestVector_IDSignature(t *testing.T) { if err != nil { t.Fatal(err) } + t.Logf("static-key = %#x", key.D) t.Logf("challenge-data = %#x", cdata) t.Logf("ephemeral-pubkey = %#x", ephkey) t.Logf("node-id-B = %#x", destID.Bytes()) + expected := "0x94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6" check(t, "id-signature", sig, hexutil.MustDecode(expected)) } @@ -87,11 +91,14 @@ func TestDeriveKeys(t *testing.T) { n2 = enode.ID{2} cdata = []byte{1, 2, 3, 4} ) + sec1 := deriveKeys(sha256.New, testKeyA, &testKeyB.PublicKey, n1, n2, cdata) sec2 := deriveKeys(sha256.New, testKeyB, &testKeyA.PublicKey, n1, n2, cdata) + if sec1 == nil || sec2 == nil { t.Fatal("key agreement failed") } + if !reflect.DeepEqual(sec1, sec2) { t.Fatalf("keys not equal:\n %+v\n %+v", sec1, sec2) } @@ -112,6 +119,7 @@ func hexPrivkey(input string) *ecdsa.PrivateKey { if err != nil { panic(err) } + return key } @@ -120,5 +128,6 @@ func hexPubkey(curve elliptic.Curve, input string) *ecdsa.PublicKey { if err != nil { panic(err) } + return key } diff --git a/p2p/discover/v5wire/encoding.go b/p2p/discover/v5wire/encoding.go index 82afefb113..816ff44153 100644 --- a/p2p/discover/v5wire/encoding.go +++ b/p2p/discover/v5wire/encoding.go @@ -173,6 +173,7 @@ func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock, pr if protocolID != nil { c.protocolID = *protocolID } + return c } @@ -187,6 +188,7 @@ func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoar msgData []byte err error ) + switch { case packet.Kind() == WhoareyouPacket: head, err = c.encodeWhoareyou(id, packet.(*Whoareyou)) @@ -203,6 +205,7 @@ func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoar head, msgData, err = c.encodeRandom(id) } } + if err != nil { return nil, Nonce{}, err } @@ -221,6 +224,7 @@ func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoar c.sc.storeSentHandshake(id, addr, challenge) } else if msgData == nil { headerData := c.buf.Bytes() + msgData, err = c.encryptMessage(session, packet, &head, headerData) if err != nil { return nil, Nonce{}, err @@ -228,6 +232,7 @@ func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoar } enc, err := c.EncodeRaw(id, head, msgData) + return enc, head.Nonce, err } @@ -242,6 +247,7 @@ func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, err // Write message data. c.buf.Write(msgdata) + return c.buf.Bytes(), nil } @@ -255,6 +261,7 @@ func (c *Codec) writeHeaders(head *Header) { // makeHeader creates a packet header. func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header { var authsize int + switch flag { case flagMessage: authsize = sizeofMessageAuthData @@ -265,10 +272,12 @@ func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header { default: panic(fmt.Errorf("BUG: invalid packet header flag %x", flag)) } + authsize += authsizeExtra if authsize > int(^uint16(0)) { panic(fmt.Errorf("BUG: auth size %d overflows uint16", authsize)) } + return Header{ StaticHeader: StaticHeader{ ProtocolID: c.protocolID, @@ -285,9 +294,11 @@ func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) { // Encode auth data. auth := messageAuthData{SrcID: c.localnode.ID()} + if _, err := crand.Read(head.Nonce[:]); err != nil { return head, nil, fmt.Errorf("can't get random data: %v", err) } + c.headbuf.Reset() binary.Write(&c.headbuf, binary.BigEndian, auth) head.AuthData = c.headbuf.Bytes() @@ -295,6 +306,7 @@ func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) { // Fill message ciphertext buffer with random bytes. c.msgctbuf = append(c.msgctbuf[:0], make([]byte, randomPacketMsgSize)...) crand.Read(c.msgctbuf) + return head, c.msgctbuf, nil } @@ -315,9 +327,11 @@ func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error IDNonce: packet.IDNonce, RecordSeq: packet.RecordSeq, } + c.headbuf.Reset() binary.Write(&c.headbuf, binary.BigEndian, auth) head.AuthData = c.headbuf.Bytes() + return head, nil } @@ -348,6 +362,7 @@ func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Who authsizeExtra = len(auth.pubkey) + len(auth.signature) + len(auth.record) head = c.makeHeader(toID, flagHandshake, authsizeExtra) ) + c.headbuf.Reset() binary.Write(&c.headbuf, binary.BigEndian, &auth.h) c.headbuf.Write(auth.signature) @@ -355,6 +370,7 @@ func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Who c.headbuf.Write(auth.record) head.AuthData = c.headbuf.Bytes() head.Nonce = nonce + return head, session, err } @@ -369,20 +385,24 @@ func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoarey if err := challenge.Node.Load((*enode.Secp256k1)(remotePubkey)); err != nil { return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient") } + ephkey, err := c.sc.ephemeralKeyGen() if err != nil { return nil, nil, fmt.Errorf("can't generate ephemeral key") } + ephpubkey := EncodePubkey(&ephkey.PublicKey) auth.pubkey = ephpubkey[:] auth.h.PubkeySize = byte(len(auth.pubkey)) // Add ID nonce signature to response. cdata := challenge.ChallengeData + idsig, err := makeIDSignature(c.sha256, c.privkey, cdata, ephpubkey[:], toID) if err != nil { return nil, nil, fmt.Errorf("can't sign: %v", err) } + auth.signature = idsig auth.h.SigSize = byte(len(auth.signature)) @@ -397,6 +417,7 @@ func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoarey if sec == nil { return nil, nil, fmt.Errorf("key derivation failed") } + return auth, sec, err } @@ -409,11 +430,13 @@ func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) { if err != nil { return Header{}, fmt.Errorf("can't generate nonce: %v", err) } + auth := messageAuthData{SrcID: c.localnode.ID()} c.buf.Reset() binary.Write(&c.buf, binary.BigEndian, &auth) head.AuthData = bytesCopy(&c.buf) head.Nonce = nonce + return head, err } @@ -421,9 +444,11 @@ func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData [] // Encode message plaintext. c.msgbuf.Reset() c.msgbuf.WriteByte(p.Kind()) + if err := rlp.Encode(&c.msgbuf, p); err != nil { return nil, err } + messagePT := c.msgbuf.Bytes() // Encrypt into message ciphertext buffer. @@ -431,6 +456,7 @@ func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData [] if err == nil { c.msgctbuf = messageCT } + return messageCT, err } @@ -444,6 +470,7 @@ func (c *Codec) Decode(inputData []byte, addr string) (src enode.ID, n *enode.No input := c.decbuf // Unmask the static header. var head Header + copy(head.IV[:], input[:sizeofMaskingIV]) mask := head.mask(c.localnode.ID()) staticHeader := input[sizeofMaskingIV:sizeofStaticPacketData] @@ -452,6 +479,7 @@ func (c *Codec) Decode(inputData []byte, addr string) (src enode.ID, n *enode.No // Decode and verify the static header. c.reader.Reset(staticHeader) binary.Read(&c.reader, binary.BigEndian, &head.StaticHeader) + remainingInput := len(input) - sizeofStaticPacketData if err := head.checkValid(remainingInput, c.protocolID); err != nil { @@ -471,6 +499,7 @@ func (c *Codec) Decode(inputData []byte, addr string) (src enode.ID, n *enode.No // Decode auth part and message. headerData := input[:authDataEnd] msgData := input[authDataEnd:] + switch head.Flag { case flagWhoareyou: p, err = c.decodeWhoareyou(&head, headerData) @@ -481,6 +510,7 @@ func (c *Codec) Decode(inputData []byte, addr string) (src enode.ID, n *enode.No default: err = errInvalidFlag } + return head.src, n, p, err } @@ -489,7 +519,9 @@ func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) if len(head.AuthData) != sizeofWhoareyouAuthData { return nil, fmt.Errorf("invalid auth size %d for WHOAREYOU", len(head.AuthData)) } + var auth whoareyouAuthData + c.reader.Reset(head.AuthData) binary.Read(&c.reader, binary.BigEndian, &auth) p := &Whoareyou{ @@ -499,6 +531,7 @@ func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) ChallengeData: make([]byte, len(headerData)), } copy(p.ChallengeData, headerData) + return p, nil } @@ -519,6 +552,7 @@ func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData // Handshake OK, drop the challenge and store the new session keys. c.sc.storeNewSession(auth.h.SrcID, fromAddr, session) c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return node, msg, nil } @@ -540,6 +574,7 @@ func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, a // Verify ID nonce signature. sig := auth.signature cdata := challenge.ChallengeData + err = verifyIDSignature(c.sha256, sig, n, cdata, auth.pubkey, c.localnode.ID()) if err != nil { return nil, auth, nil, err @@ -552,6 +587,7 @@ func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, a // Derive session keys. session := deriveKeys(sha256.New, c.privkey, ephkey, auth.h.SrcID, c.localnode.ID(), cdata) session = session.keysFlipped() + return n, auth, session, nil } @@ -561,6 +597,7 @@ func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, e if len(head.AuthData) < sizeofHandshakeAuthData { return auth, fmt.Errorf("header authsize %d too low for handshake", head.AuthSize) } + c.reader.Reset(head.AuthData) binary.Read(&c.reader, binary.BigEndian, &auth.h) head.src = auth.h.SrcID @@ -572,12 +609,15 @@ func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, e keyOffset = int(auth.h.SigSize) recOffset = keyOffset + int(auth.h.PubkeySize) ) + if len(vardata) < sigAndKeySize { return auth, errTooShort } + auth.signature = vardata[:keyOffset] auth.pubkey = vardata[keyOffset:recOffset] auth.record = vardata[recOffset:] + return auth, nil } @@ -586,25 +626,31 @@ func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, e // latest sequence number. func (c *Codec) decodeHandshakeRecord(local *enode.Node, wantID enode.ID, remote []byte) (*enode.Node, error) { node := local + if len(remote) > 0 { var record enr.Record if err := rlp.DecodeBytes(remote, &record); err != nil { return nil, err } + if local == nil || local.Seq() < record.Seq() { n, err := enode.New(enode.ValidSchemes, &record) if err != nil { return nil, fmt.Errorf("invalid node record: %v", err) } + if n.ID() != wantID { return nil, fmt.Errorf("record in handshake has wrong ID: %v", n.ID()) } + node = n } } + if node == nil { return nil, errNoRecord } + return node, nil } @@ -613,7 +659,9 @@ func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData if len(head.AuthData) != sizeofMessageAuthData { return nil, fmt.Errorf("invalid auth size %d for message packet", len(head.AuthData)) } + var auth messageAuthData + c.reader.Reset(head.AuthData) binary.Read(&c.reader, binary.BigEndian, &auth) head.src = auth.SrcID @@ -626,6 +674,7 @@ func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData // It didn't work. Start the handshake since this is an ordinary message packet. return &Unknown{Nonce: head.Nonce}, nil } + return msg, err } @@ -634,9 +683,11 @@ func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet if err != nil { return nil, errMessageDecrypt } + if len(msgdata) == 0 { return nil, errMessageTooShort } + return DecodeMessage(msgdata[0], msgdata[1:]) } @@ -646,15 +697,19 @@ func (h *StaticHeader) checkValid(packetLen int, protocolID [6]byte) error { if h.ProtocolID != protocolID { return errInvalidHeader } + if h.Version < minVersion { return errMinVersion } + if h.Flag != flagWhoareyou && packetLen < minMessageSize { return errMsgTooShort } + if int(h.AuthSize) > packetLen { return errAuthSize } + return nil } @@ -664,11 +719,13 @@ func (h *Header) mask(destID enode.ID) cipher.Stream { if err != nil { panic("can't create cipher") } + return cipher.NewCTR(block, h.IV[:]) } func bytesCopy(r *bytes.Buffer) []byte { b := make([]byte, r.Len()) copy(b, r.Bytes()) + return b } diff --git a/p2p/discover/v5wire/encoding_test.go b/p2p/discover/v5wire/encoding_test.go index a5387311a5..40f707e7f2 100644 --- a/p2p/discover/v5wire/encoding_test.go +++ b/p2p/discover/v5wire/encoding_test.go @@ -54,12 +54,15 @@ func TestMinSizes(t *testing.T) { gcmTagSize = 16 emptyMsg = sizeofMessageAuthData + gcmTagSize ) + t.Log("static header size", sizeofStaticPacketData) t.Log("whoareyou size", sizeofStaticPacketData+sizeofWhoareyouAuthData) t.Log("empty msg size", sizeofStaticPacketData+emptyMsg) + if want := emptyMsg; minMessageSize != want { t.Fatalf("wrong minMessageSize %d, want %d", minMessageSize, want) } + if sizeofMessageAuthData+randomPacketMsgSize < minMessageSize { t.Fatalf("randomPacketMsgSize %d too small", randomPacketMsgSize) } @@ -68,6 +71,7 @@ func TestMinSizes(t *testing.T) { // This test checks the basic handshake flow where A talks to B and A has no secrets. func TestHandshake(t *testing.T) { t.Parallel() + net := newHandshakeTest() defer net.close() @@ -87,6 +91,7 @@ func TestHandshake(t *testing.T) { // A -> B FINDNODE (handshake packet) findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) net.nodeB.expectDecode(t, FindnodeMsg, findnode) + if len(net.nodeB.c.sc.handshakes) > 0 { t.Fatalf("node B didn't remove handshake from challenge map") } @@ -99,6 +104,7 @@ func TestHandshake(t *testing.T) { // This test checks that handshake attempts are removed within the timeout. func TestHandshake_timeout(t *testing.T) { t.Parallel() + net := newHandshakeTest() defer net.close() @@ -124,6 +130,7 @@ func TestHandshake_timeout(t *testing.T) { // This test checks handshake behavior when no record is sent in the auth response. func TestHandshake_norecord(t *testing.T) { t.Parallel() + net := newHandshakeTest() defer net.close() @@ -136,6 +143,7 @@ func TestHandshake_norecord(t *testing.T) { if nodeA.Seq() == 0 { t.Fatal("need non-zero sequence number") } + challenge := &Whoareyou{ Nonce: resp.(*Unknown).Nonce, IDNonce: testIDnonce, @@ -158,6 +166,7 @@ func TestHandshake_norecord(t *testing.T) { // anything about A. func TestHandshake_rekey(t *testing.T) { t.Parallel() + net := newHandshakeTest() defer net.close() @@ -181,6 +190,7 @@ func TestHandshake_rekey(t *testing.T) { if !bytes.Equal(sa.writeKey, session.writeKey) || !bytes.Equal(sa.readKey, session.readKey) { t.Fatal("node A stored keys too early") } + if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil { t.Fatal("node B stored keys too early") } @@ -197,6 +207,7 @@ func TestHandshake_rekey(t *testing.T) { // In this test A and B have different keys before the handshake. func TestHandshake_rekey2(t *testing.T) { t.Parallel() + net := newHandshakeTest() defer net.close() @@ -208,6 +219,7 @@ func TestHandshake_rekey2(t *testing.T) { readKey: []byte("CCCCCCCCCCCCCCCC"), writeKey: []byte("DDDDDDDDDDDDDDDD"), } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA) net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB) @@ -231,6 +243,7 @@ func TestHandshake_rekey2(t *testing.T) { func TestHandshake_BadHandshakeAttack(t *testing.T) { t.Parallel() + net := newHandshakeTest() defer net.close() @@ -271,6 +284,7 @@ func TestHandshake_BadHandshakeAttack(t *testing.T) { // This test checks some malformed packets. func TestDecodeErrorsV5(t *testing.T) { t.Parallel() + net := newHandshakeTest() defer net.close() @@ -282,7 +296,6 @@ func TestDecodeErrorsV5(t *testing.T) { b = make([]byte, 63) net.nodeA.expectDecodeErr(t, errInvalidHeader, b) - // TODO some more tests would be nice :) // - check invalid authdata sizes // - check invalid handshake data sizes @@ -320,6 +333,7 @@ func TestTestVectorsV5(t *testing.T) { challenge *Whoareyou // handshake challenge passed to encoder prep func(*handshakeTest) // called before encode/decode } + tests := []testVectorTest{ { name: "v5.1-whoareyou", @@ -387,12 +401,14 @@ func TestTestVectorsV5(t *testing.T) { } file := filepath.Join("testdata", test.name+".txt") + if *writeTestVectorsFlag { // Encode the packet. d, nonce := net.nodeA.encodeWithChallenge(t, net.nodeB, test.challenge, test.packet) comment := testVectorComment(net, test.packet, test.challenge, nonce) writeTestVector(file, comment, d) } + enc := hexFile(file) net.nodeB.expectDecode(t, test.packet.Kind(), enc) }) @@ -411,6 +427,7 @@ func testVectorComment(net *handshakeTest, p Packet, challenge *Whoareyou, nonce fmt.Fprintf(o, "src-node-id = %#x\n", net.nodeA.id().Bytes()) fmt.Fprintf(o, "dest-node-id = %#x\n", net.nodeB.id().Bytes()) + switch p := p.(type) { case *Whoareyou: // WHOAREYOU packet. @@ -420,6 +437,7 @@ func testVectorComment(net *handshakeTest, p Packet, challenge *Whoareyou, nonce fmt.Fprintf(o, "read-key = %#x\n", net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()).writeKey) fmt.Fprintf(o, "ping.req-id = %#x\n", p.ReqID) fmt.Fprintf(o, "ping.enr-seq = %d\n", p.ENRSeq) + if challenge != nil { // Handshake message packet. fmt.Fprint(o, "\nhandshake inputs:\n\n") @@ -430,6 +448,7 @@ func testVectorComment(net *handshakeTest, p Packet, challenge *Whoareyou, nonce default: panic(fmt.Errorf("unhandled packet type %T", p)) } + return o.String() } @@ -443,17 +462,21 @@ func BenchmarkV5_DecodeHandshakePingSecp256k1(b *testing.B) { challenge = &Whoareyou{Node: net.nodeB.n()} message = &Ping{ReqID: []byte("reqid")} ) + enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), "", message, challenge) if err != nil { b.Fatal("can't encode handshake packet") } + challenge.Node = nil // force ENR signature verification in decoder + b.ResetTimer() input := make([]byte, len(enc)) for i := 0; i < b.N; i++ { copy(input, enc) net.nodeB.c.sc.storeSentHandshake(idA, "", challenge) + _, _, _, err := net.nodeB.c.Decode(input, "") if err != nil { b.Fatal(err) @@ -474,15 +497,18 @@ func BenchmarkV5_DecodePing(b *testing.B) { net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped()) addrB := net.nodeA.addr() ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5} + enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil) if err != nil { b.Fatalf("can't encode: %v", err) } + b.ResetTimer() input := make([]byte, len(enc)) for i := 0; i < b.N; i++ { copy(input, enc) + _, _, packet, _ := net.nodeB.c.Decode(input, addrB) if _, ok := packet.(*Ping); !ok { b.Fatalf("wrong packet type %T", packet) @@ -506,6 +532,7 @@ func newHandshakeTest() *handshakeTest { t := new(handshakeTest) t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock, DefaultProtocolID) t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock, DefaultProtocolID) + return t } @@ -531,6 +558,7 @@ func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNo // Copy challenge and add destination node. This avoids sharing 'c' among the two codecs. var challenge *Whoareyou + if c != nil { challengeCopy := *c challenge = &challengeCopy @@ -541,7 +569,9 @@ func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNo if err != nil { t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) } + t.Logf("(%s) -> (%s) %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.Name(), hex.Dump(enc)) + return enc, nonce } @@ -552,15 +582,19 @@ func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) Pac if err != nil { t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) } + t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec)) + if dec.Kind() != ptype { t.Fatalf("expected packet type %d, got %d", ptype, dec.Kind()) } + return dec } func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) { t.Helper() + if _, err := n.decode(p); !errors.Is(err, wantErr) { t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr)) } @@ -593,11 +627,13 @@ func hexFile(file string) []byte { // Gather hex data, ignore comments. var text []byte + for _, line := range bytes.Split(fileContent, []byte("\n")) { line = bytes.TrimSpace(line) if len(line) > 0 && line[0] == '#' { continue } + text = append(text, line...) } @@ -605,10 +641,12 @@ func hexFile(file string) []byte { if bytes.HasPrefix(text, []byte("0x")) { text = text[2:] } + data := make([]byte, hex.DecodedLen(len(text))) if _, err := hex.Decode(data, text); err != nil { panic("invalid hex in " + file) } + return data } @@ -624,8 +662,10 @@ func writeTestVector(file, comment string, data []byte) { for _, line := range strings.Split(strings.TrimSpace(comment), "\n") { fmt.Fprintf(fd, "# %s\n", line) } + fmt.Fprintln(fd) } + for len(data) > 0 { var chunk []byte if len(data) < 32 { @@ -633,6 +673,7 @@ func writeTestVector(file, comment string, data []byte) { } else { chunk = data[:32] } + data = data[len(chunk):] fmt.Fprintf(fd, "%x\n", chunk) } diff --git a/p2p/discover/v5wire/msg.go b/p2p/discover/v5wire/msg.go index afc7bb54f9..48512a8130 100644 --- a/p2p/discover/v5wire/msg.go +++ b/p2p/discover/v5wire/msg.go @@ -123,6 +123,7 @@ type ( // DecodeMessage decodes the message body of a packet. func DecodeMessage(ptype byte, body []byte) (Packet, error) { var dec Packet + switch ptype { case PingMsg: dec = new(Ping) @@ -139,12 +140,15 @@ func DecodeMessage(ptype byte, body []byte) (Packet, error) { default: return nil, fmt.Errorf("unknown packet type %d", ptype) } + if err := rlp.DecodeBytes(body, dec); err != nil { return nil, err } + if dec.RequestID() != nil && len(dec.RequestID()) > 8 { return nil, ErrInvalidReqID } + return dec, nil } diff --git a/p2p/discover/v5wire/session.go b/p2p/discover/v5wire/session.go index 862c21fcee..8035f3e158 100644 --- a/p2p/discover/v5wire/session.go +++ b/p2p/discover/v5wire/session.go @@ -75,6 +75,7 @@ func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache { func generateNonce(counter uint32) (n Nonce, err error) { binary.BigEndian.PutUint32(n[:4], counter) _, err = crand.Read(n[4:]) + return n, err } @@ -100,6 +101,7 @@ func (sc *SessionCache) readKey(id enode.ID, addr string) []byte { if s := sc.session(id, addr); s != nil { return s.readKey } + return nil } diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go index 8f1c221b80..0ba9bce3d1 100644 --- a/p2p/dnsdisc/client.go +++ b/p2p/dnsdisc/client.go @@ -69,27 +69,35 @@ func (cfg Config) withDefaults() Config { defaultRateLimit = 3 defaultCache = 1000 ) + if cfg.Timeout == 0 { cfg.Timeout = defaultTimeout } + if cfg.RecheckInterval == 0 { cfg.RecheckInterval = defaultRecheck } + if cfg.CacheLimit == 0 { cfg.CacheLimit = defaultCache } + if cfg.RateLimit == 0 { cfg.RateLimit = defaultRateLimit } + if cfg.ValidSchemes == nil { cfg.ValidSchemes = enode.ValidSchemes } + if cfg.Resolver == nil { cfg.Resolver = new(net.Resolver) } + if cfg.Logger == nil { cfg.Logger = log.Root() } + return cfg } @@ -97,6 +105,7 @@ func (cfg Config) withDefaults() Config { func NewClient(cfg Config) *Client { cfg = cfg.withDefaults() rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10) + return &Client{ cfg: cfg, entries: lru.NewCache[string, entry](cfg.CacheLimit), @@ -111,12 +120,16 @@ func (c *Client) SyncTree(url string) (*Tree, error) { if err != nil { return nil, fmt.Errorf("invalid enrtree URL: %v", err) } + ct := newClientTree(c, new(linkCache), le) t := &Tree{entries: make(map[string]entry)} + if err := ct.syncAll(t.entries); err != nil { return nil, err } + t.root = ct.root + return t, nil } @@ -129,6 +142,7 @@ func (c *Client) NewIterator(urls ...string) (enode.Iterator, error) { return nil, err } } + return it, nil } @@ -137,16 +151,20 @@ func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, er e, err, _ := c.singleflight.Do(loc.str, func() (interface{}, error) { txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain) c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err) + if err != nil { return rootEntry{}, err } + for _, txt := range txts { if strings.HasPrefix(txt, rootPrefix) { return parseAndVerifyRoot(txt, loc) } } + return rootEntry{}, nameError{loc.domain, errNoRoot} }) + return e.(rootEntry), err } @@ -155,9 +173,11 @@ func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) { if err != nil { return e, err } + if !e.verifySignature(loc.pubkey) { return e, entryError{typ: "root", err: errInvalidSig} } + return e, nil } @@ -170,6 +190,7 @@ func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, if err := c.ratelimit.Wait(ctx); err != nil { return nil, err } + cacheKey := truncateHash(hash) if e, ok := c.entries.Get(cacheKey); ok { return e, nil @@ -180,10 +201,13 @@ func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, if err != nil { return nil, err } + c.entries.Add(cacheKey, e) + return e, nil }) e, _ := ei.(entry) + return e, err } @@ -193,24 +217,30 @@ func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry if err != nil { return nil, fmt.Errorf("invalid base32 hash") } + name := hash + "." + domain txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain) c.cfg.Logger.Trace("DNS discovery lookup", "name", name, "err", err) + if err != nil { return nil, err } + for _, txt := range txts { e, err := parseEntry(txt, c.cfg.ValidSchemes) if errors.Is(err, errUnknownEntry) { continue } + if !bytes.HasPrefix(crypto.Keccak256([]byte(txt)), wantHash) { err = nameError{name, errHashMismatch} } else if err != nil { err = nameError{name, err} } + return e, err } + return nil, nameError{name, errNoEntry} } @@ -231,6 +261,7 @@ type randomIterator struct { func (c *Client) newRandomIterator() *randomIterator { ctx, cancel := context.WithCancel(context.Background()) + return &randomIterator{ c: c, ctx: ctx, @@ -265,7 +296,9 @@ func (it *randomIterator) addTree(url string) error { if err != nil { return fmt.Errorf("invalid enrtree URL: %v", err) } + it.lc.addLink("", le.str) + return nil } @@ -276,14 +309,18 @@ func (it *randomIterator) nextNode() *enode.Node { if ct == nil { return nil } + n, err := ct.syncRandom(it.ctx) if err != nil { if errors.Is(err, it.ctx.Err()) { return nil // context canceled. } + it.c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err) + continue } + if n != nil { return n } @@ -309,6 +346,7 @@ func (it *randomIterator) pickTree() *clientTree { for { canSync, trees := it.syncableTrees() + switch { case canSync: // Pick a random tree. @@ -341,16 +379,20 @@ func (it *randomIterator) syncableTrees() (canSync bool, trees []*clientTree) { it.disabledList = append(it.disabledList, ct) } } + 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 { @@ -361,6 +403,7 @@ func (it *randomIterator) waitForRootUpdates(trees []*clientTree) bool { 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 { diff --git a/p2p/dnsdisc/client_test.go b/p2p/dnsdisc/client_test.go index 30b6b12c2a..b5859e1c9b 100644 --- a/p2p/dnsdisc/client_test.go +++ b/p2p/dnsdisc/client_test.go @@ -51,6 +51,7 @@ func TestClientSyncTree(t *testing.T) { "H4FHT4B454P6UXFD7JCYQ5PWDY.n": nodes[1], "MHTDO6TMUBRIA2XWG5LUDACK24.n": nodes[2], } + var ( wantNodes = sortByID(parseNodes(nodes)) wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"} @@ -89,7 +90,6 @@ func TestClientSyncTreeBadNode(t *testing.T) { // url, _ := tree.Sign(signingKeyForTesting, "n") // fmt.Println(url) // fmt.Printf("%#v\n", tree.ToTXT("n")) - r := mapResolver{ "n": "enrtree-root:v1 e=INDMVBZEEQ4ESVYAKGIYU74EAA l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=3 sig=Vl3AmunLur0JZ3sIyJPSH6A3Vvdp4F40jWQeCmkIhmcgwE4VC5U9wpK8C_uL_CMY29fd6FAhspRvq2z_VysTLAA", "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", @@ -98,6 +98,7 @@ func TestClientSyncTreeBadNode(t *testing.T) { c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) _, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n") wantErr := nameError{name: "INDMVBZEEQ4ESVYAKGIYU74EAA.n", err: entryError{typ: "enr", err: errInvalidENR}} + if err != wantErr { t.Fatalf("expected sync error %q, got %q", wantErr, err) } @@ -117,6 +118,7 @@ func TestIterator(t *testing.T) { Logger: testlog.Logger(t, log.LvlTrace), RateLimit: 500, }) + it, err := c.NewIterator(url) if err != nil { t.Fatal(err) @@ -128,12 +130,14 @@ func TestIterator(t *testing.T) { func TestIteratorCloseWithoutNext(t *testing.T) { tree1, url1 := makeTestTree("t1", nil, nil) c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))}) + it, err := c.NewIterator(url1) if err != nil { t.Fatal(err) } it.Close() + ok := it.Next() if ok { t.Fatal("Next returned true after Close") @@ -149,6 +153,7 @@ func TestIteratorClose(t *testing.T) { ) c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))}) + it, err := c.NewIterator(url1) if err != nil { t.Fatal(err) @@ -181,6 +186,7 @@ func TestIteratorLinks(t *testing.T) { Logger: testlog.Logger(t, log.LvlTrace), RateLimit: 500, }) + it, err := c.NewIterator(url2) if err != nil { t.Fatal(err) @@ -204,8 +210,10 @@ func TestIteratorNodeUpdates(t *testing.T) { RateLimit: 500, }) ) + c.clock = clock tree1, url := makeTestTree("n", nodes[:25], nil) + it, err := c.NewIterator(url) if err != nil { t.Fatal(err) @@ -218,6 +226,7 @@ func TestIteratorNodeUpdates(t *testing.T) { // Ensure RandomNode returns the new nodes after the tree is updated. updateSomeNodes(keys, nodes) tree2, _ := makeTestTree("n", nodes, nil) + resolver.clear() resolver.add(tree2.ToTXT("n")) t.Log("tree updated") @@ -245,8 +254,10 @@ func TestIteratorRootRecheckOnFail(t *testing.T) { CacheLimit: 1, }) ) + c.clock = clock tree1, url := makeTestTree("n", nodes[:25], nil) + it, err := c.NewIterator(url) if err != nil { t.Fatal(err) @@ -259,6 +270,7 @@ func TestIteratorRootRecheckOnFail(t *testing.T) { // Ensure RandomNode returns the new nodes after the tree is updated. updateSomeNodes(keys, nodes) tree2, _ := makeTestTree("n", nodes, nil) + resolver.clear() resolver.add(tree2.ToTXT("n")) t.Log("tree updated") @@ -280,17 +292,21 @@ func TestIteratorEmptyTree(t *testing.T) { 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, 1) + it, err := c.NewIterator(url) if err != nil { t.Fatal(err) } + go func() { it.Next() node <- it.Node() @@ -341,6 +357,7 @@ func TestIteratorLinkUpdates(t *testing.T) { RateLimit: 500, }) ) + c.clock = clock tree3, url3 := makeTestTree("t3", nodes[20:30], nil) tree2, url2 := makeTestTree("t2", nodes[10:20], nil) @@ -384,17 +401,21 @@ func checkIterator(t *testing.T, it enode.Iterator, wantNodes []*enode.Node) { maxCalls = len(wantNodes) * 3 calls = 0 ) + for _, n := range wantNodes { want[n.ID()] = n } + for ; len(want) > 0 && calls < maxCalls; calls++ { if !it.Next() { t.Fatalf("Next returned false (call %d)", calls) } + n := it.Node() delete(want, n.ID()) } t.Logf("checkIterator called Next %d times to find %d nodes", calls, len(wantNodes)) + for _, n := range want { t.Errorf("iterator didn't discover node %v", n.ID()) } @@ -417,28 +438,35 @@ func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, st // testKeys creates deterministic private keys for testing. func testKeys(n int) []*ecdsa.PrivateKey { keys := make([]*ecdsa.PrivateKey, n) + for i := 0; i < n; i++ { key, err := crypto.GenerateKey() if err != nil { panic("can't generate key: " + err.Error()) } + keys[i] = key } + return keys } func testNodes(keys []*ecdsa.PrivateKey) []*enode.Node { nodes := make([]*enode.Node, len(keys)) + for i, key := range keys { record := new(enr.Record) record.SetSeq(uint64(i)) enode.SignV4(record, key) + n, err := enode.New(enode.ValidSchemes, record) if err != nil { panic(err) } + nodes[i] = n } + return nodes } @@ -449,6 +477,7 @@ func newMapResolver(maps ...map[string]string) mapResolver { for _, m := range maps { mr.add(m) } + return mr } @@ -468,6 +497,7 @@ func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, err if record, ok := mr[name]; ok { return []string{record}, nil } + return nil, errors.New("not found") } diff --git a/p2p/dnsdisc/error.go b/p2p/dnsdisc/error.go index 39955cabff..71a704a977 100644 --- a/p2p/dnsdisc/error.go +++ b/p2p/dnsdisc/error.go @@ -50,6 +50,7 @@ func (err nameError) Error() string { if ee, ok := err.err.(entryError); ok { return fmt.Sprintf("invalid %s entry at %s: %v", ee.typ, err.name, ee.err) } + return err.name + ": " + err.err.Error() } diff --git a/p2p/dnsdisc/sync.go b/p2p/dnsdisc/sync.go index 073547c90d..9dcb24727a 100644 --- a/p2p/dnsdisc/sync.go +++ b/p2p/dnsdisc/sync.go @@ -56,12 +56,15 @@ func (ct *clientTree) syncAll(dest map[string]entry) error { if err := ct.updateRoot(context.Background()); err != nil { return err } + if err := ct.links.resolveAll(dest); err != nil { return err } + if err := ct.enrs.resolveAll(dest); err != nil { return err } + return nil } @@ -86,6 +89,7 @@ func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error) err := ct.syncNextLink(ctx) return nil, err } + ct.gcLinks() // Sync next random entry in ENR tree. Once every node has been visited, we simply @@ -94,6 +98,7 @@ func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error) if ct.enrs.done() { ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false) } + return ct.syncNextRandomENR(ctx) } @@ -111,36 +116,44 @@ func (ct *clientTree) gcLinks() { if !ct.links.done() || ct.root.lroot == ct.linkGCRoot { return } + ct.lc.resetLinks(ct.loc.str, ct.curLinks) ct.linkGCRoot = ct.root.lroot } func (ct *clientTree) syncNextLink(ctx context.Context) error { hash := ct.links.missing[0] + e, err := ct.links.resolveNext(ctx, hash) if err != nil { return err } + ct.links.missing = ct.links.missing[1:] if dest, ok := e.(*linkEntry); ok { ct.lc.addLink(ct.loc.str, dest.str) ct.curLinks[dest.str] = struct{}{} } + return nil } func (ct *clientTree) syncNextRandomENR(ctx context.Context) (*enode.Node, error) { index := rand.Intn(len(ct.enrs.missing)) hash := ct.enrs.missing[index] + e, err := ct.enrs.resolveNext(ctx, hash) if err != nil { return nil, err } + ct.enrs.missing = removeHash(ct.enrs.missing, index) + if ee, ok := e.(*enrEntry); ok { return ee.node, nil } + return nil, nil } @@ -153,11 +166,13 @@ func removeHash(h []string, index int) []string { if len(h) == 1 { return nil } + last := len(h) - 1 if index < last { h[index] = h[last] h[last] = "" } + return h[:last] } @@ -168,13 +183,16 @@ func (ct *clientTree) updateRoot(ctx context.Context) error { } ct.lastRootCheck = ct.c.clock.Now() + ctx, cancel := context.WithTimeout(ctx, ct.c.cfg.Timeout) defer cancel() + root, err := ct.c.resolveRoot(ctx, ct.loc) if err != nil { ct.rootFailCount++ return err } + ct.root = &root ct.rootFailCount = 0 ct.leafFailCount = 0 @@ -184,9 +202,11 @@ func (ct *clientTree) updateRoot(ctx context.Context) error { ct.links = newSubtreeSync(ct.c, ct.loc, root.lroot, true) ct.curLinks = make(map[string]struct{}) } + if ct.enrs == nil || root.eroot != ct.enrs.root { ct.enrs = newSubtreeSync(ct.c, ct.loc, root.eroot, false) } + return nil } @@ -194,6 +214,7 @@ func (ct *clientTree) updateRoot(ctx context.Context) error { func (ct *clientTree) rootUpdateDue() bool { tooManyFailures := ct.leafFailCount > rootRecheckFailCount scheduledCheck := ct.c.clock.Now() >= ct.nextScheduledRootCheck() + return ct.root == nil || tooManyFailures || scheduledCheck } @@ -206,6 +227,7 @@ func (ct *clientTree) nextScheduledRootCheck() mclock.AbsTime { // Returns true if the timeout passed, false if sync was canceled. func (ct *clientTree) slowdownRootUpdate(ctx context.Context) bool { var delay time.Duration + switch { case ct.rootFailCount > 20: delay = 10 * time.Second @@ -214,6 +236,7 @@ func (ct *clientTree) slowdownRootUpdate(ctx context.Context) bool { default: return true } + timeout := ct.c.clock.NewTimer(delay) defer timeout.Stop() select { @@ -247,13 +270,17 @@ func (ts *subtreeSync) resolveAll(dest map[string]entry) error { hash := ts.missing[0] ctx, cancel := context.WithTimeout(context.Background(), ts.c.cfg.Timeout) e, err := ts.resolveNext(ctx, hash) + cancel() + if err != nil { return err } + dest[hash] = e ts.missing = ts.missing[1:] } + return nil } @@ -262,20 +289,24 @@ func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, err if err != nil { return nil, err } + switch e := e.(type) { case *enrEntry: 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...) } + return e, nil } @@ -297,9 +328,11 @@ func (lc *linkCache) addLink(from, to string) { if lc.backrefs == nil { lc.backrefs = make(map[string]map[string]struct{}) } + if _, ok := lc.backrefs[to]; !ok { lc.backrefs[to] = make(map[string]struct{}) } + lc.backrefs[to][from] = struct{}{} lc.changed = true } @@ -315,11 +348,15 @@ func (lc *linkCache) resetLinks(from string, keep map[string]struct{}) { if _, ok := keep[r]; ok { continue } + if _, ok := refs[item]; !ok { continue } + lc.changed = true + delete(refs, item) + if len(refs) == 0 { delete(lc.backrefs, r) stk = append(stk, r) diff --git a/p2p/dnsdisc/sync_test.go b/p2p/dnsdisc/sync_test.go index ce1cb90bae..0b754f141f 100644 --- a/p2p/dnsdisc/sync_test.go +++ b/p2p/dnsdisc/sync_test.go @@ -27,14 +27,18 @@ func TestLinkCache(t *testing.T) { // Check adding links. lc.addLink("1", "2") + if !lc.changed { t.Error("changed flag not set") } + lc.changed = false lc.addLink("1", "2") + if lc.changed { t.Error("changed flag set after adding link that's already present") } + lc.addLink("2", "3") lc.addLink("3", "1") lc.addLink("2", "4") @@ -43,14 +47,17 @@ func TestLinkCache(t *testing.T) { if !lc.isReferenced("3") { t.Error("3 not referenced") } + if lc.isReferenced("6") { t.Error("6 is referenced") } lc.resetLinks("1", nil) + if !lc.changed { t.Error("changed flag not set") } + if len(lc.backrefs) != 0 { t.Logf("%+v", lc) t.Error("reference maps should be empty") @@ -65,7 +72,9 @@ func TestLinkCacheRandom(t *testing.T) { // Create random links. var lc linkCache + var remove []string + for i := 0; i < 100; i++ { a, b := tags[rand.Intn(len(tags))], tags[rand.Intn(len(tags))] lc.addLink(a, b) @@ -76,6 +85,7 @@ func TestLinkCacheRandom(t *testing.T) { for _, s := range remove { lc.resetLinks(s, nil) } + if len(lc.backrefs) != 0 { t.Logf("%+v", lc) t.Error("reference maps should be empty") diff --git a/p2p/dnsdisc/tree.go b/p2p/dnsdisc/tree.go index a3f426e428..f76445ef16 100644 --- a/p2p/dnsdisc/tree.go +++ b/p2p/dnsdisc/tree.go @@ -42,13 +42,16 @@ type Tree struct { // Sign signs the tree with the given private key and sets the sequence number. func (t *Tree) Sign(key *ecdsa.PrivateKey, domain string) (url string, err error) { root := *t.root + sig, err := crypto.Sign(root.sigHash(), key) if err != nil { return "", err } + root.sig = sig t.root = &root link := newLinkEntry(domain, &key.PublicKey) + return link.String(), nil } @@ -59,12 +62,16 @@ func (t *Tree) SetSignature(pubkey *ecdsa.PublicKey, signature string) error { if err != nil || len(sig) != crypto.SignatureLength { return errInvalidSig } + root := *t.root root.sig = sig + if !root.verifySignature(pubkey) { return errInvalidSig } + t.root = &root + return nil } @@ -81,35 +88,42 @@ func (t *Tree) Signature() string { // ToTXT returns all DNS TXT records required for the tree. func (t *Tree) ToTXT(domain string) map[string]string { records := map[string]string{domain: t.root.String()} + for _, e := range t.entries { sd := subdomain(e) if domain != "" { sd = sd + "." + domain } + records[sd] = e.String() } + return records } // Links returns all links contained in the tree. func (t *Tree) Links() []string { var links []string + for _, e := range t.entries { if le, ok := e.(*linkEntry); ok { links = append(links, le.String()) } } + return links } // Nodes returns all nodes contained in the tree. func (t *Tree) Nodes() []*enode.Node { var nodes []*enode.Node + for _, e := range t.entries { if ee, ok := e.(*enrEntry); ok { nodes = append(nodes, ee.node) } } + return nodes } @@ -157,6 +171,7 @@ func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) { copy(records, nodes) sortByID(records) + for _, n := range records { if len(n.Record().Signature()) == 0 { return nil, fmt.Errorf("can't add node %v: unsigned node record", n.ID()) @@ -168,12 +183,15 @@ func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) { for i, r := range records { enrEntries[i] = &enrEntry{r} } + linkEntries := make([]entry, len(links)) + for i, l := range links { le, err := parseLink(l) if err != nil { return nil, err } + linkEntries[i] = le } @@ -184,6 +202,7 @@ func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) { lroot := t.build(linkEntries) t.entries[subdomain(lroot)] = lroot t.root = &rootEntry{seq: seq, eroot: subdomain(eroot), lroot: subdomain(lroot)} + return t, nil } @@ -191,25 +210,32 @@ func (t *Tree) build(entries []entry) entry { if len(entries) == 1 { return entries[0] } + if len(entries) <= maxChildren { hashes := make([]string, len(entries)) for i, e := range entries { hashes[i] = subdomain(e) t.entries[hashes[i]] = e } + return &branchEntry{hashes} } + var subtrees []entry + for len(entries) > 0 { n := maxChildren if len(entries) < n { n = len(entries) } + sub := t.build(entries[:n]) entries = entries[n:] + subtrees = append(subtrees, sub) t.entries[subdomain(sub)] = sub } + return t.build(subtrees) } @@ -217,6 +243,7 @@ func sortByID(nodes []*enode.Node) []*enode.Node { sort.Slice(nodes, func(i, j int) bool { return bytes.Compare(nodes[i].ID().Bytes(), nodes[j].ID().Bytes()) < 0 }) + return nodes } @@ -263,6 +290,7 @@ const ( func subdomain(e entry) string { h := sha3.NewLegacyKeccak256() io.WriteString(h, e.String()) + return b32format.EncodeToString(h.Sum(nil)[:16]) } @@ -273,12 +301,14 @@ func (e *rootEntry) String() string { func (e *rootEntry) sigHash() []byte { h := sha3.NewLegacyKeccak256() fmt.Fprintf(h, rootPrefix+" e=%s l=%s seq=%d", e.eroot, e.lroot, e.seq) + return h.Sum(nil) } func (e *rootEntry) verifySignature(pubkey *ecdsa.PublicKey) bool { sig := e.sig[:crypto.RecoveryIDOffset] // remove recovery id enckey := crypto.FromECDSAPub(pubkey) + return crypto.VerifySignature(enckey, e.sigHash(), sig) } @@ -297,6 +327,7 @@ func (e *linkEntry) String() string { func newLinkEntry(domain string, pubkey *ecdsa.PublicKey) *linkEntry { key := b32format.EncodeToString(crypto.CompressPubkey(pubkey)) str := key + "@" + domain + return &linkEntry{str, domain, pubkey} } @@ -317,17 +348,21 @@ func parseEntry(e string, validSchemes enr.IdentityScheme) (entry, error) { func parseRoot(e string) (rootEntry, error) { var eroot, lroot, sig string + var seq uint if _, err := fmt.Sscanf(e, rootPrefix+" e=%s l=%s seq=%d sig=%s", &eroot, &lroot, &seq, &sig); err != nil { return rootEntry{}, entryError{"root", errSyntax} } + if !isValidHash(eroot) || !isValidHash(lroot) { return rootEntry{}, entryError{"root", errInvalidChild} } + sigb, err := b64format.DecodeString(sig) if err != nil || len(sigb) != crypto.SignatureLength { return rootEntry{}, entryError{"root", errInvalidSig} } + return rootEntry{eroot, lroot, seq, sigb}, nil } @@ -336,6 +371,7 @@ func parseLinkEntry(e string) (entry, error) { if err != nil { return nil, err } + return le, nil } @@ -343,20 +379,26 @@ func parseLink(e string) (*linkEntry, error) { if !strings.HasPrefix(e, linkPrefix) { return nil, fmt.Errorf("wrong/missing scheme 'enrtree' in URL") } + e = e[len(linkPrefix):] + pos := strings.IndexByte(e, '@') if pos == -1 { return nil, entryError{"link", errNoPubkey} } + keystring, domain := e[:pos], e[pos+1:] + keybytes, err := b32format.DecodeString(keystring) if err != nil { return nil, entryError{"link", errBadPubkey} } + key, err := crypto.DecompressPubkey(keybytes) if err != nil { return nil, entryError{"link", errBadPubkey} } + return &linkEntry{e, domain, key}, nil } @@ -365,30 +407,38 @@ func parseBranch(e string) (entry, error) { if e == "" { return &branchEntry{}, nil // empty entry is OK } + hashes := make([]string, 0, strings.Count(e, ",")) + for _, c := range strings.Split(e, ",") { if !isValidHash(c) { return nil, entryError{"branch", errInvalidChild} } + hashes = append(hashes, c) } + return &branchEntry{hashes}, nil } func parseENR(e string, validSchemes enr.IdentityScheme) (entry, error) { e = e[len(enrPrefix):] + enc, err := b64format.DecodeString(e) if err != nil { return nil, entryError{"enr", errInvalidENR} } + var rec enr.Record if err := rlp.DecodeBytes(enc, &rec); err != nil { return nil, entryError{"enr", err} } + n, err := enode.New(validSchemes, &rec) if err != nil { return nil, entryError{"enr", err} } + return &enrEntry{n}, nil } @@ -397,8 +447,10 @@ func isValidHash(s string) bool { if dlen < minHashLength || dlen > 32 || strings.ContainsAny(s, "\n\r") { return false } + buf := make([]byte, 32) _, err := b32format.Decode(buf, []byte(s)) + return err == nil } @@ -408,6 +460,7 @@ func truncateHash(hash string) string { if len(hash) < maxLen { panic(fmt.Errorf("dnsdisc: hash %q is too short", hash)) } + return hash[:maxLen] } @@ -419,5 +472,6 @@ func ParseURL(url string) (domain string, pubkey *ecdsa.PublicKey, err error) { if err != nil { return "", nil, err } + return le.domain, le.pubkey, nil } diff --git a/p2p/dnsdisc/tree_test.go b/p2p/dnsdisc/tree_test.go index 9ed17aa4b3..58cee4f476 100644 --- a/p2p/dnsdisc/tree_test.go +++ b/p2p/dnsdisc/tree_test.go @@ -54,6 +54,7 @@ func TestParseRoot(t *testing.T) { if !reflect.DeepEqual(e, test.e) { t.Errorf("test %d: wrong entry %s, want %s", i, spew.Sdump(e), spew.Sdump(test.e)) } + if err != test.err { t.Errorf("test %d: wrong error %q, want %q", i, err, test.err) } @@ -131,6 +132,7 @@ func TestParseEntry(t *testing.T) { if !reflect.DeepEqual(e, test.e) { t.Errorf("test %d: wrong entry %s, want %s", i, spew.Sdump(e), spew.Sdump(test.e)) } + if err != test.err { t.Errorf("test %d: wrong error %q, want %q", i, err, test.err) } @@ -140,10 +142,12 @@ func TestParseEntry(t *testing.T) { func TestMakeTree(t *testing.T) { keys := testKeys(50) nodes := testNodes(keys) + tree, err := MakeTree(2, nodes, nil) if err != nil { t.Fatal(err) } + txt := tree.ToTXT("") if len(txt) < len(nodes)+1 { t.Fatal("too few TXT records in output") diff --git a/p2p/enode/idscheme.go b/p2p/enode/idscheme.go index fd5d868b76..66c04e917b 100644 --- a/p2p/enode/idscheme.go +++ b/p2p/enode/idscheme.go @@ -51,14 +51,17 @@ func SignV4(r *enr.Record, privkey *ecdsa.PrivateKey) error { h := sha3.NewLegacyKeccak256() rlp.Encode(h, cpy.AppendElements(nil)) + sig, err := crypto.Sign(h.Sum(nil), privkey) if err != nil { return err } + sig = sig[:len(sig)-1] // remove v if err = cpy.SetSig(V4ID{}, sig); err == nil { *r = cpy } + return err } @@ -72,21 +75,26 @@ func (V4ID) Verify(r *enr.Record, sig []byte) error { h := sha3.NewLegacyKeccak256() rlp.Encode(h, r.AppendElements(nil)) + if !crypto.VerifySignature(entry, h.Sum(nil), sig) { return enr.ErrInvalidSig } + return nil } func (V4ID) NodeAddr(r *enr.Record) []byte { var pubkey Secp256k1 + err := r.Load(&pubkey) if err != nil { return nil } + buf := make([]byte, 64) math.ReadBits(pubkey.X, buf[:32]) math.ReadBits(pubkey.Y, buf[32:]) + return crypto.Keccak256(buf) } @@ -106,11 +114,14 @@ func (v *Secp256k1) DecodeRLP(s *rlp.Stream) error { if err != nil { return err } + pk, err := crypto.DecompressPubkey(buf) if err != nil { return err } + *v = (Secp256k1)(*pk) + return nil } @@ -132,6 +143,7 @@ func (v4CompatID) Verify(r *enr.Record, sig []byte) error { func signV4Compat(r *enr.Record, pubkey *ecdsa.PublicKey) { r.Set((*Secp256k1)(pubkey)) + if err := r.SetSig(v4CompatID{}, []byte{}); err != nil { panic(err) } @@ -147,15 +159,19 @@ func (NullID) Verify(r *enr.Record, sig []byte) error { func (NullID) NodeAddr(r *enr.Record) []byte { var id ID + r.Load(enr.WithEntry("nulladdr", &id)) + return id[:] } func SignNull(r *enr.Record, id ID) *Node { r.Set(enr.ID("null")) r.Set(enr.WithEntry("nulladdr", id)) + if err := r.SetSig(NullID{}, []byte{}); err != nil { panic(err) } + return &Node{r: *r, id: id} } diff --git a/p2p/enode/idscheme_test.go b/p2p/enode/idscheme_test.go index 0910e6e83f..2a3df9b278 100644 --- a/p2p/enode/idscheme_test.go +++ b/p2p/enode/idscheme_test.go @@ -42,6 +42,7 @@ func TestEmptyNodeID(t *testing.T) { } require.NoError(t, SignV4(&r, privkey)) + expected := "a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7" assert.Equal(t, expected, hex.EncodeToString(ValidSchemes.NodeAddr(&r))) } @@ -52,9 +53,11 @@ func TestSignError(t *testing.T) { var r enr.Record emptyEnc, _ := rlp.EncodeToBytes(&r) + if err := SignV4(&r, invalidKey); err == nil { t.Fatal("expected error from SignV4") } + newEnc, _ := rlp.EncodeToBytes(&r) if !bytes.Equal(newEnc, emptyEnc) { t.Fatal("record modified even though signing failed") @@ -69,6 +72,7 @@ func TestGetSetSecp256k1(t *testing.T) { } var pk Secp256k1 + require.NoError(t, r.Load(&pk)) assert.EqualValues(t, pubkey, &pk) } diff --git a/p2p/enode/iter.go b/p2p/enode/iter.go index 53ec4cf43e..6a27c942cd 100644 --- a/p2p/enode/iter.go +++ b/p2p/enode/iter.go @@ -35,19 +35,24 @@ type Iterator interface { // sequences, this function calls Next at most n times. func ReadNodes(it Iterator, n int) []*Node { seen := make(map[ID]*Node, n) + for i := 0; i < n && it.Next(); i++ { // Remove duplicates, keeping the node with higher seq. node := it.Node() prevNode, ok := seen[node.ID()] + if ok && prevNode.Seq() > node.Seq() { continue } + seen[node.ID()] = node } + result := make([]*Node, 0, len(seen)) for _, node := range seen { result = append(result, node) } + return result } @@ -75,6 +80,7 @@ func (it *sliceIter) Next() bool { if len(it.nodes) == 0 { return false } + it.index++ if it.index == len(it.nodes) { if it.cycle { @@ -84,15 +90,18 @@ func (it *sliceIter) Next() bool { return false } } + return true } func (it *sliceIter) Node() *Node { it.mu.Lock() defer it.mu.Unlock() + if len(it.nodes) == 0 { return nil } + return it.nodes[it.index] } @@ -120,6 +129,7 @@ func (f *filterIter) Next() bool { return true } } + return false } @@ -163,6 +173,7 @@ func NewFairMix(timeout time.Duration) *FairMix { closed: make(chan struct{}), timeout: timeout, } + return m } @@ -174,9 +185,11 @@ func (m *FairMix) AddSource(it Iterator) { if m.closed == nil { return } + m.wg.Add(1) source := &mixSource{it, make(chan *Node), m.timeout} m.sources = append(m.sources, source) + go m.runSource(m.closed, source) } @@ -189,9 +202,11 @@ func (m *FairMix) Close() { if m.closed == nil { return } + for _, s := range m.sources { s.it.Close() } + close(m.closed) m.wg.Wait() close(m.fromAny) @@ -224,6 +239,7 @@ func (m *FairMix) Next() bool { // because the source delivered a node. source.timeout = m.timeout m.cur = n + return true } // This source has ended. @@ -250,6 +266,7 @@ func (m *FairMix) nextFromAny() bool { if ok { m.cur = n } + return ok } @@ -261,7 +278,9 @@ func (m *FairMix) pickSource() *mixSource { if len(m.sources) == 0 { return nil } + m.last = (m.last + 1) % len(m.sources) + return m.sources[m.last] } @@ -275,6 +294,7 @@ func (m *FairMix) deleteSource(s *mixSource) { copy(m.sources[i:], m.sources[i+1:]) m.sources[len(m.sources)-1] = nil m.sources = m.sources[:len(m.sources)-1] + break } } @@ -284,6 +304,7 @@ func (m *FairMix) deleteSource(s *mixSource) { func (m *FairMix) runSource(closed chan struct{}, s *mixSource) { defer m.wg.Done() defer close(s.next) + for s.it.Next() { n := s.it.Node() select { diff --git a/p2p/enode/iter_test.go b/p2p/enode/iter_test.go index 5014346af4..74c0a1a07d 100644 --- a/p2p/enode/iter_test.go +++ b/p2p/enode/iter_test.go @@ -43,6 +43,7 @@ func TestReadNodesCycle(t *testing.T) { } nodes := ReadNodes(iter, 10) checkNodes(t, nodes, 3) + if iter.count != 10 { t.Fatalf("%d calls to Next, want %d", iter.count, 100) } @@ -61,10 +62,12 @@ func TestFilterNodes(t *testing.T) { if !it.Next() { t.Fatal("Next returned false") } + if it.Node() != nodes[i] { t.Fatalf("iterator returned wrong node %v\nwant %v", it.Node(), nodes[i]) } } + if it.Next() { t.Fatal("Next returned true after underlying iterator has ended") } @@ -75,16 +78,20 @@ func checkNodes(t *testing.T, nodes []*Node, wantLen int) { t.Errorf("slice has %d nodes, want %d", len(nodes), wantLen) return } + seen := make(map[ID]bool) + for i, e := range nodes { if e == nil { t.Errorf("nil node at index %d", i) return } + if seen[e.ID()] { t.Errorf("slice has duplicate node %v", e.ID()) return } + seen[e.ID()] = true } } @@ -102,6 +109,7 @@ func testMixerFairness(t *testing.T) { mix.AddSource(&genIter{index: 1}) mix.AddSource(&genIter{index: 2}) mix.AddSource(&genIter{index: 3}) + defer mix.Close() nodes := ReadNodes(mix, 500) @@ -123,6 +131,7 @@ func TestFairMixNextFromAll(t *testing.T) { mix := NewFairMix(1 * time.Millisecond) mix.AddSource(&genIter{index: 1}) mix.AddSource(CycleNodes(nil)) + defer mix.Close() nodes := ReadNodes(mix, 500) @@ -141,6 +150,7 @@ func TestFairMixEmpty(t *testing.T) { testN = testNode(1, 1) ch = make(chan *Node) ) + defer mix.Close() go func() { @@ -149,6 +159,7 @@ func TestFairMixEmpty(t *testing.T) { }() mix.AddSource(CycleNodes([]*Node{testN})) + if n := <-ch; n != testN { t.Errorf("got wrong node: %v", n) } @@ -168,16 +179,19 @@ func TestFairMixRemoveSource(t *testing.T) { }() sig <- nil + runtime.Gosched() source.Close() wantNode := testNode(0, 0) mix.AddSource(CycleNodes([]*Node{wantNode})) + n := <-sig if len(mix.sources) != 1 { t.Fatalf("have %d sources, want one", len(mix.sources)) } + if n != wantNode { t.Fatalf("mixer returned wrong node") } @@ -212,6 +226,7 @@ func testMixerClose(t *testing.T) { done := make(chan struct{}) go func() { defer close(done) + if mix.Next() { t.Error("Next returned true") } @@ -232,10 +247,12 @@ func testMixerClose(t *testing.T) { func idPrefixDistribution(nodes []*Node) map[uint32]int { d := make(map[uint32]int) + for _, node := range nodes { id := node.ID() d[binary.BigEndian.Uint32(id[:4])]++ } + return d } @@ -243,6 +260,7 @@ func approxEqual(x, y, ε int) bool { if y > x { x, y = y, x } + return x-y > ε } @@ -258,8 +276,10 @@ func (s *genIter) Next() bool { s.node = nil return false } + s.node = testNode(uint64(index)<<32|uint64(s.gen), 0) s.gen++ + return true } @@ -273,9 +293,12 @@ func (s *genIter) Close() { func testNode(id, seq uint64) *Node { var nodeID ID + binary.BigEndian.PutUint64(nodeID[:], id) + r := new(enr.Record) r.SetSeq(seq) + return SignNull(r, nodeID) } diff --git a/p2p/enode/localnode.go b/p2p/enode/localnode.go index a18204e752..675350907e 100644 --- a/p2p/enode/localnode.go +++ b/p2p/enode/localnode.go @@ -83,6 +83,7 @@ func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode { ln.seq = db.localSeq(ln.id) ln.update = time.Now() ln.cur.Store((*Node)(nil)) + return ln } @@ -121,6 +122,7 @@ func (ln *LocalNode) Node() *Node { ln.sign() ln.update = time.Now() + return ln.cur.Load().(*Node) } @@ -179,6 +181,7 @@ func (ln *LocalNode) endpointForIP(ip net.IP) *lnEndpoint { if ip.To4() != nil { return &ln.endpoint4 } + return &ln.endpoint6 } @@ -243,16 +246,19 @@ func (ln *LocalNode) updateEndpoints() { } else { ln.delete(enr.IPv4{}) } + if ip6 != nil && !ip6.IsUnspecified() { ln.set(enr.IPv6(ip6)) } else { ln.delete(enr.IPv6{}) } + if udp4 != 0 { ln.set(enr.UDP(udp4)) } else { ln.delete(enr.UDP(0)) } + if udp6 != 0 && udp6 != udp4 { ln.set(enr.UDP6(udp6)) } else { @@ -263,15 +269,18 @@ func (ln *LocalNode) updateEndpoints() { // get returns the endpoint with highest precedence. func (e *lnEndpoint) get() (newIP net.IP, newPort uint16) { newPort = e.fallbackUDP + if e.fallbackIP != nil { newIP = e.fallbackIP } + if e.staticIP != nil { newIP = e.staticIP } else if ip, port := predictAddr(e.track); ip != nil { newIP = ip newPort = port } + return newIP, newPort } @@ -282,12 +291,15 @@ func predictAddr(t *netutil.IPTracker) (net.IP, uint16) { if ep == "" { return nil, 0 } + ipString, portString, _ := net.SplitHostPort(ep) ip := net.ParseIP(ipString) + port, err := strconv.ParseUint(portString, 10, 16) if err != nil { return nil, 0 } + return ip, uint16(port) } @@ -304,15 +316,19 @@ func (ln *LocalNode) sign() { for _, e := range ln.entries { r.Set(e) } + ln.bumpSeq() r.SetSeq(ln.seq) + if err := SignV4(&r, ln.key); err != nil { panic(fmt.Errorf("enode: can't sign record: %v", err)) } + n, err := New(ValidSchemes, &r) if err != nil { panic(fmt.Errorf("enode: can't verify local record: %v", err)) } + ln.cur.Store(n) log.Info("New local node record", "seq", ln.seq, "id", n.ID(), "ip", n.IP(), "udp", n.UDP(), "tcp", n.TCP()) } @@ -328,5 +344,6 @@ func nowMilliseconds() uint64 { if ns < 0 { return 0 } + return uint64(ns / 1000 / 1000) } diff --git a/p2p/enode/localnode_test.go b/p2p/enode/localnode_test.go index 7f97ad392f..de5f0f6c93 100644 --- a/p2p/enode/localnode_test.go +++ b/p2p/enode/localnode_test.go @@ -29,6 +29,7 @@ import ( func newLocalNodeForTesting() (*LocalNode, *DB) { db, _ := OpenDB("") key, _ := crypto.GenerateKey() + return NewLocalNode(db, key), db } @@ -41,6 +42,7 @@ func TestLocalNode(t *testing.T) { } ln.Set(enr.WithEntry("x", uint(3))) + var x uint if err := ln.Node().Load(enr.WithEntry("x", &x)); err != nil { t.Fatal("can't load entry 'x':", err) @@ -62,6 +64,7 @@ func TestLocalNodeSeqPersist(t *testing.T) { } ln.Set(enr.WithEntry("x", uint(1))) + if s := ln.Node().Seq(); s != initialSeq+1 { t.Fatalf("wrong seq %d after set, want %d", s, initialSeq+1) } @@ -79,6 +82,7 @@ func TestLocalNodeSeqPersist(t *testing.T) { // Create a new instance with a different node key on the same database. // This should reset the sequence number. key, _ := crypto.GenerateKey() + ln3 := NewLocalNode(db, key) if s := ln3.Node().Seq(); s < finalSeq { t.Fatalf("wrong seq %d on instance with changed key, want >= %d", s, finalSeq) @@ -92,6 +96,7 @@ func TestLocalNodeEndpoint(t *testing.T) { predicted = &net.UDPAddr{IP: net.IP{127, 0, 1, 2}, Port: 81} staticIP = net.IP{127, 0, 1, 2} ) + ln, db := newLocalNodeForTesting() defer db.Close() diff --git a/p2p/enode/node.go b/p2p/enode/node.go index d7a1a9a156..c96c38ade5 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -44,10 +44,12 @@ func New(validSchemes enr.IdentityScheme, r *enr.Record) (*Node, error) { if err := r.VerifySignature(validSchemes); err != nil { return nil, err } + node := &Node{r: *r} if n := copy(node.id[:], validSchemes.NodeAddr(&node.r)); n != len(ID{}) { return nil, fmt.Errorf("invalid node ID length %d, need %d", n, len(ID{})) } + return node, nil } @@ -57,6 +59,7 @@ func MustParse(rawurl string) *Node { if err != nil { panic("invalid node: " + err.Error()) } + return n } @@ -65,17 +68,21 @@ func Parse(validSchemes enr.IdentityScheme, input string) (*Node, error) { if strings.HasPrefix(input, "enode://") { return ParseV4(input) } + if !strings.HasPrefix(input, "enr:") { return nil, errMissingPrefix } + bin, err := base64.RawURLEncoding.DecodeString(input[4:]) if err != nil { return nil, err } + var r enr.Record if err := rlp.DecodeBytes(bin, &r); err != nil { return nil, err } + return New(validSchemes, &r) } @@ -105,26 +112,33 @@ func (n *Node) IP() net.IP { ip4 enr.IPv4 ip6 enr.IPv6 ) + if n.Load(&ip4) == nil { return net.IP(ip4) } + if n.Load(&ip6) == nil { return net.IP(ip6) } + return nil } // UDP returns the UDP port of the node. func (n *Node) UDP() int { var port enr.UDP + n.Load(&port) + return int(port) } // TCP returns the TCP port of the node. func (n *Node) TCP() int { var port enr.TCP + n.Load(&port) + return int(port) } @@ -134,6 +148,7 @@ func (n *Node) Pubkey() *ecdsa.PublicKey { if n.Load((*Secp256k1)(&key)) != nil { return nil } + return &key } @@ -150,15 +165,18 @@ func (n *Node) ValidateComplete() error { if n.Incomplete() { return errors.New("missing IP address") } + if n.UDP() == 0 { return errors.New("missing UDP port") } + ip := n.IP() if ip.IsMulticast() || ip.IsUnspecified() { return errors.New("invalid IP (multicast/unspecified)") } // Validate the node key (on curve, etc.). var key Secp256k1 + return n.Load(&key) } @@ -167,8 +185,10 @@ func (n *Node) String() string { if isNewV4(n) { return n.URLv4() // backwards-compatibility glue for NewV4 nodes } + enc, _ := rlp.EncodeToBytes(&n.r) // always succeeds because record is valid b64 := base64.RawURLEncoding.EncodeToString(enc) + return "enr:" + b64 } @@ -183,6 +203,7 @@ func (n *Node) UnmarshalText(text []byte) error { if err == nil { *n = *dec } + return err } @@ -220,7 +241,9 @@ func (n *ID) UnmarshalText(text []byte) error { if err != nil { return err } + *n = id + return nil } @@ -232,18 +255,22 @@ func HexID(in string) ID { if err != nil { panic(err) } + return id } func ParseID(in string) (ID, error) { var id ID + 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 } @@ -254,18 +281,21 @@ func DistCmp(target, a, b ID) 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 } // LogDist returns the logarithmic distance between a and b, log2(a ^ b). func LogDist(a, b ID) int { lz := 0 + for i := range a { x := a[i] ^ b[i] if x == 0 { @@ -275,5 +305,6 @@ func LogDist(a, b ID) int { break } } + return len(a)*8 - lz } diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go index d15859c477..92be7c95ab 100644 --- a/p2p/enode/node_test.go +++ b/p2p/enode/node_test.go @@ -38,6 +38,7 @@ func TestPythonInterop(t *testing.T) { if err := rlp.DecodeBytes(pyRecord, &r); err != nil { t.Fatalf("can't decode: %v", err) } + n, err := New(ValidSchemes, &r) if err != nil { t.Fatalf("can't verify record: %v", err) @@ -49,12 +50,15 @@ func TestPythonInterop(t *testing.T) { wantIP = enr.IPv4{127, 0, 0, 1} wantUDP = enr.UDP(30303) ) + if n.Seq() != wantSeq { t.Errorf("wrong seq: got %d, want %d", n.Seq(), wantSeq) } + if n.ID() != wantID { t.Errorf("wrong id: got %x, want %x", n.ID(), wantID) } + want := map[enr.Entry]interface{}{new(enr.IPv4): &wantIP, new(enr.UDP): &wantUDP} for k, v := range want { desc := fmt.Sprintf("loading key %q", k.ENRKey()) @@ -72,6 +76,7 @@ func TestHexID(t *testing.T) { 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[:]) } @@ -90,6 +95,7 @@ func TestID_textEncoding(t *testing.T) { if err != nil { t.Fatal(err) } + if !bytes.Equal(text, []byte(hex)) { t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text) } @@ -98,6 +104,7 @@ func TestID_textEncoding(t *testing.T) { if err := id.UnmarshalText(text); err != nil { t.Fatal(err) } + if *id != ref { t.Fatalf("text decoding did not match\nexpected: %s\ngot: %s", ref, id) } @@ -108,6 +115,7 @@ func TestID_distcmp(t *testing.T) { 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, nil); err != nil { @@ -120,6 +128,7 @@ func TestID_distcmp(t *testing.T) { func TestID_distcmpEqual(t *testing.T) { base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} x := ID{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") } diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index 7e7fb69b29..2f34ff7a55 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -81,6 +81,7 @@ func OpenDB(path string) (*DB, error) { if path == "" { return newMemoryDB() } + return newPersistentDB(path) } @@ -90,6 +91,7 @@ func newMemoryDB() (*DB, error) { if err != nil { return nil, err } + return &DB{lvl: db, quit: make(chan struct{})}, nil } @@ -97,10 +99,12 @@ func newMemoryDB() (*DB, error) { // also flushing its contents in case of a version mismatch. func newPersistentDB(path string) (*DB, 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 } @@ -122,12 +126,15 @@ func newPersistentDB(path string) (*DB, error) { // Version present, flush if different if !bytes.Equal(blob, currentVer) { db.Close() + if err = os.RemoveAll(path); err != nil { return nil, err } + return newPersistentDB(path) } } + return &DB{lvl: db, quit: make(chan struct{})}, nil } @@ -136,6 +143,7 @@ func nodeKey(id ID) []byte { key := append([]byte(dbNodePrefix), id[:]...) key = append(key, ':') key = append(key, dbDiscoverRoot...) + return key } @@ -144,8 +152,10 @@ func splitNodeKey(key []byte) (id ID, rest []byte) { if !bytes.HasPrefix(key, []byte(dbNodePrefix)) { return ID{}, nil } + item := key[len(dbNodePrefix):] copy(id[:], item[:len(id)]) + return id, item[len(id)+1:] } @@ -155,6 +165,7 @@ func nodeItemKey(id ID, ip net.IP, field string) []byte { if ip16 == nil { panic(fmt.Errorf("invalid IP (length %d)", len(ip))) } + return bytes.Join([][]byte{nodeKey(id), ip16, []byte(field)}, []byte{':'}) } @@ -165,15 +176,18 @@ func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) { if string(key) == dbDiscoverRoot { return id, nil, "" } + key = key[len(dbDiscoverRoot)+1:] // Split out the IP. ip = key[:16] if ip4 := ip.To4(); ip4 != nil { ip = ip4 } + key = key[16+1:] // Field is the remainder of key. field = string(key) + return id, ip, field } @@ -192,6 +206,7 @@ func localItemKey(id ID, field string) []byte { key := append([]byte(dbLocalPrefix), id[:]...) key = append(key, ':') key = append(key, field...) + return key } @@ -201,10 +216,12 @@ func (db *DB) fetchInt64(key []byte) int64 { if err != nil { return 0 } + val, read := binary.Varint(blob) if read <= 0 { return 0 } + return val } @@ -212,6 +229,7 @@ func (db *DB) fetchInt64(key []byte) int64 { func (db *DB) storeInt64(key []byte, n int64) error { blob := make([]byte, binary.MaxVarintLen64) blob = blob[:binary.PutVarint(blob, n)] + return db.lvl.Put(key, blob, nil) } @@ -221,7 +239,9 @@ func (db *DB) fetchUint64(key []byte) uint64 { if err != nil { return 0 } + val, _ := binary.Uvarint(blob) + return val } @@ -229,6 +249,7 @@ func (db *DB) fetchUint64(key []byte) uint64 { func (db *DB) storeUint64(key []byte, n uint64) error { blob := make([]byte, binary.MaxVarintLen64) blob = blob[:binary.PutUvarint(blob, n)] + return db.lvl.Put(key, blob, nil) } @@ -238,6 +259,7 @@ func (db *DB) Node(id ID) *Node { if err != nil { return nil } + return mustDecodeNode(id[:], blob) } @@ -248,6 +270,7 @@ func mustDecodeNode(id, data []byte) *Node { } // Restore node id cache. copy(node.id[:], id) + return node } @@ -256,13 +279,16 @@ func (db *DB) UpdateNode(node *Node) error { if node.Seq() < db.NodeSeq(node.ID()) { return nil } + blob, err := rlp.EncodeToBytes(&node.r) if err != nil { return err } + if err := db.lvl.Put(nodeKey(node.ID()), blob, nil); err != nil { return err } + return db.storeUint64(nodeItemKey(node.ID(), zeroIP, dbNodeSeq), node.Seq()) } @@ -277,6 +303,7 @@ func (db *DB) Resolve(n *Node) *Node { if n.Seq() > db.NodeSeq(n.ID()) { return n } + return db.Node(n.ID()) } @@ -288,6 +315,7 @@ func (db *DB) DeleteNode(id ID) { func deleteRange(db *leveldb.DB, prefix []byte) { it := db.NewIterator(util.BytesPrefix(prefix), nil) defer it.Release() + for it.Next() { db.Delete(it.Key(), nil) } @@ -311,6 +339,7 @@ func (db *DB) ensureExpirer() { func (db *DB) expirer() { tick := time.NewTicker(dbCleanupCycle) defer tick.Stop() + for { select { case <-tick.C: @@ -326,6 +355,7 @@ func (db *DB) expirer() { func (db *DB) expireNodes() { it := db.lvl.NewIterator(util.BytesPrefix([]byte(dbNodePrefix)), nil) defer it.Release() + if !it.Next() { return } @@ -335,6 +365,7 @@ func (db *DB) expireNodes() { youngestPong int64 atEnd = false ) + for !atEnd { id, ip, field := splitNodeItemKey(it.Key()) if field == dbNodePong { @@ -342,19 +373,23 @@ func (db *DB) expireNodes() { if time > youngestPong { youngestPong = time } + if time < threshold { // Last pong from this IP older than threshold, remove fields belonging to it. deleteRange(db.lvl, nodeItemKey(id, ip, "")) } } + atEnd = !it.Next() nextID, _ := splitNodeKey(it.Key()) + if atEnd || nextID != id { // We've moved beyond the last entry of the current ID. // Remove everything if there was no recent enough pong. if youngestPong > 0 && youngestPong < threshold { deleteRange(db.lvl, nodeKey(id)) } + youngestPong = 0 } } @@ -366,6 +401,7 @@ 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) } @@ -374,6 +410,7 @@ 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()) } @@ -384,6 +421,7 @@ func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time { } // Launch expirer db.ensureExpirer() + return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePong)), 0) } @@ -392,6 +430,7 @@ 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()) } @@ -400,6 +439,7 @@ 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))) } @@ -408,6 +448,7 @@ 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)) } @@ -416,6 +457,7 @@ 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))) } @@ -424,6 +466,7 @@ 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)) } @@ -434,6 +477,7 @@ func (db *DB) localSeq(id ID) uint64 { if seq := db.fetchUint64(localItemKey(id, dbLocalSeq)); seq > 0 { return seq } + return nowMilliseconds() } @@ -451,6 +495,7 @@ func (db *DB) QuerySeeds(n int, maxAge time.Duration) []*Node { it = db.lvl.NewIterator(nil, nil) id ID ) + defer it.Release() seek: @@ -478,6 +523,7 @@ seek: } nodes = append(nodes, n) } + return nodes } @@ -489,8 +535,10 @@ func nextNode(it iterator.Iterator) *Node { if string(rest) != dbDiscoverRoot { continue } + return mustDecodeNode(id[:], it.Value()) } + return nil } diff --git a/p2p/enode/nodedb_test.go b/p2p/enode/nodedb_test.go index ba3f8c762b..da143e6ac8 100644 --- a/p2p/enode/nodedb_test.go +++ b/p2p/enode/nodedb_test.go @@ -38,9 +38,11 @@ func TestDBNodeKey(t *testing.T) { 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // ':', 'v', '4', } + if !bytes.Equal(enc, want) { t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want) } + id, _ := splitNodeKey(enc) if id != keytestID { t.Errorf("wrong ID from splitNodeKey") @@ -62,16 +64,20 @@ func TestDBNodeItemKey(t *testing.T) { 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x03, // ':', 'f', 'o', 'o', 'b', 'a', 'r', } + if !bytes.Equal(enc, want) { t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want) } + id, ip, field := splitNodeItemKey(enc) if id != keytestID { t.Errorf("splitNodeItemKey returned wrong ID: %v", id) } + if !ip.Equal(wantIP) { t.Errorf("splitNodeItemKey returned wrong IP: %v", ip) } + if field != wantField { t.Errorf("splitNodeItemKey returned wrong field: %q", field) } @@ -99,6 +105,7 @@ func TestDBInt64(t *testing.T) { // 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) @@ -126,9 +133,11 @@ func TestDBFetchStore(t *testing.T) { if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != 0 { t.Errorf("ping: non-existing object: %v", stored) } + if err := db.UpdateLastPingReceived(node.ID(), node.IP(), inst); err != nil { t.Errorf("ping: failed to update: %v", err) } + if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() { t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) } @@ -136,9 +145,11 @@ func TestDBFetchStore(t *testing.T) { if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != 0 { t.Errorf("pong: non-existing object: %v", stored) } + if err := db.UpdateLastPongReceived(node.ID(), node.IP(), inst); err != nil { t.Errorf("pong: failed to update: %v", err) } + if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() { t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) } @@ -146,9 +157,11 @@ func TestDBFetchStore(t *testing.T) { if stored := db.FindFails(node.ID(), node.IP()); stored != 0 { t.Errorf("find-node fails: non-existing object: %v", stored) } + if err := db.UpdateFindFails(node.ID(), node.IP(), num); err != nil { t.Errorf("find-node fails: failed to update: %v", err) } + if stored := db.FindFails(node.ID(), node.IP()); stored != num { t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num) } @@ -156,9 +169,11 @@ func TestDBFetchStore(t *testing.T) { 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) { @@ -246,12 +261,14 @@ func TestDBSeedQuery(t *testing.T) { // every time when the database is small. Run the test multiple // times to avoid flakes. const attempts = 15 + var err error for i := 0; i < attempts; i++ { if err = testSeedQuery(); err == nil { return } } + if err != nil { t.Errorf("no successful run in %d attempts: %v", attempts, err) } @@ -266,6 +283,7 @@ func testSeedQuery() error { if err := db.UpdateNode(seed.node); err != nil { return fmt.Errorf("node %d: failed to insert: %v", i, err) } + if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil { return fmt.Errorf("node %d: failed to insert bondTime: %v", i, err) } @@ -274,26 +292,32 @@ func testSeedQuery() error { // Retrieve the entire batch and check for duplicates seeds := db.QuerySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour) have := make(map[ID]struct{}) + for _, seed := range seeds { have[seed.ID()] = struct{}{} } + want := make(map[ID]struct{}) for _, seed := range nodeDBSeedQueryNodes[1:] { want[seed.node.ID()] = struct{}{} } + if len(seeds) != len(want) { return fmt.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want)) } + for id := range have { if _, ok := want[id]; !ok { return fmt.Errorf("extra seed: %v", id) } } + for id := range want { if _, ok := have[id]; !ok { return fmt.Errorf("missing seed: %v", id) } } + return nil } @@ -310,9 +334,11 @@ func TestDBPersistency(t *testing.T) { 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 @@ -320,9 +346,11 @@ func TestDBPersistency(t *testing.T) { 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() } @@ -427,6 +455,7 @@ func TestDBExpiration(t *testing.T) { t.Fatalf("node %d: failed to insert: %v", i, err) } } + if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil { t.Fatalf("node %d: failed to update bondTime: %v", i, err) } @@ -436,13 +465,16 @@ func TestDBExpiration(t *testing.T) { // Check that expired entries have been removed. unixZeroTime := time.Unix(0, 0) + for i, seed := range nodeDBExpirationNodes { node := db.Node(seed.node.ID()) pong := db.LastPongReceived(seed.node.ID(), seed.node.IP()) + if seed.exp { if seed.storeNode && node != nil { t.Errorf("node %d (%s) shouldn't be present after expiration", i, seed.node.ID().TerminalString()) } + if !pong.Equal(unixZeroTime) { t.Errorf("pong time %d (%s %v) shouldn't be present after expiration", i, seed.node.ID().TerminalString(), seed.node.IP()) } @@ -450,6 +482,7 @@ func TestDBExpiration(t *testing.T) { if seed.storeNode && node == nil { t.Errorf("node %d (%s) should be present after expiration", i, seed.node.ID().TerminalString()) } + if !pong.Equal(seed.pong.Truncate(1 * time.Second)) { t.Errorf("pong time %d (%s) should be %v after expiration, but is %v", i, seed.node.ID().TerminalString(), seed.pong, pong) } diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go index 0272eee987..5c31e376ec 100644 --- a/p2p/enode/urlv4.go +++ b/p2p/enode/urlv4.go @@ -42,6 +42,7 @@ func MustParseV4(rawurl string) *Node { if err != nil { panic("invalid node URL: " + err.Error()) } + return n } @@ -75,8 +76,10 @@ func ParseV4(rawurl string) (*Node, error) { if err != nil { return nil, fmt.Errorf("invalid public key (%v)", err) } + return NewV4(id, nil, 0, 0), nil } + return parseComplete(rawurl) } @@ -87,17 +90,22 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node { if len(ip) > 0 { r.Set(enr.IP(ip)) } + if udp != 0 { r.Set(enr.UDP(udp)) } + if tcp != 0 { r.Set(enr.TCP(tcp)) } + signV4Compat(&r, pubkey) + n, err := New(v4CompatID{}, &r) if err != nil { panic(err) } + return n } @@ -112,10 +120,12 @@ func parseComplete(rawurl string) (*Node, error) { id *ecdsa.PublicKey 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\"") } @@ -123,6 +133,7 @@ func parseComplete(rawurl string) (*Node, error) { if u.User == nil { return nil, errors.New("does not contain node ID") } + if id, err = parsePubkey(u.User.String()); err != nil { return nil, fmt.Errorf("invalid public key (%v)", err) } @@ -133,6 +144,7 @@ func parseComplete(rawurl string) (*Node, error) { if err != nil { return nil, err } + ip = ips[0] } // Ensure the IP is 4 bytes long for IPv4 addresses. @@ -143,14 +155,17 @@ func parseComplete(rawurl string) (*Node, error) { if tcpPort, err = strconv.ParseUint(u.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 NewV4(id, ip, int(tcpPort), int(udpPort)), nil } @@ -162,7 +177,9 @@ func parsePubkey(in string) (*ecdsa.PublicKey, error) { } else if len(b) != 64 { return nil, fmt.Errorf("wrong length, want %d hex chars", 128) } + b = append([]byte{0x4}, b...) + return crypto.UnmarshalPubkey(b) } @@ -172,14 +189,17 @@ func (n *Node) URLv4() string { nodeid string key ecdsa.PublicKey ) + n.Load(&scheme) n.Load((*Secp256k1)(&key)) + switch { case scheme == "v4" || key != ecdsa.PublicKey{}: nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:]) default: nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:]) } + u := url.URL{Scheme: "enode"} if n.Incomplete() { u.Host = nodeid @@ -187,10 +207,12 @@ func (n *Node) URLv4() string { addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()} u.User = url.User(nodeid) u.Host = addr.String() + if n.UDP() != n.TCP() { u.RawQuery = "discport=" + strconv.Itoa(n.UDP()) } } + return u.String() } @@ -199,5 +221,6 @@ func PubkeyToIDV4(key *ecdsa.PublicKey) ID { e := make([]byte, 64) math.ReadBits(key.X, e[:len(e)/2]) math.ReadBits(key.Y, e[len(e)/2:]) + return ID(crypto.Keccak256Hash(e)) } diff --git a/p2p/enode/urlv4_test.go b/p2p/enode/urlv4_test.go index 33de96cc57..64cee00d4a 100644 --- a/p2p/enode/urlv4_test.go +++ b/p2p/enode/urlv4_test.go @@ -33,6 +33,7 @@ func init() { if name == "node.example.org" { return []net.IP{{33, 44, 55, 66}}, nil } + return nil, errors.New("no such host") } } @@ -162,6 +163,7 @@ func hexPubkey(h string) *ecdsa.PublicKey { if err != nil { panic(err) } + return k } @@ -181,6 +183,7 @@ func TestParseNode(t *testing.T) { t.Errorf("test %q:\n unexpected error: %v", test.input, err) continue } + if !reflect.DeepEqual(n, test.wantResult) { t.Errorf("test %q:\n result mismatch:\ngot: %#v\nwant: %#v", test.input, n, test.wantResult) } diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go index e049930913..b7ec6a0c5d 100644 --- a/p2p/enr/enr.go +++ b/p2p/enr/enr.go @@ -71,6 +71,7 @@ func (m SchemeMap) Verify(r *Record, sig []byte) error { if s == nil { return ErrInvalidSig } + return s.Verify(r, sig) } @@ -79,6 +80,7 @@ func (m SchemeMap) NodeAddr(r *Record) []byte { if s == nil { return nil } + return s.NodeAddr(r) } @@ -142,8 +144,10 @@ func (r *Record) Load(e Entry) error { if err := rlp.DecodeBytes(r.pairs[i].v, e); err != nil { return &KeyError{Key: e.ENRKey(), Err: err} } + return nil } + return &KeyError{Key: e.ENRKey(), Err: errNotFound} } @@ -155,11 +159,13 @@ func (r *Record) Set(e Entry) { if err != nil { panic(fmt.Errorf("enr: can't encode %s: %v", e.ENRKey(), err)) } + r.invalidate() pairs := make([]pair, len(r.pairs)) copy(pairs, r.pairs) i := sort.Search(len(pairs), func(i int) bool { return pairs[i].k >= e.ENRKey() }) + switch { case i < len(pairs) && pairs[i].k == e.ENRKey(): // element is present at r.pairs[i] @@ -167,6 +173,7 @@ func (r *Record) Set(e Entry) { case i < len(r.pairs): // insert pair before i-th elem el := pair{e.ENRKey(), blob} + pairs = append(pairs, pair{}) copy(pairs[i+1:], pairs[i:]) pairs[i] = el @@ -174,6 +181,7 @@ func (r *Record) Set(e Entry) { // element should be placed at the end of r.pairs pairs = append(pairs, pair{e.ENRKey(), blob}) } + r.pairs = pairs } @@ -181,6 +189,7 @@ func (r *Record) invalidate() { if r.signature != nil { r.seq++ } + r.signature = nil r.raw = nil } @@ -190,8 +199,10 @@ func (r *Record) Signature() []byte { if r.signature == nil { return nil } + cpy := make([]byte, len(r.signature)) copy(cpy, r.signature) + return cpy } @@ -201,7 +212,9 @@ func (r Record) EncodeRLP(w io.Writer) error { if r.signature == nil { return errEncodeUnsigned } + _, err := w.Write(r.raw) + return err } @@ -211,8 +224,10 @@ func (r *Record) DecodeRLP(s *rlp.Stream) error { if err != nil { return err } + *r = dec r.raw = raw + return nil } @@ -221,6 +236,7 @@ func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) { if err != nil { return dec, raw, err } + if len(raw) > SizeLimit { return dec, raw, errTooBig } @@ -230,52 +246,66 @@ func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) { if _, err := s.List(); err != nil { return dec, raw, err } + if err = s.Decode(&dec.signature); err != nil { if err == rlp.EOL { err = errIncompleteList } + return dec, raw, err } + if err = s.Decode(&dec.seq); err != nil { if err == rlp.EOL { err = errIncompleteList } + return dec, raw, err } // The rest of the record contains sorted k/v pairs. var prevkey string + for i := 0; ; i++ { var kv pair if err := s.Decode(&kv.k); err != nil { if err == rlp.EOL { break } + return dec, raw, err } + if err := s.Decode(&kv.v); err != nil { if err == rlp.EOL { return dec, raw, errIncompletePair } + return dec, raw, err } + if i > 0 { if kv.k == prevkey { return dec, raw, errDuplicateKey } + if kv.k < prevkey { return dec, raw, errNotSorted } } + dec.pairs = append(dec.pairs, kv) prevkey = kv.k } + return dec, raw, s.ListEnd() } // IdentityScheme returns the name of the identity scheme in the record. func (r *Record) IdentityScheme() string { var id ID + r.Load(&id) + return string(id) } @@ -303,15 +333,18 @@ func (r *Record) SetSig(s IdentityScheme, sig []byte) error { if err := s.Verify(r, sig); err != nil { return err } + raw, err := r.encode(sig) if err != nil { return err } + r.signature, r.raw = sig, raw // Reset otherwise. default: r.signature, r.raw = nil, nil } + return nil } @@ -321,6 +354,7 @@ func (r *Record) AppendElements(list []interface{}) []interface{} { for _, p := range r.pairs { list = append(list, p.k, p.v) } + return list } @@ -328,11 +362,14 @@ func (r *Record) encode(sig []byte) (raw []byte, err error) { list := make([]interface{}, 1, 2*len(r.pairs)+2) list[0] = sig list = r.AppendElements(list) + if raw, err = rlp.EncodeToBytes(list); err != nil { return nil, err } + if len(raw) > SizeLimit { return nil, errTooBig } + return raw, nil } diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go index 00fe9bdbc6..6ccb617bc0 100644 --- a/p2p/enr/enr_test.go +++ b/p2p/enr/enr_test.go @@ -34,16 +34,20 @@ var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) func randomString(strlen int) string { b := make([]byte, strlen) rnd.Read(b) + return string(b) } // TestGetSetID tests encoding/decoding and setting/getting of the ID key. func TestGetSetID(t *testing.T) { id := ID("someid") + var r Record + r.Set(id) var id2 ID + require.NoError(t, r.Load(&id2)) assert.Equal(t, id, id2) } @@ -51,10 +55,13 @@ func TestGetSetID(t *testing.T) { // TestGetSetIP4 tests encoding/decoding and setting/getting of the IP key. func TestGetSetIPv4(t *testing.T) { ip := IPv4{192, 168, 0, 3} + var r Record + r.Set(ip) var ip2 IPv4 + require.NoError(t, r.Load(&ip2)) assert.Equal(t, ip, ip2) } @@ -62,10 +69,13 @@ func TestGetSetIPv4(t *testing.T) { // TestGetSetIP6 tests encoding/decoding and setting/getting of the IP6 key. func TestGetSetIPv6(t *testing.T) { ip := IPv6{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68} + var r Record + r.Set(ip) var ip2 IPv6 + require.NoError(t, r.Load(&ip2)) assert.Equal(t, ip, ip2) } @@ -73,36 +83,45 @@ func TestGetSetIPv6(t *testing.T) { // TestGetSetUDP tests encoding/decoding and setting/getting of the UDP key. func TestGetSetUDP(t *testing.T) { port := UDP(30309) + var r Record + r.Set(port) var port2 UDP + require.NoError(t, r.Load(&port2)) assert.Equal(t, port, port2) } func TestLoadErrors(t *testing.T) { var r Record + ip4 := IPv4{127, 0, 0, 1} r.Set(ip4) // Check error for missing keys. var udp UDP + err := r.Load(&udp) if !IsNotFound(err) { t.Error("IsNotFound should return true for missing key") } + assert.Equal(t, &KeyError{Key: udp.ENRKey(), Err: errNotFound}, err) // Check error for invalid keys. var list []uint err = r.Load(WithEntry(ip4.ENRKey(), &list)) kerr, ok := err.(*KeyError) + if !ok { t.Fatalf("expected KeyError, got %T", err) } + assert.Equal(t, kerr.Key, ip4.ENRKey()) assert.Error(t, kerr.Err) + if IsNotFound(err) { t.Error("IsNotFound should return false for decoding errors") } @@ -136,6 +155,7 @@ func TestSortedGetAndSet(t *testing.T) { for _, i := range tt.input { r.Set(WithEntry(i.k, &i.v)) } + for i, w := range tt.want { // set got's key from r.pair[i], so that we preserve order of pairs got := pair{k: r.pairs[i].k} @@ -154,16 +174,20 @@ func TestDirty(t *testing.T) { } require.NoError(t, signTest([]byte{5}, &r)) + if len(r.signature) == 0 { t.Error("record is not signed") } + _, err := rlp.EncodeToBytes(r) assert.NoError(t, err) r.SetSeq(3) + if len(r.signature) != 0 { t.Error("signature still set after modification") } + if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned { t.Errorf("expected errEncodeUnsigned, got %#v", err) } @@ -222,6 +246,7 @@ func TestGetSetOverwrite(t *testing.T) { r.Set(ip2) var ip3 IPv4 + require.NoError(t, r.Load(&ip3)) assert.Equal(t, ip2, ip3) } @@ -229,6 +254,7 @@ func TestGetSetOverwrite(t *testing.T) { // TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record. func TestSignEncodeAndDecode(t *testing.T) { var r Record + r.Set(UDP(30303)) r.Set(IPv4{127, 0, 0, 1}) require.NoError(t, signTest([]byte{5}, &r)) @@ -237,6 +263,7 @@ func TestSignEncodeAndDecode(t *testing.T) { require.NoError(t, err) var r2 Record + require.NoError(t, rlp.DecodeBytes(blob, &r2)) assert.Equal(t, r, r2) @@ -248,10 +275,12 @@ func TestSignEncodeAndDecode(t *testing.T) { // TestRecordTooBig tests that records bigger than SizeLimit bytes cannot be signed. func TestRecordTooBig(t *testing.T) { var r Record + key := randomString(10) // set a big value for random key, expect error r.Set(WithEntry(key, randomString(SizeLimit))) + if err := signTest([]byte{5}, &r); err != errTooBig { t.Fatalf("expected to get errTooBig, got %#v", err) } @@ -267,6 +296,7 @@ func TestDecodeIncomplete(t *testing.T) { input []byte err error } + tests := []decTest{ {[]byte{0xC0}, errIncompleteList}, {[]byte{0xC1, 0x1}, errIncompleteList}, @@ -277,6 +307,7 @@ func TestDecodeIncomplete(t *testing.T) { } for _, test := range tests { var r Record + err := rlp.DecodeBytes(test.input, &r) if err != test.err { t.Errorf("wrong error for %X: %v", test.input, err) @@ -290,6 +321,7 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) { // random key/value pairs for testing pairs := map[string]uint32{} + for i := 0; i < 10; i++ { key := randomString(7) value := rnd.Uint32() @@ -306,6 +338,7 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) { for k, v := range pairs { desc := fmt.Sprintf("key %q", k) + var got uint32 buf := WithEntry(k, &got) require.NoError(t, r.Load(buf), desc) @@ -322,6 +355,7 @@ func (id testID) ENRKey() string { return "testid" } func signTest(id []byte, r *Record) error { r.Set(ID("test")) r.Set(testID(id)) + return r.SetSig(testSig{}, makeTestSig(id, r.Seq())) } @@ -329,6 +363,7 @@ func makeTestSig(id []byte, seq uint64) []byte { sig := make([]byte, 8, len(id)+8) binary.BigEndian.PutUint64(sig[:8], seq) sig = append(sig, id...) + return sig } @@ -337,9 +372,11 @@ func (testSig) Verify(r *Record, sig []byte) error { if err := r.Load((*testID)(&id)); err != nil { return err } + if !bytes.Equal(sig, makeTestSig(id, r.Seq())) { return ErrInvalidSig } + return nil } @@ -348,5 +385,6 @@ func (testSig) NodeAddr(r *Record) []byte { if err := r.Load((*testID)(&id)); err != nil { return nil } + return id } diff --git a/p2p/enr/entries.go b/p2p/enr/entries.go index 6df2813258..896d67835a 100644 --- a/p2p/enr/entries.go +++ b/p2p/enr/entries.go @@ -92,6 +92,7 @@ func (v IP) ENRKey() string { if net.IP(v).To4() == nil { return "ip6" } + return "ip" } @@ -100,9 +101,11 @@ func (v IP) EncodeRLP(w io.Writer) error { if ip4 := net.IP(v).To4(); ip4 != nil { return rlp.Encode(w, ip4) } + if ip6 := net.IP(v).To16(); ip6 != nil { return rlp.Encode(w, ip6) } + return fmt.Errorf("invalid IP address: %v", net.IP(v)) } @@ -111,9 +114,11 @@ func (v *IP) DecodeRLP(s *rlp.Stream) error { if err := s.Decode((*net.IP)(v)); err != nil { return err } + if len(*v) != 4 && len(*v) != 16 { return fmt.Errorf("invalid IP address, want 4 or 16 bytes: %v", *v) } + return nil } @@ -128,6 +133,7 @@ func (v IPv4) EncodeRLP(w io.Writer) error { if ip4 == nil { return fmt.Errorf("invalid IPv4 address: %v", net.IP(v)) } + return rlp.Encode(w, ip4) } @@ -136,9 +142,11 @@ func (v *IPv4) DecodeRLP(s *rlp.Stream) error { if err := s.Decode((*net.IP)(v)); err != nil { return err } + if len(*v) != 4 { return fmt.Errorf("invalid IPv4 address, want 4 bytes: %v", *v) } + return nil } @@ -153,6 +161,7 @@ func (v IPv6) EncodeRLP(w io.Writer) error { if ip6 == nil { return fmt.Errorf("invalid IPv6 address: %v", net.IP(v)) } + return rlp.Encode(w, ip6) } @@ -161,9 +170,11 @@ func (v *IPv6) DecodeRLP(s *rlp.Stream) error { if err := s.Decode((*net.IP)(v)); err != nil { return err } + if len(*v) != 16 { return fmt.Errorf("invalid IPv6 address, want 16 bytes: %v", *v) } + return nil } @@ -178,6 +189,7 @@ func (err *KeyError) Error() string { if err.Err == errNotFound { return fmt.Sprintf("missing ENR key %q", err.Key) } + return fmt.Sprintf("ENR key %q: %v", err.Key, err.Err) } diff --git a/p2p/message.go b/p2p/message.go index 10cb50d8ad..b0052ab7d1 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -56,6 +56,7 @@ func (msg Msg) Decode(val interface{}) error { if err := s.Decode(val); err != nil { return newPeerError(errInvalidMsg, "(code %x) (size %d) %v", msg.Code, msg.Size, err) } + return nil } @@ -101,6 +102,7 @@ func Send(w MsgWriter, msgcode uint64, data interface{}) error { if err != nil { return err } + return w.WriteMsg(Msg{Code: msgcode, Size: uint32(size), Payload: r}) } @@ -133,6 +135,7 @@ func (r *eofSignal) Read(buf []byte) (int, error) { r.eof <- struct{}{} r.eof = nil } + return 0, io.EOF } @@ -140,12 +143,15 @@ func (r *eofSignal) Read(buf []byte) (int, error) { if int(r.count) < len(buf) { max = int(r.count) } + n, err := r.wrapped.Read(buf[:max]) r.count -= uint32(n) + if (err != nil || r.count == 0) && r.eof != nil { r.eof <- struct{}{} // tell Peer that msg has been consumed r.eof = nil } + return n, err } @@ -160,6 +166,7 @@ func MsgPipe() (*MsgPipeRW, *MsgPipeRW) { rw1 = &MsgPipeRW{c1, c2, closing, closed} rw2 = &MsgPipeRW{c2, c1, closing, closed} ) + return rw1, rw2 } @@ -190,10 +197,12 @@ func (p *MsgPipeRW) WriteMsg(msg Msg) error { case <-p.closing: } } + return nil case <-p.closing: } } + return ErrPipeClosed } @@ -206,6 +215,7 @@ func (p *MsgPipeRW) ReadMsg() (Msg, error) { case <-p.closing: } } + return Msg{}, ErrPipeClosed } @@ -218,7 +228,9 @@ func (p *MsgPipeRW) Close() error { atomic.StoreInt32(p.closed, 1) // avoid overflow return nil } + close(p.closing) + return nil } @@ -230,16 +242,20 @@ func ExpectMsg(r MsgReader, code uint64, content interface{}) error { if err != nil { return err } + if msg.Code != code { return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, code) } + if content == nil { return msg.Discard() } + contentEnc, err := rlp.EncodeToBytes(content) if err != nil { panic("content encode error: " + err.Error()) } + if int(msg.Size) != len(contentEnc) { return fmt.Errorf("message size mismatch: got %d, want %d", msg.Size, len(contentEnc)) } @@ -248,9 +264,11 @@ func ExpectMsg(r MsgReader, code uint64, content interface{}) error { if err != nil { return err } + if !bytes.Equal(actualContent, contentEnc) { return fmt.Errorf("message payload mismatch:\ngot: %x\nwant: %x", actualContent, contentEnc) } + return nil } @@ -286,6 +304,7 @@ func (ev *msgEventer) ReadMsg() (Msg, error) { if err != nil { return msg, err } + ev.feed.Send(&PeerEvent{ Type: PeerEventTypeMsgRecv, Peer: ev.peerID, @@ -295,6 +314,7 @@ func (ev *msgEventer) ReadMsg() (Msg, error) { LocalAddress: ev.localAddress, RemoteAddress: ev.remoteAddress, }) + return msg, nil } @@ -305,6 +325,7 @@ func (ev *msgEventer) WriteMsg(msg Msg) error { if err != nil { return err } + ev.feed.Send(&PeerEvent{ Type: PeerEventTypeMsgSend, Peer: ev.peerID, @@ -314,6 +335,7 @@ func (ev *msgEventer) WriteMsg(msg Msg) error { LocalAddress: ev.localAddress, RemoteAddress: ev.remoteAddress, }) + return nil } @@ -323,5 +345,6 @@ func (ev *msgEventer) Close() error { if v, ok := ev.MsgReadWriter.(io.Closer); ok { return v.Close() } + return nil } diff --git a/p2p/message_test.go b/p2p/message_test.go index e575c5d96e..9dd1534e6d 100644 --- a/p2p/message_test.go +++ b/p2p/message_test.go @@ -38,7 +38,9 @@ func ExampleMsgPipe() { if err != nil { break } + var data [][]byte + msg.Decode(&data) fmt.Printf("msg: %d, %x\n", msg.Code, data[0]) } @@ -90,6 +92,7 @@ func TestEOFSignal(t *testing.T) { // empty reader eof := make(chan struct{}, 1) + sig := &eofSignal{new(bytes.Buffer), 0, eof} if n, err := sig.Read(rb); n != 0 || err != io.EOF { t.Errorf("Read returned unexpected values: (%v, %v)", n, err) @@ -102,6 +105,7 @@ func TestEOFSignal(t *testing.T) { // count before error eof = make(chan struct{}, 1) + sig = &eofSignal{bytes.NewBufferString("aaaaaaaa"), 4, eof} if n, err := sig.Read(rb); n != 4 || err != nil { t.Errorf("Read returned unexpected values: (%v, %v)", n, err) @@ -114,10 +118,12 @@ func TestEOFSignal(t *testing.T) { // error before count eof = make(chan struct{}, 1) + sig = &eofSignal{bytes.NewBufferString("aaaa"), 999, eof} if n, err := sig.Read(rb); n != 4 || err != nil { t.Errorf("Read returned unexpected values: (%v, %v)", n, err) } + if n, err := sig.Read(rb); n != 0 || err != io.EOF { t.Errorf("Read returned unexpected values: (%v, %v)", n, err) } @@ -129,6 +135,7 @@ func TestEOFSignal(t *testing.T) { // no signal if neither occurs eof = make(chan struct{}, 1) + sig = &eofSignal{bytes.NewBufferString("aaaaaaaaaaaaaaaaaaaaa"), 999, eof} if n, err := sig.Read(rb); n != 10 || err != nil { t.Errorf("Read returned unexpected values: (%v, %v)", n, err) diff --git a/p2p/metrics.go b/p2p/metrics.go index 1bb505cdfb..a8348a8b54 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -63,7 +63,9 @@ func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn { } else { egressConnectMeter.Mark(1) } + activePeerGauge.Inc(1) + return &meteredConn{Conn: conn} } @@ -72,6 +74,7 @@ func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn { func (c *meteredConn) Read(b []byte) (n int, err error) { n, err = c.Conn.Read(b) ingressTrafficMeter.Mark(int64(n)) + return n, err } @@ -80,6 +83,7 @@ func (c *meteredConn) Read(b []byte) (n int, err error) { func (c *meteredConn) Write(b []byte) (n int, err error) { n, err = c.Conn.Write(b) egressTrafficMeter.Mark(int64(n)) + return n, err } @@ -90,5 +94,6 @@ func (c *meteredConn) Close() error { if err == nil { activePeerGauge.Dec(1) } + return err } diff --git a/p2p/msgrate/msgrate.go b/p2p/msgrate/msgrate.go index c9010e3ed3..b607eb7b8e 100644 --- a/p2p/msgrate/msgrate.go +++ b/p2p/msgrate/msgrate.go @@ -142,6 +142,7 @@ func NewTracker(caps map[uint64]float64, rtt time.Duration) *Tracker { if caps == nil { caps = make(map[uint64]float64) } + return &Tracker{ capacity: caps, roundtrip: rtt, @@ -192,6 +193,7 @@ func (t *Tracker) Update(kind uint64, elapsed time.Duration, items int) { if elapsed <= 0 { elapsed = 1 // +1 (ns) to ensure non-zero divisor } + measured := float64(items) / (float64(elapsed) / float64(time.Second)) t.capacity[kind] = (1-measurementImpact)*(t.capacity[kind]) + measurementImpact*measured @@ -254,6 +256,7 @@ func (t *Trackers) Track(id string, tracker *Tracker) error { if _, ok := t.trackers[id]; ok { return errors.New("already tracking") } + t.trackers[id] = tracker t.detune() @@ -268,7 +271,9 @@ func (t *Trackers) Untrack(id string) error { if _, ok := t.trackers[id]; !ok { return errors.New("not tracking") } + delete(t.trackers, id) + return nil } @@ -288,11 +293,13 @@ func (t *Trackers) MedianRoundTrip() time.Duration { func (t *Trackers) medianRoundTrip() time.Duration { // Gather all the currently measured round trip times rtts := make([]float64, 0, len(t.trackers)) + for _, tt := range t.trackers { tt.lock.RLock() rtts = append(rtts, float64(tt.roundtrip)) tt.lock.RUnlock() } + sort.Float64s(rtts) var median time.Duration @@ -310,9 +317,11 @@ func (t *Trackers) medianRoundTrip() time.Duration { if median < rttMinEstimate { median = rttMinEstimate } + if median > rttMaxEstimate { median = rttMaxEstimate } + return median } @@ -331,6 +340,7 @@ func (t *Trackers) MeanCapacities() map[uint64]float64 { // debug logging. func (t *Trackers) meanCapacities() map[uint64]float64 { capacities := make(map[uint64]float64) + for _, tt := range t.trackers { tt.lock.RLock() for key, val := range tt.capacity { @@ -338,9 +348,11 @@ func (t *Trackers) meanCapacities() map[uint64]float64 { } tt.lock.RUnlock() } + for key, val := range capacities { capacities[key] = val / float64(len(t.trackers)) } + return capacities } @@ -382,6 +394,7 @@ func (t *Trackers) targetTimeout() time.Duration { if timeout > t.OverrideTTLLimit { timeout = t.OverrideTTLLimit } + return timeout } @@ -394,6 +407,7 @@ func (t *Trackers) tune() { t.lock.RLock() dirty := time.Since(t.tuned) > t.roundtrip t.lock.RUnlock() + if !dirty { return } @@ -435,6 +449,7 @@ func (t *Trackers) detune() { if t.confidence < rttMinConfidence { t.confidence = rttMinConfidence } + t.log.Debug("Relaxed msgrate QoS values", "rtt", t.roundtrip, "confidence", t.confidence, "ttl", t.targetTimeout()) } @@ -448,6 +463,7 @@ func (t *Trackers) Capacity(id string, kind uint64, targetRTT time.Duration) int if tracker == nil { return 1 // Unregister race, don't return 0, it's a dangerous number } + return tracker.Capacity(kind, targetRTT) } diff --git a/p2p/msgrate/msgrate_test.go b/p2p/msgrate/msgrate_test.go index a5c8dd0518..1a67797f52 100644 --- a/p2p/msgrate/msgrate_test.go +++ b/p2p/msgrate/msgrate_test.go @@ -21,6 +21,7 @@ import "testing" func TestCapacityOverflow(t *testing.T) { tracker := NewTracker(nil, 1) tracker.Update(1, 1, 100000) + cap := tracker.Capacity(1, 10000000) if int32(cap) < 0 { t.Fatalf("Negative: %v", int32(cap)) diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index ad4c36582a..9d0eac42e5 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -65,12 +65,14 @@ func Parse(spec string) (Interface, error) { mech = strings.ToLower(parts[0]) ip net.IP ) + if len(parts) > 1 { ip = net.ParseIP(parts[1]) if ip == nil { return nil, errors.New("invalid IP address") } } + switch mech { case "", "none", "off": return nil, nil @@ -80,6 +82,7 @@ func Parse(spec string) (Interface, error) { if ip == nil { return nil, errors.New("missing IP address") } + return ExtIP(ip), nil case "upnp": return UPnP(), nil @@ -99,16 +102,19 @@ const ( func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) { log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m) refresh := time.NewTimer(mapTimeout) + defer func() { refresh.Stop() log.Debug("Deleting port mapping") m.DeleteMapping(protocol, extport, intport) }() + if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { log.Debug("Couldn't add port mapping", "err", err) } else { log.Info("Mapped network port") } + for { select { case _, ok := <-c: @@ -117,9 +123,11 @@ func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, } case <-refresh.C: log.Trace("Refreshing port mapping") + if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { log.Debug("Couldn't add port mapping", "err", err) } + refresh.Reset(mapTimeout) } } @@ -147,11 +155,13 @@ func Any() Interface { found := make(chan Interface, 2) go func() { found <- discoverUPnP() }() go func() { found <- discoverPMP() }() + for i := 0; i < cap(found); i++ { if c := <-found; c != nil { return c } } + return nil }) } @@ -169,6 +179,7 @@ func PMP(gateway net.IP) Interface { if gateway != nil { return &pmp{gw: gateway, c: natpmp.NewClient(gateway)} } + return startautodisc("NAT-PMP", discoverPMP) } @@ -197,6 +208,7 @@ func (n *autodisc) AddMapping(protocol string, extport, intport int, name string if err := n.wait(); err != nil { return err } + return n.found.AddMapping(protocol, extport, intport, name, lifetime) } @@ -204,6 +216,7 @@ func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error { if err := n.wait(); err != nil { return err } + return n.found.DeleteMapping(protocol, extport, intport) } @@ -211,15 +224,18 @@ func (n *autodisc) ExternalIP() (net.IP, error) { if err := n.wait(); err != nil { return nil, err } + return n.found.ExternalIP() } func (n *autodisc) String() string { n.mu.Lock() defer n.mu.Unlock() + if n.found == nil { return n.what } + return n.found.String() } @@ -230,8 +246,10 @@ func (n *autodisc) wait() error { n.found = n.doit() n.mu.Unlock() }) + if n.found == nil { return fmt.Errorf("no %s router discovered", n.what) } + return nil } diff --git a/p2p/nat/nat_test.go b/p2p/nat/nat_test.go index 814e6d9e14..71325ddc61 100644 --- a/p2p/nat/nat_test.go +++ b/p2p/nat/nat_test.go @@ -36,6 +36,7 @@ func TestAutoDiscRace(t *testing.T) { ip net.IP err error } + results := make(chan rval, 50) for i := 0; i < cap(results); i++ { go func() { @@ -46,6 +47,7 @@ func TestAutoDiscRace(t *testing.T) { // Check that they all return the correct result within the deadline. deadline := time.After(2 * time.Second) + for i := 0; i < cap(results); i++ { select { case <-deadline: @@ -54,6 +56,7 @@ func TestAutoDiscRace(t *testing.T) { if rval.err != nil { t.Errorf("result %d: unexpected error: %v", i, rval.err) } + wantIP := net.IP{33, 44, 55, 66} if !rval.ip.Equal(wantIP) { t.Errorf("result %d: got IP %v, want %v", i, rval.ip, wantIP) diff --git a/p2p/nat/natpmp.go b/p2p/nat/natpmp.go index 91de9fec58..3cb55af62e 100644 --- a/p2p/nat/natpmp.go +++ b/p2p/nat/natpmp.go @@ -41,6 +41,7 @@ func (n *pmp) ExternalIP() (net.IP, error) { if err != nil { return nil, err } + return response.ExternalIPAddress[:], nil } @@ -80,6 +81,7 @@ func discoverPMP() Interface { // run external address lookups on all potential gateways gws := potentialGateways() found := make(chan *pmp, len(gws)) + for i := range gws { gw := gws[i] go func() { @@ -96,6 +98,7 @@ func discoverPMP() Interface { // any responses after a very short timeout. timeout := time.NewTimer(1 * time.Second) defer timeout.Stop() + for range gws { select { case c := <-found: @@ -106,6 +109,7 @@ func discoverPMP() Interface { return nil } } + return nil } @@ -116,11 +120,13 @@ func potentialGateways() (gws []net.IP) { if err != nil { return nil } + for _, iface := range ifaces { ifaddrs, err := iface.Addrs() if err != nil { return gws } + for _, addr := range ifaddrs { if x, ok := addr.(*net.IPNet); ok { if x.IP.IsPrivate() { @@ -133,5 +139,6 @@ func potentialGateways() (gws []net.IP) { } } } + return gws } diff --git a/p2p/nat/natupnp.go b/p2p/nat/natupnp.go index 213885d4eb..6a23dc96b4 100644 --- a/p2p/nat/natupnp.go +++ b/p2p/nat/natupnp.go @@ -51,16 +51,20 @@ type upnpClient interface { func (n *upnp) natEnabled() bool { var ok bool + var err error + n.withRateLimit(func() error { _, ok, err = n.client.GetNATRSIPStatus() return err }) + return err == nil && ok } func (n *upnp) ExternalIP() (addr net.IP, err error) { var ipString string + n.withRateLimit(func() error { ipString, err = n.client.GetExternalIPAddress() return err @@ -69,10 +73,12 @@ func (n *upnp) ExternalIP() (addr net.IP, err error) { if err != nil { return nil, err } + ip := net.ParseIP(ipString) if ip == nil { return nil, errors.New("bad IP in response") } + return ip, nil } @@ -82,8 +88,10 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li //nolint:nilerr return nil // TODO: Shouldn't we return the error? } + protocol = strings.ToUpper(protocol) lifetimeS := uint32(lifetime / time.Second) + n.DeleteMapping(protocol, extport, intport) return n.withRateLimit(func() error { @@ -96,21 +104,25 @@ func (n *upnp) internalAddress() (net.IP, error) { if err != nil { return nil, err } + ifaces, err := net.Interfaces() if err != nil { return nil, err } + for _, iface := range ifaces { addrs, err := iface.Addrs() if err != nil { return nil, err } + for _, addr := range addrs { if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) { return x.IP, nil } } } + return nil, fmt.Errorf("could not find local address in same net as %v", devaddr) } @@ -132,8 +144,10 @@ func (n *upnp) withRateLimit(fn func() error) error { if lastreq < rateLimit { time.Sleep(rateLimit - lastreq) } + err := fn() n.lastReqTime = time.Now() + return err } @@ -149,6 +163,7 @@ func discoverUPnP() Interface { case internetgateway1.URN_WANPPPConnection_1: return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}} } + return nil }) // IGDv2 @@ -161,13 +176,16 @@ func discoverUPnP() Interface { case internetgateway2.URN_WANPPPConnection_1: return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}} } + return nil }) + for i := 0; i < cap(found); i++ { if c := <-found; c != nil { return c } } + return nil } @@ -180,11 +198,13 @@ func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient out <- nil return } + found := false for i := 0; i < len(devs) && !found; i++ { if devs[i].Root == nil { continue } + devs[i].Root.Device.VisitServices(func(service *goupnp.Service) { if found { return @@ -197,19 +217,23 @@ func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient Service: service, } sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout + upnp := matcher(sc) if upnp == nil { return } + upnp.dev = devs[i].Root // check whether port mapping is enabled if upnp.natEnabled() { out <- upnp + found = true } }) } + if !found { out <- nil } diff --git a/p2p/nat/natupnp_test.go b/p2p/nat/natupnp_test.go index 9072451d50..c9cb188f5e 100644 --- a/p2p/nat/natupnp_test.go +++ b/p2p/nat/natupnp_test.go @@ -157,7 +157,9 @@ func TestUPNP_DDWRT(t *testing.T) { if err := dev.listen(); err != nil { t.Skipf("cannot listen: %v", err) } + dev.serve() + defer dev.close() // Attempt to discover the fake device. @@ -169,10 +171,12 @@ func TestUPNP_DDWRT(t *testing.T) { t.Skipf("UPnP not discovered (known issue, see https://github.com/ethereum/go-ethereum/issues/21476)") } } + upnp, _ := discovered.(*upnp) if upnp.service != "IGDv1-IP1" { t.Errorf("upnp.service mismatch: got %q, want %q", upnp.service, "IGDv1-IP1") } + wantURL := "http://" + dev.listener.Addr().String() + "/InternetGatewayDevice.xml" if upnp.dev.URLBaseStr != wantURL { t.Errorf("upnp.dev.URLBaseStr mismatch: got %q, want %q", upnp.dev.URLBaseStr, wantURL) @@ -202,11 +206,13 @@ type fakeIGD struct { // httpu.Handler func (dev *fakeIGD) ServeMessage(r *http.Request) { dev.t.Logf(`HTTPU request %s %s`, r.Method, r.RequestURI) + conn, err := net.Dial("udp4", r.RemoteAddr) if err != nil { fmt.Printf("reply Dial error: %v", err) return } + defer conn.Close() io.WriteString(conn, dev.replaceListenAddr(dev.ssdpResp)) } @@ -230,11 +236,13 @@ func (dev *fakeIGD) listen() (err error) { if dev.listener, err = net.Listen("tcp", "127.0.0.1:0"); err != nil { return err } + laddr := &net.UDPAddr{IP: net.ParseIP("239.255.255.250"), Port: 1900} if dev.mcastListener, err = net.ListenMulticastUDP("udp", nil, laddr); err != nil { dev.listener.Close() return err } + return nil } diff --git a/p2p/netutil/error.go b/p2p/netutil/error.go index 5d3d9bfd65..e73ae26e00 100644 --- a/p2p/netutil/error.go +++ b/p2p/netutil/error.go @@ -21,6 +21,7 @@ func IsTemporaryError(err error) bool { tempErr, ok := err.(interface { Temporary() bool }) + return ok && tempErr.Temporary() || isPacketTooBig(err) } @@ -29,5 +30,6 @@ func IsTimeout(err error) bool { timeoutErr, ok := err.(interface { Timeout() bool }) + return ok && timeoutErr.Timeout() } diff --git a/p2p/netutil/error_test.go b/p2p/netutil/error_test.go index 84d5c2c206..f09eac94d8 100644 --- a/p2p/netutil/error_test.go +++ b/p2p/netutil/error_test.go @@ -30,39 +30,50 @@ func TestIsPacketTooBig(t *testing.T) { if err != nil { t.Fatal(err) } + defer listener.Close() + sender, err := net.Dial("udp", listener.LocalAddr().String()) if err != nil { t.Fatal(err) } + defer sender.Close() sendN := 1800 recvN := 300 + for i := 0; i < 20; i++ { go func() { buf := make([]byte, sendN) for i := range buf { buf[i] = byte(i) } + sender.Write(buf) }() buf := make([]byte, recvN) + listener.SetDeadline(time.Now().Add(1 * time.Second)) + n, _, err := listener.ReadFrom(buf) if err != nil { if nerr, ok := err.(net.Error); ok && nerr.Timeout() { continue } + if !isPacketTooBig(err) { t.Fatalf("unexpected read error: %v", err) } + continue } + if n != recvN { t.Fatalf("short read: %d, want %d", n, recvN) } + for i := range buf { if buf[i] != byte(i) { t.Fatalf("error in pattern") diff --git a/p2p/netutil/iptrack.go b/p2p/netutil/iptrack.go index b9cbd5e1ca..4c5cf0342b 100644 --- a/p2p/netutil/iptrack.go +++ b/p2p/netutil/iptrack.go @@ -65,11 +65,13 @@ func (it *IPTracker) PredictFullConeNAT() bool { now := it.clock.Now() it.gcContact(now) it.gcStatements(now) + for host, st := range it.statements { if c, ok := it.contact[host]; !ok || c > st.time { return true } } + return false } @@ -80,13 +82,16 @@ func (it *IPTracker) PredictEndpoint() string { // The current strategy is simple: find the endpoint with most statements. counts := make(map[string]int) maxcount, max := 0, "" + for _, s := range it.statements { c := counts[s.endpoint] + 1 counts[s.endpoint] = c + if c > maxcount && c >= it.minStatements { maxcount, max = c, s.endpoint } } + return max } @@ -94,6 +99,7 @@ func (it *IPTracker) PredictEndpoint() string { func (it *IPTracker) AddStatement(host, endpoint string) { now := it.clock.Now() it.statements[host] = ipStatement{endpoint, now} + if time.Duration(now-it.lastStatementGC) >= it.window { it.gcStatements(now) } @@ -104,6 +110,7 @@ func (it *IPTracker) AddStatement(host, endpoint string) { func (it *IPTracker) AddContact(host string) { now := it.clock.Now() it.contact[host] = now + if time.Duration(now-it.lastContactGC) >= it.contactWindow { it.gcContact(now) } @@ -112,6 +119,7 @@ func (it *IPTracker) AddContact(host string) { func (it *IPTracker) gcStatements(now mclock.AbsTime) { it.lastStatementGC = now cutoff := now.Add(-it.window) + for host, s := range it.statements { if s.time < cutoff { delete(it.statements, host) @@ -122,6 +130,7 @@ func (it *IPTracker) gcStatements(now mclock.AbsTime) { func (it *IPTracker) gcContact(now mclock.AbsTime) { it.lastContactGC = now cutoff := now.Add(-it.contactWindow) + for host, ct := range it.contact { if ct < cutoff { delete(it.contact, host) diff --git a/p2p/netutil/iptrack_test.go b/p2p/netutil/iptrack_test.go index f975e6651c..bf0bf2e905 100644 --- a/p2p/netutil/iptrack_test.go +++ b/p2p/netutil/iptrack_test.go @@ -87,10 +87,13 @@ func runIPTrackerTest(t *testing.T, evs []iptrackTestEvent) { clock mclock.Simulated it = NewIPTracker(10*time.Second, 10*time.Second, 3) ) + it.clock = &clock + for i, ev := range evs { evtime := time.Duration(ev.time) * time.Millisecond clock.Run(evtime - time.Duration(clock.Now())) + switch ev.op { case opStatement: it.AddStatement(ev.from, ev.ip) @@ -118,6 +121,7 @@ func TestIPTrackerForceGC(t *testing.T) { max = int(window/rate) + 1 it = NewIPTracker(window, window, 3) ) + it.clock = &clock for i := 0; i < 5*max; i++ { @@ -129,9 +133,11 @@ func TestIPTrackerForceGC(t *testing.T) { it.AddContact(string(e1)) clock.Run(rate) } + if len(it.contact) > 2*max { t.Errorf("contacts not GCed, have %d", len(it.contact)) } + if len(it.statements) > 2*max { t.Errorf("statements not GCed, have %d", len(it.statements)) } diff --git a/p2p/netutil/net.go b/p2p/netutil/net.go index d5da3c694f..7fc357b8fb 100644 --- a/p2p/netutil/net.go +++ b/p2p/netutil/net.go @@ -74,16 +74,20 @@ func ParseNetlist(s string) (*Netlist, error) { ws := strings.NewReplacer(" ", "", "\n", "", "\t", "") masks := strings.Split(ws.Replace(s), ",") l := make(Netlist, 0) + for _, mask := range masks { if mask == "" { continue } + _, n, err := net.ParseCIDR(mask) if err != nil { return nil, err } + l = append(l, *n) } + return &l, nil } @@ -93,6 +97,7 @@ func (l Netlist) MarshalTOML() interface{} { for _, net := range l { list = append(list, net.String()) } + return list } @@ -102,13 +107,16 @@ func (l *Netlist) UnmarshalTOML(fn func(interface{}) error) error { if err := fn(&masks); err != nil { return err } + for _, mask := range masks { _, n, err := net.ParseCIDR(mask) if err != nil { return err } + *l = append(*l, *n) } + return nil } @@ -119,6 +127,7 @@ func (l *Netlist) Add(cidr string) { if err != nil { panic(err) } + *l = append(*l, *n) } @@ -127,11 +136,13 @@ func (l *Netlist) Contains(ip net.IP) bool { if l == nil { return false } + for _, net := range *l { if net.Contains(ip) { return true } } + return false } @@ -140,9 +151,11 @@ func IsLAN(ip net.IP) bool { if ip.IsLoopback() { return true } + if v4 := ip.To4(); v4 != nil { return lan4.Contains(v4) } + return lan6.Contains(ip) } @@ -152,9 +165,11 @@ func IsSpecialNetwork(ip net.IP) bool { if ip.IsMulticast() { return true } + if v4 := ip.To4(); v4 != nil { return special4.Contains(v4) } + return special6.Contains(ip) } @@ -178,24 +193,30 @@ func CheckRelayIP(sender, addr net.IP) error { if len(addr) != net.IPv4len && len(addr) != net.IPv6len { return errInvalid } + if addr.IsUnspecified() { return errUnspecified } + if IsSpecialNetwork(addr) { return errSpecial } + if addr.IsLoopback() && !sender.IsLoopback() { return errLoopback } + if IsLAN(addr) && !IsLAN(sender) { return errLAN } + return nil } // SameNet reports whether two IP addresses have an equal prefix of the given bit length. func SameNet(bits uint, ip, other net.IP) bool { ip4, other4 := ip.To4(), other.To4() + switch { case (ip4 == nil) != (other4 == nil): return false @@ -209,9 +230,11 @@ func SameNet(bits uint, ip, other net.IP) bool { func sameNet(bits uint, ip, other net.IP) bool { nb := int(bits / 8) mask := ^byte(0xFF >> (bits % 8)) + if mask != 0 && nb < len(ip) && ip[nb]&mask != other[nb]&mask { return false } + return nb <= len(ip) && ip[:nb].Equal(other[:nb]) } @@ -229,11 +252,13 @@ type DistinctNetSet struct { // number of existing IPs in the defined range exceeds the limit. func (s *DistinctNetSet) Add(ip net.IP) bool { key := s.key(ip) + n := s.members[string(key)] if n < s.Limit { s.members[string(key)] = n + 1 return true } + return false } @@ -253,6 +278,7 @@ func (s *DistinctNetSet) Remove(ip net.IP) { func (s DistinctNetSet) Contains(ip net.IP) bool { key := s.key(ip) _, ok := s.members[string(key)] + return ok } @@ -262,6 +288,7 @@ func (s DistinctNetSet) Len() int { for _, i := range s.members { n += i } + return int(n) } @@ -280,6 +307,7 @@ func (s *DistinctNetSet) key(ip net.IP) net.IP { if ip4 := ip.To4(); ip4 != nil { typ, ip = '4', ip4 } + bits := s.Subnet if bits > uint(len(ip)*8) { bits = uint(len(ip) * 8) @@ -288,22 +316,28 @@ func (s *DistinctNetSet) key(ip net.IP) net.IP { nb := int(bits / 8) mask := ^byte(0xFF >> (bits % 8)) s.buf[0] = typ + buf := append(s.buf[:1], ip[:nb]...) if nb < len(ip) && mask != 0 { buf = append(buf, ip[nb]&mask) } + return buf } // String implements fmt.Stringer func (s DistinctNetSet) String() string { var buf bytes.Buffer + buf.WriteString("{") + keys := make([]string, 0, len(s.members)) for k := range s.members { keys = append(keys, k) } + sort.Strings(keys) + for i, k := range keys { var ip net.IP if k[0] == '4' { @@ -311,12 +345,16 @@ func (s DistinctNetSet) String() string { } else { ip = make(net.IP, 16) } + copy(ip, k[1:]) fmt.Fprintf(&buf, "%v×%d", ip, s.members[k]) + if i != len(keys)-1 { buf.WriteString(" ") } } + buf.WriteString("}") + return buf.String() } diff --git a/p2p/netutil/net_test.go b/p2p/netutil/net_test.go index 3a6aa081f2..34493a226d 100644 --- a/p2p/netutil/net_test.go +++ b/p2p/netutil/net_test.go @@ -60,6 +60,7 @@ func TestParseNetlist(t *testing.T) { t.Errorf("%q: got error %q, want %q", test.input, err, test.wantErr) continue } + if !reflect.DeepEqual(l, test.wantList) { spew.Dump(l) spew.Dump(test.wantList) @@ -70,6 +71,7 @@ func TestParseNetlist(t *testing.T) { func TestNilNetListContains(t *testing.T) { var list *Netlist + checkContains(t, list.Contains, nil, []string{"1.2.3.4"}) } @@ -121,6 +123,7 @@ func checkContains(t *testing.T, fn func(net.IP) bool, inc, exc []string) { t.Error("returned false for included address", s) } } + for _, s := range exc { if fn(parseIP(s)) { t.Error("returned true for excluded address", s) @@ -133,6 +136,7 @@ func parseIP(s string) net.IP { if ip == nil { panic("invalid " + s) } + return ip } @@ -169,6 +173,7 @@ func TestCheckRelayIP(t *testing.T) { func BenchmarkCheckRelayIP(b *testing.B) { sender := parseIP("23.55.1.242") addr := parseIP("23.55.1.2") + for i := 0; i < b.N; i++ { CheckRelayIP(sender, addr) } @@ -228,6 +233,7 @@ func TestDistinctNetSet(t *testing.T) { } set := DistinctNetSet{Subnet: 15, Limit: 2} + for _, op := range ops { var desc string if op.add != "" { @@ -239,6 +245,7 @@ func TestDistinctNetSet(t *testing.T) { desc = fmt.Sprintf("Remove(%s)", op.remove) set.Remove(parseIP(op.remove)) } + t.Logf("%s: %v", desc, set) } } @@ -250,9 +257,11 @@ func TestDistinctNetSetAddRemove(t *testing.T) { for _, ip := range ips { s.Add(ip) } + for _, ip := range ips { s.Remove(ip) } + return s.Len() == 0 } diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go index 3adcd6c463..6fb41eae88 100644 --- a/p2p/nodestate/nodestate.go +++ b/p2p/nodestate/nodestate.go @@ -186,8 +186,10 @@ func (s *Setup) NewFlag(name string) Flags { if s.flags == nil { s.flags = []flagDefinition{{name: "offline"}} } + f := Flags{mask: bitMask(1) << uint(len(s.flags)), setup: s} s.flags = append(s.flags, flagDefinition{name: name}) + return f } @@ -196,8 +198,10 @@ func (s *Setup) NewPersistentFlag(name string) Flags { if s.flags == nil { s.flags = []flagDefinition{{name: "offline"}} } + f := Flags{mask: bitMask(1) << uint(len(s.flags)), setup: s} s.flags = append(s.flags, flagDefinition{name: name, persistent: true}) + return f } @@ -213,6 +217,7 @@ func (s *Setup) NewField(name string, ftype reflect.Type) Field { name: name, ftype: ftype, }) + return f } @@ -225,6 +230,7 @@ func (s *Setup) NewPersistentField(name string, ftype reflect.Type, encode func( encode: encode, decode: decode, }) + return f } @@ -234,27 +240,35 @@ func flagOp(a, b Flags, trueIfA, trueIfB, trueIfBoth bool) Flags { if a.mask != 0 { panic("Node state flags have no setup reference") } + a.setup = b.setup } + if b.setup == nil { if b.mask != 0 { panic("Node state flags have no setup reference") } + b.setup = a.setup } + if a.setup != b.setup { panic("Node state flags belong to a different setup") } + res := Flags{setup: a.setup} if trueIfA { res.mask |= a.mask & ^b.mask } + if trueIfB { res.mask |= b.mask & ^a.mask } + if trueIfBoth { res.mask |= a.mask & b.mask } + return res } @@ -287,10 +301,12 @@ func MergeFlags(list ...Flags) Flags { if len(list) == 0 { return Flags{} } + res := list[0] for i := 1; i < len(list); i++ { res = res.Or(list[i]) } + return res } @@ -299,18 +315,23 @@ func (f Flags) String() string { if f.mask == 0 { return "[]" } + s := "[" comma := false + for index, flag := range f.setup.flags { if f.mask&(bitMask(1)< 8*int(unsafe.Sizeof(bitMask(0))) { panic("Too many node state flags") } + ns := &NodeStateMachine{ db: db, dbNodeKey: dbKey, @@ -333,24 +356,30 @@ func NewNodeStateMachine(db ethdb.KeyValueStore, dbKey []byte, clock mclock.Cloc fields: make([]*fieldInfo, len(setup.fields)), } ns.opWait = sync.NewCond(&ns.lock) + stateNameMap := make(map[string]int) for index, flag := range setup.flags { if _, ok := stateNameMap[flag.name]; ok { panic("Node state flag name collision: " + flag.name) } + stateNameMap[flag.name] = index + if flag.persistent { ns.saveFlags |= bitMask(1) << uint(index) } } + fieldNameMap := make(map[string]int) for index, field := range setup.fields { if _, ok := fieldNameMap[field.name]; ok { panic("Node field name collision: " + field.name) } + ns.fields[index] = &fieldInfo{fieldDefinition: field} fieldNameMap[field.name] = index } + return ns } @@ -359,6 +388,7 @@ func (ns *NodeStateMachine) stateMask(flags Flags) bitMask { if flags.setup != ns.setup && flags.mask != 0 { panic("Node state flags belong to a different setup") } + return flags.mask } @@ -367,6 +397,7 @@ func (ns *NodeStateMachine) fieldIndex(field Field) int { if field.setup != ns.setup { panic("Node field belongs to a different setup") } + return field.index } @@ -386,6 +417,7 @@ func (ns *NodeStateMachine) SubscribeState(flags Flags, callback StateCallback) if ns.started { panic("state machine already started") } + ns.stateSubs = append(ns.stateSubs, stateSub{ns.stateMask(flags), callback}) } @@ -397,6 +429,7 @@ func (ns *NodeStateMachine) SubscribeField(field Field, callback FieldCallback) if ns.started { panic("state machine already started") } + f := ns.fields[ns.fieldIndex(field)] f.subs = append(f.subs, callback) } @@ -420,6 +453,7 @@ func (ns *NodeStateMachine) Start() { if ns.started { panic("state machine already started") } + ns.started = true if ns.db != nil { ns.loadFromDb() @@ -437,17 +471,21 @@ func (ns *NodeStateMachine) Stop() { defer ns.lock.Unlock() ns.checkStarted() + if !ns.opStart() { panic("already closed") } + for _, node := range ns.nodes { fields := make([]interface{}, len(node.fields)) copy(fields, node.fields) ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node, node.state, fields}) } + if ns.db != nil { ns.saveToDb() } + ns.offlineCallbacks(false) ns.closed = true ns.opFinish() @@ -462,6 +500,7 @@ func (ns *NodeStateMachine) loadFromDb() { log.Error("Node state db entry with invalid length", "found", len(it.Key()), "expected", len(ns.dbNodeKey)+len(id)) continue } + copy(id[:], it.Key()[len(ns.dbNodeKey):]) ns.decodeNode(id, it.Value()) } @@ -479,6 +518,7 @@ func (ns *NodeStateMachine) decodeNode(id enode.ID, data []byte) { log.Error("Failed to decode node info", "id", id, "error", err) return } + n, _ := enode.New(dummyIdentity(id), &enc.Enr) node := ns.newNode(n) node.db = true @@ -486,8 +526,10 @@ func (ns *NodeStateMachine) decodeNode(id enode.ID, data []byte) { if enc.Version != ns.setup.Version { log.Debug("Removing stored node with unknown version", "current", ns.setup.Version, "stored", enc.Version) ns.deleteNode(id) + return } + if len(enc.Fields) > len(ns.setup.fields) { log.Error("Invalid node field count", "id", id, "stored", len(enc.Fields)) return @@ -497,6 +539,7 @@ func (ns *NodeStateMachine) decodeNode(id enode.ID, data []byte) { if len(encField) == 0 { continue } + if decode := ns.fields[i].decode; decode != nil { if field, err := decode(encField); err == nil { node.fields[i] = field @@ -529,6 +572,7 @@ func (ns *NodeStateMachine) saveNode(id enode.ID, node *nodeInfo) error { for _, t := range node.timeouts { storedState &= ^t.mask } + enc := nodeInfoEnc{ Enr: *node.node.Record(), Version: ns.setup.Version, @@ -536,43 +580,57 @@ func (ns *NodeStateMachine) saveNode(id enode.ID, node *nodeInfo) error { Fields: make([][]byte, len(ns.fields)), } log.Debug("Saved node state", "id", id, "state", Flags{mask: enc.State, setup: ns.setup}) + lastIndex := -1 + for i, f := range node.fields { if f == nil { continue } + encode := ns.fields[i].encode if encode == nil { continue } + blob, err := encode(f) if err != nil { return err } + enc.Fields[i] = blob lastIndex = i } + if storedState == 0 && lastIndex == -1 { if node.db { node.db = false + ns.deleteNode(id) } + node.dirty = false + return nil } + enc.Fields = enc.Fields[:lastIndex+1] + data, err := rlp.EncodeToBytes(&enc) if err != nil { return err } + if err := ns.db.Put(append(ns.dbNodeKey, id[:]...), data); err != nil { return err } + node.dirty, node.db = false, true if ns.saveNodeHook != nil { ns.saveNodeHook(node) } + return nil } @@ -596,11 +654,13 @@ func (ns *NodeStateMachine) saveToDb() { // updateEnode updates the enode entry belonging to the given node if it already exists func (ns *NodeStateMachine) updateEnode(n *enode.Node) (enode.ID, *nodeInfo) { id := n.ID() + node := ns.nodes[id] if node != nil && n.Seq() > node.node.Seq() { node.node = n node.dirty = true } + return id, node } @@ -610,13 +670,16 @@ func (ns *NodeStateMachine) Persist(n *enode.Node) error { defer ns.lock.Unlock() ns.checkStarted() + if id, node := ns.updateEnode(n); node != nil && node.dirty { err := ns.saveNode(id, node) if err != nil { log.Error("Failed to save node", "id", id, "error", err) } + return err } + return nil } @@ -629,8 +692,10 @@ func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, if !ns.opStart() { return ErrClosed } + ns.setState(n, setFlags, resetFlags, timeout) ns.opFinish() + return nil } @@ -648,13 +713,16 @@ func (ns *NodeStateMachine) setState(n *enode.Node, setFlags, resetFlags Flags, ns.checkStarted() set, reset := ns.stateMask(setFlags), ns.stateMask(resetFlags) id, node := ns.updateEnode(n) + if node == nil { if set == 0 { return } + node = ns.newNode(n) ns.nodes[id] = node } + oldState := node.state newState := (node.state & (^reset)) | set changed := oldState ^ newState @@ -668,11 +736,14 @@ func (ns *NodeStateMachine) setState(n *enode.Node, setFlags, resetFlags Flags, if timeout != 0 && set != 0 { ns.addTimeout(n, set, timeout) } + if newState == oldState { return } + if newState == 0 && node.fieldCount == 0 { delete(ns.nodes, id) + if node.db { ns.deleteNode(id) } @@ -681,6 +752,7 @@ func (ns *NodeStateMachine) setState(n *enode.Node, setFlags, resetFlags Flags, node.dirty = true } } + callback := func() { for _, sub := range ns.stateSubs { if changed&sub.mask != 0 { @@ -703,10 +775,13 @@ func (ns *NodeStateMachine) opStart() bool { for ns.opFlag { ns.opWait.Wait() } + if ns.closed { return false } + ns.opFlag = true + return true } @@ -718,12 +793,15 @@ func (ns *NodeStateMachine) opFinish() { for len(ns.opPending) != 0 { list := ns.opPending ns.lock.Unlock() + for _, cb := range list { cb() } + ns.lock.Lock() ns.opPending = ns.opPending[len(list):] } + ns.opPending = nil ns.opFlag = false ns.opWait.Broadcast() @@ -736,13 +814,16 @@ func (ns *NodeStateMachine) Operation(fn func()) error { ns.lock.Lock() started := ns.opStart() ns.lock.Unlock() + if !started { return ErrClosed } + fn() ns.lock.Lock() ns.opFinish() ns.lock.Unlock() + return nil } @@ -754,19 +835,23 @@ func (ns *NodeStateMachine) offlineCallbacks(start bool) { for _, sub := range ns.stateSubs { offState := offlineState & sub.mask onState := cb.state & sub.mask + if offState == onState { continue } + if start { sub.callback(cb.node.node, Flags{mask: offState, setup: ns.setup}, Flags{mask: onState, setup: ns.setup}) } else { sub.callback(cb.node.node, Flags{mask: onState, setup: ns.setup}, Flags{mask: offState, setup: ns.setup}) } } + for i, f := range cb.fields { if f == nil || ns.fields[i].subs == nil { continue } + for _, fsub := range ns.fields[i].subs { if start { fsub(cb.node.node, Flags{mask: offlineState, setup: ns.setup}, nil, f) @@ -778,6 +863,7 @@ func (ns *NodeStateMachine) offlineCallbacks(start bool) { } ns.opPending = append(ns.opPending, callback) } + ns.offlineCallbackList = nil } @@ -788,10 +874,13 @@ func (ns *NodeStateMachine) AddTimeout(n *enode.Node, flags Flags, timeout time. defer ns.lock.Unlock() ns.checkStarted() + if ns.closed { return ErrClosed } + ns.addTimeout(n, ns.stateMask(flags), timeout) + return nil } @@ -801,10 +890,12 @@ func (ns *NodeStateMachine) addTimeout(n *enode.Node, mask bitMask, timeout time if node == nil { return } + mask &= node.state if mask == 0 { return } + ns.removeTimeouts(node, mask) t := &nodeStateTimeout{mask: mask} t.timer = ns.clock.AfterFunc(timeout, func() { @@ -814,9 +905,11 @@ func (ns *NodeStateMachine) addTimeout(n *enode.Node, mask bitMask, timeout time if !ns.opStart() { return } + ns.setState(n, Flags{}, Flags{mask: t.mask, setup: ns.setup}, 0) ns.opFinish() }) + node.timeouts = append(node.timeouts, t) if mask&ns.saveFlags != 0 { node.dirty = true @@ -830,18 +923,23 @@ func (ns *NodeStateMachine) addTimeout(n *enode.Node, mask bitMask, timeout time func (ns *NodeStateMachine) removeTimeouts(node *nodeInfo, mask bitMask) { for i := 0; i < len(node.timeouts); i++ { t := node.timeouts[i] + match := t.mask & mask if match == 0 { continue } + t.mask -= match if t.mask != 0 { continue } + t.timer.Stop() + node.timeouts[i] = node.timeouts[len(node.timeouts)-1] node.timeouts = node.timeouts[:len(node.timeouts)-1] i-- + if match&ns.saveFlags != 0 { node.dirty = true } @@ -856,12 +954,15 @@ func (ns *NodeStateMachine) GetField(n *enode.Node, field Field) interface{} { defer ns.lock.Unlock() ns.checkStarted() + if ns.closed { return nil } + if _, node := ns.updateEnode(n); node != nil { return node.fields[ns.fieldIndex(field)] } + return nil } @@ -873,12 +974,15 @@ func (ns *NodeStateMachine) GetState(n *enode.Node) Flags { defer ns.lock.Unlock() ns.checkStarted() + if ns.closed { return Flags{} } + if _, node := ns.updateEnode(n); node != nil { return Flags{mask: node.state, setup: ns.setup} } + return Flags{} } @@ -890,8 +994,10 @@ func (ns *NodeStateMachine) SetField(n *enode.Node, field Field, value interface if !ns.opStart() { return ErrClosed } + err := ns.setField(n, field, value) ns.opFinish() + return err } @@ -902,38 +1008,48 @@ func (ns *NodeStateMachine) SetFieldSub(n *enode.Node, field Field, value interf defer ns.lock.Unlock() ns.opCheck() + return ns.setField(n, field, value) } func (ns *NodeStateMachine) setField(n *enode.Node, field Field, value interface{}) error { ns.checkStarted() + id, node := ns.updateEnode(n) if node == nil { if value == nil { return nil } + node = ns.newNode(n) ns.nodes[id] = node } + fieldIndex := ns.fieldIndex(field) + f := ns.fields[fieldIndex] if value != nil && reflect.TypeOf(value) != f.ftype { log.Error("Invalid field type", "type", reflect.TypeOf(value), "required", f.ftype) return ErrInvalidField } + oldValue := node.fields[fieldIndex] if value == oldValue { return nil } + if oldValue != nil { node.fieldCount-- } + if value != nil { node.fieldCount++ } + node.fields[fieldIndex] = value if node.state == 0 && node.fieldCount == 0 { delete(ns.nodes, id) + if node.db { ns.deleteNode(id) } @@ -942,6 +1058,7 @@ func (ns *NodeStateMachine) setField(n *enode.Node, field Field, value interface node.dirty = true } } + state := node.state callback := func() { for _, cb := range f.subs { @@ -949,6 +1066,7 @@ func (ns *NodeStateMachine) setField(n *enode.Node, field Field, value interface } } ns.opPending = append(ns.opPending, callback) + return nil } @@ -959,18 +1077,23 @@ func (ns *NodeStateMachine) setField(n *enode.Node, field Field, value interface func (ns *NodeStateMachine) ForEach(requireFlags, disableFlags Flags, cb func(n *enode.Node, state Flags)) { ns.lock.Lock() ns.checkStarted() + type callback struct { node *enode.Node state bitMask } + require, disable := ns.stateMask(requireFlags), ns.stateMask(disableFlags) + var callbacks []callback + for _, node := range ns.nodes { if node.state&require == require && node.state&disable == 0 { callbacks = append(callbacks, callback{node.node, node.state & (require | disable)}) } } ns.lock.Unlock() + for _, c := range callbacks { cb(c.node, Flags{mask: c.state, setup: ns.setup}) } @@ -982,9 +1105,11 @@ func (ns *NodeStateMachine) GetNode(id enode.ID) *enode.Node { defer ns.lock.Unlock() ns.checkStarted() + if node := ns.nodes[id]; node != nil { return node.node } + return nil } @@ -992,9 +1117,11 @@ func (ns *NodeStateMachine) GetNode(id enode.ID) *enode.Node { // being in a given set specified by required and disabled state flags func (ns *NodeStateMachine) AddLogMetrics(requireFlags, disableFlags Flags, name string, inMeter, outMeter metrics.Meter, gauge metrics.Gauge) { var count int64 + ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState Flags) { oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags) newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags) + if newMatch == oldMatch { return } @@ -1004,6 +1131,7 @@ func (ns *NodeStateMachine) AddLogMetrics(requireFlags, disableFlags Flags, name if name != "" { log.Debug("Node entered", "set", name, "id", n.ID(), "count", count) } + if inMeter != nil { inMeter.Mark(1) } @@ -1012,10 +1140,12 @@ func (ns *NodeStateMachine) AddLogMetrics(requireFlags, disableFlags Flags, name if name != "" { log.Debug("Node left", "set", name, "id", n.ID(), "count", count) } + if outMeter != nil { outMeter.Mark(1) } } + if gauge != nil { gauge.Update(count) } diff --git a/p2p/nodestate/nodestate_test.go b/p2p/nodestate/nodestate_test.go index d06ad755e2..5286cfd8a2 100644 --- a/p2p/nodestate/nodestate_test.go +++ b/p2p/nodestate/nodestate_test.go @@ -33,6 +33,7 @@ import ( func testSetup(flagPersist []bool, fieldType []reflect.Type) (*Setup, []Flags, []Field) { setup := &Setup{} flags := make([]Flags, len(flagPersist)) + for i, persist := range flagPersist { if persist { flags[i] = setup.NewPersistentFlag(fmt.Sprintf("flag-%d", i)) @@ -40,7 +41,9 @@ func testSetup(flagPersist []bool, fieldType []reflect.Type) (*Setup, []Flags, [ flags[i] = setup.NewFlag(fmt.Sprintf("flag-%d", i)) } } + fields := make([]Field, len(fieldType)) + for i, ftype := range fieldType { switch ftype { case reflect.TypeOf(uint64(0)): @@ -51,6 +54,7 @@ func testSetup(flagPersist []bool, fieldType []reflect.Type) (*Setup, []Flags, [ fields[i] = setup.NewField(fmt.Sprintf("field-%d", i), ftype) } } + return setup, flags, fields } @@ -58,6 +62,7 @@ func testNode(b byte) *enode.Node { r := &enr.Record{} r.SetSig(dummyIdentity{b}, []byte{42}) n, _ := enode.New(dummyIdentity{b}, r) + return n } @@ -70,6 +75,7 @@ func TestCallback(t *testing.T) { set0 := make(chan struct{}, 1) set1 := make(chan struct{}, 1) set2 := make(chan struct{}, 1) + ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) { set0 <- struct{}{} }) ns.SubscribeState(flags[1], func(n *enode.Node, oldState, newState Flags) { set1 <- struct{}{} }) ns.SubscribeState(flags[2], func(n *enode.Node, oldState, newState Flags) { set2 <- struct{}{} }) @@ -146,11 +152,14 @@ func TestSetField(t *testing.T) { // Set field before setting state ns.SetField(testNode(1), fields[0], "hello world") + field := ns.GetField(testNode(1), fields[0]) if field == nil { t.Fatalf("Field should be set before setting states") } + ns.SetField(testNode(1), fields[0], nil) + field = ns.GetField(testNode(1), fields[0]) if field != nil { t.Fatalf("Field should be unset") @@ -158,10 +167,12 @@ func TestSetField(t *testing.T) { // Set field after setting state ns.SetState(testNode(1), flags[0], Flags{}, 0) ns.SetField(testNode(1), fields[0], "hello world") + field = ns.GetField(testNode(1), fields[0]) if field == nil { t.Fatalf("Field should be set after setting states") } + if err := ns.SetField(testNode(1), fields[0], 123); err == nil { t.Fatalf("Invalid field should be rejected") } @@ -181,7 +192,9 @@ func TestSetState(t *testing.T) { ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s) type change struct{ old, new Flags } + set := make(chan change, 1) + ns.SubscribeState(flags[0].Or(flags[1]), func(n *enode.Node, oldState, newState Flags) { set <- change{ old: oldState, @@ -198,11 +211,13 @@ func TestSetState(t *testing.T) { if !c.old.Equals(expectOld) { t.Fatalf("Old state mismatch") } + if !c.new.Equals(expectNew) { t.Fatalf("New state mismatch") } case <-time.After(time.Second): } + return } select { @@ -212,6 +227,7 @@ func TestSetState(t *testing.T) { return } } + ns.SetState(testNode(1), flags[0], Flags{}, 0) check(Flags{}, flags[0], true) @@ -241,12 +257,14 @@ func uint64FieldEnc(field interface{}) ([]byte, error) { enc, err := rlp.EncodeToBytes(&u) return enc, err } + return nil, errors.New("invalid field type") } func uint64FieldDec(enc []byte) (interface{}, error) { var u uint64 err := rlp.DecodeBytes(enc, &u) + return u, err } @@ -254,6 +272,7 @@ func stringFieldEnc(field interface{}) ([]byte, error) { if s, ok := field.(string); ok { return []byte(s), nil } + return nil, errors.New("invalid field type") } @@ -276,10 +295,12 @@ func TestPersistentFields(t *testing.T) { ns2 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s) ns2.Start() + field0 := ns2.GetField(testNode(1), fields[0]) if !reflect.DeepEqual(field0, uint64(100)) { t.Fatalf("Field changed") } + field1 := ns2.GetField(testNode(1), fields[1]) if !reflect.DeepEqual(field1, "hello world") { t.Fatalf("Field changed") @@ -288,6 +309,7 @@ func TestPersistentFields(t *testing.T) { s.Version++ ns3 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s) ns3.Start() + if ns3.GetField(testNode(1), fields[0]) != nil { t.Fatalf("Old field version should have been discarded") } @@ -303,14 +325,17 @@ func TestFieldSub(t *testing.T) { lastState Flags lastOldValue, lastNewValue interface{} ) + ns.SubscribeField(fields[0], func(n *enode.Node, state Flags, oldValue, newValue interface{}) { lastState, lastOldValue, lastNewValue = state, oldValue, newValue }) + check := func(state Flags, oldValue, newValue interface{}) { if !lastState.Equals(state) || lastOldValue != oldValue || lastNewValue != newValue { t.Fatalf("Incorrect field sub callback (expected [%v %v %v], got [%v %v %v])", state, oldValue, newValue, lastState, lastOldValue, lastNewValue) } } + ns.Start() ns.SetState(testNode(1), flags[0], Flags{}, 0) ns.SetField(testNode(1), fields[0], uint64(100)) @@ -337,7 +362,9 @@ func TestDuplicatedFlags(t *testing.T) { ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s) type change struct{ old, new Flags } + set := make(chan change, 1) + ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) { set <- change{oldState, newState} }) @@ -352,11 +379,13 @@ func TestDuplicatedFlags(t *testing.T) { if !c.old.Equals(expectOld) { t.Fatalf("Old state mismatch") } + if !c.new.Equals(expectNew) { t.Fatalf("New state mismatch") } case <-time.After(time.Second): } + return } select { @@ -366,6 +395,7 @@ func TestDuplicatedFlags(t *testing.T) { return } } + ns.SetState(testNode(1), flags[0], Flags{}, time.Second) check(Flags{}, flags[0], true) ns.SetState(testNode(1), flags[0], Flags{}, 2*time.Second) // extend the timeout to 2s @@ -392,11 +422,14 @@ func TestCallbackOrder(t *testing.T) { ns.SetStateSub(n, flags[3], Flags{}, 0) } }) + lastState := Flags{} + ns.SubscribeState(MergeFlags(flags[1], flags[2], flags[3]), func(n *enode.Node, oldState, newState Flags) { if !oldState.Equals(lastState) { t.Fatalf("Wrong callback order") } + lastState = newState }) diff --git a/p2p/peer.go b/p2p/peer.go index 469a1b7974..b0657d6f6e 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -129,11 +129,13 @@ func NewPeer(id enode.ID, name string, caps []Cap) *Peer { protos[i].Name = cap.Name protos[i].Version = cap.Version } + pipe, _ := net.Pipe() node := enode.SignNull(new(enr.Record), id) conn := &conn{fd: pipe, transport: nil, node: node, caps: caps, name: name} peer := newPeer(log.Root(), conn, protos) close(peer.closed) // ensures Disconnect doesn't block + return peer } @@ -143,6 +145,7 @@ func NewPeer(id enode.ID, name string, caps []Cap) *Peer { func NewPeerPipe(id enode.ID, name string, caps []Cap, pipe *MsgPipeRW) *Peer { p := NewPeer(id, name, caps) p.testPipe = pipe + return p } @@ -162,6 +165,7 @@ func (p *Peer) Name() string { if len(s) > 20 { return s[:20] + "..." } + return s } @@ -187,6 +191,7 @@ func (p *Peer) RunningCap(protocol string, versions []uint) bool { } } } + return false } @@ -235,6 +240,7 @@ func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer { closed: make(chan struct{}), log: log.New("id", conn.node.ID(), "conn", conn.flags), } + return p } @@ -249,7 +255,9 @@ func (p *Peer) run() (remoteRequested bool, err error) { readErr = make(chan error, 1) reason DiscReason // sent to the peer ) + p.wg.Add(2) + go p.readLoop(readErr) go p.pingLoop() @@ -289,13 +297,16 @@ loop: close(p.closed) p.rw.close(reason) p.wg.Wait() + return remoteRequested, err } func (p *Peer) pingLoop() { ping := time.NewTimer(pingInterval) + defer p.wg.Done() defer ping.Stop() + for { select { case <-ping.C: @@ -303,6 +314,7 @@ func (p *Peer) pingLoop() { p.protoErr <- err return } + ping.Reset(pingInterval) case <-p.closed: return @@ -312,12 +324,14 @@ func (p *Peer) pingLoop() { func (p *Peer) readLoop(errc chan<- error) { defer p.wg.Done() + for { msg, err := p.rw.ReadMsg() if err != nil { errc <- err return } + msg.ReceivedAt = time.Now() if err = p.handle(msg); err != nil { errc <- err @@ -330,12 +344,15 @@ func (p *Peer) handle(msg Msg) error { switch { case msg.Code == pingMsg: msg.Discard() + go SendItems(p.rw, pongMsg) case msg.Code == discMsg: // This is the last message. We don't need to discard or // check errors because, the connection will be closed after it. var m struct{ R DiscReason } + rlp.Decode(msg.Payload, &m) + return m.R case msg.Code < baseProtocolLength: // ignore other base protocol messages @@ -346,6 +363,7 @@ func (p *Peer) handle(msg Msg) error { if err != nil { return fmt.Errorf("msg code out of range: %v", msg.Code) } + if metrics.Enabled { m := fmt.Sprintf("%s/%s/%d/%#02x", ingressMeterName, proto.Name, proto.Version, msg.Code-proto.offset) metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize)) @@ -358,11 +376,13 @@ func (p *Peer) handle(msg Msg) error { return io.EOF } } + return nil } func countMatchingProtocols(protocols []Protocol, caps []Cap) int { n := 0 + for _, cap := range caps { for _, proto := range protocols { if proto.Name == cap.Name && proto.Version == cap.Version { @@ -370,12 +390,14 @@ func countMatchingProtocols(protocols []Protocol, caps []Cap) int { } } } + return n } // matchProtocols creates structures for matching named subprotocols. func matchProtocols(protocols []Protocol, caps []Cap, rw MsgReadWriter) map[string]*protoRW { sort.Sort(capsByNameAndVersion(caps)) + offset := baseProtocolLength result := make(map[string]*protoRW) @@ -395,26 +417,34 @@ outer: } } } + return result } func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error) { p.wg.Add(len(p.running)) + for _, proto := range p.running { proto := proto proto.closed = p.closed proto.wstart = writeStart proto.werr = writeErr + var rw MsgReadWriter = proto + if p.events != nil { rw = newMsgEventer(rw, p.events, p.ID(), proto.Name, p.Info().Network.RemoteAddress, p.Info().Network.LocalAddress) } + p.log.Trace(fmt.Sprintf("Starting protocol %s/%d", proto.Name, proto.Version)) + go func() { defer p.wg.Done() + err := proto.Run(p, rw) if err == nil { p.log.Trace(fmt.Sprintf("Protocol %s/%d returned", proto.Name, proto.Version)) + err = errProtocolReturned } else if !errors.Is(err, io.EOF) { p.log.Trace(fmt.Sprintf("Protocol %s/%d failed", proto.Name, proto.Version), "err", err) @@ -432,6 +462,7 @@ func (p *Peer) getProto(code uint64) (*protoRW, error) { return proto, nil } } + return nil, newPeerError(errInvalidMsgCode, "%d", code) } @@ -449,6 +480,7 @@ func (rw *protoRW) WriteMsg(msg Msg) (err error) { if msg.Code >= rw.Length { return newPeerError(errInvalidMsgCode, "not handled") } + msg.meterCap = rw.cap() msg.meterCode = msg.Code @@ -465,6 +497,7 @@ func (rw *protoRW) WriteMsg(msg Msg) (err error) { case <-rw.closed: err = ErrShuttingDown } + return err } @@ -515,6 +548,7 @@ func (p *Peer) Info() *PeerInfo { if p.Node().Seq() > 0 { info.ENR = p.Node().String() } + info.Network.LocalAddress = p.LocalAddr().String() info.Network.RemoteAddress = p.RemoteAddr().String() info.Network.Inbound = p.rw.is(inboundConn) @@ -524,6 +558,7 @@ func (p *Peer) Info() *PeerInfo { // Gather all the running protocol infos for _, proto := range p.running { protoInfo := interface{}("unknown") + if query := proto.Protocol.PeerInfo; query != nil { if metadata := query(p.ID()); metadata != nil { protoInfo = metadata @@ -531,7 +566,9 @@ func (p *Peer) Info() *PeerInfo { protoInfo = "handshake" } } + info.Protocols[proto.Name] = protoInfo } + return info } diff --git a/p2p/peer_error.go b/p2p/peer_error.go index ff1a1dfdd4..d6acb3f761 100644 --- a/p2p/peer_error.go +++ b/p2p/peer_error.go @@ -41,10 +41,12 @@ func newPeerError(code int, format string, v ...interface{}) *peerError { if !ok { panic("invalid error code") } + err := &peerError{code, desc} if format != "" { err.message += ": " + fmt.Sprintf(format, v...) } + return err } @@ -93,6 +95,7 @@ func (d DiscReason) String() string { if len(discReasonToString) <= int(d) { return fmt.Sprintf("unknown disconnect reason %d", d) } + return discReasonToString[d] } @@ -108,6 +111,7 @@ func discReasonForError(err error) DiscReason { if errors.Is(err, errProtocolReturned) { return DiscQuitting } + peerError, ok := err.(*peerError) if ok { switch peerError.code { @@ -117,5 +121,6 @@ func discReasonForError(err error) DiscReason { return DiscSubprotocolError } } + return DiscSubprotocolError } diff --git a/p2p/peer_test.go b/p2p/peer_test.go index 4308bbd2eb..56e6d77d40 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -53,13 +53,16 @@ var discard = Protocol{ // uintID encodes i into a node ID. func uintID(i uint16) enode.ID { var id enode.ID + binary.BigEndian.PutUint16(id[:], i) + return id } // newNode creates a node record with the given address. func newNode(id enode.ID, addr string) *enode.Node { var r enr.Record + if addr != "" { // Set the port if present. if strings.Contains(addr, ":") { @@ -67,12 +70,15 @@ func newNode(id enode.ID, addr string) *enode.Node { if err != nil { panic(fmt.Errorf("invalid address %q", addr)) } + port, err := strconv.Atoi(ps) if err != nil { panic(fmt.Errorf("invalid port in %q", addr)) } + r.Set(enr.TCP(port)) r.Set(enr.UDP(port)) + addr = hs } // Set the IP. @@ -80,8 +86,10 @@ func newNode(id enode.ID, addr string) *enode.Node { if ip == nil { panic(fmt.Errorf("invalid IP %q", addr)) } + r.Set(enr.IP(ip)) } + return enode.SignNull(&r, id) } @@ -95,6 +103,7 @@ func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) { c1 := &conn{fd: fd1, node: newNode(uintID(1), ""), transport: t1} c2 := &conn{fd: fd2, node: newNode(uintID(2), ""), transport: t2} + for _, p := range protos { c1.caps = append(c1.caps, p.cap()) c2.caps = append(c2.caps, p.cap()) @@ -102,12 +111,14 @@ func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) { peer := newPeer(log.Root(), c1, protos) errc := make(chan error, 1) + go func() { _, err := peer.run() errc <- err }() closer := func() { c2.close(errors.New("close func called")) } + return closer, c2, peer, errc } @@ -160,6 +171,7 @@ func TestPeerProtoEncodeMsg(t *testing.T) { return nil }, } + closer, rw, _, _ := testPeer([]Protocol{proto}) defer closer() @@ -171,9 +183,11 @@ func TestPeerProtoEncodeMsg(t *testing.T) { func TestPeerPing(t *testing.T) { closer, rw, _, _ := testPeer(nil) defer closer() + if err := SendItems(rw, pingMsg); err != nil { t.Fatal(err) } + if err := ExpectMsg(rw, pongMsg, nil); err != nil { t.Error(err) } @@ -253,12 +267,15 @@ func TestNewPeer(t *testing.T) { caps := []Cap{{"foo", 2}, {"bar", 3}} id := randomID() p := NewPeer(id, name, caps) + if p.ID() != id { t.Errorf("ID mismatch: got %v, expected %v", p.ID(), id) } + if p.Name() != name { t.Errorf("Name mismatch: got %v, expected %v", p.Name(), name) } + if !reflect.DeepEqual(p.Caps(), caps) { t.Errorf("Caps mismatch: got %v, expected %v", p.Caps(), caps) } @@ -341,12 +358,15 @@ func TestMatchProtocols(t *testing.T) { t.Errorf("test %d, proto '%s': negotiated but shouldn't have", i, name) continue } + if proto.Name != match.Name { t.Errorf("test %d, proto '%s': name mismatch: have %v, want %v", i, name, proto.Name, match.Name) } + if proto.Version != match.Version { t.Errorf("test %d, proto '%s': version mismatch: have %v, want %v", i, name, proto.Version, match.Version) } + if proto.offset-baseProtocolLength != match.offset { t.Errorf("test %d, proto '%s': offset mismatch: have %v, want %v", i, name, proto.offset-baseProtocolLength, match.offset) } diff --git a/p2p/rlpx/buffer.go b/p2p/rlpx/buffer.go index bb38e10577..7e1f3ad78a 100644 --- a/p2p/rlpx/buffer.go +++ b/p2p/rlpx/buffer.go @@ -65,8 +65,10 @@ func (b *readBuffer) read(r io.Reader, n int) ([]byte, error) { if err != nil { return nil, err } + b.end += rn b.data = b.data[:offset+n] + return b.data[offset : offset+n], nil } @@ -75,6 +77,7 @@ func (b *readBuffer) grow(n int) { if cap(b.data)-b.end >= n { return } + need := n - (cap(b.data) - b.end) offset := len(b.data) b.data = append(b.data[:cap(b.data)], make([]byte, need)...) @@ -94,6 +97,7 @@ func (b *writeBuffer) reset() { func (b *writeBuffer) appendZero(n int) []byte { offset := len(b.data) b.data = append(b.data, make([]byte, n)...) + return b.data[offset : offset+n] } @@ -120,8 +124,10 @@ func growslice(b []byte, wantLength int) []byte { if len(b) >= wantLength { return b } + if cap(b) >= wantLength { return b[:cap(b)] } + return make([]byte, wantLength) } diff --git a/p2p/rlpx/buffer_test.go b/p2p/rlpx/buffer_test.go index 9fee4172bd..40b7968421 100644 --- a/p2p/rlpx/buffer_test.go +++ b/p2p/rlpx/buffer_test.go @@ -26,6 +26,7 @@ import ( func TestReadBufferReset(t *testing.T) { reader := bytes.NewReader(hexutil.MustDecode("0x010202030303040505")) + var b readBuffer s1, _ := b.read(reader, 1) diff --git a/p2p/rlpx/rlpx.go b/p2p/rlpx/rlpx.go index 8bd6f64b9b..aaf61f723a 100644 --- a/p2p/rlpx/rlpx.go +++ b/p2p/rlpx/rlpx.go @@ -83,9 +83,11 @@ func newHashMAC(cipher cipher.Block, h hash.Hash) hashMAC { if cipher.BlockSize() != len(m.aesBuffer) { panic(fmt.Errorf("invalid MAC cipher block size %d", cipher.BlockSize())) } + if h.Size() != len(m.hashBuffer) { panic(fmt.Errorf("invalid MAC digest size %d", h.Size())) } + return m } @@ -137,25 +139,31 @@ func (c *Conn) Read() (code uint64, data []byte, wireSize int, err error) { if err != nil { return 0, nil, 0, err } + code, data, err = rlp.SplitUint64(frame) if err != nil { return 0, nil, 0, fmt.Errorf("invalid message code: %v", err) } + wireSize = len(data) // If snappy is enabled, verify and decompress message. if c.snappyReadBuffer != nil { var actualSize int + actualSize, err = snappy.DecodedLen(data) if err != nil { return code, nil, 0, err } + if actualSize > maxUint24 { return code, nil, 0, errPlainMessageTooLarge } + c.snappyReadBuffer = growslice(c.snappyReadBuffer, actualSize) data, err = snappy.Decode(c.snappyReadBuffer, data) } + return code, data, wireSize, err } @@ -194,6 +202,7 @@ func (h *sessionState) readFrame(conn io.Reader) ([]byte, error) { if err != nil { return nil, err } + wantFrameMAC := h.ingressMAC.computeFrame(frame) if !hmac.Equal(wantFrameMAC, frameMAC) { return nil, errors.New("bad frame MAC") @@ -201,6 +210,7 @@ func (h *sessionState) readFrame(conn io.Reader) ([]byte, error) { // Decrypt the frame data. h.dec.XORKeyStream(frame, frame) + return frame[:fsize], nil } @@ -212,9 +222,11 @@ func (c *Conn) Write(code uint64, data []byte) (uint32, error) { if c.session == nil { panic("can't WriteMsg before handshake") } + if len(data) > maxUint24 { return 0, errPlainMessageTooLarge } + if c.snappyWriteBuffer != nil { // Ensure the buffer has sufficient size. // Package snappy will allocate its own buffer if the provided @@ -225,6 +237,7 @@ func (c *Conn) Write(code uint64, data []byte) (uint32, error) { wireSize := uint32(len(data)) err := c.session.writeFrame(c.conn, code, data) + return wireSize, err } @@ -236,6 +249,7 @@ func (h *sessionState) writeFrame(conn io.Writer, code uint64, data []byte) erro if fsize > maxUint24 { return errPlainMessageTooLarge } + header := h.wbuf.appendZero(16) putUint24(uint32(fsize), header) copy(header[3:], zeroHeader) @@ -248,9 +262,11 @@ func (h *sessionState) writeFrame(conn io.Writer, code uint64, data []byte) erro offset := len(h.wbuf.data) h.wbuf.data = rlp.AppendUint64(h.wbuf.data, code) h.wbuf.Write(data) + if padding := fsize % 16; padding > 0 { h.wbuf.appendZero(16 - padding) } + framedata := h.wbuf.data[offset:] h.enc.XORKeyStream(framedata, framedata) @@ -258,6 +274,7 @@ func (h *sessionState) writeFrame(conn io.Writer, code uint64, data []byte) erro h.wbuf.Write(h.egressMAC.computeFrame(framedata)) _, err := conn.Write(h.wbuf.data) + return err } @@ -271,6 +288,7 @@ func (m *hashMAC) computeHeader(header []byte) []byte { func (m *hashMAC) computeFrame(framedata []byte) []byte { m.hash.Write(framedata) seed := m.hash.Sum(m.seedBuffer[:0]) + return m.compute(seed, seed[:16]) } @@ -287,11 +305,14 @@ func (m *hashMAC) compute(sum1, seed []byte) []byte { } m.cipher.Encrypt(m.aesBuffer[:], sum1) + for i := range m.aesBuffer { m.aesBuffer[i] ^= seed[i] } + m.hash.Write(m.aesBuffer[:]) sum2 := m.hash.Sum(m.hashBuffer[:0]) + return sum2[:16] } @@ -303,17 +324,21 @@ func (c *Conn) Handshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { err error h handshakeState ) + if c.dialDest != nil { sec, err = h.runInitiator(c.conn, prv, c.dialDest) } else { sec, err = h.runRecipient(c.conn, prv) } + if err != nil { return nil, err } + c.InitWithSecrets(sec) c.session.rbuf = h.rbuf c.session.wbuf = h.wbuf + return sec.remote, err } @@ -323,10 +348,12 @@ func (c *Conn) InitWithSecrets(sec Secrets) { if c.session != nil { panic("can't handshake twice") } + macc, err := aes.NewCipher(sec.MAC) if err != nil { panic("invalid MAC secret: " + err.Error()) } + encc, err := aes.NewCipher(sec.AES) if err != nil { panic("invalid AES secret: " + err.Error()) @@ -413,10 +440,12 @@ type authRespV4 struct { // prv is the local client's private key. func (h *handshakeState) runRecipient(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, err error) { authMsg := new(authMsgV4) + authPacket, err := h.readMsg(authMsg, prv, conn) if err != nil { return s, err } + if err := h.handleAuthMsg(authMsg, prv); err != nil { return s, err } @@ -425,10 +454,12 @@ func (h *handshakeState) runRecipient(conn io.ReadWriter, prv *ecdsa.PrivateKey) if err != nil { return s, err } + authRespPacket, err := h.sealEIP8(authRespMsg) if err != nil { return s, err } + if _, err = conn.Write(authRespPacket); err != nil { return s, err } @@ -442,6 +473,7 @@ func (h *handshakeState) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) er if err != nil { return err } + h.initNonce = msg.Nonce[:] h.remote = rpub @@ -459,12 +491,16 @@ func (h *handshakeState) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) er if err != nil { return err } + signedMsg := xor(token, h.initNonce) + remoteRandomPub, err := crypto.Ecrecover(signedMsg, msg.Signature[:]) if err != nil { return err } + h.remoteRandomPub, _ = importPublicKey(remoteRandomPub) + return nil } @@ -489,9 +525,11 @@ func (h *handshakeState) secrets(auth, authResp []byte) (Secrets, error) { mac1 := sha3.NewLegacyKeccak256() mac1.Write(xor(s.MAC, h.respNonce)) mac1.Write(auth) + mac2 := sha3.NewLegacyKeccak256() mac2.Write(xor(s.MAC, h.initNonce)) mac2.Write(authResp) + if h.initiator { s.EgressMAC, s.IngressMAC = mac1, mac2 } else { @@ -519,6 +557,7 @@ func (h *handshakeState) runInitiator(conn io.ReadWriter, prv *ecdsa.PrivateKey, if err != nil { return s, err } + authPacket, err := h.sealEIP8(authMsg) if err != nil { return s, err @@ -529,10 +568,12 @@ func (h *handshakeState) runInitiator(conn io.ReadWriter, prv *ecdsa.PrivateKey, } authRespMsg := new(authRespV4) + authRespPacket, err := h.readMsg(authRespMsg, prv, conn) if err != nil { return s, err } + if err := h.handleAuthResp(authRespMsg); err != nil { return s, err } @@ -544,6 +585,7 @@ func (h *handshakeState) runInitiator(conn io.ReadWriter, prv *ecdsa.PrivateKey, func (h *handshakeState) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) { // Generate random initiator nonce. h.initNonce = make([]byte, shaLen) + _, err := rand.Read(h.initNonce) if err != nil { return nil, err @@ -559,7 +601,9 @@ func (h *handshakeState) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) if err != nil { return nil, err } + signed := xor(token, h.initNonce) + signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA()) if err != nil { return nil, err @@ -570,12 +614,14 @@ func (h *handshakeState) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) copy(msg.InitiatorPubkey[:], crypto.FromECDSAPub(&prv.PublicKey)[1:]) copy(msg.Nonce[:], h.initNonce) msg.Version = 4 + return msg, nil } func (h *handshakeState) handleAuthResp(msg *authRespV4) (err error) { h.respNonce = msg.Nonce[:] h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:]) + return err } @@ -590,6 +636,7 @@ func (h *handshakeState) makeAuthResp() (msg *authRespV4, err error) { copy(msg.Nonce[:], h.respNonce) copy(msg.RandomPubkey[:], exportPubkey(&h.randomPrivKey.PublicKey)) msg.Version = 4 + return msg, nil } @@ -603,6 +650,7 @@ func (h *handshakeState) readMsg(msg interface{}, prv *ecdsa.PrivateKey, r io.Re if err != nil { return nil, err } + size := binary.BigEndian.Uint16(prefix) // Read the handshake packet. @@ -610,6 +658,7 @@ func (h *handshakeState) readMsg(msg interface{}, prv *ecdsa.PrivateKey, r io.Re if err != nil { return nil, err } + dec, err := ecies.ImportECDSA(prv).Decrypt(packet, nil, prefix) if err != nil { return nil, err @@ -618,6 +667,7 @@ func (h *handshakeState) readMsg(msg interface{}, prv *ecdsa.PrivateKey, r io.Re // trailing data (forward-compatibility). s := rlp.NewStream(bytes.NewReader(dec), 0) err = s.Decode(msg) + return h.rbuf.data[:len(prefix)+len(packet)], err } @@ -637,12 +687,14 @@ func (h *handshakeState) sealEIP8(msg interface{}) ([]byte, error) { binary.BigEndian.PutUint16(prefix, uint16(len(h.wbuf.data)+eciesOverhead)) enc, err := ecies.Encrypt(rand.Reader, h.remote, h.wbuf.data, nil, prefix) + return append(prefix, enc...), err } // importPublicKey unmarshals 512 bit public keys. func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) { var pubKey65 []byte + switch len(pubKey) { case 64: // add 'uncompressed key' flag @@ -657,6 +709,7 @@ func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) { if err != nil { return nil, err } + return ecies.ImportECDSAPublic(pub), nil } @@ -664,6 +717,7 @@ func exportPubkey(pub *ecies.PublicKey) []byte { if pub == nil { panic("nil pubkey") } + return elliptic.Marshal(pub.Curve, pub.X, pub.Y)[1:] } @@ -672,5 +726,6 @@ func xor(one, other []byte) (xor []byte) { for i := 0; i < len(one); i++ { xor[i] = one[i] ^ other[i] } + return xor } diff --git a/p2p/rlpx/rlpx_test.go b/p2p/rlpx/rlpx_test.go index 28759f2b49..46ae3eba21 100644 --- a/p2p/rlpx/rlpx_test.go +++ b/p2p/rlpx/rlpx_test.go @@ -91,6 +91,7 @@ func createPeers(t *testing.T) (peer1, peer2 *Conn) { peer1 = NewConn(conn1, &key2.PublicKey) // dialer peer2 = NewConn(conn2, nil) // listener doHandshake(t, peer1, peer2, key1, key2) + return peer1, peer2 } @@ -108,6 +109,7 @@ func doHandshake(t *testing.T, peer1, peer2 *Conn, key1, key2 *ecdsa.PrivateKey) if err != nil { t.Errorf("peer1 could not do handshake: %v", err) } + pubKey1 := <-keyChan // Confirm the handshake was successful. @@ -126,6 +128,7 @@ func TestFrameReadWrite(t *testing.T) { IngressMAC: hash, EgressMAC: hash, }) + h := conn.session golden := unhex(` @@ -143,6 +146,7 @@ func TestFrameReadWrite(t *testing.T) { if err := h.writeFrame(buf, msgCode, msgEnc); err != nil { t.Fatalf("WriteMsg error: %v", err) } + if !bytes.Equal(buf.Bytes(), golden) { t.Fatalf("output mismatch:\n got: %x\n want: %x", buf.Bytes(), golden) } @@ -152,6 +156,7 @@ func TestFrameReadWrite(t *testing.T) { if err != nil { t.Fatalf("ReadMsg error: %v", err) } + wantContent := unhex("08C401020304") if !bytes.Equal(content, wantContent) { t.Errorf("frame content mismatch:\ngot %x\nwant %x", content, wantContent) @@ -280,33 +285,40 @@ func TestHandshakeForwardCompatibility(t *testing.T) { authSignature = unhex("299ca6acfd35e3d72d8ba3d1e2b60b5561d5af5218eb5bc182045769eb4226910a301acae3b369fffc4a4899d6b02531e89fd4fe36a2cf0d93607ba470b50f7800") _ = authSignature ) + makeAuth := func(test handshakeAuthTest) *authMsgV4 { msg := &authMsgV4{Version: test.wantVersion, Rest: test.wantRest} copy(msg.Signature[:], authSignature) copy(msg.InitiatorPubkey[:], pubA) copy(msg.Nonce[:], nonceA) + return msg } makeAck := func(test handshakeAckTest) *authRespV4 { msg := &authRespV4{Version: test.wantVersion, Rest: test.wantRest} copy(msg.RandomPubkey[:], ephPubB) copy(msg.Nonce[:], nonceB) + return msg } // check auth msg parsing for _, test := range eip8HandshakeAuthTests { var h handshakeState + r := bytes.NewReader(unhex(test.input)) msg := new(authMsgV4) + ciphertext, err := h.readMsg(msg, keyB, r) if err != nil { t.Errorf("error for input %x:\n %v", unhex(test.input), err) continue } + if !bytes.Equal(ciphertext, unhex(test.input)) { t.Errorf("wrong ciphertext for input %x:\n %x", unhex(test.input), ciphertext) } + want := makeAuth(test) if !reflect.DeepEqual(msg, want) { t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", unhex(test.input), spew.Sdump(msg), spew.Sdump(want)) @@ -316,17 +328,21 @@ func TestHandshakeForwardCompatibility(t *testing.T) { // check auth resp parsing for _, test := range eip8HandshakeRespTests { var h handshakeState + input := unhex(test.input) r := bytes.NewReader(input) msg := new(authRespV4) + ciphertext, err := h.readMsg(msg, keyA, r) if err != nil { t.Errorf("error for input %x:\n %v", input, err) continue } + if !bytes.Equal(ciphertext, input) { t.Errorf("wrong ciphertext for input %x:\n %x", input, err) } + want := makeAck(test) if !reflect.DeepEqual(msg, want) { t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", input, spew.Sdump(msg), spew.Sdump(want)) @@ -347,20 +363,26 @@ func TestHandshakeForwardCompatibility(t *testing.T) { wantMAC = unhex("2ea74ec5dae199227dff1af715362700e989d889d7a493cb0639691efb8e5f98") wantFooIngressHash = unhex("0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5") ) + if err := hs.handleAuthMsg(authMsg, keyB); err != nil { t.Fatalf("handleAuthMsg: %v", err) } + derived, err := hs.secrets(authCiphertext, authRespCiphertext) if err != nil { t.Fatalf("secrets: %v", err) } + if !bytes.Equal(derived.AES, wantAES) { t.Errorf("aes-secret mismatch:\ngot %x\nwant %x", derived.AES, wantAES) } + if !bytes.Equal(derived.MAC, wantMAC) { t.Errorf("mac-secret mismatch:\ngot %x\nwant %x", derived.MAC, wantMAC) } + io.WriteString(derived.IngressMAC, "foo") + fooIngressHash := derived.IngressMAC.Sum(nil) if !bytes.Equal(fooIngressHash, wantFooIngressHash) { t.Errorf("ingress-mac('foo') mismatch:\ngot %x\nwant %x", fooIngressHash, wantFooIngressHash) @@ -376,6 +398,7 @@ func BenchmarkHandshakeRead(b *testing.B) { r = bytes.NewReader(input) msg = new(authMsgV4) ) + if _, err := h.readMsg(msg, keyB, r); err != nil { b.Fatal(err) } @@ -394,6 +417,7 @@ func BenchmarkThroughput(b *testing.B) { msgdata = make([]byte, 1024) rand = rand.New(rand.NewSource(1337)) ) + rand.Read(msgdata) // Server side. @@ -402,9 +426,11 @@ func BenchmarkThroughput(b *testing.B) { // Perform handshake. _, err := conn1.Handshake(keyA) handshakeDone <- err + if err != nil { return } + conn1.SetSnappy(true) // Keep sending messages until connection closed. for { @@ -416,10 +442,13 @@ func BenchmarkThroughput(b *testing.B) { // Set up client side. defer conn2.Close() + if _, err := conn2.Handshake(keyB); err != nil { b.Fatal("client handshake error:", err) } + conn2.SetSnappy(true) + if err := <-handshakeDone; err != nil { b.Fatal("server hanshake error:", err) } @@ -427,6 +456,7 @@ func BenchmarkThroughput(b *testing.B) { // Read N messages. b.SetBytes(int64(len(msgdata))) b.ReportAllocs() + for i := 0; i < b.N; i++ { _, _, _, err := conn2.Read() if err != nil { @@ -437,10 +467,12 @@ func BenchmarkThroughput(b *testing.B) { func unhex(str string) []byte { r := strings.NewReplacer("\t", "", " ", "", "\n", "") + b, err := hex.DecodeString(r.Replace(str)) if err != nil { panic(fmt.Sprintf("invalid hex string: %q", str)) } + return b } @@ -449,5 +481,6 @@ func newkey() *ecdsa.PrivateKey { if err != nil { panic("couldn't generate key: " + err.Error()) } + return key } diff --git a/p2p/server.go b/p2p/server.go index 95849f12b8..dd1cb327fc 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -254,7 +254,9 @@ func (c *conn) String() string { if (c.node.ID() != enode.ID{}) { s += " " + c.node.ID().String() } + s += " " + c.fd.RemoteAddr().String() + return s } @@ -263,18 +265,23 @@ func (f connFlag) String() string { if f&trustedConn != 0 { s += "-trusted" } + if f&dynDialedConn != 0 { s += "-dyndial" } + if f&staticDialedConn != 0 { s += "-staticdial" } + if f&inboundConn != 0 { s += "-inbound" } + if s != "" { s = s[1:] } + return s } @@ -286,12 +293,14 @@ func (c *conn) is(f connFlag) bool { func (c *conn) set(f connFlag, val bool) { for { oldFlags := connFlag(atomic.LoadInt32((*int32)(&c.flags))) + flags := oldFlags if val { flags |= f } else { flags &= ^f } + if atomic.CompareAndSwapInt32((*int32)(&c.flags), int32(oldFlags), int32(flags)) { return } @@ -306,11 +315,13 @@ func (srv *Server) LocalNode() *enode.LocalNode { // Peers returns all connected peers. func (srv *Server) Peers() []*Peer { var ps []*Peer + srv.doPeerOp(func(peers map[enode.ID]*Peer) { for _, p := range peers { ps = append(ps, p) } }) + return ps } @@ -346,9 +357,11 @@ func (srv *Server) SetMaxPeers(maxPeers int) { // PeerCount returns the number of connected peers. func (srv *Server) PeerCount() int { var count int + srv.doPeerOp(func(ps map[enode.ID]*Peer) { count = len(ps) }) + return count } @@ -372,15 +385,18 @@ func (srv *Server) RemovePeer(node *enode.Node) { // Disconnect the peer on the main loop. srv.doPeerOp(func(peers map[enode.ID]*Peer) { srv.dialsched.removeStatic(node) + if peer := peers[node.ID()]; peer != nil { ch = make(chan *PeerEvent, 1) sub = srv.peerFeed.Subscribe(ch) + peer.Disconnect(DiscRequested) } }) // Wait for the peer connection to end. if ch != nil { defer sub.Unsubscribe() + for ev := range ch { if ev.Peer == node.ID() && ev.Type == PeerEventTypeDrop { return @@ -422,6 +438,7 @@ func (srv *Server) Self() *enode.Node { if ln == nil { return enode.NewV4(&srv.PrivateKey.PublicKey, net.ParseIP("0.0.0.0"), 0, 0) } + return ln.Node() } @@ -433,11 +450,13 @@ func (srv *Server) Stop() { srv.lock.Unlock() return } + srv.running = false if srv.listener != nil { // this unblocks listener Accept srv.listener.Close() } + close(srv.quit) srv.lock.Unlock() srv.loopWG.Wait() @@ -456,11 +475,14 @@ func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err err if !ok { return 0, nil, errors.New("connection was closed") } + l := len(packet.Data) if l > len(b) { l = len(b) } + copy(b[:l], packet.Data[:l]) + return l, packet.Addr, nil } @@ -474,17 +496,22 @@ func (s *sharedUDPConn) Close() error { func (srv *Server) Start() (err error) { srv.lock.Lock() defer srv.lock.Unlock() + if srv.running { return errors.New("server already running") } + srv.running = true srv.log = srv.Logger + if srv.log == nil { srv.log = log.Root() } + if srv.clock == nil { srv.clock = mclock.System{} } + if srv.NoDial && srv.ListenAddr == "" { srv.log.Warn("P2P server will be useless, neither dialing nor listening") } @@ -493,12 +520,15 @@ func (srv *Server) Start() (err error) { if srv.PrivateKey == nil { return errors.New("Server.PrivateKey must be set to a non-nil key") } + if srv.newTransport == nil { srv.newTransport = newRLPX } + if srv.listenFunc == nil { srv.listenFunc = net.Listen } + srv.quit = make(chan struct{}) srv.delpeer = make(chan peerDrop) srv.checkpointPostHandshake = make(chan *conn) @@ -511,28 +541,34 @@ func (srv *Server) Start() (err error) { if err := srv.setupLocalNode(); err != nil { return err } + if srv.ListenAddr != "" { if err := srv.setupListening(); err != nil { return err } } + if err := srv.setupDiscovery(); err != nil { return err } + srv.setupDialScheduler() srv.loopWG.Add(1) go srv.run() + return nil } func (srv *Server) setupLocalNode() error { // Create the devp2p handshake. pubkey := crypto.FromECDSAPub(&srv.PrivateKey.PublicKey) + srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: pubkey[1:]} for _, p := range srv.Protocols { srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap()) } + sort.Sort(capsByNameAndVersion(srv.ourHandshake.Caps)) // Create the local node. @@ -540,6 +576,7 @@ func (srv *Server) setupLocalNode() error { if err != nil { return err } + srv.nodedb = db srv.localnode = enode.NewLocalNode(db, srv.PrivateKey) srv.localnode.SetFallbackIP(net.IP{127, 0, 0, 1}) @@ -549,6 +586,7 @@ func (srv *Server) setupLocalNode() error { srv.localnode.Set(e) } } + switch srv.NAT.(type) { case nil: // No NAT interface, do nothing. @@ -560,13 +598,16 @@ func (srv *Server) setupLocalNode() error { // Ask the router about the IP. This takes a while and blocks startup, // do it in the background. srv.loopWG.Add(1) + go func() { defer srv.loopWG.Done() + if ip, err := srv.NAT.ExternalIP(); err == nil { srv.localnode.SetStaticIP(ip) } }() } + return nil } @@ -578,6 +619,7 @@ func (srv *Server) setupDiscovery() error { for _, proto := range srv.Protocols { if proto.DialCandidates != nil && !added[proto.Name] { srv.discmix.AddSource(proto.DialCandidates) + added[proto.Name] = true } } @@ -599,31 +641,39 @@ func (srv *Server) setupDiscovery() error { if err != nil { return err } + conn, err := net.ListenUDP("udp", addr) if err != nil { return err } + realaddr := conn.LocalAddr().(*net.UDPAddr) srv.log.Debug("UDP listener up", "addr", realaddr) + if srv.NAT != nil { if !realaddr.IP.IsLoopback() { srv.loopWG.Add(1) + go func() { nat.Map(srv.NAT, srv.quit, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") srv.loopWG.Done() }() } } + srv.localnode.SetFallbackUDP(realaddr.Port) // Discovery V4 var unhandled chan discover.ReadPacket + var sconn *sharedUDPConn + if !srv.NoDiscovery { if srv.DiscoveryV5 { unhandled = make(chan discover.ReadPacket, 100) sconn = &sharedUDPConn{conn, unhandled} } + cfg := discover.Config{ PrivateKey: srv.PrivateKey, NetRestrict: srv.NetRestrict, @@ -631,10 +681,12 @@ func (srv *Server) setupDiscovery() error { Unhandled: unhandled, Log: srv.log, } + ntab, err := discover.ListenV4(conn, srv.localnode, cfg) if err != nil { return err } + srv.ntab = ntab srv.discmix.AddSource(ntab.RandomNodes()) } @@ -647,16 +699,20 @@ func (srv *Server) setupDiscovery() error { Bootnodes: srv.BootstrapNodesV5, Log: srv.log, } + var err error + if sconn != nil { srv.DiscV5, err = discover.ListenV5(sconn, srv.localnode, cfg) } else { srv.DiscV5, err = discover.ListenV5(conn, srv.localnode, cfg) } + if err != nil { return err } } + return nil } @@ -673,9 +729,11 @@ func (srv *Server) setupDialScheduler() { if srv.ntab != nil { config.resolver = srv.ntab } + if config.dialer == nil { config.dialer = tcpDialer{&net.Dialer{Timeout: defaultDialTimeout}} } + srv.dialsched = newDialScheduler(config, srv.discmix, srv.SetupConn) for _, n := range srv.StaticNodes { srv.dialsched.addStatic(n) @@ -690,14 +748,17 @@ func (srv *Server) maxDialedConns() (limit int) { if srv.NoDial || srv.MaxPeers == 0 { return 0 } + if srv.DialRatio == 0 { limit = srv.MaxPeers / defaultDialRatio } else { limit = srv.MaxPeers / srv.DialRatio } + if limit == 0 { limit = 1 } + return limit } @@ -707,14 +768,17 @@ func (srv *Server) setupListening() error { if err != nil { return err } + srv.listener = listener srv.ListenAddr = listener.Addr().String() // Update the local node record and map the TCP listening port if NAT is configured. if tcp, ok := listener.Addr().(*net.TCPAddr); ok { srv.localnode.Set(enr.TCP(tcp.Port)) + if !tcp.IP.IsLoopback() && srv.NAT != nil { srv.loopWG.Add(1) + go func() { nat.Map(srv.NAT, srv.quit, "tcp", tcp.Port, tcp.Port, "ethereum p2p") srv.loopWG.Done() @@ -724,6 +788,7 @@ func (srv *Server) setupListening() error { srv.loopWG.Add(1) go srv.listenLoop() + return nil } @@ -829,6 +894,7 @@ running: if srv.ntab != nil { srv.ntab.Close() } + if srv.DiscV5 != nil { srv.DiscV5.Close() } @@ -881,6 +947,7 @@ func (srv *Server) listenLoop() { if srv.MaxPendingPeers > 0 { tokens = srv.MaxPendingPeers } + slots := make(chan struct{}, tokens) for i := 0; i < tokens; i++ { slots <- struct{}{} @@ -904,20 +971,26 @@ func (srv *Server) listenLoop() { err error lastLog time.Time ) + for { fd, err = srv.listener.Accept() if netutil.IsTemporaryError(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) slots <- struct{}{} + return } + break } @@ -926,16 +999,20 @@ func (srv *Server) listenLoop() { srv.log.Debug("Rejected inbound connection", "addr", fd.RemoteAddr(), "err", err) fd.Close() slots <- struct{}{} + continue } + if remoteIP != nil { var addr *net.TCPAddr if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok { addr = tcp } + fd = newMeteredConn(fd, true, addr) srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr()) } + go func() { srv.SetupConn(fd, inboundConn, nil) slots <- struct{}{} @@ -954,10 +1031,13 @@ func (srv *Server) checkInboundConn(remoteIP net.IP) error { // Reject Internet peers that try too often. now := srv.clock.Now() srv.inboundHistory.expire(now, nil) + if !netutil.IsLAN(remoteIP) && srv.inboundHistory.contains(remoteIP.String()) { return fmt.Errorf("too many attempts") } + srv.inboundHistory.add(remoteIP.String(), now.Add(inboundThrottleTime)) + return nil } @@ -976,6 +1056,7 @@ func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) if err != nil { c.close(err) } + return err } @@ -984,6 +1065,7 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro srv.lock.Lock() running := srv.running srv.lock.Unlock() + if !running { return errServerStopped } @@ -994,6 +1076,7 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro if err := dialDest.Load((*enode.Secp256k1)(dialPubkey)); err != nil { err = errors.New("dial destination doesn't have a secp256k1 public key") srv.log.Trace("Setting up connection failed", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) + return err } } @@ -1004,13 +1087,16 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) return err } + if dialDest != nil { c.node = dialDest } else { c.node = nodeFromConn(remotePubkey, c.fd) } + clog := srv.log.New("id", c.node.ID(), "addr", c.fd.RemoteAddr(), "conn", c.flags) err = srv.checkpoint(c, srv.checkpointPostHandshake) + if err != nil { clog.Trace("Rejected peer", "err", err) return err @@ -1022,11 +1108,14 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro clog.Trace("Failed p2p handshake", "err", err) return err } + if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) { clog.Trace("Wrong devp2p handshake identity", "phsid", hex.EncodeToString(phs.ID)) return DiscUnexpectedIdentity } + c.caps, c.name = phs.Caps, phs.Name + err = srv.checkpoint(c, srv.checkpointAddPeer) if err != nil { clog.Trace("Rejected peer", "err", err) @@ -1038,11 +1127,14 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro func nodeFromConn(pubkey *ecdsa.PublicKey, conn net.Conn) *enode.Node { var ip net.IP + var port int + if tcp, ok := conn.RemoteAddr().(*net.TCPAddr); ok { ip = tcp.IP port = tcp.Port } + return enode.NewV4(pubkey, ip, port, port) } @@ -1054,6 +1146,7 @@ func (srv *Server) checkpoint(c *conn, stage chan<- *conn) error { case <-srv.quit: return errServerStopped } + return <-c.cont } @@ -1064,7 +1157,9 @@ func (srv *Server) launchPeer(c *conn) *Peer { // to the peer. p.events = &srv.peerFeed } + go srv.runPeer(p) + return p } @@ -1073,6 +1168,7 @@ func (srv *Server) runPeer(p *Peer) { if srv.newPeerHook != nil { srv.newPeerHook(p) } + srv.peerFeed.Send(&PeerEvent{ Type: PeerEventTypeAdd, Peer: p.ID(), @@ -1139,9 +1235,11 @@ func (srv *Server) NodeInfo() *NodeInfo { if query := proto.NodeInfo; query != nil { nodeInfo = proto.NodeInfo() } + info.Protocols[proto.Name] = nodeInfo } } + return info } @@ -1149,6 +1247,7 @@ func (srv *Server) NodeInfo() *NodeInfo { func (srv *Server) PeersInfo() []*PeerInfo { // Gather all the generic and sub-protocol specific infos infos := make([]*PeerInfo, 0, srv.PeerCount()) + for _, peer := range srv.Peers() { if peer != nil { infos = append(infos, peer.Info()) @@ -1162,5 +1261,6 @@ func (srv *Server) PeersInfo() []*PeerInfo { } } } + return infos } diff --git a/p2p/server_test.go b/p2p/server_test.go index f6f5700c5e..789b6e45c3 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -49,6 +49,7 @@ func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn, dialDest *ecdsa.Public EgressMAC: sha256.New(), IngressMAC: sha256.New(), }) + return &testTransport{rpub: rpub, rlpxTransport: wrapped} } @@ -75,6 +76,7 @@ func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) * PrivateKey: newkey(), Logger: testlog.Logger(t, log.LvlTrace), } + server := &Server{ Config: config, newPeerHook: pf, @@ -85,6 +87,7 @@ func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) * if err := server.Start(); err != nil { t.Fatalf("Could not start server: %v", err) } + return server } @@ -98,6 +101,7 @@ func TestServerListen(t *testing.T) { } connected <- p }) + defer close(connected) defer srv.Stop() @@ -114,6 +118,7 @@ func TestServerListen(t *testing.T) { t.Errorf("peer started with wrong conn: got %v, want %v", peer.LocalAddr(), conn.RemoteAddr()) } + peers := srv.Peers() if !reflect.DeepEqual(peers, []*Peer{peer}) { t.Errorf("Peers mismatch: got %v, want %v", peers, []*Peer{peer}) @@ -129,8 +134,11 @@ func TestServerDial(t *testing.T) { if err != nil { t.Fatalf("could not setup listener: %v", err) } + defer listener.Close() + accepted := make(chan net.Conn, 1) + go func() { conn, err := listener.Accept() if err != nil { @@ -143,6 +151,7 @@ func TestServerDial(t *testing.T) { connected := make(chan *Peer) remid := &newkey().PublicKey srv := startTestServer(t, remid, func(p *Peer) { connected <- p }) + defer close(connected) defer srv.Stop() @@ -160,13 +169,16 @@ func TestServerDial(t *testing.T) { if peer.ID() != enode.PubkeyToIDV4(remid) { t.Errorf("peer has wrong id") } + if peer.Name() != "test" { t.Errorf("peer has wrong name") } + if peer.RemoteAddr().String() != conn.LocalAddr().String() { t.Errorf("peer started with wrong conn: got %v, want %v", peer.RemoteAddr(), conn.LocalAddr()) } + peers := srv.Peers() if !reflect.DeepEqual(peers, []*Peer{peer}) { t.Errorf("Peers mismatch: got %v, want %v", peers, []*Peer{peer}) @@ -177,13 +189,18 @@ func TestServerDial(t *testing.T) { if peer := srv.Peers()[0]; peer.Info().Network.Trusted { t.Errorf("peer is trusted prematurely: %v", peer) } + done := make(chan bool) + go func() { srv.AddTrustedPeer(node) + if peer := srv.Peers()[0]; !peer.Info().Network.Trusted { t.Errorf("peer is not trusted after AddTrustedPeer: %v", peer) } + srv.RemoveTrustedPeer(node) + if peer := srv.Peers()[0]; peer.Info().Network.Trusted { t.Errorf("peer is trusted after RemoveTrustedPeer: %v", peer) } @@ -193,6 +210,7 @@ func TestServerDial(t *testing.T) { peer = srv.Peers()[0] _ = peer.Inbound() _ = peer.Info() + <-done case <-time.After(1 * time.Second): t.Error("server did not launch peer within one second") @@ -219,15 +237,20 @@ func TestServerRemovePeerDisconnect(t *testing.T) { ListenAddr: "127.0.0.1:0", Logger: testlog.Logger(t, log.LvlTrace).New("server", "2"), }} + srv1.Start() + defer srv1.Stop() srv2.Start() + defer srv2.Stop() if !syncAddPeer(srv1, srv2.Self()) { t.Fatal("peer not connected") } + srv1.RemovePeer(srv2.Self()) + if srv1.PeerCount() > 0 { t.Fatal("removed peer still connected") } @@ -238,6 +261,7 @@ func TestServerRemovePeerDisconnect(t *testing.T) { func TestServerAtCap(t *testing.T) { trustedNode := newkey() trustedID := enode.PubkeyToIDV4(&trustedNode.PublicKey) + srv := &Server{ Config: Config{ PrivateKey: newkey(), @@ -251,12 +275,14 @@ func TestServerAtCap(t *testing.T) { if err := srv.Start(); err != nil { t.Fatalf("could not start: %v", err) } + defer srv.Stop() newconn := func(id enode.ID) *conn { fd, _ := net.Pipe() tx := newTestTransport(&trustedNode.PublicKey, fd, nil) node := enode.SignNull(new(enr.Record), id) + return &conn{fd: fd, transport: tx, flags: inboundConn, node: node, cont: make(chan error)} } @@ -269,6 +295,7 @@ func TestServerAtCap(t *testing.T) { } // Try inserting a non-trusted connection. anotherID := randomID() + c := newconn(anotherID) if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != DiscTooManyPeers { t.Error("wrong error for insert:", err) @@ -278,12 +305,14 @@ func TestServerAtCap(t *testing.T) { if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != nil { t.Error("unexpected error for trusted conn @posthandshake:", err) } + if !c.is(trustedConn) { t.Error("Server did not set trusted flag") } // Remove from trusted set and try again srv.RemoveTrustedPeer(newNode(trustedID, "")) + c = newconn(trustedID) if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != DiscTooManyPeers { t.Error("wrong error for insert:", err) @@ -291,10 +320,12 @@ func TestServerAtCap(t *testing.T) { // Add anotherID to trusted set and try again srv.AddTrustedPeer(newNode(anotherID, "")) + c = newconn(anotherID) if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != nil { t.Error("unexpected error for trusted conn @posthandshake:", err) } + if !c.is(trustedConn) { t.Error("Server did not set trusted flag") } @@ -335,9 +366,11 @@ func TestServerPeerLimits(t *testing.T) { dialDest := clientnode conn, _ := net.Pipe() srv.SetupConn(conn, flags, dialDest) + if tp.closeErr != DiscTooManyPeers { t.Errorf("unexpected close error: %q", tp.closeErr) } + conn.Close() srv.AddTrustedPeer(clientnode) @@ -345,6 +378,7 @@ func TestServerPeerLimits(t *testing.T) { // Check that server allows a trusted peer despite being full. conn, _ = net.Pipe() srv.SetupConn(conn, flags, dialDest) + if tp.closeErr == DiscTooManyPeers { t.Errorf("failed to bypass MaxPeers with trusted node: %q", tp.closeErr) } @@ -352,6 +386,7 @@ func TestServerPeerLimits(t *testing.T) { if tp.closeErr != DiscUselessPeer { t.Errorf("unexpected close error: %q", tp.closeErr) } + conn.Close() srv.RemoveTrustedPeer(clientnode) @@ -359,9 +394,11 @@ func TestServerPeerLimits(t *testing.T) { // Check that server is full again. conn, _ = net.Pipe() srv.SetupConn(conn, flags, dialDest) + if tp.closeErr != DiscTooManyPeers { t.Errorf("unexpected close error: %q", tp.closeErr) } + conn.Close() } @@ -373,6 +410,7 @@ func TestServerSetupConn(t *testing.T) { fooErr = errors.New("foo") readErr = errors.New("read error") ) + tests := []struct { dontstart bool tt *setupTransport @@ -432,6 +470,7 @@ func TestServerSetupConn(t *testing.T) { Protocols: []Protocol{discard}, Logger: testlog.Logger(t, log.LvlTrace), } + srv := &Server{ Config: cfg, newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { return test.tt }, @@ -443,11 +482,14 @@ func TestServerSetupConn(t *testing.T) { } defer srv.Stop() } + p1, _ := net.Pipe() srv.SetupConn(p1, test.flags, test.dialDest) + if !errors.Is(test.tt.closeErr, test.wantCloseErr) { t.Errorf("test %d: close error mismatch: got %q, want %q", i, test.tt.closeErr, test.wantCloseErr) } + if test.tt.calls != test.wantCalls { t.Errorf("test %d: calls mismatch: got %q, want %q", i, test.tt.calls, test.wantCalls) } @@ -475,6 +517,7 @@ func (c *setupTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, if c.protoHandshakeErr != nil { return nil, c.protoHandshakeErr } + return &c.phs, nil } func (c *setupTransport) close(err error) { @@ -495,6 +538,7 @@ func newkey() *ecdsa.PrivateKey { if err != nil { panic("couldn't generate key: " + err.Error()) } + return key } @@ -502,13 +546,16 @@ func randomID() (id enode.ID) { for i := range id { id[i] = byte(rand.Intn(255)) } + return id } // This test checks that inbound connections are throttled by IP. func TestServerInboundThrottle(t *testing.T) { const timeout = 5 * time.Second + newTransportCalled := make(chan struct{}) + srv := &Server{ Config: Config{ PrivateKey: newkey(), @@ -531,6 +578,7 @@ func TestServerInboundThrottle(t *testing.T) { if err := srv.Start(); err != nil { t.Fatal("can't start: ", err) } + defer srv.Stop() // Dial the test server. @@ -548,13 +596,17 @@ func TestServerInboundThrottle(t *testing.T) { // Dial again. This time the server should close the connection immediately. connClosed := make(chan struct{}, 1) + conn, err = net.DialTimeout("tcp", srv.ListenAddr, timeout) if err != nil { t.Fatalf("could not dial: %v", err) } + defer conn.Close() + go func() { conn.SetDeadline(time.Now().Add(timeout)) + buf := make([]byte, 10) if n, err := conn.Read(buf); err != io.EOF || n != 0 { t.Errorf("expected io.EOF and n == 0, got error %q and n == %d", err, n) @@ -576,6 +628,7 @@ func listenFakeAddr(network, laddr string, remoteAddr net.Addr) (net.Listener, e if err == nil { l = &fakeAddrListener{l, remoteAddr} } + return l, err } @@ -595,6 +648,7 @@ func (l *fakeAddrListener) Accept() (net.Conn, error) { if err != nil { return nil, err } + return &fakeAddrConn{c, l.remoteAddr}, nil } @@ -608,8 +662,10 @@ func syncAddPeer(srv *Server, node *enode.Node) bool { sub = srv.SubscribeEvents(ch) timeout = time.After(2 * time.Second) ) + defer sub.Unsubscribe() srv.AddPeer(node) + for { select { case ev := <-ch: diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 8a138dd93d..0d00905587 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -78,6 +78,7 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { if len(config.Lifecycles) == 0 { return nil, errors.New("node must have at least one service lifecycle") } + for _, service := range config.Lifecycles { if _, exists := lifecycleConstructorFuncs[service]; !exists { return nil, fmt.Errorf("unknown node service %q", service) @@ -128,6 +129,7 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { } node.newCmd = node.execCommand e.nodes[node.ID] = node + return node, nil } @@ -151,6 +153,7 @@ func (n *ExecNode) Addr() []byte { if n.Info == nil { return nil } + return []byte(n.Info.Enode) } @@ -166,6 +169,7 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { if n.Cmd != nil { return errors.New("already started") } + defer func() { if err != nil { n.Stop() @@ -176,9 +180,11 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { confCopy := *n.Config confCopy.Snapshots = snapshots confCopy.PeerAddrs = make(map[string]string) + for id, node := range n.adapter.nodes { confCopy.PeerAddrs[id.String()] = node.wsAddr } + confData, err := json.Marshal(confCopy) if err != nil { return fmt.Errorf("error generating node config: %s", err) @@ -193,12 +199,14 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { } } } + if !exposed { confCopy.Stack.WSModules = append(confCopy.Stack.WSModules, "admin") } // start the one-shot server that waits for startup information ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + statusURL, statusC := n.waitForStartupJSON(ctx) // start the node @@ -209,9 +217,11 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { envStatusURL+"="+statusURL, envNodeConfig+"="+string(confData), ) + if err := cmd.Start(); err != nil { return fmt.Errorf("error starting node: %s", err) } + n.Cmd = cmd // Wait for the node to start. @@ -219,6 +229,7 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { if status.Err != "" { return errors.New(status.Err) } + client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "") if err != nil { return fmt.Errorf("can't connect to RPC server: %v", err) @@ -228,6 +239,7 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { n.client = client n.wsAddr = status.WSEndpoint n.Info = status.NodeInfo + return nil } @@ -238,11 +250,13 @@ func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeSta quitOnce sync.Once srv http.Server ) + l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { ch <- nodeStartupJSON{Err: err.Error()} return "", ch } + quit := func(status nodeStartupJSON) { quitOnce.Do(func() { l.Close() @@ -254,6 +268,7 @@ func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeSta if err := json.NewDecoder(r.Body).Decode(&status); err != nil { status.Err = fmt.Sprintf("can't decode startup report: %v", err) } + quit(status) }) // Run the HTTP server, but don't wait forever and shut it down @@ -265,6 +280,7 @@ func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeSta }() url := "http://" + l.Addr().String() + return url, ch } @@ -284,6 +300,7 @@ func (n *ExecNode) Stop() error { if n.Cmd == nil { return nil } + defer func() { n.Cmd = nil }() @@ -298,7 +315,9 @@ func (n *ExecNode) Stop() error { if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil { return n.Cmd.Process.Kill() } + waitErr := make(chan error, 1) + go func() { waitErr <- n.Cmd.Wait() }() @@ -318,6 +337,7 @@ func (n *ExecNode) NodeInfo() *p2p.NodeInfo { if n.client != nil { n.client.Call(&info, "admin_nodeInfo") } + return info } @@ -328,26 +348,33 @@ func (n *ExecNode) ServeRPC(clientConn *websocket.Conn) error { if err != nil { return err } + var wg sync.WaitGroup + wg.Add(2) + go wsCopy(&wg, conn, clientConn) go wsCopy(&wg, clientConn, conn) wg.Wait() conn.Close() + return nil } func wsCopy(wg *sync.WaitGroup, src, dst *websocket.Conn) { defer wg.Done() + for { msgType, r, err := src.NextReader() if err != nil { return } + w, err := dst.NextWriter(msgType) if err != nil { return } + if _, err = io.Copy(w, r); err != nil { return } @@ -360,7 +387,9 @@ func (n *ExecNode) Snapshots() (map[string][]byte, error) { if n.client == nil { return nil, errors.New("RPC not started") } + var snapshots map[string][]byte + return snapshots, n.client.Call(&snapshots, "simulation_snapshot") } @@ -383,18 +412,23 @@ func initLogging() { if confEnv == "" { return } + var conf execNodeConfig if err := json.Unmarshal([]byte(confEnv), &conf); err != nil { return } + var writer = os.Stderr + if conf.Node.LogFile != "" { logWriter, err := os.Create(conf.Node.LogFile) if err != nil { return } + writer = logWriter } + var verbosity = log.LvlInfo if conf.Node.LogVerbosity <= log.LvlTrace && conf.Node.LogVerbosity >= log.LvlCrit { verbosity = conf.Node.LogVerbosity @@ -418,6 +452,7 @@ func execP2PNode() { // Start the node and gather startup report. var status nodeStartupJSON + stack, stackErr := startExecNodeStack() if stackErr != nil { status.Err = stackErr.Error() @@ -431,15 +466,19 @@ func execP2PNode() { Timeout: 10 * time.Second, } statusJSON, _ := json.Marshal(status) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, statusURL, bytes.NewReader(statusJSON)) if err != nil { log.Crit("Can't build request", "url", statusURL, "err", err) } + resp, err := cli.Do(req) if err != nil { log.Crit("Can't post startup info", "url", statusURL, "err", err) } + resp.Body.Close() + if stackErr != nil { os.Exit(1) } @@ -448,6 +487,7 @@ func execP2PNode() { go func() { sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGTERM) + defer signal.Stop(sigc) <-sigc log.Info("Received SIGTERM, shutting down...") @@ -465,6 +505,7 @@ func startExecNodeStack() (*node.Node, error) { if confEnv == "" { return nil, fmt.Errorf("missing " + envNodeConfig) } + var conf execNodeConfig if err := json.Unmarshal([]byte(confEnv), &conf); err != nil { return nil, fmt.Errorf("error decoding %s: %v", envNodeConfig, err) @@ -475,6 +516,7 @@ func startExecNodeStack() (*node.Node, error) { if nodeTcpConn.IP == nil { nodeTcpConn.IP = net.IPv4(127, 0, 0, 1) } + conf.Node.initEnode(nodeTcpConn.IP, nodeTcpConn.Port, nodeTcpConn.Port) conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey conf.Stack.Logger = log.New("node.id", conf.Node.ID.String()) @@ -488,11 +530,13 @@ func startExecNodeStack() (*node.Node, error) { // Register the services, collecting them into a map so they can // be accessed by the snapshot API. services := make(map[string]node.Lifecycle, len(serviceNames)) + for _, name := range serviceNames { lifecycleFunc, exists := lifecycleConstructorFuncs[name] if !exists { return nil, fmt.Errorf("unknown node service %q", err) } + ctx := &ServiceContext{ RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, Config: conf.Node, @@ -500,10 +544,12 @@ func startExecNodeStack() (*node.Node, error) { if conf.Snapshots != nil { ctx.Snapshot = conf.Snapshots[name] } + service, err := lifecycleFunc(ctx, stack) if err != nil { return nil, err } + services[name] = service } @@ -516,6 +562,7 @@ func startExecNodeStack() (*node.Node, error) { if err = stack.Start(); err != nil { err = fmt.Errorf("error starting stack: %v", err) } + return stack, err } @@ -538,6 +585,7 @@ type SnapshotAPI struct { func (api SnapshotAPI) Snapshot() (map[string][]byte, error) { snapshots := make(map[string][]byte) + for name, service := range api.services { if s, ok := service.(interface { Snapshot() ([]byte, error) @@ -546,9 +594,11 @@ func (api SnapshotAPI) Snapshot() (map[string][]byte, error) { if err != nil { return nil, err } + snapshots[name] = snap } } + return snapshots, nil } @@ -563,5 +613,6 @@ func (w *wsRPCDialer) DialRPC(id enode.ID) (*rpc.Client, error) { if !ok { return nil, fmt.Errorf("unknown node: %s", id) } + return rpc.DialWebsocket(context.Background(), addr, "http://localhost") } diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 36b5286517..0bed453310 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -80,6 +80,7 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { if len(config.Lifecycles) == 0 { return nil, errors.New("node must have at least one service") } + for _, service := range config.Lifecycles { if _, exists := s.lifecycles[service]; !exists { return nil, fmt.Errorf("unknown node service %q", service) @@ -114,6 +115,7 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { running: make(map[string]node.Lifecycle), } s.nodes[id] = simNode + return simNode, nil } @@ -124,6 +126,7 @@ func (s *SimAdapter) Dial(ctx context.Context, dest *enode.Node) (conn net.Conn, if !ok { return nil, fmt.Errorf("unknown node: %s", dest.ID()) } + srv := node.Server() if srv == nil { return nil, fmt.Errorf("node not running: %s", dest.ID()) @@ -137,6 +140,7 @@ func (s *SimAdapter) Dial(ctx context.Context, dest *enode.Node) (conn net.Conn, // asynchronously call the dialed destination node's p2p server // to set up connection on the 'listening' side go srv.SetupConn(pipe1, 0, nil) + return pipe2, nil } @@ -147,6 +151,7 @@ func (s *SimAdapter) DialRPC(id enode.ID) (*rpc.Client, error) { if !ok { return nil, fmt.Errorf("unknown node: %s", id) } + return node.node.Attach() } @@ -155,6 +160,7 @@ func (s *SimAdapter) GetNode(id enode.ID) (*SimNode, bool) { s.mtx.RLock() defer s.mtx.RUnlock() node, ok := s.nodes[id] + return node, ok } @@ -193,9 +199,11 @@ func (sn *SimNode) Node() *enode.Node { func (sn *SimNode) Client() (*rpc.Client, error) { sn.lock.RLock() defer sn.lock.RUnlock() + if sn.client == nil { return nil, errors.New("node not started") } + return sn.client, nil } @@ -206,8 +214,10 @@ func (sn *SimNode) ServeRPC(conn *websocket.Conn) error { if err != nil { return err } + codec := rpc.NewFuncCodec(conn, func(v any, _ bool) error { return conn.WriteJSON(v) }, conn.ReadJSON) handler.ServeCodec(codec, 0) + return nil } @@ -215,15 +225,19 @@ func (sn *SimNode) ServeRPC(conn *websocket.Conn) error { // simulation_snapshot RPC method func (sn *SimNode) Snapshots() (map[string][]byte, error) { sn.lock.RLock() + services := make(map[string]node.Lifecycle, len(sn.running)) for name, service := range sn.running { services[name] = service } sn.lock.RUnlock() + if len(services) == 0 { return nil, errors.New("no running services") } + snapshots := make(map[string][]byte) + for name, service := range services { if s, ok := service.(interface { Snapshot() ([]byte, error) @@ -232,9 +246,11 @@ func (sn *SimNode) Snapshots() (map[string][]byte, error) { if err != nil { return nil, err } + snapshots[name] = snap } } + return snapshots, nil } @@ -243,6 +259,7 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error { // ensure we only register the services once in the case of the node // being stopped and then started again var regErr error + sn.registerOnce.Do(func() { for _, name := range sn.config.Lifecycles { ctx := &ServiceContext{ @@ -252,7 +269,9 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error { if snapshots != nil { ctx.Snapshot = snapshots[name] } + serviceFunc := sn.adapter.lifecycles[name] + service, err := serviceFunc(ctx, sn.node) if err != nil { regErr = err @@ -262,9 +281,11 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error { if _, ok := sn.running[name]; ok { continue } + sn.running[name] = service } }) + if regErr != nil { return regErr } @@ -278,6 +299,7 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error { if err != nil { return err } + sn.lock.Lock() sn.client = client sn.lock.Unlock() @@ -293,6 +315,7 @@ func (sn *SimNode) Stop() error { sn.client = nil } sn.lock.Unlock() + return sn.node.Close() } @@ -300,6 +323,7 @@ func (sn *SimNode) Stop() error { func (sn *SimNode) Service(name string) node.Lifecycle { sn.lock.RLock() defer sn.lock.RUnlock() + return sn.running[name] } @@ -307,10 +331,12 @@ func (sn *SimNode) Service(name string) node.Lifecycle { func (sn *SimNode) Services() []node.Lifecycle { sn.lock.RLock() defer sn.lock.RUnlock() + services := make([]node.Lifecycle, 0, len(sn.running)) for _, service := range sn.running { services = append(services, service) } + return services } @@ -318,10 +344,12 @@ func (sn *SimNode) Services() []node.Lifecycle { func (sn *SimNode) ServiceMap() map[string]node.Lifecycle { sn.lock.RLock() defer sn.lock.RUnlock() + services := make(map[string]node.Lifecycle, len(sn.running)) for name, service := range sn.running { services[name] = service } + return services } @@ -337,6 +365,7 @@ func (sn *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription { if srv == nil { panic("node not running") } + return srv.SubscribeEvents(ch) } @@ -349,5 +378,6 @@ func (sn *SimNode) NodeInfo() *p2p.NodeInfo { Enode: sn.Node().String(), } } + return server.NodeInfo() } diff --git a/p2p/simulations/adapters/inproc_test.go b/p2p/simulations/adapters/inproc_test.go index 2a61508fe1..5bcd8ca4d3 100644 --- a/p2p/simulations/adapters/inproc_test.go +++ b/p2p/simulations/adapters/inproc_test.go @@ -34,9 +34,11 @@ func TestTCPPipe(t *testing.T) { msgs := 50 size := 1024 + for i := 0; i < msgs; i++ { msg := make([]byte, size) binary.PutUvarint(msg, uint64(i)) + if _, err := c1.Write(msg); err != nil { t.Fatal(err) } @@ -45,10 +47,12 @@ func TestTCPPipe(t *testing.T) { for i := 0; i < msgs; i++ { msg := make([]byte, size) binary.PutUvarint(msg, uint64(i)) + out := make([]byte, size) if _, err := c2.Read(out); err != nil { t.Fatal(err) } + if !bytes.Equal(msg, out) { t.Fatalf("expected %#v, got %#v", msg, out) } @@ -63,6 +67,7 @@ func TestTCPPipeBidirections(t *testing.T) { msgs := 50 size := 7 + for i := 0; i < msgs; i++ { msg := []byte(fmt.Sprintf("ping %02d", i)) if _, err := c1.Write(msg); err != nil { @@ -72,6 +77,7 @@ func TestTCPPipeBidirections(t *testing.T) { for i := 0; i < msgs; i++ { expected := []byte(fmt.Sprintf("ping %02d", i)) + out := make([]byte, size) if _, err := c2.Read(out); err != nil { t.Fatal(err) @@ -89,10 +95,12 @@ func TestTCPPipeBidirections(t *testing.T) { for i := 0; i < msgs; i++ { expected := []byte(fmt.Sprintf("pong %02d", i)) + out := make([]byte, size) if _, err := c1.Read(out); err != nil { t.Fatal(err) } + if !bytes.Equal(expected, out) { t.Fatalf("expected %#v, got %#v", out, expected) } @@ -107,17 +115,20 @@ func TestNetPipe(t *testing.T) { msgs := 50 size := 1024 + var wg sync.WaitGroup defer wg.Wait() // netPipe is blocking, so writes are emitted asynchronously wg.Add(1) + go func() { defer wg.Done() for i := 0; i < msgs; i++ { msg := make([]byte, size) binary.PutUvarint(msg, uint64(i)) + if _, err := c1.Write(msg); err != nil { t.Error(err) } @@ -127,10 +138,12 @@ func TestNetPipe(t *testing.T) { for i := 0; i < msgs; i++ { msg := make([]byte, size) binary.PutUvarint(msg, uint64(i)) + out := make([]byte, size) if _, err := c2.Read(out); err != nil { t.Error(err) } + if !bytes.Equal(msg, out) { t.Errorf("expected %#v, got %#v", msg, out) } @@ -147,11 +160,13 @@ func TestNetPipeBidirections(t *testing.T) { size := 8 pingTemplate := "ping %03d" pongTemplate := "pong %03d" + var wg sync.WaitGroup defer wg.Wait() // netPipe is blocking, so writes are emitted asynchronously wg.Add(1) + go func() { defer wg.Done() @@ -165,15 +180,18 @@ func TestNetPipeBidirections(t *testing.T) { // netPipe is blocking, so reads for pong are emitted asynchronously wg.Add(1) + go func() { defer wg.Done() for i := 0; i < msgs; i++ { expected := []byte(fmt.Sprintf(pongTemplate, i)) + out := make([]byte, size) if _, err := c1.Read(out); err != nil { t.Error(err) } + if !bytes.Equal(expected, out) { t.Errorf("expected %#v, got %#v", expected, out) } @@ -185,6 +203,7 @@ func TestNetPipeBidirections(t *testing.T) { expected := []byte(fmt.Sprintf(pingTemplate, i)) out := make([]byte, size) + _, err := c2.Read(out) if err != nil { t.Fatal(err) diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 3b4e05a901..663b495f9e 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -162,6 +162,7 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) { if n.PrivateKey != nil { confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey)) } + return json.Marshal(confJSON) } @@ -184,10 +185,12 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { if err != nil { return err } + privKey, err := crypto.ToECDSA(key) if err != nil { return err } + n.PrivateKey = privKey } @@ -221,6 +224,7 @@ func RandomNodeConfig() *NodeConfig { } enodId := enode.PubkeyToIDV4(&prvkey.PublicKey) + return &NodeConfig{ PrivateKey: prvkey, ID: enodId, @@ -236,15 +240,19 @@ func assignTCPPort() (uint16, error) { if err != nil { return 0, err } + l.Close() + _, port, err := net.SplitHostPort(l.Addr().String()) if err != nil { return 0, err } + p, err := strconv.ParseUint(port, 10, 16) if err != nil { return 0, err } + return uint16(p), nil } @@ -286,6 +294,7 @@ func RegisterLifecycles(lifecycles LifecycleConstructors) { if _, exists := lifecycleConstructorFuncs[name]; exists { panic(fmt.Sprintf("node service already exists: %q", name)) } + lifecycleConstructorFuncs[name] = f } @@ -302,8 +311,10 @@ func RegisterLifecycles(lifecycles LifecycleConstructors) { func (n *NodeConfig) initEnode(ip net.IP, tcpport int, udpport int) error { enrIp := enr.IP(ip) n.Record.Set(&enrIp) + enrTcpPort := enr.TCP(tcpport) n.Record.Set(&enrTcpPort) + enrUdpPort := enr.UDP(udpport) n.Record.Set(&enrUdpPort) @@ -311,12 +322,15 @@ func (n *NodeConfig) initEnode(ip net.IP, tcpport int, udpport int) error { if err != nil { return fmt.Errorf("unable to generate ENR: %v", err) } + nod, err := enode.New(enode.V4ID{}, &n.Record) if err != nil { return fmt.Errorf("unable to create enode: %v", err) } + log.Trace("simnode new", "record", n.Record) n.node = nod + return nil } diff --git a/p2p/simulations/connect.go b/p2p/simulations/connect.go index ede96b34c1..565fb22d30 100644 --- a/p2p/simulations/connect.go +++ b/p2p/simulations/connect.go @@ -36,14 +36,17 @@ func (net *Network) ConnectToLastNode(id enode.ID) (err error) { defer net.lock.Unlock() ids := net.getUpNodeIDs() + l := len(ids) if l < 2 { return nil } + last := ids[l-1] if last == id { last = ids[l-2] } + return net.connectNotConnected(last, id) } @@ -57,6 +60,7 @@ func (net *Network) ConnectToRandomNode(id enode.ID) (err error) { if selected == nil { return ErrNodeNotFound } + return net.connectNotConnected(selected.ID(), id) } @@ -70,6 +74,7 @@ func (net *Network) ConnectNodesFull(ids []enode.ID) (err error) { if ids == nil { ids = net.getUpNodeIDs() } + for i, lid := range ids { for _, rid := range ids[i+1:] { if err = net.connectNotConnected(lid, rid); err != nil { @@ -77,6 +82,7 @@ func (net *Network) ConnectNodesFull(ids []enode.ID) (err error) { } } } + return nil } @@ -93,12 +99,14 @@ func (net *Network) connectNodesChain(ids []enode.ID) (err error) { if ids == nil { ids = net.getUpNodeIDs() } + l := len(ids) for i := 0; i < l-1; i++ { if err := net.connectNotConnected(ids[i], ids[i+1]); err != nil { return err } } + return nil } @@ -111,13 +119,16 @@ func (net *Network) ConnectNodesRing(ids []enode.ID) (err error) { if ids == nil { ids = net.getUpNodeIDs() } + l := len(ids) if l < 2 { return nil } + if err := net.connectNodesChain(ids); err != nil { return err } + return net.connectNotConnected(ids[l-1], ids[0]) } @@ -130,14 +141,17 @@ func (net *Network) ConnectNodesStar(ids []enode.ID, center enode.ID) (err error if ids == nil { ids = net.getUpNodeIDs() } + for _, id := range ids { if center == id { continue } + if err := net.connectNotConnected(center, id); err != nil { return err } } + return nil } @@ -149,5 +163,6 @@ func ignoreAlreadyConnectedErr(err error) error { if err == nil || strings.Contains(err.Error(), "already connected") { return nil } + return err } diff --git a/p2p/simulations/connect_test.go b/p2p/simulations/connect_test.go index 0154a18b03..4ec3a0e630 100644 --- a/p2p/simulations/connect_test.go +++ b/p2p/simulations/connect_test.go @@ -26,6 +26,7 @@ import ( func newTestNetwork(t *testing.T, nodeCount int) (*Network, []enode.ID) { t.Helper() + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return NewNoopService(nil), nil @@ -41,13 +42,16 @@ func newTestNetwork(t *testing.T, nodeCount int) (*Network, []enode.ID) { ids := make([]enode.ID, nodeCount) for i := range ids { conf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(conf) if err != nil { t.Fatalf("error creating node: %s", err) } + if err := network.Start(node.ID()); err != nil { t.Fatalf("error starting node: %s", err) } + ids[i] = node.ID() } @@ -93,6 +97,7 @@ func TestConnectToRandomNode(t *testing.T) { } var cc int + for i, a := range ids { for _, b := range ids[i:] { if net.GetConn(a, b) != nil { diff --git a/p2p/simulations/events.go b/p2p/simulations/events.go index d0d03794ed..d5b543c5b3 100644 --- a/p2p/simulations/events.go +++ b/p2p/simulations/events.go @@ -85,6 +85,7 @@ func NewEvent(v interface{}) *Event { default: panic(fmt.Sprintf("invalid event type: %T", v)) } + return event } @@ -92,6 +93,7 @@ func NewEvent(v interface{}) *Event { func ControlEvent(v interface{}) *Event { event := NewEvent(v) event.Control = true + return event } diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go index d9b51dc09b..6bb86460f6 100644 --- a/p2p/simulations/examples/ping-pong.go +++ b/p2p/simulations/examples/ping-pong.go @@ -57,9 +57,9 @@ func main() { var adapter adapters.NodeAdapter switch *adapterType { - case "sim": log.Info("using sim adapter") + adapter = adapters.NewSimAdapter(services) case "exec": @@ -67,6 +67,7 @@ func main() { if err != nil { log.Crit("error creating temp dir", "err", err) } + defer os.RemoveAll(tmpdir) log.Info("using exec adapter", "tmpdir", tmpdir) adapter = adapters.NewExecAdapter(tmpdir) @@ -77,6 +78,7 @@ func main() { // start the HTTP API log.Info("starting simulation server on 0.0.0.0:8888...") + network := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ DefaultService: "ping-pong", }) @@ -140,9 +142,11 @@ func (p *pingPongService) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error { log := p.log.New("peer.id", peer.ID()) errC := make(chan error, 1) + go func() { for range time.Tick(10 * time.Second) { log.Info("sending ping") + if err := p2p.Send(rw, pingMsgCode, "PING"); err != nil { errC <- err return @@ -156,18 +160,23 @@ func (p *pingPongService) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error { errC <- err return } + payload, err := io.ReadAll(msg.Payload) if err != nil { errC <- err return } + log.Info("received message", "msg.code", msg.Code, "msg.payload", string(payload)) atomic.AddInt64(&p.received, 1) + if msg.Code == pingMsgCode { log.Info("sending pong") + go p2p.Send(rw, pongMsgCode, "PONG") } } }() + return <-errC } diff --git a/p2p/simulations/http.go b/p2p/simulations/http.go index 9b3ab3535d..b8f7b0403e 100644 --- a/p2p/simulations/http.go +++ b/p2p/simulations/http.go @@ -102,18 +102,23 @@ type SubscribeOpts struct { // nodes and connections and filtering message events func (c *Client) SubscribeNetwork(events chan *Event, opts SubscribeOpts) (event.Subscription, error) { url := fmt.Sprintf("%s/events?current=%t&filter=%s", c.URL, opts.Current, opts.Filter) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) if err != nil { return nil, err } + req.Header.Set("Accept", "text/event-stream") + res, err := c.client.Do(req) if err != nil { return nil, err } + if res.StatusCode != http.StatusOK { response, _ := io.ReadAll(res.Body) res.Body.Close() + return nil, fmt.Errorf("unexpected HTTP status: %s: %s", res.Status, response) } @@ -127,6 +132,7 @@ func (c *Client) SubscribeNetwork(events chan *Event, opts SubscribeOpts) (event // always reading from the stop channel lines := make(chan string) errC := make(chan error, 1) + go func() { s := bufio.NewScanner(res.Body) for s.Scan() { @@ -147,8 +153,10 @@ func (c *Client) SubscribeNetwork(events chan *Event, opts SubscribeOpts) (event if !strings.HasPrefix(line, "data:") { continue } + data := strings.TrimSpace(strings.TrimPrefix(line, "data:")) event := &Event{} + if err := json.Unmarshal([]byte(data), event); err != nil { return fmt.Errorf("error decoding SSE event: %s", err) } @@ -233,33 +241,42 @@ func (c *Client) Delete(path string) error { // decoding the JSON response into "out" func (c *Client) Send(method, path string, in, out interface{}) error { var body []byte + if in != nil { var err error + body, err = json.Marshal(in) if err != nil { return err } } + req, err := http.NewRequest(method, c.URL+path, bytes.NewReader(body)) if err != nil { return err } + req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") + res, err := c.client.Do(req) if err != nil { return err } + defer res.Body.Close() + if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated { response, _ := io.ReadAll(res.Body) return fmt.Errorf("unexpected HTTP status: %s: %s", res.Status, response) } + if out != nil { if err := json.NewDecoder(res.Body).Decode(out); err != nil { return err } } + return nil } @@ -330,21 +347,26 @@ func (s *Server) StopNetwork(w http.ResponseWriter, req *http.Request) { func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) { s.mockerMtx.Lock() defer s.mockerMtx.Unlock() + if s.mockerStop != nil { http.Error(w, "mocker already running", http.StatusInternalServerError) return } + mockerType := req.FormValue("mocker-type") mockerFn := LookupMocker(mockerType) + if mockerFn == nil { http.Error(w, fmt.Sprintf("unknown mocker type %q", html.EscapeString(mockerType)), http.StatusBadRequest) return } + nodeCount, err := strconv.Atoi(req.FormValue("node-count")) if err != nil { http.Error(w, "invalid node-count provided", http.StatusBadRequest) return } + s.mockerStop = make(chan struct{}) go mockerFn(s.network, s.mockerStop, nodeCount) @@ -355,10 +377,12 @@ func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) { func (s *Server) StopMocker(w http.ResponseWriter, req *http.Request) { s.mockerMtx.Lock() defer s.mockerMtx.Unlock() + if s.mockerStop == nil { http.Error(w, "stop channel not initialized", http.StatusInternalServerError) return } + close(s.mockerStop) s.mockerStop = nil @@ -381,6 +405,7 @@ func (s *Server) ResetNetwork(w http.ResponseWriter, req *http.Request) { // StreamNetworkEvents streams network events as a server-sent-events stream func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { events := make(chan *Event) + sub := s.network.events.Subscribe(events) defer sub.Unsubscribe() @@ -392,6 +417,7 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { write := func(event, data string) { fmt.Fprintf(w, "event: %s\n", event) fmt.Fprintf(w, "data: %s\n\n", data) + if fw, ok := w.(http.Flusher); ok { fw.Flush() } @@ -401,7 +427,9 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { if err != nil { return err } + write("network", string(data)) + return nil } writeErr := func(err error) { @@ -410,8 +438,10 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { // check if filtering has been requested var filters MsgFilters + if filterParam := req.URL.Query().Get("filter"); filterParam != "" { var err error + filters, err = NewMsgFilters(filterParam) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -422,6 +452,7 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "text/event-stream; charset=utf-8") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "\n\n") + if fw, ok := w.(http.Flusher); ok { fw.Flush() } @@ -433,6 +464,7 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { writeErr(err) return } + for _, node := range snap.Nodes { event := NewEvent(&node.Node) if err := writeEvent(event); err != nil { @@ -440,8 +472,10 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { return } } + for _, conn := range snap.Conns { conn := conn + event := NewEvent(&conn) if err := writeEvent(event); err != nil { writeErr(err) @@ -451,6 +485,7 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { } clientGone := req.Context().Done() + for { select { case event := <-events: @@ -458,6 +493,7 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { if event.Msg != nil && !filters.Match(event.Msg) { continue } + if err := writeEvent(event); err != nil { writeErr(err) return @@ -478,24 +514,30 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { // A message code of '*' or '-1' is considered a wildcard and matches any code. func NewMsgFilters(filterParam string) (MsgFilters, error) { filters := make(MsgFilters) + for _, filter := range strings.Split(filterParam, "-") { protoCodes := strings.SplitN(filter, ":", 2) if len(protoCodes) != 2 || protoCodes[0] == "" || protoCodes[1] == "" { return nil, fmt.Errorf("invalid message filter: %s", filter) } + proto := protoCodes[0] + for _, code := range strings.Split(protoCodes[1], ",") { if code == "*" || code == "-1" { filters[MsgFilter{Proto: proto, Code: -1}] = struct{}{} continue } + n, err := strconv.ParseUint(code, 10, 64) if err != nil { return nil, fmt.Errorf("invalid message code: %s", code) } + filters[MsgFilter{Proto: proto, Code: int64(n)}] = struct{}{} } } + return filters, nil } @@ -661,7 +703,9 @@ func (s *Server) NodeRPC(w http.ResponseWriter, req *http.Request) { if err != nil { return } + defer conn.Close() + node := req.Context().Value("node").(*Node) node.ServeRPC(conn) } @@ -710,31 +754,37 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle { if id := params.ByName("nodeid"); id != "" { var nodeID enode.ID + var node *Node if nodeID.UnmarshalText([]byte(id)) == nil { node = s.network.GetNode(nodeID) } else { node = s.network.GetNodeByName(id) } + if node == nil { http.NotFound(w, req) return } + ctx = context.WithValue(ctx, "node", node) } if id := params.ByName("peerid"); id != "" { var peerID enode.ID + var peer *Node if peerID.UnmarshalText([]byte(id)) == nil { peer = s.network.GetNode(peerID) } else { peer = s.network.GetNodeByName(id) } + if peer == nil { http.NotFound(w, req) return } + ctx = context.WithValue(ctx, "peer", peer) } diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go index 05e43238ab..1d2fa2731e 100644 --- a/p2p/simulations/http_test.go +++ b/p2p/simulations/http_test.go @@ -73,6 +73,7 @@ func newTestService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecy stack.RegisterProtocols(svc.Protocols()) stack.RegisterAPIs(svc.APIs()) + return svc, nil } @@ -84,14 +85,17 @@ type testPeer struct { func (t *testService) peer(id enode.ID) *testPeer { t.peersMtx.Lock() defer t.peersMtx.Unlock() + if peer, ok := t.peers[id]; ok { return peer } + peer := &testPeer{ testReady: make(chan struct{}), dumReady: make(chan struct{}), } t.peers[id] = peer + return peer } @@ -143,11 +147,13 @@ func (t *testService) handshake(rw p2p.MsgReadWriter, code uint64) error { errc := make(chan error, 2) go func() { errc <- p2p.SendItems(rw, code) }() go func() { errc <- p2p.ExpectMsg(rw, code, struct{}{}) }() + for i := 0; i < 2; i++ { if err := <-errc; err != nil { return err } } + return nil } @@ -159,9 +165,11 @@ func (t *testService) RunTest(p *p2p.Peer, rw p2p.MsgReadWriter) error { if err := t.handshake(rw, 2); err != nil { return err } + if err := t.handshake(rw, 1); err != nil { return err } + if err := t.handshake(rw, 0); err != nil { return err } @@ -271,6 +279,7 @@ func (t *TestAPI) Events(ctx context.Context) (*rpc.Subscription, error) { go func() { events := make(chan int64) + sub := t.feed.Subscribe(events) defer sub.Unsubscribe() @@ -297,10 +306,12 @@ var testServices = adapters.LifecycleConstructors{ func testHTTPServer(t *testing.T) (*Network, *httptest.Server) { t.Helper() + adapter := adapters.NewSimAdapter(testServices) network := NewNetwork(adapter, &NetworkConfig{ DefaultService: "test", }) + return network, httptest.NewServer(NewServer(network)) } @@ -314,11 +325,14 @@ func TestHTTPNetwork(t *testing.T) { // subscribe to events so we can check them later client := NewClient(s.URL) events := make(chan *Event, 100) + var opts SubscribeOpts + sub, err := client.SubscribeNetwork(events, opts) if err != nil { t.Fatalf("error subscribing to network events: %s", err) } + defer sub.Unsubscribe() // check we can retrieve details about the network @@ -326,6 +340,7 @@ func TestHTTPNetwork(t *testing.T) { if err != nil { t.Fatalf("error getting network: %s", err) } + if gotNetwork.ID != network.ID { t.Fatalf("expected network to have ID %q, got %q", network.ID, gotNetwork.ID) } @@ -347,10 +362,12 @@ func TestHTTPNetwork(t *testing.T) { // reconnect the stream and check we get the current nodes and conns events = make(chan *Event, 100) opts.Current = true + sub, err = client.SubscribeNetwork(events, opts) if err != nil { t.Fatalf("error subscribing to network events: %s", err) } + defer sub.Unsubscribe() x = &expectEvents{t, events, sub} x.expect( @@ -364,12 +381,15 @@ func startTestNetwork(t *testing.T, client *Client) []string { // create two nodes nodeCount := 2 nodeIDs := make([]string, nodeCount) + for i := 0; i < nodeCount; i++ { config := adapters.RandomNodeConfig() + node, err := client.CreateNode(config) if err != nil { t.Fatalf("error creating node: %s", err) } + nodeIDs[i] = node.ID } @@ -378,17 +398,21 @@ func startTestNetwork(t *testing.T, client *Client) []string { if err != nil { t.Fatalf("error getting nodes: %s", err) } + if len(nodes) != nodeCount { t.Fatalf("expected %d nodes, got %d", nodeCount, len(nodes)) } + for i, nodeID := range nodeIDs { if nodes[i].ID != nodeID { t.Fatalf("expected node %d to have ID %q, got %q", i, nodeID, nodes[i].ID) } + node, err := client.GetNode(nodeID) if err != nil { t.Fatalf("error getting node %d: %s", i, err) } + if node.ID != nodeID { t.Fatalf("expected node %d to have ID %q, got %q", i, nodeID, node.ID) } @@ -407,6 +431,7 @@ func startTestNetwork(t *testing.T, client *Client) []string { if i == nodeCount-1 { peerId = 0 } + if err := client.ConnectNode(nodeIDs[i], nodeIDs[peerId]); err != nil { t.Fatalf("error connecting nodes: %s", err) } @@ -476,8 +501,10 @@ loop: func (t *expectEvents) expect(events ...*Event) { t.Helper() + timeout := time.After(10 * time.Second) i := 0 + for { select { case event := <-t.events: @@ -493,9 +520,11 @@ func (t *expectEvents) expect(events ...*Event) { if event.Node == nil { t.Fatal("expected event.Node to be set") } + if event.Node.ID() != expected.Node.ID() { t.Fatalf("expected node event %d to have id %q, got %q", i, expected.Node.ID().TerminalString(), event.Node.ID().TerminalString()) } + if event.Node.Up() != expected.Node.Up() { t.Fatalf("expected node event %d to have up=%t, got up=%t", i, expected.Node.Up(), event.Node.Up()) } @@ -504,12 +533,15 @@ func (t *expectEvents) expect(events ...*Event) { if event.Conn == nil { t.Fatal("expected event.Conn to be set") } + if event.Conn.One != expected.Conn.One { t.Fatalf("expected conn event %d to have one=%q, got one=%q", i, expected.Conn.One.TerminalString(), event.Conn.One.TerminalString()) } + if event.Conn.Other != expected.Conn.Other { t.Fatalf("expected conn event %d to have other=%q, got other=%q", i, expected.Conn.Other.TerminalString(), event.Conn.Other.TerminalString()) } + if event.Conn.Up != expected.Conn.Up { t.Fatalf("expected conn event %d to have up=%t, got up=%t", i, expected.Conn.Up, event.Conn.Up) } @@ -539,10 +571,12 @@ func TestHTTPNodeRPC(t *testing.T) { client := NewClient(s.URL) config := adapters.RandomNodeConfig() + node, err := client.CreateNode(config) if err != nil { t.Fatalf("error creating node: %s", err) } + if err := client.StartNode(node.ID); err != nil { t.Fatalf("error starting node: %s", err) } @@ -550,10 +584,12 @@ func TestHTTPNodeRPC(t *testing.T) { // create two RPC clients ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() + rpcClient1, err := client.RPCClient(ctx, node.ID) if err != nil { t.Fatalf("error getting node RPC client: %s", err) } + rpcClient2, err := client.RPCClient(ctx, node.ID) if err != nil { t.Fatalf("error getting node RPC client: %s", err) @@ -561,20 +597,24 @@ func TestHTTPNodeRPC(t *testing.T) { // subscribe to events using client 1 events := make(chan int64, 1) + sub, err := rpcClient1.Subscribe(ctx, "test", events, "events") if err != nil { t.Fatalf("error subscribing to events: %s", err) } + defer sub.Unsubscribe() // call some RPC methods using client 2 if err := rpcClient2.CallContext(ctx, nil, "test_add", 10); err != nil { t.Fatalf("error calling RPC method: %s", err) } + var result int64 if err := rpcClient2.CallContext(ctx, &result, "test_get"); err != nil { t.Fatalf("error calling RPC method: %s", err) } + if result != 10 { t.Fatalf("expected result to be 10, got %d", result) } @@ -597,11 +637,14 @@ func TestHTTPSnapshot(t *testing.T) { defer s.Close() var eventsDone = make(chan struct{}, 1) + count := 1 eventsDoneChan := make(chan *Event) eventSub := network.Events().Subscribe(eventsDoneChan) + go func() { defer eventSub.Unsubscribe() + for event := range eventsDoneChan { if event.Type == EventTypeConn && !event.Control { count-- @@ -617,41 +660,52 @@ func TestHTTPSnapshot(t *testing.T) { client := NewClient(s.URL) nodeCount := 2 nodes := make([]*p2p.NodeInfo, nodeCount) + for i := 0; i < nodeCount; i++ { config := adapters.RandomNodeConfig() + node, err := client.CreateNode(config) if err != nil { t.Fatalf("error creating node: %s", err) } + if err := client.StartNode(node.ID); err != nil { t.Fatalf("error starting node: %s", err) } + nodes[i] = node } + if err := client.ConnectNode(nodes[0].ID, nodes[1].ID); err != nil { t.Fatalf("error connecting nodes: %s", err) } // store some state in the test services states := make([]string, nodeCount) + for i, node := range nodes { rpc, err := client.RPCClient(context.Background(), node.ID) if err != nil { t.Fatalf("error getting RPC client: %s", err) } + defer rpc.Close() + state := fmt.Sprintf("%x", rand.Int()) if err := rpc.Call(nil, "test_setState", []byte(state)); err != nil { t.Fatalf("error setting service state: %s", err) } + states[i] = state } + <-eventsDone // create a snapshot snap, err := client.CreateSnapshot() if err != nil { t.Fatalf("error creating snapshot: %s", err) } + for i, state := range states { gotState := snap.Nodes[i].Snapshots["test"] if string(gotState) != state { @@ -665,8 +719,10 @@ func TestHTTPSnapshot(t *testing.T) { client = NewClient(s.URL) count = 1 eventSub = network2.Events().Subscribe(eventsDoneChan) + go func() { defer eventSub.Unsubscribe() + for event := range eventsDoneChan { if event.Type == EventTypeConn && !event.Control { count-- @@ -680,17 +736,21 @@ func TestHTTPSnapshot(t *testing.T) { // subscribe to events so we can check them later events := make(chan *Event, 100) + var opts SubscribeOpts + sub, err := client.SubscribeNetwork(events, opts) if err != nil { t.Fatalf("error subscribing to network events: %s", err) } + defer sub.Unsubscribe() // load the snapshot if err := client.LoadSnapshot(snap); err != nil { t.Fatalf("error loading snapshot: %s", err) } + <-eventsDone // check the nodes and connection exists @@ -698,25 +758,31 @@ func TestHTTPSnapshot(t *testing.T) { if err != nil { t.Fatalf("error getting network: %s", err) } + if len(net.Nodes) != nodeCount { t.Fatalf("expected network to have %d nodes, got %d", nodeCount, len(net.Nodes)) } + for i, node := range nodes { id := net.Nodes[i].ID().String() if id != node.ID { t.Fatalf("expected node %d to have ID %s, got %s", i, node.ID, id) } } + if len(net.Conns) != 1 { t.Fatalf("expected network to have 1 connection, got %d", len(net.Conns)) } + conn := net.Conns[0] if conn.One.String() != nodes[0].ID { t.Fatalf("expected connection to have one=%q, got one=%q", nodes[0].ID, conn.One) } + if conn.Other.String() != nodes[1].ID { t.Fatalf("expected connection to have other=%q, got other=%q", nodes[1].ID, conn.Other) } + if !conn.Up { t.Fatal("should be up") } @@ -727,11 +793,14 @@ func TestHTTPSnapshot(t *testing.T) { if err != nil { t.Fatalf("error getting RPC client: %s", err) } + defer rpc.Close() + var state []byte if err := rpc.Call(&state, "test_getState"); err != nil { t.Fatalf("error getting service state: %s", err) } + if string(state) != states[i] { t.Fatalf("expected snapshot state %q, got %q", states[i], state) } @@ -762,10 +831,12 @@ func TestMsgFilterPassMultiple(t *testing.T) { opts := SubscribeOpts{ Filter: "prb:0-test:0", } + sub, err := client.SubscribeNetwork(events, opts) if err != nil { t.Fatalf("error subscribing to network events: %s", err) } + defer sub.Unsubscribe() // start a simulation network @@ -792,10 +863,12 @@ func TestMsgFilterPassWildcard(t *testing.T) { opts := SubscribeOpts{ Filter: "prb:0,2-test:*", } + sub, err := client.SubscribeNetwork(events, opts) if err != nil { t.Fatalf("error subscribing to network events: %s", err) } + defer sub.Unsubscribe() // start a simulation network @@ -824,10 +897,12 @@ func TestMsgFilterPassSingle(t *testing.T) { opts := SubscribeOpts{ Filter: "dum:0", } + sub, err := client.SubscribeNetwork(events, opts) if err != nil { t.Fatalf("error subscribing to network events: %s", err) } + defer sub.Unsubscribe() // start a simulation network @@ -852,18 +927,21 @@ func TestMsgFilterFailBadParams(t *testing.T) { opts := SubscribeOpts{ Filter: "foo:", } + _, err := client.SubscribeNetwork(events, opts) if err == nil { t.Fatalf("expected event subscription to fail but succeeded!") } opts.Filter = "bzz:aa" + _, err = client.SubscribeNetwork(events, opts) if err == nil { t.Fatalf("expected event subscription to fail but succeeded!") } opts.Filter = "invalid" + _, err = client.SubscribeNetwork(events, opts) if err == nil { t.Fatalf("expected event subscription to fail but succeeded!") diff --git a/p2p/simulations/mocker.go b/p2p/simulations/mocker.go index 0dc04e65f9..f47d1bd42f 100644 --- a/p2p/simulations/mocker.go +++ b/p2p/simulations/mocker.go @@ -48,6 +48,7 @@ func GetMockerList() []string { for k := range mockerList { list = append(list, k) } + return list } @@ -65,8 +66,10 @@ func startStop(net *Network, quit chan struct{}, nodeCount int) { if err != nil { panic("Could not startup node network for mocker") } + tick := time.NewTicker(10 * time.Second) defer tick.Stop() + for { select { case <-quit: @@ -75,6 +78,7 @@ func startStop(net *Network, quit chan struct{}, nodeCount int) { case <-tick.C: id := nodes[rand.Intn(len(nodes))] log.Info("stopping node", "id", id) + if err := net.Stop(id); err != nil { log.Error("error stopping node", "id", id, "err", err) return @@ -88,6 +92,7 @@ func startStop(net *Network, quit chan struct{}, nodeCount int) { } log.Debug("starting node", "id", id) + if err := net.Start(id); err != nil { log.Error("error starting node", "id", id, "err", err) return @@ -111,6 +116,7 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) { panic("Could not startup node network for mocker") } } + for { select { case <-quit: @@ -118,11 +124,15 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) { return default: } + var lowid, highid int + var wg sync.WaitGroup + randWait := time.Duration(rand.Intn(5000)+1000) * time.Millisecond rand1 := rand.Intn(nodeCount - 1) rand2 := rand.Intn(nodeCount - 1) + if rand1 <= rand2 { lowid = rand1 highid = rand2 @@ -130,8 +140,11 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) { highid = rand1 lowid = rand2 } + var steps = highid - lowid + wg.Add(steps) + for i := lowid; i < highid; i++ { select { case <-quit: @@ -140,18 +153,23 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) { case <-time.After(randWait): } log.Debug(fmt.Sprintf("node %v shutting down", nodes[i])) + err := net.Stop(nodes[i]) if err != nil { log.Error("Error stopping node", "node", nodes[i]) wg.Done() + continue } + go func(id enode.ID) { time.Sleep(randWait) + err := net.Start(id) if err != nil { log.Error("Error starting node", "node", id) } + wg.Done() }(nodes[i]) } @@ -162,13 +180,16 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) { // connect nodeCount number of nodes in a ring func connectNodesInRing(net *Network, nodeCount int) ([]enode.ID, error) { ids := make([]enode.ID, nodeCount) + for i := 0; i < nodeCount; i++ { conf := adapters.RandomNodeConfig() + node, err := net.NewNodeWithConfig(conf) if err != nil { log.Error("Error creating a node!", "err", err) return nil, err } + ids[i] = node.ID() } @@ -177,8 +198,10 @@ func connectNodesInRing(net *Network, nodeCount int) ([]enode.ID, error) { log.Error("Error starting a node!", "err", err) return nil, err } + log.Debug(fmt.Sprintf("node %v starting up", id)) } + for i, id := range ids { peerID := ids[(i+1)%len(ids)] if err := net.Connect(id, peerID); err != nil { diff --git a/p2p/simulations/mocker_test.go b/p2p/simulations/mocker_test.go index d01e227a67..ce3b78951b 100644 --- a/p2p/simulations/mocker_test.go +++ b/p2p/simulations/mocker_test.go @@ -66,6 +66,7 @@ func TestMocker(t *testing.T) { //check the list is at least 1 in size var mockerlist []string + err = json.NewDecoder(resp.Body).Decode(&mockerlist) if err != nil { t.Fatalf("Error decoding JSON mockerlist: %s", err) @@ -76,10 +77,13 @@ func TestMocker(t *testing.T) { } nodeCount := 10 + var wg sync.WaitGroup events := make(chan *Event, 10) + var opts SubscribeOpts + sub, err := client.SubscribeNetwork(events, opts) defer sub.Unsubscribe() @@ -88,7 +92,9 @@ func TestMocker(t *testing.T) { nodemap := make(map[enode.ID]bool) nodesComplete := false connCount := 0 + wg.Add(1) + go func() { defer wg.Done() @@ -126,7 +132,9 @@ func TestMocker(t *testing.T) { if err != nil { t.Fatalf("Could not start mocker: %s", err) } + resp.Body.Close() + if resp.StatusCode != 200 { t.Fatalf("Invalid Status Code received for starting mocker, expected 200, got %d", resp.StatusCode) } @@ -148,7 +156,9 @@ func TestMocker(t *testing.T) { if err != nil { t.Fatalf("Could not stop mocker: %s", err) } + resp.Body.Close() + if resp.StatusCode != 200 { t.Fatalf("Invalid Status Code received for stopping mocker, expected 200, got %d", resp.StatusCode) } @@ -159,13 +169,16 @@ func TestMocker(t *testing.T) { } resetUrl := s.URL + "/reset" req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, resetUrl, nil) + if err != nil { log.Crit("Can't build request", "url", resetUrl, "err", err) } + resp, err = cli.Do(req) if err != nil { t.Fatalf("Could not reset network: %s", err) } + resp.Body.Close() //now the number of nodes in the network should be zero diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index 4735e5cfa6..eab262723d 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -96,6 +96,7 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) if err != nil && bytes.Compare(conf.ID.Bytes(), otherID.Bytes()) < 0 { return false } + return true } } @@ -104,6 +105,7 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) if node := net.getNode(conf.ID); node != nil { return nil, fmt.Errorf("node with ID %q already exists", conf.ID) } + if node := net.getNodeByName(conf.Name); node != nil { return nil, fmt.Errorf("node with name %q already exists", conf.Name) } @@ -118,6 +120,7 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) if err != nil { return nil, err } + node := newNode(adapterNode, conf, false) log.Trace("Node created", "id", conf.ID) @@ -147,10 +150,12 @@ func (net *Network) StartAll() error { if node.Up() { continue } + if err := net.Start(node.ID()); err != nil { return err } } + return nil } @@ -160,10 +165,12 @@ func (net *Network) StopAll() error { if !node.Up() { continue } + if err := net.Stop(node.ID()); err != nil { return err } } + return nil } @@ -182,16 +189,21 @@ func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) if node == nil { return fmt.Errorf("node %v does not exist", id) } + if node.Up() { return fmt.Errorf("node %v already up", id) } + log.Trace("Starting node", "id", id, "adapter", net.nodeAdapter.Name()) + if err := node.Start(snapshots); err != nil { log.Warn("Node startup failed", "id", id, "err", err) return err } + node.SetUp(true) log.Info("Started node", "id", id) + ev := NewEvent(node) net.events.Send(ev) @@ -200,12 +212,16 @@ func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) if err != nil { return fmt.Errorf("error getting rpc client for node %v: %s", id, err) } + events := make(chan *p2p.PeerEvent) + sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents") if err != nil { return fmt.Errorf("error getting peer events for node %v: %s", id, err) } + go net.watchPeerEvents(id, events, sub) + return nil } @@ -223,16 +239,19 @@ func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub if node == nil { return } + node.SetUp(false) ev := NewEvent(node) net.events.Send(ev) }() + for { select { case event, ok := <-events: if !ok { return } + peer := event.Peer switch event.Type { case p2p.PeerEventTypeAdd: @@ -252,6 +271,7 @@ func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub if err != nil { log.Error("Error in peer event subscription", "id", id, "err", err) } + return } } @@ -263,7 +283,6 @@ func (net *Network) Stop(id enode.ID) error { // node.Reachable() closure has a reference to the network and // calls net.InitConn() what also locks the network. => DEADLOCK // That holds until the following ticket is not resolved: - var err error node, err := func() (*Node, error) { @@ -274,10 +293,13 @@ func (net *Network) Stop(id enode.ID) error { if node == nil { return nil, fmt.Errorf("node %v does not exist", id) } + if !node.Up() { return nil, fmt.Errorf("node %v already down", id) } + node.SetUp(false) + return node, nil }() if err != nil { @@ -293,9 +315,12 @@ func (net *Network) Stop(id enode.ID) error { node.SetUp(true) return err } + log.Info("Stopped node", "id", id, "err", err) + ev := ControlEvent(node) net.events.Send(ev) + return nil } @@ -304,20 +329,25 @@ func (net *Network) Stop(id enode.ID) error { func (net *Network) Connect(oneID, otherID enode.ID) error { net.lock.Lock() defer net.lock.Unlock() + return net.connect(oneID, otherID) } func (net *Network) connect(oneID, otherID enode.ID) error { log.Debug("Connecting nodes with addPeer", "id", oneID, "other", otherID) + conn, err := net.initConn(oneID, otherID) if err != nil { return err } + client, err := conn.one.Client() if err != nil { return err } + net.events.Send(ControlEvent(conn)) + return client.Call(nil, "admin_addPeer", string(conn.other.Addr())) } @@ -328,14 +358,18 @@ func (net *Network) Disconnect(oneID, otherID enode.ID) error { if conn == nil { return fmt.Errorf("connection between %v and %v does not exist", oneID, otherID) } + if !conn.Up { return fmt.Errorf("%v and %v already disconnected", oneID, otherID) } + client, err := conn.one.Client() if err != nil { return err } + net.events.Send(ControlEvent(conn)) + return client.Call(nil, "admin_removePeer", string(conn.other.Addr())) } @@ -343,15 +377,19 @@ func (net *Network) Disconnect(oneID, otherID enode.ID) error { func (net *Network) DidConnect(one, other enode.ID) error { net.lock.Lock() defer net.lock.Unlock() + conn, err := net.getOrCreateConn(one, other) if err != nil { return fmt.Errorf("connection between %v and %v does not exist", one, other) } + if conn.Up { return fmt.Errorf("%v and %v already connected", one, other) } + conn.Up = true net.events.Send(NewEvent(conn)) + return nil } @@ -360,16 +398,20 @@ func (net *Network) DidConnect(one, other enode.ID) error { func (net *Network) DidDisconnect(one, other enode.ID) error { net.lock.Lock() defer net.lock.Unlock() + conn := net.getConn(one, other) if conn == nil { return fmt.Errorf("connection between %v and %v does not exist", one, other) } + if !conn.Up { return fmt.Errorf("%v and %v already disconnected", one, other) } + conn.Up = false conn.initiated = time.Now().Add(-DialBanTimeout) net.events.Send(NewEvent(conn)) + return nil } @@ -383,6 +425,7 @@ func (net *Network) DidSend(sender, receiver enode.ID, proto string, code uint64 Received: false, } net.events.Send(NewEvent(msg)) + return nil } @@ -396,6 +439,7 @@ func (net *Network) DidReceive(sender, receiver enode.ID, proto string, code uin Received: true, } net.events.Send(NewEvent(msg)) + return nil } @@ -404,6 +448,7 @@ func (net *Network) DidReceive(sender, receiver enode.ID, proto string, code uin func (net *Network) GetNode(id enode.ID) *Node { net.lock.RLock() defer net.lock.RUnlock() + return net.getNode(id) } @@ -412,6 +457,7 @@ func (net *Network) getNode(id enode.ID) *Node { if !found { return nil } + return net.Nodes[i] } @@ -420,6 +466,7 @@ func (net *Network) getNode(id enode.ID) *Node { func (net *Network) GetNodeByName(name string) *Node { net.lock.RLock() defer net.lock.RUnlock() + return net.getNodeByName(name) } @@ -429,6 +476,7 @@ func (net *Network) getNodeByName(name string) *Node { return node } } + return nil } @@ -452,6 +500,7 @@ func (net *Network) getNodeIDs(excludeIDs []enode.ID) []enode.ID { // Return the difference of nodeIDs and excludeIDs return filterIDs(nodeIDs, excludeIDs) } + return nodeIDs } @@ -469,6 +518,7 @@ func (net *Network) getNodes(excludeIDs []enode.ID) []*Node { nodeIDs := net.getNodeIDs(excludeIDs) return net.getNodesByID(nodeIDs) } + return net.Nodes } @@ -483,6 +533,7 @@ func (net *Network) GetNodesByID(nodeIDs []enode.ID) []*Node { func (net *Network) getNodesByID(nodeIDs []enode.ID) []*Node { nodes := make([]*Node, 0, len(nodeIDs)) + for _, id := range nodeIDs { node := net.getNode(id) if node != nil { @@ -520,6 +571,7 @@ func (net *Network) GetNodeIDsByProperty(property string) []enode.ID { func (net *Network) getNodeIDsByProperty(property string) []enode.ID { nodeIDs := make([]enode.ID, 0, len(net.propertyMap[property])) + for _, nodeIndex := range net.propertyMap[property] { node := net.Nodes[nodeIndex] nodeIDs = append(nodeIDs, node.ID()) @@ -532,6 +584,7 @@ func (net *Network) getNodeIDsByProperty(property string) []enode.ID { func (net *Network) GetRandomUpNode(excludeIDs ...enode.ID) *Node { net.lock.RLock() defer net.lock.RUnlock() + return net.getRandomUpNode(excludeIDs...) } @@ -546,6 +599,7 @@ func (net *Network) getUpNodeIDs() (ids []enode.ID) { ids = append(ids, node.ID()) } } + return ids } @@ -553,6 +607,7 @@ func (net *Network) getUpNodeIDs() (ids []enode.ID) { func (net *Network) GetRandomDownNode(excludeIDs ...enode.ID) *Node { net.lock.RLock() defer net.lock.RUnlock() + return net.getRandomNode(net.getDownNodeIDs(), excludeIDs) } @@ -562,6 +617,7 @@ func (net *Network) getDownNodeIDs() (ids []enode.ID) { ids = append(ids, node.ID()) } } + return ids } @@ -569,6 +625,7 @@ func (net *Network) getDownNodeIDs() (ids []enode.ID) { func (net *Network) GetRandomNode(excludeIDs ...enode.ID) *Node { net.lock.RLock() defer net.lock.RUnlock() + return net.getRandomNode(net.getNodeIDs(nil), excludeIDs) // no need to exclude twice } @@ -579,6 +636,7 @@ func (net *Network) getRandomNode(ids []enode.ID, excludeIDs []enode.ID) *Node { if l == 0 { return nil } + return net.getNode(filtered[rand.Intn(l)]) } @@ -587,12 +645,15 @@ func filterIDs(ids []enode.ID, excludeIDs []enode.ID) []enode.ID { for _, id := range excludeIDs { exclude[id] = true } + var filtered []enode.ID + for _, id := range ids { if _, found := exclude[id]; !found { filtered = append(filtered, id) } } + return filtered } @@ -601,6 +662,7 @@ func filterIDs(ids []enode.ID, excludeIDs []enode.ID) []enode.ID { func (net *Network) GetConn(oneID, otherID enode.ID) *Conn { net.lock.RLock() defer net.lock.RUnlock() + return net.getConn(oneID, otherID) } @@ -609,6 +671,7 @@ func (net *Network) GetConn(oneID, otherID enode.ID) *Conn { func (net *Network) GetOrCreateConn(oneID, otherID enode.ID) (*Conn, error) { net.lock.Lock() defer net.lock.Unlock() + return net.getOrCreateConn(oneID, otherID) } @@ -621,10 +684,12 @@ func (net *Network) getOrCreateConn(oneID, otherID enode.ID) (*Conn, error) { if one == nil { return nil, fmt.Errorf("node %v does not exist", oneID) } + other := net.getNode(otherID) if other == nil { return nil, fmt.Errorf("node %v does not exist", otherID) } + conn := &Conn{ One: oneID, Other: otherID, @@ -634,15 +699,18 @@ func (net *Network) getOrCreateConn(oneID, otherID enode.ID) (*Conn, error) { label := ConnLabel(oneID, otherID) net.connMap[label] = len(net.Conns) net.Conns = append(net.Conns, conn) + return conn, nil } func (net *Network) getConn(oneID, otherID enode.ID) *Conn { label := ConnLabel(oneID, otherID) + i, found := net.connMap[label] if !found { return nil } + return net.Conns[i] } @@ -657,6 +725,7 @@ func (net *Network) getConn(oneID, otherID enode.ID) *Conn { func (net *Network) InitConn(oneID, otherID enode.ID) (*Conn, error) { net.lock.Lock() defer net.lock.Unlock() + return net.initConn(oneID, otherID) } @@ -664,13 +733,16 @@ func (net *Network) initConn(oneID, otherID enode.ID) (*Conn, error) { if oneID == otherID { return nil, fmt.Errorf("refusing to connect to self %v", oneID) } + conn, err := net.getOrCreateConn(oneID, otherID) if err != nil { return nil, err } + if conn.Up { return nil, fmt.Errorf("%v and %v already connected", oneID, otherID) } + if time.Since(conn.initiated) < DialBanTimeout { return nil, fmt.Errorf("connection between %v and %v recently attempted", oneID, otherID) } @@ -680,8 +752,11 @@ func (net *Network) initConn(oneID, otherID enode.ID) (*Conn, error) { log.Trace("Nodes not up", "err", err) return nil, fmt.Errorf("nodes not up: %v", err) } + log.Debug("Connection initiated", "id", oneID, "other", otherID) + conn.initiated = time.Now() + return conn, nil } @@ -689,10 +764,12 @@ func (net *Network) initConn(oneID, otherID enode.ID) (*Conn, error) { func (net *Network) Shutdown() { for _, node := range net.Nodes { log.Debug("Stopping node", "id", node.ID()) + if err := node.Stop(); err != nil { log.Warn("Can't stop node", "id", node.ID(), "err", err) } } + close(net.quitc) } @@ -737,6 +814,7 @@ func (n *Node) copy() *Node { func (n *Node) Up() bool { n.upMu.RLock() defer n.upMu.RUnlock() + return n.up } @@ -763,8 +841,10 @@ func (n *Node) NodeInfo() *p2p.NodeInfo { if n.Node == nil { return nil } + info := n.Node.NodeInfo() info.Name = n.Config.Name + return info } @@ -791,10 +871,13 @@ func (n *Node) UnmarshalJSON(raw []byte) error { Config *adapters.NodeConfig `json:"config,omitempty"` Up bool `json:"up"` } + if err := json.Unmarshal(raw, &node); err != nil { return err } + *n = *newNode(nil, node.Config, node.Up) + return nil } @@ -820,9 +903,11 @@ func (c *Conn) nodesUp() error { if !c.one.Up() { return fmt.Errorf("one %v is not up", c.One) } + if !c.other.Up() { return fmt.Errorf("other %v is not up", c.Other) } + return nil } @@ -857,6 +942,7 @@ func ConnLabel(source, target enode.ID) string { first = source second = target } + return fmt.Sprintf("%v-%v", first, second) } @@ -887,53 +973,67 @@ func (net *Network) SnapshotWithServices(addServices []string, removeServices [] func (net *Network) snapshot(addServices []string, removeServices []string) (*Snapshot, error) { net.lock.Lock() defer net.lock.Unlock() + snap := &Snapshot{ Nodes: make([]NodeSnapshot, len(net.Nodes)), } for i, node := range net.Nodes { snap.Nodes[i] = NodeSnapshot{Node: *node.copy()} + if !node.Up() { continue } + snapshots, err := node.Snapshots() if err != nil { return nil, err } + snap.Nodes[i].Snapshots = snapshots + for _, addSvc := range addServices { haveSvc := false + for _, svc := range snap.Nodes[i].Node.Config.Lifecycles { if svc == addSvc { haveSvc = true break } } + if !haveSvc { snap.Nodes[i].Node.Config.Lifecycles = append(snap.Nodes[i].Node.Config.Lifecycles, addSvc) } } + if len(removeServices) > 0 { var cleanedServices []string + for _, svc := range snap.Nodes[i].Node.Config.Lifecycles { haveSvc := false + for _, rmSvc := range removeServices { if rmSvc == svc { haveSvc = true break } } + if !haveSvc { cleanedServices = append(cleanedServices, svc) } } + snap.Nodes[i].Node.Config.Lifecycles = cleanedServices } } + for _, conn := range net.Conns { if conn.Up { snap.Conns = append(snap.Conns, *conn) } } + return snap, nil } @@ -947,9 +1047,11 @@ func (net *Network) Load(snap *Snapshot) error { if _, err := net.NewNodeWithConfig(n.Node.Config); err != nil { return err } + if !n.Node.Up() { continue } + if err := net.startWithSnapshots(n.Node.Config.ID, n.Snapshots); err != nil { return err } @@ -957,13 +1059,15 @@ func (net *Network) Load(snap *Snapshot) error { // Prepare connection events counter. allConnected := make(chan struct{}) // closed when all connections are established - done := make(chan struct{}) // ensures that the event loop goroutine is terminated + + done := make(chan struct{}) // ensures that the event loop goroutine is terminated defer close(done) // Subscribe to event channel. // It needs to be done outside of the event loop goroutine (created below) // to ensure that the event channel is blocking before connect calls are made. events := make(chan *Event) + sub := net.Events().Subscribe(events) defer sub.Unsubscribe() @@ -986,6 +1090,7 @@ func (net *Network) Load(snap *Snapshot) error { if e.Type != EventTypeConn { continue } + connection := [2]enode.ID{e.Conn.One, e.Conn.Other} // Nodes are still not connected or have been disconnected. if !e.Conn.Up { @@ -993,6 +1098,7 @@ func (net *Network) Load(snap *Snapshot) error { // This will prevent false positive in case disconnections happen. delete(connections, connection) log.Warn("load snapshot: unexpected disconnection", "one", e.Conn.One, "other", e.Conn.Other) + continue } // Check that the connection is from the snapshot. @@ -1023,6 +1129,7 @@ func (net *Network) Load(snap *Snapshot) error { //so it would result in the snapshot `Load` to fail continue } + if err := net.Connect(conn.One, conn.Other); err != nil { return err } @@ -1035,6 +1142,7 @@ func (net *Network) Load(snap *Snapshot) error { case <-time.After(snapshotLoadTimeout): return errors.New("snapshot connections not established") } + return nil } @@ -1046,6 +1154,7 @@ func (net *Network) Subscribe(events chan *Event) { if !ok { return } + if event.Control { net.executeControlEvent(event) } @@ -1057,6 +1166,7 @@ func (net *Network) Subscribe(events chan *Event) { func (net *Network) executeControlEvent(event *Event) { log.Trace("Executing control event", "type", event.Type, "event", event) + switch event.Type { case EventTypeNode: if err := net.executeNodeEvent(event); err != nil { @@ -1079,6 +1189,7 @@ func (net *Network) executeNodeEvent(e *Event) error { if _, err := net.NewNodeWithConfig(e.Node.Config); err != nil { return err } + return net.Start(e.Node.ID()) } @@ -1086,5 +1197,6 @@ func (net *Network) executeConnEvent(e *Event) error { if e.Conn.Up { return net.Connect(e.Conn.One, e.Conn.Other) } + return net.Disconnect(e.Conn.One, e.Conn.Other) } diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index ab8cf19462..2eaa0e452a 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -38,7 +38,6 @@ import ( func TestSnapshot(t *testing.T) { // PART I // create snapshot from ring network - // this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { @@ -61,26 +60,32 @@ func TestSnapshot(t *testing.T) { // create and start nodes nodeCount := 20 ids := make([]enode.ID, nodeCount) + for i := 0; i < nodeCount; i++ { conf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(conf) if err != nil { t.Fatalf("error creating node: %s", err) } + if err := network.Start(node.ID()); err != nil { t.Fatalf("error starting node: %s", err) } + ids[i] = node.ID() } // subscribe to peer events evC := make(chan *Event) + sub := network.Events().Subscribe(evC) defer sub.Unsubscribe() // connect nodes in a ring // spawn separate thread to avoid deadlock in the event listeners connectErr := make(chan error, 1) + go func() { for i, id := range ids { peerID := ids[(i+1)%len(ids)] @@ -94,6 +99,7 @@ func TestSnapshot(t *testing.T) { // collect connection events up to expected number ctx, cancel := context.WithTimeout(context.TODO(), time.Second) defer cancel() + checkIds := make(map[enode.ID][]enode.ID) connEventCount := nodeCount OUTER: @@ -125,10 +131,12 @@ OUTER: if err != nil { t.Fatal(err) } + j, err := json.Marshal(snap) if err != nil { t.Fatal(err) } + log.Debug("snapshot taken", "nodes", len(snap.Nodes), "conns", len(snap.Conns), "json", string(j)) // verify that the snap element numbers check out @@ -138,6 +146,7 @@ OUTER: // shut down sim network runningOne = false + sub.Unsubscribe() network.Shutdown() @@ -145,6 +154,7 @@ OUTER: for nodid, nodConns := range checkIds { for _, nodConn := range nodConns { var match bool + for _, snapConn := range snap.Conns { if snapConn.One == nodid && snapConn.Other == nodConn { match = true @@ -154,11 +164,13 @@ OUTER: break } } + if !match { t.Fatalf("snapshot missing conn %v -> %v", nodid, nodConn) } } } + log.Info("snapshot checked") // PART II @@ -172,6 +184,7 @@ OUTER: network = NewNetwork(adapter, &NetworkConfig{ DefaultService: "noopwoop", }) + defer func() { network.Shutdown() }() @@ -180,6 +193,7 @@ OUTER: // every node up and conn up event will generate one additional control event // therefore multiply the count by two evC = make(chan *Event, (len(snap.Conns)*2)+(len(snap.Nodes)*2)) + sub = network.Events().Subscribe(evC) defer sub.Unsubscribe() @@ -222,6 +236,7 @@ OuterTwo: // check that we have all expected connections in the network for _, snapConn := range snap.Conns { var match bool + for nodid, nodConns := range checkIds { for _, nodConn := range nodConns { if snapConn.One == nodid && snapConn.Other == nodConn { @@ -233,6 +248,7 @@ OuterTwo: } } } + if !match { t.Fatalf("network missing conn %v -> %v", snapConn.One, snapConn.Other) } @@ -289,21 +305,27 @@ func TestNetworkSimulation(t *testing.T) { adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) + network := NewNetwork(adapter, &NetworkConfig{ DefaultService: "test", }) defer network.Shutdown() + nodeCount := 20 ids := make([]enode.ID, nodeCount) + for i := 0; i < nodeCount; i++ { conf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(conf) if err != nil { t.Fatalf("error creating node: %s", err) } + if err := network.Start(node.ID()); err != nil { t.Fatalf("error starting node: %s", err) } + ids[i] = node.ID() } @@ -317,6 +339,7 @@ func TestNetworkSimulation(t *testing.T) { return err } } + return nil } check := func(ctx context.Context, id enode.ID) (bool, error) { @@ -338,10 +361,12 @@ func TestNetworkSimulation(t *testing.T) { if err != nil { return false, err } + var peerCount int64 if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil { return false, err } + switch { case peerCount < 2: return false, nil @@ -353,6 +378,7 @@ func TestNetworkSimulation(t *testing.T) { } timeout := 30 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -377,17 +403,21 @@ func TestNetworkSimulation(t *testing.T) { if err != nil { t.Fatal(err) } + if len(snap.Nodes) != nodeCount { t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes)) } + if len(snap.Conns) != nodeCount { t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns)) } + for i, id := range ids { conn := snap.Conns[i] if conn.One != id { t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One) } + peerID := ids[(i+1)%len(ids)] if conn.Other != peerID { t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other) @@ -398,10 +428,12 @@ func TestNetworkSimulation(t *testing.T) { func createTestNodes(count int, network *Network) (nodes []*Node, err error) { for i := 0; i < count; i++ { nodeConf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(nodeConf) if err != nil { return nil, err } + if err := network.Start(node.ID()); err != nil { return nil, err } @@ -421,6 +453,7 @@ func createTestNodesWithProperty(property string, count int, network *Network) ( if err != nil { return nil, err } + if err := network.Start(node.ID()); err != nil { return nil, err } @@ -438,12 +471,14 @@ func TestGetNodeIDs(t *testing.T) { adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) + network := NewNetwork(adapter, &NetworkConfig{ DefaultService: "test", }) defer network.Shutdown() numNodes := 5 + nodes, err := createTestNodes(numNodes, network) if err != nil { t.Fatalf("Could not create test nodes %v", err) @@ -456,6 +491,7 @@ func TestGetNodeIDs(t *testing.T) { for _, node1 := range nodes { match := false + for _, node2ID := range gotNodeIDs { if bytes.Equal(node1.ID().Bytes(), node2ID.Bytes()) { match = true @@ -469,10 +505,12 @@ func TestGetNodeIDs(t *testing.T) { } excludeNodeID := nodes[3].ID() + gotNodeIDsExcl := network.GetNodeIDs(excludeNodeID) if len(gotNodeIDsExcl) != numNodes-1 { t.Fatalf("Expected one less node ID to be returned") } + for _, nodeID := range gotNodeIDsExcl { if bytes.Equal(excludeNodeID.Bytes(), nodeID.Bytes()) { t.Fatalf("GetNodeIDs returned the node ID we excluded, ID: %s", nodeID.String()) @@ -487,12 +525,14 @@ func TestGetNodes(t *testing.T) { adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) + network := NewNetwork(adapter, &NetworkConfig{ DefaultService: "test", }) defer network.Shutdown() numNodes := 5 + nodes, err := createTestNodes(numNodes, network) if err != nil { t.Fatalf("Could not create test nodes %v", err) @@ -505,6 +545,7 @@ func TestGetNodes(t *testing.T) { for _, node1 := range nodes { match := false + for _, node2 := range gotNodes { if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) { match = true @@ -518,10 +559,12 @@ func TestGetNodes(t *testing.T) { } excludeNodeID := nodes[3].ID() + gotNodesExcl := network.GetNodes(excludeNodeID) if len(gotNodesExcl) != numNodes-1 { t.Fatalf("Expected one less node to be returned") } + for _, node := range gotNodesExcl { if bytes.Equal(excludeNodeID.Bytes(), node.ID().Bytes()) { t.Fatalf("GetNodes returned the node we excluded, ID: %s", node.ID().String()) @@ -535,12 +578,14 @@ func TestGetNodesByID(t *testing.T) { adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) + network := NewNetwork(adapter, &NetworkConfig{ DefaultService: "test", }) defer network.Shutdown() numNodes := 5 + nodes, err := createTestNodes(numNodes, network) if err != nil { t.Fatalf("Could not create test nodes: %v", err) @@ -548,7 +593,9 @@ func TestGetNodesByID(t *testing.T) { numSubsetNodes := 2 subsetNodes := nodes[0:numSubsetNodes] + var subsetNodeIDs []enode.ID + for _, node := range subsetNodes { subsetNodeIDs = append(subsetNodeIDs, node.ID()) } @@ -560,6 +607,7 @@ func TestGetNodesByID(t *testing.T) { for _, node1 := range subsetNodes { match := false + for _, node2 := range gotNodesByID { if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) { match = true @@ -580,12 +628,14 @@ func TestGetNodesByProperty(t *testing.T) { adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) + network := NewNetwork(adapter, &NetworkConfig{ DefaultService: "test", }) defer network.Shutdown() numNodes := 3 + _, err := createTestNodes(numNodes, network) if err != nil { t.Fatalf("Failed to create nodes: %v", err) @@ -593,6 +643,7 @@ func TestGetNodesByProperty(t *testing.T) { numPropertyNodes := 3 propertyTest := "test" + propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network) if err != nil { t.Fatalf("Failed to create nodes with property: %v", err) @@ -605,6 +656,7 @@ func TestGetNodesByProperty(t *testing.T) { for _, node1 := range propertyNodes { match := false + for _, node2 := range gotNodesByProperty { if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) { match = true @@ -625,12 +677,14 @@ func TestGetNodeIDsByProperty(t *testing.T) { adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) + network := NewNetwork(adapter, &NetworkConfig{ DefaultService: "test", }) defer network.Shutdown() numNodes := 3 + _, err := createTestNodes(numNodes, network) if err != nil { t.Fatalf("Failed to create nodes: %v", err) @@ -638,6 +692,7 @@ func TestGetNodeIDsByProperty(t *testing.T) { numPropertyNodes := 3 propertyTest := "test" + propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network) if err != nil { t.Fatalf("Failed to created nodes with property: %v", err) @@ -650,6 +705,7 @@ func TestGetNodeIDsByProperty(t *testing.T) { for _, node1 := range propertyNodes { match := false + id1 := node1.ID() for _, id2 := range gotNodeIDsByProperty { if bytes.Equal(id1.Bytes(), id2.Bytes()) { @@ -667,6 +723,7 @@ func TestGetNodeIDsByProperty(t *testing.T) { func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) { tick := time.NewTicker(interval) defer tick.Stop() + for { select { case <-tick.C: @@ -693,6 +750,7 @@ func BenchmarkMinimalService(b *testing.B) { func benchmarkMinimalServiceTmp(b *testing.B) { // stop timer to discard setup time pollution args := strings.Split(b.Name(), "/") + nodeCount, err := strconv.ParseInt(args[2], 10, 16) if err != nil { b.Fatal(err) @@ -718,15 +776,19 @@ func benchmarkMinimalServiceTmp(b *testing.B) { // create and start nodes ids := make([]enode.ID, nodeCount) + for i := 0; i < int(nodeCount); i++ { conf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(conf) if err != nil { b.Fatalf("error creating node: %s", err) } + if err := network.Start(node.ID()); err != nil { b.Fatalf("error starting node: %s", err) } + ids[i] = node.ID() } @@ -744,6 +806,7 @@ func benchmarkMinimalServiceTmp(b *testing.B) { // wait for all protocols to signal to close down ctx, cancel := context.WithTimeout(context.TODO(), time.Second) defer cancel() + for nodid, peers := range protoCMap { for peerid, peerC := range peers { log.Debug("getting ", "node", nodid, "peer", peerid) @@ -768,13 +831,16 @@ func TestNode_UnmarshalJSON(t *testing.T) { func runNodeUnmarshalJSON(t *testing.T, tests []nodeUnmarshalTestCase) { t.Helper() + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got *Node if err := json.Unmarshal([]byte(tt.marshaled), &got); err != nil { expectErrorMessageToContain(t, err, tt.wantErr) + got = nil } + expectNodeEquality(t, got, tt.want) }) } @@ -789,6 +855,7 @@ type nodeUnmarshalTestCase struct { func expectErrorMessageToContain(t *testing.T, got error, want string) { t.Helper() + if got == nil && want == "" { return } @@ -809,6 +876,7 @@ func expectErrorMessageToContain(t *testing.T, got error, want string) { func expectNodeEquality(t *testing.T, got, want *Node) { t.Helper() + if !reflect.DeepEqual(got, want) { t.Errorf("Node.UnmarshalJSON() = %v, want %v", got, want) } diff --git a/p2p/simulations/pipes/pipes.go b/p2p/simulations/pipes/pipes.go index ec277c0d14..3309671ee7 100644 --- a/p2p/simulations/pipes/pipes.go +++ b/p2p/simulations/pipes/pipes.go @@ -35,7 +35,9 @@ func TCPPipe() (net.Conn, net.Conn, error) { defer l.Close() var aconn net.Conn + aerr := make(chan error, 1) + go func() { var err error aconn, err = l.Accept() @@ -47,9 +49,11 @@ func TCPPipe() (net.Conn, net.Conn, error) { <-aerr return nil, nil, err } + if err := <-aerr; err != nil { dconn.Close() return nil, nil, err } + return aconn, dconn, nil } diff --git a/p2p/simulations/simulation.go b/p2p/simulations/simulation.go index ae62c42b9c..8ede39c3f5 100644 --- a/p2p/simulations/simulation.go +++ b/p2p/simulations/simulation.go @@ -59,6 +59,7 @@ func (s *Simulation) Run(ctx context.Context, step *Step) (result *StepResult) { for _, id := range step.Expect.Nodes { nodes[id] = struct{}{} } + for len(result.Passes) < len(nodes) { select { case id := <-step.Trigger: @@ -78,6 +79,7 @@ func (s *Simulation) Run(ctx context.Context, step *Step) (result *StepResult) { result.Error = err return } + if pass { result.Passes[id] = time.Now() } @@ -95,9 +97,11 @@ func (s *Simulation) watchNetwork(result *StepResult) func() { done := make(chan struct{}) events := make(chan *Event) sub := s.network.Events().Subscribe(events) + go func() { defer close(done) defer sub.Unsubscribe() + for { select { case event := <-events: @@ -107,6 +111,7 @@ func (s *Simulation) watchNetwork(result *StepResult) func() { } } }() + return func() { close(stop) <-done diff --git a/p2p/simulations/test.go b/p2p/simulations/test.go index 0edb07b127..0f74f5298d 100644 --- a/p2p/simulations/test.go +++ b/p2p/simulations/test.go @@ -76,6 +76,7 @@ func (t *NoopService) Stop() error { func VerifyRing(t *testing.T, net *Network, ids []enode.ID) { t.Helper() + n := len(ids) for i := 0; i < n; i++ { for j := i + 1; j < n; j++ { @@ -95,6 +96,7 @@ func VerifyRing(t *testing.T, net *Network, ids []enode.ID) { func VerifyChain(t *testing.T, net *Network, ids []enode.ID) { t.Helper() + n := len(ids) for i := 0; i < n; i++ { for j := i + 1; j < n; j++ { @@ -114,8 +116,11 @@ func VerifyChain(t *testing.T, net *Network, ids []enode.ID) { func VerifyFull(t *testing.T, net *Network, ids []enode.ID) { t.Helper() + n := len(ids) + var connections int + for i, lid := range ids { for _, rid := range ids[i+1:] { if net.GetConn(lid, rid) != nil { @@ -132,6 +137,7 @@ func VerifyFull(t *testing.T, net *Network, ids []enode.ID) { func VerifyStar(t *testing.T, net *Network, ids []enode.ID, centerIndex int) { t.Helper() + n := len(ids) for i := 0; i < n; i++ { for j := i + 1; j < n; j++ { diff --git a/p2p/tracker/tracker.go b/p2p/tracker/tracker.go index 6a733b9ba5..3ad1748f29 100644 --- a/p2p/tracker/tracker.go +++ b/p2p/tracker/tracker.go @@ -87,6 +87,7 @@ func (t *Tracker) Track(peer string, version uint, reqCode uint64, resCode uint6 if !metrics.Enabled { return } + t.lock.Lock() defer t.lock.Unlock() @@ -135,6 +136,7 @@ func (t *Tracker) clean() { id = head.Value.(uint64) req = t.pending[id] ) + if time.Since(req.time) < t.timeout+5*time.Millisecond { break } @@ -158,6 +160,7 @@ func (t *Tracker) schedule() { t.wake = nil return } + t.wake = time.AfterFunc(time.Until(t.pending[t.expire.Front().Value.(uint64)].time.Add(t.timeout)), t.clean) } @@ -166,6 +169,7 @@ func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) { if !metrics.Enabled { return } + t.lock.Lock() defer t.lock.Unlock() @@ -174,6 +178,7 @@ func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) { if !ok { m := fmt.Sprintf("%s/%s/%d/%#02x", staleMeterName, t.protocol, version, code) metrics.GetOrRegisterMeter(m, nil).Mark(1) + return } // If the response is funky, it might be some active attack @@ -182,16 +187,19 @@ func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) { "have", fmt.Sprintf("%s:%s/%d:%d", peer, t.protocol, version, code), "want", fmt.Sprintf("%s:%s/%d:%d", peer, t.protocol, req.version, req.resCode), ) + return } // Everything matches, mark the request serviced and meter it t.expire.Remove(req.expire) delete(t.pending, id) + if req.expire.Prev() == nil { if t.wake.Stop() { t.schedule() } } + g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, req.version, req.reqCode) metrics.GetOrRegisterGauge(g, nil).Dec(1) diff --git a/p2p/transport.go b/p2p/transport.go index 4f6bb569bf..b4be03a661 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -60,7 +60,9 @@ func (t *rlpxTransport) ReadMsg() (Msg, error) { defer t.rmu.Unlock() var msg Msg + t.conn.SetReadDeadline(time.Now().Add(frameReadTimeout)) + code, data, wireSize, err := t.conn.Read() if err == nil { // Protocol messages are dispatched to subprotocol handlers asynchronously, @@ -75,6 +77,7 @@ func (t *rlpxTransport) ReadMsg() (Msg, error) { Payload: bytes.NewReader(data), } } + return msg, err } @@ -84,12 +87,14 @@ func (t *rlpxTransport) WriteMsg(msg Msg) error { // Copy message data to write buffer. t.wbuf.Reset() + if _, err := io.CopyN(&t.wbuf, msg.Payload, int64(msg.Size)); err != nil { return err } // Write the message. t.conn.SetWriteDeadline(time.Now().Add(frameWriteTimeout)) + size, err := t.conn.Write(msg.Code, t.wbuf.Bytes()) if err != nil { return err @@ -102,6 +107,7 @@ func (t *rlpxTransport) WriteMsg(msg Msg) error { metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize)) metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1) } + return nil } @@ -123,6 +129,7 @@ func (t *rlpxTransport) close(err error) { } } } + t.conn.Close() } @@ -138,10 +145,12 @@ func (t *rlpxTransport) doProtoHandshake(our *protoHandshake) (their *protoHands // as the error so it can be tracked elsewhere. werr := make(chan error, 1) go func() { werr <- Send(t, handshakeMsg, our) }() + if their, err = readProtocolHandshake(t); err != nil { <-werr // make sure the write terminates too return nil, err } + if err := <-werr; err != nil { return nil, fmt.Errorf("write error: %v", err) } @@ -156,27 +165,35 @@ func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) { if err != nil { return nil, err } + if msg.Size > baseProtocolMaxMsgSize { return nil, fmt.Errorf("message too big") } + if msg.Code == discMsg { // Disconnect before protocol handshake is valid according to the // spec and we send it ourself if the post-handshake checks fail. // We can't return the reason directly, though, because it is echoed // back otherwise. Wrap it in a string instead. var reason [1]DiscReason + rlp.Decode(msg.Payload, &reason) + return nil, reason[0] } + if msg.Code != handshakeMsg { return nil, fmt.Errorf("expected handshake, got %x", msg.Code) } + var hs protoHandshake if err := msg.Decode(&hs); err != nil { return nil, err } + if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) { return nil, DiscInvalidIdentity } + return &hs, nil } diff --git a/p2p/transport_test.go b/p2p/transport_test.go index 24e06c5a06..ee62a4b53f 100644 --- a/p2p/transport_test.go +++ b/p2p/transport_test.go @@ -46,15 +46,18 @@ func TestProtocolHandshake(t *testing.T) { } wg.Add(2) + go func() { defer wg.Done() defer fd0.Close() frame := newRLPX(fd0, &prv1.PublicKey) + rpubkey, err := frame.doEncHandshake(prv0) if err != nil { t.Errorf("dial side enc handshake failed: %v", err) return } + if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) { t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey) return @@ -65,22 +68,26 @@ func TestProtocolHandshake(t *testing.T) { t.Errorf("dial side proto handshake error: %v", err) return } + phs.Rest = nil if !reflect.DeepEqual(phs, hs1) { t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1)) return } + frame.close(DiscQuitting) }() go func() { defer wg.Done() defer fd1.Close() rlpx := newRLPX(fd1, nil) + rpubkey, err := rlpx.doEncHandshake(prv1) if err != nil { t.Errorf("listen side enc handshake failed: %v", err) return } + if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) { t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey) return @@ -91,6 +98,7 @@ func TestProtocolHandshake(t *testing.T) { t.Errorf("listen side proto handshake error: %v", err) return } + phs.Rest = nil if !reflect.DeepEqual(phs, hs0) { t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0)) @@ -140,6 +148,7 @@ func TestProtocolHandshakeErrors(t *testing.T) { for i, test := range tests { p1, p2 := MsgPipe() go Send(p1, test.code, test.msg) + _, err := readProtocolHandshake(p2) if !reflect.DeepEqual(err, test.err) { t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) diff --git a/p2p/util.go b/p2p/util.go index 2c8f322a66..b0b167495a 100644 --- a/p2p/util.go +++ b/p2p/util.go @@ -48,6 +48,7 @@ func (h expHeap) contains(item string) bool { return true } } + return false } @@ -72,5 +73,6 @@ func (h *expHeap) Pop() interface{} { x := old[n-1] old[n-1] = expItem{} *h = old[0 : n-1] + return x } diff --git a/p2p/util_test.go b/p2p/util_test.go index cc0d2b215f..0a98439457 100644 --- a/p2p/util_test.go +++ b/p2p/util_test.go @@ -32,6 +32,7 @@ func TestExpHeap(t *testing.T) { exptimeB = basetime.Add(3 * time.Second) exptimeC = basetime.Add(4 * time.Second) ) + h.add("b", exptimeB) h.add("a", exptimeA) h.add("c", exptimeC) @@ -39,17 +40,21 @@ func TestExpHeap(t *testing.T) { if h.nextExpiry() != exptimeA { t.Fatal("wrong nextExpiry") } + if !h.contains("a") || !h.contains("b") || !h.contains("c") { t.Fatal("heap doesn't contain all live items") } h.expire(exptimeA.Add(1), nil) + if h.nextExpiry() != exptimeB { t.Fatal("wrong nextExpiry") } + if h.contains("a") { t.Fatal("heap contains a even though it has already expired") } + if !h.contains("b") || !h.contains("c") { t.Fatal("heap doesn't contain all live items") } diff --git a/params/bootnodes.go b/params/bootnodes.go index b88f5e5e51..94824a6256 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -113,6 +113,7 @@ const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUD // information. func KnownDNSNetwork(genesis common.Hash, protocol string) string { var net string + switch genesis { case MainnetGenesisHash: net = "mainnet" @@ -127,5 +128,6 @@ func KnownDNSNetwork(genesis common.Hash, protocol string) string { default: return "" } + return dnsPrefix + protocol + "." + net + ".ethdisco.net" } diff --git a/params/config.go b/params/config.go index f645bf7178..1431f23a1b 100644 --- a/params/config.go +++ b/params/config.go @@ -563,12 +563,14 @@ func (c *TrustedCheckpoint) HashEqual(hash common.Hash) bool { if c.Empty() { return hash == common.Hash{} } + return c.Hash() == hash } // Hash returns the hash of checkpoint's four key fields(index, sectionHead, chtRoot and bloomTrieRoot). func (c *TrustedCheckpoint) Hash() common.Hash { var sectionIndex [8]byte + binary.BigEndian.PutUint64(sectionIndex[:], c.SectionIndex) w := sha3.NewLegacyKeccak256() @@ -578,7 +580,9 @@ func (c *TrustedCheckpoint) Hash() common.Hash { w.Write(c.BloomRoot[:]) var h common.Hash + w.Sum(h[:0]) + return h } @@ -777,14 +781,18 @@ func (c *BorConfig) CalculateBurntContract(number uint64) string { for k := range c.BurntContract { keys = append(keys, k) } + sort.Strings(keys) + for i := 0; i < len(keys)-1; i++ { valUint, _ := strconv.ParseUint(keys[i], 10, 64) valUintNext, _ := strconv.ParseUint(keys[i+1], 10, 64) + if number > valUint && number < valUintNext { return c.BurntContract[keys[i]] } } + return c.BurntContract[keys[len(keys)-1]] } @@ -799,6 +807,7 @@ func (c *ChainConfig) Description() string { } banner += fmt.Sprintf("Chain ID: %v (%s)\n", c.ChainID, network) + switch { case c.Ethash != nil: if c.TerminalTotalDifficulty == nil { @@ -868,6 +877,7 @@ func (c *ChainConfig) Description() string { banner += " - Hard-fork specification: https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/paris.md\n" banner += fmt.Sprintf(" - Network known to be merged: %v\n", c.TerminalTotalDifficultyPassed) banner += fmt.Sprintf(" - Total terminal difficulty: %v\n", c.TerminalTotalDifficulty) + if c.MergeNetsplitBlock != nil { banner += fmt.Sprintf(" - Merge netsplit block: #%-8v\n", c.MergeNetsplitBlock) } @@ -969,6 +979,7 @@ func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *bi if c.TerminalTotalDifficulty == nil { return false } + return parentTotalDiff.Cmp(c.TerminalTotalDifficulty) < 0 && totalDiff.Cmp(c.TerminalTotalDifficulty) >= 0 } @@ -997,11 +1008,13 @@ func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time u ) // Iterate checkCompatible to find the lowest conflict. var lasterr *ConfigCompatError + for { err := c.checkCompatible(newcfg, bhead, btime) if err == nil || (lasterr != nil && err.RewindToBlock == lasterr.RewindToBlock && err.RewindToTime == lasterr.RewindToTime) { break } + lasterr = err if err.RewindToTime > 0 { @@ -1010,6 +1023,7 @@ func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time u bhead.SetUint64(err.RewindToBlock) } } + return lasterr } @@ -1022,6 +1036,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { timestamp *uint64 // forks after the merge are scheduled using timestamps optional bool // if true, the fork may be nil and next fork is still allowed } + var lastFork fork for _, cur := range []fork{ {name: "homesteadBlock", block: c.HomesteadBlock}, @@ -1077,6 +1092,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { lastFork = cur } } + return nil } @@ -1164,6 +1180,7 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if isForkTimestampIncompatible(c.PragueTime, newcfg.PragueTime, headTimestamp) { return newTimestampCompatError("Prague fork timestamp", c.PragueTime, newcfg.PragueTime) } + return nil } @@ -1190,6 +1207,7 @@ func isBlockForked(s, head *big.Int) bool { if s == nil || head == nil { return false } + return s.Cmp(head) <= 0 } @@ -1197,9 +1215,11 @@ func configBlockEqual(x, y *big.Int) bool { if x == nil { return y == nil } + if y == nil { return x == nil } + return x.Cmp(y) == 0 } @@ -1252,6 +1272,7 @@ type ConfigCompatError struct { func newBlockCompatError(what string, storedblock, newblock *big.Int) *ConfigCompatError { var rew *big.Int + switch { case storedblock == nil: rew = newblock @@ -1296,6 +1317,7 @@ func newTimestampCompatError(what string, storedtime, newtime *uint64) *ConfigCo if rew != nil { err.RewindToTime = *rew - 1 } + return err } @@ -1326,6 +1348,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules if chainID == nil { chainID = new(big.Int) } + return Rules{ ChainID: new(big.Int).Set(chainID), IsHomestead: c.IsHomestead(num), diff --git a/params/config_test.go b/params/config_test.go index b07b736fe4..713fbaf880 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -32,6 +32,7 @@ func TestCheckCompatible(t *testing.T) { headTimestamp uint64 wantErr *ConfigCompatError } + tests := []test{ {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, headBlock: 0, headTimestamp: 0, wantErr: nil}, {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, headBlock: 0, headTimestamp: uint64(time.Now().Unix()), wantErr: nil}, diff --git a/params/version.go b/params/version.go index 7209254a62..f792b4d61a 100644 --- a/params/version.go +++ b/params/version.go @@ -55,15 +55,16 @@ var VersionWithMetaCommitDetails = func() string { // ArchiveVersion holds the textual version string used for Geth archives. // e.g. "1.8.11-dea1ce05" for stable releases, or -// func ArchiveVersion(gitCommit string) string { vsn := Version if VersionMeta != "stable" { vsn += "-" + VersionMeta } + if len(gitCommit) >= 8 { vsn += "-" + gitCommit[:8] } + return vsn } @@ -72,8 +73,10 @@ func VersionWithCommit(gitCommit, gitDate string) string { if len(gitCommit) >= 8 { vsn += "-" + gitCommit[:8] } + if (VersionMeta != "stable") && (gitDate != "") { vsn += "-" + gitDate } + return vsn } diff --git a/rlp/decode.go b/rlp/decode.go index b1d826ac37..9e4ff8c433 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -85,6 +85,7 @@ func Decode(r io.Reader, val interface{}) error { defer streamPool.Put(stream) stream.Reset(r, 0) + return stream.Decode(val) } @@ -97,12 +98,15 @@ func DecodeBytes(b []byte, val interface{}) error { defer streamPool.Put(stream) stream.Reset(r, uint64(len(b))) + if err := stream.Decode(val); err != nil { return err } + if r.Len() > 0 { return ErrMoreThanOneValue } + return nil } @@ -120,6 +124,7 @@ func (err *decodeError) Error() string { ctx += err.ctx[i] } } + return fmt.Sprintf("rlp: %s for %v%s", err.msg, err.typ, ctx) } @@ -138,6 +143,7 @@ func wrapStreamError(err error, typ reflect.Type) error { case errNotAtEOL: return &decodeError{msg: "input list has too many elements", typ: typ} } + return err } @@ -145,6 +151,7 @@ func addErrorContext(err error, ctx string) error { if decErr, ok := err.(*decodeError); ok { decErr.ctx = append(decErr.ctx, ctx) } + return err } @@ -156,6 +163,7 @@ var ( func makeDecoder(typ reflect.Type, tags rlpstruct.Tags) (dec decoder, err error) { kind := typ.Kind() + switch { case typ == rawValueType: return decodeRawValue, nil @@ -193,17 +201,22 @@ func decodeRawValue(s *Stream, val reflect.Value) error { if err != nil { return err } + val.SetBytes(r) + return nil } func decodeUint(s *Stream, val reflect.Value) error { typ := val.Type() + num, err := s.uint(typ.Bits()) if err != nil { return wrapStreamError(err, val.Type()) } + val.SetUint(num) + return nil } @@ -212,7 +225,9 @@ func decodeBool(s *Stream, val reflect.Value) error { if err != nil { return wrapStreamError(err, val.Type()) } + val.SetBool(b) + return nil } @@ -221,7 +236,9 @@ func decodeString(s *Stream, val reflect.Value) error { if err != nil { return wrapStreamError(err, val.Type()) } + val.SetString(string(b)) + return nil } @@ -240,6 +257,7 @@ func decodeBigInt(s *Stream, val reflect.Value) error { if err != nil { return wrapStreamError(err, val.Type()) } + return nil } @@ -268,12 +286,15 @@ func makeListDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) { if typ.Kind() == reflect.Array { return decodeByteArray, nil } + return decodeByteSlice, nil } + etypeinfo := theTC.infoWhileGenerating(etype, rlpstruct.Tags{}) if etypeinfo.decoderErr != nil { return nil, etypeinfo.decoderErr } + var dec decoder switch { case typ.Kind() == reflect.Array: @@ -293,6 +314,7 @@ func makeListDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) { return decodeListSlice(s, val, etypeinfo.decoder) } } + return dec, nil } @@ -301,18 +323,22 @@ func decodeListSlice(s *Stream, val reflect.Value, elemdec decoder) error { if err != nil { return wrapStreamError(err, val.Type()) } + if size == 0 { val.Set(reflect.MakeSlice(val.Type(), 0, 0)) return s.ListEnd() } + if err := decodeSliceElems(s, val, elemdec); err != nil { return err } + return s.ListEnd() } func decodeSliceElems(s *Stream, val reflect.Value, elemdec decoder) error { i := 0 + for ; ; i++ { // grow slice if necessary if i >= val.Cap() { @@ -320,10 +346,12 @@ func decodeSliceElems(s *Stream, val reflect.Value, elemdec decoder) error { if newcap < 4 { newcap = 4 } + newv := reflect.MakeSlice(val.Type(), val.Len(), newcap) reflect.Copy(newv, val) val.Set(newv) } + if i >= val.Len() { val.SetLen(i + 1) } @@ -334,9 +362,11 @@ func decodeSliceElems(s *Stream, val reflect.Value, elemdec decoder) error { return addErrorContext(err, fmt.Sprint("[", i, "]")) } } + if i < val.Len() { val.SetLen(i) } + return nil } @@ -344,8 +374,10 @@ func decodeListArray(s *Stream, val reflect.Value, elemdec decoder) error { if _, err := s.List(); err != nil { return wrapStreamError(err, val.Type()) } + vlen := val.Len() i := 0 + for ; i < vlen; i++ { if err := elemdec(s, val.Index(i)); err == EOL { break @@ -353,9 +385,11 @@ func decodeListArray(s *Stream, val reflect.Value, elemdec decoder) error { return addErrorContext(err, fmt.Sprint("[", i, "]")) } } + if i < vlen { return &decodeError{msg: "input list has too few elements", typ: val.Type()} } + return wrapStreamError(s.ListEnd(), val.Type()) } @@ -364,7 +398,9 @@ func decodeByteSlice(s *Stream, val reflect.Value) error { if err != nil { return wrapStreamError(err, val.Type()) } + val.SetBytes(b) + return nil } @@ -373,6 +409,7 @@ func decodeByteArray(s *Stream, val reflect.Value) error { if err != nil { return err } + slice := byteArrayBytes(val, val.Len()) switch kind { case Byte: @@ -381,15 +418,18 @@ func decodeByteArray(s *Stream, val reflect.Value) error { } else if len(slice) > 1 { return &decodeError{msg: "input string too short", typ: val.Type()} } + slice[0] = s.byteval s.kind = -1 case String: if uint64(len(slice)) < size { return &decodeError{msg: "input string too long", typ: val.Type()} } + if uint64(len(slice)) > size { return &decodeError{msg: "input string too short", typ: val.Type()} } + if err := s.readFull(slice); err != nil { return err } @@ -400,6 +440,7 @@ func decodeByteArray(s *Stream, val reflect.Value) error { case List: return wrapStreamError(ErrExpectedString, val.Type()) } + return nil } @@ -408,15 +449,18 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) { if err != nil { return nil, err } + for _, f := range fields { if f.info.decoderErr != nil { return nil, structFieldError{typ, f.index, f.info.decoderErr} } } + dec := func(s *Stream, val reflect.Value) (err error) { if _, err := s.List(); err != nil { return wrapStreamError(err, typ) } + for i, f := range fields { err := f.info.decoder(s, val.Field(f.index)) if err == EOL { @@ -427,13 +471,16 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) { zeroFields(val, fields[i:]) break } + return &decodeError{msg: "too few elements", typ: typ} } else if err != nil { return addErrorContext(err, "."+typ.Field(f.index).Name) } } + return wrapStreamError(s.ListEnd(), typ) } + return dec, nil } @@ -447,6 +494,7 @@ func zeroFields(structval reflect.Value, fields []field) { // makePtrDecoder creates a decoder that decodes into the pointer's element type. func makePtrDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) { etype := typ.Elem() + etypeinfo := theTC.infoWhileGenerating(etype, rlpstruct.Tags{}) switch { case etypeinfo.decoderErr != nil: @@ -464,9 +512,11 @@ func makeSimplePtrDecoder(etype reflect.Type, etypeinfo *typeinfo) decoder { if val.IsNil() { newval = reflect.New(etype) } + if err = etypeinfo.decoder(s, newval.Elem()); err == nil { val.Set(newval) } + return err } } @@ -500,16 +550,21 @@ func makeNilPtrDecoder(etype reflect.Type, etypeinfo *typeinfo, ts rlpstruct.Tag // position must advance to the next value even though // we don't read anything. s.kind = -1 + val.Set(nilPtr) + return nil } + newval := val if val.IsNil() { newval = reflect.New(etype) } + if err = etypeinfo.decoder(s, newval.Elem()); err == nil { val.Set(newval) } + return err } } @@ -520,23 +575,28 @@ func decodeInterface(s *Stream, val reflect.Value) error { if val.Type().NumMethod() != 0 { return fmt.Errorf("rlp: type %v is not RLP-serializable", val.Type()) } + kind, _, err := s.Kind() if err != nil { return err } + if kind == List { slice := reflect.New(ifsliceType).Elem() if err := decodeListSlice(s, slice, decodeInterface); err != nil { return err } + val.Set(slice) } else { b, err := s.Bytes() if err != nil { return err } + val.Set(reflect.ValueOf(b)) } + return nil } @@ -618,6 +678,7 @@ type Stream struct { func NewStream(r io.Reader, inputLimit uint64) *Stream { s := new(Stream) s.Reset(r, inputLimit) + return s } @@ -628,6 +689,7 @@ func NewListStream(r io.Reader, len uint64) *Stream { s.Reset(r, len) s.kind = List s.size = len + return s } @@ -639,6 +701,7 @@ func (s *Stream) Bytes() ([]byte, error) { if err != nil { return nil, err } + switch kind { case Byte: s.kind = -1 // rearm Kind @@ -648,9 +711,11 @@ func (s *Stream) Bytes() ([]byte, error) { if err = s.readFull(b); err != nil { return nil, err } + if size == 1 && b[0] < 128 { return nil, ErrCanonSize } + return b, nil default: return nil, ErrExpectedString @@ -664,24 +729,30 @@ func (s *Stream) ReadBytes(b []byte) error { if err != nil { return err } + switch kind { case Byte: if len(b) != 1 { return fmt.Errorf("input value has wrong size 1, want %d", len(b)) } + b[0] = s.byteval s.kind = -1 // rearm Kind + return nil case String: if uint64(len(b)) != size { return fmt.Errorf("input value has wrong size %d, want %d", size, len(b)) } + if err = s.readFull(b); err != nil { return err } + if size == 1 && b[0] < 128 { return ErrCanonSize } + return nil default: return ErrExpectedString @@ -694,6 +765,7 @@ func (s *Stream) Raw() ([]byte, error) { if err != nil { return nil, err } + if kind == Byte { s.kind = -1 // rearm Kind return []byte{s.byteval}, nil @@ -702,14 +774,17 @@ func (s *Stream) Raw() ([]byte, error) { // available. Read content and put a new header in front of it. start := headsize(size) buf := make([]byte, uint64(start)+size) + if err := s.readFull(buf[start:]); err != nil { return nil, err } + if kind == String { puthead(buf, 0x80, 0xB7, size) } else { puthead(buf, 0xC0, 0xF7, size) } + return buf, nil } @@ -746,18 +821,23 @@ func (s *Stream) uint(maxbits int) (uint64, error) { if err != nil { return 0, err } + switch kind { case Byte: if s.byteval == 0 { return 0, ErrCanonInt } + s.kind = -1 // rearm Kind + return uint64(s.byteval), nil case String: if size > uint64(maxbits/8) { return 0, errUintOverflow } + v, err := s.readUint(byte(size)) + switch { case err == ErrCanonSize: // Adjust error because we're not reading a size right now. @@ -782,6 +862,7 @@ func (s *Stream) Bool() (bool, error) { if err != nil { return false, err } + switch num { case 0: return false, nil @@ -800,6 +881,7 @@ func (s *Stream) List() (size uint64, err error) { if err != nil { return 0, err } + if kind != List { return 0, ErrExpectedList } @@ -810,9 +892,11 @@ func (s *Stream) List() (size uint64, err error) { if inList, limit := s.listLimit(); inList { s.stack[len(s.stack)-1] = limit - size } + s.stack = append(s.stack, size) s.kind = -1 s.size = 0 + return size, nil } @@ -825,9 +909,11 @@ func (s *Stream) ListEnd() error { } else if listLimit > 0 { return errNotAtEOL } + s.stack = s.stack[:len(s.stack)-1] // pop s.kind = -1 s.size = 0 + return nil } @@ -844,11 +930,13 @@ func (s *Stream) BigInt() (*big.Int, error) { if err := s.decodeBigInt(i); err != nil { return nil, err } + return i, nil } func (s *Stream) decodeBigInt(dst *big.Int) error { var buffer []byte + kind, size, err := s.Kind() switch { case err != nil: @@ -887,6 +975,7 @@ func (s *Stream) decodeBigInt(dst *big.Int) error { } // Set the integer bytes. dst.SetBytes(buffer) + return nil } @@ -939,14 +1028,18 @@ func (s *Stream) Decode(val interface{}) error { if val == nil { return errDecodeIntoNil } + rval := reflect.ValueOf(val) + rtyp := rval.Type() if rtyp.Kind() != reflect.Ptr { return errNoPointer } + if rval.IsNil() { return errDecodeIntoNil } + decoder, err := cachedDecoder(rtyp.Elem()) if err != nil { return err @@ -957,6 +1050,7 @@ func (s *Stream) Decode(val interface{}) error { // Add decode target type to error so context has more meaning. decErr.ctx = append(decErr.ctx, fmt.Sprint("(", rtyp.Elem(), ")")) } + return err } @@ -992,6 +1086,7 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) { if !ok { bufr = bufio.NewReader(r) } + s.r = bufr // Reset the decoding context. s.stack = s.stack[:0] @@ -1037,6 +1132,7 @@ func (s *Stream) Kind() (kind Kind, size uint64, err error) { s.kinderr = ErrValueTooLarge } } + return s.kind, s.size, s.kinderr } @@ -1053,8 +1149,10 @@ func (s *Stream) readKind() (kind Kind, size uint64, err error) { err = io.EOF } } + return 0, 0, err } + s.byteval = 0 switch { case b < 0x80: @@ -1078,6 +1176,7 @@ func (s *Stream) readKind() (kind Kind, size uint64, err error) { if err == nil && size < 56 { err = ErrCanonSize } + return String, size, err case b < 0xF8: // If the total payload of a list (i.e. the combined length of all its @@ -1096,6 +1195,7 @@ func (s *Stream) readKind() (kind Kind, size uint64, err error) { if err == nil && size < 56 { err = ErrCanonSize } + return List, size, err } } @@ -1113,15 +1213,18 @@ func (s *Stream) readUint(size byte) (uint64, error) { for i := range buffer { buffer[i] = 0 } + start := int(8 - size) if err := s.readFull(buffer[start:]); err != nil { return 0, err } + if buffer[start] == 0 { // Note: readUint is also used to decode integer values. // The error needs to be adjusted to become ErrCanonInt in this case. return 0, ErrCanonSize } + return binary.BigEndian.Uint64(buffer[:]), nil } } @@ -1131,11 +1234,13 @@ func (s *Stream) readFull(buf []byte) (err error) { if err := s.willRead(uint64(len(buf))); err != nil { return err } + var nn, n int for n < len(buf) && err == nil { nn, err = s.r.Read(buf[n:]) n += nn } + if err == io.EOF { if n < len(buf) { err = io.ErrUnexpectedEOF @@ -1145,6 +1250,7 @@ func (s *Stream) readFull(buf []byte) (err error) { err = nil } } + return err } @@ -1153,10 +1259,12 @@ func (s *Stream) readByte() (byte, error) { if err := s.willRead(1); err != nil { return 0, err } + b, err := s.r.ReadByte() if err == io.EOF { err = io.ErrUnexpectedEOF } + return b, err } @@ -1169,14 +1277,18 @@ func (s *Stream) willRead(n uint64) error { if n > limit { return ErrElemTooLarge } + s.stack[len(s.stack)-1] = limit - n } + if s.limited { if n > s.remaining { return ErrValueTooLarge } + s.remaining -= n } + return nil } @@ -1185,5 +1297,6 @@ func (s *Stream) listLimit() (inList bool, limit uint64) { if len(s.stack) == 0 { return false, 0 } + return true, s.stack[len(s.stack)-1] } diff --git a/rlp/decode_test.go b/rlp/decode_test.go index cb0e1376e4..2f92b8d87d 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -55,14 +55,17 @@ func TestStreamKind(t *testing.T) { for i, test := range tests { // using plainReader to inhibit input limit errors. s := NewStream(newPlainReader(unhex(test.input)), 0) + kind, len, err := s.Kind() if err != nil { t.Errorf("test %d: Kind returned error: %v", i, err) continue } + if kind != test.wantKind { t.Errorf("test %d: kind mismatch: got %d, want %d", i, kind, test.wantKind) } + if len != test.wantLen { t.Errorf("test %d: len mismatch: got %d, want %d", i, len, test.wantLen) } @@ -74,14 +77,17 @@ func TestNewListStream(t *testing.T) { if k, size, err := ls.Kind(); k != List || size != 3 || err != nil { t.Errorf("Kind() returned (%v, %d, %v), expected (List, 3, nil)", k, size, err) } + if size, err := ls.List(); size != 3 || err != nil { t.Errorf("List() returned (%d, %v), expected (3, nil)", size, err) } + for i := 0; i < 3; i++ { if val, err := ls.Uint(); val != 1 || err != nil { t.Errorf("Uint() returned (%d, %v), expected (1, nil)", val, err) } } + if err := ls.ListEnd(); err != nil { t.Errorf("ListEnd() returned %v, expected (3, nil)", err) } @@ -98,6 +104,7 @@ func TestStreamErrors(t *testing.T) { } type calls []string + tests := []struct { string calls @@ -237,6 +244,7 @@ func TestStreamList(t *testing.T) { if err != nil { t.Fatalf("List error: %v", err) } + if len != 8 { t.Fatalf("List returned invalid length, got %d, want 8", len) } @@ -246,6 +254,7 @@ func TestStreamList(t *testing.T) { if err != nil { t.Fatalf("Uint error: %v", err) } + if i != v { t.Errorf("Uint returned wrong value, got %d, want %d", v, i) } @@ -254,6 +263,7 @@ func TestStreamList(t *testing.T) { if _, err := s.Uint(); err != EOL { t.Errorf("Uint error mismatch, got %v, want %v", err, EOL) } + if err = s.ListEnd(); err != nil { t.Fatalf("ListEnd error: %v", err) } @@ -278,10 +288,12 @@ func TestStreamRaw(t *testing.T) { s.List() want := unhex(tt.output) + raw, err := s.Raw() if err != nil { t.Fatal(err) } + if !bytes.Equal(want, raw) { t.Errorf("test %d: raw mismatch: got %x, want %x", i, raw, want) } @@ -313,6 +325,7 @@ func TestStreamReadBytes(t *testing.T) { t.Run(name, func(t *testing.T) { s := NewStream(bytes.NewReader(unhex(test.input)), 0) b := make([]byte, test.size) + err := s.ReadBytes(b) if test.err == "" { if err != nil { @@ -886,17 +899,20 @@ func runTests(t *testing.T, decode func([]byte, interface{}) error) { t.Errorf("test %d: invalid hex input %q", i, test.input) continue } + err = decode(input, test.ptr) if err != nil && test.error == "" { t.Errorf("test %d: unexpected Decode error: %v\ndecoding into %T\ninput %q", i, err, test.ptr, test.input) continue } + if test.error != "" && fmt.Sprint(err) != test.error { t.Errorf("test %d: Decode error mismatch\ngot %v\nwant %v\ndecoding into %T\ninput %q", i, err, test.error, test.ptr, test.input) continue } + deref := reflect.ValueOf(test.ptr).Elem().Interface() if err == nil && !reflect.DeepEqual(deref, test.value) { t.Errorf("test %d: value mismatch\ngot %#v\nwant %#v\ndecoding into %T\ninput %q", @@ -914,11 +930,14 @@ 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) } @@ -945,8 +964,10 @@ func (r *plainReader) Read(buf []byte) (n int, err error) { if len(*r) == 0 { return 0, io.EOF } + n = copy(buf, *r) *r = (*r)[n:] + return n, nil } @@ -958,6 +979,7 @@ func TestDecodeWithNonByteReader(t *testing.T) { func TestDecodeStreamReset(t *testing.T) { s := NewStream(nil, 0) + runTests(t, func(input []byte, into interface{}) error { s.Reset(bytes.NewReader(input), 0) return s.Decode(into) @@ -970,7 +992,9 @@ func (t *testDecoder) DecodeRLP(s *Stream) error { if _, err := s.Uint(); err != nil { return err } + t.called = true + return nil } @@ -980,6 +1004,7 @@ func TestDecodeDecoder(t *testing.T) { T2 *testDecoder T3 **testDecoder } + if err := Decode(bytes.NewReader(unhex("C3010203")), &s); err != nil { t.Fatalf("Decode error: %v", err) } @@ -1006,12 +1031,15 @@ func TestDecodeDecoderNilPointer(t *testing.T) { T1 *testDecoder `rlp:"nil"` T2 *testDecoder } + if err := Decode(bytes.NewReader(unhex("C2C002")), &s); err != nil { t.Fatalf("Decode error: %v", err) } + if s.T1 != nil { t.Errorf("decoder T1 allocated for empty input (called: %v)", s.T1.called) } + if s.T2 == nil || !s.T2.called { t.Errorf("decoder T2 not allocated/called") } @@ -1022,6 +1050,7 @@ type byteDecoder byte func (bd *byteDecoder) DecodeRLP(s *Stream) error { _, err := s.Uint() *bd = 255 + return err } @@ -1053,10 +1082,13 @@ func (f *unencodableDecoder) DecodeRLP(s *Stream) error { if _, err := s.List(); err != nil { return err } + if err := s.ListEnd(); err != nil { return err } + *f = func() {} + return nil } @@ -1065,6 +1097,7 @@ func TestDecoderFunc(t *testing.T) { if err := DecodeBytes([]byte{0xC0}, (*unencodableDecoder)(&x)); err != nil { t.Fatal(err) } + x() } @@ -1110,6 +1143,7 @@ func ExampleDecode() { } var s example + err := Decode(bytes.NewReader(input), &s) if err != nil { fmt.Printf("Error: %v\n", err) @@ -1131,6 +1165,7 @@ func ExampleDecode_structTagNil() { var normalRules struct { String *string } + Decode(bytes.NewReader(input), &normalRules) fmt.Printf("normal: String = %q\n", *normalRules.String) @@ -1139,6 +1174,7 @@ func ExampleDecode_structTagNil() { var withEmptyOK struct { String *string `rlp:"nil"` } + Decode(bytes.NewReader(input), &withEmptyOK) fmt.Printf("with nil tag: String = %v\n", withEmptyOK.String) @@ -1185,6 +1221,7 @@ func BenchmarkDecodeUints(b *testing.B) { for i := 0; i < b.N; i++ { var s []uint + r := bytes.NewReader(enc) if err := Decode(r, &s); err != nil { b.Fatalf("Decode error: %v", err) @@ -1199,6 +1236,7 @@ func BenchmarkDecodeUintsReused(b *testing.B) { b.ResetTimer() var s []uint + for i := 0; i < b.N; i++ { r := bytes.NewReader(enc) if err := Decode(r, &s); err != nil { @@ -1212,6 +1250,7 @@ func BenchmarkDecodeByteArrayStruct(b *testing.B) { if err != nil { b.Fatal(err) } + b.SetBytes(int64(len(enc))) b.ReportAllocs() b.ResetTimer() @@ -1229,10 +1268,12 @@ func BenchmarkDecodeBigInts(b *testing.B) { for i := range ints { ints[i] = math.BigPow(2, int64(i)) } + enc, err := EncodeToBytes(ints) if err != nil { b.Fatal(err) } + b.SetBytes(int64(len(enc))) b.ReportAllocs() b.ResetTimer() @@ -1274,10 +1315,12 @@ func encodeTestSlice(n uint) []byte { for i := uint(0); i < n; i++ { s[i] = i } + b, err := EncodeToBytes(s) if err != nil { panic(fmt.Sprintf("encode error: %v", err)) } + return b } @@ -1286,5 +1329,6 @@ func unhex(str string) []byte { if err != nil { panic(fmt.Sprintf("invalid hex string: %q", str)) } + return b } diff --git a/rlp/encbuffer.go b/rlp/encbuffer.go index a95490420b..3daa27b928 100644 --- a/rlp/encbuffer.go +++ b/rlp/encbuffer.go @@ -41,6 +41,7 @@ var encBufferPool = sync.Pool{ func getEncBuffer() *encBuffer { buf := encBufferPool.Get().(*encBuffer) buf.reset() + return buf } @@ -59,12 +60,14 @@ func (buf *encBuffer) size() int { func (w *encBuffer) makeBytes() []byte { out := make([]byte, w.size()) w.copyTo(out) + return out } func (w *encBuffer) copyTo(dst []byte) { strpos := 0 pos := 0 + for _, head := range w.lheads { // write string data before header n := copy(dst[pos:], w.str[strpos:head.offset]) @@ -86,6 +89,7 @@ func (buf *encBuffer) writeTo(w io.Writer) (err error) { if head.offset-strpos > 0 { n, err := w.Write(buf.str[strpos:head.offset]) strpos += n + if err != nil { return err } @@ -96,10 +100,12 @@ func (buf *encBuffer) writeTo(w io.Writer) (err error) { return err } } + if strpos < len(buf.str) { // write string data after the last list header _, err = w.Write(buf.str[strpos:]) } + return err } @@ -163,6 +169,7 @@ func (w *encBuffer) writeBigInt(i *big.Int) { w.str = append(w.str, make([]byte, length)...) index := length buf := w.str[len(w.str)-length:] + for _, d := range i.Bits() { for j := 0; j < wordBytes && index > 0; j++ { index-- @@ -202,6 +209,7 @@ func (buf *encBuffer) list() int { func (buf *encBuffer) listEnd(index int) { lh := &buf.lheads[index] lh.size = buf.size() - lh.offset - lh.size + if lh.size < 56 { buf.lhsize++ // length encoded into kind tag } else { @@ -211,10 +219,12 @@ func (buf *encBuffer) listEnd(index int) { func (buf *encBuffer) encode(val interface{}) error { rval := reflect.ValueOf(val) + writer, err := cachedWriter(rval.Type()) if err != nil { return err } + return writer(rval, buf) } @@ -247,15 +257,19 @@ func (r *encReader) Read(b []byte) (n int, err error) { encBufferPool.Put(r.buf) r.buf = nil } + return n, io.EOF } + nn := copy(b[n:], r.piece) n += nn + if nn < len(r.piece) { // piece didn't fit, see you next time. r.piece = r.piece[nn:] return n, nil } + r.piece = nil } } @@ -275,19 +289,24 @@ func (r *encReader) next() []byte { // We're before the last list header. head := r.buf.lheads[r.lhpos] sizebefore := head.offset - r.strpos + if sizebefore > 0 { // String data before header. p := r.buf.str[r.strpos:head.offset] r.strpos += sizebefore + return p } + r.lhpos++ + return head.encode(r.buf.sizebuf[:]) case r.strpos < len(r.buf.str): // String data at the end, after all list headers. p := r.buf.str[r.strpos:] r.strpos = len(r.buf.str) + return p default: @@ -322,7 +341,9 @@ type EncoderBuffer struct { // NewEncoderBuffer creates an encoder buffer. func NewEncoderBuffer(dst io.Writer) EncoderBuffer { var w EncoderBuffer + w.Reset(dst) + return w } @@ -346,6 +367,7 @@ func (w *EncoderBuffer) Reset(dst io.Writer) { w.buf = encBufferPool.Get().(*encBuffer) w.ownBuffer = true } + w.buf.reset() w.dst = dst } @@ -361,7 +383,9 @@ func (w *EncoderBuffer) Flush() error { if w.ownBuffer { encBufferPool.Put(w.buf) } + *w = EncoderBuffer{} + return err } @@ -375,6 +399,7 @@ func (w *EncoderBuffer) AppendToBytes(dst []byte) []byte { size := w.buf.size() out := append(dst, make([]byte, size)...) w.buf.copyTo(out[len(dst):]) + return out } diff --git a/rlp/encbuffer_example_test.go b/rlp/encbuffer_example_test.go index ee15d82a77..bdb36abf50 100644 --- a/rlp/encbuffer_example_test.go +++ b/rlp/encbuffer_example_test.go @@ -39,6 +39,7 @@ func ExampleEncoderBuffer() { if err := buf.Flush(); err != nil { panic(err) } + fmt.Printf("%X\n", w.Bytes()) // Output: // C404C20506 diff --git a/rlp/encode.go b/rlp/encode.go index a7896d8936..ac14f35861 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -67,9 +67,11 @@ func Encode(w io.Writer, val interface{}) error { buf := getEncBuffer() defer encBufferPool.Put(buf) + if err := buf.encode(val); err != nil { return err } + return buf.writeTo(w) } @@ -82,6 +84,7 @@ func EncodeToBytes(val interface{}) ([]byte, error) { if err := buf.encode(val); err != nil { return nil, err } + return buf.makeBytes(), nil } @@ -119,6 +122,7 @@ func headsize(size uint64) int { if size < 56 { return 1 } + return 1 + intsize(size) } @@ -129,8 +133,10 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int { buf[0] = smalltag + byte(size) return 1 } + sizesize := putint(buf[1:], size) buf[0] = largetag + byte(sizesize) + return sizesize + 1 } @@ -139,6 +145,7 @@ var encoderInterface = reflect.TypeOf(new(Encoder)).Elem() // makeWriter creates a writer function for the given type. func makeWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { kind := typ.Kind() + switch { case typ == rawValueType: return writeRawValue, nil @@ -196,10 +203,13 @@ func writeBigIntPtr(val reflect.Value, w *encBuffer) error { w.str = append(w.str, 0x80) return nil } + if ptr.Sign() == -1 { return ErrNegativeBigInt } + w.writeBigInt(ptr) + return nil } @@ -208,7 +218,9 @@ func writeBigIntNoPtr(val reflect.Value, w *encBuffer) error { if i.Sign() == -1 { return ErrNegativeBigInt } + w.writeBigInt(&i) + return nil } @@ -244,6 +256,7 @@ func makeByteArrayWriter(typ reflect.Type) writer { return writeLengthOneByteArray default: length := typ.Len() + return func(val reflect.Value, w *encBuffer) error { if !val.CanAddr() { // Getting the byte slice of val requires it to be addressable. Make it @@ -252,9 +265,11 @@ func makeByteArrayWriter(typ reflect.Type) writer { copy.Set(val) val = copy } + slice := byteArrayBytes(val, length) w.encodeStringHeader(len(slice)) w.str = append(w.str, slice...) + return nil } } @@ -272,6 +287,7 @@ func writeLengthOneByteArray(val reflect.Value, w *encBuffer) error { } else { w.str = append(w.str, 0x81, b) } + return nil } @@ -284,6 +300,7 @@ func writeString(val reflect.Value, w *encBuffer) error { w.encodeStringHeader(len(s)) w.str = append(w.str, s...) } + return nil } @@ -295,11 +312,14 @@ func writeInterface(val reflect.Value, w *encBuffer) error { w.str = append(w.str, 0xC0) return nil } + eval := val.Elem() + writer, err := cachedWriter(eval.Type()) if err != nil { return err } + return writer(eval, w) } @@ -320,6 +340,7 @@ func makeSliceWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { return err } } + return nil } } else { @@ -330,16 +351,20 @@ func makeSliceWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { w.str = append(w.str, 0xC0) return nil } + listOffset := w.list() + for i := 0; i < vlen; i++ { if err := etypeinfo.writer(val.Index(i), w); err != nil { return err } } w.listEnd(listOffset) + return nil } } + return wfn, nil } @@ -348,6 +373,7 @@ func makeStructWriter(typ reflect.Type) (writer, error) { if err != nil { return nil, err } + for _, f := range fields { if f.info.writerErr != nil { return nil, structFieldError{typ, f.index, f.info.writerErr} @@ -355,17 +381,21 @@ func makeStructWriter(typ reflect.Type) (writer, error) { } var writer writer + firstOptionalField := firstOptionalField(fields) if firstOptionalField == len(fields) { // This is the writer function for structs without any optional fields. writer = func(val reflect.Value, w *encBuffer) error { lh := w.list() + for _, f := range fields { if err := f.info.writer(val.Field(f.index), w); err != nil { return err } } + w.listEnd(lh) + return nil } } else { @@ -378,16 +408,20 @@ func makeStructWriter(typ reflect.Type) (writer, error) { break } } + lh := w.list() + for i := 0; i <= lastField; i++ { if err := fields[i].info.writer(val.Field(fields[i].index), w); err != nil { return err } } w.listEnd(lh) + return nil } } + return writer, nil } @@ -406,9 +440,12 @@ func makePtrWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { if ev := val.Elem(); ev.IsValid() { return etypeinfo.writer(ev, w) } + w.str = append(w.str, nilEncoding) + return nil } + return writer, nil } @@ -418,6 +455,7 @@ func makeEncoderWriter(typ reflect.Type) writer { return val.Interface().(Encoder).EncodeRLP(w) } } + w := func(val reflect.Value, w *encBuffer) error { if !val.CanAddr() { // package json simply doesn't call MarshalJSON for this case, but encodes the @@ -425,8 +463,10 @@ func makeEncoderWriter(typ reflect.Type) writer { // way. return fmt.Errorf("rlp: unadressable value of type %v, EncodeRLP is pointer method", val.Type()) } + return val.Addr().Interface().(Encoder).EncodeRLP(w) } + return w } @@ -440,17 +480,20 @@ func putint(b []byte, i uint64) (size int) { case i < (1 << 16): b[0] = byte(i >> 8) b[1] = byte(i) + return 2 case i < (1 << 24): b[0] = byte(i >> 16) b[1] = byte(i >> 8) b[2] = byte(i) + return 3 case i < (1 << 32): b[0] = byte(i >> 24) b[1] = byte(i >> 16) b[2] = byte(i >> 8) b[3] = byte(i) + return 4 case i < (1 << 40): b[0] = byte(i >> 32) @@ -458,6 +501,7 @@ func putint(b []byte, i uint64) (size int) { b[2] = byte(i >> 16) b[3] = byte(i >> 8) b[4] = byte(i) + return 5 case i < (1 << 48): b[0] = byte(i >> 40) @@ -466,6 +510,7 @@ func putint(b []byte, i uint64) (size int) { b[3] = byte(i >> 16) b[4] = byte(i >> 8) b[5] = byte(i) + return 6 case i < (1 << 56): b[0] = byte(i >> 48) @@ -475,6 +520,7 @@ func putint(b []byte, i uint64) (size int) { b[4] = byte(i >> 16) b[5] = byte(i >> 8) b[6] = byte(i) + return 7 default: b[0] = byte(i >> 56) @@ -485,6 +531,7 @@ func putint(b []byte, i uint64) (size int) { b[5] = byte(i >> 16) b[6] = byte(i >> 8) b[7] = byte(i) + return 8 } } diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 46e7a8f8b7..56107d99b8 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -39,10 +39,13 @@ func (e *testEncoder) EncodeRLP(w io.Writer) error { if e == nil { panic("EncodeRLP called on nil value") } + if e.err != nil { return e.err } + w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) + return nil } @@ -411,11 +414,13 @@ func runEncTests(t *testing.T, f func(val interface{}) ([]byte, error)) { i, err, test.val, test.val) continue } + if test.error != "" && fmt.Sprint(err) != test.error { t.Errorf("test %d: error mismatch\ngot %v\nwant %v\nvalue %#v\ntype %T", i, err, test.error, test.val, test.val) continue } + if err == nil && !bytes.Equal(output, unhex(test.output)) { t.Errorf("test %d: output mismatch:\ngot %X\nwant %s\nvalue %#v\ntype %T", i, output, test.output, test.val, test.val) @@ -427,6 +432,7 @@ func TestEncode(t *testing.T) { runEncTests(t, func(val interface{}) ([]byte, error) { b := new(bytes.Buffer) err := Encode(b, val) + return b.Bytes(), err }) } @@ -437,6 +443,7 @@ func TestEncodeToBytes(t *testing.T) { func TestEncodeAppendToBytes(t *testing.T) { buffer := make([]byte, 20) + runEncTests(t, func(val interface{}) ([]byte, error) { w := NewEncoderBuffer(nil) defer w.Flush() @@ -445,7 +452,9 @@ func TestEncodeAppendToBytes(t *testing.T) { if err != nil { return nil, err } + output := w.AppendToBytes(buffer[:0]) + return output, nil }) } @@ -456,6 +465,7 @@ func TestEncodeToReader(t *testing.T) { if err != nil { return nil, err } + return io.ReadAll(r) }) } @@ -469,20 +479,24 @@ func TestEncodeToReaderPiecewise(t *testing.T) { // read output piecewise output := make([]byte, size) + for start, end := 0, 0; start < size; start = end { if remaining := size - start; remaining < 3 { end += remaining } else { end = start + 3 } + n, err := r.Read(output[start:end]) end = start + n + if err == io.EOF { break } else if err != nil { return nil, err } } + return output, nil }) } @@ -491,9 +505,11 @@ func TestEncodeToReaderPiecewise(t *testing.T) { // returns its encbuf to the pool only once. func TestEncodeToReaderReturnToPool(t *testing.T) { buf := make([]byte, 50) + wg := new(sync.WaitGroup) for i := 0; i < 5; i++ { wg.Add(1) + go func() { for i := 0; i < 1000; i++ { _, r, _ := EncodeToReader("foo") @@ -530,12 +546,15 @@ func BenchmarkEncodeBigInts(b *testing.B) { for i := range ints { ints[i] = math.BigPow(2, int64(i)) } + out := bytes.NewBuffer(make([]byte, 0, 4096)) + b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { out.Reset() + if err := Encode(out, ints); err != nil { b.Fatal(err) } @@ -568,6 +587,7 @@ func BenchmarkEncodeConcurrentInterface(b *testing.B) { B *big.Int C [20]byte } + value := []interface{}{ uint(999), &struct1{A: "hello", B: big.NewInt(0xFFFFFFFF)}, @@ -578,12 +598,14 @@ func BenchmarkEncodeConcurrentInterface(b *testing.B) { var wg sync.WaitGroup for cpu := 0; cpu < runtime.NumCPU(); cpu++ { wg.Add(1) + go func() { defer wg.Done() var buffer bytes.Buffer for i := 0; i < b.N; i++ { buffer.Reset() + err := Encode(&buffer, value) if err != nil { panic(err) @@ -602,11 +624,14 @@ type byteArrayStruct struct { func BenchmarkEncodeByteArrayStruct(b *testing.B) { var out bytes.Buffer + var value byteArrayStruct b.ReportAllocs() + for i := 0; i < b.N; i++ { out.Reset() + if err := Encode(&out, &value); err != nil { b.Fatal(err) } @@ -623,6 +648,7 @@ type structPtrSlice []*structSliceElem func BenchmarkEncodeStructPtrSlice(b *testing.B) { var out bytes.Buffer + var value = structPtrSlice{ &structSliceElem{1, 1, 1}, &structSliceElem{2, 2, 2}, @@ -633,8 +659,10 @@ func BenchmarkEncodeStructPtrSlice(b *testing.B) { } b.ReportAllocs() + for i := 0; i < b.N; i++ { out.Reset() + if err := Encode(&out, &value); err != nil { b.Fatal(err) } diff --git a/rlp/internal/rlpstruct/rlpstruct.go b/rlp/internal/rlpstruct/rlpstruct.go index 2e3eeb6881..71996473f6 100644 --- a/rlp/internal/rlpstruct/rlpstruct.go +++ b/rlp/internal/rlpstruct/rlpstruct.go @@ -51,6 +51,7 @@ func (t Type) DefaultNilValue() NilKind { if isUint(k) || k == reflect.String || k == reflect.Bool || isByteArray(t) { return NilKindString } + return NilKindList } @@ -96,6 +97,7 @@ func (e TagError) Error() string { if e.StructType != "" { field = e.StructType + "." + e.Field } + return fmt.Sprintf("rlp: invalid struct tag %q for %s (%s)", e.Tag, field, e.Err) } @@ -106,18 +108,23 @@ func ProcessFields(allFields []Field) ([]Field, []Tags, error) { // Gather all exported fields and their tags. var fields []Field + var tags []Tags + for _, field := range allFields { if !field.Exported { continue } + ts, err := parseTag(field, lastPublic) if err != nil { return nil, nil, err } + if ts.Ignored { continue } + fields = append(fields, field) tags = append(tags, ts) } @@ -126,13 +133,17 @@ func ProcessFields(allFields []Field) ([]Field, []Tags, error) { // all fields after it must also be optional. Note: optional + tail // is supported. var anyOptional bool + var firstOptionalName string + for i, ts := range tags { name := fields[i].Name + if ts.Optional || ts.Tail { if !anyOptional { firstOptionalName = name } + anyOptional = true } else { if anyOptional { @@ -141,13 +152,16 @@ func ProcessFields(allFields []Field) ([]Field, []Tags, error) { } } } + return fields, tags, nil } func parseTag(field Field, lastPublic int) (Tags, error) { name := field.Name tag := reflect.StructTag(field.Tag) + var ts Tags + for _, t := range strings.Split(tag.Get("rlp"), ",") { switch t = strings.TrimSpace(t); t { case "": @@ -159,6 +173,7 @@ func parseTag(field Field, lastPublic int) (Tags, error) { if field.Type.Kind != reflect.Ptr { return ts, TagError{Field: name, Tag: t, Err: "field is not a pointer"} } + switch t { case "nil": ts.NilKind = field.Type.Elem.DefaultNilValue() @@ -177,9 +192,11 @@ func parseTag(field Field, lastPublic int) (Tags, error) { if field.Index != lastPublic { return ts, TagError{Field: name, Tag: t, Err: "must be on last field"} } + if ts.Optional { return ts, TagError{Field: name, Tag: t, Err: `also has "optional" tag`} } + if field.Type.Kind != reflect.Slice { return ts, TagError{Field: name, Tag: t, Err: "field type is not slice"} } @@ -187,16 +204,19 @@ func parseTag(field Field, lastPublic int) (Tags, error) { return ts, TagError{Field: name, Tag: t, Err: "unknown tag"} } } + return ts, nil } func lastPublicField(fields []Field) int { last := 0 + for _, f := range fields { if f.Exported { last = f.Index } } + return last } diff --git a/rlp/iterator.go b/rlp/iterator.go index 6be574572e..0d144f0d4d 100644 --- a/rlp/iterator.go +++ b/rlp/iterator.go @@ -29,12 +29,15 @@ func NewListIterator(data RawValue) (*listIterator, error) { if err != nil { return nil, err } + if k != List { return nil, ErrExpectedList } + it := &listIterator{ data: data[t : t+c], } + return it, nil } @@ -43,10 +46,12 @@ func (it *listIterator) Next() bool { if len(it.data) == 0 { return false } + _, t, c, err := readKind(it.data) it.next = it.data[:t+c] it.data = it.data[t+c:] it.err = err + return true } diff --git a/rlp/iterator_test.go b/rlp/iterator_test.go index a22aaec862..4dab97e162 100644 --- a/rlp/iterator_test.go +++ b/rlp/iterator_test.go @@ -37,22 +37,28 @@ func TestIterator(t *testing.T) { if !it.Next() { t.Fatal("expected two elems, got zero") } + txs := it.Value() // Check that uncles exist if !it.Next() { t.Fatal("expected two elems, got one") } + txit, err := NewListIterator(txs) if err != nil { t.Fatal(err) } + var i = 0 + for txit.Next() { if txit.err != nil { t.Fatal(txit.err) } + i++ } + if exp := 2; i != exp { t.Errorf("count wrong, expected %d got %d", i, exp) } diff --git a/rlp/raw.go b/rlp/raw.go index 773aa7e614..a671892e69 100644 --- a/rlp/raw.go +++ b/rlp/raw.go @@ -72,6 +72,7 @@ func IntSize(x uint64) int { if x < 0x80 { return 1 } + return 1 + intsize(x) } @@ -82,6 +83,7 @@ func Split(b []byte) (k Kind, content, rest []byte, err error) { if err != nil { return 0, nil, b, err } + return k, b[ts : ts+cs], b[ts+cs:], nil } @@ -92,9 +94,11 @@ func SplitString(b []byte) (content, rest []byte, err error) { if err != nil { return nil, b, err } + if k == List { return nil, b, ErrExpectedString } + return content, rest, nil } @@ -105,6 +109,7 @@ func SplitUint64(b []byte) (x uint64, rest []byte, err error) { if err != nil { return 0, b, err } + switch { case len(content) == 0: return 0, rest, nil @@ -112,6 +117,7 @@ func SplitUint64(b []byte) (x uint64, rest []byte, err error) { if content[0] == 0 { return 0, b, ErrCanonInt } + return uint64(content[0]), rest, nil case len(content) > 8: return 0, b, errUintOverflow @@ -120,6 +126,7 @@ func SplitUint64(b []byte) (x uint64, rest []byte, err error) { if err != nil { return 0, b, ErrCanonInt } + return x, rest, nil } } @@ -131,22 +138,27 @@ func SplitList(b []byte) (content, rest []byte, err error) { if err != nil { return nil, b, err } + if k != List { return nil, b, ErrExpectedList } + return content, rest, nil } // CountValues counts the number of encoded values in b. func CountValues(b []byte) (int, error) { i := 0 + for ; len(b) > 0; i++ { _, tagsize, size, err := readKind(b) if err != nil { return 0, err } + b = b[tagsize+size:] } + return i, nil } @@ -154,7 +166,9 @@ func readKind(buf []byte) (k Kind, tagsize, contentsize uint64, err error) { if len(buf) == 0 { return 0, 0, 0, io.ErrUnexpectedEOF } + b := buf[0] + switch { case b < 0x80: k = Byte @@ -181,6 +195,7 @@ func readKind(buf []byte) (k Kind, tagsize, contentsize uint64, err error) { tagsize = uint64(b-0xF7) + 1 contentsize, err = readSize(buf[1:], b-0xF7) } + if err != nil { return 0, 0, 0, err } @@ -188,6 +203,7 @@ func readKind(buf []byte) (k Kind, tagsize, contentsize uint64, err error) { if contentsize > uint64(len(buf))-tagsize { return 0, 0, 0, ErrValueTooLarge } + return k, tagsize, contentsize, err } @@ -195,6 +211,7 @@ func readSize(b []byte, slen byte) (uint64, error) { if int(slen) > len(b) { return 0, io.ErrUnexpectedEOF } + var s uint64 switch slen { case 1: @@ -219,6 +236,7 @@ func readSize(b []byte, slen byte) (uint64, error) { if s < 56 || b[0] == 0 { return 0, ErrCanonSize } + return s, nil } @@ -229,6 +247,7 @@ func AppendUint64(b []byte, i uint64) []byte { } else if i < 128 { return append(b, byte(i)) } + switch { case i < (1 << 8): return append(b, 0x81, byte(i)) diff --git a/rlp/raw_test.go b/rlp/raw_test.go index c5d81c618e..e8f4ea705b 100644 --- a/rlp/raw_test.go +++ b/rlp/raw_test.go @@ -54,6 +54,7 @@ func TestCountValues(t *testing.T) { if count != test.count { t.Errorf("test %d: count mismatch, got %d want %d\ninput: %s", i, count, test.count, test.input) } + if !errors.Is(err, test.err) { t.Errorf("test %d: err mismatch, got %q want %q\ninput: %s", i, err, test.err, test.input) } @@ -130,9 +131,11 @@ func TestSplitUint64(t *testing.T) { if val != test.val { t.Errorf("test %d: val mismatch: got %x, want %x (input %q)", i, val, test.val, test.input) } + if !bytes.Equal(rest, unhex(test.rest)) { t.Errorf("test %d: rest mismatch: got %x, want %s (input %q)", i, rest, test.rest, test.input) } + if err != test.err { t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) } @@ -211,12 +214,15 @@ func TestSplit(t *testing.T) { if kind != test.kind { t.Errorf("test %d: kind mismatch: got %v, want %v", i, kind, test.kind) } + if !bytes.Equal(val, unhex(test.val)) { t.Errorf("test %d: val mismatch: got %x, want %s", i, val, test.val) } + if !bytes.Equal(rest, unhex(test.rest)) { t.Errorf("test %d: rest mismatch: got %x, want %s", i, rest, test.rest) } + if err != test.err { t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) } @@ -259,6 +265,7 @@ func TestReadSize(t *testing.T) { t.Errorf("readSize(%s, %d): error mismatch: got %q, want %q", test.input, test.slen, err, test.err) continue } + if size != test.size { t.Errorf("readSize(%s, %d): size mismatch: got %#x, want %#x", test.input, test.slen, size, test.size) } @@ -300,9 +307,11 @@ func TestAppendUint64Random(t *testing.T) { fn := func(i uint64) bool { enc, _ := EncodeToBytes(i) encAppend := AppendUint64(nil, i) + return bytes.Equal(enc, encAppend) } config := quick.Config{MaxCountScale: 50} + if err := quick.Check(fn, &config); err != nil { t.Fatal(err) } diff --git a/rlp/rlpgen/gen.go b/rlp/rlpgen/gen.go index a0d20970b3..e0bdc75bb5 100644 --- a/rlp/rlpgen/gen.go +++ b/rlp/rlpgen/gen.go @@ -41,6 +41,7 @@ func newBuildContext(packageRLP *types.Package) *buildContext { enc := packageRLP.Scope().Lookup("Encoder").Type().Underlying() dec := packageRLP.Scope().Lookup("Decoder").Type().Underlying() rawv := packageRLP.Scope().Lookup("RawValue").Type() + return &buildContext{ typeToStructCache: make(map[types.Type]*rlpstruct.Type), encoderIface: enc.(*types.Interface), @@ -65,11 +66,13 @@ func (bctx *buildContext) typeToStructType(typ types.Type) *rlpstruct.Type { // Resolve named types to their underlying type, but keep the name. name := types.TypeString(typ, nil) + for { utype := typ.Underlying() if utype == typ { break } + typ = utype } @@ -88,6 +91,7 @@ func (bctx *buildContext) typeToStructType(typ types.Type) *rlpstruct.Type { etype := typ.(interface{ Elem() types.Type }).Elem() t.Elem = bctx.typeToStructType(etype) } + return t } @@ -110,6 +114,7 @@ func newGenContext(inPackage *types.Package) *genContext { func (ctx *genContext) temp() string { v := fmt.Sprintf("_tmp%d", ctx.tempCounter) ctx.tempCounter++ + return v } @@ -131,7 +136,9 @@ func (ctx *genContext) importsList() []string { for k := range ctx.imports { imp = append(imp, k) } + sort.Strings(imp) + return imp } @@ -140,6 +147,7 @@ func (ctx *genContext) qualify(pkg *types.Package) string { if pkg.Path() == ctx.inPackage.Path() { return "" } + ctx.addImport(pkg.Path()) // TODO: renaming? return pkg.Name() @@ -168,6 +176,7 @@ type basicOp struct { func (*buildContext) makeBasicOp(typ *types.Basic) (op, error) { op := basicOp{typ: typ} kind := typ.Kind() + switch { case kind == types.Bool: op.writeMethod = "WriteBool" @@ -188,6 +197,7 @@ func (*buildContext) makeBasicOp(typ *types.Basic) (op, error) { default: return nil, fmt.Errorf("unhandled basic type: %v", typ) } + return op, nil } @@ -195,7 +205,9 @@ func (*buildContext) makeByteSliceOp(typ *types.Slice) op { if !isByte(typ.Elem()) { panic("non-byte slice type in makeByteSliceOp") } + bslice := types.NewSlice(types.Typ[types.Uint8]) + return basicOp{ typ: typ, writeMethod: "WriteBytes", @@ -207,6 +219,7 @@ func (*buildContext) makeByteSliceOp(typ *types.Slice) op { func (bctx *buildContext) makeRawValueOp() op { bslice := types.NewSlice(types.Typ[types.Uint8]) + return basicOp{ typ: bctx.rawValueType, writeMethod: "Write", @@ -228,6 +241,7 @@ func (op basicOp) genWrite(ctx *genContext, v string) string { if op.writeNeedsConversion() { v = fmt.Sprintf("%s(%s)", op.writeArgType, v) } + return fmt.Sprintf("w.%s(%s)\n", op.writeMethod, v) } @@ -237,6 +251,7 @@ func (op basicOp) genDecode(ctx *genContext) (string, string) { result = resultV method = op.decMethod ) + if op.decUseBitSize { // Note: For now, this only works for platform-independent integer // sizes. makeBasicOp forbids the platform-dependent types. @@ -246,13 +261,16 @@ func (op basicOp) genDecode(ctx *genContext) (string, string) { // Call the decoder method. var b bytes.Buffer + fmt.Fprintf(&b, "%s, err := dec.%s()\n", resultV, method) fmt.Fprintf(&b, "if err != nil { return err }\n") + if op.decodeNeedsConversion() { conv := ctx.temp() fmt.Fprintf(&b, "%s := %s(%s)\n", conv, types.TypeString(op.typ, ctx.qualify), resultV) result = conv } + return result, b.String() } @@ -267,6 +285,7 @@ func (bctx *buildContext) makeByteArrayOp(name *types.Named, typ *types.Array) b if name == nil { nt = typ } + return byteArrayOp{typ, nt} } @@ -278,8 +297,10 @@ func (op byteArrayOp) genDecode(ctx *genContext) (string, string) { var resultV = ctx.temp() var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(op.name, ctx.qualify)) fmt.Fprintf(&b, "if err := dec.ReadBytes(%s[:]); err != nil { return err }\n", resultV) + return resultV, b.String() } @@ -296,10 +317,12 @@ func (op bigIntOp) genWrite(ctx *genContext, v string) string { fmt.Fprintf(&b, "if %s.Sign() == -1 {\n", v) fmt.Fprintf(&b, " return rlp.ErrNegativeBigInt\n") fmt.Fprintf(&b, "}\n") + dst := v if !op.pointer { dst = "&" + v } + fmt.Fprintf(&b, "w.WriteBigInt(%s)\n", dst) // Wrap with nil check. @@ -320,6 +343,7 @@ func (op bigIntOp) genDecode(ctx *genContext) (string, string) { var resultV = ctx.temp() var b bytes.Buffer + fmt.Fprintf(&b, "%s, err := dec.BigInt()\n", resultV) fmt.Fprintf(&b, "if err != nil { return err }\n") @@ -327,6 +351,7 @@ func (op bigIntOp) genDecode(ctx *genContext) (string, string) { if !op.pointer { result = "(*" + resultV + ")" } + return result, b.String() } @@ -391,11 +416,14 @@ func (op encoderDecoderOp) genWrite(ctx *genContext, v string) string { func (op encoderDecoderOp) genDecode(ctx *genContext) (string, string) { // DecodeRLP must have pointer receiver, and this is verified in makeOp. etyp := op.typ.(*types.Pointer).Elem() + var resultV = ctx.temp() var b bytes.Buffer + fmt.Fprintf(&b, "%s := new(%s)\n", resultV, types.TypeString(etyp, ctx.qualify)) fmt.Fprintf(&b, "if err := %s.DecodeRLP(dec); err != nil { return err }\n", resultV) + return resultV, b.String() } @@ -412,6 +440,7 @@ func (bctx *buildContext) makePtrOp(elemTyp types.Type, tags rlpstruct.Tags) (op if err != nil { return nil, err } + op := ptrOp{elemTyp: elemTyp, elem: elemOp} // Determine nil value. @@ -422,6 +451,7 @@ func (bctx *buildContext) makePtrOp(elemTyp types.Type, tags rlpstruct.Tags) (op styp := bctx.typeToStructType(elemTyp) op.nilValue = styp.DefaultNilValue() } + return op, nil } @@ -433,8 +463,10 @@ func (op ptrOp) genWrite(ctx *genContext, v string) string { // // For `v.field` and `v[:]` on arrays, the dereference operation is not required. var vv string + _, isStruct := op.elem.(structOp) _, isByteArray := op.elem.(byteArrayOp) + if isStruct || isByteArray { vv = v } else { @@ -442,11 +474,13 @@ func (op ptrOp) genWrite(ctx *genContext, v string) string { } var b bytes.Buffer + fmt.Fprintf(&b, "if %s == nil {\n", v) fmt.Fprintf(&b, " w.Write([]byte{0x%X})\n", op.nilValue) fmt.Fprintf(&b, "} else {\n") fmt.Fprintf(&b, " %s", op.elem.genWrite(ctx, vv)) fmt.Fprintf(&b, "}\n") + return b.String() } @@ -466,12 +500,15 @@ func (op ptrOp) genDecode(ctx *genContext) (string, string) { sizeV = ctx.temp() wantKind string ) + if op.nilValue == rlpstruct.NilKindList { wantKind = "rlp.List" } else { wantKind = "rlp.String" } + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(types.NewPointer(op.elemTyp), ctx.qualify)) fmt.Fprintf(&b, "if %s, %s, err := dec.Kind(); err != nil {\n", kindV, sizeV) fmt.Fprintf(&b, " return err\n") @@ -479,6 +516,7 @@ func (op ptrOp) genDecode(ctx *genContext) (string, string) { fmt.Fprint(&b, code) fmt.Fprintf(&b, " %s = &%s\n", resultV, result) fmt.Fprintf(&b, "}\n") + return resultV, b.String() } @@ -499,6 +537,7 @@ type structField struct { func (bctx *buildContext) makeStructOp(named *types.Named, typ *types.Struct) (op, error) { // Convert fields to []rlpstruct.Field. var allStructFields []rlpstruct.Field + for i := 0; i < typ.NumFields(); i++ { f := typ.Field(i) allStructFields = append(allStructFields, rlpstruct.Field{ @@ -518,17 +557,21 @@ func (bctx *buildContext) makeStructOp(named *types.Named, typ *types.Struct) (o // Create field ops. var op = structOp{named: named, typ: typ} + for i, field := range fields { // Advanced struct tags are not supported yet. tag := tags[i] if err := checkUnsupportedTags(field.Name, tag); err != nil { return nil, err } + typ := typ.Field(field.Index).Type() + elem, err := bctx.makeOp(nil, typ, tags[i]) if err != nil { return nil, fmt.Errorf("field %s: %v", field.Name, err) } + f := &structField{name: field.Name, typ: typ, elem: elem} if tag.Optional { op.optionalFields = append(op.optionalFields, f) @@ -536,6 +579,7 @@ func (bctx *buildContext) makeStructOp(named *types.Named, typ *types.Struct) (o op.fields = append(op.fields, f) } } + return op, nil } @@ -543,19 +587,25 @@ func checkUnsupportedTags(field string, tag rlpstruct.Tags) error { if tag.Tail { return fmt.Errorf(`field %s has unsupported struct tag "tail"`, field) } + return nil } func (op structOp) genWrite(ctx *genContext, v string) string { var b bytes.Buffer + var listMarker = ctx.temp() + fmt.Fprintf(&b, "%s := w.List()\n", listMarker) + for _, field := range op.fields { selector := v + "." + field.name fmt.Fprint(&b, field.elem.genWrite(ctx, selector)) } + op.writeOptionalFields(&b, ctx, v) fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker) + return b.String() } @@ -565,6 +615,7 @@ func (op structOp) writeOptionalFields(b *bytes.Buffer, ctx *genContext, v strin } // First check zero-ness of all optional fields. var zeroV = make([]string, len(op.optionalFields)) + for i, field := range op.optionalFields { selector := v + "." + field.name zeroV[i] = ctx.temp() @@ -574,10 +625,12 @@ func (op structOp) writeOptionalFields(b *bytes.Buffer, ctx *genContext, v strin for i, field := range op.optionalFields { selector := v + "." + field.name cond := "" + for j := i; j < len(op.optionalFields); j++ { if j > i { cond += " || " } + cond += zeroV[j] } fmt.Fprintf(b, "if %s {\n", cond) @@ -599,26 +652,32 @@ func (op structOp) genDecode(ctx *genContext) (string, string) { // Create struct object. var resultV = ctx.temp() + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", resultV, typeName) // Decode fields. fmt.Fprintf(&b, "{\n") fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n") + for _, field := range op.fields { result, code := field.elem.genDecode(ctx) fmt.Fprintf(&b, "// %s:\n", field.name) fmt.Fprint(&b, code) fmt.Fprintf(&b, "%s.%s = %s\n", resultV, field.name, result) } + op.decodeOptionalFields(&b, ctx, resultV) fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n") fmt.Fprintf(&b, "}\n") + return resultV, b.String() } func (op structOp) decodeOptionalFields(b *bytes.Buffer, ctx *genContext, resultV string) { var suffix bytes.Buffer + for _, field := range op.optionalFields { result, code := field.elem.genDecode(ctx) fmt.Fprintf(b, "// %s:\n", field.name) @@ -627,6 +686,7 @@ func (op structOp) decodeOptionalFields(b *bytes.Buffer, ctx *genContext, result fmt.Fprintf(b, "%s.%s = %s\n", resultV, field.name, result) fmt.Fprintf(&suffix, "}\n") } + suffix.WriteTo(b) } @@ -641,6 +701,7 @@ func (bctx *buildContext) makeSliceOp(typ *types.Slice) (op, error) { if err != nil { return nil, err } + return sliceOp{typ: typ, elemOp: elemOp}, nil } @@ -652,19 +713,23 @@ func (op sliceOp) genWrite(ctx *genContext, v string) string { ) var b bytes.Buffer + fmt.Fprintf(&b, "%s := w.List()\n", listMarker) fmt.Fprintf(&b, "for _, %s := range %s {\n", iterElemV, v) fmt.Fprint(&b, elemCode) fmt.Fprintf(&b, "}\n") fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker) + return b.String() } func (op sliceOp) genDecode(ctx *genContext) (string, string) { var sliceV = ctx.temp() // holds the output slice + elemResult, elemCode := op.elemOp.genDecode(ctx) var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", sliceV, types.TypeString(op.typ, ctx.qualify)) fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n") fmt.Fprintf(&b, "for dec.MoreDataInList() {\n") @@ -672,6 +737,7 @@ func (op sliceOp) genDecode(ctx *genContext) (string, string) { fmt.Fprintf(&b, " %s = append(%s, %s)\n", sliceV, sliceV, elemResult) fmt.Fprintf(&b, "}\n") fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n") + return sliceV, b.String() } @@ -685,9 +751,11 @@ func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstru if isUint256(typ) { return uint256Op{}, nil } + if typ == bctx.rawValueType { return bctx.makeRawValueOp(), nil } + if bctx.isDecoder(typ) { return nil, fmt.Errorf("type %v implements rlp.Decoder with non-pointer receiver", typ) } @@ -706,8 +774,10 @@ func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstru if bctx.isDecoder(typ) { return encoderDecoderOp{typ}, nil } + return nil, fmt.Errorf("type %v implements rlp.Encoder but not rlp.Decoder", typ) } + if bctx.isDecoder(typ) { return nil, fmt.Errorf("type %v implements rlp.Decoder but not rlp.Encoder", typ) } @@ -722,12 +792,14 @@ func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstru if isByte(etyp) && !bctx.isEncoder(etyp) { return bctx.makeByteSliceOp(typ), nil } + return bctx.makeSliceOp(typ) case *types.Array: etyp := typ.Elem() if isByte(etyp) && !bctx.isEncoder(etyp) { return bctx.makeByteArrayOp(name, typ), nil } + return nil, fmt.Errorf("unhandled array type: %v", typ) default: return nil, fmt.Errorf("unhandled type: %v", typ) @@ -740,12 +812,15 @@ func generateDecoder(ctx *genContext, typ string, op op) []byte { ctx.addImport(pathOfPackageRLP) result, code := op.genDecode(ctx) + var b bytes.Buffer + fmt.Fprintf(&b, "func (obj *%s) DecodeRLP(dec *rlp.Stream) error {\n", typ) fmt.Fprint(&b, code) fmt.Fprintf(&b, " *obj = %s\n", result) fmt.Fprintf(&b, " return nil\n") fmt.Fprintf(&b, "}\n") + return b.Bytes() } @@ -756,11 +831,13 @@ func generateEncoder(ctx *genContext, typ string, op op) []byte { ctx.addImport(pathOfPackageRLP) var b bytes.Buffer + fmt.Fprintf(&b, "func (obj *%s) EncodeRLP(_w io.Writer) error {\n", typ) fmt.Fprintf(&b, " w := rlp.NewEncoderBuffer(_w)\n") fmt.Fprint(&b, op.genWrite(ctx, "obj")) fmt.Fprintf(&b, " return w.Flush()\n") fmt.Fprintf(&b, "}\n") + return b.Bytes() } @@ -768,6 +845,7 @@ func (bctx *buildContext) generate(typ *types.Named, encoder, decoder bool) ([]b bctx.topType = typ pkg := typ.Obj().Pkg() + op, err := bctx.makeOp(nil, typ, rlpstruct.Tags{}) if err != nil { return nil, err @@ -778,22 +856,28 @@ func (bctx *buildContext) generate(typ *types.Named, encoder, decoder bool) ([]b encSource []byte decSource []byte ) + if encoder { encSource = generateEncoder(ctx, typ.Obj().Name(), op) } + if decoder { decSource = generateDecoder(ctx, typ.Obj().Name(), op) } var b bytes.Buffer + fmt.Fprintf(&b, "package %s\n\n", pkg.Name()) + for _, imp := range ctx.importsList() { fmt.Fprintf(&b, "import %q\n", imp) } + if encoder { fmt.Fprintln(&b) b.Write(encSource) } + if decoder { fmt.Fprintln(&b) b.Write(decSource) diff --git a/rlp/rlpgen/gen_test.go b/rlp/rlpgen/gen_test.go index 3b4f5df287..fea3fa95db 100644 --- a/rlp/rlpgen/gen_test.go +++ b/rlp/rlpgen/gen_test.go @@ -41,6 +41,7 @@ func init() { if err != nil { panic(err) } + testPackageRLP, err = testImporter.ImportFrom(pathOfPackageRLP, cwd, 0) if err != nil { panic(fmt.Errorf("can't load package RLP: %v", err)) @@ -55,10 +56,12 @@ func TestOutput(t *testing.T) { t.Run(test, func(t *testing.T) { inputFile := filepath.Join("testdata", test+".in.txt") outputFile := filepath.Join("testdata", test+".out.txt") + bctx, typ, err := loadTestSource(inputFile, "Test") if err != nil { t.Fatal("error loading test source:", err) } + output, err := bctx.generate(typ, true, true) if err != nil { t.Fatal("error in generate:", err) @@ -74,6 +77,7 @@ func TestOutput(t *testing.T) { if err != nil { t.Fatal("error loading expected test output:", err) } + if !bytes.Equal(output, wantOutput) { t.Fatalf("output mismatch, want: %v got %v", string(wantOutput), string(output)) } @@ -87,11 +91,14 @@ func loadTestSource(file string, typeName string) (*buildContext, *types.Named, if err != nil { return nil, nil, err } + f, err := parser.ParseFile(testFset, file, content, 0) if err != nil { return nil, nil, err } + conf := types.Config{Importer: testImporter} + pkg, err := conf.Check("test", testFset, []*ast.File{f}, nil) if err != nil { return nil, nil, err @@ -99,9 +106,11 @@ func loadTestSource(file string, typeName string) (*buildContext, *types.Named, // Find the test struct. bctx := newBuildContext(testPackageRLP) + typ, err := lookupStructType(pkg.Scope(), typeName) if err != nil { return nil, nil, fmt.Errorf("can't find type %s: %v", typeName, err) } + return bctx, typ, nil } diff --git a/rlp/rlpgen/main.go b/rlp/rlpgen/main.go index 655eea2689..4942142899 100644 --- a/rlp/rlpgen/main.go +++ b/rlp/rlpgen/main.go @@ -39,6 +39,7 @@ func main() { genDecoder = flag.Bool("decoder", false, "generate DecodeRLP?") typename = flag.String("type", "", "type to generate methods for") ) + flag.Parse() cfg := Config{ @@ -47,10 +48,12 @@ func main() { GenerateEncoder: *genEncoder, GenerateDecoder: *genDecoder, } + code, err := cfg.process() if err != nil { fatal(err) } + if *output == "-" { os.Stdout.Write(code) } else { @@ -59,6 +62,7 @@ func main() { fmt.Println("path not verified: " + err.Error()) fatal(err) } + if err := os.WriteFile(canonicalPath, code, 0600); err != nil { fatal(err) } @@ -86,13 +90,16 @@ func (cfg *Config) process() (code []byte, err error) { Dir: cfg.Dir, BuildFlags: []string{"-tags", "norlpgen"}, } + ps, err := packages.Load(pcfg, pathOfPackageRLP, ".") if err != nil { return nil, err } + if len(ps) == 0 { return nil, fmt.Errorf("no Go package found in %s", cfg.Dir) } + packages.PrintErrors(ps) // Find the packages that were loaded. @@ -100,16 +107,19 @@ func (cfg *Config) process() (code []byte, err error) { pkg *types.Package packageRLP *types.Package ) + for _, p := range ps { if len(p.Errors) > 0 { return nil, fmt.Errorf("package %s has errors", p.PkgPath) } + if p.PkgPath == pathOfPackageRLP { packageRLP = p.Types } else { pkg = p.Types } } + bctx := newBuildContext(packageRLP) // Find the type and generate. @@ -117,6 +127,7 @@ func (cfg *Config) process() (code []byte, err error) { if err != nil { return nil, fmt.Errorf("can't find %s in %s: %v", cfg.Type, pkg, err) } + code, err = bctx.generate(typ, cfg.GenerateEncoder, cfg.GenerateDecoder) if err != nil { return nil, err @@ -125,9 +136,11 @@ func (cfg *Config) process() (code []byte, err error) { // Add build comments. // This is done here to avoid processing these lines with gofmt. var header bytes.Buffer + fmt.Fprint(&header, "// Code generated by rlpgen. DO NOT EDIT.\n\n") fmt.Fprint(&header, "//go:build !norlpgen\n") fmt.Fprint(&header, "// +build !norlpgen\n\n") + return append(header.Bytes(), code...), nil } @@ -136,10 +149,12 @@ func lookupStructType(scope *types.Scope, name string) (*types.Named, error) { if err != nil { return nil, err } + _, ok := typ.Underlying().(*types.Struct) if !ok { return nil, errors.New("not a struct type") } + return typ, nil } @@ -148,9 +163,11 @@ func lookupType(scope *types.Scope, name string) (*types.Named, error) { if obj == nil { return nil, errors.New("no such identifier") } + typ, ok := obj.(*types.TypeName) if !ok { return nil, errors.New("not a type") } + return typ.Type().(*types.Named), nil } diff --git a/rlp/rlpgen/types.go b/rlp/rlpgen/types.go index d87c1e83b8..09eef8a93c 100644 --- a/rlp/rlpgen/types.go +++ b/rlp/rlpgen/types.go @@ -31,12 +31,15 @@ func typeReflectKind(typ types.Type) reflect.Kind { // value order matches for Bool..Complex128 return reflect.Bool + reflect.Kind(k-types.Bool) } + if k == types.String { return reflect.String } + if k == types.UnsafePointer { return reflect.UnsafePointer } + panic(fmt.Errorf("unhandled BasicKind %v", k)) case *types.Array: return reflect.Array @@ -66,6 +69,7 @@ func nonZeroCheck(v string, vtyp types.Type, qualify types.Qualifier) string { switch typ := typ.(type) { case *types.Basic: k := typ.Kind() + switch { case k == types.Bool: return v @@ -93,7 +97,9 @@ func isBigInt(typ types.Type) bool { if !ok { return false } + name := named.Obj() + return name.Pkg().Path() == "math/big" && name.Name() == "Int" } @@ -121,6 +127,7 @@ func resolveUnderlying(typ types.Type) types.Type { if t == typ { return t } + typ = t } } diff --git a/rlp/typecache.go b/rlp/typecache.go index 3e37c9d2fc..537a3c868e 100644 --- a/rlp/typecache.go +++ b/rlp/typecache.go @@ -57,6 +57,7 @@ type typeCache struct { func newTypeCache() *typeCache { c := new(typeCache) c.cur.Store(make(map[typekey]*typeinfo)) + return c } @@ -101,6 +102,7 @@ func (c *typeCache) generate(typ reflect.Type, tags rlpstruct.Tags) *typeinfo { // next -> cur c.cur.Store(c.next) c.next = nil + return info } @@ -115,6 +117,7 @@ func (c *typeCache) infoWhileGenerating(typ reflect.Type, tags rlpstruct.Tags) * info := new(typeinfo) c.next[key] = info info.generate(typ, tags) + return info } @@ -128,6 +131,7 @@ type field struct { func structFields(typ reflect.Type) (fields []field, err error) { // Convert fields to rlpstruct.Field. var allStructFields []rlpstruct.Field + for i := 0; i < typ.NumField(); i++ { rf := typ.Field(i) allStructFields = append(allStructFields, rlpstruct.Field{ @@ -146,6 +150,7 @@ func structFields(typ reflect.Type) (fields []field, err error) { tagErr.StructType = typ.String() return nil, tagErr } + return nil, err } @@ -156,6 +161,7 @@ func structFields(typ reflect.Type) (fields []field, err error) { info := theTC.infoWhileGenerating(typ, tags) fields = append(fields, field{sf.Index, info, tags.Optional}) } + return fields, nil } @@ -166,6 +172,7 @@ func firstOptionalField(fields []field) int { return i } } + return len(fields) } @@ -194,6 +201,7 @@ func rtypeToStructType(typ reflect.Type, rec map[reflect.Type]*rlpstruct.Type) * if prev := rec[typ]; prev != nil { return prev // short-circuit for recursive types } + if rec == nil { rec = make(map[reflect.Type]*rlpstruct.Type) } @@ -205,9 +213,11 @@ func rtypeToStructType(typ reflect.Type, rec map[reflect.Type]*rlpstruct.Type) * IsDecoder: typ.Implements(decoderInterface), } rec[typ] = t + if k == reflect.Array || k == reflect.Slice || k == reflect.Ptr { t.Elem = rtypeToStructType(typ.Elem(), rec) } + return t } @@ -221,6 +231,7 @@ func typeNilKind(typ reflect.Type, tags rlpstruct.Tags) Kind { } else { nk = styp.DefaultNilValue() } + switch nk { case rlpstruct.NilKindString: return String diff --git a/rlp/unsafe.go b/rlp/unsafe.go index 2152ba35fc..d5b0d29a09 100644 --- a/rlp/unsafe.go +++ b/rlp/unsafe.go @@ -31,5 +31,6 @@ func byteArrayBytes(v reflect.Value, length int) []byte { hdr.Data = v.UnsafeAddr() hdr.Cap = length hdr.Len = length + return s } diff --git a/rpc/client.go b/rpc/client.go index f828d3574b..27848b1fd6 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -115,6 +115,7 @@ func (c *Client) newClientConn(conn ServerCodec) *clientConn { ctx = context.WithValue(ctx, clientContextKey{}, c) ctx = context.WithValue(ctx, peerInfoContextKey{}, conn.peerInfo()) handler := newHandler(ctx, conn, c.idgen, c.services, NewExecutionPool(100, 0)) + return &clientConn{conn, handler} } @@ -145,6 +146,7 @@ func (op *requestOp) wait(ctx context.Context, c *Client) (*jsonrpcMessage, erro case <-c.closing: } } + return nil, ctx.Err() case resp := <-op.resp: return resp, op.err @@ -194,6 +196,7 @@ func DialOptions(ctx context.Context, rawurl string, options ...ClientOption) (* } var reconnect reconnectFunc + switch u.Scheme { case "http", "https": reconnect = newClientTransportHTTP(rawurl, cfg) @@ -227,8 +230,10 @@ func newClient(initctx context.Context, connect reconnectFunc) (*Client, error) if err != nil { return nil, err } + c := initClient(conn, randomIDGenerator(), new(serviceRegistry)) c.reconnectFunc = connect + return c, nil } @@ -249,9 +254,11 @@ func initClient(conn ServerCodec, idgen func() ID, services *serviceRegistry) *C reqSent: make(chan error, 1), reqTimeout: make(chan *requestOp), } + if !isHTTP { go c.dispatch(conn) } + return c } @@ -272,9 +279,12 @@ func (c *Client) nextID() json.RawMessage { // APIs that are available on the server. func (c *Client) SupportedModules() (map[string]string, error) { var result map[string]string + ctx, cancel := context.WithTimeout(context.Background(), subscribeTimeout) defer cancel() + err := c.CallContext(ctx, &result, "rpc_modules") + return result, err } @@ -297,6 +307,7 @@ func (c *Client) SetHeader(key, value string) { if !c.isHTTP { return } + conn := c.writeConn.(*httpConn) conn.mu.Lock() conn.headers.Set(key, value) @@ -322,10 +333,12 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str if result != nil && reflect.TypeOf(result).Kind() != reflect.Ptr { return fmt.Errorf("call result parameter must be pointer or nil interface: %v", result) } + msg, err := c.newMessage(method, args...) if err != nil { return err } + op := &requestOp{ids: []json.RawMessage{msg.ID}, resp: make(chan *jsonrpcMessage, 1)} if c.isHTTP { @@ -333,6 +346,7 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str } else { err = c.send(ctx, op, msg) } + if err != nil { return err } @@ -380,15 +394,18 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error { msgs = make([]*jsonrpcMessage, len(b)) byID = make(map[string]int, len(b)) ) + op := &requestOp{ ids: make([]json.RawMessage, len(b)), resp: make(chan *jsonrpcMessage, len(b)), } + for i, elem := range b { msg, err := c.newMessage(elem.Method, elem.Args...) if err != nil { return err } + msgs[i] = msg op.ids[i] = msg.ID byID[string(msg.ID)] = i @@ -404,6 +421,7 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error { // Wait for all responses to come back. for n := 0; n < len(b) && err == nil; n++ { var resp *jsonrpcMessage + resp, err = op.wait(ctx, c) if err != nil { break @@ -416,27 +434,33 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error { elem.Error = resp.Error continue } + if len(resp.Result) == 0 { elem.Error = ErrNoResult continue } + elem.Error = json.Unmarshal(resp.Result, elem.Result) } + return err } // Notify sends a notification, i.e. a method call that doesn't expect a response. func (c *Client) Notify(ctx context.Context, method string, args ...interface{}) error { op := new(requestOp) + msg, err := c.newMessage(method, args...) if err != nil { return err } + msg.ID = nil if c.isHTTP { return c.sendHTTP(ctx, op, msg) } + return c.send(ctx, op, msg) } @@ -469,9 +493,11 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf if chanVal.Kind() != reflect.Chan || chanVal.Type().ChanDir()&reflect.SendDir == 0 { panic(fmt.Sprintf("channel argument of Subscribe has type %T, need writable channel", channel)) } + if chanVal.IsNil() { panic("channel given to Subscribe must not be nil") } + if c.isHTTP { return nil, ErrNotificationsUnsupported } @@ -480,6 +506,7 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf if err != nil { return nil, err } + op := &requestOp{ ids: []json.RawMessage{msg.ID}, resp: make(chan *jsonrpcMessage), @@ -491,20 +518,24 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf if err := c.send(ctx, op, msg); err != nil { return nil, err } + if _, err := op.wait(ctx, c); err != nil { return nil, err } + return op.sub, nil } func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) { msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method} + if paramsIn != nil { // prevent sending "params":null var err error if msg.Params, err = json.Marshal(paramsIn); err != nil { return nil, err } } + return msg, nil } @@ -515,6 +546,7 @@ func (c *Client) send(ctx context.Context, op *requestOp, msg interface{}) error case c.reqInit <- op: err := c.write(ctx, msg, false) c.reqSent <- err + return err case <-ctx.Done(): // This can happen if the client is overloaded or unable to keep up with @@ -540,6 +572,7 @@ func (c *Client) write(ctx context.Context, msg interface{}, retry bool) error { return c.write(ctx, msg, true) } } + return err } @@ -553,6 +586,7 @@ func (c *Client) reconnect(ctx context.Context) error { ctx, cancel = context.WithTimeout(ctx, defaultDialTimeout) defer cancel() } + newconn, err := c.reconnectFunc(ctx) if err != nil { log.Trace("RPC client reconnect failed", "err", err) @@ -578,12 +612,15 @@ func (c *Client) dispatch(codec ServerCodec) { conn = c.newClientConn(codec) reading = true ) + defer func() { close(c.closing) + if reading { conn.close(ErrClientQuit, nil) c.drainRead() } + close(c.didClose) }() @@ -606,11 +643,13 @@ func (c *Client) dispatch(codec ServerCodec) { case err := <-c.readErr: conn.handler.log.Debug("RPC connection read error", "err", err) conn.close(err, lastOp) + reading = false // Reconnect: case newcodec := <-c.reconnected: log.Debug("RPC client reconnected", "reading", reading, "conn", newcodec.remoteAddr()) + if reading { // Wait for the previous read loop to exit. This is a rare case which // happens if this loop isn't notified in time after the connection breaks. @@ -620,7 +659,9 @@ func (c *Client) dispatch(codec ServerCodec) { conn.close(errClientReconnected, lastOp) c.drainRead() } + go c.read(newcodec) + reading = true conn = c.newClientConn(newcodec) // Re-register the in-flight request on the new handler @@ -669,6 +710,7 @@ func (c *Client) read(codec ServerCodec) { msg := errorMessage(&parseError{err.Error()}) _ = codec.writeJSON(context.Background(), msg, true) } + if err != nil { c.readErr <- err return diff --git a/rpc/client_example_test.go b/rpc/client_example_test.go index 044b57a9c4..cef30f132c 100644 --- a/rpc/client_example_test.go +++ b/rpc/client_example_test.go @@ -49,6 +49,7 @@ func ExampleClientSubscription() { if i > 0 { time.Sleep(2 * time.Second) } + subscribeBlocks(client, subch) } }() @@ -75,6 +76,7 @@ func subscribeBlocks(client *rpc.Client, subch chan Block) { // The connection is established now. // Update the channel with the current block. var lastBlock Block + err = client.CallContext(ctx, &lastBlock, "eth_getBlockByNumber", "latest", false) if err != nil { fmt.Println("can't get latest block:", err) diff --git a/rpc/client_opt.go b/rpc/client_opt.go index 72b5001319..9c8a8873d0 100644 --- a/rpc/client_opt.go +++ b/rpc/client_opt.go @@ -73,6 +73,7 @@ func WithHeader(key, value string) ClientOption { func WithHeaders(headers http.Header) ClientOption { return optionFunc(func(cfg *clientConfig) { cfg.initHeaders() + for k, vs := range headers { cfg.httpHeaders[k] = vs } diff --git a/rpc/client_test.go b/rpc/client_test.go index 5e10b6d454..e46690dc4e 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -58,12 +58,14 @@ func TestClientRequest(t *testing.T) { func TestClientResponseType(t *testing.T) { server := newTestServer() defer server.Stop() + client := DialInProc(server) defer client.Close() if err := client.Call(nil, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil { t.Errorf("Passing nil as result should be fine, but got an error: %v", err) } + var resultVar echoResult // Note: passing the var, not a ref err := client.Call(resultVar, "test_echo", "hello", 10, &echoArgs{"world"}) @@ -100,10 +102,12 @@ func TestClientNullResponse(t *testing.T) { func TestClientErrorData(t *testing.T) { server := newTestServer() defer server.Stop() + client := DialInProc(server) defer client.Close() var resp interface{} + err := client.Call(&resp, "test_returnError") if err == nil { t.Fatal("expected error") @@ -113,6 +117,7 @@ func TestClientErrorData(t *testing.T) { // The method handler returns an error value which implements the rpc.Error // interface, i.e. it has a custom error code. The server returns this error code. expectedCode := testError{}.ErrorCode() + if e, ok := err.(Error); !ok { t.Fatalf("client did not return rpc.Error, got %#v", e) } else if e.ErrorCode() != expectedCode { @@ -130,6 +135,7 @@ func TestClientErrorData(t *testing.T) { func TestClientBatchRequest(t *testing.T) { server := newTestServer() defer server.Stop() + client := DialInProc(server) defer client.Close() @@ -153,6 +159,7 @@ func TestClientBatchRequest(t *testing.T) { if err := client.BatchCall(batch); err != nil { t.Fatal(err) } + wantResult := []BatchElem{ { Method: "test_echo", @@ -210,8 +217,10 @@ func TestClientBatchRequest_len(t *testing.T) { {Method: "bar"}, {Method: "baz"}, } + ctx, cancelFn := context.WithTimeout(context.Background(), time.Second) defer cancelFn() + if err := client.BatchCallContext(ctx, batch); !errors.Is(err, ErrBadResult) { t.Errorf("expected %q but got: %v", ErrBadResult, err) } @@ -223,8 +232,10 @@ func TestClientBatchRequest_len(t *testing.T) { batch := []BatchElem{ {Method: "foo"}, } + ctx, cancelFn := context.WithTimeout(context.Background(), time.Second) defer cancelFn() + if err := client.BatchCallContext(ctx, batch); !errors.Is(err, ErrBadResult) { t.Errorf("expected %q but got: %v", ErrBadResult, err) } @@ -234,6 +245,7 @@ func TestClientBatchRequest_len(t *testing.T) { func TestClientNotify(t *testing.T) { server := newTestServer() defer server.Stop() + client := DialInProc(server) defer client.Close() @@ -278,14 +290,17 @@ func testClientCancel(transport string, t *testing.T) { } var client *Client + switch transport { case "ws", "http": c, hs := httpTestClient(server, transport, fl) defer hs.Close() + client = c case "ipc": c, l := ipcTestClient(server, fl) defer l.Close() + client = c default: panic("unknown transport: " + transport) @@ -297,14 +312,17 @@ func testClientCancel(transport string, t *testing.T) { nreqs = 10 ncallers = 10 ) + caller := func(index int) { defer wg.Done() + for i := 0; i < nreqs; i++ { var ( ctx context.Context cancel func() timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout))) ) + if index < ncallers/2 { // For half of the callers, create a context without deadline // and cancel it later. @@ -320,6 +338,7 @@ func testClientCancel(transport string, t *testing.T) { // Now perform a call with the context. // The key thing here is that no call will ever complete successfully. err := client.CallContext(ctx, nil, "test_block") + switch { case err == nil: _, hasDeadline := ctx.Deadline() @@ -327,10 +346,13 @@ func testClientCancel(transport string, t *testing.T) { // default: // t.Logf("got expected error with %v wait time: %v", timeout, err) } + cancel() } } + wg.Add(ncallers) + for i := 0; i < ncallers; i++ { go caller(i) } @@ -340,6 +362,7 @@ func testClientCancel(transport string, t *testing.T) { func TestClientSubscribeInvalidArg(t *testing.T) { server := newTestServer() defer server.Stop() + client := DialInProc(server) defer client.Close() @@ -349,10 +372,13 @@ func TestClientSubscribeInvalidArg(t *testing.T) { if shouldPanic && err == nil { t.Errorf("EthSubscribe should've panicked for %#v", arg) } + if !shouldPanic && err != nil { t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg) + buf := make([]byte, 1024*1024) buf = buf[:runtime.Stack(buf, false)] + t.Error(err) t.Error(string(buf)) } @@ -370,15 +396,18 @@ func TestClientSubscribeInvalidArg(t *testing.T) { func TestClientSubscribe(t *testing.T) { server := newTestServer() defer server.Stop() + client := DialInProc(server) defer client.Close() nc := make(chan int) count := 10 + sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0) if err != nil { t.Fatal("can't subscribe:", err) } + for i := 0; i < count; i++ { if val := <-nc; val != i { t.Fatalf("value mismatch: got %d, want %d", val, i) @@ -405,11 +434,13 @@ func TestClientSubscribeClose(t *testing.T) { gotHangSubscriptionReq: make(chan struct{}), unblockHangSubscription: make(chan struct{}), } + if err := server.RegisterName("nftest2", service); err != nil { t.Fatal(err) } defer server.Stop() + client := DialInProc(server) defer client.Close() @@ -419,6 +450,7 @@ func TestClientSubscribeClose(t *testing.T) { sub *ClientSubscription err error ) + go func() { sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999) errc <- err @@ -433,6 +465,7 @@ func TestClientSubscribeClose(t *testing.T) { if err == nil { t.Errorf("Subscribe returned nil error after Close") } + if sub != nil { t.Error("Subscribe returned non-nil subscription after Close") } @@ -450,10 +483,12 @@ func TestClientCloseUnsubscribeRace(t *testing.T) { for i := 0; i < 20; i++ { client := DialInProc(server) nc := make(chan int) + sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1) if err != nil { t.Fatal(err) } + go client.Close() go sub.Unsubscribe() select { @@ -482,9 +517,11 @@ func (r *unsubscribeRecorder) readBatch() ([]*jsonrpcMessage, bool, error) { if err := json.Unmarshal(msg.Params, ¶ms); err != nil { panic("unsubscribe decode error: " + err.Error()) } + r.unsubscribes[params[0]] = true } } + return msgs, batch, err } @@ -496,9 +533,12 @@ func TestClientSubscriptionUnsubscribeServer(t *testing.T) { // Create the server. srv := NewServer(0, 0) srv.RegisterName("nftest", new(notificationTestService)) + p1, p2 := net.Pipe() + recorder := &unsubscribeRecorder{ServerCodec: NewCodec(p1)} go srv.ServeCodec(recorder, OptionMethodInvocation|OptionSubscriptions) + defer srv.Stop() // Create the client on the other end of the pipe. @@ -509,6 +549,7 @@ func TestClientSubscriptionUnsubscribeServer(t *testing.T) { // Create the subscription. ch := make(chan int) + sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 1, 1) if err != nil { t.Fatal(err) @@ -516,9 +557,11 @@ func TestClientSubscriptionUnsubscribeServer(t *testing.T) { // Unsubscribe and check that unsubscribe was called. sub.Unsubscribe() + if !recorder.unsubscribes[sub.subid] { t.Fatal("client did not call unsubscribe method") } + if _, open := <-sub.Err(); open { t.Fatal("subscription error channel not closed after unsubscribe") } @@ -534,18 +577,22 @@ func TestClientSubscriptionChannelClose(t *testing.T) { httpsrv = httptest.NewServer(srv.WebsocketHandler(nil)) wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") ) + defer srv.Stop() defer httpsrv.Close() srv.RegisterName("nftest", new(notificationTestService)) + client, _ := Dial(wsURL) for i := 0; i < 100; i++ { ch := make(chan int, 100) + sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", maxClientSubscriptionBuffer-1, 1) if err != nil { t.Fatal(err) } + sub.Unsubscribe() close(ch) } @@ -560,16 +607,19 @@ func TestClientNotificationStorm(t *testing.T) { doTest := func(count int, wantError bool) { client := DialInProc(server) defer client.Close() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Subscribe on the server. It will start sending many notifications // very quickly. nc := make(chan int) + sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0) if err != nil { t.Fatal("can't subscribe:", err) } + defer sub.Unsubscribe() // Process each notification, try to run a call in between each of them. @@ -585,17 +635,22 @@ func TestClientNotificationStorm(t *testing.T) { } else if !wantError { t.Fatalf("(%d/%d) got unexpected error %q", i, count, err) } + return } + var r int + err := client.CallContext(ctx, &r, "nftest_echo", i) if err != nil { if !wantError { t.Fatalf("(%d/%d) call error: %v", i, count, err) } + return } } + if wantError { t.Fatalf("didn't get expected error") } @@ -607,11 +662,14 @@ func TestClientNotificationStorm(t *testing.T) { func TestClientSetHeader(t *testing.T) { var gotHeader bool + srv := newTestServer() + httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("test") == "ok" { gotHeader = true } + srv.ServeHTTP(w, r) })) defer httpsrv.Close() @@ -624,15 +682,18 @@ func TestClientSetHeader(t *testing.T) { defer client.Close() client.SetHeader("test", "ok") + if _, err := client.SupportedModules(); err != nil { t.Fatal(err) } + if !gotHeader { t.Fatal("client did not set custom header") } // Check that Content-Type can be replaced. client.SetHeader("content-type", "application/x-garbage") + _, err = client.SupportedModules() if err == nil { t.Fatal("no error for invalid content-type header") @@ -655,9 +716,12 @@ func TestClientHTTP(t *testing.T) { errc = make(chan error, len(results)) wantResult = echoResult{"a", 1, new(echoArgs)} ) + defer client.Close() + for i := range results { i := i + go func() { errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args) }() @@ -666,6 +730,7 @@ func TestClientHTTP(t *testing.T) { // Wait for all of them to complete. timeout := time.NewTimer(5 * time.Second) defer timeout.Stop() + for i := range results { select { case err := <-errc: @@ -688,11 +753,14 @@ func TestClientHTTP(t *testing.T) { func TestClientReconnect(t *testing.T) { startServer := func(addr string) (*Server, net.Listener) { srv := newTestServer() + l, err := net.Listen("tcp", addr) if err != nil { t.Fatal("can't listen:", err) } + go http.Serve(l, srv.WebsocketHandler([]string{"*"})) + return srv, l } @@ -701,11 +769,14 @@ func TestClientReconnect(t *testing.T) { // Start a server and corresponding client. s1, l1 := startServer("127.0.0.1:0") + client, err := DialContext(ctx, "ws://"+l1.Addr().String()) defer client.Close() + if err != nil { t.Fatal("can't dial", err) } + defer client.Close() // Perform a call. This should work because the server is up. @@ -733,22 +804,27 @@ func TestClientReconnect(t *testing.T) { defer s2.Stop() start := make(chan struct{}) + errors := make(chan error, 20) for i := 0; i < cap(errors); i++ { go func() { <-start + var resp echoResult errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil) }() } close(start) + errcount := 0 + for i := 0; i < cap(errors); i++ { if err = <-errors; err != nil { errcount++ } } t.Logf("%d errors, last error: %v", errcount, err) + if errcount > 1 { t.Errorf("expected one error after disconnect, got %d", errcount) } @@ -757,6 +833,7 @@ func TestClientReconnect(t *testing.T) { func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { // Create the HTTP server. var hs *httptest.Server + switch transport { case "ws": hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"})) @@ -772,10 +849,12 @@ func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, } // Connect the client. hs.Start() + client, err := Dial(transport + "://" + hs.Listener.Addr().String()) if err != nil { panic(err) } + return client, hs } @@ -787,6 +866,7 @@ func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) { } else { endpoint = os.TempDir() + "/" + endpoint } + l, err := ipcListen(endpoint) if err != nil { panic(err) @@ -796,12 +876,14 @@ func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) { fl.Listener = l l = fl } + go srv.ServeListener(l) // Connect the client. client, err := Dial(endpoint) if err != nil { panic(err) } + return client, l } @@ -824,5 +906,6 @@ func (l *flakeyListener) Accept() (net.Conn, error) { c.Close() }) } + return c, err } diff --git a/rpc/endpoints.go b/rpc/endpoints.go index 2a539d4fc5..361af181c9 100644 --- a/rpc/endpoints.go +++ b/rpc/endpoints.go @@ -31,22 +31,27 @@ func StartIPCEndpoint(ipcEndpoint string, apis []API) (net.Listener, *Server, er 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 } + 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 { return nil, nil, err } + go handler.ServeListener(listener) + return listener, handler, nil } diff --git a/rpc/errors.go b/rpc/errors.go index ec577dedff..f1e90d26e1 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -30,6 +30,7 @@ func (err HTTPError) Error() string { if len(err.Body) == 0 { return err.Status } + return fmt.Sprintf("%v: %s", err.Status, err.Body) } diff --git a/rpc/handler.go b/rpc/handler.go index 3b40537421..fb8aee1a54 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -75,6 +75,7 @@ type callProc struct { func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry, pool *SafePool) *handler { rootCtx, cancelRoot := context.WithCancel(connCtx) + h := &handler{ reg: reg, idgen: idgen, @@ -91,6 +92,7 @@ func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg * if conn.remoteAddr() != "" { h.log = h.log.New("conn", conn.remoteAddr()) } + h.unsubscribeCb = newCallback(reflect.Value{}, reflect.ValueOf(h.unsubscribe)) return h @@ -179,16 +181,19 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) { resp := errorMessage(&invalidRequestError{"empty batch"}) _ = h.conn.writeJSON(cp.ctx, resp, true) }) + return } // Handle non-call messages first: calls := make([]*jsonrpcMessage, 0, len(msgs)) + for _, msg := range msgs { if handled := h.handleImmediate(msg); !handled { calls = append(calls, msg) } } + if len(calls) == 0 { return } @@ -218,18 +223,23 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) { if cp.ctx.Err() != nil { break } + msg := callBuffer.nextCall() if msg == nil { break } + resp := h.handleCallMsg(cp, msg) callBuffer.pushResponse(resp) } + if timer != nil { timer.Stop() } + callBuffer.write(cp.ctx, h.conn) h.addSubscriptions(cp.notifiers) + for _, n := range cp.notifiers { n.activate() } @@ -241,12 +251,14 @@ func (h *handler) handleMsg(msg *jsonrpcMessage) { if ok := h.handleImmediate(msg); ok { return } + h.startCallProc(func(cp *callProc) { var ( responded sync.Once timer *time.Timer cancel context.CancelFunc ) + cp.ctx, cancel = context.WithCancel(cp.ctx) defer cancel() @@ -264,15 +276,19 @@ func (h *handler) handleMsg(msg *jsonrpcMessage) { } answer := h.handleCallMsg(cp, msg) + if timer != nil { timer.Stop() } + h.addSubscriptions(cp.notifiers) + if answer != nil { responded.Do(func() { _ = h.conn.writeJSON(cp.ctx, answer, false) }) } + for _, n := range cp.notifiers { _ = n.activate() } @@ -316,9 +332,11 @@ func (h *handler) cancelAllRequests(err error, inflightReq *requestOp) { if !didClose[op] { op.err = err close(op.resp) + didClose[op] = true } } + for id, sub := range h.clientSubs { delete(h.clientSubs, id) sub.close(err) @@ -367,16 +385,19 @@ func (h *handler) startCallProc(fn func(*callProc)) { // call or requires a reply. func (h *handler) handleImmediate(msg *jsonrpcMessage) bool { start := time.Now() + switch { case msg.isNotification(): if strings.HasSuffix(msg.Method, notificationMethodSuffix) { h.handleSubscriptionResult(msg) return true } + return false case msg.isResponse(): h.handleResponse(msg) h.log.Trace("Handled RPC response", "reqid", idForLog{msg.ID}, "duration", time.Since(start)) + return true default: return false @@ -390,6 +411,7 @@ func (h *handler) handleSubscriptionResult(msg *jsonrpcMessage) { h.log.Debug("Dropping invalid subscription message") return } + if h.clientSubs[result.ID] != nil { h.clientSubs[result.ID].deliver(result.Result) } @@ -402,6 +424,7 @@ func (h *handler) handleResponse(msg *jsonrpcMessage) { h.log.Debug("Unsolicited RPC response", "reqid", idForLog{msg.ID}) return } + delete(h.respWait, string(msg.ID)) // For normal responses, just forward the reply to Call/BatchCall. if op.sub == nil { @@ -412,10 +435,12 @@ func (h *handler) handleResponse(msg *jsonrpcMessage) { // indicates success. EthSubscribe gets unblocked in either case through // the op.resp channel. defer close(op.resp) + if msg.Error != nil { op.err = msg.Error return } + if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil { h.executionPool.Submit(context.Background(), func() error { op.sub.run() @@ -429,24 +454,30 @@ func (h *handler) handleResponse(msg *jsonrpcMessage) { // handleCallMsg executes a call message and returns the answer. func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMessage { start := time.Now() + switch { case msg.isNotification(): h.handleCall(ctx, msg) h.log.Debug("Served "+msg.Method, "duration", time.Since(start)) + return nil case msg.isCall(): resp := h.handleCall(ctx, msg) + var ctx []interface{} + ctx = append(ctx, "reqid", idForLog{msg.ID}, "duration", time.Since(start)) if resp.Error != nil { ctx = append(ctx, "err", resp.Error.Message) if resp.Error.Data != nil { ctx = append(ctx, "errdata", resp.Error.Data) } + h.log.Warn("Served "+msg.Method, ctx...) } else { h.log.Debug("Served "+msg.Method, ctx...) } + return resp case msg.hasValidID(): return msg.errorResponse(&invalidRequestError{"invalid request"}) @@ -460,33 +491,40 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage if msg.isSubscribe() { return h.handleSubscribe(cp, msg) } + var callb *callback if msg.isUnsubscribe() { callb = h.unsubscribeCb } else { callb = h.reg.callback(msg.Method) } + if callb == nil { return msg.errorResponse(&methodNotFoundError{method: msg.Method}) } + args, err := parsePositionalArguments(msg.Params, callb.argTypes) if err != nil { return msg.errorResponse(&invalidParamsError{err.Error()}) } + start := time.Now() answer := h.runMethod(cp.ctx, msg, callb, args) // Collect the statistics for RPC calls if metrics is enabled. // We only care about pure rpc call. Filter out subscription. if callb != h.unsubscribeCb { rpcRequestGauge.Inc(1) + if answer.Error != nil { failedRequestGauge.Inc(1) } else { successfulRequestGauge.Inc(1) } + rpcServingTimer.UpdateSince(start) updateServeTimeHistogram(msg.Method, answer.Error == nil, time.Since(start)) } + return answer } @@ -504,18 +542,22 @@ func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMes if err != nil { return msg.errorResponse(&invalidParamsError{err.Error()}) } + namespace := msg.namespace() callb := h.reg.subscription(namespace, name) + if callb == nil { return msg.errorResponse(&subscriptionNotFoundError{namespace, name}) } // Parse subscription name arg too, but remove it before calling the callback. argTypes := append([]reflect.Type{stringType}, callb.argTypes...) + args, err := parsePositionalArguments(msg.Params, argTypes) if err != nil { return msg.errorResponse(&invalidParamsError{err.Error()}) } + args = args[1:] // Install notifier in context so the subscription handler can find it. @@ -532,6 +574,7 @@ func (h *handler) runMethod(ctx context.Context, msg *jsonrpcMessage, callb *cal if err != nil { return msg.errorResponse(err) } + return msg.response(result) } @@ -544,8 +587,10 @@ func (h *handler) unsubscribe(ctx context.Context, id ID) (bool, error) { if s == nil { return false, ErrSubscriptionNotFound } + close(s.err) delete(h.serverSubs, id) + return true, nil } @@ -555,5 +600,6 @@ func (id idForLog) String() string { if s, err := strconv.Unquote(string(id.RawMessage)); err == nil { return s } + return string(id.RawMessage) } diff --git a/rpc/http.go b/rpc/http.go index e9c6e5ff58..deed67e241 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -172,10 +172,12 @@ func newClientTransportHTTP(endpoint string, cfg *clientConfig) reconnectFunc { func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { hc := c.writeConn.(*httpConn) + respBody, err := hc.doRequest(ctx, msg) if err != nil { return err } + defer respBody.Close() var respmsg jsonrpcMessage @@ -183,16 +185,20 @@ func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) e return err } op.resp <- &respmsg + return nil } func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error { hc := c.writeConn.(*httpConn) + respBody, err := hc.doRequest(ctx, msgs) if err != nil { return err } + defer respBody.Close() + var respmsgs []jsonrpcMessage if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { return err @@ -201,9 +207,11 @@ func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonr if len(respmsgs) != len(msgs) { return fmt.Errorf("batch has %d requests but response has %d: %w", len(msgs), len(respmsgs), ErrBadResult) } + for i := 0; i < len(respmsgs); i++ { op.resp <- &respmsgs[i] } + return nil } @@ -217,6 +225,7 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos if err != nil { return nil, err } + req.ContentLength = int64(len(body)) req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil } @@ -237,8 +246,10 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos if err != nil { return nil, err } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { var buf bytes.Buffer + var body []byte if _, err := buf.ReadFrom(resp.Body); err == nil { body = buf.Bytes() @@ -250,6 +261,7 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos Body: body, } } + return resp.Body, nil } @@ -322,6 +334,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) return } + if code, err := validateRequest(r); err != nil { http.Error(w, err.Error(), code) return @@ -340,6 +353,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // until EOF, writes the response to w, and orders the server to process a // single request. w.Header().Set("content-type", contentType) + codec := newHTTPServerConn(r, w) defer codec.close() s.serveSingleRequest(ctx, codec) @@ -351,6 +365,7 @@ func validateRequest(r *http.Request) (int, error) { if r.Method == http.MethodPut || r.Method == http.MethodDelete { return http.StatusMethodNotAllowed, errors.New("method not allowed") } + if r.ContentLength > maxRequestContentLength { err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) return http.StatusRequestEntityTooLarge, err @@ -369,6 +384,7 @@ func validateRequest(r *http.Request) (int, error) { } // Invalid content-type err := fmt.Errorf("invalid content type, only %s is supported", contentType) + return http.StatusUnsupportedMediaType, err } diff --git a/rpc/http_test.go b/rpc/http_test.go index 85bc617eec..7735f3cf9e 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -27,23 +27,28 @@ import ( func confirmStatusCode(t *testing.T, got, want int) { t.Helper() + if got == want { return } + if gotName := http.StatusText(got); len(gotName) > 0 { if wantName := http.StatusText(want); len(wantName) > 0 { t.Fatalf("response status code: got %d (%s), want %d (%s)", got, gotName, want, wantName) } } + t.Fatalf("response status code: got %d, want %d", got, want) } func confirmRequestValidationCode(t *testing.T, method, contentType, body string, expectedStatusCode int) { t.Helper() + request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body)) if len(contentType) > 0 { request.Header.Set("Content-Type", contentType) } + code, err := validateRequest(request) if code == 0 { if err != nil { @@ -52,6 +57,7 @@ func confirmRequestValidationCode(t *testing.T, method, contentType, body string } else if err == nil { t.Errorf("validation: code %d: got nil, expected error", code) } + confirmStatusCode(t, code, expectedStatusCode) } @@ -79,7 +85,9 @@ func TestHTTPErrorResponseWithValidRequest(t *testing.T) { func confirmHTTPRequestYieldsStatusCode(t *testing.T, method, contentType, body string, expectedStatusCode int) { t.Helper() + s := Server{} + ts := httptest.NewServer(&s) defer ts.Close() @@ -87,9 +95,11 @@ func confirmHTTPRequestYieldsStatusCode(t *testing.T, method, contentType, body if err != nil { t.Fatalf("failed to create a valid HTTP request: %v", err) } + if len(contentType) > 0 { request.Header.Set("Content-Type", contentType) } + resp, err := http.DefaultClient.Do(request) if err != nil { t.Fatalf("request failed: %v", err) @@ -110,6 +120,7 @@ func TestHTTPRespBodyUnlimited(t *testing.T) { s := NewServer(0, 0) defer s.Stop() s.RegisterName("test", largeRespService{respLength}) + ts := httptest.NewServer(s) defer ts.Close() @@ -123,6 +134,7 @@ func TestHTTPRespBodyUnlimited(t *testing.T) { 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) } @@ -142,6 +154,7 @@ func TestHTTPErrorResponse(t *testing.T) { } var r string + err = c.Call(&r, "test_method") if err == nil { t.Fatal("error was expected") @@ -155,9 +168,11 @@ func TestHTTPErrorResponse(t *testing.T) { if httpErr.StatusCode != http.StatusTeapot { t.Error("unexpected status code", httpErr.StatusCode) } + if httpErr.Status != "418 I'm a teapot" { t.Error("unexpected status text", httpErr.Status) } + if body := string(httpErr.Body); body != "error has occurred!\n" { t.Error("unexpected body", body) } @@ -170,6 +185,7 @@ func TestHTTPErrorResponse(t *testing.T) { func TestHTTPPeerInfo(t *testing.T) { s := newTestServer() defer s.Stop() + ts := httptest.NewServer(s) defer ts.Close() @@ -177,6 +193,7 @@ func TestHTTPPeerInfo(t *testing.T) { if err != nil { t.Fatal(err) } + c.SetHeader("user-agent", "ua-testing") c.SetHeader("origin", "origin.example.com") @@ -189,15 +206,19 @@ func TestHTTPPeerInfo(t *testing.T) { if info.RemoteAddr == "" { t.Error("RemoteAddr not set") } + if info.Transport != "http" { t.Errorf("wrong Transport %q", info.Transport) } + if info.HTTP.Version != "HTTP/1.1" { t.Errorf("wrong HTTP.Version %q", info.HTTP.Version) } + if info.HTTP.UserAgent != "ua-testing" { t.Errorf("wrong HTTP.UserAgent %q", info.HTTP.UserAgent) } + if info.HTTP.Origin != "origin.example.com" { t.Errorf("wrong HTTP.Origin %q", info.HTTP.UserAgent) } diff --git a/rpc/inproc.go b/rpc/inproc.go index 29af5507b9..e3d845dc64 100644 --- a/rpc/inproc.go +++ b/rpc/inproc.go @@ -35,5 +35,6 @@ func DialInProc(handler *Server) *Client { return NewCodec(p2), nil }) + return c } diff --git a/rpc/ipc.go b/rpc/ipc.go index c64affc8b1..eedb10ba6d 100644 --- a/rpc/ipc.go +++ b/rpc/ipc.go @@ -34,6 +34,7 @@ func (s *Server) ServeListener(l net.Listener) error { } else if err != nil { return err } + log.Trace("Accepted RPC connection", "conn", conn.RemoteAddr()) s.executionPool.Submit(context.Background(), func() error { @@ -59,6 +60,7 @@ func newClientTransportIPC(endpoint string) reconnectFunc { if err != nil { return nil, err } + return NewCodec(conn), err } } diff --git a/rpc/ipc_unix.go b/rpc/ipc_unix.go index 9876347708..913dc24923 100644 --- a/rpc/ipc_unix.go +++ b/rpc/ipc_unix.go @@ -41,12 +41,16 @@ func ipcListen(endpoint string) (net.Listener, error) { if err := os.MkdirAll(filepath.Dir(endpoint), 0751); err != nil { return nil, err } + os.Remove(endpoint) + l, err := net.Listen("unix", endpoint) if err != nil { return nil, err } + os.Chmod(endpoint, 0600) + return l, nil } diff --git a/rpc/json.go b/rpc/json.go index d2d0b0c64a..f45c08b82d 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -98,6 +98,7 @@ func (msg *jsonrpcMessage) String() string { func (msg *jsonrpcMessage) errorResponse(err error) *jsonrpcMessage { resp := errorMessage(err) resp.ID = msg.ID + return resp } @@ -106,6 +107,7 @@ func (msg *jsonrpcMessage) response(result interface{}) *jsonrpcMessage { if err != nil { return msg.errorResponse(&internalServerError{errcodeMarshalError, err.Error()}) } + return &jsonrpcMessage{Version: vsn, ID: msg.ID, Result: enc} } @@ -115,13 +117,16 @@ func errorMessage(err error) *jsonrpcMessage { Message: err.Error(), }} ec, ok := err.(Error) + if ok { msg.Error.Code = ec.ErrorCode() } + de, ok := err.(DataError) if ok { msg.Error.Data = de.ErrorData() } + return msg } @@ -135,6 +140,7 @@ func (err *jsonError) Error() string { if err.Message == "" { return fmt.Sprintf("json-rpc error %d", err.Code) } + return err.Message } @@ -193,6 +199,7 @@ func NewFuncCodec(conn deadlineCloser, encode encodeFunc, decode decodeFunc) Ser if ra, ok := conn.(ConnRemoteAddr); ok { codec.remote = ra.RemoteAddr() } + return codec } @@ -226,6 +233,7 @@ func (c *jsonCodec) readBatch() (messages []*jsonrpcMessage, batch bool, err err if err := c.decode(&rawmsg); err != nil { return nil, false, err } + messages, batch = parseMessage(rawmsg) for i, msg := range messages { if msg == nil { @@ -234,6 +242,7 @@ func (c *jsonCodec) readBatch() (messages []*jsonrpcMessage, batch bool, err err messages[i] = new(jsonrpcMessage) } } + return messages, batch, nil } @@ -245,6 +254,7 @@ func (c *jsonCodec) writeJSON(ctx context.Context, v interface{}, isErrorRespons if !ok { deadline = time.Now().Add(defaultWriteTimeout) } + c.conn.SetWriteDeadline(deadline) return c.encode(v, isErrorResponse) @@ -270,15 +280,19 @@ func parseMessage(raw json.RawMessage) ([]*jsonrpcMessage, bool) { if !isBatch(raw) { msgs := []*jsonrpcMessage{{}} json.Unmarshal(raw, &msgs[0]) + return msgs, false } + dec := json.NewDecoder(bytes.NewReader(raw)) dec.Token() // skip '[' + var msgs []*jsonrpcMessage for dec.More() { msgs = append(msgs, new(jsonrpcMessage)) dec.Decode(&msgs[len(msgs)-1]) } + return msgs, true } @@ -289,8 +303,10 @@ func isBatch(raw json.RawMessage) bool { if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d { continue } + return c == '[' } + return false } @@ -299,8 +315,11 @@ func isBatch(raw json.RawMessage) bool { // parsed. Missing optional arguments are returned as reflect.Zero values. func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { dec := json.NewDecoder(bytes.NewReader(rawArgs)) + var args []reflect.Value + tok, err := dec.Token() + switch { case err == io.EOF || tok == nil && err == nil: // "params" is optional and may be empty. Also allow "params":null even though it's @@ -320,28 +339,35 @@ func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([] if types[i].Kind() != reflect.Ptr { return nil, fmt.Errorf("missing value for required argument %d", i) } + args = append(args, reflect.Zero(types[i])) } + return args, nil } func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Value, error) { args := make([]reflect.Value, 0, len(types)) + for i := 0; dec.More(); i++ { if i >= len(types) { return args, fmt.Errorf("too many arguments, want at most %d", len(types)) } + argval := reflect.New(types[i]) if err := dec.Decode(argval.Interface()); err != nil { return args, fmt.Errorf("invalid argument %d: %v", i, err) } + if argval.IsNil() && types[i].Kind() != reflect.Ptr { return args, fmt.Errorf("missing value for required argument %d", i) } + args = append(args, argval.Elem()) } // Read end of args array. _, err := dec.Token() + return args, err } @@ -351,10 +377,13 @@ func parseSubscriptionName(rawArgs json.RawMessage) (string, error) { if tok, _ := dec.Token(); tok != json.Delim('[') { return "", errors.New("non-array args") } + v, _ := dec.Token() + method, ok := v.(string) if !ok { return "", errors.New("expected subscription name as first argument") } + return method, nil } diff --git a/rpc/server.go b/rpc/server.go index eafebeff6b..4bd47bfc82 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -69,6 +69,7 @@ func NewServer(executionPoolSize uint64, executionPoolRequesttimeout time.Durati // as the services and methods it offers. rpcService := &RPCService{server} server.RegisterName(MetadataApi, rpcService) + return server } @@ -158,6 +159,7 @@ func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) { resp := errorMessage(&invalidMessageError{"parse error"}) _ = codec.writeJSON(ctx, resp, true) } + return } @@ -208,6 +210,7 @@ func (s *RPCService) Modules() map[string]string { for name := range s.server.services.services { modules[name] = "1.0" } + return modules } diff --git a/rpc/server_test.go b/rpc/server_test.go index 2a8e64fdb4..ac82303c93 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -56,10 +56,12 @@ func TestServer(t *testing.T) { if err != nil { t.Fatal("where'd my testdata go?") } + for _, f := range files { if f.IsDir() || strings.HasPrefix(f.Name(), ".") { continue } + path := filepath.Join("testdata", f.Name()) name := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) t.Run(name, func(t *testing.T) { @@ -70,6 +72,7 @@ func TestServer(t *testing.T) { func runTestScript(t *testing.T, file string) { server := newTestServer() + content, err := os.ReadFile(file) if err != nil { t.Fatal(err) @@ -77,10 +80,14 @@ func runTestScript(t *testing.T, file string) { clientConn, serverConn := net.Pipe() defer clientConn.Close() + go server.ServeCodec(NewCodec(serverConn), 0) + readbuf := bufio.NewReader(clientConn) + for _, line := range strings.Split(string(content), "\n") { line = strings.TrimSpace(line) + switch { case len(line) == 0 || strings.HasPrefix(line, "//"): // skip comments, blank lines @@ -89,6 +96,7 @@ func runTestScript(t *testing.T, file string) { t.Log(line) // write to connection clientConn.SetWriteDeadline(time.Now().Add(5 * time.Second)) + if _, err := io.WriteString(clientConn, line[4:]+"\n"); err != nil { t.Fatalf("write error: %v", err) } @@ -97,10 +105,12 @@ func runTestScript(t *testing.T, file string) { want := line[4:] // read line from connection and compare text clientConn.SetReadDeadline(time.Now().Add(5 * time.Second)) + sent, err := readbuf.ReadString('\n') if err != nil { t.Fatalf("read error: %v", err) } + sent = strings.TrimRight(sent, "\r\n") if sent != want { t.Errorf("wrong line from server\ngot: %s\nwant: %s", sent, want) @@ -121,7 +131,9 @@ func TestServerShortLivedConn(t *testing.T) { if err != nil { t.Fatal("can't listen:", err) } + defer listener.Close() + go server.ServeListener(listener) var ( @@ -129,6 +141,7 @@ func TestServerShortLivedConn(t *testing.T) { wantResp = `{"jsonrpc":"2.0","id":1,"result":{"nftest":"1.0","rpc":"1.0","test":"1.0"}}` + "\n" deadline = time.Now().Add(10 * time.Second) ) + for i := 0; i < 20; i++ { conn, err := net.Dial("tcp", listener.Addr().String()) if err != nil { @@ -147,6 +160,7 @@ func TestServerShortLivedConn(t *testing.T) { if err != nil { t.Fatal("read error:", err) } + if !bytes.Equal(buf[:n], []byte(wantResp)) { t.Fatalf("wrong response: %s", buf[:n]) } diff --git a/rpc/service.go b/rpc/service.go index a666b7c2c7..f2d37e2f15 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -62,6 +62,7 @@ func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { if name == "" { return fmt.Errorf("no service name for type %s", rcvrVal.Type().String()) } + callbacks := suitableCallbacks(rcvrVal) if len(callbacks) == 0 { return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", rcvr) @@ -69,9 +70,11 @@ func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { r.mu.Lock() defer r.mu.Unlock() + if r.services == nil { r.services = make(map[string]service) } + svc, ok := r.services[name] if !ok { svc = service{ @@ -81,6 +84,7 @@ func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { } r.services[name] = svc } + for name, cb := range callbacks { if cb.isSubscribe { svc.subscriptions[name] = cb @@ -88,6 +92,7 @@ func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { svc.callbacks[name] = cb } } + return nil } @@ -97,8 +102,10 @@ func (r *serviceRegistry) callback(method string) *callback { if len(elem) != 2 { return nil } + r.mu.Lock() defer r.mu.Unlock() + return r.services[elem[0]].callbacks[elem[1]] } @@ -106,6 +113,7 @@ func (r *serviceRegistry) callback(method string) *callback { func (r *serviceRegistry) subscription(service, name string) *callback { r.mu.Lock() defer r.mu.Unlock() + return r.services[service].subscriptions[name] } @@ -115,18 +123,22 @@ func (r *serviceRegistry) subscription(service, name string) *callback { func suitableCallbacks(receiver reflect.Value) map[string]*callback { typ := receiver.Type() callbacks := make(map[string]*callback) + for m := 0; m < typ.NumMethod(); m++ { method := typ.Method(m) if method.PkgPath != "" { continue // method not exported } + cb := newCallback(receiver, method.Func) if cb == nil { continue // function invalid } + name := formatName(method.Name) callbacks[name] = cb } + return callbacks } @@ -144,6 +156,7 @@ func newCallback(receiver, fn reflect.Value) *callback { for i := 0; i < fntype.NumOut(); i++ { outs[i] = fntype.Out(i) } + if len(outs) > 2 { return nil } @@ -155,8 +168,10 @@ func newCallback(receiver, fn reflect.Value) *callback { if isErrorType(outs[0]) || !isErrorType(outs[1]) { return nil } + c.errPos = 1 } + return c } @@ -168,6 +183,7 @@ func (c *callback) makeArgTypes() { if c.rcvr.IsValid() { firstArg++ } + if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType { c.hasCtx = true firstArg++ @@ -186,9 +202,11 @@ func (c *callback) call(ctx context.Context, method string, args []reflect.Value if c.rcvr.IsValid() { fullargs = append(fullargs, c.rcvr) } + if c.hasCtx { fullargs = append(fullargs, reflect.ValueOf(ctx)) } + fullargs = append(fullargs, args...) // Catch panic while running the callback. @@ -207,11 +225,13 @@ func (c *callback) call(ctx context.Context, method string, args []reflect.Value if len(results) == 0 { return nil, nil } + if c.errPos >= 0 && !results[c.errPos].IsNil() { // Method has returned non-nil error value. err := results[c.errPos].Interface().(error) return reflect.Value{}, err } + return results[0].Interface(), nil } @@ -220,6 +240,7 @@ func isContextType(t reflect.Type) bool { for t.Kind() == reflect.Ptr { t = t.Elem() } + return t == contextType } @@ -228,6 +249,7 @@ func isErrorType(t reflect.Type) bool { for t.Kind() == reflect.Ptr { t = t.Elem() } + return t.Implements(errorType) } @@ -236,6 +258,7 @@ func isSubscriptionType(t reflect.Type) bool { for t.Kind() == reflect.Ptr { t = t.Elem() } + return t == subscriptionType } @@ -246,6 +269,7 @@ func isPubSub(methodType reflect.Type) bool { if methodType.NumIn() < 2 || methodType.NumOut() != 2 { return false } + return isContextType(methodType.In(1)) && isSubscriptionType(methodType.Out(0)) && isErrorType(methodType.Out(1)) @@ -257,5 +281,6 @@ func formatName(name string) string { if len(ret) > 0 { ret[0] = unicode.ToLower(ret[0]) } + return string(ret) } diff --git a/rpc/subscription.go b/rpc/subscription.go index 91c4b0c1f6..a127de63a3 100644 --- a/rpc/subscription.go +++ b/rpc/subscription.go @@ -51,6 +51,7 @@ func NewID() ID { // randomIDGenerator returns a function generates a random IDs. func randomIDGenerator() func() ID { var buf = make([]byte, 8) + var seed int64 if _, err := crand.Read(buf); err == nil { seed = int64(binary.BigEndian.Uint64(buf)) @@ -62,11 +63,14 @@ func randomIDGenerator() func() ID { mu sync.Mutex rng = rand.New(rand.NewSource(seed)) ) + return func() ID { mu.Lock() defer mu.Unlock() + id := make([]byte, 16) rng.Read(id) + return encodeID(id) } } @@ -74,9 +78,11 @@ func randomIDGenerator() func() ID { func encodeID(b []byte) ID { id := hex.EncodeToString(b) id = strings.TrimLeft(id, "0") + if id == "" { id = "0" // ID's are RPC quantities, no leading zero's and 0 is 0x0. } + return ID("0x" + id) } @@ -114,7 +120,9 @@ func (n *Notifier) CreateSubscription() *Subscription { } else if n.callReturned { panic("can't create subscription after subscribe call has returned") } + n.sub = &Subscription{ID: n.h.idgen(), namespace: n.namespace, err: make(chan error, 1)} + return n.sub } @@ -134,10 +142,13 @@ func (n *Notifier) Notify(id ID, data interface{}) error { } else if n.sub.ID != id { panic("Notify with wrong ID") } + if n.activated { return n.send(n.sub, enc) } + n.buffer = append(n.buffer, enc) + return nil } @@ -153,6 +164,7 @@ func (n *Notifier) takeSubscription() *Subscription { n.mu.Lock() defer n.mu.Unlock() n.callReturned = true + return n.sub } @@ -168,7 +180,9 @@ func (n *Notifier) activate() error { return err } } + n.activated = true + return nil } @@ -243,6 +257,7 @@ func newClientSubscription(c *Client, namespace string, channel reflect.Value) * unsubDone: make(chan struct{}), err: make(chan error, 1), } + return sub } @@ -328,6 +343,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { for { var chosen int + var recv reflect.Value if buffer.Len() == 0 { // Idle, omit send case. @@ -343,10 +359,12 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { if !recv.IsNil() { err = recv.Interface().(error) } + if err == errUnsubscribed { // Exiting because Unsubscribe was called, unsubscribe on server. return true, nil } + return false, err case 1: // <-sub.in @@ -354,13 +372,16 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { if err != nil { return true, err } + if buffer.Len() == maxClientSubscriptionBuffer { return true, ErrSubscriptionQueueOverflow } + buffer.PushBack(val) case 2: // sub.channel<- cases[2].Send = reflect.Value{} // Don't hold onto the value. + buffer.Remove(buffer.Front()) } } @@ -369,6 +390,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) { val := reflect.New(sub.etype) err := json.Unmarshal(result, val.Interface()) + return val.Elem().Interface(), err } diff --git a/rpc/subscription_test.go b/rpc/subscription_test.go index f803d96bad..0307721209 100644 --- a/rpc/subscription_test.go +++ b/rpc/subscription_test.go @@ -27,6 +27,7 @@ import ( func TestNewID(t *testing.T) { hexchars := "0123456789ABCDEFabcdef" + for i := 0; i < 100; i++ { id := string(NewID()) if !strings.HasPrefix(id, "0x") { @@ -68,7 +69,9 @@ func TestSubscriptions(t *testing.T) { t.Fatalf("unable to register test service %v", err) } } + go server.ServeCodec(NewCodec(serverConn), 0) + defer server.Stop() // wait for message and write them to the given channels @@ -90,13 +93,16 @@ func TestSubscriptions(t *testing.T) { timeout := time.After(30 * time.Second) subids := make(map[string]string, subCount) count := make(map[string]int, subCount) + allReceived := func() bool { done := len(count) == subCount + for _, c := range count { if c < notificationCount { done = false } } + return done } for !allReceived() { @@ -114,10 +120,12 @@ func TestSubscriptions(t *testing.T) { t.Errorf("subscription for %q not created", namespace) continue } + if count, found := count[subid]; !found || count < notificationCount { t.Errorf("didn't receive all notifications (%d<%d) in time for namespace %q", count, notificationCount, namespace) } } + t.Fatal("timed out") } } @@ -132,6 +140,7 @@ func TestServerUnsubscribe(t *testing.T) { server := newTestServer() service := ¬ificationTestService{unsubscribed: make(chan string, 1)} server.RegisterName("nftest2", service) + go server.ServeCodec(NewCodec(p1), 0) // Subscribe. @@ -144,6 +153,7 @@ func TestServerUnsubscribe(t *testing.T) { notifications = make(chan subscriptionResult) errors = make(chan error, 1) ) + go waitForMessages(json.NewDecoder(p2), resps, notifications, errors) // Receive the subscription ID. @@ -156,12 +166,14 @@ func TestServerUnsubscribe(t *testing.T) { // Unsubscribe and check that it is handled on the server side. p2.Write([]byte(`{"jsonrpc":"2.0","method":"nftest2_unsubscribe","params":["` + sub.subid + `"]}`)) + for { select { case id := <-service.unsubscribed: if id != string(sub.subid) { t.Errorf("wrong subscription ID unsubscribed") } + return case err := <-errors: t.Fatal(err) @@ -197,15 +209,18 @@ func readAndValidateMessage(in *json.Decoder) (*subConfirmation, *subscriptionRe if err := in.Decode(&msg); err != nil { return nil, nil, fmt.Errorf("decode error: %v", err) } + switch { case msg.isNotification(): var res subscriptionResult if err := json.Unmarshal(msg.Params, &res); err != nil { return nil, nil, fmt.Errorf("invalid subscription result: %v", err) } + return nil, &res, nil case msg.isResponse(): var c subConfirmation + if msg.Error != nil { return nil, nil, msg.Error } else if err := json.Unmarshal(msg.Result, &c.subid); err != nil { diff --git a/rpc/testservice_test.go b/rpc/testservice_test.go index 3fdc025992..2ae5b04388 100644 --- a/rpc/testservice_test.go +++ b/rpc/testservice_test.go @@ -28,12 +28,15 @@ import ( func newTestServer() *Server { server := NewServer(0, 0) server.idgen = sequentialIDGenerator() + if err := server.RegisterName("test", new(testService)); err != nil { panic(err) } + if err := server.RegisterName("nftest", new(notificationTestService)); err != nil { panic(err) } + return server } @@ -42,12 +45,15 @@ func sequentialIDGenerator() func() ID { mu sync.Mutex counter uint64 ) + return func() ID { mu.Lock() defer mu.Unlock() + counter++ id := make([]byte, 8) binary.BigEndian.PutUint64(id, counter) + return encodeID(id) } } @@ -137,8 +143,10 @@ func (s *testService) CallMeBack(ctx context.Context, method string, args []inte if !ok { return nil, errors.New("no client") } + var result interface{} err := c.Call(&result, method, args...) + return result, err } @@ -147,11 +155,15 @@ func (s *testService) CallMeBackLater(ctx context.Context, method string, args [ if !ok { return errors.New("no client") } + go func() { <-ctx.Done() + var result interface{} + c.Call(&result, method, args...) }() + return nil } @@ -185,6 +197,7 @@ func (s *notificationTestService) SomeSubscription(ctx context.Context, n, val i // back to the client before the first subscription.Notify is called. Otherwise the // events might be send before the response for the *_subscribe method. subscription := notifier.CreateSubscription() + go func() { for i := 0; i < n; i++ { if err := notifier.Notify(subscription.ID, val+i); err != nil { @@ -195,10 +208,12 @@ func (s *notificationTestService) SomeSubscription(ctx context.Context, n, val i case <-notifier.Closed(): case <-subscription.Err(): } + if s.unsubscribed != nil { s.unsubscribed <- string(subscription.ID) } }() + return subscription, nil } @@ -210,11 +225,13 @@ func (s *notificationTestService) HangSubscription(ctx context.Context, val int) } s.gotHangSubscriptionReq <- struct{}{} <-s.unblockHangSubscription + subscription := notifier.CreateSubscription() go func() { notifier.Notify(subscription.ID, val) }() + return subscription, nil } diff --git a/rpc/types.go b/rpc/types.go index d04dfe7d95..6150bba8fc 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -105,10 +105,13 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error { if err != nil { return err } + if blckNum > math.MaxInt64 { return fmt.Errorf("block number larger than int64") } + *bn = BlockNumber(blckNum) + return nil } @@ -144,30 +147,39 @@ type BlockNumberOrHash struct { func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error { type erased BlockNumberOrHash + e := erased{} err := json.Unmarshal(data, &e) + if err == nil { if e.BlockNumber != nil && e.BlockHash != nil { return fmt.Errorf("cannot specify both BlockHash and BlockNumber, choose one or the other") } + bnh.BlockNumber = e.BlockNumber bnh.BlockHash = e.BlockHash bnh.RequireCanonical = e.RequireCanonical + return nil } + var input string + err = json.Unmarshal(data, &input) if err != nil { return err } + switch input { case "earliest": bn := EarliestBlockNumber bnh.BlockNumber = &bn + return nil case "latest": bn := LatestBlockNumber bnh.BlockNumber = &bn + return nil case "pending": bn := PendingBlockNumber @@ -187,22 +199,28 @@ func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error { default: if len(input) == 66 { hash := common.Hash{} + err := hash.UnmarshalText([]byte(input)) if err != nil { return err } + bnh.BlockHash = &hash + return nil } else { blckNum, err := hexutil.DecodeUint64(input) if err != nil { return err } + if blckNum > math.MaxInt64 { return fmt.Errorf("blocknumber too high") } + bn := BlockNumber(blckNum) bnh.BlockNumber = &bn + return nil } } @@ -212,6 +230,7 @@ func (bnh *BlockNumberOrHash) Number() (BlockNumber, bool) { if bnh.BlockNumber != nil { return *bnh.BlockNumber, true } + return BlockNumber(0), false } @@ -219,9 +238,11 @@ func (bnh *BlockNumberOrHash) String() string { if bnh.BlockNumber != nil { return strconv.Itoa(int(*bnh.BlockNumber)) } + if bnh.BlockHash != nil { return bnh.BlockHash.String() } + return "nil" } @@ -229,6 +250,7 @@ func (bnh *BlockNumberOrHash) Hash() (common.Hash, bool) { if bnh.BlockHash != nil { return *bnh.BlockHash, true } + return common.Hash{}, false } @@ -262,9 +284,12 @@ func (dh *DecimalOrHex) UnmarshalJSON(data []byte) error { if err != nil { value, err = hexutil.DecodeUint64(input) } + if err != nil { return err } + *dh = DecimalOrHex(value) + return nil } diff --git a/rpc/types_test.go b/rpc/types_test.go index f110dee7c6..6add6c5da4 100644 --- a/rpc/types_test.go +++ b/rpc/types_test.go @@ -52,15 +52,18 @@ func TestBlockNumberJSONUnmarshal(t *testing.T) { for i, test := range tests { var num BlockNumber + err := json.Unmarshal([]byte(test.input), &num) if test.mustFail && err == nil { t.Errorf("Test %d should fail", i) continue } + if !test.mustFail && err != nil { t.Errorf("Test %d should pass but got err: %v", i, err) continue } + if num != test.expected { t.Errorf("Test %d got unexpected value, want %d, got %d", i, test.expected, num) } @@ -103,19 +106,23 @@ func TestBlockNumberOrHash_UnmarshalJSON(t *testing.T) { for i, test := range tests { var bnh BlockNumberOrHash + err := json.Unmarshal([]byte(test.input), &bnh) if test.mustFail && err == nil { t.Errorf("Test %d should fail", i) continue } + if !test.mustFail && err != nil { t.Errorf("Test %d should pass but got err: %v", i, err) continue } + hash, hashOk := bnh.Hash() expectedHash, expectedHashOk := test.expected.Hash() num, numOk := bnh.Number() expectedNum, expectedNumOk := test.expected.Number() + if bnh.RequireCanonical != test.expected.RequireCanonical || hash != expectedHash || hashOk != expectedHashOk || num != expectedNum || numOk != expectedNumOk { @@ -138,15 +145,19 @@ func TestBlockNumberOrHash_WithNumber_MarshalAndUnmarshal(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { bnh := BlockNumberOrHashWithNumber(BlockNumber(test.number)) + marshalled, err := json.Marshal(bnh) if err != nil { t.Fatal("cannot marshal:", err) } + var unmarshalled BlockNumberOrHash + err = json.Unmarshal(marshalled, &unmarshalled) if err != nil { t.Fatal("cannot unmarshal:", err) } + if !reflect.DeepEqual(bnh, unmarshalled) { t.Fatalf("wrong result: expected %v, got %v", bnh, unmarshalled) } diff --git a/rpc/websocket.go b/rpc/websocket.go index 43fa140f9c..bfae9ae0e7 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -54,12 +54,14 @@ func (s *Server) WebsocketHandler(allowedOrigins []string) http.Handler { WriteBufferPool: wsBufferPool, CheckOrigin: wsHandshakeValidator(allowedOrigins), } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Debug("WebSocket upgrade failed", "err", err) return } + codec := newWebsocketCodec(conn, r.Host, r.Header) s.ServeCodec(codec, 0) }) @@ -76,6 +78,7 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool { if origin == "*" { allowAllOrigins = true } + if origin != "" { origins.Add(origin) } @@ -83,10 +86,12 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool { // 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://" + hostname) } } + log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v", origins.ToSlice())) f := func(req *http.Request) bool { @@ -102,7 +107,9 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool { if allowAllOrigins || originIsAllowed(origins, origin) { return true } + log.Warn("Rejected WebSocket connection", "origin", origin) + return false } @@ -119,6 +126,7 @@ func (e wsHandshakeError) Error() string { if e.status != "" { s += " (HTTP status " + e.status + ")" } + return s } @@ -129,6 +137,7 @@ func originIsAllowed(allowedOrigins mapset.Set[string], browserOrigin string) bo return true } } + return false } @@ -138,25 +147,31 @@ func ruleAllowsOrigin(allowedOrigin string, browserOrigin string) bool { 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 } @@ -165,6 +180,7 @@ func parseOriginURL(origin string) (string, string, string, error) { if err != nil { return "", "", "", err } + var scheme, hostname, port string if strings.Contains(origin, "://") { scheme = parsedURL.Scheme @@ -174,10 +190,12 @@ func parseOriginURL(origin string) (string, string, string, error) { scheme = "" hostname = parsedURL.Scheme port = parsedURL.Opaque + if hostname == "" { hostname = origin } } + return scheme, hostname, port, nil } @@ -274,15 +292,19 @@ func wsClientHeaders(endpoint, origin string) (string, http.Header, error) { if err != nil { return endpoint, nil, err } + header := make(http.Header) if origin != "" { header.Add("origin", origin) } + if endpointURL.User != nil { b64auth := base64.StdEncoding.EncodeToString([]byte(endpointURL.User.String())) header.Add("authorization", "Basic "+b64auth) + endpointURL.User = nil } + return endpointURL.String(), header, nil } @@ -321,6 +343,7 @@ func newWebsocketCodec(conn *websocket.Conn, host string, req http.Header) Serve // Start pinger. wc.wg.Add(1) go wc.pingLoop() + return wc } @@ -342,12 +365,14 @@ func (wc *websocketCodec) writeJSON(ctx context.Context, v interface{}, isError default: } } + return err } // pingLoop sends periodic ping frames when the connection is idle. func (wc *websocketCodec) pingLoop() { var timer = time.NewTimer(wsPingInterval) + defer wc.wg.Done() defer timer.Stop() @@ -359,6 +384,7 @@ func (wc *websocketCodec) pingLoop() { if !timer.Stop() { <-timer.C } + timer.Reset(wsPingInterval) case <-timer.C: wc.jsonCodec.encMu.Lock() diff --git a/rpc/websocket_test.go b/rpc/websocket_test.go index 2932137894..551022df40 100644 --- a/rpc/websocket_test.go +++ b/rpc/websocket_test.go @@ -36,12 +36,15 @@ func TestWebsocketClientHeaders(t *testing.T) { if err != nil { t.Fatalf("wsGetConfig failed: %s", err) } + if endpoint != "wss://example.com:1234" { t.Fatal("User should have been stripped from the URL") } + if header.Get("authorization") != "Basic dGVzdHVzZXI6dGVzdC1QQVNTXzAx" { t.Fatal("Basic auth header is incorrect") } + if header.Get("origin") != "https://example.com" { t.Fatal("Origin not set") } @@ -56,6 +59,7 @@ func TestWebsocketOriginCheck(t *testing.T) { httpsrv = httptest.NewServer(srv.WebsocketHandler([]string{"http://example.com"})) wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") ) + defer srv.Stop() defer httpsrv.Close() @@ -64,6 +68,7 @@ func TestWebsocketOriginCheck(t *testing.T) { client.Close() t.Fatal("no error for wrong origin") } + wantErr := wsHandshakeError{websocket.ErrBadHandshake, "403 Forbidden"} if !errors.Is(err, wantErr) { t.Fatalf("wrong error for wrong origin: %q", err) @@ -74,6 +79,7 @@ func TestWebsocketOriginCheck(t *testing.T) { if err != nil { t.Fatalf("error for empty origin: %v", err) } + client.Close() } @@ -86,6 +92,7 @@ func TestWebsocketLargeCall(t *testing.T) { httpsrv = httptest.NewServer(srv.WebsocketHandler([]string{"*"})) wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") ) + defer srv.Stop() defer httpsrv.Close() @@ -97,16 +104,19 @@ func TestWebsocketLargeCall(t *testing.T) { // This call sends slightly less than the limit and should work. var result echoResult + arg := strings.Repeat("x", maxRequestContentLength-200) if err := client.Call(&result, "test_echo", arg, 1); err != nil { t.Fatalf("valid call didn't work: %v", err) } + if result.String != arg { t.Fatal("wrong string echoed") } // This call sends twice the allowed size and shouldn't work. arg = strings.Repeat("x", maxRequestContentLength*2) + err = client.Call(&result, "test_echo", arg) if err == nil { t.Fatal("no error for too large call") @@ -119,10 +129,12 @@ func TestWebsocketPeerInfo(t *testing.T) { ts = httptest.NewServer(s.WebsocketHandler([]string{"origin.example.com"})) tsurl = "ws:" + strings.TrimPrefix(ts.URL, "http:") ) + defer s.Stop() defer ts.Close() ctx := context.Background() + c, err := DialWebsocket(ctx, tsurl, "origin.example.com") if err != nil { t.Fatal(err) @@ -137,12 +149,15 @@ func TestWebsocketPeerInfo(t *testing.T) { if connInfo.RemoteAddr == "" { t.Error("RemoteAddr not set") } + if connInfo.Transport != "ws" { t.Errorf("wrong Transport %q", connInfo.Transport) } + if connInfo.HTTP.UserAgent != "Go-http-client/1.1" { t.Errorf("wrong HTTP.UserAgent %q", connInfo.HTTP.UserAgent) } + if connInfo.HTTP.Origin != "origin.example.com" { t.Errorf("wrong HTTP.Origin %q", connInfo.HTTP.UserAgent) } @@ -157,6 +172,7 @@ func TestClientWebsocketPing(t *testing.T) { server = wsPingTestServer(t, sendPing) ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) ) + defer cancel() defer server.Shutdown(ctx) @@ -167,6 +183,7 @@ func TestClientWebsocketPing(t *testing.T) { defer client.Close() resultChan := make(chan int) + sub, err := client.EthSubscribe(ctx, resultChan, "foo") if err != nil { t.Fatalf("client subscribe error: %v", err) @@ -182,6 +199,7 @@ func TestClientWebsocketPing(t *testing.T) { // Wait for the subscription result. timeout := time.NewTimer(5 * time.Second) defer timeout.Stop() + for { select { case err := <-sub.Err(): @@ -203,6 +221,7 @@ func TestClientWebsocketLargeMessage(t *testing.T) { httpsrv = httptest.NewServer(srv.WebsocketHandler(nil)) wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") ) + defer srv.Stop() defer httpsrv.Close() @@ -218,6 +237,7 @@ func TestClientWebsocketLargeMessage(t *testing.T) { 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) } @@ -228,20 +248,25 @@ func TestClientWebsocketLargeMessage(t *testing.T) { // pong and finally delivers a single subscription result. func wsPingTestServer(t *testing.T, sendPing <-chan struct{}) *http.Server { var srv http.Server + shutdown := make(chan struct{}) + srv.RegisterOnShutdown(func() { close(shutdown) }) + srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Upgrade to WebSocket. upgrader := websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } + conn, err := upgrader.Upgrade(w, r, nil) if err != nil { t.Errorf("server WS upgrade error: %v", err) return } + defer conn.Close() // Handle the connection. @@ -253,8 +278,10 @@ func wsPingTestServer(t *testing.T, sendPing <-chan struct{}) *http.Server { if err != nil { t.Fatal("can't listen:", err) } + srv.Addr = listener.Addr().String() go srv.Serve(listener) + return &srv } @@ -270,6 +297,7 @@ func wsPingTestHandler(t *testing.T, conn *websocket.Conn, shutdown, sendPing <- t.Errorf("server read error: %v", err) return } + if err := conn.WriteMessage(websocket.TextMessage, []byte(subResp)); err != nil { t.Errorf("server write error: %v", err) return @@ -277,17 +305,21 @@ func wsPingTestHandler(t *testing.T, conn *websocket.Conn, shutdown, sendPing <- // Read from the connection to process control messages. var pongCh = make(chan string) + conn.SetPongHandler(func(d string) error { t.Logf("server got pong: %q", d) pongCh <- d + return nil }) + go func() { for { typ, msg, err := conn.ReadMessage() if err != nil { return } + t.Logf("server got message (%d): %q", typ, msg) } }() @@ -297,16 +329,20 @@ func wsPingTestHandler(t *testing.T, conn *websocket.Conn, shutdown, sendPing <- wantPong string timer = time.NewTimer(0) ) + defer timer.Stop() <-timer.C + for { select { case _, open := <-sendPing: if !open { sendPing = nil } + t.Logf("server sending ping") conn.WriteMessage(websocket.PingMessage, []byte("ping")) + wantPong = "ping" case data := <-pongCh: if wantPong == "" { @@ -314,7 +350,9 @@ func wsPingTestHandler(t *testing.T, conn *websocket.Conn, shutdown, sendPing <- } else if data != wantPong { t.Errorf("got pong with wrong data %q", data) } + wantPong = "" + timer.Reset(200 * time.Millisecond) case <-timer.C: t.Logf("server sending response") diff --git a/scripts/getconfig.go b/scripts/getconfig.go index f4870d14ab..0185588760 100644 --- a/scripts/getconfig.go +++ b/scripts/getconfig.go @@ -409,6 +409,7 @@ func getStaticTrustedNodes(args []string) { if !checkFileExists(path) { return } + writeTempStaticJSON(path) } } diff --git a/signer/core/api.go b/signer/core/api.go index 3c1c94801b..9dc0528afe 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -135,6 +135,7 @@ func StartClefAccountManager(ksLocation string, nousb, lightKDF bool, scpath str backends []accounts.Backend n, p = keystore.StandardScryptN, keystore.StandardScryptP ) + if lightKDF { n, p = keystore.LightScryptN, keystore.LightScryptP } @@ -142,12 +143,14 @@ func StartClefAccountManager(ksLocation string, nousb, lightKDF bool, scpath str if len(ksLocation) > 0 { backends = append(backends, keystore.NewKeyStore(ksLocation, n, p)) } + if !nousb { // 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)) } else { backends = append(backends, ledgerhub) + log.Debug("Ledger support enabled") } // Start a USB hub for Trezor hardware wallets (HID version) @@ -155,6 +158,7 @@ func StartClefAccountManager(ksLocation string, nousb, lightKDF bool, scpath str log.Warn(fmt.Sprintf("Failed to start HID Trezor hub, disabling: %v", err)) } else { backends = append(backends, trezorhub) + log.Debug("Trezor support enabled via HID") } // Start a USB hub for Trezor hardware wallets (WebUSB version) @@ -162,6 +166,7 @@ func StartClefAccountManager(ksLocation string, nousb, lightKDF bool, scpath str log.Warn(fmt.Sprintf("Failed to start WebUSB Trezor hub, disabling: %v", err)) } else { backends = append(backends, trezorhub) + log.Debug("Trezor support enabled via WebUSB") } } @@ -199,16 +204,21 @@ func MetadataFromContext(ctx context.Context) Metadata { if info.Transport == "http" { m.Scheme = info.HTTP.Version } + m.Scheme = info.Transport } + if info.RemoteAddr != "" { m.Remote = info.RemoteAddr } + if info.HTTP.Host != "" { m.Local = info.HTTP.Host } + m.Origin = info.HTTP.Origin m.UserAgent = info.HTTP.UserAgent + return m } @@ -218,6 +228,7 @@ func (m Metadata) String() string { if err == nil { return string(s) } + return err.Error() } @@ -287,10 +298,12 @@ func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAP if advancedMode { log.Info("Clef is in advanced mode: will warn instead of reject") } + signer := &SignerAPI{big.NewInt(chainID), am, ui, validator, !advancedMode, credentials} if !noUSB { signer.startUSBListener() } + return signer } func (api *SignerAPI) openTrezor(url accounts.URL) { @@ -316,6 +329,7 @@ func (api *SignerAPI) openTrezor(url accounts.URL) { log.Warn("wallet unavailable", "url", url) return } + err = w.Open(resp.Text) if err != nil { log.Warn("failed to open wallet", "wallet", url, "err", err) @@ -332,11 +346,13 @@ func (api *SignerAPI) startUSBListener() { 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) } @@ -348,6 +364,7 @@ func (api *SignerAPI) derivationLoop(events chan accounts.WalletEvent) { 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()) } @@ -355,6 +372,7 @@ func (api *SignerAPI) derivationLoop(events chan accounts.WalletEvent) { 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++ { @@ -366,8 +384,10 @@ func (api *SignerAPI) derivationLoop(events chan accounts.WalletEvent) { } } } + 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)) @@ -395,17 +415,21 @@ func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) { for _, wallet := range api.am.Wallets() { accs = append(accs, wallet.Accounts()...) } + result, err := api.UI.ApproveListing(&ListRequest{Accounts: accs, Meta: MetadataFromContext(ctx)}) if err != nil { return nil, err } + if result.Accounts == nil { return nil, ErrRequestDenied } + addresses := make([]common.Address, 0) for _, acc := range result.Accounts { addresses = append(addresses, acc.Address) } + return addresses, nil } @@ -416,11 +440,13 @@ func (api *SignerAPI) New(ctx context.Context) (common.Address, error) { if be := api.am.Backends(keystore.KeyStoreType); len(be) == 0 { return common.Address{}, errors.New("password based accounts not supported") } + if resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil { return common.Address{}, err } else if !resp.Approved { return common.Address{}, ErrRequestDenied } + return api.newAccount() } @@ -441,6 +467,7 @@ func (api *SignerAPI) newAccount() (common.Address, error) { log.Warn("error obtaining password", "attempt", i, "error", err) 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)) } else { @@ -449,6 +476,7 @@ func (api *SignerAPI) newAccount() (common.Address, error) { log.Info("Your new key was generated", "address", acc.Address) log.Warn("Please backup your key file!", "path", acc.URL.Path) log.Warn("Please remember your password!") + return acc.Address, err } } @@ -471,52 +499,74 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool { } modified := false + if f0, f1 := original.Transaction.From, new.Transaction.From; !reflect.DeepEqual(f0, f1) { log.Info("Sender-account changed by UI", "was", f0, "is", f1) + modified = true } + if t0, t1 := original.Transaction.To, new.Transaction.To; !reflect.DeepEqual(t0, t1) { log.Info("Recipient-account changed by UI", "was", t0, "is", t1) + modified = true } + if g0, g1 := original.Transaction.Gas, new.Transaction.Gas; g0 != g1 { modified = true + log.Info("Gas changed by UI", "was", g0, "is", g1) } + if a, b := original.Transaction.GasPrice, new.Transaction.GasPrice; intPtrModified(a, b) { log.Info("GasPrice changed by UI", "was", a, "is", b) + modified = true } + if a, b := original.Transaction.MaxPriorityFeePerGas, new.Transaction.MaxPriorityFeePerGas; intPtrModified(a, b) { log.Info("maxPriorityFeePerGas changed by UI", "was", a, "is", b) + modified = true } + if a, b := original.Transaction.MaxFeePerGas, new.Transaction.MaxFeePerGas; intPtrModified(a, b) { log.Info("maxFeePerGas changed by UI", "was", a, "is", b) + modified = true } + if v0, v1 := big.Int(original.Transaction.Value), big.Int(new.Transaction.Value); v0.Cmp(&v1) != 0 { modified = true + log.Info("Value changed by UI", "was", v0, "is", v1) } + if d0, d1 := original.Transaction.Data, new.Transaction.Data; d0 != d1 { d0s := "" d1s := "" + if d0 != nil { d0s = hexutil.Encode(*d0) } + if d1 != nil { d1s = hexutil.Encode(*d1) } + if d1s != d0s { modified = true + log.Info("Data changed by UI", "was", d0s, "is", d1s) } } + if n0, n1 := original.Transaction.Nonce, new.Transaction.Nonce; n0 != n1 { modified = true + log.Info("Nonce changed by UI", "was", n0, "is", n1) } + return modified } @@ -537,6 +587,7 @@ func (api *SignerAPI) lookupOrQueryPassword(address common.Address, title, promp // which could leak the password if it was malformed json or something return "", errors.New("internal error") } + return pwResp.Text, nil } @@ -546,6 +597,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxA err error result SignTxResponse ) + msgs, err := api.validator.ValidateTransaction(methodSelector, &args) if err != nil { return nil, err @@ -556,6 +608,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxA return nil, err } } + if args.ChainID != nil { requestedChainId := (*big.Int)(args.ChainID) if api.chainID.Cmp(requestedChainId) != 0 { @@ -564,6 +617,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxA requestedChainId) } } + req := SignTxRequest{ Transaction: args, Meta: MetadataFromContext(ctx), @@ -574,16 +628,20 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxA if err != nil { return nil, err } + if !result.Approved { return nil, ErrRequestDenied } // Log changes made by the UI to the signing-request logDiff(&req, &result) + var ( acc accounts.Account wallet accounts.Wallet ) + acc = accounts.Account{Address: result.Transaction.From.Address()} + wallet, err = api.am.Find(acc) if err != nil { return nil, err @@ -607,6 +665,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxA if err != nil { return nil, err } + response := ethapi.SignTransactionResult{Raw: data, Tx: signedTx} // Finally, send the signed tx to the UI @@ -618,6 +677,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxA func (api *SignerAPI) SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) { // Do the usual validations, but on the last-stage transaction args := gnosisTx.ArgsForValidation() + msgs, err := api.validator.ValidateTransaction(methodSelector, args) if err != nil { return nil, err @@ -628,6 +688,7 @@ func (api *SignerAPI) SignGnosisSafeTx(ctx context.Context, signerAddress common return nil, err } } + typedData := gnosisTx.ToTypedData() // might aswell error early. // we are expected to sign. If our calculated hash does not match what they want, @@ -636,22 +697,26 @@ func (api *SignerAPI) SignGnosisSafeTx(ctx context.Context, signerAddress common if err != nil { return nil, err } + if !bytes.Equal(sighash, gnosisTx.InputExpHash.Bytes()) { // It might be the case that the json is missing chain id. if gnosisTx.ChainId == nil { gnosisTx.ChainId = (*math.HexOrDecimal256)(api.chainID) typedData = gnosisTx.ToTypedData() + sighash, _, _ = apitypes.TypedDataAndHash(typedData) if !bytes.Equal(sighash, gnosisTx.InputExpHash.Bytes()) { return nil, fmt.Errorf("mismatched safeTxHash; have %#x want %#x", sighash, gnosisTx.InputExpHash[:]) } } } + signature, preimage, err := api.signTypedData(ctx, signerAddress, typedData, msgs) if err != nil { return nil, err } + checkSummedSender, _ := common.NewMixedcaseAddressFromString(signerAddress.Address().Hex()) gnosisTx.Signature = signature diff --git a/signer/core/api_test.go b/signer/core/api_test.go index 9bb55bddca..a51f6b9607 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -63,6 +63,7 @@ func (ui *headlessUi) ApproveTx(request *core.SignTxRequest) (core.SignTxRespons old := big.Int(request.Transaction.Value) newVal := new(big.Int).Add(&old, big.NewInt(1)) request.Transaction.Value = hexutil.Big(*newVal) + return core.SignTxResponse{request.Transaction, true}, nil default: return core.SignTxResponse{request.Transaction, false}, nil @@ -83,6 +84,7 @@ func (ui *headlessUi) ApproveListing(request *core.ListRequest) (core.ListRespon case "1": l := make([]accounts.Account, 1) l[0] = request.Accounts[1] + return core.ListResponse{l}, nil default: return core.ListResponse{nil}, nil @@ -93,6 +95,7 @@ func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.N if <-ui.approveCh == "Y" { return core.NewAccountResponse{true}, nil } + return core.NewAccountResponse{false}, nil } @@ -108,10 +111,12 @@ func (ui *headlessUi) ShowInfo(message string) { func tmpDirName(t *testing.T) string { d := t.TempDir() + d, err := filepath.EvalSymlinks(d) if err != nil { t.Fatal(err) } + return d } @@ -120,14 +125,17 @@ func setup(t *testing.T) (*core.SignerAPI, *headlessUi) { if err != nil { t.Fatal(err.Error()) } + ui := &headlessUi{make(chan string, 20), make(chan string, 20)} am := core.StartClefAccountManager(tmpDirName(t), true, true, "") api := core.NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{}) + return api, ui } func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { ui.approveCh <- "Y" ui.inputCh <- "a_long_password" + _, err := api.New(context.Background()) if err != nil { t.Fatal(err) @@ -147,6 +155,7 @@ func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password if err == nil { t.Fatal("Should have returned an error") } + if addr != (common.Address{}) { t.Fatal("Empty address should be returned") } @@ -154,10 +163,12 @@ func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { ui.approveCh <- "N" + addr, err := api.New(context.Background()) if err != core.ErrRequestDenied { t.Fatal(err) } + if addr != (common.Address{}) { t.Fatal("Empty address should be returned") } @@ -175,6 +186,7 @@ func TestNewAcc(t *testing.T) { if err != nil { t.Errorf("Unexpected error %v", err) } + if len(list) != num { t.Errorf("Expected %d accounts, got %d", num, len(list)) } @@ -198,19 +210,23 @@ func TestNewAcc(t *testing.T) { // Testing listing: // Listing one Account control.approveCh <- "1" + list, err := api.List(context.Background()) if err != nil { t.Fatal(err) } + if len(list) != 1 { t.Fatalf("List should only show one Account") } // Listing denied control.approveCh <- "Nope" + list, err = api.List(context.Background()) if len(list) != 0 { t.Fatalf("List should be empty") } + if err != core.ErrRequestDenied { t.Fatal("Expected deny") } @@ -231,6 +247,7 @@ func mkTestTx(from common.MixedcaseAddress) apitypes.SendTxArgs { Value: value, Data: &data, Nonce: nonce} + return tx } @@ -244,13 +261,16 @@ func TestSignTx(t *testing.T) { api, control := setup(t) createAccount(control, api, t) control.approveCh <- "A" + list, err = api.List(context.Background()) if err != nil { t.Fatal(err) } + if len(list) == 0 { t.Fatal("Unexpected empty list") } + a := common.NewMixedcaseAddress(list[0]) methodSig := "test(uint)" @@ -258,29 +278,35 @@ func TestSignTx(t *testing.T) { control.approveCh <- "Y" control.inputCh <- "wrongpassword" + res, err = api.SignTransaction(context.Background(), tx, &methodSig) if res != nil { t.Errorf("Expected nil-response, got %v", res) } + if err != keystore.ErrDecrypt { t.Errorf("Expected ErrLocked! %v", err) } control.approveCh <- "No way" + res, err = api.SignTransaction(context.Background(), tx, &methodSig) if res != nil { t.Errorf("Expected nil-response, got %v", res) } + if err != core.ErrRequestDenied { t.Errorf("Expected ErrRequestDenied! %v", err) } // Sign with correct password control.approveCh <- "Y" control.inputCh <- "a_long_password" + res, err = api.SignTransaction(context.Background(), tx, &methodSig) if err != nil { t.Fatal(err) } + parsedTx := &types.Transaction{} rlp.Decode(bytes.NewReader(res.Raw), parsedTx) @@ -295,6 +321,7 @@ func TestSignTx(t *testing.T) { if err != nil { t.Fatal(err) } + if !bytes.Equal(res.Raw, res2.Raw) { t.Error("Expected tx to be unmodified by UI") } @@ -307,6 +334,7 @@ func TestSignTx(t *testing.T) { if err != nil { t.Fatal(err) } + parsedTx2 := &types.Transaction{} rlp.Decode(bytes.NewReader(res.Raw), parsedTx2) @@ -314,6 +342,7 @@ func TestSignTx(t *testing.T) { if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value()) } + if bytes.Equal(res.Raw, res2.Raw) { t.Error("Expected tx to be modified by UI") } diff --git a/signer/core/apitypes/signed_data_internal_test.go b/signer/core/apitypes/signed_data_internal_test.go index 340ef40ad8..745e2564a8 100644 --- a/signer/core/apitypes/signed_data_internal_test.go +++ b/signer/core/apitypes/signed_data_internal_test.go @@ -76,9 +76,11 @@ func TestBytesPadding(t *testing.T) { if err != nil { t.Errorf("test %d: expected no error, got %v", i, err) } + if len(val) != 32 { t.Errorf("test %d: expected len 32, got %d", i, len(val)) } + if !bytes.Equal(val, test.Output) { t.Errorf("test %d: expected %x, got %x", i, test.Output, val) } @@ -164,11 +166,14 @@ func TestParseBytes(t *testing.T) { if ok || out != nil { t.Errorf("test %d: expected !ok, got ok = %v with out = %x", i, ok, out) } + continue } + if !ok { t.Errorf("test %d: expected ok got !ok", i) } + if !bytes.Equal(out, tt.exp) { t.Errorf("test %d: expected %x got %x", i, tt.exp, out) } @@ -191,14 +196,17 @@ func TestParseInteger(t *testing.T) { if tt.exp == nil && res == nil { continue } + if tt.exp == nil && res != nil { t.Errorf("test %d, got %v, expected nil", i, res) continue } + if tt.exp != nil && res == nil { t.Errorf("test %d, got '%v', expected %v", i, err, tt.exp) continue } + if tt.exp.Cmp(res) != 0 { t.Errorf("test %d, got %v expected %v", i, res, tt.exp) } diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go index 9963df6be7..aea29918dd 100644 --- a/signer/core/apitypes/types.go +++ b/signer/core/apitypes/types.go @@ -65,14 +65,17 @@ func (vs *ValidationMessages) Info(msg string) { // getWarnings returns an error with all messages of type WARN of above, or nil if no warnings were present func (v *ValidationMessages) GetWarnings() error { var messages []string + for _, msg := range v.Messages { if msg.Typ == WARN || msg.Typ == CRIT { messages = append(messages, msg.Message) } } + if len(messages) > 0 { return fmt.Errorf("validation failed: %s", strings.Join(messages, ",")) } + return nil } @@ -105,6 +108,7 @@ func (args SendTxArgs) String() string { if err == nil { return string(s) } + return err.Error() } @@ -112,6 +116,7 @@ func (args SendTxArgs) String() string { func (args *SendTxArgs) ToTransaction() *types.Transaction { // Add the To-field, if specified var to *common.Address + if args.To != nil { dstAddr := args.To.Address() to = &dstAddr @@ -125,12 +130,14 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction { } var data types.TxData + switch { case args.MaxFeePerGas != nil: al := types.AccessList{} if args.AccessList != nil { al = *args.AccessList } + data = &types.DynamicFeeTx{ To: to, ChainID: (*big.Int)(args.ChainID), @@ -163,6 +170,7 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction { Data: input, } } + return types.NewTx(data) } @@ -219,6 +227,7 @@ func (t *Type) typeName() string { if strings.HasSuffix(t.Type, "[]") { return strings.TrimSuffix(t.Type, "[]") } + return t.Type } @@ -269,6 +278,7 @@ func (typedData *TypedData) HashStruct(primaryType string, data TypedDataMessage if err != nil { return nil, err } + return crypto.Keccak256(encodedData), nil } @@ -281,15 +291,18 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s return true } } + return false } if includes(found, primaryType) { return found } + if typedData.Types[primaryType] == nil { return found } + found = append(found, primaryType) for _, field := range typedData.Types[primaryType] { for _, dep := range typedData.Dependencies(field.Type, found) { @@ -298,6 +311,7 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s } } } + return found } @@ -319,15 +333,18 @@ func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { for _, dep := range deps { buffer.WriteString(dep) buffer.WriteString("(") + for _, obj := range typedData.Types[dep] { buffer.WriteString(obj.Type) buffer.WriteString(" ") buffer.WriteString(obj.Name) buffer.WriteString(",") } + buffer.Truncate(buffer.Len() - 1) buffer.WriteString(")") } + return buffer.Bytes() } @@ -359,6 +376,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter for _, field := range typedData.Types[primaryType] { encType := field.Type encValue := data[field.Name] + if encType[len(encType)-1:] == "]" { arrayValue, err := convertDataToSlice(encValue) if err != nil { @@ -366,6 +384,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } arrayBuffer := bytes.Buffer{} + parsedType := strings.Split(encType, "[")[0] for _, item := range arrayValue { if typedData.Types[parsedType] != nil { @@ -373,16 +392,19 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter if !ok { return nil, dataMismatchError(parsedType, item) } + encodedData, err := typedData.EncodeData(parsedType, mapValue, depth+1) if err != nil { return nil, err } + arrayBuffer.Write(crypto.Keccak256(encodedData)) } else { bytesValue, err := typedData.EncodePrimitiveValue(parsedType, item, depth) if err != nil { return nil, err } + arrayBuffer.Write(bytesValue) } } @@ -393,19 +415,23 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter if !ok { return nil, dataMismatchError(encType, encValue) } + encodedData, err := typedData.EncodeData(field.Type, mapValue, depth+1) if err != nil { return nil, err } + buffer.Write(crypto.Keccak256(encodedData)) } else { byteValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } + buffer.Write(byteValue) } } + return buffer.Bytes(), nil } @@ -430,6 +456,7 @@ func parseBytes(encType interface{}) ([]byte, bool) { if err != nil { return nil, false } + return bytes, true default: return nil, false @@ -442,6 +469,7 @@ func parseInteger(encType string, encValue interface{}) (*big.Int, error) { signed = strings.HasPrefix(encType, "int") b *big.Int ) + if encType == "int" || encType == "uint" { length = 256 } else { @@ -451,12 +479,15 @@ func parseInteger(encType string, encValue interface{}) (*big.Int, error) { } else { lengthStr = strings.TrimPrefix(encType, "int") } + atoiSize, err := strconv.Atoi(lengthStr) if err != nil { return nil, fmt.Errorf("invalid size on integer: %v", lengthStr) } + length = atoiSize } + switch v := encValue.(type) { case *math.HexOrDecimal256: b = (*big.Int)(v) @@ -467,6 +498,7 @@ func parseInteger(encType string, encValue interface{}) (*big.Int, error) { if err := hexIntValue.UnmarshalText([]byte(v)); err != nil { return nil, err } + b = (*big.Int)(&hexIntValue) case float64: // JSON parses non-strings as float64. Fail if we cannot @@ -477,15 +509,19 @@ func parseInteger(encType string, encValue interface{}) (*big.Int, error) { return nil, fmt.Errorf("invalid float value %v for type %v", v, encType) } } + if b == nil { return nil, fmt.Errorf("invalid integer value %v/%v for type %v", encValue, reflect.TypeOf(encValue), encType) } + if b.BitLen() > length { return nil, fmt.Errorf("integer larger than '%v'", encType) } + if !signed && b.Sign() == -1 { return nil, fmt.Errorf("invalid negative value for unsigned type %v", encType) } + return b, nil } @@ -518,48 +554,60 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf if !ok { return nil, dataMismatchError(encType, encValue) } + if boolValue { return math.PaddedBigBytes(common.Big1, 32), nil } + return math.PaddedBigBytes(common.Big0, 32), nil case "string": strVal, ok := encValue.(string) if !ok { return nil, dataMismatchError(encType, encValue) } + return crypto.Keccak256([]byte(strVal)), nil case "bytes": bytesValue, ok := parseBytes(encValue) if !ok { return nil, dataMismatchError(encType, encValue) } + return crypto.Keccak256(bytesValue), nil } + if strings.HasPrefix(encType, "bytes") { lengthStr := strings.TrimPrefix(encType, "bytes") length, err := strconv.Atoi(lengthStr) + if err != nil { return nil, fmt.Errorf("invalid size on bytes: %v", lengthStr) } + if length < 0 || length > 32 { return nil, fmt.Errorf("invalid size on bytes: %d", length) } + if byteValue, ok := parseBytes(encValue); !ok || len(byteValue) != length { return nil, dataMismatchError(encType, encValue) } else { // Right-pad the bits dst := make([]byte, 32) copy(dst, byteValue) + return dst, nil } } + if strings.HasPrefix(encType, "int") || strings.HasPrefix(encType, "uint") { b, err := parseInteger(encType, encValue) if err != nil { return nil, err } + return math.U256Bytes(b), nil } + return nil, fmt.Errorf("unrecognized type '%s'", encType) } @@ -590,9 +638,11 @@ func (typedData *TypedData) validate() error { if err := typedData.Types.validate(); err != nil { return err } + if err := typedData.Domain.validate(); err != nil { return err } + return nil } @@ -604,6 +654,7 @@ func (typedData *TypedData) Map() map[string]interface{} { "primaryType": typedData.PrimaryType, "message": typedData.Message, } + return dataMap } @@ -614,10 +665,12 @@ func (typedData *TypedData) Format() ([]*NameValueType, error) { if err != nil { return nil, err } + ptype, err := typedData.formatData(typedData.PrimaryType, typedData.Message) if err != nil { return nil, err } + var nvts []*NameValueType nvts = append(nvts, &NameValueType{ Name: "EIP712Domain", @@ -629,6 +682,7 @@ func (typedData *TypedData) Format() ([]*NameValueType, error) { Value: ptype, Typ: "primary type", }) + return nvts, nil } @@ -643,22 +697,27 @@ func (typedData *TypedData) formatData(primaryType string, data map[string]inter Name: encName, Typ: field.Type, } + if field.isArray() { arrayValue, _ := convertDataToSlice(encValue) parsedType := field.typeName() + for _, v := range arrayValue { if typedData.Types[parsedType] != nil { mapValue, _ := v.(map[string]interface{}) + mapOutput, err := typedData.formatData(parsedType, mapValue) if err != nil { return nil, err } + item.Value = mapOutput } else { primitiveOutput, err := formatPrimitiveValue(field.Type, encValue) if err != nil { return nil, err } + item.Value = primitiveOutput } } @@ -668,6 +727,7 @@ func (typedData *TypedData) formatData(primaryType string, data map[string]inter if err != nil { return nil, err } + item.Value = mapOutput } else { item.Value = "" @@ -677,10 +737,13 @@ func (typedData *TypedData) formatData(primaryType string, data map[string]inter if err != nil { return nil, err } + item.Value = primitiveOutput } + output = append(output, item) } + return output, nil } @@ -701,9 +764,11 @@ func formatPrimitiveValue(encType string, encValue interface{}) (string, error) case "bytes", "string": return fmt.Sprintf("%s", encValue), nil } + if strings.HasPrefix(encType, "bytes") { return fmt.Sprintf("%s", encValue), nil } + if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { if b, err := parseInteger(encType, encValue); err != nil { return "", err @@ -711,6 +776,7 @@ func formatPrimitiveValue(encType string, encValue interface{}) (string, error) return fmt.Sprintf("%d (%#x)", b, b), nil } } + return "", fmt.Errorf("unhandled type %v", encType) } @@ -720,13 +786,16 @@ func (t Types) validate() error { if len(typeKey) == 0 { return fmt.Errorf("empty type key") } + for i, typeObj := range typeArr { if len(typeObj.Type) == 0 { return fmt.Errorf("type %q:%d: empty Type", typeKey, i) } + if len(typeObj.Name) == 0 { return fmt.Errorf("type %q:%d: empty Name", typeKey, i) } + if typeKey == typeObj.Type { return fmt.Errorf("type %q cannot reference itself", typeObj.Type) } @@ -744,6 +813,7 @@ func (t Types) validate() error { } } } + return nil } @@ -780,6 +850,7 @@ func isPrimitiveTypeValid(primitiveType string) bool { return true } } + return false } @@ -816,6 +887,7 @@ func (domain *TypedDataDomain) Map() map[string]interface{} { if len(domain.Salt) > 0 { dataMap["salt"] = domain.Salt } + return dataMap } @@ -832,8 +904,10 @@ func (nvt *NameValueType) Pprint(depth int) string { output := bytes.Buffer{} output.WriteString(strings.Repeat("\u00a0", depth*2)) output.WriteString(fmt.Sprintf("%s [%s]: ", nvt.Name, nvt.Typ)) + if nvts, ok := nvt.Value.([]*NameValueType); ok { output.WriteString("\n") + for _, next := range nvts { sublevel := next.Pprint(depth + 1) output.WriteString(sublevel) @@ -845,5 +919,6 @@ func (nvt *NameValueType) Pprint(depth int) string { output.WriteString("\n") } } + return output.String() } diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index a0b292bf71..48a803bd17 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -49,6 +49,7 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args apitypes.SendTxA if methodSelector != nil { sel = *methodSelector } + l.log.Info("SignTransaction", "type", "request", "metadata", MetadataFromContext(ctx).String(), "tx", args.String(), "methodSelector", sel) @@ -59,6 +60,7 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args apitypes.SendTxA } else { l.log.Info("SignTransaction", "type", "response", "data", res, "error", e) } + return res, e } @@ -66,8 +68,10 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com marshalledData, _ := json.Marshal(data) // can ignore error, marshalling what we just unmarshalled l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(), "addr", addr.String(), "data", marshalledData, "content-type", contentType) + b, e := l.api.SignData(ctx, contentType, addr, data) l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e) + return b, e } @@ -76,9 +80,11 @@ func (l *AuditLogger) SignGnosisSafeTx(ctx context.Context, addr common.Mixedcas if methodSelector != nil { sel = *methodSelector } + data, _ := json.Marshal(gnosisTx) // can ignore error, marshalling what we just unmarshalled l.log.Info("SignGnosisSafeTx", "type", "request", "metadata", MetadataFromContext(ctx).String(), "addr", addr.String(), "data", string(data), "selector", sel) + res, e := l.api.SignGnosisSafeTx(ctx, addr, gnosisTx, methodSelector) if res != nil { data, _ := json.Marshal(res) // can ignore error, marshalling what we just unmarshalled @@ -86,22 +92,27 @@ func (l *AuditLogger) SignGnosisSafeTx(ctx context.Context, addr common.Mixedcas } else { l.log.Info("SignGnosisSafeTx", "type", "response", "data", res, "error", e) } + return res, e } func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data apitypes.TypedData) (hexutil.Bytes, error) { l.log.Info("SignTypedData", "type", "request", "metadata", MetadataFromContext(ctx).String(), "addr", addr.String(), "data", data) + b, e := l.api.SignTypedData(ctx, addr, data) l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) + return b, e } func (l *AuditLogger) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(), "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig)) + b, e := l.api.EcRecover(ctx, data, sig) l.log.Info("EcRecover", "type", "response", "address", b.String(), "error", e) + return b, e } @@ -109,16 +120,20 @@ func (l *AuditLogger) Version(ctx context.Context) (string, error) { l.log.Info("Version", "type", "request", "metadata", MetadataFromContext(ctx).String()) data, err := l.api.Version(ctx) l.log.Info("Version", "type", "response", "data", data, "error", err) + return data, err } func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) { l := log.New("api", "signer") + handler, err := log.FileHandler(path, log.LogfmtFormat()) if err != nil { return nil, err } + l.SetHandler(handler) l.Info("Configured", "audit log", path) + return &AuditLogger{l, api}, nil } diff --git a/signer/core/cliui.go b/signer/core/cliui.go index b1bd3206ed..b242dd8568 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -50,10 +50,12 @@ func (ui *CommandlineUI) RegisterUIServer(api *UIServerAPI) { func (ui *CommandlineUI) readString() string { for { fmt.Printf("> ") + text, err := ui.in.ReadString('\n') if err != nil { log.Crit("Failed to read user input", "err", err) } + if text = strings.TrimSpace(text); text != "" { return text } @@ -63,25 +65,32 @@ func (ui *CommandlineUI) readString() string { func (ui *CommandlineUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) { fmt.Printf("## %s\n\n%s\n", info.Title, info.Prompt) defer fmt.Println("-----------------------") + if info.IsPassword { text, err := prompt.Stdin.PromptPassword("> ") if err != nil { log.Error("Failed to read password", "error", err) return UserInputResponse{}, err } + return UserInputResponse{text}, nil } + text := ui.readString() + return UserInputResponse{text}, nil } // confirm returns true if user enters 'Yes', otherwise false func (ui *CommandlineUI) confirm() bool { fmt.Printf("Approve? [y/N]:\n") + if ui.readString() == "y" { return true } + fmt.Println("-----------------------") + return false } @@ -91,6 +100,7 @@ func sanitize(txt string, limit int) string { if len(txt) > limit { return fmt.Sprintf("%q...", txt[:limit]) } + return fmt.Sprintf("%q", txt) } @@ -104,57 +114,75 @@ func showMetadata(metadata Metadata) { func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { ui.mu.Lock() defer ui.mu.Unlock() + weival := request.Transaction.Value.ToInt() + fmt.Printf("--------- Transaction request-------------\n") + if to := request.Transaction.To; to != nil { fmt.Printf("to: %v\n", to.Original()) + if !to.ValidChecksum() { fmt.Printf("\nWARNING: Invalid checksum on to-address!\n\n") } } else { fmt.Printf("to: \n") } + fmt.Printf("from: %v\n", request.Transaction.From.String()) fmt.Printf("value: %v wei\n", weival) fmt.Printf("gas: %v (%v)\n", request.Transaction.Gas, uint64(request.Transaction.Gas)) + if request.Transaction.MaxFeePerGas != nil { fmt.Printf("maxFeePerGas: %v wei\n", request.Transaction.MaxFeePerGas.ToInt()) fmt.Printf("maxPriorityFeePerGas: %v wei\n", request.Transaction.MaxPriorityFeePerGas.ToInt()) } else { fmt.Printf("gasprice: %v wei\n", request.Transaction.GasPrice.ToInt()) } + fmt.Printf("nonce: %v (%v)\n", request.Transaction.Nonce, uint64(request.Transaction.Nonce)) + if chainId := request.Transaction.ChainID; chainId != nil { fmt.Printf("chainid: %v\n", chainId) } + if list := request.Transaction.AccessList; list != nil { fmt.Printf("Accesslist\n") + for i, el := range *list { fmt.Printf(" %d. %v\n", i, el.Address) + for j, slot := range el.StorageKeys { fmt.Printf(" %d. %v\n", j, slot) } } } + if request.Transaction.Data != nil { d := *request.Transaction.Data if len(d) > 0 { fmt.Printf("data: %v\n", hexutil.Encode(d)) } } + if request.Callinfo != nil { fmt.Printf("\nTransaction validation:\n") + for _, m := range request.Callinfo { fmt.Printf(" * %s : %s\n", m.Typ, m.Message) } + fmt.Println() } + fmt.Printf("\n") showMetadata(request.Meta) fmt.Printf("-------------------------------------------\n") + if !ui.confirm() { return SignTxResponse{request.Transaction, false}, nil } + return SignTxResponse{request.Transaction, true}, nil } @@ -165,24 +193,32 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("Account: %s\n", request.Address.String()) + if len(request.Callinfo) != 0 { fmt.Printf("\nValidation messages:\n") + for _, m := range request.Callinfo { fmt.Printf(" * %s : %s\n", m.Typ, m.Message) } + fmt.Println() } + fmt.Printf("messages:\n") + for _, nvt := range request.Messages { fmt.Printf("\u00a0\u00a0%v\n", strings.TrimSpace(nvt.Pprint(1))) } + fmt.Printf("raw data: \n\t%q\n", request.Rawdata) fmt.Printf("data hash: %v\n", request.Hash) fmt.Printf("-------------------------------------------\n") showMetadata(request.Meta) + if !ui.confirm() { return SignDataResponse{false}, nil } + return SignDataResponse{true}, nil } @@ -195,15 +231,19 @@ func (ui *CommandlineUI) ApproveListing(request *ListRequest) (ListResponse, err fmt.Printf("-------- List Account request--------------\n") fmt.Printf("A request has been made to list all accounts. \n") fmt.Printf("You can select which accounts the caller can see\n") + for _, account := range request.Accounts { fmt.Printf(" [x] %v\n", account.Address.Hex()) fmt.Printf(" URL: %v\n", account.URL) } + fmt.Printf("-------------------------------------------\n") showMetadata(request.Meta) + if !ui.confirm() { return ListResponse{nil}, nil } + return ListResponse{request.Accounts}, nil } @@ -217,9 +257,11 @@ func (ui *CommandlineUI) ApproveNewAccount(request *NewAccountRequest) (NewAccou fmt.Printf("Approving this operation means that a new account is created,\n") fmt.Printf("and the address is returned to the external caller\n\n") showMetadata(request.Meta) + if !ui.confirm() { return NewAccountResponse{false}, nil } + return NewAccountResponse{true}, nil } @@ -236,6 +278,7 @@ func (ui *CommandlineUI) ShowInfo(message string) { func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) { fmt.Printf("Transaction signed:\n ") + if jsn, err := json.MarshalIndent(tx.Tx, " ", " "); err != nil { fmt.Printf("WARN: marshalling error %v\n", err) } else { @@ -249,27 +292,36 @@ func (ui *CommandlineUI) showAccounts() { log.Error("Error listing accounts", "err", err) return } + if len(accounts) == 0 { fmt.Print("No accounts found\n") return } + var msg string + var out = new(strings.Builder) + if limit := 20; len(accounts) > limit { msg = fmt.Sprintf("\nFirst %d accounts listed (%d more available).\n", limit, len(accounts)-limit) accounts = accounts[:limit] } + fmt.Fprint(out, "\n------- Available accounts -------\n") + for i, account := range accounts { fmt.Fprintf(out, "%d. %s at %s\n", i, account.Address, account.URL) } + fmt.Print(out.String(), msg) } func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) { fmt.Print("\n------- Signer info -------\n") + for k, v := range info.Info { fmt.Printf("* %v : %v\n", k, v) } + go ui.showAccounts() } diff --git a/signer/core/gnosis_safe.go b/signer/core/gnosis_safe.go index 01724e5383..0f1e418f40 100644 --- a/signer/core/gnosis_safe.go +++ b/signer/core/gnosis_safe.go @@ -56,6 +56,7 @@ func (tx *GnosisSafeTx) ToTypedData() apitypes.TypedData { if tx.Data != nil { data = *tx.Data } + var domainType = []apitypes.Type{{Name: "verifyingContract", Type: "address"}} if tx.ChainId != nil { domainType = append([]apitypes.Type{{Name: "chainId", Type: "uint256"}}, domainType[0]) @@ -95,6 +96,7 @@ func (tx *GnosisSafeTx) ToTypedData() apitypes.TypedData { "nonce": fmt.Sprintf("%d", tx.Nonce.Uint64()), }, } + return gnosisTypedData } @@ -113,5 +115,6 @@ func (tx *GnosisSafeTx) ArgsForValidation() *apitypes.SendTxArgs { Input: nil, ChainID: (*hexutil.Big)(tx.ChainId), } + return args } diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index f6ac3a52e4..b86a5ce0b0 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -44,15 +44,18 @@ func (api *SignerAPI) sign(req *SignDataRequest, legacyV bool) (hexutil.Bytes, e if err != nil { return nil, err } + if !res.Approved { return nil, ErrRequestDenied } // Look up the wallet containing the requested signer account := accounts.Account{Address: req.Address.Address()} + wallet, err := api.am.Find(account) if err != nil { return nil, err } + pw, err := api.lookupOrQueryPassword(account.Address, "Password for signing", fmt.Sprintf("Please enter password for signing data with account %s", account.Address.Hex())) @@ -64,9 +67,11 @@ func (api *SignerAPI) sign(req *SignDataRequest, legacyV bool) (hexutil.Bytes, e if err != nil { return nil, err } + if legacyV { signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper } + return signature, nil } @@ -79,11 +84,13 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com if err != nil { return nil, err } + signature, err := api.sign(req, transformV) if err != nil { api.UI.ShowError(err.Error()) return nil, err } + return signature, nil } @@ -98,6 +105,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType req *SignDataRequest useEthereumV = true // Default to use V = 27 or 28, the legacy Ethereum format ) + mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return nil, useEthereumV, err @@ -110,6 +118,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType if err != nil { return nil, useEthereumV, err } + sighash, msg := SignTextValidator(validatorData) messages := []*apitypes.NameValueType{ { @@ -140,6 +149,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType if err != nil { return nil, useEthereumV, err } + header := &types.Header{} if err := rlp.DecodeBytes(cliqueData, header); err != nil { return nil, useEthereumV, err @@ -154,6 +164,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType if err != nil { return nil, useEthereumV, err } + messages := []*apitypes.NameValueType{ { Name: "Clique header", @@ -167,6 +178,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType case apitypes.DataTyped.Mime: // EIP-712 conformant typed data var err error + req, err = typedDataRequest(data) if err != nil { return nil, useEthereumV, err @@ -179,8 +191,9 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType if err != nil { return nil, useEthereumV, err } + sighash, msg := accounts.TextAndHash(textData) - messages := []*apitypes.NameValueType{ + messages := []*apitypes.NameValueType{ { Name: "message", Typ: accounts.MimetypeTextPlain, @@ -189,8 +202,10 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType } req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash} } + req.Address = addr req.Meta = MetadataFromContext(ctx) + return req, useEthereumV, nil } @@ -214,8 +229,10 @@ func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error) err = fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) return } + rlp = clique.CliqueRLP(header) hash = clique.SealHash(header).Bytes() + return hash, rlp, err } @@ -237,16 +254,20 @@ func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAd if err != nil { return nil, nil, err } + req.Address = addr req.Meta = MetadataFromContext(ctx) + if validationMessages != nil { req.Callinfo = validationMessages.Messages } + signature, err := api.sign(req, true) if err != nil { api.UI.ShowError(err.Error()) return nil, nil, err } + return signature, req.Hash, nil } @@ -257,6 +278,7 @@ func fromHex(data any) ([]byte, error) { binary, err := hexutil.Decode(stringData) return binary, err } + return nil, fmt.Errorf("wrong type %T", data) } @@ -270,18 +292,22 @@ func typedDataRequest(data any) (*SignDataRequest, error) { if err != nil { return nil, err } + if err = json.Unmarshal(jsonData, &typedData); err != nil { return nil, err } } + messages, err := typedData.Format() if err != nil { return nil, err } + sighash, rawData, err := apitypes.TypedDataAndHash(typedData) if err != nil { return nil, err } + return &SignDataRequest{ ContentType: apitypes.DataTyped.Mime, Rawdata: []byte(rawData), @@ -306,15 +332,19 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hex if len(sig) != 65 { return common.Address{}, fmt.Errorf("signature must be 65 bytes long") } + if sig[64] != 27 && sig[64] != 28 { return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 hash := accounts.TextHash(data) + rpk, err := crypto.SigToPub(hash, sig) if err != nil { return common.Address{}, err } + return crypto.PubkeyToAddress(*rpk), nil } @@ -324,20 +354,25 @@ func UnmarshalValidatorData(data interface{}) (apitypes.ValidatorData, error) { if !ok { return apitypes.ValidatorData{}, errors.New("validator input is not a map[string]interface{}") } + addrBytes, err := fromHex(raw["address"]) if err != nil { return apitypes.ValidatorData{}, fmt.Errorf("validator address error: %w", err) } + if len(addrBytes) == 0 { return apitypes.ValidatorData{}, errors.New("validator address is undefined") } + messageBytes, err := fromHex(raw["message"]) if err != nil { return apitypes.ValidatorData{}, fmt.Errorf("message error: %w", err) } + if len(messageBytes) == 0 { return apitypes.ValidatorData{}, errors.New("message is undefined") } + return apitypes.ValidatorData{ Address: common.BytesToAddress(addrBytes), Message: messageBytes, diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index f0653df580..d6af695b07 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -188,36 +188,44 @@ func TestSignData(t *testing.T) { createAccount(control, api, t) createAccount(control, api, t) control.approveCh <- "1" + list, err := api.List(context.Background()) if err != nil { t.Fatal(err) } + a := common.NewMixedcaseAddress(list[0]) control.approveCh <- "Y" control.inputCh <- "wrongpassword" + signature, err := api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) if signature != nil { t.Errorf("Expected nil-data, got %x", signature) } + if err != keystore.ErrDecrypt { t.Errorf("Expected ErrLocked! '%v'", err) } control.approveCh <- "No way" + signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) if signature != nil { t.Errorf("Expected nil-data, got %x", signature) } + if err != core.ErrRequestDenied { t.Errorf("Expected ErrRequestDenied! '%v'", err) } // text/plain control.approveCh <- "Y" control.inputCh <- "a_long_password" + signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) if err != nil { t.Fatal(err) } + if signature == nil || len(signature) != 65 { t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature)) } @@ -269,6 +277,7 @@ func TestDomainChainId(t *testing.T) { if _, err := withoutChainID.HashStruct("EIP712Domain", withoutChainID.Domain.Map()); err != nil { t.Errorf("Expected the typedData to encode the domain successfully, got %v", err) } + withChainID := apitypes.TypedData{ Types: apitypes.Types{ "EIP712Domain": []apitypes.Type{ @@ -296,6 +305,7 @@ func TestHashStruct(t *testing.T) { if err != nil { t.Fatal(err) } + mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if mainHash != "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e" { t.Errorf("Expected different hashStruct result (got %s)", mainHash) @@ -305,6 +315,7 @@ func TestHashStruct(t *testing.T) { if err != nil { t.Error(err) } + domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" { t.Errorf("Expected different domain hashStruct result (got %s)", domainHash) @@ -335,6 +346,7 @@ func TestEncodeData(t *testing.T) { if err != nil { t.Fatal(err) } + dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if dataEncoding != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" { t.Errorf("Expected different encodeData result (got %s)", dataEncoding) @@ -343,10 +355,12 @@ func TestEncodeData(t *testing.T) { func TestFormatter(t *testing.T) { var d apitypes.TypedData + err := json.Unmarshal([]byte(jsonTypedData), &d) if err != nil { t.Fatalf("unmarshalling failed '%v'", err) } + formatted, _ := d.Format() for _, item := range formatted { t.Logf("'%v'\n", item.Pprint(0)) @@ -361,12 +375,15 @@ func sign(typedData apitypes.TypedData) ([]byte, []byte, error) { if err != nil { return nil, nil, err } + typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { return nil, nil, err } + rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) sighash := crypto.Keccak256(rawData) + return typedDataHash, sighash, nil } @@ -375,27 +392,35 @@ func TestJsonFiles(t *testing.T) { if err != nil { t.Fatalf("failed reading files: %v", err) } + for i, fInfo := range testfiles { if !strings.HasSuffix(fInfo.Name(), "json") { continue } + expectedFailure := strings.HasPrefix(fInfo.Name(), "expfail") + data, err := os.ReadFile(path.Join("testdata", fInfo.Name())) if err != nil { t.Errorf("Failed to read file %v: %v", fInfo.Name(), err) continue } + var typedData apitypes.TypedData + err = json.Unmarshal(data, &typedData) if err != nil { t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err) continue } + _, _, err = sign(typedData) t.Logf("Error %v\n", err) + if err != nil && !expectedFailure { t.Errorf("Test %d failed, file %v: %v", i, fInfo.Name(), err) } + if expectedFailure && err == nil { t.Errorf("Test %d succeeded (expected failure), file %v: %v", i, fInfo.Name(), err) } @@ -406,31 +431,39 @@ func TestJsonFiles(t *testing.T) { // crashes or hangs. func TestFuzzerFiles(t *testing.T) { corpusdir := path.Join("testdata", "fuzzing") + testfiles, err := os.ReadDir(corpusdir) if err != nil { t.Fatalf("failed reading files: %v", err) } + verbose := false + for i, fInfo := range testfiles { data, err := os.ReadFile(path.Join(corpusdir, fInfo.Name())) if err != nil { t.Errorf("Failed to read file %v: %v", fInfo.Name(), err) continue } + var typedData apitypes.TypedData + err = json.Unmarshal(data, &typedData) if err != nil { t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err) continue } + _, err = typedData.EncodeData("EIP712Domain", typedData.Domain.Map(), 1) if verbose && err != nil { t.Logf("%d, EncodeData[1] err: %v\n", i, err) } + _, err = typedData.EncodeData(typedData.PrimaryType, typedData.Message, 1) if verbose && err != nil { t.Logf("%d, EncodeData[2] err: %v\n", i, err) } + typedData.Format() } } @@ -518,14 +551,17 @@ var gnosisTx = ` // struct without using the gnosis-specific endpoint func TestGnosisTypedData(t *testing.T) { var td apitypes.TypedData + err := json.Unmarshal([]byte(gnosisTypedData), &td) if err != nil { t.Fatalf("unmarshalling failed '%v'", err) } + _, sighash, err := sign(td) if err != nil { t.Fatal(err) } + expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f") if !bytes.Equal(expSigHash, sighash) { t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) @@ -536,15 +572,19 @@ func TestGnosisTypedData(t *testing.T) { // specific data, and we fill the TypedData struct on our side func TestGnosisCustomData(t *testing.T) { var tx core.GnosisSafeTx + err := json.Unmarshal([]byte(gnosisTx), &tx) if err != nil { t.Fatal(err) } + var td = tx.ToTypedData() + _, sighash, err := sign(td) if err != nil { t.Fatal(err) } + expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f") if !bytes.Equal(expSigHash, sighash) { t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) @@ -648,14 +688,17 @@ var gnosisTxWithChainId = ` func TestGnosisTypedDataWithChainId(t *testing.T) { var td apitypes.TypedData + err := json.Unmarshal([]byte(gnosisTypedDataWithChainId), &td) if err != nil { t.Fatalf("unmarshalling failed '%v'", err) } + _, sighash, err := sign(td) if err != nil { t.Fatal(err) } + expSigHash := common.FromHex("0x6619dab5401503f2735256e12b898e69eb701d6a7e0d07abf1be4bb8aebfba29") if !bytes.Equal(expSigHash, sighash) { t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) @@ -666,15 +709,19 @@ func TestGnosisTypedDataWithChainId(t *testing.T) { // specific data, and we fill the TypedData struct on our side func TestGnosisCustomDataWithChainId(t *testing.T) { var tx core.GnosisSafeTx + err := json.Unmarshal([]byte(gnosisTxWithChainId), &tx) if err != nil { t.Fatal(err) } + var td = tx.ToTypedData() + _, sighash, err := sign(td) if err != nil { t.Fatal(err) } + expSigHash := common.FromHex("0x6619dab5401503f2735256e12b898e69eb701d6a7e0d07abf1be4bb8aebfba29") if !bytes.Equal(expSigHash, sighash) { t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) @@ -817,14 +864,17 @@ var complexTypedData = ` func TestComplexTypedData(t *testing.T) { var td apitypes.TypedData + err := json.Unmarshal([]byte(complexTypedData), &td) if err != nil { t.Fatalf("unmarshalling failed '%v'", err) } + _, sighash, err := sign(td) if err != nil { t.Fatal(err) } + expSigHash := common.FromHex("0x42b1aca82bb6900ff75e90a136de550a58f1a220a071704088eabd5e6ce20446") if !bytes.Equal(expSigHash, sighash) { t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) diff --git a/signer/core/stdioui.go b/signer/core/stdioui.go index 6963a89122..3afbffc28c 100644 --- a/signer/core/stdioui.go +++ b/signer/core/stdioui.go @@ -33,7 +33,9 @@ func NewStdIOUI() *StdIOUI { if err != nil { log.Crit("Could not create stdio client", "err", err) } + ui := &StdIOUI{client: *client} + return ui } @@ -47,40 +49,47 @@ func (ui *StdIOUI) dispatch(serviceMethod string, args interface{}, reply interf if err != nil { log.Info("Error", "exc", err.Error()) } + return err } // notify sends a request over the stdio, and does not listen for a response func (ui *StdIOUI) notify(serviceMethod string, args interface{}) error { ctx := context.Background() + err := ui.client.Notify(ctx, serviceMethod, args) if err != nil { log.Info("Error", "exc", err.Error()) } + return err } func (ui *StdIOUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { var result SignTxResponse err := ui.dispatch("ui_approveTx", request, &result) + return result, err } func (ui *StdIOUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { var result SignDataResponse err := ui.dispatch("ui_approveSignData", request, &result) + return result, err } func (ui *StdIOUI) ApproveListing(request *ListRequest) (ListResponse, error) { var result ListResponse err := ui.dispatch("ui_approveListing", request, &result) + return result, err } func (ui *StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) { var result NewAccountResponse err := ui.dispatch("ui_approveNewAccount", request, &result) + return result, err } @@ -112,9 +121,11 @@ func (ui *StdIOUI) OnSignerStartup(info StartupInfo) { } func (ui *StdIOUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) { var result UserInputResponse + err := ui.dispatch("ui_onInputRequired", info, &result) if err != nil { log.Info("Error calling 'ui_onInputRequired'", "exc", err.Error(), "info", info) } + return result, err } diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go index 924203a139..da23fd0957 100644 --- a/signer/core/uiapi.go +++ b/signer/core/uiapi.go @@ -57,6 +57,7 @@ func (s *UIServerAPI) ListAccounts(ctx context.Context) ([]accounts.Account, err for _, wallet := range s.am.Wallets() { accs = append(accs, wallet.Accounts()...) } + return accs, nil } @@ -74,6 +75,7 @@ type rawWallet struct { // {"jsonrpc":"2.0","method":"clef_listWallets","params":[], "id":5} func (s *UIServerAPI) ListWallets() []rawWallet { wallets := make([]rawWallet, 0) // return [] instead of nil if empty + for _, wallet := range s.am.Wallets() { status, failure := wallet.Status() @@ -85,8 +87,10 @@ func (s *UIServerAPI) ListWallets() []rawWallet { if failure != nil { raw.Failure = failure.Error() } + wallets = append(wallets, raw) } + return wallets } @@ -99,13 +103,16 @@ func (s *UIServerAPI) DeriveAccount(url string, path string, pin *bool) (account if err != nil { return accounts.Account{}, err } + derivPath, err := accounts.ParseDerivationPath(path) if err != nil { return accounts.Account{}, err } + if pin == nil { pin = new(bool) } + return wallet.Derive(derivPath, *pin) } @@ -115,6 +122,7 @@ func fetchKeystore(am *accounts.Manager) *keystore.KeyStore { if len(ks) == 0 { return nil } + return ks[0].(*keystore.KeyStore) } @@ -127,6 +135,7 @@ func (s *UIServerAPI) ImportRawKey(privkey string, password string) (accounts.Ac if err != nil { return accounts.Account{}, err } + if err := ValidatePasswordFormat(password); err != nil { return accounts.Account{}, fmt.Errorf("password requirements not met: %v", err) } @@ -145,10 +154,12 @@ func (s *UIServerAPI) OpenWallet(url string, passphrase *string) error { if err != nil { return err } + pass := "" if passphrase != nil { pass = *passphrase } + return wallet.Open(pass) } @@ -176,9 +187,11 @@ func (s *UIServerAPI) Export(ctx context.Context, addr common.Address) (json.Raw if err != nil { return nil, err } + if wallet.URL().Scheme != keystore.KeyStoreScheme { return nil, fmt.Errorf("account is not a keystore-account") } + return os.ReadFile(wallet.URL().Path) } @@ -193,9 +206,11 @@ func (api *UIServerAPI) Import(ctx context.Context, keyJSON json.RawMessage, old if len(be) == 0 { return accounts.Account{}, errors.New("password based accounts not supported") } + if err := ValidatePasswordFormat(newPassphrase); err != nil { return accounts.Account{}, fmt.Errorf("password requirements not met: %v", err) } + return be[0].(*keystore.KeyStore).Import(keyJSON, oldPassphrase, newPassphrase) } diff --git a/signer/core/validation.go b/signer/core/validation.go index 7639dbf649..b3d5487aef 100644 --- a/signer/core/validation.go +++ b/signer/core/validation.go @@ -29,8 +29,10 @@ func ValidatePasswordFormat(password string) error { if len(password) < 10 { return errors.New("password too short (<10 characters)") } + if !printable7BitAscii.MatchString(password) { return errors.New("password contains invalid characters - only 7bit printable ascii allowed") } + return nil } diff --git a/signer/fourbyte/abi.go b/signer/fourbyte/abi.go index 352abc59e1..5ca435b7a7 100644 --- a/signer/fourbyte/abi.go +++ b/signer/fourbyte/abi.go @@ -50,6 +50,7 @@ func (arg decodedArgument) String() string { default: value = fmt.Sprintf("%v", val) } + return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value) } @@ -59,6 +60,7 @@ func (cd decodedCallData) String() string { for i, arg := range cd.inputs { args[i] = arg.String() } + return fmt.Sprintf("%s(%s)", cd.name, strings.Join(args, ",")) } @@ -92,6 +94,7 @@ func parseCallData(calldata []byte, unescapedAbidata string) (*decodedCallData, if len(calldata) < 4 { return nil, fmt.Errorf("invalid call data, incomplete method signature (%d bytes < 4)", len(calldata)) } + sigdata := calldata[:4] argdata := calldata[4:] @@ -103,10 +106,12 @@ func parseCallData(calldata []byte, unescapedAbidata string) (*decodedCallData, if err != nil { return nil, fmt.Errorf("invalid method signature (%q): %v", unescapedAbidata, err) } + method, err := abispec.MethodById(sigdata) if err != nil { return nil, err } + values, err := method.Inputs.UnpackValues(argdata) if err != nil { return nil, fmt.Errorf("signature %q matches, but arguments mismatch: %v", method.String(), err) @@ -127,10 +132,13 @@ func parseCallData(calldata []byte, unescapedAbidata string) (*decodedCallData, if err != nil { return nil, err } + if !bytes.Equal(encoded, argdata) { was := common.Bytes2Hex(encoded) exp := common.Bytes2Hex(argdata) + return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig) } + return &decoded, nil } diff --git a/signer/fourbyte/abi_test.go b/signer/fourbyte/abi_test.go index 68c027ecea..da38ab4b1f 100644 --- a/signer/fourbyte/abi_test.go +++ b/signer/fourbyte/abi_test.go @@ -31,19 +31,24 @@ func verify(t *testing.T, jsondata, calldata string, exp []interface{}) { if err != nil { t.Fatal(err) } + cd := common.Hex2Bytes(calldata) sigdata, argdata := cd[:4], cd[4:] + method, err := abispec.MethodById(sigdata) if err != nil { t.Fatal(err) } + data, err := method.Inputs.UnpackValues(argdata) if err != nil { t.Fatal(err) } + if len(data) != len(exp) { t.Fatalf("Mismatched length, expected %d, got %d", len(exp), len(data)) } + for i, elem := range data { if !reflect.DeepEqual(elem, exp[i]) { t.Fatalf("Unpack error, arg %d, got %v, want %v", i, elem, exp[i]) @@ -57,6 +62,7 @@ func TestNewUnpacker(t *testing.T) { calldata string exp []interface{} } + testcases := []unpackTest{ { // https://solidity.readthedocs.io/en/develop/abi-spec.html#use-of-dynamic-types `[{"type":"function","name":"f", "inputs":[{"type":"uint256"},{"type":"uint32[]"},{"type":"bytes10"},{"type":"bytes"}]}]`, @@ -165,6 +171,7 @@ func TestMaliciousABIStrings(t *testing.T) { "func(,uint256,uint256,uint256)", } data := common.Hex2Bytes("4401a6e40000000000000000000000000000000000000000000000000000000000000012") + for i, tt := range tests { _, err := verifySelector(tt, data) if err == nil { diff --git a/signer/fourbyte/fourbyte.go b/signer/fourbyte/fourbyte.go index fcaa055dd7..7f0d671f69 100644 --- a/signer/fourbyte/fourbyte.go +++ b/signer/fourbyte/fourbyte.go @@ -66,6 +66,7 @@ func NewFromFile(path string) (*Database, error) { if err := json.NewDecoder(raw).Decode(&db.embedded); err != nil { return nil, err } + return db, nil } @@ -86,10 +87,12 @@ func NewWithFile(path string) (*Database, error) { if blob, err = os.ReadFile(path); err != nil { return nil, err } + if err := json.Unmarshal(blob, &db.custom); err != nil { return nil, err } } + return db, nil } @@ -105,13 +108,16 @@ func (db *Database) Selector(id []byte) (string, error) { if len(id) < 4 { return "", fmt.Errorf("expected 4-byte id, got %d", len(id)) } + sig := hex.EncodeToString(id[:4]) if selector, exists := db.embedded[sig]; exists { return selector, nil } + if selector, exists := db.custom[sig]; exists { return selector, nil } + return "", fmt.Errorf("signature %v not found", sig) } @@ -125,6 +131,7 @@ func (db *Database) AddSelector(selector string, data []byte) error { if len(data) < 4 { return nil } + if _, err := db.Selector(data[:4]); err == nil { return nil } @@ -133,6 +140,7 @@ func (db *Database) AddSelector(selector string, data []byte) error { if db.customPath == "" { return nil } + blob, err := json.Marshal(db.custom) if err != nil { return err diff --git a/signer/fourbyte/fourbyte_test.go b/signer/fourbyte/fourbyte_test.go index 017001f97b..9d147a12bd 100644 --- a/signer/fourbyte/fourbyte_test.go +++ b/signer/fourbyte/fourbyte_test.go @@ -31,22 +31,26 @@ func TestEmbeddedDatabase(t *testing.T) { if err != nil { t.Fatal(err) } + for id, selector := range db.embedded { abistring, err := parseSelector(selector) if err != nil { t.Errorf("Failed to convert selector to ABI: %v", err) continue } + abistruct, err := abi.JSON(strings.NewReader(string(abistring))) if err != nil { t.Errorf("Failed to parse ABI: %v", err) continue } + m, err := abistruct.MethodById(common.Hex2Bytes(id)) if err != nil { t.Errorf("Failed to get method by id (%s): %v", id, err) continue } + if m.Sig != selector { t.Errorf("Selector mismatch: have %v, want %v", m.Sig, selector) } @@ -63,6 +67,7 @@ func TestCustomDatabase(t *testing.T) { if err != nil { t.Fatal(err) } + db.embedded = make(map[string]string) // Ensure the database is empty, insert and verify @@ -70,9 +75,11 @@ func TestCustomDatabase(t *testing.T) { if _, err = db.Selector(calldata); err == nil { t.Fatalf("Should not find a match on empty database") } + if err = db.AddSelector("send(uint256)", calldata); err != nil { t.Fatalf("Failed to save file: %v", err) } + if _, err = db.Selector(calldata); err != nil { t.Fatalf("Failed to find a match for abi signature: %v", err) } @@ -81,6 +88,7 @@ func TestCustomDatabase(t *testing.T) { if err != nil { t.Fatalf("Failed to create new abidb: %v", err) } + if _, err = db2.Selector(calldata); err != nil { t.Fatalf("Failed to find a match for persisted abi signature: %v", err) } diff --git a/signer/fourbyte/validation.go b/signer/fourbyte/validation.go index 58111e8e00..eb260f74d8 100644 --- a/signer/fourbyte/validation.go +++ b/signer/fourbyte/validation.go @@ -38,10 +38,12 @@ func (db *Database) ValidateTransaction(selector *string, tx *apitypes.SendTxArg } // Place data on 'data', and nil 'input' var data []byte + if tx.Input != nil { tx.Data = tx.Input tx.Input = nil } + if tx.Data != nil { data = *tx.Data } @@ -64,15 +66,18 @@ func (db *Database) ValidateTransaction(selector *string, tx *apitypes.SendTxArg if selector != nil { messages.Warn("Transaction will create a contract, but method selector supplied, indicating an intent to call a method") } + return messages, nil } // Not a contract creation, validate as a plain transaction if !tx.To.ValidChecksum() { messages.Warn("Invalid checksum on recipient address") } + if bytes.Equal(tx.To.Address().Bytes(), common.Address{}.Bytes()) { messages.Crit("Transaction recipient is the zero address") } + switch { case tx.GasPrice == nil && tx.MaxFeePerGas == nil: messages.Crit("Neither 'gasPrice' nor 'maxFeePerGas' specified.") @@ -85,6 +90,7 @@ func (db *Database) ValidateTransaction(selector *string, tx *apitypes.SendTxArg } // Semantic fields validated, try to make heads or tails of the call data db.ValidateCallData(selector, data, messages) + return messages, nil } @@ -100,6 +106,7 @@ func (db *Database) ValidateCallData(selector *string, data []byte, messages *ap messages.Warn("Transaction data is not valid ABI (missing the 4 byte call prefix)") return } + if n := len(data) - 4; n%32 != 0 { messages.Warn(fmt.Sprintf("Transaction data is not valid ABI (length should be a multiple of 32 (was %d))", n)) } @@ -111,6 +118,7 @@ func (db *Database) ValidateCallData(selector *string, data []byte, messages *ap messages.Info(fmt.Sprintf("Transaction invokes the following method: %q", info.String())) db.AddSelector(*selector, data[:4]) } + return } // No method selector was provided, check the database for embedded ones @@ -119,6 +127,7 @@ func (db *Database) ValidateCallData(selector *string, data []byte, messages *ap messages.Warn(fmt.Sprintf("Transaction contains data, but the ABI signature could not be found: %v", err)) return } + if info, err := verifySelector(embedded, data); err != nil { messages.Warn(fmt.Sprintf("Transaction contains data, but provided ABI signature could not be verified: %v", err)) } else { diff --git a/signer/fourbyte/validation_test.go b/signer/fourbyte/validation_test.go index 1b0ab507a8..6e052942f2 100644 --- a/signer/fourbyte/validation_test.go +++ b/signer/fourbyte/validation_test.go @@ -43,17 +43,21 @@ func dummyTxArgs(t txtestcase) *apitypes.SendTxArgs { gas := toHexUint(t.g) gasPrice := toHexBig(t.gp) value := toHexBig(t.value) + var ( data, input *hexutil.Bytes ) + if t.d != "" { a := hexutil.Bytes(common.FromHex(t.d)) data = &a } + if t.i != "" { a := hexutil.Bytes(common.FromHex(t.i)) input = &a } + return &apitypes.SendTxArgs{ From: *from, To: to, @@ -77,6 +81,7 @@ func TestTransactionValidation(t *testing.T) { // use empty db, there are other tests for the abi-specific stuff db = newEmpty() ) + testcases := []txtestcase{ // Invalid to checksum {from: "000000000000000000000000000000000000dead", to: "000000000000000000000000000000000000dead", @@ -110,25 +115,30 @@ func TestTransactionValidation(t *testing.T) { msgs, err := db.ValidateTransaction(nil, dummyTxArgs(test)) if err == nil && test.expectErr { t.Errorf("Test %d, expected error", i) + for _, msg := range msgs.Messages { t.Logf("* %s: %s", msg.Typ, msg.Message) } } + if err != nil && !test.expectErr { t.Errorf("Test %d, unexpected error: %v", i, err) } + if err == nil { got := len(msgs.Messages) if got != test.numMessages { for _, msg := range msgs.Messages { t.Logf("* %s: %s", msg.Typ, msg.Message) } + t.Errorf("Test %d, expected %d messages, got %d", i, test.numMessages, got) } else { //Debug printout, remove later for _, msg := range msgs.Messages { t.Logf("* [%d] %s: %s", i, msg.Typ, msg.Message) } + t.Log() } } diff --git a/signer/rules/rules.go b/signer/rules/rules.go index 5ed4514e02..cb576a0996 100644 --- a/signer/rules/rules.go +++ b/signer/rules/rules.go @@ -37,7 +37,9 @@ func consoleOutput(call goja.FunctionCall) goja.Value { for _, argument := range call.Arguments { output = append(output, fmt.Sprintf("%v", argument)) } + fmt.Fprintln(os.Stderr, strings.Join(output, " ")) + return goja.Undefined() } @@ -85,11 +87,13 @@ func (r *rulesetUI) execute(jsfunc string, jsarg interface{}) (goja.Value, error } else { r.storage.Put(key, val) } + return goja.Null() }) storageObj.Set("get", func(call goja.FunctionCall) goja.Value { goval, _ := r.storage.Get(call.Argument(0).String()) jsval := vm.ToValue(goval) + return jsval }) vm.Set("storage", storageObj) @@ -100,6 +104,7 @@ func (r *rulesetUI) execute(jsfunc string, jsarg interface{}) (goja.Value, error log.Warn("Failed loading libraries", "err", err) return goja.Undefined(), err } + vm.RunProgram(script) // Run the actual rule implementation @@ -126,6 +131,7 @@ func (r *rulesetUI) execute(jsfunc string, jsarg interface{}) (goja.Value, error } else { call = fmt.Sprintf("%v()", jsfunc) } + return vm.RunString(call) } @@ -133,11 +139,13 @@ func (r *rulesetUI) checkApproval(jsfunc string, jsarg []byte, err error) (bool, if err != nil { return false, err } + v, err := r.execute(jsfunc, string(jsarg)) if err != nil { log.Info("error occurred during execution", "error", err) return false, err } + result := v.ToString().String() if result == "Approve" { log.Info("Op approved") @@ -146,12 +154,14 @@ func (r *rulesetUI) checkApproval(jsfunc string, jsarg []byte, err error) (bool, log.Info("Op rejected") return false, nil } + return false, fmt.Errorf("unknown response") } func (r *rulesetUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { jsonreq, err := json.Marshal(request) approved, err := r.checkApproval("ApproveTx", jsonreq, err) + if err != nil { log.Info("Rule-based approval error, going to manual", "error", err) return r.next.ApproveTx(request) @@ -163,19 +173,23 @@ func (r *rulesetUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, Approved: true}, nil } + return core.SignTxResponse{Approved: false}, err } func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { jsonreq, err := json.Marshal(request) approved, err := r.checkApproval("ApproveSignData", jsonreq, err) + if err != nil { log.Info("Rule-based approval error, going to manual", "error", err) return r.next.ApproveSignData(request) } + if approved { return core.SignDataResponse{Approved: true}, nil } + return core.SignDataResponse{Approved: false}, err } @@ -187,13 +201,16 @@ func (r *rulesetUI) OnInputRequired(info core.UserInputRequest) (core.UserInputR func (r *rulesetUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { jsonreq, err := json.Marshal(request) approved, err := r.checkApproval("ApproveListing", jsonreq, err) + if err != nil { log.Info("Rule-based approval error, going to manual", "error", err) return r.next.ApproveListing(request) } + if approved { return core.ListResponse{Accounts: request.Accounts}, nil } + return core.ListResponse{}, err } @@ -219,7 +236,9 @@ func (r *rulesetUI) OnSignerStartup(info core.StartupInfo) { log.Warn("failed marshalling data", "data", info) return } + r.next.OnSignerStartup(info) + _, err = r.execute("OnSignerStartup", string(jsonInfo)) if err != nil { log.Info("error occurred during execution", "error", err) @@ -232,6 +251,7 @@ func (r *rulesetUI) OnApprovedTx(tx ethapi.SignTransactionResult) { log.Warn("failed marshalling transaction", "tx", tx) return } + _, err = r.execute("OnApprovedTx", string(jsonTx)) if err != nil { log.Info("error occurred during execution", "error", err) diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go index c35da8ecc1..188fea6c63 100644 --- a/signer/rules/rules_test.go +++ b/signer/rules/rules_test.go @@ -117,9 +117,11 @@ func initRuleEngine(js string) (*rulesetUI, error) { if err != nil { return nil, fmt.Errorf("failed to create js engine: %v", err) } + if err = r.Init(js); err != nil { return nil, fmt.Errorf("failed to load bootstrap js: %v", err) } + return r, nil } @@ -142,6 +144,7 @@ func TestListRequest(t *testing.T) { t.Errorf("Couldn't create evaluator %v", err) return } + resp, _ := r.ApproveListing(&core.ListRequest{ Accounts: accs, Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, @@ -167,18 +170,22 @@ func TestSignTxRequest(t *testing.T) { t.Errorf("Couldn't create evaluator %v", err) return } + to, err := mixAddr("000000000000000000000000000000000000dead") if err != nil { t.Error(err) return } + from, err := mixAddr("0000000000000000000000000000000000001337") if err != nil { t.Error(err) return } + t.Logf("to %v", to.Address().String()) + resp, err := r.ApproveTx(&core.SignTxRequest{ Transaction: apitypes.SendTxArgs{ From: *from, @@ -189,6 +196,7 @@ func TestSignTxRequest(t *testing.T) { if err != nil { t.Errorf("Unexpected error %v", err) } + if !resp.Approved { t.Errorf("Expected check to resolve to 'Approve'") } @@ -247,13 +255,16 @@ func TestForwarding(t *testing.T) { js := "" ui := &dummyUI{make([]string, 0)} jsBackend := storage.NewEphemeralStorage() + r, err := NewRuleEvaluator(ui, jsBackend) if err != nil { t.Fatalf("Failed to create js engine: %v", err) } + if err = r.Init(js); err != nil { t.Fatalf("Failed to load bootstrap js: %v", err) } + r.ApproveSignData(nil) r.ApproveTx(nil) r.ApproveNewAccount(nil) @@ -287,9 +298,11 @@ func TestMissingFunc(t *testing.T) { if err == nil { t.Errorf("Expected missing method to yield error'") } + if approved { t.Errorf("Expected missing method to cause non-approval") } + t.Logf("Err %v", err) } func TestStorage(t *testing.T) { @@ -320,6 +333,7 @@ func TestStorage(t *testing.T) { return a } ` + r, err := initRuleEngine(js) if err != nil { t.Errorf("Couldn't create evaluator %v", err) @@ -331,15 +345,18 @@ func TestStorage(t *testing.T) { if err != nil { t.Errorf("Unexpected error %v", err) } + retval := v.ToString().String() if err != nil { t.Errorf("Unexpected error %v", err) } + exp := `myvaluea,list[object Object]{"an":"object"}` if retval != exp { t.Errorf("Unexpected data, expected '%v', got '%v'", exp, retval) } + t.Logf("Err %v", err) } @@ -443,6 +460,7 @@ func dummyTx(value hexutil.Big) *core.SignTxRequest { func dummyTxWithV(value uint64) *core.SignTxRequest { v := new(big.Int).SetUint64(value) h := hexutil.Big(*v) + return dummyTx(h) } @@ -451,6 +469,7 @@ func dummySigned(value *big.Int) *types.Transaction { gas := uint64(21000) gasPrice := big.NewInt(2000000) data := make([]byte, 0) + return types.NewTransaction(3, to, value, gas, gasPrice, data) } @@ -466,10 +485,12 @@ func TestLimitWindow(t *testing.T) { // The first three should succeed for i := 0; i < 3; i++ { unsigned := dummyTx(h) + resp, err := r.ApproveTx(unsigned) if err != nil { t.Errorf("Unexpected error %v", err) } + if !resp.Approved { t.Errorf("Expected check to resolve to 'Approve'") } @@ -555,16 +576,20 @@ func TestContextIsCleared(t *testing.T) { } ` ui := &dontCallMe{t} + r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage()) if err != nil { t.Fatalf("Failed to create js engine: %v", err) } + if err = r.Init(js); err != nil { t.Fatalf("Failed to load bootstrap js: %v", err) } + tx := dummyTxWithV(0) r1, _ := r.ApproveTx(tx) r2, _ := r.ApproveTx(tx) + if r1.Approved != r2.Approved { t.Errorf("Expected execution context to be cleared between executions") } @@ -584,11 +609,13 @@ function ApproveSignData(r){ } // Otherwise goes to manual processing }` + r, err := initRuleEngine(js) if err != nil { t.Errorf("Couldn't create evaluator %v", err) return } + message := "baz bazonk foo" hash, rawdata := accounts.TextAndHash([]byte(message)) addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") @@ -602,6 +629,7 @@ function ApproveSignData(r){ Value: message, }, } + resp, err := r.ApproveSignData(&core.SignDataRequest{ Address: *addr, Messages: nvt, @@ -612,6 +640,7 @@ function ApproveSignData(r){ if err != nil { t.Fatalf("Unexpected error %v", err) } + if !resp.Approved { t.Fatalf("Expected approved") } diff --git a/signer/storage/aes_gcm_storage.go b/signer/storage/aes_gcm_storage.go index c222648bb0..e645e43d43 100644 --- a/signer/storage/aes_gcm_storage.go +++ b/signer/storage/aes_gcm_storage.go @@ -56,17 +56,21 @@ func (s *AESEncryptedStorage) Put(key, value string) { if len(key) == 0 { return } + data, err := s.readEncryptedStorage() if err != nil { log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) return } + ciphertext, iv, err := encrypt(s.key, []byte(value), []byte(key)) if err != nil { log.Warn("Failed to encrypt entry", "err", err) return } + encrypted := storedCredential{Iv: iv, CipherText: ciphertext} + data[key] = encrypted if err = s.writeEncryptedStorage(data); err != nil { log.Warn("Failed to write entry", "err", err) @@ -79,21 +83,25 @@ func (s *AESEncryptedStorage) Get(key string) (string, error) { if len(key) == 0 { return "", ErrZeroKey } + data, err := s.readEncryptedStorage() if err != nil { log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) return "", err } + encrypted, exist := data[key] if !exist { log.Warn("Key does not exist", "key", key) return "", ErrNotFound } + entry, err := decrypt(s.key, encrypted.Iv, encrypted.CipherText, []byte(key)) if err != nil { log.Warn("Failed to decrypt key", "key", key) return "", err } + return string(entry), nil } @@ -104,7 +112,9 @@ func (s *AESEncryptedStorage) Del(key string) { log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) return } + delete(data, key) + if err = s.writeEncryptedStorage(data); err != nil { log.Warn("Failed to write entry", "err", err) } @@ -120,12 +130,15 @@ func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]storedCredentia // Doesn't exist yet return creds, nil } + log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) } + if err = json.Unmarshal(raw, &creds); err != nil { log.Warn("Failed to unmarshal encrypted storage", "err", err, "file", s.filename) return nil, err } + return creds, nil } @@ -139,6 +152,7 @@ func (s *AESEncryptedStorage) writeEncryptedStorage(creds map[string]storedCrede if err = os.WriteFile(s.filename, raw, 0600); err != nil { return err } + return nil } @@ -150,15 +164,19 @@ func encrypt(key []byte, plaintext []byte, additionalData []byte) ([]byte, []byt if err != nil { return nil, nil, err } + aesgcm, err := cipher.NewGCM(block) if err != nil { return nil, nil, err } + nonce := make([]byte, aesgcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return nil, nil, err } + ciphertext := aesgcm.Seal(nil, nonce, plaintext, additionalData) + return ciphertext, nonce, nil } @@ -167,13 +185,16 @@ func decrypt(key []byte, nonce []byte, ciphertext []byte, additionalData []byte) if err != nil { return nil, err } + aesgcm, err := cipher.NewGCM(block) if err != nil { return nil, err } + plaintext, err := aesgcm.Open(nil, nonce, ciphertext, additionalData) if err != nil { return nil, err } + return plaintext, nil } diff --git a/signer/storage/aes_gcm_storage_test.go b/signer/storage/aes_gcm_storage_test.go index a25bce61cb..88cea1e0f0 100644 --- a/signer/storage/aes_gcm_storage_test.go +++ b/signer/storage/aes_gcm_storage_test.go @@ -38,13 +38,16 @@ func TestEncryption(t *testing.T) { if err != nil { t.Fatal(err) } + t.Logf("Ciphertext %x, nonce %x\n", c, iv) p, err := decrypt(key, iv, c, nil) if err != nil { t.Fatal(err) } + t.Logf("Plaintext %v\n", string(p)) + if !bytes.Equal(plaintext, p) { t.Errorf("Failed: expected plaintext recovery, got %v expected %v", string(plaintext), string(p)) } @@ -67,14 +70,17 @@ func TestFileStorage(t *testing.T) { key: []byte("AES256Key-32Characters1234567890"), } stored.writeEncryptedStorage(a) + read := &AESEncryptedStorage{ filename: fmt.Sprintf("%v/vault.json", d), key: []byte("AES256Key-32Characters1234567890"), } + creds, err := read.readEncryptedStorage() if err != nil { t.Fatal(err) } + for k, v := range a { if v2, exist := creds[k]; !exist { t.Errorf("Missing entry %v", k) @@ -82,6 +88,7 @@ func TestFileStorage(t *testing.T) { if !bytes.Equal(v.CipherText, v2.CipherText) { t.Errorf("Wrong ciphertext, expected %x got %x", v.CipherText, v2.CipherText) } + if !bytes.Equal(v.Iv, v2.Iv) { t.Errorf("Wrong iv") } @@ -103,6 +110,7 @@ func TestEnd2End(t *testing.T) { } s1.Put("bazonk", "foobar") + if v, err := s2.Get("bazonk"); v != "foobar" || err != nil { t.Errorf("Expected bazonk->foobar (nil error), got '%v' (%v error)", v, err) } @@ -124,17 +132,21 @@ func TestSwappedKeys(t *testing.T) { // Now make a modified copy creds := make(map[string]storedCredential) + raw, err := os.ReadFile(s1.filename) if err != nil { t.Fatal(err) } + if err = json.Unmarshal(raw, &creds); err != nil { t.Fatal(err) } + swap := func() { // Turn it into K1:V2, K2:V2 v1, v2 := creds["k1"], creds["k2"] creds["k2"], creds["k1"] = v1, v2 + raw, err = json.Marshal(creds) if err != nil { t.Fatal(err) @@ -145,10 +157,13 @@ func TestSwappedKeys(t *testing.T) { } } swap() + if v, _ := s1.Get("k1"); v != "" { t.Errorf("swapped value should return empty") } + swap() + if v, _ := s1.Get("k1"); v != "v1" { t.Errorf("double-swapped value should work fine") } diff --git a/signer/storage/storage.go b/signer/storage/storage.go index 33c0d66f9b..f8bd48607e 100644 --- a/signer/storage/storage.go +++ b/signer/storage/storage.go @@ -49,6 +49,7 @@ func (s *EphemeralStorage) Put(key, value string) { if len(key) == 0 { return } + s.data[key] = value } @@ -58,9 +59,11 @@ func (s *EphemeralStorage) Get(key string) (string, error) { if len(key) == 0 { return "", ErrZeroKey } + if v, ok := s.data[key]; ok { return v, nil } + return "", ErrNotFound } @@ -73,6 +76,7 @@ func NewEphemeralStorage() Storage { s := &EphemeralStorage{ data: make(map[string]string), } + return s } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index dfca3cf277..e6bdc44699 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -110,13 +110,16 @@ func (t *BlockTest) Run(snapshotter bool) error { // import pre accounts & construct test genesis block & state root db := rawdb.NewMemoryDatabase() gspec := t.genesis(config) + gblock := gspec.MustCommit(db) if gblock.Hash() != t.json.Genesis.Hash { return fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6]) } + if gblock.Root() != t.json.Genesis.StateRoot { return fmt.Errorf("genesis block state root does not match test: computed=%x, test=%x", gblock.Root().Bytes()[:6], t.json.Genesis.StateRoot[:6]) } + var engine consensus.Engine if t.json.SealEngine == "NoProof" { engine = ethash.NewFaker() @@ -142,14 +145,17 @@ func (t *BlockTest) Run(snapshotter bool) error { if err != nil { return err } + cmlast := chain.CurrentBlock().Hash() if common.Hash(t.json.BestBlock) != cmlast { return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, cmlast) } + newDB, err := chain.State() if err != nil { return err } + if err = t.validatePostState(newDB); err != nil { return fmt.Errorf("post state validation failed: %v", err) } @@ -159,6 +165,7 @@ func (t *BlockTest) Run(snapshotter bool) error { return err } } + return t.validateImportedHeaders(chain, validBlocks) } @@ -206,6 +213,7 @@ func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error) } // RLP decoding worked, try to insert into chain: blocks := types.Blocks{cb} + i, err := blockchain.InsertChain(blocks) if err != nil { if b.BlockHeader == nil { @@ -214,11 +222,13 @@ func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error) return nil, fmt.Errorf("block #%v insertion into chain failed: %v", blocks[i].Number(), err) } } + if b.BlockHeader == nil { if data, err := json.MarshalIndent(cb.Header(), "", " "); err == nil { fmt.Fprintf(os.Stderr, "block (index %d) insertion should have failed due to: %v:\n%v\n", bi, b.ExpectException, string(data)) } + return nil, fmt.Errorf("block (index %d) insertion should have failed due to: %v", bi, b.ExpectException) } @@ -227,8 +237,10 @@ func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error) if err = validateHeader(b.BlockHeader, cb.Header()); err != nil { return nil, fmt.Errorf("deserialised block header validation failed: %v", err) } + validBlocks = append(validBlocks, b) } + return validBlocks, nil } @@ -236,45 +248,59 @@ func validateHeader(h *btHeader, h2 *types.Header) error { if h.Bloom != h2.Bloom { return fmt.Errorf("bloom: want: %x have: %x", h.Bloom, h2.Bloom) } + if h.Coinbase != h2.Coinbase { return fmt.Errorf("coinbase: want: %x have: %x", h.Coinbase, h2.Coinbase) } + if h.MixHash != h2.MixDigest { return fmt.Errorf("MixHash: want: %x have: %x", h.MixHash, h2.MixDigest) } + if h.Nonce != h2.Nonce { return fmt.Errorf("nonce: want: %x have: %x", h.Nonce, h2.Nonce) } + if h.Number.Cmp(h2.Number) != 0 { return fmt.Errorf("number: want: %v have: %v", h.Number, h2.Number) } + if h.ParentHash != h2.ParentHash { return fmt.Errorf("parent hash: want: %x have: %x", h.ParentHash, h2.ParentHash) } + if h.ReceiptTrie != h2.ReceiptHash { return fmt.Errorf("receipt hash: want: %x have: %x", h.ReceiptTrie, h2.ReceiptHash) } + if h.TransactionsTrie != h2.TxHash { return fmt.Errorf("tx hash: want: %x have: %x", h.TransactionsTrie, h2.TxHash) } + if h.StateRoot != h2.Root { return fmt.Errorf("state hash: want: %x have: %x", h.StateRoot, h2.Root) } + if h.UncleHash != h2.UncleHash { return fmt.Errorf("uncle hash: want: %x have: %x", h.UncleHash, h2.UncleHash) } + if !bytes.Equal(h.ExtraData, h2.Extra) { return fmt.Errorf("extra data: want: %x have: %x", h.ExtraData, h2.Extra) } + if h.Difficulty.Cmp(h2.Difficulty) != 0 { return fmt.Errorf("difficulty: want: %v have: %v", h.Difficulty, h2.Difficulty) } + if h.GasLimit != h2.GasLimit { return fmt.Errorf("gasLimit: want: %d have: %d", h.GasLimit, h2.GasLimit) } + if h.GasUsed != h2.GasUsed { return fmt.Errorf("gasUsed: want: %d have: %d", h.GasUsed, h2.GasUsed) } + if h.Timestamp != h2.Time { return fmt.Errorf("timestamp: want: %v have: %v", h.Timestamp, h2.Time) } @@ -286,6 +312,7 @@ func validateHeader(h *btHeader, h2 *types.Header) error { if !reflect.DeepEqual(h.WithdrawalsRoot, h2.WithdrawalsHash) { return fmt.Errorf("withdrawalsRoot: want: %v have: %v", h.WithdrawalsRoot, h2.WithdrawalsHash) } + return nil } @@ -296,16 +323,20 @@ func (t *BlockTest) validatePostState(statedb *state.StateDB) error { code2 := statedb.GetCode(addr) balance2 := statedb.GetBalance(addr) nonce2 := statedb.GetNonce(addr) + if !bytes.Equal(code2, acct.Code) { return fmt.Errorf("account code mismatch for addr: %s want: %v have: %s", addr, acct.Code, hex.EncodeToString(code2)) } + if balance2.Cmp(acct.Balance) != 0 { return fmt.Errorf("account balance mismatch for addr: %s, want: %d, have: %d", addr, acct.Balance, balance2) } + if nonce2 != acct.Nonce { return fmt.Errorf("account nonce mismatch for addr: %s want: %d have: %d", addr, acct.Nonce, nonce2) } } + return nil } @@ -325,6 +356,7 @@ func (t *BlockTest) validateImportedHeaders(cm *core.BlockChain, validBlocks []b return fmt.Errorf("imported block header validation failed: %v", err) } } + return nil } @@ -333,7 +365,9 @@ func (bb *btBlock) decode() (*types.Block, error) { if err != nil { return nil, err } + var b types.Block err = rlp.DecodeBytes(data, &b) + return &b, err } diff --git a/tests/bor/mocks/IHeimdallClient.go b/tests/bor/mocks/IHeimdallClient.go index 15a09712f1..dc3c212465 100644 --- a/tests/bor/mocks/IHeimdallClient.go +++ b/tests/bor/mocks/IHeimdallClient.go @@ -29,6 +29,7 @@ type MockIHeimdallClientMockRecorder struct { func NewMockIHeimdallClient(ctrl *gomock.Controller) *MockIHeimdallClient { mock := &MockIHeimdallClient{ctrl: ctrl} mock.recorder = &MockIHeimdallClientMockRecorder{mock} + return mock } @@ -55,6 +56,7 @@ func (m *MockIHeimdallClient) FetchCheckpoint(arg0 context.Context, arg1 int64) ret := m.ctrl.Call(m, "FetchCheckpoint", arg0, arg1) ret0, _ := ret[0].(*checkpoint.Checkpoint) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -70,6 +72,7 @@ func (m *MockIHeimdallClient) FetchCheckpointCount(arg0 context.Context) (int64, ret := m.ctrl.Call(m, "FetchCheckpointCount", arg0) ret0, _ := ret[0].(int64) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -85,6 +88,7 @@ func (m *MockIHeimdallClient) Span(arg0 context.Context, arg1 uint64) (*span.Hei ret := m.ctrl.Call(m, "Span", arg0, arg1) ret0, _ := ret[0].(*span.HeimdallSpan) ret1, _ := ret[1].(error) + return ret0, ret1 } @@ -100,6 +104,7 @@ func (m *MockIHeimdallClient) StateSyncEvents(arg0 context.Context, arg1 uint64, ret := m.ctrl.Call(m, "StateSyncEvents", arg0, arg1, arg2) ret0, _ := ret[0].([]*clerk.EventRecordWithTime) ret1, _ := ret[1].(error) + return ret0, ret1 } diff --git a/tests/difficulty_test_util.go b/tests/difficulty_test_util.go index 62b978f9ef..2201425c51 100644 --- a/tests/difficulty_test_util.go +++ b/tests/difficulty_test_util.go @@ -64,5 +64,6 @@ func (test *DifficultyTest) Run(config *params.ChainConfig) error { test.ParentTimestamp, test.ParentDifficulty, test.UncleHash, test.CurrentTimestamp, test.CurrentBlockNumber, actual, exp) } + return nil } diff --git a/tests/fuzzers/abi/abifuzzer.go b/tests/fuzzers/abi/abifuzzer.go index 60233d158a..d591e85b15 100644 --- a/tests/fuzzers/abi/abifuzzer.go +++ b/tests/fuzzers/abi/abifuzzer.go @@ -56,26 +56,33 @@ func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) err.Error() == "abi: cannot use uint8 as type int8 as argument" { return out, false } + panic(err) } + return out, true } + return nil, false } 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) if err != nil { panic(err) } + out := outptr.Elem().Interface() if !reflect.DeepEqual(input, out) { panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, out)) } + return true } + return false } @@ -89,9 +96,11 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab if stateMutability != nil { sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability) } + if payable != nil { sig += fmt.Sprintf(`, "payable": %v `, *payable) } + if len(inputs) > 0 { sig += `, "inputs" : [ {` for i, inp := range inputs { @@ -100,16 +109,20 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab sig += "," } } + sig += "} ]" sig += `, "outputs" : [ {` + for i, inp := range inputs { sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ) if i+1 < len(inputs) { sig += "," } } + sig += "} ]" } + sig += `}]` return abi.JSON(strings.NewReader(sig)) @@ -122,11 +135,14 @@ func runFuzzer(input []byte) int { 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[getUInt(fuzzer)%len(varTypes)] if getUInt(fuzzer)%10 == 0 { argTyp += "[]" @@ -134,22 +150,27 @@ func runFuzzer(input []byte) int { arrayArgs := getUInt(fuzzer)%30 + 1 argTyp += fmt.Sprintf("[%d]", arrayArgs) } + arg = append(arg, args{ name: argName, typ: argTyp, }) } + abi, err := createABI(name, stateM, payable, arg) if err != nil { continue } + structs, b := unpackPack(abi, name, input) c := packUnpack(abi, name, &structs) good = good || b || c } + if good { return 1 } + return 0 } @@ -159,12 +180,15 @@ func Fuzz(input []byte) int { func getUInt(fuzzer *fuzz.Fuzzer) int { var i int + fuzzer.Fuzz(&i) + if i < 0 { i = -i if i < 0 { return 0 } } + return i } diff --git a/tests/fuzzers/bitutil/compress_fuzz.go b/tests/fuzzers/bitutil/compress_fuzz.go index 5903cf2f93..aad151515d 100644 --- a/tests/fuzzers/bitutil/compress_fuzz.go +++ b/tests/fuzzers/bitutil/compress_fuzz.go @@ -28,9 +28,11 @@ func Fuzz(data []byte) int { if len(data) == 0 { return 0 } + if data[0]%2 == 0 { return fuzzEncode(data[1:]) } + return fuzzDecode(data[1:]) } @@ -41,6 +43,7 @@ func fuzzEncode(data []byte) int { if !bytes.Equal(data, proc) { panic("content mismatch") } + return 1 } @@ -63,8 +66,10 @@ func fuzzDecode(data []byte) int { if err != nil { panic(err) } + if !bytes.Equal(decomp, blob) { panic("content mismatch") } + return 1 } diff --git a/tests/fuzzers/bls12381/precompile_fuzzer.go b/tests/fuzzers/bls12381/precompile_fuzzer.go index cab2bcba38..e0eb95c831 100644 --- a/tests/fuzzers/bls12381/precompile_fuzzer.go +++ b/tests/fuzzers/bls12381/precompile_fuzzer.go @@ -67,6 +67,7 @@ func checkInput(id byte, inputLen int) bool { case blsMapG2: return inputLen == 128 } + panic("programmer error") } @@ -83,6 +84,7 @@ func fuzz(id byte, data []byte) int { // Even on bad input, it should not crash, so we still test the gas calc precompile := vm.PrecompiledContractsBLS[common.BytesToAddress([]byte{id})] gas := precompile.RequiredGas(data) + if !checkInput(id, len(data)) { return 0 } @@ -90,14 +92,18 @@ func fuzz(id byte, data []byte) int { 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/difficulty/debug/main.go b/tests/fuzzers/difficulty/debug/main.go index 88e5696eaf..34a74b00fe 100644 --- a/tests/fuzzers/difficulty/debug/main.go +++ b/tests/fuzzers/difficulty/debug/main.go @@ -28,6 +28,7 @@ func main() { fmt.Fprintf(os.Stderr, "Usage: debug ") os.Exit(1) } + crasher := os.Args[1] data, err := os.ReadFile(crasher) @@ -35,5 +36,6 @@ func main() { 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 index e8753bb623..897f7daf84 100644 --- a/tests/fuzzers/difficulty/difficulty-fuzz.go +++ b/tests/fuzzers/difficulty/difficulty-fuzz.go @@ -37,17 +37,21 @@ func (f *fuzzer) read(size int) []byte { 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 } @@ -55,11 +59,14 @@ 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 { @@ -80,6 +87,7 @@ func Fuzz(data []byte) int { input: bytes.NewReader(data), exhausted: false, } + return f.fuzz() } @@ -99,6 +107,7 @@ func (f *fuzzer) fuzz() int { 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) @@ -137,10 +146,12 @@ func (f *fuzzer) fuzz() int { } { 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 } diff --git a/tests/fuzzers/keystore/keystore-fuzzer.go b/tests/fuzzers/keystore/keystore-fuzzer.go index e3bcae92e1..ae695d629c 100644 --- a/tests/fuzzers/keystore/keystore-fuzzer.go +++ b/tests/fuzzers/keystore/keystore-fuzzer.go @@ -32,6 +32,8 @@ func Fuzz(input []byte) int { if err := ks.Unlock(a, string(input)); err != nil { panic(err) } + os.Remove(a.URL.Path) + return 1 } diff --git a/tests/fuzzers/les/debug/main.go b/tests/fuzzers/les/debug/main.go index 3ddba2f0da..9cf0aee4fd 100644 --- a/tests/fuzzers/les/debug/main.go +++ b/tests/fuzzers/les/debug/main.go @@ -30,6 +30,7 @@ func main() { fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") os.Exit(1) } + crasher := os.Args[1] data, err := os.ReadFile(crasher) @@ -37,5 +38,6 @@ func main() { 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 index 7dd7c60e18..4621def72b 100644 --- a/tests/fuzzers/les/les-fuzzer.go +++ b/tests/fuzzers/les/les-fuzzer.go @@ -67,6 +67,7 @@ func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) { 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) @@ -75,20 +76,25 @@ func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) { 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(rawdb.NewMemoryDatabase(), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, 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.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase())) bloomTrie = trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase())) + for i := 0; i < testChainLen; i++ { // The element in CHT is -> key := make([]byte, 8) @@ -102,6 +108,7 @@ func makeTries() (chtTrie *trie.Trie, bloomTrie *trie.Trie, chtKeys, bloomKeys [ bloomTrie.MustUpdate(key2, []byte{0x2, 0xe}) bloomKeys = append(bloomKeys, key2) } + return } @@ -148,6 +155,7 @@ func (f *fuzzer) read(size int) []byte { if _, err := f.input.Read(out); err != nil { f.exhausted = true } + return out } @@ -165,13 +173,16 @@ 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)) } @@ -180,9 +191,11 @@ func (f *fuzzer) randomX(max int) uint64 { 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) } @@ -191,6 +204,7 @@ func (f *fuzzer) randomBlockHash() common.Hash { if h != (common.Hash{}) { return h } + return common.BytesToHash(f.read(common.HashLength)) } @@ -199,6 +213,7 @@ func (f *fuzzer) randomAddrHash() []byte { if i < len(f.addr) { return f.addr[i].Bytes() } + return f.read(common.HashLength) } @@ -207,6 +222,7 @@ func (f *fuzzer) randomCHTTrieKey() []byte { if i < len(f.chtKeys) { return f.chtKeys[i] } + return f.read(8) } @@ -215,6 +231,7 @@ func (f *fuzzer) randomBloomTrieKey() []byte { if i < len(f.bloomKeys) { return f.bloomKeys[i] } + return f.read(10) } @@ -223,6 +240,7 @@ func (f *fuzzer) randomTxHash() common.Hash { if i < len(f.txs) { return f.txs[i] } + return common.BytesToHash(f.read(common.HashLength)) } @@ -248,6 +266,7 @@ func (f *fuzzer) GetHelperTrie(typ uint, index uint64) *trie.Trie { } else if typ == 1 { return f.bloomTrie } + return nil } @@ -264,13 +283,17 @@ func (f *fuzzer) doFuzz(msgCode uint64, packet interface{}) { if err != nil { panic(err) } + version := f.randomInt(3) + 2 // [LES2, LES3, LES4] + peer, closeFn := l.NewFuzzerPeer(version) defer closeFn() + fn, _, _, err := l.Les3[msgCode].Handle(dummyMsg{enc}) if err != nil { panic(err) } + fn(f, peer, func() bool { return true }) } @@ -279,10 +302,12 @@ func Fuzz(input []byte) int { if len(input) < 100 { return -1 } + f := newFuzzer(input) if f.exhausted { return -1 } + for !f.exhausted { switch f.randomInt(8) { case 0: @@ -298,6 +323,7 @@ func Fuzz(input []byte) int { } else { req.Query.Origin.Number = uint64(f.randomInt(f.chainLen * 2)) } + f.doFuzz(l.GetBlockHeadersMsg, req) case 1: @@ -305,6 +331,7 @@ func Fuzz(input []byte) int { for i := range req.Hashes { req.Hashes[i] = f.randomBlockHash() } + f.doFuzz(l.GetBlockBodiesMsg, req) case 2: @@ -315,6 +342,7 @@ func Fuzz(input []byte) int { AccKey: f.randomAddrHash(), } } + f.doFuzz(l.GetCodeMsg, req) case 3: @@ -322,6 +350,7 @@ func Fuzz(input []byte) int { for i := range req.Hashes { req.Hashes[i] = f.randomBlockHash() } + f.doFuzz(l.GetReceiptsMsg, req) case 4: @@ -342,6 +371,7 @@ func Fuzz(input []byte) int { } } } + f.doFuzz(l.GetProofsV2Msg, req) case 5: @@ -377,11 +407,13 @@ func Fuzz(input []byte) int { } } } + 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() { @@ -390,8 +422,10 @@ func Fuzz(input []byte) int { 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: @@ -399,8 +433,10 @@ func Fuzz(input []byte) int { for i := range req.Hashes { req.Hashes[i] = f.randomTxHash() } + f.doFuzz(l.GetTxStatusMsg, req) } } + return 0 } diff --git a/tests/fuzzers/rangeproof/debug/main.go b/tests/fuzzers/rangeproof/debug/main.go index e7a894af61..06ab69b634 100644 --- a/tests/fuzzers/rangeproof/debug/main.go +++ b/tests/fuzzers/rangeproof/debug/main.go @@ -30,6 +30,7 @@ func main() { fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") os.Exit(1) } + crasher := os.Args[1] data, err := os.ReadFile(crasher) @@ -37,5 +38,6 @@ func main() { 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 index 2881c7a7c2..b86659fcb5 100644 --- a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go +++ b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go @@ -50,6 +50,7 @@ func (f *fuzzer) randBytes(n int) []byte { if _, err := f.input.Read(r); err != nil { f.exhausted = true } + return r } @@ -58,6 +59,7 @@ func (f *fuzzer) readInt() uint64 { if err := binary.Read(f.input, binary.LittleEndian, &x); err != nil { f.exhausted = true } + return x } @@ -69,11 +71,14 @@ func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) { 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.MustUpdate(value.k, value.v) trie.MustUpdate(value2.k, value2.v) + vals[string(value.k)] = value vals[string(value2.k)] = value2 } + if f.exhausted { return nil, nil } @@ -83,56 +88,73 @@ func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) { v := f.randBytes(20) value := &kv{k, v, false} trie.MustUpdate(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: @@ -158,15 +180,16 @@ func (f *fuzzer) fuzz() int { 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 hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof) @@ -176,6 +199,7 @@ func (f *fuzzer) fuzz() int { } } } + return ok } @@ -193,10 +217,12 @@ 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/tests/fuzzers/rlp/rlp_fuzzer.go b/tests/fuzzers/rlp/rlp_fuzzer.go index 44432f13d4..262e62d67a 100644 --- a/tests/fuzzers/rlp/rlp_fuzzer.go +++ b/tests/fuzzers/rlp/rlp_fuzzer.go @@ -33,6 +33,7 @@ func decodeEncode(input []byte, val interface{}, i int) { if err != nil { panic(err) } + if !bytes.Equal(input, output) { panic(fmt.Sprintf("case %d: encode-decode is not equal, \ninput : %x\noutput: %x", i, input, output)) } @@ -43,6 +44,7 @@ func Fuzz(input []byte) int { if len(input) == 0 { return 0 } + if len(input) > 500*1024 { return 0 } @@ -63,6 +65,7 @@ func Fuzz(input []byte) int { { decodeEncode(input, new(interface{}), i) + i++ } { @@ -71,7 +74,9 @@ func Fuzz(input []byte) int { String string Bytes []byte } + decodeEncode(input, &v, i) + i++ } @@ -82,8 +87,11 @@ func Fuzz(input []byte) int { Slice []*Types Iface []interface{} } + var v Types + decodeEncode(input, &v, i) + i++ } { @@ -97,12 +105,16 @@ func Fuzz(input []byte) int { Array [3]*AllTypes Iface []interface{} } + var v AllTypes + decodeEncode(input, &v, i) + i++ } { decodeEncode(input, [10]byte{}, i) + i++ } { @@ -110,27 +122,43 @@ func Fuzz(input []byte) int { Byte [10]byte Rool [10]bool } + decodeEncode(input, &v, i) + i++ } { var h types.Header + decodeEncode(input, &h, i) + i++ + var b types.Block + decodeEncode(input, &b, i) + i++ + var t types.Transaction + decodeEncode(input, &t, i) + i++ + var txs types.Transactions + decodeEncode(input, &txs, i) + i++ + var rs types.Receipts + decodeEncode(input, &rs, i) } { i++ + var v struct { AnIntPtr *big.Int AnInt big.Int @@ -138,7 +166,9 @@ func Fuzz(input []byte) int { AnU256 uint256.Int NotAnU256 [4]uint64 } + decodeEncode(input, &v, i) } + return 1 } diff --git a/tests/fuzzers/runtime/runtime_fuzz.go b/tests/fuzzers/runtime/runtime_fuzz.go index b30e9243d8..0452b0d0ae 100644 --- a/tests/fuzzers/runtime/runtime_fuzz.go +++ b/tests/fuzzers/runtime/runtime_fuzz.go @@ -32,5 +32,6 @@ func Fuzz(input []byte) int { if err != nil && len(err.Error()) > 6 && err.Error()[:7] == "invalid" { return 0 } + return 1 } diff --git a/tests/fuzzers/secp256k1/secp_fuzzer.go b/tests/fuzzers/secp256k1/secp_fuzzer.go index 47083d5fe3..d1d3f05127 100644 --- a/tests/fuzzers/secp256k1/secp_fuzzer.go +++ b/tests/fuzzers/secp256k1/secp_fuzzer.go @@ -42,9 +42,11 @@ func Fuzz(input []byte) int { x2, y2 := curveB.ScalarBaseMult(dataP2) resAX, resAY := curveA.Add(x1, y1, x2, y2) resBX, resBY := curveB.Add(x1, y1, x2, y2) + if resAX.Cmp(resBX) != 0 || resAY.Cmp(resBY) != 0 { fmt.Printf("%s %s %s %s\n", x1, y1, x2, y2) panic(fmt.Sprintf("Addition failed: geth: %s %s btcd: %s %s", resAX, resAY, resBX, resBY)) } + return 0 } diff --git a/tests/fuzzers/snap/debug/main.go b/tests/fuzzers/snap/debug/main.go index b417fcd8ac..7c4628fe75 100644 --- a/tests/fuzzers/snap/debug/main.go +++ b/tests/fuzzers/snap/debug/main.go @@ -28,6 +28,7 @@ func main() { fmt.Fprintf(os.Stderr, "Usage: debug \n") os.Exit(1) } + crasher := os.Args[1] data, err := os.ReadFile(crasher) @@ -35,5 +36,6 @@ func main() { fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) os.Exit(1) } + snap.FuzzTrieNodes(data) } diff --git a/tests/fuzzers/snap/fuzz_handler.go b/tests/fuzzers/snap/fuzz_handler.go index 31458906c3..f49893247e 100644 --- a/tests/fuzzers/snap/fuzz_handler.go +++ b/tests/fuzzers/snap/fuzz_handler.go @@ -40,7 +40,9 @@ var trieRoot common.Hash func getChain() *core.BlockChain { ga := make(core.GenesisAlloc, 1000) + var a = make([]byte, 20) + var mkStorage = func(k, v int) (common.Hash, common.Hash) { var kB = make([]byte, 32) var vB = make([]byte, 32) @@ -48,17 +50,22 @@ func getChain() *core.BlockChain { binary.LittleEndian.PutUint64(vB, uint64(v)) return common.BytesToHash(kB), common.BytesToHash(vB) } + storage := make(map[common.Hash]common.Hash) + for i := 0; i < 10; i++ { k, v := mkStorage(i, i) storage[k] = v } + for i := 0; i < 1000; i++ { binary.LittleEndian.PutUint64(a, uint64(i+0xff)) + acc := core.GenesisAccount{Balance: big.NewInt(int64(i))} if i%2 == 1 { acc.Storage = storage } + ga[common.BytesToAddress(a)] = acc } @@ -77,10 +84,12 @@ func getChain() *core.BlockChain { SnapshotWait: true, } trieRoot = blocks[len(blocks)-1].Root() + bc, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), cacheConf, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if _, err := bc.InsertChain(blocks); err != nil { panic(err) } + return bc } @@ -117,11 +126,15 @@ func doFuzz(input []byte, obj interface{}, code int) int { if len(input) > 1024*4 { return -1 } + bc := getChain() defer bc.Stop() backend := &dummyBackend{bc} + fuzz.NewFromGoFuzz(input).Fuzz(obj) + var data []byte + switch p := obj.(type) { case *snap.GetTrieNodesPacket: p.Root = trieRoot @@ -129,18 +142,21 @@ func doFuzz(input []byte, obj interface{}, code int) int { default: data, _ = rlp.EncodeToBytes(obj) } + cli := &dummyRW{ code: uint64(code), data: data, } peer := snap.NewFakePeer(65, "gazonk01", cli) err := snap.HandleMessage(backend, peer) + switch { case err == nil && cli.writeCount != 1: panic(fmt.Sprintf("Expected 1 response, got %d", cli.writeCount)) case err != nil && cli.writeCount != 0: panic(fmt.Sprintf("Expected 0 response, got %d", cli.writeCount)) } + return 1 } diff --git a/tests/fuzzers/stacktrie/debug/main.go b/tests/fuzzers/stacktrie/debug/main.go index 359b6e070e..8bdda2b5f7 100644 --- a/tests/fuzzers/stacktrie/debug/main.go +++ b/tests/fuzzers/stacktrie/debug/main.go @@ -28,6 +28,7 @@ func main() { fmt.Fprintf(os.Stderr, "Usage: debug ") os.Exit(1) } + crasher := os.Args[1] data, err := os.ReadFile(crasher) @@ -35,5 +36,6 @@ func main() { fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) os.Exit(1) } + stacktrie.Debug(data) } diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go index 809dba8ce5..826a7de9a1 100644 --- a/tests/fuzzers/stacktrie/trie_fuzzer.go +++ b/tests/fuzzers/stacktrie/trie_fuzzer.go @@ -44,17 +44,21 @@ func (f *fuzzer) read(size int) []byte { 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 } @@ -78,8 +82,10 @@ func (s *spongeDb) Put(key []byte, value []byte) error { if s.debug { fmt.Printf("db.Put %x : %x\n", key, value) } + s.sponge.Write(key) s.sponge.Write(value) + return nil } func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") } @@ -131,6 +137,7 @@ func Fuzz(data []byte) int { input: bytes.NewReader(data), exhausted: false, } + return f.fuzz() } @@ -140,6 +147,7 @@ func Debug(data []byte) int { exhausted: false, debugging: true, } + return f.fuzz() } @@ -164,20 +172,27 @@ func (f *fuzzer) fuzz() int { for i := 0; !f.exhausted && i < maxElements; i++ { k := f.read(32) v := f.readSlice(1, 500) + if f.exhausted { // If it was exhausted while reading, the value may be all zeroes, // 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.MustUpdate(k, v) + useful = true } + if !useful { return 0 } @@ -191,19 +206,25 @@ func (f *fuzzer) fuzz() int { // Stacktrie requires sorted insertion sort.Sort(vals) + for _, kv := range vals { if f.debugging { fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v) } + trieB.MustUpdate(kv.k, kv.v) } + rootB := trieB.Hash() trieB.Commit() + if rootA != rootB { panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB)) } + sumA := spongeA.sponge.Sum(nil) sumB := spongeB.sponge.Sum(nil) + if !bytes.Equal(sumA, sumB) { panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB)) } @@ -222,33 +243,43 @@ func (f *fuzzer) fuzz() int { }) checked int ) + for _, kv := range vals { trieC.MustUpdate(kv.k, kv.v) } + rootC, _ := trieC.Commit() if rootA != rootC { panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC)) } + trieA, _ = trie.New(trie.TrieID(rootA), dbA) + iterA := trieA.NodeIterator(nil) for iterA.Next(true) { if iterA.Hash() == (common.Hash{}) { if _, present := nodeset[string(iterA.Path())]; present { panic("unexpected tiny node") } + continue } + nodeBlob, present := nodeset[string(iterA.Path())] if !present { panic("missing node") } + if !bytes.Equal(nodeBlob, iterA.NodeBlob()) { panic("node blob is not matched") } + checked += 1 } + if checked != len(nodeset) { panic("node number is not matched") } + return 1 } diff --git a/tests/fuzzers/trie/trie-fuzzer.go b/tests/fuzzers/trie/trie-fuzzer.go index c0cbceff31..514c6725bd 100644 --- a/tests/fuzzers/trie/trie-fuzzer.go +++ b/tests/fuzzers/trie/trie-fuzzer.go @@ -83,6 +83,7 @@ func (ds *dataSource) Ended() bool { func Generate(input []byte) randTest { var allKeys [][]byte + r := newDataSource(input) genKey := func() []byte { if len(allKeys) < 2 || r.readByte() < 0x0f { @@ -90,6 +91,7 @@ func Generate(input []byte) randTest { key := make([]byte, r.readByte()%50) r.Read(key) allKeys = append(allKeys, key) + return key } // use existing key @@ -108,6 +110,7 @@ func Generate(input []byte) randTest { case opGet, opDelete, opProve: step.key = genKey() } + steps = append(steps, step) if len(steps) > 500 { break @@ -132,9 +135,11 @@ func Fuzz(input []byte) int { if len(program) == 0 { return 0 } + if err := runRandTest(program); err != nil { panic(err) } + return 1 } @@ -155,6 +160,7 @@ func runRandTest(rt randTest) error { case opGet: v := tr.MustGet(step.key) want := values[string(step.key)] + if string(v) != want { rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want) } @@ -167,17 +173,21 @@ func runRandTest(rt randTest) error { return err } } + newtr, err := trie.New(trie.TrieID(hash), triedb) if err != nil { return err } + tr = newtr case opItercheckhash: checktr := trie.NewEmpty(triedb) it := trie.NewIterator(tr.NodeIterator(nil)) + for it.Next() { checktr.MustUpdate(it.Key, it.Value) } + if tr.Hash() != checktr.Hash() { return fmt.Errorf("hash mismatch in opItercheckhash") } @@ -189,5 +199,6 @@ func runRandTest(rt randTest) error { return rt[i].err } } + return nil } diff --git a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go index fc15e07c7e..b22c357bad 100644 --- a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go +++ b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go @@ -42,6 +42,7 @@ func init() { for i := 0; i < len(peers); i++ { peers[i] = fmt.Sprintf("Peer #%d", i) } + txs = make([]*types.Transaction, 65536) // We need to bump enough to hit all the limits for i := 0; i < len(txs); i++ { txs[i] = types.NewTransaction(rand.Uint64(), common.Address{byte(rand.Intn(256))}, new(big.Int), 0, new(big.Int), nil) @@ -53,6 +54,7 @@ func Fuzz(input []byte) int { if len(input) > 16*1024 { return 0 } + verbose := false r := bytes.NewReader(input) @@ -63,6 +65,7 @@ func Fuzz(input []byte) int { if err != nil { return 0 } + switch limit % 4 { case 0: txs = txs[:4] @@ -95,6 +98,7 @@ func Fuzz(input []byte) int { if err != nil { return 0 } + switch cmd % 4 { case 0: // Notify a new set of transactions: @@ -105,29 +109,35 @@ func Fuzz(input []byte) int { if err != nil { return 0 } + peer := peers[int(peerIdx)%len(peers)] announceCnt, err := r.ReadByte() if err != nil { return 0 } + announce := int(announceCnt) % (2 * len(txs)) // No point in generating too many duplicates var ( announceIdxs = make([]int, announce) announces = make([]common.Hash, announce) ) + for i := 0; i < len(announces); i++ { annBuf := make([]byte, 2) if n, err := r.Read(annBuf); err != nil || n != 2 { return 0 } + announceIdxs[i] = (int(annBuf[0])*256 + int(annBuf[1])) % len(txs) announces[i] = txs[announceIdxs[i]].Hash() } + if verbose { fmt.Println("Notify", peer, announceIdxs) } + if err := f.Notify(peer, announces); err != nil { panic(err) } @@ -141,34 +151,41 @@ func Fuzz(input []byte) int { if err != nil { return 0 } + peer := peers[int(peerIdx)%len(peers)] deliverCnt, err := r.ReadByte() if err != nil { return 0 } + deliver := int(deliverCnt) % (2 * len(txs)) // No point in generating too many duplicates var ( deliverIdxs = make([]int, deliver) deliveries = make([]*types.Transaction, deliver) ) + for i := 0; i < len(deliveries); i++ { deliverBuf := make([]byte, 2) if n, err := r.Read(deliverBuf); err != nil || n != 2 { return 0 } + deliverIdxs[i] = (int(deliverBuf[0])*256 + int(deliverBuf[1])) % len(txs) deliveries[i] = txs[deliverIdxs[i]] } + directFlag, err := r.ReadByte() if err != nil { return 0 } + direct := (directFlag % 2) == 0 if verbose { fmt.Println("Enqueue", peer, deliverIdxs, direct) } + if err := f.Enqueue(peer, deliveries, direct); err != nil { panic(err) } @@ -180,10 +197,12 @@ func Fuzz(input []byte) int { if err != nil { return 0 } + peer := peers[int(peerIdx)%len(peers)] if verbose { fmt.Println("Drop", peer) } + if err := f.Drop(peer); err != nil { panic(err) } @@ -195,10 +214,12 @@ func Fuzz(input []byte) int { if err != nil { return 0 } + tick := time.Duration(tickCnt) * 100 * time.Millisecond if verbose { fmt.Println("Sleep", tick) } + clock.Run(tick) } } diff --git a/tests/fuzzers/vflux/clientpool-fuzzer.go b/tests/fuzzers/vflux/clientpool-fuzzer.go index b3b523cc82..65e3cef73c 100644 --- a/tests/fuzzers/vflux/clientpool-fuzzer.go +++ b/tests/fuzzers/vflux/clientpool-fuzzer.go @@ -77,15 +77,19 @@ func (p *clientPeer) InactiveAllowance() time.Duration { func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) { origin, originTotal := p.capacity, p.fuzzer.activeCap + p.fuzzer.activeCap -= p.capacity if p.capacity != 0 { p.fuzzer.activeCount-- } + p.capacity = newCap p.fuzzer.activeCap += p.capacity + if p.capacity != 0 { p.fuzzer.activeCount++ } + doLog("Update capacity", "peer", p.node.ID(), "origin", origin, "cap", newCap, "origintotal", originTotal, "total", p.fuzzer.activeCap, "requested", requested) } @@ -93,9 +97,11 @@ func (p *clientPeer) Disconnect() { origin, originTotal := p.capacity, p.fuzzer.activeCap p.fuzzer.disconnectList = append(p.fuzzer.disconnectList, p) p.fuzzer.activeCap -= p.capacity + if p.capacity != 0 { p.fuzzer.activeCount-- } + p.capacity = 0 p.balance = nil doLog("Disconnect", "peer", p.node.ID(), "origin", origin, "origintotal", originTotal, "total", p.fuzzer.activeCap) @@ -113,6 +119,7 @@ func newFuzzer(input []byte) *fuzzer { timeout: f.randomDelay(), } } + return f } @@ -121,6 +128,7 @@ func (f *fuzzer) read(size int) []byte { if _, err := f.input.Read(out); err != nil { f.exhausted = true } + return out } @@ -138,13 +146,16 @@ 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)) } @@ -156,11 +167,14 @@ func (f *fuzzer) randomTokenAmount(signed bool) int64 { if x <= math.MaxInt64 { return -int64(x) } + return math.MinInt64 } + if x <= math.MaxInt64 { return int64(x) } + return math.MaxInt64 } @@ -169,6 +183,7 @@ func (f *fuzzer) randomDelay() time.Duration { if delay < 128 { return time.Duration(delay) * time.Second } + return 0 } @@ -218,19 +233,23 @@ func FuzzClientPool(input []byte) int { if len(input) > 10000 { return -1 } + f := newFuzzer(input) if f.exhausted { return 0 } + clock := &mclock.Simulated{} db := memorydb.New() pool := vfs.NewClientPool(db, 10, f.randomDelay(), clock, func() bool { return true }) pool.Start() + defer pool.Stop() count := 0 for !f.exhausted && count < 1000 { count++ + switch f.randomInt(11) { case 0: i := int(f.randomByte()) @@ -267,6 +286,7 @@ func FuzzClientPool(input []byte) int { bias = f.randomDelay() requested = f.randomBool() ) + pool.SetCapacity(f.peers[index].node, reqCap, bias, requested) doLog("Set capacity", "id", f.peers[index].node.ID(), "reqcap", reqCap, "bias", bias, "requested", requested) case 7: @@ -298,16 +318,20 @@ func FuzzClientPool(input []byte) int { if v.Type < 2 { v.Value = *big.NewInt(f.randomTokenAmount(false)) } + req.AddTokens[i] = v } + reqEnc, err := rlp.EncodeToBytes(&req) if err != nil { panic(err) } + p := int(f.randomByte()) if p < len(reqEnc) { reqEnc[p] = f.randomByte() } + pool.Handle(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].freeID, vflux.CapacityQueryName, reqEnc) } @@ -315,19 +339,24 @@ func FuzzClientPool(input []byte) int { pool.Unregister(peer) doLog("Unregister peer", "id", peer.node.ID()) } + f.disconnectList = nil if d := f.randomDelay(); d > 0 { clock.Run(d) } + doLog("Clientpool stats in fuzzer", "count", f.activeCap, "maxcount", f.maxCount, "cap", f.activeCap, "maxcap", f.maxCap) activeCount, activeCap := pool.Active() doLog("Clientpool stats in pool", "count", activeCount, "cap", activeCap) + if activeCount != f.activeCount || activeCap != f.activeCap { panic(nil) } + if f.activeCount > f.maxCount || f.activeCap > f.maxCap { panic(nil) } } + return 0 } diff --git a/tests/fuzzers/vflux/debug/main.go b/tests/fuzzers/vflux/debug/main.go index e6cec04606..467214b803 100644 --- a/tests/fuzzers/vflux/debug/main.go +++ b/tests/fuzzers/vflux/debug/main.go @@ -33,11 +33,14 @@ func main() { fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") os.Exit(1) } + crasher := os.Args[1] data, err := os.ReadFile(crasher) + if err != nil { fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) os.Exit(1) } + vflux.FuzzClientPool(data) } diff --git a/tests/gen_btheader.go b/tests/gen_btheader.go index 985ea692d7..5f802baf44 100644 --- a/tests/gen_btheader.go +++ b/tests/gen_btheader.go @@ -36,6 +36,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) { BaseFeePerGas *math.HexOrDecimal256 WithdrawalsRoot *common.Hash } + var enc btHeader enc.Bloom = b.Bloom enc.Coinbase = b.Coinbase @@ -55,6 +56,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) { enc.Timestamp = math.HexOrDecimal64(b.Timestamp) enc.BaseFeePerGas = (*math.HexOrDecimal256)(b.BaseFeePerGas) enc.WithdrawalsRoot = b.WithdrawalsRoot + return json.Marshal(&enc) } @@ -80,63 +82,83 @@ func (b *btHeader) UnmarshalJSON(input []byte) error { BaseFeePerGas *math.HexOrDecimal256 WithdrawalsRoot *common.Hash } + var dec btHeader if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Bloom != nil { b.Bloom = *dec.Bloom } + if dec.Coinbase != nil { b.Coinbase = *dec.Coinbase } + if dec.MixHash != nil { b.MixHash = *dec.MixHash } + if dec.Nonce != nil { b.Nonce = *dec.Nonce } + if dec.Number != nil { b.Number = (*big.Int)(dec.Number) } + if dec.Hash != nil { b.Hash = *dec.Hash } + if dec.ParentHash != nil { b.ParentHash = *dec.ParentHash } + if dec.ReceiptTrie != nil { b.ReceiptTrie = *dec.ReceiptTrie } + if dec.StateRoot != nil { b.StateRoot = *dec.StateRoot } + if dec.TransactionsTrie != nil { b.TransactionsTrie = *dec.TransactionsTrie } + if dec.UncleHash != nil { b.UncleHash = *dec.UncleHash } + if dec.ExtraData != nil { b.ExtraData = *dec.ExtraData } + if dec.Difficulty != nil { b.Difficulty = (*big.Int)(dec.Difficulty) } + if dec.GasLimit != nil { b.GasLimit = uint64(*dec.GasLimit) } + if dec.GasUsed != nil { b.GasUsed = uint64(*dec.GasUsed) } + if dec.Timestamp != nil { b.Timestamp = uint64(*dec.Timestamp) } + if dec.BaseFeePerGas != nil { b.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas) } + if dec.WithdrawalsRoot != nil { b.WithdrawalsRoot = dec.WithdrawalsRoot } + return nil } diff --git a/tests/gen_difficultytest.go b/tests/gen_difficultytest.go index cd15ae31b5..d877ec7ab8 100644 --- a/tests/gen_difficultytest.go +++ b/tests/gen_difficultytest.go @@ -22,6 +22,7 @@ func (d DifficultyTest) MarshalJSON() ([]byte, error) { CurrentBlockNumber math.HexOrDecimal64 `json:"currentBlockNumber"` CurrentDifficulty *math.HexOrDecimal256 `json:"currentDifficulty"` } + var enc DifficultyTest enc.ParentTimestamp = math.HexOrDecimal64(d.ParentTimestamp) enc.ParentDifficulty = (*math.HexOrDecimal256)(d.ParentDifficulty) @@ -29,6 +30,7 @@ func (d DifficultyTest) MarshalJSON() ([]byte, error) { enc.CurrentTimestamp = math.HexOrDecimal64(d.CurrentTimestamp) enc.CurrentBlockNumber = math.HexOrDecimal64(d.CurrentBlockNumber) enc.CurrentDifficulty = (*math.HexOrDecimal256)(d.CurrentDifficulty) + return json.Marshal(&enc) } @@ -42,27 +44,35 @@ func (d *DifficultyTest) UnmarshalJSON(input []byte) error { CurrentBlockNumber *math.HexOrDecimal64 `json:"currentBlockNumber"` CurrentDifficulty *math.HexOrDecimal256 `json:"currentDifficulty"` } + var dec DifficultyTest if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.ParentTimestamp != nil { d.ParentTimestamp = uint64(*dec.ParentTimestamp) } + if dec.ParentDifficulty != nil { d.ParentDifficulty = (*big.Int)(dec.ParentDifficulty) } + if dec.UncleHash != nil { d.UncleHash = *dec.UncleHash } + if dec.CurrentTimestamp != nil { d.CurrentTimestamp = uint64(*dec.CurrentTimestamp) } + if dec.CurrentBlockNumber != nil { d.CurrentBlockNumber = uint64(*dec.CurrentBlockNumber) } + if dec.CurrentDifficulty != nil { d.CurrentDifficulty = (*big.Int)(dec.CurrentDifficulty) } + return nil } diff --git a/tests/gen_stenv.go b/tests/gen_stenv.go index 71f0063178..5d4208bf25 100644 --- a/tests/gen_stenv.go +++ b/tests/gen_stenv.go @@ -24,6 +24,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) { Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"` } + var enc stEnv enc.Coinbase = common.UnprefixedAddress(s.Coinbase) enc.Difficulty = (*math.HexOrDecimal256)(s.Difficulty) @@ -32,6 +33,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) { enc.Number = math.HexOrDecimal64(s.Number) enc.Timestamp = math.HexOrDecimal64(s.Timestamp) enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee) + return json.Marshal(&enc) } @@ -46,34 +48,45 @@ func (s *stEnv) UnmarshalJSON(input []byte) error { Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"` } + var dec stEnv if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Coinbase == nil { return errors.New("missing required field 'currentCoinbase' for stEnv") } + s.Coinbase = common.Address(*dec.Coinbase) if dec.Difficulty != nil { s.Difficulty = (*big.Int)(dec.Difficulty) } + if dec.Random != nil { s.Random = (*big.Int)(dec.Random) } + if dec.GasLimit == nil { return errors.New("missing required field 'currentGasLimit' for stEnv") } + s.GasLimit = uint64(*dec.GasLimit) + if dec.Number == nil { return errors.New("missing required field 'currentNumber' for stEnv") } + s.Number = uint64(*dec.Number) + if dec.Timestamp == nil { return errors.New("missing required field 'currentTimestamp' for stEnv") } + s.Timestamp = uint64(*dec.Timestamp) if dec.BaseFee != nil { s.BaseFee = (*big.Int)(dec.BaseFee) } + return nil } diff --git a/tests/gen_sttransaction.go b/tests/gen_sttransaction.go index 7693a207a5..ce6ea8d3a8 100644 --- a/tests/gen_sttransaction.go +++ b/tests/gen_sttransaction.go @@ -27,6 +27,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { Value []string `json:"value"` PrivateKey hexutil.Bytes `json:"secretKey"` } + var enc stTransaction enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice) enc.MaxFeePerGas = (*math.HexOrDecimal256)(s.MaxFeePerGas) @@ -35,14 +36,17 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { 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 { enc.GasLimit[k] = math.HexOrDecimal64(v) } } + enc.Value = s.Value enc.PrivateKey = s.PrivateKey + return json.Marshal(&enc) } @@ -60,42 +64,54 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error { Value []string `json:"value"` PrivateKey *hexutil.Bytes `json:"secretKey"` } + var dec stTransaction if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.GasPrice != nil { s.GasPrice = (*big.Int)(dec.GasPrice) } + if dec.MaxFeePerGas != nil { s.MaxFeePerGas = (*big.Int)(dec.MaxFeePerGas) } + if dec.MaxPriorityFeePerGas != nil { s.MaxPriorityFeePerGas = (*big.Int)(dec.MaxPriorityFeePerGas) } + if dec.Nonce != nil { s.Nonce = uint64(*dec.Nonce) } + if dec.To != nil { s.To = *dec.To } + 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 { s.GasLimit[k] = uint64(v) } } + if dec.Value != nil { s.Value = dec.Value } + if dec.PrivateKey != nil { s.PrivateKey = *dec.PrivateKey } + return nil } diff --git a/tests/init.go b/tests/init.go index a8800b8ce2..13456feb9a 100644 --- a/tests/init.go +++ b/tests/init.go @@ -332,7 +332,9 @@ func AvailableForks() []string { for k := range Forks { availableForks = append(availableForks, k) } + sort.Strings(availableForks) + return availableForks } diff --git a/tests/rlp_test_util.go b/tests/rlp_test_util.go index 15acb3a244..7e61ebcfcf 100644 --- a/tests/rlp_test_util.go +++ b/tests/rlp_test_util.go @@ -49,9 +49,11 @@ func FromHex(s string) ([]byte, error) { if len(s) > 1 && (s[0:2] == "0x" || s[0:2] == "0X") { s = s[2:] } + if len(s)%2 == 1 { s = "0" + s } + return hex.DecodeString(s) } @@ -69,26 +71,31 @@ func (t *RLPTest) Run() error { // Check whether encoding the value produces the same bytes. in := translateJSON(t.In) + b, err := rlp.EncodeToBytes(in) if err != nil { return fmt.Errorf("encode failed: %v", err) } + if !bytes.Equal(b, outb) { return fmt.Errorf("encode produced %x, want %x", b, outb) } // Test stream decoding. s := rlp.NewStream(bytes.NewReader(outb), 0) + return checkDecodeFromJSON(s, in) } func checkDecodeInterface(b []byte, isValid bool) error { err := rlp.DecodeBytes(b, new(interface{})) + switch { case isValid && err != nil: return fmt.Errorf("decoding failed: %v", err) case !isValid && err == nil: return fmt.Errorf("decoding of invalid value succeeded") } + return nil } @@ -103,14 +110,17 @@ func translateJSON(v interface{}) interface{} { if !ok { panic(fmt.Errorf("bad test: bad big int: %q", v)) } + return big } + return []byte(v) case []interface{}: new := make([]interface{}, len(v)) for i := range v { new[i] = translateJSON(v[i]) } + return new default: panic(fmt.Errorf("can't handle %T", v)) @@ -128,6 +138,7 @@ func checkDecodeFromJSON(s *rlp.Stream, exp interface{}) error { if err != nil { return addStack("Uint", exp, err) } + if i != exp { return addStack("Uint", exp, fmt.Errorf("result mismatch: got %d", i)) } @@ -136,6 +147,7 @@ func checkDecodeFromJSON(s *rlp.Stream, exp interface{}) error { if err := s.Decode(&big); err != nil { return addStack("Big", exp, err) } + if big.Cmp(exp) != 0 { return addStack("Big", exp, fmt.Errorf("result mismatch: got %d", big)) } @@ -144,6 +156,7 @@ func checkDecodeFromJSON(s *rlp.Stream, exp interface{}) error { if err != nil { return addStack("Bytes", exp, err) } + if !bytes.Equal(b, exp) { return addStack("Bytes", exp, fmt.Errorf("result mismatch: got %x", b)) } @@ -151,22 +164,26 @@ func checkDecodeFromJSON(s *rlp.Stream, exp interface{}) error { if _, err := s.List(); err != nil { return addStack("List", exp, err) } + for i, v := range exp { if err := checkDecodeFromJSON(s, v); err != nil { return addStack(fmt.Sprintf("[%d]", i), exp, err) } } + if err := s.ListEnd(); err != nil { return addStack("ListEnd", exp, err) } default: panic(fmt.Errorf("unhandled type: %T", exp)) } + return nil } func addStack(op string, val interface{}, err error) error { lines := strings.Split(err.Error(), "\n") lines = append(lines, fmt.Sprintf("\t%s: %v", op, val)) + return errors.New(strings.Join(lines, "\n")) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index e772b47f13..ab65a635f8 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -134,9 +134,11 @@ func GetChainConfig(forkString string) (baseConfig *params.ChainConfig, eips []i ok bool baseName, eipsStrings = splitForks[0], splitForks[1:] ) + if baseConfig, ok = Forks[baseName]; !ok { return nil, nil, UnsupportedForkError{baseName} } + for _, eip := range eipsStrings { if eipNum, err := strconv.Atoi(eip); err != nil { return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) @@ -144,20 +146,24 @@ func GetChainConfig(forkString string) (baseConfig *params.ChainConfig, eips []i if !vm.ValidEip(eipNum) { return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) } + eips = append(eips, eipNum) } } + return baseConfig, eips, nil } // Subtests returns all valid subtests of the test. func (t *StateTest) Subtests() []StateSubtest { var sub []StateSubtest + for fork, pss := range t.json.Post { for i := range pss { sub = append(sub, StateSubtest{fork, i}) } } + return sub } @@ -200,15 +206,18 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bo //nolint:nilerr return snaps, statedb, nil } + post := t.json.Post[subtest.Fork][subtest.Index] // N.B: We need to do this in a two-step process, because the first Commit takes care // of suicides, and we need to touch the coinbase _after_ it has potentially suicided. if root != common.Hash(post.Root) { return snaps, statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) } + if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) { return snaps, statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) } + return snaps, statedb, nil } @@ -218,6 +227,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh if err != nil { return nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork} } + vmconfig.ExtraEips = eips block := t.genesis(config).ToBlock() snaps, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter) @@ -231,7 +241,9 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh baseFee = big.NewInt(0x0a) } } + post := t.json.Post[subtest.Fork][subtest.Index] + msg, err := t.json.Tx.toMessage(post, baseFee) if err != nil { return nil, nil, common.Hash{}, err @@ -240,6 +252,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh // Try to recover tx with current signer if len(post.TxBytes) != 0 { var ttx types.Transaction + err := ttx.UnmarshalBinary(post.TxBytes) if err != nil { return nil, nil, common.Hash{}, err @@ -297,11 +310,13 @@ func (t *StateTest) gasLimit(subtest StateSubtest) uint64 { func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool) (*snapshot.Tree, *state.StateDB) { sdb := state.NewDatabaseWithConfig(db, &trie.Config{Preimages: true}) + statedb, _ := state.New(common.Hash{}, sdb, nil) for addr, a := range accounts { statedb.SetCode(addr, a.Code) statedb.SetNonce(addr, a.Nonce) statedb.SetBalance(addr, a.Balance) + for k, v := range a.Storage { statedb.SetState(addr, k, v) } @@ -310,6 +325,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo root, _ := statedb.Commit(false) var snaps *snapshot.Tree + if snapshotter { snapconfig := snapshot.Config{ CacheSize: 1, @@ -319,7 +335,9 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo } snaps, _ = snapshot.New(snapconfig, db, sdb.TrieDB(), root) } + statedb, _ = state.New(root, sdb, snaps) + return snaps, statedb } @@ -338,17 +356,20 @@ func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis { genesis.Mixhash = common.BigToHash(t.json.Env.Random) genesis.Difficulty = big.NewInt(0) } + return genesis } func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Message, error) { // Derive sender from private key if present. var from common.Address + if len(tx.PrivateKey) > 0 { key, err := crypto.ToECDSA(tx.PrivateKey) if err != nil { return nil, fmt.Errorf("invalid private key: %v", err) } + from = crypto.PubkeyToAddress(key.PublicKey) } // Parse recipient if present. @@ -364,47 +385,59 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess if ps.Indexes.Data > len(tx.Data) { return nil, fmt.Errorf("tx data index %d out of bounds", ps.Indexes.Data) } + if ps.Indexes.Value > len(tx.Value) { return nil, fmt.Errorf("tx value index %d out of bounds", ps.Indexes.Value) } + if ps.Indexes.Gas > len(tx.GasLimit) { return nil, fmt.Errorf("tx gas limit index %d out of bounds", ps.Indexes.Gas) } + dataHex := tx.Data[ps.Indexes.Data] valueHex := tx.Value[ps.Indexes.Value] gasLimit := tx.GasLimit[ps.Indexes.Gas] // Value, Data hex encoding is messy: https://github.com/ethereum/tests/issues/203 value := new(big.Int) + if valueHex != "0x" { v, ok := math.ParseBig256(valueHex) if !ok { return nil, fmt.Errorf("invalid tx value %q", valueHex) } + value = v } + data, err := hex.DecodeString(strings.TrimPrefix(dataHex, "0x")) if err != nil { return nil, fmt.Errorf("invalid tx data %q", dataHex) } + var accessList types.AccessList if tx.AccessLists != nil && tx.AccessLists[ps.Indexes.Data] != nil { accessList = *tx.AccessLists[ps.Indexes.Data] } // If baseFee provided, set gasPrice to effectiveGasPrice. gasPrice := tx.GasPrice + if baseFee != nil { if tx.MaxFeePerGas == nil { tx.MaxFeePerGas = gasPrice } + if tx.MaxFeePerGas == nil { tx.MaxFeePerGas = new(big.Int) } + if tx.MaxPriorityFeePerGas == nil { tx.MaxPriorityFeePerGas = tx.MaxFeePerGas } + gasPrice = math.BigMin(new(big.Int).Add(tx.MaxPriorityFeePerGas, baseFee), tx.MaxFeePerGas) } + if gasPrice == nil { return nil, fmt.Errorf("no gas price provided") } @@ -421,6 +454,7 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess Data: data, AccessList: accessList, } + return msg, nil } @@ -428,6 +462,7 @@ func rlpHash(x interface{}) (h common.Hash) { hw := sha3.NewLegacyKeccak256() rlp.Encode(hw, x) hw.Sum(h[:0]) + return h } diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 391aa57584..a56cc29d07 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -50,6 +50,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { if err := rlp.DecodeBytes(rlpData, tx); err != nil { return nil, nil, err } + sender, err := types.Sender(signer, tx) if err != nil { return nil, nil, err @@ -59,10 +60,13 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { if err != nil { return nil, nil, err } + if requiredGas > tx.Gas() { return nil, nil, fmt.Errorf("insufficient gas ( %d < %d )", tx.Gas(), requiredGas) } + h := tx.Hash() + return &sender, &h, nil } @@ -87,24 +91,30 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { if err == nil { return fmt.Errorf("expected error, got none (address %v)[%v]", sender.String(), testcase.name) } + continue } // Should resolve the right address if err != nil { return fmt.Errorf("got error, expected none: %v", err) } + if sender == nil { return fmt.Errorf("sender was nil, should be %x", common.Address(testcase.fork.Sender)) } + if *sender != common.Address(testcase.fork.Sender) { return fmt.Errorf("sender mismatch: got %x, want %x", sender, testcase.fork.Sender) } + if txhash == nil { return fmt.Errorf("txhash was nil, should be %x", common.Hash(testcase.fork.Hash)) } + if *txhash != common.Hash(testcase.fork.Hash) { return fmt.Errorf("hash mismatch: got %x, want %x", *txhash, testcase.fork.Hash) } } + return nil } diff --git a/trie/committer.go b/trie/committer.go index 9f978873a8..e96ba06579 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -70,10 +70,12 @@ func (c *committer) commit(path []byte, n node) node { // The key needs to be copied, since we're adding it to the // modified nodeset. collapsed.Key = hexToCompact(cn.Key) + hashedNode := c.store(path, collapsed) if hn, ok := hashedNode.(hashNode); ok { return hn } + return collapsed case *fullNode: hashedKids := c.commitChildren(path, cn) @@ -84,6 +86,7 @@ func (c *committer) commit(path []byte, n node) node { if hn, ok := hashedNode.(hashNode); ok { return hn } + return collapsed case hashNode: return cn @@ -96,6 +99,7 @@ func (c *committer) commit(path []byte, n node) node { // commitChildren commits the children of the given fullnode func (c *committer) commitChildren(path []byte, n *fullNode) [17]node { var children [17]node + for i := 0; i < 16; i++ { child := n.Children[i] if child == nil { @@ -117,6 +121,7 @@ func (c *committer) commitChildren(path []byte, n *fullNode) [17]node { if n.Children[16] != nil { children[16] = n.Children[16] } + return children } @@ -137,6 +142,7 @@ func (c *committer) store(path []byte, n node) node { if _, ok := c.nodes.accessList[string(path)]; ok { c.nodes.markDeleted(path) } + return n } // We have the hash already, estimate the RLP encoding-size of the node. @@ -163,6 +169,7 @@ func (c *committer) store(path []byte, n node) node { } } } + return hash } @@ -178,6 +185,7 @@ func estimateSize(n node) int { case *fullNode: // A full node contains up to 16 hashes (some nils), and a key s := 3 + for i := 0; i < 16; i++ { if child := n.Children[i]; child != nil { s += estimateSize(child) @@ -185,6 +193,7 @@ func estimateSize(n node) int { s++ } } + return s case valueNode: return 1 + len(n) diff --git a/trie/database.go b/trie/database.go index bb93edad43..25e3fd2d2d 100644 --- a/trie/database.go +++ b/trie/database.go @@ -114,6 +114,7 @@ func (n rawFullNode) fstring(ind string) string { panic("this should never end u func (n rawFullNode) EncodeRLP(w io.Writer) error { eb := rlp.NewEncoderBuffer(w) n.encode(eb) + return eb.Flush() } @@ -156,6 +157,7 @@ func (n *cachedNode) rlp() []byte { if node, ok := n.node.(rawNode); ok { return node } + return nodeToBytes(n.node) } @@ -168,6 +170,7 @@ func (n *cachedNode) obj(hash common.Hash) node { // copy and safe to use unsafe decoder. return mustDecodeNodeUnsafe(hash[:], node) } + return expandNode(hash[:], n.node) } @@ -178,6 +181,7 @@ func (n *cachedNode) forChilds(onChild func(hash common.Hash)) { for child := range n.children { onChild(child) } + if _, ok := n.node.(rawNode); !ok { forGatherChildren(n.node, onChild) } @@ -217,6 +221,7 @@ func simplifyNode(n node) node { node[i] = simplifyNode(node[i]) } } + return node case valueNode, hashNode, rawNode: @@ -253,6 +258,7 @@ func expandNode(hash hashNode, n node) node { node.Children[i] = expandNode(nil, n[i]) } } + return node case valueNode, hashNode: @@ -282,6 +288,7 @@ func NewDatabase(diskdb ethdb.Database) *Database { // for nodes loaded from disk. func NewDatabaseWithConfig(diskdb ethdb.Database, config *Config) *Database { var cleans *fastcache.Cache + if config != nil && config.Cache > 0 { if config.Journal == "" { cleans = fastcache.New(config.Cache * 1024 * 1024) @@ -289,10 +296,12 @@ func NewDatabaseWithConfig(diskdb ethdb.Database, config *Config) *Database { cleans = fastcache.LoadFromFileOrNew(config.Journal, config.Cache*1024*1024) } } + var preimage *preimageStore if config != nil && config.Preimages { preimage = newPreimageStore(diskdb) } + db := &Database{ diskdb: diskdb, cleans: cleans, @@ -301,6 +310,7 @@ func NewDatabaseWithConfig(diskdb ethdb.Database, config *Config) *Database { }}, preimages: preimage, } + return db } @@ -312,6 +322,7 @@ func (db *Database) insert(hash common.Hash, size int, node node) { if _, ok := db.dirties[hash]; ok { return } + memcacheDirtyWriteMeter.Mark(int64(size)) // Create the cached entry for this node @@ -325,6 +336,7 @@ func (db *Database) insert(hash common.Hash, size int, node node) { c.parents++ } }) + db.dirties[hash] = entry // Update the flush-list endpoints @@ -333,6 +345,7 @@ func (db *Database) insert(hash common.Hash, size int, node node) { } else { db.dirties[db.newest].flushNext, db.newest = hash, hash } + db.dirtiesSize += common.StorageSize(common.HashLength + entry.size) } @@ -358,8 +371,10 @@ func (db *Database) node(hash common.Hash) node { if dirty != nil { memcacheDirtyHitMeter.Mark(1) memcacheDirtyReadMeter.Mark(int64(dirty.size)) + return dirty.obj(hash) } + memcacheDirtyMissMeter.Mark(1) // Content unavailable in memory, attempt to retrieve from disk @@ -367,6 +382,7 @@ func (db *Database) node(hash common.Hash) node { if err != nil || enc == nil { return nil } + if db.cleans != nil { db.cleans.Set(hash[:], enc) memcacheCleanMissMeter.Mark(1) @@ -389,6 +405,7 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { if enc := db.cleans.Get(nil, hash[:]); enc != nil { memcacheCleanHitMeter.Mark(1) memcacheCleanReadMeter.Mark(int64(len(enc))) + return enc, nil } } @@ -400,8 +417,10 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { if dirty != nil { memcacheDirtyHitMeter.Mark(1) memcacheDirtyReadMeter.Mark(int64(dirty.size)) + return dirty.rlp(), nil } + memcacheDirtyMissMeter.Mark(1) // Content unavailable in memory, attempt to retrieve from disk @@ -412,8 +431,10 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { memcacheCleanMissMeter.Mark(1) memcacheCleanWriteMeter.Mark(int64(len(enc))) } + return enc, nil } + return nil, errors.New("not found") } @@ -425,11 +446,13 @@ func (db *Database) Nodes() []common.Hash { defer db.lock.RUnlock() var hashes = make([]common.Hash, 0, len(db.dirties)) + for hash := range db.dirties { if hash != (common.Hash{}) { // Special case for "root" references/nodes hashes = append(hashes, hash) } } + return hashes } @@ -458,7 +481,9 @@ func (db *Database) reference(child common.Hash, parent common.Hash) { } else if _, ok = db.dirties[parent].children[child]; ok && parent != (common.Hash{}) { return } + node.parents++ + db.dirties[parent].children[child]++ if db.dirties[parent].children[child] == 1 { db.childrenSize += common.HashLength + 2 // uint16 counter @@ -472,6 +497,7 @@ func (db *Database) Dereference(root common.Hash) { log.Error("Attempted to dereference the trie cache meta root") return } + db.lock.Lock() defer db.lock.Unlock() @@ -499,6 +525,7 @@ func (db *Database) dereference(child common.Hash, parent common.Hash) { node.children[child]-- if node.children[child] == 0 { delete(node.children, child) + db.childrenSize -= (common.HashLength + 2) // uint16 counter } } @@ -515,6 +542,7 @@ func (db *Database) dereference(child common.Hash, parent common.Hash) { // no problem in itself, but don't make maxint parents out of it. node.parents-- } + if node.parents == 0 { // Remove the node from the flush-list switch child { @@ -533,6 +561,7 @@ func (db *Database) dereference(child common.Hash, parent common.Hash) { db.dereference(hash, child) }) delete(db.dirties, child) + db.dirtiesSize -= common.StorageSize(common.HashLength + int(node.size)) if node.children != nil { db.childrenSize -= cachedNodeChildrenSize @@ -579,6 +608,7 @@ func (db *Database) Cap(limit common.StorageSize) error { log.Error("Failed to write flush list to disk", "err", err) return err } + batch.Reset() } // Iterate to the next flush item, or abort if the size cap was achieved. Size @@ -588,6 +618,7 @@ func (db *Database) Cap(limit common.StorageSize) error { if node.children != nil { size -= common.StorageSize(cachedNodeChildrenSize + len(node.children)*(common.HashLength+2)) } + oldest = node.flushNext } // Flush out any remainder data from the last batch @@ -609,9 +640,11 @@ func (db *Database) Cap(limit common.StorageSize) error { db.childrenSize -= common.StorageSize(cachedNodeChildrenSize + len(node.children)*(common.HashLength+2)) } } + if db.oldest != (common.Hash{}) { db.dirties[db.oldest].flushPrev = common.Hash{} } + db.flushnodes += uint64(nodes - len(db.dirties)) db.flushsize += storage - db.dirtiesSize db.flushtime += time.Since(start) @@ -662,9 +695,11 @@ func (db *Database) Commit(node common.Hash, report bool) error { // Uncache any leftovers in the last batch db.lock.Lock() defer db.lock.Unlock() + if err := batch.Replay(uncacher); err != nil { return err } + batch.Reset() // Reset the storage counters and bumped metrics @@ -676,6 +711,7 @@ func (db *Database) Commit(node common.Hash, report bool) error { if !report { logger = log.Debug } + logger("Persisted trie from memory database", "nodes", nodes-len(db.dirties)+int(db.flushnodes), "size", storage-db.dirtiesSize+db.flushsize, "time", time.Since(start)+db.flushtime, "gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize) @@ -693,29 +729,36 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane if !ok { return nil } + var err error + node.forChilds(func(child common.Hash) { if err == nil { err = db.commit(child, batch, uncacher) } }) + if err != nil { return err } // If we've reached an optimal batch size, commit and start over rawdb.WriteLegacyTrieNode(batch, hash, node.rlp()) + if batch.ValueSize() >= ethdb.IdealBatchSize { if err := batch.Write(); err != nil { return err } + db.lock.Lock() err := batch.Replay(uncacher) batch.Reset() db.lock.Unlock() + if err != nil { return err } } + return nil } @@ -752,6 +795,7 @@ func (c *cleaner) Put(key []byte, rlp []byte) error { } // Remove the node from the dirty cache delete(c.db.dirties, hash) + c.db.dirtiesSize -= common.StorageSize(common.HashLength + int(node.size)) if node.children != nil { c.db.childrenSize -= common.StorageSize(cachedNodeChildrenSize + len(node.children)*(common.HashLength+2)) @@ -761,6 +805,7 @@ func (c *cleaner) Put(key []byte, rlp []byte) error { c.db.cleans.Set(hash[:], rlp) memcacheCleanWriteMeter.Mark(int64(len(rlp))) } + return nil } @@ -782,21 +827,26 @@ func (db *Database) Update(nodes *MergedNodeSet) error { // Note, the storage tries must be flushed before the account trie to // retain the invariant that children go into the dirty cache first. var order []common.Hash + for owner := range nodes.sets { if owner == (common.Hash{}) { continue } + order = append(order, owner) } + if _, ok := nodes.sets[common.Hash{}]; ok { order = append(order, common.Hash{}) } + for _, owner := range order { subset := nodes.sets[owner] subset.forEachWithOrder(func(path string, n *memoryNode) { if n.isDeleted() { return // ignore deletion } + db.insert(n.hash, int(n.size), n.node) }) } @@ -808,11 +858,13 @@ func (db *Database) Update(nodes *MergedNodeSet) error { if err := rlp.DecodeBytes(n.blob, &account); err != nil { return err } + if account.Root != types.EmptyRootHash { db.reference(account.Root, n.parent) } } } + return nil } @@ -826,11 +878,14 @@ func (db *Database) Size() (common.StorageSize, common.StorageSize) { // the total memory consumption, the maintenance metadata is also needed to be // counted. var metadataSize = common.StorageSize((len(db.dirties) - 1) * cachedNodeSize) + var metarootRefs = common.StorageSize(len(db.dirties[common.Hash{}].children) * (common.HashLength + 2)) + var preimageSize common.StorageSize if db.preimages != nil { preimageSize = db.preimages.size() } + return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, preimageSize } @@ -868,15 +923,19 @@ func (db *Database) saveCache(dir string, threads int) error { if db.cleans == nil { return nil } + log.Info("Writing clean trie cache to disk", "path", dir, "threads", threads) start := time.Now() + err := db.cleans.SaveToFileConcurrent(dir, threads) if err != nil { log.Error("Failed to persist clean trie cache", "error", err) return err } + log.Info("Persisted the clean trie cache", "path", dir, "elapsed", common.PrettyDuration(time.Since(start))) + return nil } @@ -912,6 +971,7 @@ func (db *Database) CommitPreimages() error { if db.preimages == nil { return nil } + return db.preimages.commit(true) } diff --git a/trie/encoding.go b/trie/encoding.go index 8ee0022ef3..2665c12d82 100644 --- a/trie/encoding.go +++ b/trie/encoding.go @@ -40,14 +40,18 @@ func hexToCompact(hex []byte) []byte { terminator = 1 hex = hex[:len(hex)-1] } + buf := make([]byte, len(hex)/2+1) buf[0] = terminator << 5 // the flag byte + if len(hex)&1 == 1 { buf[0] |= 1 << 4 // odd flag buf[0] |= hex[0] // first nibble is contained in the first byte hex = hex[1:] } + decodeNibbles(hex, buf[1:]) + return buf } @@ -63,20 +67,25 @@ func hexToCompactInPlace(hex []byte) int { firstByte = 1 << 5 hexLen-- // last part was the terminator, ignore that } + var ( binLen = hexLen/2 + 1 ni = 0 // index in hex bi = 1 // index in bin (compact) ) + if hexLen&1 == 1 { firstByte |= 1 << 4 // odd flag firstByte |= hex[0] // first nibble is contained in the first byte ni++ } + for ; ni < hexLen; bi, ni = bi+1, ni+2 { hex[bi] = hex[ni]<<4 | hex[ni+1] } + hex[0] = firstByte + return binLen } @@ -84,6 +93,7 @@ func compactToHex(compact []byte) []byte { if len(compact) == 0 { return compact } + base := keybytesToHex(compact) // delete terminator flag if base[0] < 2 { @@ -91,17 +101,21 @@ func compactToHex(compact []byte) []byte { } // apply odd flag chop := 2 - base[0]&1 + return base[chop:] } func keybytesToHex(str []byte) []byte { l := len(str)*2 + 1 + var nibbles = make([]byte, l) for i, b := range str { nibbles[i*2] = b / 16 nibbles[i*2+1] = b % 16 } + nibbles[l-1] = 16 + return nibbles } @@ -111,11 +125,14 @@ func hexToKeybytes(hex []byte) []byte { if hasTerm(hex) { hex = hex[:len(hex)-1] } + if len(hex)&1 != 0 { panic("can't convert hex key of odd length") } + key := make([]byte, len(hex)/2) decodeNibbles(hex, key) + return key } @@ -131,11 +148,13 @@ func prefixLen(a, b []byte) int { if len(b) < length { length = len(b) } + for ; i < length; i++ { if a[i] != b[i] { break } } + return i } diff --git a/trie/encoding_test.go b/trie/encoding_test.go index d16d25c359..9b809b529f 100644 --- a/trie/encoding_test.go +++ b/trie/encoding_test.go @@ -42,6 +42,7 @@ func TestHexCompact(t *testing.T) { if c := hexToCompact(test.hex); !bytes.Equal(c, test.compact) { t.Errorf("hexToCompact(%x) -> %x, want %x", test.hex, c, test.compact) } + if h := compactToHex(test.compact); !bytes.Equal(h, test.hex) { t.Errorf("compactToHex(%x) -> %x, want %x", test.compact, h, test.hex) } @@ -72,6 +73,7 @@ func TestHexKeybytes(t *testing.T) { if h := keybytesToHex(test.key); !bytes.Equal(h, test.hexOut) { t.Errorf("keybytesToHex(%x) -> %x, want %x", test.key, h, test.hexOut) } + if k := hexToKeybytes(test.hexIn); !bytes.Equal(k, test.key) { t.Errorf("hexToKeybytes(%x) -> %x, want %x", test.hexIn, k, test.key) } @@ -87,6 +89,7 @@ func TestHexToCompactInPlace(t *testing.T) { hexBytes, _ := hex.DecodeString(key) exp := hexToCompact(hexBytes) sz := hexToCompactInPlace(hexBytes) + got := hexBytes[:sz] if !bytes.Equal(exp, got) { t.Fatalf("test %d: encoding err\ninp %v\ngot %x\nexp %x\n", i, key, got, exp) diff --git a/trie/errors.go b/trie/errors.go index bd82b950aa..949e589d3b 100644 --- a/trie/errors.go +++ b/trie/errors.go @@ -42,5 +42,6 @@ func (err *MissingNodeError) Error() string { if err.Owner == (common.Hash{}) { return fmt.Sprintf("missing trie node %x (path %x) %v", err.NodeHash, err.Path, err.err) } + return fmt.Sprintf("missing trie node %x (owner %x) (path %x) %v", err.NodeHash, err.Owner, err.Path, err.err) } diff --git a/trie/hasher.go b/trie/hasher.go index 875cdca459..84eab382d3 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -48,6 +48,7 @@ var hasherPool = sync.Pool{ func newHasher(parallel bool) *hasher { h := hasherPool.Get().(*hasher) h.parallel = parallel + return h } @@ -74,15 +75,18 @@ func (h *hasher) hash(n node, force bool) (hashed node, cached node) { } else { cached.flags.hash = nil } + return hashed, cached case *fullNode: collapsed, cached := h.hashFullNodeChildren(n) hashed = h.fullnodeToHash(collapsed, force) + if hn, ok := hashed.(hashNode); ok { cached.flags.hash = hn } else { cached.flags.hash = nil } + return hashed, cached default: // Value and hash nodes don't have children so they're left as were @@ -105,6 +109,7 @@ func (h *hasher) hashShortNodeChildren(n *shortNode) (collapsed, cached *shortNo case *fullNode, *shortNode: collapsed.Val, cached.Val = h.hash(n.Val, false) } + return collapsed, cached } @@ -112,9 +117,12 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached // Hash the full node's children, caching the newly hashed subtrees cached = n.copy() collapsed = n.copy() + if h.parallel { var wg sync.WaitGroup + wg.Add(16) + for i := 0; i < 16; i++ { go func(i int) { hasher := newHasher(false) @@ -123,6 +131,7 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached } else { collapsed.Children[i] = nilValueNode } + returnHasherToPool(hasher) wg.Done() }(i) @@ -137,6 +146,7 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached } } } + return collapsed, cached } @@ -151,6 +161,7 @@ func (h *hasher) shortnodeToHash(n *shortNode, force bool) node { if len(enc) < 32 && !force { return n // Nodes smaller than 32 bytes are stored inside their parent } + return h.hashData(enc) } @@ -163,6 +174,7 @@ func (h *hasher) fullnodeToHash(n *fullNode, force bool) node { if len(enc) < 32 && !force { return n // Nodes smaller than 32 bytes are stored inside their parent } + return h.hashData(enc) } @@ -179,15 +191,18 @@ func (h *hasher) fullnodeToHash(n *fullNode, force bool) node { func (h *hasher) encodedBytes() []byte { h.tmp = h.encbuf.AppendToBytes(h.tmp[:0]) h.encbuf.Reset(nil) + return h.tmp } // hashData hashes the provided data func (h *hasher) hashData(data []byte) hashNode { n := make(hashNode, 32) + h.sha.Reset() h.sha.Write(data) h.sha.Read(n) + return n } diff --git a/trie/iterator.go b/trie/iterator.go index f42beec4ab..0cce5911c1 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -56,12 +56,15 @@ func (it *Iterator) Next() bool { if it.nodeIt.Leaf() { it.Key = it.nodeIt.LeafKey() it.Value = it.nodeIt.LeafBlob() + return true } } + it.Key = nil it.Value = nil it.Err = it.nodeIt.Error() + return false } @@ -167,8 +170,10 @@ func newNodeIterator(trie *Trie, start []byte) NodeIterator { err: errIteratorEnd, } } + it := &nodeIterator{trie: trie} it.err = it.seek(start) + return it } @@ -180,6 +185,7 @@ func (it *nodeIterator) Hash() common.Hash { if len(it.stack) == 0 { return common.Hash{} } + return it.stack[len(it.stack)-1].hash } @@ -187,6 +193,7 @@ func (it *nodeIterator) Parent() common.Hash { if len(it.stack) == 0 { return common.Hash{} } + return it.stack[len(it.stack)-1].parent } @@ -200,6 +207,7 @@ func (it *nodeIterator) LeafKey() []byte { return hexToKeybytes(it.path) } } + panic("not at leaf") } @@ -209,6 +217,7 @@ func (it *nodeIterator) LeafBlob() []byte { return node } } + panic("not at leaf") } @@ -217,6 +226,7 @@ func (it *nodeIterator) LeafProof() [][]byte { if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { hasher := newHasher(false) defer returnHasherToPool(hasher) + proofs := make([][]byte, 0, len(it.stack)) for i, item := range it.stack[:len(it.stack)-1] { @@ -226,9 +236,11 @@ func (it *nodeIterator) LeafProof() [][]byte { proofs = append(proofs, nodeToBytes(node)) } } + return proofs } } + panic("not at leaf") } @@ -240,11 +252,13 @@ func (it *nodeIterator) NodeBlob() []byte { if it.Hash() == (common.Hash{}) { return nil // skip the non-standalone node } + blob, err := it.resolveBlob(it.Hash().Bytes(), it.Path()) if err != nil { it.err = err return nil } + return blob } @@ -252,9 +266,11 @@ func (it *nodeIterator) Error() error { if it.err == errIteratorEnd { return nil } + if seek, ok := it.err.(seekError); ok { return seek.err } + return it.err } @@ -266,6 +282,7 @@ func (it *nodeIterator) Next(descend bool) bool { if it.err == errIteratorEnd { return false } + if seek, ok := it.err.(seekError); ok { if it.err = it.seek(seek.key); it.err != nil { return false @@ -274,10 +291,13 @@ func (it *nodeIterator) Next(descend bool) bool { // Otherwise step forward with the iterator and report any errors. state, parentIndex, path, err := it.peek(descend) it.err = err + if it.err != nil { return false } + it.push(state, parentIndex, path) + return true } @@ -295,6 +315,7 @@ func (it *nodeIterator) seek(prefix []byte) error { } else if bytes.Compare(path, key) >= 0 { return nil } + it.push(state, parentIndex, path) } } @@ -303,9 +324,11 @@ func (it *nodeIterator) seek(prefix []byte) error { func (it *nodeIterator) init() (*nodeIteratorState, error) { root := it.trie.Hash() state := &nodeIteratorState{node: it.trie.root, index: -1} + if root != types.EmptyRootHash { state.hash = root } + return state, state.resolve(it, nil) } @@ -316,6 +339,7 @@ func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, er state, err := it.init() return state, nil, nil, err } + if !descend { // If we're skipping children, pop the current node first it.pop() @@ -325,19 +349,23 @@ func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, er for len(it.stack) > 0 { parent := it.stack[len(it.stack)-1] ancestor := parent.hash + if (ancestor == common.Hash{}) { ancestor = parent.parent } + state, path, ok := it.nextChild(parent, ancestor) if ok { if err := state.resolve(it, path); err != nil { return parent, &parent.index, path, err } + return state, &parent.index, path, nil } // No more child nodes, move back up. it.pop() } + return nil, nil, nil, errIteratorEnd } @@ -349,6 +377,7 @@ func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []by state, err := it.init() return state, nil, nil, err } + if !bytes.HasPrefix(seekKey, it.path) { // If we're skipping children, pop the current node first it.pop() @@ -358,19 +387,23 @@ func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []by for len(it.stack) > 0 { parent := it.stack[len(it.stack)-1] ancestor := parent.hash + if (ancestor == common.Hash{}) { ancestor = parent.parent } + state, path, ok := it.nextChildAt(parent, ancestor, seekKey) if ok { if err := state.resolve(it, path); err != nil { return parent, &parent.index, path, err } + return state, &parent.index, path, nil } // No more child nodes, move back up. it.pop() } + return nil, nil, nil, errIteratorEnd } @@ -410,9 +443,11 @@ func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error { if err != nil { return err } + st.node = resolved st.hash = common.BytesToHash(hash) } + return nil } @@ -422,6 +457,7 @@ func findChild(n *fullNode, index int, path []byte, ancestor common.Hash) (node, state *nodeIteratorState childPath []byte ) + for ; index < len(n.Children); index++ { if n.Children[index] != nil { child = n.Children[index] @@ -433,11 +469,14 @@ func findChild(n *fullNode, index int, path []byte, ancestor common.Hash) (node, index: -1, pathlen: len(path), } + childPath = append(childPath, path...) childPath = append(childPath, byte(index)) + return child, state, childPath, index } } + return nil, nil, nil, 0 } @@ -460,10 +499,13 @@ func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Has index: -1, pathlen: len(it.path), } + path := append(it.path, node.Key...) + return state, path, true } } + return parent, it.path, false } @@ -506,16 +548,20 @@ func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.H index: -1, pathlen: len(it.path), } + path := append(it.path, n.Key...) + return state, path, true } } + return parent, it.path, false } func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []byte) { it.path = path it.stack = append(it.stack, state) + if parentIndex != nil { *parentIndex++ } @@ -532,17 +578,21 @@ func compareNodes(a, b NodeIterator) int { if cmp := bytes.Compare(a.Path(), b.Path()); cmp != 0 { return cmp } + if a.Leaf() && !b.Leaf() { return -1 } else if b.Leaf() && !a.Leaf() { return 1 } + if cmp := bytes.Compare(a.Hash().Bytes(), b.Hash().Bytes()); cmp != 0 { return cmp } + if a.Leaf() && b.Leaf() { return bytes.Compare(a.LeafBlob(), b.LeafBlob()) } + return 0 } @@ -561,6 +611,7 @@ func NewDifferenceIterator(a, b NodeIterator) (NodeIterator, *int) { a: a, b: b, } + return it, &it.count } @@ -607,6 +658,7 @@ func (it *differenceIterator) Next(bool) bool { if !it.b.Next(true) { return false } + it.count++ if it.eof { @@ -622,6 +674,7 @@ func (it *differenceIterator) Next(bool) bool { it.eof = true return true } + it.count++ case 1: // b is before a @@ -632,11 +685,13 @@ func (it *differenceIterator) Next(bool) bool { if !it.b.Next(hasHash) { return false } + it.count++ if !it.a.Next(hasHash) { it.eof = true return true } + it.count++ } } @@ -646,6 +701,7 @@ func (it *differenceIterator) Error() error { if err := it.a.Error(); err != nil { return err } + return it.b.Error() } @@ -659,6 +715,7 @@ func (h *nodeIteratorHeap) Pop() interface{} { n := len(*h) x := (*h)[n-1] *h = (*h)[0 : n-1] + return x } @@ -676,6 +733,7 @@ func NewUnionIterator(iters []NodeIterator) (NodeIterator, *int) { heap.Init(&h) ui := &unionIterator{items: &h} + return ui, &ui.count } @@ -748,10 +806,12 @@ func (it *unionIterator) Next(descend bool) bool { heap.Push(it.items, skipped) } } + if least.Next(descend) { it.count++ heap.Push(it.items, least) } + return len(*it.items) > 0 } @@ -761,5 +821,6 @@ func (it *unionIterator) Error() error { return err } } + return nil } diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 9b48984d20..e993d99953 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -38,6 +38,7 @@ func TestEmptyIterator(t *testing.T) { for iter.Next(true) { seen[string(iter.Path())] = struct{}{} } + if len(seen) != 0 { t.Fatal("Unexpected trie node iterated") } @@ -56,6 +57,7 @@ func TestIterator(t *testing.T) { {"somethingveryoddindeedthis is", "myothernodedata"}, } all := make(map[string]string) + for _, val := range vals { all[val.k] = val.v trie.MustUpdate([]byte(val.k), []byte(val.v)) @@ -67,6 +69,7 @@ func TestIterator(t *testing.T) { trie, _ = New(TrieID(root), db) found := make(map[string]string) it := NewIterator(trie.NodeIterator(nil)) + for it.Next() { found[string(it.Key)] = string(it.Value) } @@ -93,6 +96,7 @@ func TestIteratorLargeData(t *testing.T) { trie.MustUpdate(value.k, value.v) trie.MustUpdate(value2.k, value2.v) + vals[string(value.k)] = value vals[string(value2.k)] = value2 } @@ -103,6 +107,7 @@ func TestIteratorLargeData(t *testing.T) { } var untouched []*kv + for _, value := range vals { if !value.t { untouched = append(untouched, value) @@ -111,6 +116,7 @@ func TestIteratorLargeData(t *testing.T) { if len(untouched) > 0 { t.Errorf("Missed %d nodes", len(untouched)) + for _, value := range untouched { t.Error(value) } @@ -124,6 +130,7 @@ func TestNodeIteratorCoverage(t *testing.T) { // Gather all the node hashes found by the iterator hashes := make(map[common.Hash]struct{}) + for it := trie.NodeIterator(nil); it.Next(true); { if it.Hash() != (common.Hash{}) { hashes[it.Hash()] = struct{}{} @@ -135,6 +142,7 @@ func TestNodeIteratorCoverage(t *testing.T) { t.Errorf("failed to retrieve reported node %x: %v", hash, err) } } + for hash, obj := range db.dirties { if obj != nil && hash != (common.Hash{}) { if _, ok := hashes[hash]; !ok { @@ -142,6 +150,7 @@ func TestNodeIteratorCoverage(t *testing.T) { } } } + it := db.diskdb.NewIterator(nil, nil) for it.Next() { key := it.Key() @@ -207,19 +216,24 @@ func checkIteratorOrder(want []kvs, it *Iterator) error { if len(want) == 0 { return fmt.Errorf("didn't expect any more values, got key %q", it.Key) } + if !bytes.Equal(it.Key, []byte(want[0].k)) { return fmt.Errorf("wrong key: got %q, want %q", it.Key, want[0].k) } + want = want[1:] } + if len(want) > 0 { return fmt.Errorf("iterator ended early, want key %q", want[0]) } + return nil } func TestDifferenceIterator(t *testing.T) { dba := NewDatabase(rawdb.NewMemoryDatabase()) + triea := NewEmpty(dba) for _, val := range testdata1 { triea.MustUpdate([]byte(val.k), []byte(val.v)) @@ -230,6 +244,7 @@ func TestDifferenceIterator(t *testing.T) { triea, _ = New(TrieID(rootA), dba) dbb := NewDatabase(rawdb.NewMemoryDatabase()) + trieb := NewEmpty(dbb) for _, val := range testdata2 { trieb.MustUpdate([]byte(val.k), []byte(val.v)) @@ -241,6 +256,7 @@ func TestDifferenceIterator(t *testing.T) { found := make(map[string]string) di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil)) + it := NewIterator(di) for it.Next() { found[string(it.Key)] = string(it.Value) @@ -257,6 +273,7 @@ func TestDifferenceIterator(t *testing.T) { t.Errorf("iterator value mismatch for %s: got %v want %v", item.k, found[item.k], item.v) } } + if len(found) != len(all) { t.Errorf("iterator count mismatch: got %d values, want %d", len(found), len(all)) } @@ -264,6 +281,7 @@ func TestDifferenceIterator(t *testing.T) { func TestUnionIterator(t *testing.T) { dba := NewDatabase(rawdb.NewMemoryDatabase()) + triea := NewEmpty(dba) for _, val := range testdata1 { triea.MustUpdate([]byte(val.k), []byte(val.v)) @@ -274,6 +292,7 @@ func TestUnionIterator(t *testing.T) { triea, _ = New(TrieID(rootA), dba) dbb := NewDatabase(rawdb.NewMemoryDatabase()) + trieb := NewEmpty(dbb) for _, val := range testdata2 { trieb.MustUpdate([]byte(val.k), []byte(val.v)) @@ -305,13 +324,16 @@ func TestUnionIterator(t *testing.T) { if !it.Next() { t.Errorf("Iterator ends prematurely at element %d", i) } + if kv.k != string(it.Key) { t.Errorf("iterator value mismatch for element %d: got key %s want %s", i, it.Key, kv.k) } + if kv.v != string(it.Value) { t.Errorf("iterator value mismatch for element %d: got value %s want %s", i, it.Value, kv.v) } } + if it.Next() { t.Errorf("Iterator returned extra values.") } @@ -322,6 +344,7 @@ func TestIteratorNoDups(t *testing.T) { for _, val := range testdata1 { tr.MustUpdate([]byte(val.k), []byte(val.v)) } + checkIteratorNoDups(t, tr.NodeIterator(nil), nil) } @@ -340,15 +363,18 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { _, nodes := tr.Commit(false) triedb.Update(NewWithNodeSet(nodes)) + if !memonly { triedb.Commit(tr.Hash(), false) } + wantNodeCount := checkIteratorNoDups(t, tr.NodeIterator(nil), nil) var ( diskKeys [][]byte memKeys []common.Hash ) + if memonly { memKeys = triedb.Nodes() } else { @@ -358,6 +384,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { } it.Release() } + for i := 0; i < 20; i++ { // Create trie that will load all nodes from DB. tr, _ := New(TrieID(tr.Hash()), triedb) @@ -369,16 +396,19 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { rval []byte robj *cachedNode ) + for { if memonly { rkey = memKeys[rand.Intn(len(memKeys))] } else { copy(rkey[:], diskKeys[rand.Intn(len(diskKeys))]) } + if rkey != tr.Hash() { break } } + if memonly { robj = triedb.dirties[rkey] delete(triedb.dirties, rkey) @@ -390,6 +420,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { seen := make(map[string]bool) it := tr.NodeIterator(nil) checkIteratorNoDups(t, it, seen) + missing, ok := it.Error().(*MissingNodeError) if !ok || missing.NodeHash != rkey { t.Fatal("didn't hit missing node, got", it.Error()) @@ -401,10 +432,13 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { } else { diskdb.Put(rkey[:], rval) } + checkIteratorNoDups(t, it, seen) + if it.Error() != nil { t.Fatal("unexpected error", it.Error()) } + if len(seen) != wantNodeCount { t.Fatal("wrong node iteration count, got", len(seen), "want", wantNodeCount) } @@ -433,14 +467,18 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { root, nodes := ctr.Commit(false) triedb.Update(NewWithNodeSet(nodes)) + if !memonly { triedb.Commit(root, false) } + barNodeHash := common.HexToHash("05041990364eb72fcb1127652ce40d8bab765f2bfe53225b1170d276cc101c2e") + var ( barNodeBlob []byte barNodeObj *cachedNode ) + if memonly { barNodeObj = triedb.dirties[barNodeHash] delete(triedb.dirties, barNodeHash) @@ -453,6 +491,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { tr, _ := New(TrieID(root), triedb) it := tr.NodeIterator([]byte("bars")) missing, ok := it.Error().(*MissingNodeError) + if !ok { t.Fatal("want MissingNodeError, got", it.Error()) } else if missing.NodeHash != barNodeHash { @@ -474,12 +513,15 @@ func checkIteratorNoDups(t *testing.T, it NodeIterator, seen map[string]bool) in if seen == nil { seen = make(map[string]bool) } + for it.Next(true) { if seen[string(it.Path())] { t.Fatalf("iterator visited node path %x twice", it.Path()) } + seen[string(it.Path())] = true } + return len(seen) } @@ -544,8 +586,10 @@ func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) { for i := 0; i < 10000; i++ { key := make([]byte, 32) val := make([]byte, 32) + binary.BigEndian.PutUint64(key, uint64(i)) binary.BigEndian.PutUint64(val, uint64(i)) + key = crypto.Keccak256(key) val = crypto.Keccak256(val) trie.MustUpdate(key, val) @@ -577,6 +621,7 @@ func TestIteratorNodeBlob(t *testing.T) { triedb = NewDatabase(db) trie = NewEmpty(triedb) ) + vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, @@ -587,6 +632,7 @@ func TestIteratorNodeBlob(t *testing.T) { {"somethingveryoddindeedthis is", "myothernodedata"}, } all := make(map[string]string) + for _, val := range vals { all[val.k] = val.v trie.MustUpdate([]byte(val.k), []byte(val.v)) @@ -597,11 +643,13 @@ func TestIteratorNodeBlob(t *testing.T) { triedb.Cap(0) found := make(map[common.Hash][]byte) + it := trie.NodeIterator(nil) for it.Next(true) { if it.Hash() == (common.Hash{}) { continue } + found[it.Hash()] = it.NodeBlob() } @@ -609,16 +657,20 @@ func TestIteratorNodeBlob(t *testing.T) { defer dbIter.Release() var count int + for dbIter.Next() { got, present := found[common.BytesToHash(dbIter.Key())] if !present { t.Fatalf("Miss trie node %v", dbIter.Key()) } + if !bytes.Equal(got, dbIter.Value()) { t.Fatalf("Unexpected trie node want %v got %v", dbIter.Value(), got) } + count += 1 } + if count != len(found) { t.Fatal("Find extra trie node via iterator") } diff --git a/trie/node.go b/trie/node.go index 6ce6551ded..2ab94338d4 100644 --- a/trie/node.go +++ b/trie/node.go @@ -55,6 +55,7 @@ var nilValueNode = valueNode(nil) func (n *fullNode) EncodeRLP(w io.Writer) error { eb := rlp.NewEncoderBuffer(w) n.encode(eb) + return eb.Flush() } @@ -80,6 +81,7 @@ func (n valueNode) String() string { return n.fstring("") } func (n *fullNode) fstring(ind string) string { resp := fmt.Sprintf("[\n%s ", ind) + for i, node := range &n.Children { if node == nil { resp += fmt.Sprintf("%s: ", indices[i]) @@ -87,6 +89,7 @@ func (n *fullNode) fstring(ind string) string { resp += fmt.Sprintf("%s: %v", indices[i], node.fstring(ind+" ")) } } + return resp + fmt.Sprintf("\n%s] ", ind) } func (n *shortNode) fstring(ind string) string { @@ -105,6 +108,7 @@ func mustDecodeNode(hash, buf []byte) node { if err != nil { panic(fmt.Sprintf("node %x: %v", hash, err)) } + return n } @@ -115,6 +119,7 @@ func mustDecodeNodeUnsafe(hash, buf []byte) node { if err != nil { panic(fmt.Sprintf("node %x: %v", hash, err)) } + return n } @@ -134,10 +139,12 @@ func decodeNodeUnsafe(hash, buf []byte) (node, error) { if len(buf) == 0 { return nil, io.ErrUnexpectedEOF } + elems, _, err := rlp.SplitList(buf) if err != nil { return nil, fmt.Errorf("decode error: %v", err) } + switch c, _ := rlp.CountValues(elems); c { case 2: n, err := decodeShort(hash, elems) @@ -155,7 +162,9 @@ func decodeShort(hash, elems []byte) (node, error) { if err != nil { return nil, err } + flag := nodeFlag{hash: hash} + key := compactToHex(kbuf) if hasTerm(key) { // value node @@ -163,31 +172,39 @@ func decodeShort(hash, elems []byte) (node, error) { if err != nil { return nil, fmt.Errorf("invalid value node: %v", err) } + return &shortNode{key, valueNode(val), flag}, nil } + r, _, err := decodeRef(rest) if err != nil { return nil, wrapError(err, "val") } + return &shortNode{key, r, flag}, nil } func decodeFull(hash, elems []byte) (*fullNode, error) { n := &fullNode{flags: nodeFlag{hash: hash}} + for i := 0; i < 16; i++ { cld, rest, err := decodeRef(elems) if err != nil { return n, wrapError(err, fmt.Sprintf("[%d]", i)) } + n.Children[i], elems = cld, rest } + val, _, err := rlp.SplitString(elems) if err != nil { return n, err } + if len(val) > 0 { n.Children[16] = valueNode(val) } + return n, nil } @@ -198,6 +215,7 @@ func decodeRef(buf []byte) (node, []byte, error) { if err != nil { return nil, buf, err } + switch { case kind == rlp.List: // 'embedded' node reference. The encoding must be smaller @@ -206,7 +224,9 @@ func decodeRef(buf []byte) (node, []byte, error) { err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen) return nil, buf, err } + n, err := decodeNode(nil, buf) + return n, rest, err case kind == rlp.String && len(val) == 0: // empty node @@ -229,10 +249,12 @@ func wrapError(err error, ctx string) error { if err == nil { return nil } + if decErr, ok := err.(*decodeError); ok { decErr.stack = append(decErr.stack, ctx) return decErr } + return &decodeError{err, []string{ctx}} } diff --git a/trie/node_enc.go b/trie/node_enc.go index cade35b707..e890b11619 100644 --- a/trie/node_enc.go +++ b/trie/node_enc.go @@ -25,11 +25,13 @@ func nodeToBytes(n node) []byte { n.encode(w) result := w.ToBytes() w.Flush() + return result } func (n *fullNode) encode(w rlp.EncoderBuffer) { offset := w.List() + for _, c := range n.Children { if c != nil { c.encode(w) @@ -37,17 +39,20 @@ func (n *fullNode) encode(w rlp.EncoderBuffer) { w.Write(rlp.EmptyString) } } + w.ListEnd(offset) } func (n *shortNode) encode(w rlp.EncoderBuffer) { offset := w.List() w.WriteBytes(n.Key) + if n.Val != nil { n.Val.encode(w) } else { w.Write(rlp.EmptyString) } + w.ListEnd(offset) } @@ -61,6 +66,7 @@ func (n valueNode) encode(w rlp.EncoderBuffer) { func (n rawFullNode) encode(w rlp.EncoderBuffer) { offset := w.List() + for _, c := range n { if c != nil { c.encode(w) @@ -68,17 +74,20 @@ func (n rawFullNode) encode(w rlp.EncoderBuffer) { w.Write(rlp.EmptyString) } } + w.ListEnd(offset) } func (n *rawShortNode) encode(w rlp.EncoderBuffer) { offset := w.List() w.WriteBytes(n.Key) + if n.Val != nil { n.Val.encode(w) } else { w.Write(rlp.EmptyString) } + w.ListEnd(offset) } diff --git a/trie/node_test.go b/trie/node_test.go index ded7de6c28..08b7d65a74 100644 --- a/trie/node_test.go +++ b/trie/node_test.go @@ -26,11 +26,14 @@ import ( func newTestFullNode(v []byte) []interface{} { fullNodeData := []interface{}{} + for i := 0; i < 16; i++ { k := bytes.Repeat([]byte{byte(i + 1)}, 32) fullNodeData = append(fullNodeData, k) } + fullNodeData = append(fullNodeData, v) + return fullNodeData } @@ -41,6 +44,7 @@ func TestDecodeNestedNode(t *testing.T) { for i := 0; i < 16; i++ { data = append(data, nil) } + data = append(data, []byte("subnode")) fullNodeData[15] = data @@ -71,6 +75,7 @@ func TestDecodeFullNodeWrongNestedFullNode(t *testing.T) { for i := 0; i < 16; i++ { data = append(data, []byte("123456")) } + data = append(data, []byte("subnode")) fullNodeData[15] = data diff --git a/trie/nodeset.go b/trie/nodeset.go index c87f901912..2e5ba818fa 100644 --- a/trie/nodeset.go +++ b/trie/nodeset.go @@ -52,6 +52,7 @@ func (n *memoryNode) rlp() []byte { if node, ok := n.node.(rawNode); ok { return node } + return nodeToBytes(n.node) } @@ -62,6 +63,7 @@ func (n *memoryNode) obj() node { if node, ok := n.node.(rawNode); ok { return mustDecodeNode(n.hash[:], node) } + return expandNode(n.hash[:], n.node) } @@ -125,6 +127,7 @@ func (set *NodeSet) forEachWithOrder(callback func(path string, n *memoryNode)) } // Bottom-up, longest path first sort.Sort(sort.Reverse(paths)) + for _, path := range paths { callback(path, set.nodes[path]) } @@ -167,7 +170,9 @@ func (set *NodeSet) Hashes() []common.Hash { // Summary returns a string-representation of the NodeSet. func (set *NodeSet) Summary() string { var out = new(strings.Builder) + fmt.Fprintf(out, "nodeset owner: %v\n", set.owner) + if set.nodes != nil { for path, n := range set.nodes { // Deletion @@ -185,9 +190,11 @@ func (set *NodeSet) Summary() string { fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", path, n.hash, origin) } } + for _, n := range set.leaves { fmt.Fprintf(out, "[leaf]: %v\n", n) } + return out.String() } @@ -205,6 +212,7 @@ func NewMergedNodeSet() *MergedNodeSet { func NewWithNodeSet(set *NodeSet) *MergedNodeSet { merged := NewMergedNodeSet() merged.Merge(set) + return merged } @@ -215,6 +223,8 @@ func (set *MergedNodeSet) Merge(other *NodeSet) error { if present { return fmt.Errorf("duplicate trie for owner %#x", other.owner) } + set.sets[other.owner] = other + return nil } diff --git a/trie/proof.go b/trie/proof.go index 65df7577b9..5d869e4a58 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -40,6 +40,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e nodes []node tn = t.root ) + key = keybytesToHex(key) for len(key) > 0 && tn != nil { switch n := tn.(type) { @@ -52,11 +53,13 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e prefix = append(prefix, n.Key...) key = key[len(n.Key):] } + nodes = append(nodes, n) case *fullNode: tn = n.Children[key[0]] prefix = append(prefix, key[0]) key = key[1:] + nodes = append(nodes, n) case hashNode: // Retrieve the specified node from the underlying node reader. @@ -65,6 +68,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e // all loaded nodes won't be linked to trie at all and track nodes // may lead to out-of-memory issue. var err error + tn, err = t.reader.node(prefix, common.BytesToHash(n)) if err != nil { log.Error("Unhandled trie error in Trie.Prove", "err", err) @@ -74,6 +78,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) } } + hasher := newHasher(false) defer returnHasherToPool(hasher) @@ -82,7 +87,9 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e fromLevel-- continue } + var hn node + n, hn = hasher.proofHash(n) if hash, ok := hn.(hashNode); ok || i == 0 { // If the node's database encoding is a hash (or is the @@ -91,9 +98,11 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e if !ok { hash = hasher.hashData(enc) } + proofDb.Put(hash, enc) } } + return nil } @@ -114,15 +123,18 @@ func (t *StateTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWrit func VerifyProof(rootHash common.Hash, key []byte, proofDb ethdb.KeyValueReader) (value []byte, err error) { key = keybytesToHex(key) wantHash := rootHash + for i := 0; ; i++ { buf, _ := proofDb.Get(wantHash[:]) if buf == nil { return nil, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash) } + n, err := decodeNode(wantHash[:], buf) if err != nil { return nil, fmt.Errorf("bad proof node %d: %v", i, err) } + keyrest, cld := get(n, key, true) switch cld := cld.(type) { case nil: @@ -130,6 +142,7 @@ func VerifyProof(rootHash common.Hash, key []byte, proofDb ethdb.KeyValueReader) return nil, nil case hashNode: key = keyrest + copy(wantHash[:], cld) case valueNode: return cld, nil @@ -149,10 +162,12 @@ func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyV if buf == nil { return nil, fmt.Errorf("proof node (hash %064x) missing", hash) } + n, err := decodeNode(hash[:], buf) if err != nil { return nil, fmt.Errorf("bad proof node %v", err) } + return n, err } // If the root node is empty, resolve it first. @@ -162,15 +177,19 @@ func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyV if err != nil { return nil, nil, err } + root = n } + var ( err error child, parent node keyrest []byte valnode []byte ) + key, parent = keybytesToHex(key), root + for { keyrest, child = get(parent, key, false) switch cld := child.(type) { @@ -182,6 +201,7 @@ func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyV if allowNonExistent { return root, nil, nil } + return nil, nil, errors.New("the node is not contained in trie") case *shortNode: key, parent = keyrest, child // Already resolved @@ -206,9 +226,11 @@ func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyV default: panic(fmt.Sprintf("%T: invalid node: %v", pnode, pnode)) } + if len(valnode) > 0 { return root, valnode, nil // The whole path is resolved } + key, parent = keyrest, child } } @@ -277,6 +299,7 @@ findFork: panic(fmt.Sprintf("%T: invalid node: %v", n, n)) } } + switch rn := n.(type) { case *shortNode: // There can have these five scenarios: @@ -288,15 +311,19 @@ findFork: if shortForkLeft == -1 && shortForkRight == -1 { return false, errors.New("empty range") } + if shortForkLeft == 1 && shortForkRight == 1 { 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 false, nil } // Only one proof points to non-existent key. @@ -306,34 +333,45 @@ findFork: if parent == nil { return true, nil } + parent.(*fullNode).Children[left[pos-1]] = nil + return false, nil } + return false, unset(rn, rn.Val, left[pos:], len(rn.Key), false) } + if shortForkLeft != 0 { 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 false, nil } + return false, unset(rn, rn.Val, right[pos:], len(rn.Key), true) } + 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 false, err } + if err := unset(rn, rn.Children[right[pos]], right[pos:], 1, true); err != nil { return false, err } + return false, nil default: panic(fmt.Sprintf("%T: invalid node: %v", n, n)) @@ -359,13 +397,16 @@ func unset(parent node, child node, key []byte, pos int, removeLeft bool) error for i := 0; i < int(key[pos]); i++ { cld.Children[i] = nil } + cld.flags = nodeFlag{dirty: true} } else { for i := key[pos] + 1; i < 16; i++ { cld.Children[i] = nil } + cld.flags = nodeFlag{dirty: true} } + return unset(cld, cld.Children[key[pos]], key, pos+1, removeLeft) case *shortNode: if len(key[pos:]) < len(cld.Key) || !bytes.Equal(cld.Key, key[pos:pos+len(cld.Key)]) { @@ -397,14 +438,19 @@ func unset(parent node, child node, key []byte, pos int, removeLeft bool) error // it with the cached hash available. //} } + return nil } + if _, ok := cld.Val.(valueNode); ok { fn := parent.(*fullNode) fn.Children[key[pos-1]] = nil + return nil } + cld.flags = nodeFlag{dirty: true} + return unset(cld, cld.Val, key, pos+len(cld.Key), removeLeft) case nil: // If the node is nil, then it's a child of the fork point @@ -421,6 +467,7 @@ func unset(parent node, child node, key []byte, pos int, removeLeft bool) error // path should already be resolved. func hasRightElement(node node, key []byte) bool { pos, key := 0, keybytesToHex(key) + for node != nil { switch rn := node.(type) { case *fullNode: @@ -429,11 +476,13 @@ func hasRightElement(node node, key []byte) bool { return true } } + node, pos = rn.Children[key[pos]], pos+1 case *shortNode: if len(key)-pos < len(rn.Key) || !bytes.Equal(rn.Key, key[pos:pos+len(rn.Key)]) { return bytes.Compare(rn.Key, key[pos:]) > 0 } + node, pos = rn.Val, pos+len(rn.Key) case valueNode: return false // We have resolved the whole path @@ -441,6 +490,7 @@ func hasRightElement(node node, key []byte) bool { panic(fmt.Sprintf("%T: invalid node: %v", node, node)) // hashnode } } + return false } @@ -488,6 +538,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key return false, errors.New("range is not monotonically increasing") } } + for _, value := range values { if len(value) == 0 { return false, errors.New("range contains deletion") @@ -500,9 +551,11 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key for index, key := range keys { tr.Update(key, values[index]) } + if have, want := tr.Hash(), rootHash; have != want { return false, fmt.Errorf("invalid proof, want hash %x, got %x", want, have) } + return false, nil // No more elements } // Special case, there is a provided edge proof but zero key/value @@ -512,9 +565,11 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key if err != nil { return false, err } + if val != nil || hasRightElement(root, firstKey) { return false, errors.New("more entries available") } + return false, nil } // Special case, there is only one element and two edge keys are same. @@ -524,12 +579,15 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key if err != nil { return false, err } + if !bytes.Equal(firstKey, keys[0]) { return false, errors.New("correct proof but invalid key") } + if !bytes.Equal(val, values[0]) { return false, errors.New("correct proof but invalid data") } + return hasRightElement(root, firstKey), nil } // Ok, in all other cases, we require two edge paths available. @@ -567,12 +625,15 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key if empty { tr.root = nil } + for index, key := range keys { tr.Update(key, values[index]) } + if tr.Hash() != rootHash { return false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) } + return hasRightElement(tr.root, keys[len(keys)-1]), nil } @@ -588,14 +649,17 @@ func get(tn node, key []byte, skipResolved bool) ([]byte, node) { if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { return nil, nil } + tn = n.Val key = key[len(n.Key):] + if !skipResolved { return key, tn } case *fullNode: tn = n.Children[key[0]] key = key[1:] + if !skipResolved { return key, tn } diff --git a/trie/proof_test.go b/trie/proof_test.go index c60dca53a9..62bf11f7bd 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -37,6 +37,7 @@ var prng = initRnd() func initRnd() *mrand.Rand { var seed [8]byte + crand.Read(seed[:]) rnd := mrand.New(mrand.NewSource(int64(binary.LittleEndian.Uint64(seed[:])))) fmt.Printf("Seed: %x\n", seed) @@ -60,34 +61,41 @@ func makeProvers(trie *Trie) []func(key []byte) *memorydb.Database { provers = append(provers, func(key []byte) *memorydb.Database { proof := memorydb.New() trie.Prove(key, 0, proof) + return proof }) // Create a leaf iterator based Merkle prover provers = append(provers, func(key []byte) *memorydb.Database { proof := memorydb.New() + if it := NewIterator(trie.NodeIterator(key)); it.Next() && bytes.Equal(key, it.Key) { for _, p := range it.Prove() { proof.Put(crypto.Keccak256(p), p) } } + return proof }) + return provers } func TestProof(t *testing.T) { trie, vals := randomTrie(500) root := trie.Hash() + for i, prover := range makeProvers(trie) { for _, kv := range vals { proof := prover(kv.k) if proof == nil { t.Fatalf("prover %d: missing key %x while constructing proof", i, kv.k) } + val, err := VerifyProof(root, kv.k, proof) if err != nil { t.Fatalf("prover %d: failed to verify proof for key %x: %v\nraw proof: %x", i, kv.k, err, proof) } + if !bytes.Equal(val, kv.v) { t.Fatalf("prover %d: verified value mismatch for key %x: have %x, want %x", i, kv.k, val, kv.v) } @@ -98,18 +106,22 @@ func TestProof(t *testing.T) { func TestOneElementProof(t *testing.T) { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) updateString(trie, "k", "v") + for i, prover := range makeProvers(trie) { proof := prover([]byte("k")) if proof == nil { t.Fatalf("prover %d: nil proof", i) } + if proof.Len() != 1 { t.Errorf("prover %d: proof should have one element", i) } + val, err := VerifyProof(trie.Hash(), []byte("k"), proof) if err != nil { t.Fatalf("prover %d: failed to verify proof: %v\nraw proof: %x", i, err, proof) } + if !bytes.Equal(val, []byte("v")) { t.Fatalf("prover %d: verified value mismatch: have %x, want 'k'", i, val) } @@ -119,16 +131,19 @@ func TestOneElementProof(t *testing.T) { func TestBadProof(t *testing.T) { trie, vals := randomTrie(800) root := trie.Hash() + for i, prover := range makeProvers(trie) { for _, kv := range vals { proof := prover(kv.k) if proof == nil { t.Fatalf("prover %d: nil proof", i) } + it := proof.NewIterator(nil, nil) for i, d := 0, mrand.Intn(proof.Len()); i <= d; i++ { it.Next() } + key := it.Key() val, _ := proof.Get(key) proof.Delete(key) @@ -157,10 +172,12 @@ func TestMissingKeyProof(t *testing.T) { if proof.Len() != 1 { t.Errorf("test %d: proof should have one element", i) } + val, err := VerifyProof(trie.Hash(), []byte(key), proof) if err != nil { t.Fatalf("test %d: failed to verify proof: %v\nraw proof: %x", i, err, proof) } + if val != nil { t.Fatalf("test %d: verified value mismatch: have %x, want nil", i, val) } @@ -177,11 +194,15 @@ func (p entrySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // as the existent proof. The test cases are generated randomly. func TestRangeProof(t *testing.T) { trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) + for i := 0; i < 500; i++ { start := mrand.Intn(len(entries)) end := mrand.Intn(len(entries)-start) + start + 1 @@ -190,15 +211,20 @@ func TestRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { t.Fatalf("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) } + _, 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) @@ -210,11 +236,15 @@ func TestRangeProof(t *testing.T) { // The test cases are generated randomly. func TestRangeProofWithNonExistentProof(t *testing.T) { trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) + for i := 0; i < 500; i++ { start := mrand.Intn(len(entries)) end := mrand.Intn(len(entries)-start) + start + 1 @@ -238,18 +268,24 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { if bytes.Compare(last, entries[end-1].k) < 0 { continue } + if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("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) } + _, 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) @@ -259,18 +295,24 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { proof := memorydb.New() first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes() + if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } + var k [][]byte + var v [][]byte + for i := 0; i < len(entries); i++ { k = append(k, entries[i].k) v = append(v, entries[i].v) } + _, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatal("Failed to verify whole rang with non-existent edges") @@ -282,10 +324,13 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { // - There exists a gap between the last element and the right edge proof func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) // Case 1 @@ -296,16 +341,20 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } + start = 105 // Gap created k := make([][]byte, 0) v := make([][]byte, 0) + for i := start; i < end; i++ { k = append(k, entries[i].k) v = append(v, entries[i].v) } + _, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") @@ -314,20 +363,25 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { // Case 2 start, end = 100, 200 last := increaseKey(common.CopyBytes(entries[end-1].k)) + proof = memorydb.New() if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } + end = 195 // Capped slice k = make([][]byte, 0) v = make([][]byte, 0) + for i := start; i < end; i++ { k = append(k, entries[i].k) v = append(v, entries[i].v) } + _, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") @@ -339,19 +393,24 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { // non-existent one. func TestOneElementRangeProof(t *testing.T) { trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) // One element with existent edge proof, both edge proofs // point to the SAME key. start := 1000 proof := memorydb.New() + 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) if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -361,12 +420,15 @@ func TestOneElementRangeProof(t *testing.T) { start = 1000 first := decreaseKey(common.CopyBytes(entries[start].k)) proof = memorydb.New() + if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + 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) if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -375,13 +437,16 @@ func TestOneElementRangeProof(t *testing.T) { // One element with right non-existent edge proof start = 1000 last := increaseKey(common.CopyBytes(entries[start].k)) + proof = memorydb.New() if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + 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) if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -391,12 +456,15 @@ func TestOneElementRangeProof(t *testing.T) { start = 1000 first, last = decreaseKey(common.CopyBytes(entries[start].k)), increaseKey(common.CopyBytes(entries[start].k)) proof = memorydb.New() + if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + 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) if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -409,13 +477,16 @@ func TestOneElementRangeProof(t *testing.T) { 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) @@ -426,18 +497,24 @@ func TestOneElementRangeProof(t *testing.T) { // The edge proofs can be nil. func TestAllElementsProof(t *testing.T) { trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) var k [][]byte + var v [][]byte + for i := 0; i < len(entries); i++ { k = append(k, entries[i].k) v = append(v, entries[i].v) } + _, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -448,9 +525,11 @@ func TestAllElementsProof(t *testing.T) { if err := trie.Prove(entries[0].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + 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) if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -460,12 +539,15 @@ func TestAllElementsProof(t *testing.T) { proof = memorydb.New() first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes() + if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + 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) if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -476,7 +558,9 @@ func TestAllElementsProof(t *testing.T) { func TestSingleSideRangeProof(t *testing.T) { for i := 0; i < 64; i++ { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + var entries entrySlice + for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} trie.MustUpdate(value.k, value.v) @@ -490,15 +574,19 @@ func TestSingleSideRangeProof(t *testing.T) { if err := trie.Prove(common.Hash{}.Bytes(), 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(entries[pos].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + k := make([][]byte, 0) v := make([][]byte, 0) + for i := 0; i <= pos; i++ { 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) if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -511,7 +599,9 @@ func TestSingleSideRangeProof(t *testing.T) { func TestReverseSingleSideRangeProof(t *testing.T) { for i := 0; i < 64; i++ { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + var entries entrySlice + for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} trie.MustUpdate(value.k, value.v) @@ -525,16 +615,20 @@ func TestReverseSingleSideRangeProof(t *testing.T) { if err := trie.Prove(entries[pos].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") if err := trie.Prove(last.Bytes(), 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } + k := make([][]byte, 0) v := make([][]byte, 0) + for i := pos; i < len(entries); i++ { k = append(k, entries[i].k) v = append(v, entries[i].v) } + _, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -547,31 +641,43 @@ func TestReverseSingleSideRangeProof(t *testing.T) { // The prover is expected to detect the error. func TestBadRangeProof(t *testing.T) { trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) for i := 0; i < 500; i++ { start := mrand.Intn(len(entries)) end := mrand.Intn(len(entries)-start) + start + 1 + proof := memorydb.New() if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { t.Fatalf("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) } + var first, last = keys[0], keys[len(keys)-1] + testcase := mrand.Intn(6) + var index int + switch testcase { case 0: // Modified key @@ -587,15 +693,18 @@ func TestBadRangeProof(t *testing.T) { if (index == 0 && start < 100) || (index == end-start-1 && end <= 100) { continue } + keys = append(keys[:index], keys[index+1:]...) vals = append(vals[:index], vals[index+1:]...) case 3: // Out of order index1 := mrand.Intn(end - start) index2 := mrand.Intn(end - start) + if index1 == index2 { continue } + keys[index1], keys[index2] = keys[index2], keys[index1] vals[index1], vals[index2] = vals[index2], vals[index1] case 4: @@ -607,6 +716,7 @@ func TestBadRangeProof(t *testing.T) { index = mrand.Intn(end - start) vals[index] = nil } + _, 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) @@ -618,29 +728,39 @@ func TestBadRangeProof(t *testing.T) { // If the gapped node is embedded in the trie, it should be detected too. func TestGappedRangeProof(t *testing.T) { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + var entries []*kv // Sorted entries + for i := byte(0); i < 10; i++ { value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} trie.MustUpdate(value.k, value.v) entries = append(entries, value) } + first, last := 2, 8 proof := memorydb.New() + if err := trie.Prove(entries[first].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(entries[last-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } + var keys [][]byte + var vals [][]byte + for i := first; i < last; i++ { if i == (first+last)/2 { continue } + 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) if err == nil { t.Fatal("expect error, got nil") @@ -650,10 +770,13 @@ func TestGappedRangeProof(t *testing.T) { // TestSameSideProofs tests the element is not in the range covered by proofs func TestSameSideProofs(t *testing.T) { trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) pos := 1000 @@ -665,9 +788,11 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + 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) if err == nil { t.Fatalf("Expected error, got nil") @@ -681,9 +806,11 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + 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) if err == nil { t.Fatalf("Expected error, got nil") @@ -692,7 +819,9 @@ func TestSameSideProofs(t *testing.T) { func TestHasRightElement(t *testing.T) { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + var entries entrySlice + for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} trie.MustUpdate(value.k, value.v) @@ -716,6 +845,7 @@ func TestHasRightElement(t *testing.T) { {-1, len(entries), false}, // The whole set with non-existent left proof {-1, -1, false}, // The whole set with non-existent left/right proof } + for _, c := range cases { var ( firstKey []byte @@ -724,6 +854,7 @@ func TestHasRightElement(t *testing.T) { end = c.end proof = memorydb.New() ) + if c.start == -1 { firstKey, start = common.Hash{}.Bytes(), 0 if err := trie.Prove(firstKey, 0, proof); err != nil { @@ -731,10 +862,12 @@ func TestHasRightElement(t *testing.T) { } } else { firstKey = entries[c.start].k + if err := trie.Prove(entries[c.start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } } + if c.end == -1 { lastKey, end = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes(), len(entries) if err := trie.Prove(lastKey, 0, proof); err != nil { @@ -742,20 +875,25 @@ func TestHasRightElement(t *testing.T) { } } else { lastKey = entries[c.end-1].k + if err := trie.Prove(entries[c.end-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } } + k := make([][]byte, 0) v := make([][]byte, 0) + for i := start; i < end; i++ { k = append(k, entries[i].k) v = append(v, entries[i].v) } + hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } + if hasMore != c.hasMore { t.Fatalf("Wrong hasMore indicator, want %t, got %t", c.hasMore, hasMore) } @@ -766,10 +904,13 @@ func TestHasRightElement(t *testing.T) { // The first edge proof must be a non-existent proof. func TestEmptyRangeProof(t *testing.T) { trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) var cases = []struct { @@ -779,16 +920,20 @@ func TestEmptyRangeProof(t *testing.T) { {len(entries) - 1, false}, {500, true}, } + for _, c := range cases { proof := memorydb.New() first := increaseKey(common.CopyBytes(entries[c.pos].k)) + 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) if c.err && err == nil { t.Fatalf("Expected error, got nil") } + if !c.err && err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -801,12 +946,17 @@ func TestEmptyRangeProof(t *testing.T) { 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() @@ -814,6 +964,7 @@ func TestBloatedProof(t *testing.T) { // (but only one key/value pair used as leaf) for i, entry := range entries { trie.Prove(entry.k, 0, proof) + if i == 50 { keys = append(keys, entry.k) vals = append(vals, entry.v) @@ -835,14 +986,18 @@ func TestBloatedProof(t *testing.T) { // noop technically, but practically should be rejected. func TestEmptyValueRangeProof(t *testing.T) { trie, values := randomTrie(512) + var entries entrySlice + for _, kv := range values { entries = append(entries, kv) } + sort.Sort(entries) // Create a new entry with a slightly modified key mid := len(entries) / 2 + key := common.CopyBytes(entries[mid-1].k) for n := len(key) - 1; n >= 0; n-- { if key[n] < 0xff { @@ -850,6 +1005,7 @@ func TestEmptyValueRangeProof(t *testing.T) { break } } + noop := &kv{key, []byte{}, false} entries = append(append(append([]*kv{}, entries[:mid]...), noop), entries[mid:]...) @@ -859,15 +1015,20 @@ func TestEmptyValueRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { t.Fatalf("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) } + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err == nil { t.Fatalf("Expected failure on noop entry") @@ -879,14 +1040,18 @@ func TestEmptyValueRangeProof(t *testing.T) { // practically should be rejected. func TestAllElementsEmptyValueRangeProof(t *testing.T) { trie, values := randomTrie(512) + var entries entrySlice + for _, kv := range values { entries = append(entries, kv) } + sort.Sort(entries) // Create a new entry with a slightly modified key mid := len(entries) / 2 + key := common.CopyBytes(entries[mid-1].k) for n := len(key) - 1; n >= 0; n-- { if key[n] < 0xff { @@ -894,15 +1059,19 @@ func TestAllElementsEmptyValueRangeProof(t *testing.T) { break } } + noop := &kv{key, []byte{}, false} entries = append(append(append([]*kv{}, entries[:mid]...), noop), entries[mid:]...) var keys [][]byte + var vals [][]byte + for i := 0; i < len(entries); i++ { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } + _, err := VerifyRangeProof(trie.Hash(), nil, nil, keys, vals, nil) if err == nil { t.Fatalf("Expected failure on noop entry") @@ -927,6 +1096,7 @@ func increaseKey(key []byte) []byte { break } } + return key } @@ -937,20 +1107,25 @@ func decreaseKey(key []byte) []byte { break } } + return key } func BenchmarkProve(b *testing.B) { trie, vals := randomTrie(100) + var keys []string + for k := range vals { keys = append(keys, k) } b.ResetTimer() + for i := 0; i < b.N; i++ { kv := vals[keys[i%len(keys)]] proofs := memorydb.New() + if trie.Prove(kv.k, 0, proofs); proofs.Len() == 0 { b.Fatalf("zero length proof for %x", kv.k) } @@ -960,8 +1135,11 @@ func BenchmarkProve(b *testing.B) { func BenchmarkVerifyProof(b *testing.B) { trie, vals := randomTrie(100) root := trie.Hash() + var keys []string + var proofs []*memorydb.Database + for k := range vals { keys = append(keys, k) proof := memorydb.New() @@ -970,6 +1148,7 @@ func BenchmarkVerifyProof(b *testing.B) { } b.ResetTimer() + for i := 0; i < b.N; i++ { im := i % len(keys) if _, err := VerifyProof(root, []byte(keys[im]), proofs[im]); err != nil { @@ -985,29 +1164,38 @@ func BenchmarkVerifyRangeProof5000(b *testing.B) { benchmarkVerifyRangeProof(b, func benchmarkVerifyRangeProof(b *testing.B, size int) { trie, vals := randomTrie(8192) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) start := 2 end := start + size + proof := memorydb.New() if err := trie.Prove(entries[start].k, 0, proof); err != nil { b.Fatalf("Failed to prove the first node %v", err) } + if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { b.Fatalf("Failed to prove the last node %v", err) } + var keys [][]byte + var values [][]byte + for i := start; i < end; i++ { keys = append(keys, entries[i].k) values = append(values, entries[i].v) } b.ResetTimer() + for i := 0; i < b.N; i++ { _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) if err != nil { @@ -1022,19 +1210,26 @@ func BenchmarkVerifyRangeNoProof1000(b *testing.B) { benchmarkVerifyRangeNoProof func benchmarkVerifyRangeNoProof(b *testing.B, size int) { trie, vals := randomTrie(size) + var entries entrySlice + for _, kv := range vals { entries = append(entries, kv) } + sort.Sort(entries) var keys [][]byte + var values [][]byte + for _, entry := range entries { keys = append(keys, entry.k) values = append(values, entry.v) } + b.ResetTimer() + for i := 0; i < b.N; i++ { _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, nil) if err != nil { @@ -1046,20 +1241,24 @@ func benchmarkVerifyRangeNoProof(b *testing.B, size int) { func randomTrie(n int) (*Trie, map[string]*kv) { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) vals := make(map[string]*kv) + for i := byte(0); i < 100; i++ { value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false} trie.MustUpdate(value.k, value.v) trie.MustUpdate(value2.k, value2.v) + vals[string(value.k)] = value vals[string(value2.k)] = value2 } + for i := 0; i < n; i++ { value := &kv{randBytes(32), randBytes(20), false} trie.MustUpdate(value.k, value.v) vals[string(value.k)] = value } + return trie, vals } @@ -1067,6 +1266,7 @@ func nonRandomTrie(n int) (*Trie, map[string]*kv) { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) vals := make(map[string]*kv) max := uint64(0xffffffffffffffff) + for i := uint64(0); i < uint64(n); i++ { value := make([]byte, 32) key := make([]byte, 32) @@ -1077,6 +1277,7 @@ func nonRandomTrie(n int) (*Trie, map[string]*kv) { trie.MustUpdate(elem.k, elem.v) vals[string(elem.k)] = elem } + return trie, vals } @@ -1090,16 +1291,20 @@ func TestRangeProofKeysWithSharedPrefix(t *testing.T) { common.Hex2Bytes("03"), } trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + for i, key := range keys { trie.MustUpdate(key, vals[i]) } + root := trie.Hash() proof := memorydb.New() start := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000") end := common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + if err := trie.Prove(start, 0, proof); err != nil { t.Fatalf("failed to prove start: %v", err) } + if err := trie.Prove(end, 0, proof); err != nil { t.Fatalf("failed to prove end: %v", err) } @@ -1108,6 +1313,7 @@ func TestRangeProofKeysWithSharedPrefix(t *testing.T) { if err != nil { t.Fatalf("failed to verify range proof: %v", err) } + if more != false { t.Error("expected more to be false") } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index e827384487..eb2d4e9291 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -155,11 +155,14 @@ func (t *StateTrie) MustUpdate(key, value []byte) { // If a node is not found in the database, a MissingNodeError is returned. func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error { hk := t.hashKey(key) + err := t.trie.Update(hk, value) if err != nil { return err } + t.getSecKeyCache()[string(hk)] = common.CopyBytes(key) + return nil } @@ -239,6 +242,7 @@ func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *NodeSet) { t.preimages.insertPreimage(preimages) } + t.secKeyCache = make(map[string][]byte) } // Commit the trie and return its modified nodeset. @@ -275,6 +279,7 @@ func (t *StateTrie) hashKey(key []byte) []byte { h.sha.Write(key) h.sha.Read(t.hashKeyBuf[:]) returnHasherToPool(h) + return t.hashKeyBuf[:] } @@ -286,5 +291,6 @@ func (t *StateTrie) getSecKeyCache() map[string][]byte { t.secKeyCacheOwner = t t.secKeyCache = make(map[string][]byte) } + return t.secKeyCache } diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index f7ad9f700d..35a4635bf6 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -41,6 +41,7 @@ func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { // Fill it with some arbitrary data content := make(map[string][]byte) + for i := byte(0); i < 255; i++ { // Map the same data under multiple keys key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i} @@ -66,11 +67,13 @@ func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { } // Re-create the trie based on the new state trie, _ = NewStateTrie(TrieID(root), triedb) + return triedb, trie, content } func TestSecureDelete(t *testing.T) { trie := newEmptySecure() + vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, @@ -88,8 +91,10 @@ func TestSecureDelete(t *testing.T) { trie.MustDelete([]byte(val.k)) } } + hash := trie.Hash() exp := common.HexToHash("29b235a58c3c25ab83010c327d5932bcf05324b7d6b1185e650798034783ca9d") + if hash != exp { t.Errorf("expected %x got %x", exp, hash) } @@ -106,6 +111,7 @@ func TestSecureGetKey(t *testing.T) { if !bytes.Equal(trie.MustGet(key), value) { t.Errorf("Get did not return bar") } + if k := trie.GetKey(seckey); !bytes.Equal(k, key) { t.Errorf("GetKey returned %q, want %q", k, key) } @@ -119,12 +125,14 @@ func TestStateTrieConcurrency(t *testing.T) { threads := runtime.NumCPU() tries := make([]*StateTrie, threads) + for i := 0; i < threads; i++ { tries[i] = trie.Copy() } // Start a batch of goroutines interacting with the trie pend := new(sync.WaitGroup) pend.Add(threads) + for i := 0; i < threads; i++ { go func(index int) { defer pend.Done() diff --git a/trie/stacktrie.go b/trie/stacktrie.go index de9194bf9b..d8f7c47b76 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -45,6 +45,7 @@ func stackTrieFromPool(writeFn NodeWriteFunc, owner common.Hash) *StackTrie { st := stPool.Get().(*StackTrie) st.owner = owner st.writeFn = writeFn + return st } @@ -93,6 +94,7 @@ func NewFromBinary(data []byte, writeFn NodeWriteFunc) (*StackTrie, error) { if writeFn != nil { st.setWriter(writeFn) } + return &st, nil } @@ -102,6 +104,7 @@ func (st *StackTrie) MarshalBinary() (data []byte, err error) { b bytes.Buffer w = bufio.NewWriter(&b) ) + if err := gob.NewEncoder(w).Encode(struct { Owner common.Hash NodeType uint8 @@ -115,19 +118,24 @@ func (st *StackTrie) MarshalBinary() (data []byte, err error) { }); err != nil { return nil, err } + for _, child := range st.children { if child == nil { w.WriteByte(0) continue } + w.WriteByte(1) + if childData, err := child.MarshalBinary(); err != nil { return nil, err } else { w.Write(childData) } } + w.Flush() + return b.Bytes(), nil } @@ -161,13 +169,16 @@ func (st *StackTrie) unmarshalBinary(r io.Reader) error { } else if hasChild[0] == 0 { continue } + var child StackTrie if err := child.unmarshalBinary(r); err != nil { return err } + st.children[i] = &child } + return nil } @@ -185,6 +196,7 @@ func newLeaf(owner common.Hash, key, val []byte, writeFn NodeWriteFunc) *StackTr st.nodeType = leafNode st.key = append(st.key, key...) st.val = val + return st } @@ -193,6 +205,7 @@ func newExt(owner common.Hash, key []byte, child *StackTrie, writeFn NodeWriteFu st.nodeType = extNode st.key = append(st.key, key...) st.children[0] = child + return st } @@ -208,11 +221,13 @@ const ( // Update inserts a (key, value) pair into the stack trie. func (st *StackTrie) Update(key, value []byte) error { k := keybytesToHex(key) + if len(value) == 0 { panic("deletion not supported") } st.insert(k[:len(k)-1], value, nil) + return nil } @@ -229,9 +244,11 @@ func (st *StackTrie) Reset() { st.writeFn = nil st.key = st.key[:0] st.val = nil + for i := range st.children { st.children[i] = nil } + st.nodeType = emptyNode } @@ -244,6 +261,7 @@ func (st *StackTrie) getDiffIndex(key []byte) int { return idx } } + return len(st.key) } @@ -260,6 +278,7 @@ func (st *StackTrie) insert(key, value []byte, prefix []byte) { if st.children[i].nodeType != hashedNode { st.children[i].hash(append(prefix, byte(i))) } + break } } @@ -305,7 +324,9 @@ func (st *StackTrie) insert(key, value []byte, prefix []byte) { n = st.children[0] n.hash(append(prefix, st.key...)) } + var p *StackTrie + if diffidx == 0 { // the break is on the first byte, so // the current node is converted into @@ -349,6 +370,7 @@ func (st *StackTrie) insert(key, value []byte, prefix []byte) { // chunk. In that case, no prefix extnode is necessary. // Otherwise, create that var p *StackTrie + if diffidx == 0 { // Convert current leaf into a branch st.nodeType = branchNode @@ -421,10 +443,12 @@ func (st *StackTrie) hashRec(hasher *hasher, path []byte) { st.val = types.EmptyRootHash.Bytes() st.key = st.key[:0] st.nodeType = hashedNode + return case branchNode: var nodes rawFullNode + for i, child := range st.children { if child == nil { nodes[i] = nilValueNode @@ -432,6 +456,7 @@ func (st *StackTrie) hashRec(hasher *hasher, path []byte) { } child.hashRec(hasher, append(path, byte(i))) + if len(child.val) < 32 { nodes[i] = rawNode(child.val) } else { @@ -440,6 +465,7 @@ func (st *StackTrie) hashRec(hasher *hasher, path []byte) { // Release child back to pool. st.children[i] = nil + returnToPool(child) } @@ -476,6 +502,7 @@ func (st *StackTrie) hashRec(hasher *hasher, path []byte) { st.nodeType = hashedNode st.key = st.key[:0] + if len(encodedNode) < 32 { st.val = common.CopyBytes(encodedNode) return @@ -495,6 +522,7 @@ func (st *StackTrie) Hash() (h common.Hash) { defer returnHasherToPool(hasher) st.hashRec(hasher, nil) + if len(st.val) == 32 { copy(h[:], st.val) return h @@ -505,6 +533,7 @@ func (st *StackTrie) Hash() (h common.Hash) { hasher.sha.Reset() hasher.sha.Write(st.val) hasher.sha.Read(h[:]) + return h } @@ -519,10 +548,12 @@ func (st *StackTrie) Commit() (h common.Hash, err error) { if st.writeFn == nil { return common.Hash{}, ErrCommitDisabled } + hasher := newHasher(false) defer returnHasherToPool(hasher) st.hashRec(hasher, nil) + if len(st.val) == 32 { copy(h[:], st.val) return h, nil @@ -535,5 +566,6 @@ func (st *StackTrie) Commit() (h common.Hash, err error) { hasher.sha.Read(h[:]) st.writeFn(st.owner, nil, h, st.val) + return h, nil } diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index ea3eef7882..589c124d21 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -32,6 +32,7 @@ func TestStackTrieInsertAndHash(t *testing.T) { V string // Value, directly converted to bytes. H string // Expected root hash after insert of (K, V) to an existing trie. } + tests := [][]KeyValueHash{ { // {0:0, 7:0, f:0} {"00", "v_______________________0___0", "5cb26357b95bb9af08475be00243ceb68ade0b66b5cd816b0c18a18c612d2d21"}, @@ -167,17 +168,20 @@ func TestStackTrieInsertAndHash(t *testing.T) { }, } st := NewStackTrie(nil) + for i, test := range tests { // The StackTrie does not allow Insert(), Hash(), Insert(), ... // so we will create new trie for every sequence length of inserts. for l := 1; l <= len(test); l++ { st.Reset() + for j := 0; j < l; j++ { kv := &test[j] if err := st.Update(common.FromHex(kv.K), []byte(kv.V)); err != nil { t.Fatal(err) } } + expected := common.HexToHash(test[l-1].H) if h := st.Hash(); h != expected { t.Errorf("%d(%d): root hash mismatch: %x, expected %x", i, l, h, expected) @@ -262,10 +266,12 @@ func TestUpdateSmallNodes(t *testing.T) { {"63303030", "3041"}, // stacktrie.Update {"65", "3000"}, // stacktrie.Update } + for _, kv := range kvs { nt.Update(common.FromHex(kv.K), common.FromHex(kv.V)) st.Update(common.FromHex(kv.K), common.FromHex(kv.V)) } + if nt.Hash() != st.Hash() { t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) } @@ -281,6 +287,7 @@ func TestUpdateSmallNodes(t *testing.T) { // This case was found via fuzzing. func TestUpdateVariableKeys(t *testing.T) { t.SkipNow() + st := NewStackTrie(nil) nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) kvs := []struct { @@ -290,10 +297,12 @@ func TestUpdateVariableKeys(t *testing.T) { {"0x33303534636532393561313031676174", "303030"}, {"0x3330353463653239356131303167617430", "313131"}, } + for _, kv := range kvs { nt.Update(common.FromHex(kv.K), common.FromHex(kv.V)) st.Update(common.FromHex(kv.K), common.FromHex(kv.V)) } + if nt.Hash() != st.Hash() { t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) } @@ -311,15 +320,19 @@ func TestStacktrieNotModifyValues(t *testing.T) { want := common.CopyBytes(value) st.Update([]byte{0x01}, value) st.Hash() + if have := value; !bytes.Equal(have, want) { t.Fatalf("tiny trie: have %#x want %#x", have, want) } + st = NewStackTrie(nil) } // Test with a larger trie keyB := big.NewInt(1) keyDelta := big.NewInt(1) + var vals [][]byte + getValue := func(i int) []byte { if i%2 == 0 { // large return crypto.Keccak256(big.NewInt(int64(i)).Bytes()) @@ -327,6 +340,7 @@ func TestStacktrieNotModifyValues(t *testing.T) { return big.NewInt(int64(i)).Bytes() } } + for i := 0; i < 1000; i++ { key := common.BigToHash(keyB) value := getValue(i) @@ -336,6 +350,7 @@ func TestStacktrieNotModifyValues(t *testing.T) { keyDelta.Add(keyDelta, common.Big1) } st.Hash() + for i := 0; i < 1000; i++ { want := getValue(i) @@ -357,6 +372,7 @@ func TestStacktrieSerialization(t *testing.T) { vals [][]byte keys [][]byte ) + getValue := func(i int) []byte { if i%2 == 0 { // large return crypto.Keccak256(big.NewInt(int64(i)).Bytes()) @@ -370,6 +386,7 @@ func TestStacktrieSerialization(t *testing.T) { keyB = keyB.Add(keyB, keyDelta) keyDelta.Add(keyDelta, common.Big1) } + for i, k := range keys { nt.Update(k, common.CopyBytes(vals[i])) } @@ -379,13 +396,16 @@ func TestStacktrieSerialization(t *testing.T) { if err != nil { t.Fatal(err) } + newSt, err := NewFromBinary(blob, nil) if err != nil { t.Fatal(err) } + st = newSt st.Update(k, common.CopyBytes(vals[i])) } + if have, want := st.Hash(), nt.Hash(); have != want { t.Fatalf("have %#x want %#x", have, want) } diff --git a/trie/sync.go b/trie/sync.go index a1f4ef3847..bc76aa52a9 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -72,6 +72,7 @@ func NewSyncPath(path []byte) SyncPath { if len(path) < 64 { return SyncPath{hexToCompact(path)} } + return SyncPath{hexToKeybytes(path[:64]), hexToCompact(path[64:])} } @@ -177,6 +178,7 @@ func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallb fetches: make(map[int]int), } ts.AddSubTrie(root, nil, common.Hash{}, nil, callback) + return ts } @@ -210,6 +212,7 @@ func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, par if ancestor == nil { panic(fmt.Sprintf("sub-trie ancestor not found: %x", parent)) } + ancestor.deps++ req.parent = ancestor } @@ -225,6 +228,7 @@ func (s *Sync) AddCodeEntry(hash common.Hash, path []byte, parent common.Hash, p if hash == types.EmptyCodeHash { return } + if s.membatch.hasCode(hash) { return } @@ -247,6 +251,7 @@ func (s *Sync) AddCodeEntry(hash common.Hash, path []byte, parent common.Hash, p if ancestor == nil { panic(fmt.Sprintf("raw-entry ancestor not found: %x", parent)) } + ancestor.deps++ req.parents = append(req.parents, ancestor) } @@ -263,6 +268,7 @@ func (s *Sync) Missing(max int) ([]string, []common.Hash, []common.Hash) { nodeHashes []common.Hash codeHashes []common.Hash ) + for !s.queue.Empty() && (max == 0 || len(nodeHashes)+len(codeHashes) < max) { // Retrieve the next item in line item, prio := s.queue.Peek() @@ -274,6 +280,7 @@ func (s *Sync) Missing(max int) ([]string, []common.Hash, []common.Hash) { } // Item is allowed to be scheduled, add it to the task list s.queue.Pop() + s.fetches[depth]++ switch item := item.(type) { @@ -350,10 +357,12 @@ func (s *Sync) ProcessNode(result NodeSyncResult) error { s.commitNodeRequest(req) } else { req.deps += len(requests) + for _, child := range requests { s.scheduleNodeRequest(child) } } + return nil } @@ -371,6 +380,7 @@ func (s *Sync) Commit(dbw ethdb.Batch) error { } // Drop the membatch data and return s.membatch = newSyncMemBatch() + return nil } @@ -460,6 +470,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { missing = make(chan *nodeRequest, len(children)) pending sync.WaitGroup ) + for _, child := range children { // Notify any external watcher of a new key/value node if req.callback != nil { @@ -522,6 +533,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { done = true } } + return requests, nil } @@ -537,6 +549,7 @@ func (s *Sync) commitNodeRequest(req *nodeRequest) error { // which eventually is written to db. s.membatch.size += common.HashLength + uint64(len(req.data)) delete(s.nodeReqs, string(req.path)) + s.fetches[len(req.path)]-- // Check parent for completion @@ -560,6 +573,7 @@ func (s *Sync) commitCodeRequest(req *codeRequest) error { s.membatch.codes[req.hash] = req.data s.membatch.size += common.HashLength + uint64(len(req.data)) delete(s.codeReqs, req.hash) + s.fetches[len(req.path)]-- // Check all parents for completion @@ -571,6 +585,7 @@ func (s *Sync) commitCodeRequest(req *codeRequest) error { } } } + return nil } diff --git a/trie/sync_test.go b/trie/sync_test.go index 3c9c24902a..faa465f602 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -36,6 +36,7 @@ func makeTestTrie() (*Database, *StateTrie, map[string][]byte) { // Fill it with some arbitrary data content := make(map[string][]byte) + for i := byte(0); i < 255; i++ { // Map the same data under multiple keys key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i} @@ -61,6 +62,7 @@ func makeTestTrie() (*Database, *StateTrie, map[string][]byte) { } // Re-create the trie based on the new state trie, _ = NewStateTrie(TrieID(root), triedb) + return triedb, trie, content } @@ -72,9 +74,11 @@ func checkTrieContents(t *testing.T, db *Database, root []byte, content map[stri if err != nil { t.Fatalf("failed to create trie at %x: %v", root, err) } + if err := checkTrieConsistency(db, common.BytesToHash(root)); err != nil { t.Fatalf("inconsistent trie at %x: %v", root, err) } + for key, val := range content { if have := trie.MustGet([]byte(key)); !bytes.Equal(have, val) { t.Errorf("entry %x: content mismatch: have %x, want %x", key, have, val) @@ -89,9 +93,11 @@ func checkTrieConsistency(db *Database, root common.Hash) error { if err != nil { return nil // Consider a non existent state consistent } + it := trie.NodeIterator(nil) for it.Next(true) { } + return it.Error() } @@ -137,6 +143,7 @@ func testIterativeSync(t *testing.T, count int, bypath bool) { // The code requests are ignored here since there is no code // at the testing trie. paths, nodes, _ := sched.Missing(count) + var elements []trieElement for i := 0; i < len(paths); i++ { @@ -165,18 +172,22 @@ func testIterativeSync(t *testing.T, count int, bypath bool) { if err != nil { t.Fatalf("failed to retrieve node data for path %x: %v", element.path, err) } + results[i] = NodeSyncResult{element.path, data} } } + for _, result := range results { if err := sched.ProcessNode(result); err != nil { t.Fatalf("failed to process result %v", err) } } + batch := diskdb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() paths, nodes, _ = sched.Missing(count) @@ -209,6 +220,7 @@ func TestIterativeDelayedSync(t *testing.T) { // The code requests are ignored here since there is no code // at the testing trie. paths, nodes, _ := sched.Missing(10000) + var elements []trieElement for i := 0; i < len(paths); i++ { @@ -230,15 +242,18 @@ func TestIterativeDelayedSync(t *testing.T) { results[i] = NodeSyncResult{element.path, data} } + for _, result := range results { if err := sched.ProcessNode(result); err != nil { t.Fatalf("failed to process result %v", err) } } + batch := diskdb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() paths, nodes, _ = sched.Missing(10000) @@ -283,6 +298,7 @@ func testIterativeRandomSync(t *testing.T, count int) { syncPath: NewSyncPath([]byte(paths[i])), } } + for len(queue) > 0 { // Fetch all the queued nodes in a random order results := make([]NodeSyncResult, 0, len(queue)) @@ -301,10 +317,12 @@ func testIterativeRandomSync(t *testing.T, count int) { t.Fatalf("failed to process result %v", err) } } + batch := diskdb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() paths, nodes, _ = sched.Missing(count) @@ -345,6 +363,7 @@ func TestIterativeRandomDelayedSync(t *testing.T) { syncPath: NewSyncPath([]byte(path)), } } + for len(queue) > 0 { // Sync only half of the scheduled nodes, even those in random order results := make([]NodeSyncResult, 0, len(queue)/2+1) @@ -367,11 +386,14 @@ func TestIterativeRandomDelayedSync(t *testing.T) { t.Fatalf("failed to process result %v", err) } } + batch := diskdb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() + for _, result := range results { delete(queue, result.Path) } @@ -404,6 +426,7 @@ func TestDuplicateAvoidanceSync(t *testing.T) { // The code requests are ignored here since there is no code // at the testing trie. paths, nodes, _ := sched.Missing(0) + var elements []trieElement for i := 0; i < len(paths); i++ { @@ -413,6 +436,7 @@ func TestDuplicateAvoidanceSync(t *testing.T) { syncPath: NewSyncPath([]byte(paths[i])), }) } + requested := make(map[common.Hash]struct{}) for len(elements) > 0 { @@ -432,15 +456,18 @@ func TestDuplicateAvoidanceSync(t *testing.T) { results[i] = NodeSyncResult{element.path, data} } + for _, result := range results { if err := sched.ProcessNode(result); err != nil { t.Fatalf("failed to process result %v", err) } } + batch := diskdb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() paths, nodes, _ = sched.Missing(0) @@ -477,6 +504,7 @@ func TestIncompleteSync(t *testing.T) { elements []trieElement root = srcTrie.Hash() ) + paths, nodes, _ := sched.Missing(1) for i := 0; i < len(paths); i++ { @@ -505,10 +533,12 @@ func TestIncompleteSync(t *testing.T) { t.Fatalf("failed to process result %v", err) } } + batch := diskdb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() for _, result := range results { @@ -541,6 +571,7 @@ func TestIncompleteSync(t *testing.T) { if err := checkTrieConsistency(triedb, root); err == nil { t.Fatalf("trie inconsistency not caught, missing: %x", hash) } + diskdb.Put(hash.Bytes(), value) } } @@ -586,15 +617,18 @@ func TestSyncOrdering(t *testing.T) { results[i] = NodeSyncResult{element.path, data} } + for _, result := range results { if err := sched.ProcessNode(result); err != nil { t.Fatalf("failed to process result %v", err) } } + batch := diskdb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } + batch.Write() paths, nodes, _ = sched.Missing(1) @@ -620,6 +654,7 @@ func TestSyncOrdering(t *testing.T) { // must always be single items. 2-tuples should be tested in state. t.Errorf("Invalid request tuples: len(%v) or len(%v) > 1", reqs[i], reqs[i+1]) } + if bytes.Compare(compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0])) > 0 { t.Errorf("Invalid request order: %v before %v", compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0])) } diff --git a/trie/tracer_test.go b/trie/tracer_test.go index c670efff35..a83677f643 100644 --- a/trie/tracer_test.go +++ b/trie/tracer_test.go @@ -385,6 +385,7 @@ func diffTries(trieA, trieB *Trie) (map[string][]byte, map[string][]byte, map[st } both[path] = blobA + continue } diff --git a/trie/trie.go b/trie/trie.go index 15d00d07ea..33b14bfd55 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -78,6 +78,7 @@ func New(id *ID, db NodeReader) (*Trie, error) { if err != nil { return nil, err } + trie := &Trie{ owner: id.Owner, reader: reader, @@ -89,8 +90,10 @@ func New(id *ID, db NodeReader) (*Trie, error) { if err != nil { return nil, err } + trie.root = rootnode } + return trie, nil } @@ -113,6 +116,7 @@ func (t *Trie) MustGet(key []byte) []byte { if err != nil { log.Error("Unhandled trie error in Trie.Get", "err", err) } + return res } @@ -126,6 +130,7 @@ func (t *Trie) Get(key []byte) ([]byte, error) { if err == nil && didResolve { t.root = newroot } + return value, err } @@ -146,6 +151,7 @@ func (t *Trie) get(origNode node, key []byte, pos int) (value []byte, newnode no n = n.copy() n.Val = newnode } + return value, n, didResolve, err case *fullNode: value, newnode, didResolve, err = t.get(n.Children[key[pos]], key, pos+1) @@ -153,6 +159,7 @@ func (t *Trie) get(origNode node, key []byte, pos int) (value []byte, newnode no n = n.copy() n.Children[key[pos]] = newnode } + return value, n, didResolve, err case hashNode: child, err := t.resolveAndTrack(n, key[:pos]) @@ -161,6 +168,7 @@ func (t *Trie) get(origNode node, key []byte, pos int) (value []byte, newnode no } value, newnode, _, err := t.get(child, key, pos) + return value, newnode, true, err default: panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode)) @@ -188,9 +196,11 @@ func (t *Trie) GetNode(path []byte) ([]byte, int, error) { if err != nil { return nil, resolved, err } + if resolved > 0 { t.root = newroot } + if item == nil { return nil, resolved, nil } @@ -214,11 +224,13 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod } else { hash, _ = origNode.cache() } + if hash == nil { return nil, origNode, 0, errors.New("non-consensus node") } blob, err := t.reader.nodeBlob(path, common.BytesToHash(hash)) + return blob, origNode, 1, err } // Path still needs to be traversed, descend into children @@ -238,6 +250,7 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod n = n.copy() n.Val = newnode } + return item, n, resolved, err case *fullNode: @@ -246,6 +259,7 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod n = n.copy() n.Children[path[pos]] = newnode } + return item, n, resolved, err case hashNode: @@ -255,6 +269,7 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod } item, newnode, resolved, err := t.getNode(child, path, pos) + return item, newnode, resolved + 1, err default: @@ -285,20 +300,24 @@ func (t *Trie) Update(key, value []byte) error { func (t *Trie) update(key, value []byte) error { t.unhashed++ + k := keybytesToHex(key) if len(value) != 0 { _, n, err := t.insert(t.root, nil, k, valueNode(value)) if err != nil { return err } + t.root = n } else { _, n, err := t.delete(t.root, nil, k) if err != nil { return err } + t.root = n } + return nil } @@ -307,8 +326,10 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error if v, ok := n.(valueNode); ok { return !bytes.Equal(v, value.(valueNode)), value, nil } + return true, value, nil } + switch n := n.(type) { case *shortNode: matchlen := prefixLen(key, n.Key) @@ -319,15 +340,19 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error if !dirty || err != nil { return false, n, err } + return true, &shortNode{n.Key, nn, t.newFlag()}, nil } // Otherwise branch out at the index where they differ. branch := &fullNode{flags: t.newFlag()} + var err error + _, branch.Children[n.Key[matchlen]], err = t.insert(nil, append(prefix, n.Key[:matchlen+1]...), n.Key[matchlen+1:], n.Val) if err != nil { return false, nil, err } + _, branch.Children[key[matchlen]], err = t.insert(nil, append(prefix, key[:matchlen+1]...), key[matchlen+1:], value) if err != nil { return false, nil, err @@ -349,9 +374,11 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error if !dirty || err != nil { return false, n, err } + n = n.copy() n.flags = t.newFlag() n.Children[key[0]] = nn + return true, n, nil case nil: @@ -370,10 +397,12 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error if err != nil { return false, nil, err } + dirty, nn, err := t.insert(rn, prefix, key, value) if !dirty || err != nil { return false, rn, err } + return true, nn, nil default: @@ -396,11 +425,14 @@ func (t *Trie) MustDelete(key []byte) { func (t *Trie) Delete(key []byte) error { t.unhashed++ k := keybytesToHex(key) + _, n, err := t.delete(t.root, nil, k) if err != nil { return err } + t.root = n + return nil } @@ -414,6 +446,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { if matchlen < len(n.Key) { return false, n, nil // don't replace n on mismatch } + if matchlen == len(key) { // The matched short node is deleted entirely and track // it in the deletion set. The same the valueNode doesn't @@ -430,6 +463,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { if !dirty || err != nil { return false, n, err } + switch child := child.(type) { case *shortNode: // The child shortNode is merged into its parent, track @@ -452,6 +486,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { if !dirty || err != nil { return false, n, err } + n = n.copy() n.flags = t.newFlag() n.Children[key[0]] = nn @@ -474,6 +509,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { // value that is left in n or -2 if n contains at least two // values. pos := -1 + for i, cld := range &n.Children { if cld != nil { if pos == -1 { @@ -484,6 +520,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { } } } + if pos >= 0 { if pos != 16 { // If the remaining entry is a short node, it replaces @@ -496,6 +533,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { if err != nil { return false, nil, err } + if cnode, ok := cnode.(*shortNode); ok { // Replace the entire full node with the short node. // Mark the original short node as deleted since the @@ -503,6 +541,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { t.tracer.onDelete(append(prefix, byte(pos))) k := append([]byte{byte(pos)}, cnode.Key...) + return true, &shortNode{k, cnode.Val, t.newFlag()}, nil } } @@ -527,10 +566,12 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { if err != nil { return false, nil, err } + dirty, nn, err := t.delete(rn, prefix, key) if !dirty || err != nil { return false, rn, err } + return true, nn, nil default: @@ -542,6 +583,7 @@ func concat(s1 []byte, s2 ...byte) []byte { r := make([]byte, len(s1)+len(s2)) copy(r, s1) copy(r[len(s1):], s2) + return r } @@ -549,6 +591,7 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) { if n, ok := n.(hashNode); ok { return t.resolveAndTrack(n, prefix) } + return n, nil } @@ -572,6 +615,7 @@ func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) { func (t *Trie) Hash() common.Hash { hash, cached := t.hashRoot() t.root = cached + return common.BytesToHash(hash.(hashNode)) } @@ -623,6 +667,7 @@ func (t *Trie) hashRoot() (node, node) { t.unhashed = 0 }() + hashed, cached := h.hash(t.root, true) return hashed, cached diff --git a/trie/trie_test.go b/trie/trie_test.go index edd875eec5..ae797bd8ec 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -47,6 +47,7 @@ func TestEmptyTrie(t *testing.T) { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) res := trie.Hash() exp := types.EmptyRootHash + if res != exp { t.Errorf("expected %x got %x", exp, res) } @@ -65,10 +66,12 @@ func TestNull(t *testing.T) { func TestMissingRoot(t *testing.T) { root := common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33") + trie, err := New(TrieID(root), NewDatabase(rawdb.NewMemoryDatabase())) if trie != nil { t.Error("New returned non-nil trie for invalid root") } + if _, ok := err.(*MissingNodeError); !ok { t.Errorf("New returned wrong error: %v", err) } @@ -86,35 +89,41 @@ func testMissingNode(t *testing.T, memonly bool) { updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") root, nodes := trie.Commit(false) triedb.Update(NewWithNodeSet(nodes)) + if !memonly { triedb.Commit(root, false) } trie, _ = New(TrieID(root), triedb) + _, err := trie.Get([]byte("120000")) if err != nil { t.Errorf("Unexpected error: %v", err) } trie, _ = New(TrieID(root), triedb) + _, err = trie.Get([]byte("120099")) if err != nil { t.Errorf("Unexpected error: %v", err) } trie, _ = New(TrieID(root), triedb) + _, err = trie.Get([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) } trie, _ = New(TrieID(root), triedb) + err = trie.Update([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv")) if err != nil { t.Errorf("Unexpected error: %v", err) } trie, _ = New(TrieID(root), triedb) + err = trie.Delete([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -128,30 +137,35 @@ func testMissingNode(t *testing.T, memonly bool) { } trie, _ = New(TrieID(root), triedb) + _, err = trie.Get([]byte("120000")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } trie, _ = New(TrieID(root), triedb) + _, err = trie.Get([]byte("120099")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } trie, _ = New(TrieID(root), triedb) + _, err = trie.Get([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) } trie, _ = New(TrieID(root), triedb) + err = trie.Update([]byte("120099"), []byte("zxcv")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } trie, _ = New(TrieID(root), triedb) + err = trie.Delete([]byte("123456")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) @@ -167,6 +181,7 @@ func TestInsert(t *testing.T) { exp := common.HexToHash("8aad789dff2f538bca5d8ea56e8abe10f4c7ba3a5dea95fea4cd6e7c3a1168d3") root := trie.Hash() + if root != exp { t.Errorf("case 1: exp %x got %x", exp, root) } @@ -176,6 +191,7 @@ func TestInsert(t *testing.T) { exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab") root, _ = trie.Commit(false) + if root != exp { t.Errorf("case 2: exp %x got %x", exp, root) } @@ -193,10 +209,12 @@ func TestGet(t *testing.T) { if !bytes.Equal(res, []byte("puppy")) { t.Errorf("expected puppy got %x", res) } + unknown := getString(trie, "unknown") if unknown != nil { t.Errorf("expected nil got %x", unknown) } + if i == 1 { return } @@ -209,6 +227,7 @@ func TestGet(t *testing.T) { func TestDelete(t *testing.T) { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, @@ -229,6 +248,7 @@ func TestDelete(t *testing.T) { hash := trie.Hash() exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84") + if hash != exp { t.Errorf("expected %x got %x", exp, hash) } @@ -253,6 +273,7 @@ func TestEmptyValues(t *testing.T) { hash := trie.Hash() exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84") + if hash != exp { t.Errorf("expected %x got %x", exp, hash) } @@ -270,6 +291,7 @@ func TestReplication(t *testing.T) { {"dog", "puppy"}, {"somethingveryoddindeedthis is", "myothernodedata"}, } + for _, val := range vals { updateString(trie, val.k, val.v) } @@ -282,6 +304,7 @@ func TestReplication(t *testing.T) { if err != nil { t.Fatalf("can't recreate trie at %x: %v", exp, err) } + for _, kv := range vals { if string(getString(trie2, kv.k)) != kv.v { t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v) @@ -318,6 +341,7 @@ func TestReplication(t *testing.T) { for _, val := range vals2 { updateString(trie2, val.k, val.v) } + if hash := trie2.Hash(); hash != exp { t.Errorf("root failure. expected %x got %x", exp, hash) } @@ -360,6 +384,7 @@ func TestRandomCases(t *testing.T) { {op: 1, key: common.Hex2Bytes("980c393656413a15c8da01978ed9f89feb80b502f58f2d640e3a2f5f7a99a7018f1b573befd92053ac6f78fca4a87268"), value: common.Hex2Bytes("")}, // step 24 {op: 1, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 25 } + runRandTest(rt) } @@ -388,12 +413,14 @@ const ( func (randTest) Generate(r *rand.Rand, size int) reflect.Value { var allKeys [][]byte + genKey := func() []byte { if len(allKeys) < 2 || r.Intn(100) < 10 { // new key key := make([]byte, r.Intn(50)) r.Read(key) allKeys = append(allKeys, key) + return key } // use existing key @@ -401,6 +428,7 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value { } var steps randTest + for i := 0; i < size; i++ { step := randTestStep{op: r.Intn(opMax)} switch step.op { @@ -411,8 +439,10 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value { case opGet, opDelete, opProve: step.key = genKey() } + steps = append(steps, step) } + return reflect.ValueOf(steps) } @@ -478,10 +508,10 @@ func runRandTest(rt randTest) bool { values = make(map[string]string) // tracks content of the trie origTrie = NewEmpty(triedb) ) + for i, step := range rt { // fmt.Printf("{op: %d, key: common.Hex2Bytes(\"%x\"), value: common.Hex2Bytes(\"%x\")}, // step %d\n", // step.op, step.key, step.value, i) - switch step.op { case opUpdate: tr.MustUpdate(step.key, step.value) @@ -492,6 +522,7 @@ func runRandTest(rt randTest) bool { case opGet: v := tr.MustGet(step.key) want := values[string(step.key)] + if string(v) != want { rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want) } @@ -533,14 +564,17 @@ func runRandTest(rt randTest) bool { return false } } + tr = newtr origTrie = tr.Copy() case opItercheckhash: checktr := NewEmpty(triedb) it := NewIterator(tr.NodeIterator(nil)) + for it.Next() { checktr.MustUpdate(it.Key, it.Value) } + if tr.Hash() != checktr.Hash() { rt[i].err = fmt.Errorf("hash mismatch in opItercheckhash") } @@ -612,6 +646,7 @@ func runRandTest(rt randTest) bool { return false } } + return true } @@ -620,6 +655,7 @@ func TestRandom(t *testing.T) { if cerr, ok := err.(*quick.CheckError); ok { t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In)) } + t.Fatal(err) } } @@ -635,6 +671,7 @@ func benchGet(b *testing.B) { triedb := NewDatabase(rawdb.NewMemoryDatabase()) trie := NewEmpty(triedb) + k := make([]byte, 32) for i := 0; i < benchElemCount; i++ { binary.LittleEndian.PutUint64(k, uint64(i)) @@ -643,6 +680,7 @@ func benchGet(b *testing.B) { binary.LittleEndian.PutUint64(k, benchElemCount/2) b.ResetTimer() + for i := 0; i < b.N; i++ { trie.MustGet(k) } @@ -652,11 +690,14 @@ func benchGet(b *testing.B) { func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie { trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) k := make([]byte, 32) + b.ReportAllocs() + for i := 0; i < b.N; i++ { e.PutUint64(k, uint64(i)) trie.MustUpdate(k, k) } + return trie } @@ -682,10 +723,12 @@ func BenchmarkHash(b *testing.B) { // Insert the accounts into the trie and hash it trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) i := 0 + for ; i < len(addresses)/2; i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } trie.Hash() + for ; i < len(addresses); i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } @@ -714,6 +757,7 @@ func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) { // Make the random benchmark deterministic addresses, accounts := makeAccounts(b.N) trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + for i := 0; i < len(addresses); i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } @@ -729,16 +773,19 @@ func TestTinyTrie(t *testing.T) { _, accounts := makeAccounts(5) trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3]) + if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root { t.Errorf("1: got %x, exp %x", root, exp) } trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001338"), accounts[4]) + if exp, root := common.HexToHash("ec63b967e98a5720e7f720482151963982890d82c9093c0d486b7eb8883a66b1"), trie.Hash(); exp != root { t.Errorf("2: got %x, exp %x", root, exp) } trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001339"), accounts[4]) + if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root { t.Errorf("3: got %x, exp %x", root, exp) } @@ -759,6 +806,7 @@ func TestCommitAfterHash(t *testing.T) { // Create a realistic account trie to hash addresses, accounts := makeAccounts(1000) trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + for i := 0; i < len(addresses); i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } @@ -789,6 +837,7 @@ func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) { random.Read(data) copy(addresses[i][:], data) } + accounts = make([][]byte, len(addresses)) for i := 0; i < len(accounts); i++ { var ( @@ -807,6 +856,7 @@ func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) { data, _ := rlp.EncodeToBytes(&types.StateAccount{Nonce: nonce, Balance: balance, Root: root, CodeHash: code}) accounts[i] = data } + return addresses, accounts } @@ -831,9 +881,11 @@ func (s *spongeDb) Put(key []byte, value []byte) error { if len(valbrief) > 8 { valbrief = valbrief[:8] } + s.journal = append(s.journal, fmt.Sprintf("%v: PUT([%x...], [%d bytes] %x...)\n", s.id, key[:8], len(value), valbrief)) s.sponge.Write(key) s.sponge.Write(value) + return nil } func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") } @@ -880,6 +932,7 @@ func TestCommitSequence(t *testing.T) { db.Update(NewWithNodeSet(nodes)) // Flush memdb -> disk (sponge) db.Commit(root, false) + if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { t.Errorf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) } @@ -905,6 +958,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) { // Fill the trie with elements for i := 0; i < tc.count; i++ { key := make([]byte, 32) + var val []byte // 50% short elements, 50% large elements if prng.Intn(2) == 0 { @@ -912,6 +966,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) { } else { val = make([]byte, 1+prng.Intn(4096)) } + prng.Read(key) prng.Read(val) trie.MustUpdate(key, val) @@ -921,6 +976,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) { db.Update(NewWithNodeSet(nodes)) // Flush memdb -> disk (sponge) db.Commit(root, false) + 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) } @@ -944,6 +1000,7 @@ func TestCommitSequenceStackTrie(t *testing.T) { // For the stack trie, we need to do inserts in proper order key := make([]byte, 32) binary.BigEndian.PutUint64(key, uint64(i)) + var val []byte // 50% short elements, 50% large elements if prng.Intn(2) == 0 { @@ -951,6 +1008,7 @@ func TestCommitSequenceStackTrie(t *testing.T) { } else { val = make([]byte, 1+prng.Intn(1024)) } + prng.Read(val) trie.Update(key, val) stTrie.Update(key, val) @@ -965,19 +1023,25 @@ func TestCommitSequenceStackTrie(t *testing.T) { if err != nil { t.Fatalf("Failed to commit stack trie %v", err) } + if stRoot != root { t.Fatalf("root wrong, got %x exp %x", stRoot, root) } + if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) { // Show the journal t.Logf("Expected:") + for i, v := range s.journal { t.Logf("op %d: %v", i, v) } + t.Logf("Stacktrie:") + for i, v := range stackTrieSponge.journal { t.Logf("op %d: %v", i, v) } + t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", count, got, exp) } } @@ -1013,11 +1077,13 @@ func TestCommitSequenceSmallRoot(t *testing.T) { if err != nil { t.Fatalf("Failed to commit stack trie %v", err) } + if stRoot != root { t.Fatalf("root wrong, got %x exp %x", stRoot, root) } t.Logf("root: %x\n", stRoot) + if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) { t.Fatalf("test, disk write sequence wrong:\ngot %x exp %x\n", got, exp) } @@ -1030,6 +1096,7 @@ func TestCommitSequenceSmallRoot(t *testing.T) { func BenchmarkHashFixedSize(b *testing.B) { b.Run("10", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(20) for i := 0; i < b.N; i++ { benchmarkHashFixedSize(b, acc, add) @@ -1037,6 +1104,7 @@ func BenchmarkHashFixedSize(b *testing.B) { }) b.Run("100", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(100) for i := 0; i < b.N; i++ { benchmarkHashFixedSize(b, acc, add) @@ -1045,6 +1113,7 @@ func BenchmarkHashFixedSize(b *testing.B) { b.Run("1K", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(1000) for i := 0; i < b.N; i++ { benchmarkHashFixedSize(b, acc, add) @@ -1052,6 +1121,7 @@ func BenchmarkHashFixedSize(b *testing.B) { }) b.Run("10K", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(10000) for i := 0; i < b.N; i++ { benchmarkHashFixedSize(b, acc, add) @@ -1059,6 +1129,7 @@ func BenchmarkHashFixedSize(b *testing.B) { }) b.Run("100K", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(100000) for i := 0; i < b.N; i++ { benchmarkHashFixedSize(b, acc, add) @@ -1083,6 +1154,7 @@ func benchmarkHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byt func BenchmarkCommitAfterHashFixedSize(b *testing.B) { b.Run("10", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(20) for i := 0; i < b.N; i++ { benchmarkCommitAfterHashFixedSize(b, acc, add) @@ -1090,6 +1162,7 @@ func BenchmarkCommitAfterHashFixedSize(b *testing.B) { }) b.Run("100", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(100) for i := 0; i < b.N; i++ { benchmarkCommitAfterHashFixedSize(b, acc, add) @@ -1098,6 +1171,7 @@ func BenchmarkCommitAfterHashFixedSize(b *testing.B) { b.Run("1K", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(1000) for i := 0; i < b.N; i++ { benchmarkCommitAfterHashFixedSize(b, acc, add) @@ -1105,6 +1179,7 @@ func BenchmarkCommitAfterHashFixedSize(b *testing.B) { }) b.Run("10K", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(10000) for i := 0; i < b.N; i++ { benchmarkCommitAfterHashFixedSize(b, acc, add) @@ -1112,6 +1187,7 @@ func BenchmarkCommitAfterHashFixedSize(b *testing.B) { }) b.Run("100K", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(100000) for i := 0; i < b.N; i++ { benchmarkCommitAfterHashFixedSize(b, acc, add) @@ -1137,6 +1213,7 @@ func benchmarkCommitAfterHashFixedSize(b *testing.B, addresses [][20]byte, accou func BenchmarkDerefRootFixedSize(b *testing.B) { b.Run("10", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(20) for i := 0; i < b.N; i++ { benchmarkDerefRootFixedSize(b, acc, add) @@ -1144,6 +1221,7 @@ func BenchmarkDerefRootFixedSize(b *testing.B) { }) b.Run("100", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(100) for i := 0; i < b.N; i++ { benchmarkDerefRootFixedSize(b, acc, add) @@ -1152,6 +1230,7 @@ func BenchmarkDerefRootFixedSize(b *testing.B) { b.Run("1K", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(1000) for i := 0; i < b.N; i++ { benchmarkDerefRootFixedSize(b, acc, add) @@ -1159,6 +1238,7 @@ func BenchmarkDerefRootFixedSize(b *testing.B) { }) b.Run("10K", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(10000) for i := 0; i < b.N; i++ { benchmarkDerefRootFixedSize(b, acc, add) @@ -1166,6 +1246,7 @@ func BenchmarkDerefRootFixedSize(b *testing.B) { }) b.Run("100K", func(b *testing.B) { b.StopTimer() + acc, add := makeAccounts(100000) for i := 0; i < b.N; i++ { benchmarkDerefRootFixedSize(b, acc, add) @@ -1182,6 +1263,7 @@ func benchmarkDerefRootFixedSize(b *testing.B, addresses [][20]byte, accounts [] for i := 0; i < len(addresses); i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } + h := trie.Hash() _, nodes := trie.Commit(false) triedb.Update(NewWithNodeSet(nodes)) @@ -1209,6 +1291,7 @@ func TestDecodeNode(t *testing.T) { hash = make([]byte, 20) elems = make([]byte, 20) ) + for i := 0; i < 5000000; i++ { prng.Read(hash) prng.Read(elems)