From dfd4f6978f678423f81b41c5705f053479f7d489 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 10 Jan 2023 21:40:15 +0300 Subject: [PATCH] bigint: don't reallocate big.Int in ToBytes(), fix #2864 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In some cases n.Add() can reuse the []Word buffer and n.Sub() reallocate it away. If that happens, we're out of luck with 0.99.0+ versions (since 3945e8185773c1001ad698f94271a082260aacbe). I'm not sure why it does that, bit width doesn't change in most of the cases and even if it does, we still have enough of it in cap() to hold the old Abs() value (when we have a negative value we in fact decreate its Abs() first and increase it back afterwards). Still, that's what we have. So when we have processTokenTransfer() doing Neg/Neg in-place its value is not affected, but the original []Word bits that are reused by amount value are (they're shared initially, Amount: *amount). name old time/op new time/op delta ToPreallocatedBytes-8 65.8ns ± 2% 45.6ns ± 2% -30.73% (p=0.008 n=5+5) name old alloc/op new alloc/op delta ToPreallocatedBytes-8 0.00B 0.00B ~ (all equal) name old allocs/op new allocs/op delta ToPreallocatedBytes-8 0.00 0.00 ~ (all equal) --- pkg/encoding/bigint/bench_test.go | 2 ++ pkg/encoding/bigint/bigint.go | 26 +++++++++++++++++++++++--- pkg/encoding/bigint/bigint_test.go | 8 +++++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/pkg/encoding/bigint/bench_test.go b/pkg/encoding/bigint/bench_test.go index 411a10b85..893be0118 100644 --- a/pkg/encoding/bigint/bench_test.go +++ b/pkg/encoding/bigint/bench_test.go @@ -7,9 +7,11 @@ import ( func BenchmarkToPreallocatedBytes(b *testing.B) { v := big.NewInt(100500) + vn := big.NewInt(-100500) buf := make([]byte, 4) for i := 0; i < b.N; i++ { _ = ToPreallocatedBytes(v, buf[:0]) + _ = ToPreallocatedBytes(vn, buf[:0]) } } diff --git a/pkg/encoding/bigint/bigint.go b/pkg/encoding/bigint/bigint.go index 4b9779e4a..8274bb684 100644 --- a/pkg/encoding/bigint/bigint.go +++ b/pkg/encoding/bigint/bigint.go @@ -1,6 +1,7 @@ package bigint import ( + "math" "math/big" "math/bits" @@ -113,9 +114,28 @@ func ToPreallocatedBytes(n *big.Int, data []byte) []byte { } if sign < 0 { - n.Add(n, bigOne) - defer func() { n.Sub(n, bigOne) }() - if n.Sign() == 0 { // n == -1 + bits := n.Bits() + carry := true + nonZero := false + for i := range bits { + if carry { + bits[i]-- + carry = (bits[i] == math.MaxUint) + } + nonZero = nonZero || (bits[i] != 0) + } + defer func() { + var carry = true + for i := range bits { + if carry { + bits[i]++ + carry = (bits[i] == 0) + } else { + break + } + } + }() + if !nonZero { // n == -1 return append(data[:0], 0xFF) } } diff --git a/pkg/encoding/bigint/bigint_test.go b/pkg/encoding/bigint/bigint_test.go index 79c277546..39c40d666 100644 --- a/pkg/encoding/bigint/bigint_test.go +++ b/pkg/encoding/bigint/bigint_test.go @@ -106,8 +106,14 @@ var testCases = []struct { func TestIntToBytes(t *testing.T) { for _, tc := range testCases { - buf := ToBytes(big.NewInt(tc.number)) + num := big.NewInt(tc.number) + var numC = *num // See #2864. + buf := ToBytes(num) assert.Equal(t, tc.buf, buf, "error while converting %d", tc.number) + _ = numC.Neg(&numC) + _ = ToBytes(&numC) + _ = numC.Neg(&numC) + assert.Equal(t, num, &numC, "number mismatch after converting %d", tc.number) } }