-
Notifications
You must be signed in to change notification settings - Fork 70
fix(api): XDPoS_getRewardByAccount to be able to parse scientific not #2301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev-upgrade
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -614,20 +614,43 @@ func getEpochReward(account common.Address, header *types.Header) (AccountEpochR | |||||||||
| return epochReward, nil | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // jsonNumberToBigInt parses a json.Number into a *big.Int, handling both plain | ||||||||||
| // decimal strings (e.g. "4500000000000000000") and scientific notation | ||||||||||
| // (e.g. "4.5e+21") that big.Int.SetString cannot parse directly. | ||||||||||
| func jsonNumberToBigInt(n json.Number) (*big.Int, bool) { | ||||||||||
| s := n.String() | ||||||||||
| // Try plain integer first — the common case. | ||||||||||
| if i, ok := new(big.Int).SetString(s, 10); ok { | ||||||||||
| return i, true | ||||||||||
| } | ||||||||||
| // Fall back to big.Float to handle scientific notation. | ||||||||||
| f, _, err := new(big.Float).SetPrec(256).Parse(s, 10) | ||||||||||
| if err != nil { | ||||||||||
| log.Warn("[jsonNumberToBigInt] Failed to parse json.Number:", "number", s, "err", err) | ||||||||||
| return nil, false | ||||||||||
| } | ||||||||||
|
|
||||||||||
| i, acc := f.Int(nil) | ||||||||||
| if acc != big.Exact { | ||||||||||
| // The value had a fractional part; truncate is the best we can do | ||||||||||
| log.Debug("[jsonNumberToBigInt] json.Number had fractional part, truncated to integer", "number", s, "truncated", i.String(), "accuracy", acc) | ||||||||||
|
Comment on lines
+635
to
+636
|
||||||||||
| // The value had a fractional part; truncate is the best we can do | |
| log.Debug("[jsonNumberToBigInt] json.Number had fractional part, truncated to integer", "number", s, "truncated", i.String(), "accuracy", acc) | |
| log.Warn("[jsonNumberToBigInt] json.Number is not an exact integer value", "number", s, "truncated", i.String(), "accuracy", acc) | |
| return nil, false |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||
| package XDPoS | ||||||||||
|
|
||||||||||
| import ( | ||||||||||
| "encoding/json" | ||||||||||
| "math/big" | ||||||||||
| "testing" | ||||||||||
|
|
||||||||||
|
|
@@ -69,3 +70,98 @@ func TestCalculateSignersTimeout(t *testing.T) { | |||||||||
| calculateSigners(info, timeouts.Get(), masternodes) | ||||||||||
| assert.Equal(t, info["10:450"].CurrentNumber, 2) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| func TestJsonNumberToBigInt(t *testing.T) { | ||||||||||
| tests := []struct { | ||||||||||
| name string | ||||||||||
| input json.Number | ||||||||||
| want *big.Int | ||||||||||
| wantOk bool | ||||||||||
| }{ | ||||||||||
| { | ||||||||||
| name: "plain decimal integer", | ||||||||||
| input: json.Number("4500000000000000000000"), | ||||||||||
| want: new(big.Int).Mul(big.NewInt(45), new(big.Int).Exp(big.NewInt(10), big.NewInt(20), nil)), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "scientific notation 4.5e+21", | ||||||||||
| input: json.Number("4.5e+21"), | ||||||||||
| want: new(big.Int).Mul(big.NewInt(45), new(big.Int).Exp(big.NewInt(10), big.NewInt(20), nil)), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "scientific notation 1e+18", | ||||||||||
| input: json.Number("1e+18"), | ||||||||||
| want: new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "scientific notation uppercase E", | ||||||||||
| input: json.Number("4.5E+21"), | ||||||||||
| want: new(big.Int).Mul(big.NewInt(45), new(big.Int).Exp(big.NewInt(10), big.NewInt(20), nil)), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "zero", | ||||||||||
| input: json.Number("0"), | ||||||||||
| want: big.NewInt(0), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "small integer", | ||||||||||
| input: json.Number("12345"), | ||||||||||
| want: big.NewInt(12345), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "fractional value truncates", | ||||||||||
| input: json.Number("1.23e+1"), | ||||||||||
| want: big.NewInt(12), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "decimal without exponent", | ||||||||||
| input: json.Number("123.456"), | ||||||||||
| want: big.NewInt(123), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "decimal whole number", | ||||||||||
| input: json.Number("1000.0"), | ||||||||||
| want: big.NewInt(1000), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "negative integer", | ||||||||||
| input: json.Number("-500"), | ||||||||||
| want: big.NewInt(-500), | ||||||||||
| wantOk: true, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "invalid string", | ||||||||||
| input: json.Number("not_a_number"), | ||||||||||
| want: nil, | ||||||||||
| wantOk: false, | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| name: "empty string", | ||||||||||
| input: json.Number(""), | ||||||||||
| want: nil, | ||||||||||
| wantOk: false, | ||||||||||
| }, | ||||||||||
| } | ||||||||||
|
|
||||||||||
| for _, tt := range tests { | ||||||||||
| t.Run(tt.name, func(t *testing.T) { | ||||||||||
| got, ok := jsonNumberToBigInt(tt.input) | ||||||||||
| if tt.wantOk { | ||||||||||
| assert.True(t, ok, "input %q: parse failed, expected %s", tt.input, tt.want) | ||||||||||
| assert.Equal(t, 0, tt.want.Cmp(got), "input %q: expected %s but got %s", tt.input, tt.want, got) | ||||||||||
| } else { | ||||||||||
|
Comment on lines
+157
to
+161
|
||||||||||
| assert.False(t, ok, "input %q: expected parse failure but got %s", tt.input, got) | ||||||||||
| assert.Nil(t, got, "input %q: expected nil but got %s", tt.input, got) | ||||||||||
|
Comment on lines
+162
to
+163
|
||||||||||
| assert.False(t, ok, "input %q: expected parse failure but got %s", tt.input, got) | |
| assert.Nil(t, got, "input %q: expected nil but got %s", tt.input, got) | |
| assert.False(t, ok, "input %q: expected parse failure but got %v", tt.input, got) | |
| assert.Nil(t, got, "input %q: expected nil but got %v", tt.input, got) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using big.Float with a fixed 256-bit precision to parse JSON numbers can round large values; when this happens, converting to *big.Int may yield an incorrect integer. For reward amounts (which should be exact), consider an exact parsing strategy (e.g., mantissa+exponent to big.Int/big.Rat) instead of big.Float.