core: use big.Int to store NEP5 balances

closes #1133
This commit is contained in:
Anna Shaleva 2020-07-09 12:57:24 +03:00
parent 43b28ffa06
commit abe3c94b95
15 changed files with 93 additions and 72 deletions

View file

@ -694,7 +694,7 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.C
}
amount = bigint.FromBytes(bs)
}
bc.processNEP5Transfer(d, h, b, note.ScriptHash, from, to, amount.Int64())
bc.processNEP5Transfer(d, h, b, note.ScriptHash, from, to, amount)
}
func parseUint160(addr []byte) util.Uint160 {
@ -704,7 +704,7 @@ func parseUint160(addr []byte) util.Uint160 {
return util.Uint160{}
}
func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *block.Block, sc util.Uint160, from, to []byte, amount int64) {
func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *block.Block, sc util.Uint160, from, to []byte, amount *big.Int) {
toAddr := parseUint160(to)
fromAddr := parseUint160(from)
transfer := &state.NEP5Transfer{
@ -721,11 +721,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
return
}
bs := balances.Trackers[sc]
bs.Balance -= amount
bs.Balance = *new(big.Int).Sub(&bs.Balance, amount)
bs.LastUpdatedBlock = b.Index
balances.Trackers[sc] = bs
transfer.Amount = -amount
transfer.Amount = *new(big.Int).Sub(&transfer.Amount, amount)
isBig, err := cache.AppendNEP5Transfer(fromAddr, balances.NextTransferBatch, transfer)
if err != nil {
return
@ -743,11 +742,11 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
return
}
bs := balances.Trackers[sc]
bs.Balance += amount
bs.Balance = *new(big.Int).Add(&bs.Balance, amount)
bs.LastUpdatedBlock = b.Index
balances.Trackers[sc] = bs
transfer.Amount = amount
transfer.Amount = *amount
isBig, err := cache.AppendNEP5Transfer(toAddr, balances.NextTransferBatch, transfer)
if err != nil {
return
@ -788,23 +787,24 @@ func (bc *Blockchain) GetNEP5Balances(acc util.Uint160) *state.NEP5Balances {
}
// GetUtilityTokenBalance returns utility token (GAS) balance for the acc.
func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) int64 {
func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
bs, err := bc.dao.GetNEP5Balances(acc)
if err != nil {
return 0
return big.NewInt(0)
}
return bs.Trackers[bc.contracts.GAS.Hash].Balance
balance := bs.Trackers[bc.contracts.GAS.Hash].Balance
return &balance
}
// GetGoverningTokenBalance returns governing token (NEO) balance and the height
// of the last balance change for the account.
func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (int64, uint32) {
func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) {
bs, err := bc.dao.GetNEP5Balances(acc)
if err != nil {
return 0, 0
return big.NewInt(0), 0
}
neo := bs.Trackers[bc.contracts.NEO.Hash]
return neo.Balance, neo.LastUpdatedBlock
return &neo.Balance, neo.LastUpdatedBlock
}
// LastBatch returns last persisted storage batch.
@ -1062,7 +1062,7 @@ func (bc *Blockchain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult)
// amount of NEO between specified blocks. The amount of NEO being passed is in
// its natural non-divisible form (1 NEO as 1, 2 NEO as 2, no multiplication by
// 10⁸ is needed as for Fixed8).
func (bc *Blockchain) CalculateClaimable(value int64, startHeight, endHeight uint32) int64 {
func (bc *Blockchain) CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int {
var amount int64
di := uint32(bc.decrementInterval)
@ -1088,7 +1088,7 @@ func (bc *Blockchain) CalculateClaimable(value int64, startHeight, endHeight uin
amount += int64(iend-istart) * int64(bc.generationAmount[ustart])
}
return amount * value
return new(big.Int).Mul(big.NewInt(amount), value)
}
// FeePerByte returns transaction network fee per byte.
@ -1148,7 +1148,7 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
}
balance := bc.GetUtilityTokenBalance(t.Sender)
need := t.SystemFee + t.NetworkFee
if balance < need {
if balance.Cmp(big.NewInt(need)) < 0 {
return errors.Errorf("insufficient funds: balance is %v, need: %v", balance, need)
}
size := io.GetVarSize(t)

View file

@ -1,6 +1,7 @@
package core
import (
"math/big"
"testing"
"time"
@ -196,23 +197,23 @@ func TestGetClaimable(t *testing.T) {
require.NoError(t, err)
t.Run("first generation period", func(t *testing.T) {
amount := bc.CalculateClaimable(1, 0, 2)
require.EqualValues(t, 8, amount)
amount := bc.CalculateClaimable(big.NewInt(1), 0, 2)
require.EqualValues(t, big.NewInt(8), amount)
})
t.Run("a number of full periods", func(t *testing.T) {
amount := bc.CalculateClaimable(1, 0, 6)
require.EqualValues(t, 4+4+3+3+2+2, amount)
amount := bc.CalculateClaimable(big.NewInt(1), 0, 6)
require.EqualValues(t, big.NewInt(4+4+3+3+2+2), amount)
})
t.Run("start from the 2-nd block", func(t *testing.T) {
amount := bc.CalculateClaimable(1, 1, 7)
require.EqualValues(t, 4+3+3+2+2+1, amount)
amount := bc.CalculateClaimable(big.NewInt(1), 1, 7)
require.EqualValues(t, big.NewInt(4+3+3+2+2+1), amount)
})
t.Run("end height after generation has ended", func(t *testing.T) {
amount := bc.CalculateClaimable(1, 1, 10)
require.EqualValues(t, 4+3+3+2+2+1+1, amount)
amount := bc.CalculateClaimable(big.NewInt(1), 1, 10)
require.EqualValues(t, big.NewInt(4+3+3+2+2+1+1), amount)
})
}

View file

@ -1,6 +1,8 @@
package blockchainer
import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
@ -19,13 +21,13 @@ type Blockchainer interface {
AddHeaders(...*block.Header) error
AddBlock(*block.Block) error
BlockHeight() uint32
CalculateClaimable(value int64, startHeight, endHeight uint32) int64
CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int
Close()
HeaderHeight() uint32
GetBlock(hash util.Uint256) (*block.Block, error)
GetContractState(hash util.Uint160) *state.Contract
GetEnrollments() ([]state.Validator, error)
GetGoverningTokenBalance(acc util.Uint160) (int64, uint32)
GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32)
GetHeaderHash(int) util.Uint256
GetHeader(hash util.Uint256) (*block.Header, error)
CurrentHeaderHash() util.Uint256

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"os"
"testing"
"time"
@ -181,7 +182,7 @@ func TestCreateBasicChain(t *testing.T) {
priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash()
require.Equal(t, int64(0), bc.GetUtilityTokenBalance(priv0ScriptHash))
require.Equal(t, big.NewInt(0), bc.GetUtilityTokenBalance(priv0ScriptHash))
// Move some NEO to one simple account.
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
txMoveNeo.ValidUntilBlock = validUntilBlock
@ -211,7 +212,7 @@ func TestCreateBasicChain(t *testing.T) {
t.Logf("txMoveNeo: %s", txMoveNeo.Hash().StringLE())
t.Logf("txMoveGas: %s", txMoveGas.Hash().StringLE())
require.True(t, bc.GetUtilityTokenBalance(priv0ScriptHash) >= 1000*native.GASFactor)
require.True(t, bc.GetUtilityTokenBalance(priv0ScriptHash).Cmp(big.NewInt(1000*native.GASFactor)) >= 0)
// info for getblockheader rpc tests
t.Logf("header hash: %s", b.Hash().StringLE())
buf := io.NewBufBinWriter()

View file

@ -1,11 +1,13 @@
package mempool
import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// Feer is an interface that abstract the implementation of the fee calculation.
type Feer interface {
FeePerByte() int64
GetUtilityTokenBalance(util.Uint160) int64
GetUtilityTokenBalance(util.Uint160) *big.Int
}

View file

@ -2,6 +2,7 @@ package mempool
import (
"errors"
"math/big"
"sort"
"sync"
"time"
@ -35,7 +36,7 @@ type items []*item
// utilityBalanceAndFees stores sender's balance and overall fees of
// sender's transactions which are currently in mempool
type utilityBalanceAndFees struct {
balance int64
balance *big.Int
feeSum int64
}
@ -124,7 +125,7 @@ func (mp *Pool) checkBalanceAndUpdate(tx *transaction.Transaction, feer Feer) bo
mp.fees[tx.Sender] = senderFee
}
needFee := senderFee.feeSum + tx.SystemFee + tx.NetworkFee
if senderFee.balance < needFee {
if senderFee.balance.Cmp(big.NewInt(needFee)) < 0 {
return false
}
return true

View file

@ -1,6 +1,7 @@
package mempool
import (
"math/big"
"sort"
"testing"
@ -16,13 +17,13 @@ type FeerStub struct {
feePerByte int64
}
const balance = 10000000
var balance = big.NewInt(10000000)
func (fs *FeerStub) FeePerByte() int64 {
return fs.feePerByte
}
func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) int64 {
func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
return balance
}
@ -184,7 +185,7 @@ func TestMemPoolFees(t *testing.T) {
mp := NewMemPool(10)
sender0 := util.Uint160{1, 2, 3}
tx0 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
tx0.NetworkFee = balance + 1
tx0.NetworkFee = balance.Int64() + 1
tx0.Sender = sender0
// insufficient funds to add transaction, but balance should be stored
require.Equal(t, false, mp.Verify(tx0, &FeerStub{}))
@ -195,9 +196,10 @@ func TestMemPoolFees(t *testing.T) {
feeSum: 0,
}, mp.fees[sender0])
balancePart := new(big.Int).Div(balance, big.NewInt(4))
// no problems with adding another transaction with lower fee
tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
tx1.NetworkFee = balance * 0.7
tx1.NetworkFee = balancePart.Int64()
tx1.Sender = sender0
require.NoError(t, mp.Add(tx1, &FeerStub{}))
require.Equal(t, 1, len(mp.fees))
@ -208,14 +210,14 @@ func TestMemPoolFees(t *testing.T) {
// balance shouldn't change after adding one more transaction
tx2 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
tx2.NetworkFee = balance * 0.3
tx2.NetworkFee = new(big.Int).Sub(balance, balancePart).Int64()
tx2.Sender = sender0
require.NoError(t, mp.Add(tx2, &FeerStub{}))
require.Equal(t, 2, len(mp.verifiedTxes))
require.Equal(t, 1, len(mp.fees))
require.Equal(t, utilityBalanceAndFees{
balance: balance,
feeSum: balance,
feeSum: balance.Int64(),
}, mp.fees[sender0])
// can't add more transactions as we don't have enough GAS
@ -227,7 +229,7 @@ func TestMemPoolFees(t *testing.T) {
require.Equal(t, 1, len(mp.fees))
require.Equal(t, utilityBalanceAndFees{
balance: balance,
feeSum: balance,
feeSum: balance.Int64(),
}, mp.fees[sender0])
// check whether sender's fee updates correctly

View file

@ -197,9 +197,9 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB
if ic.Block == nil || ic.Block.Index == 0 {
return nil
}
gen := ic.Chain.CalculateClaimable(acc.Balance.Int64(), acc.BalanceHeight, ic.Block.Index)
gen := ic.Chain.CalculateClaimable(&acc.Balance, acc.BalanceHeight, ic.Block.Index)
acc.BalanceHeight = ic.Block.Index
n.GAS.mint(ic, h, big.NewInt(gen))
n.GAS.mint(ic, h, gen)
return nil
}
@ -212,8 +212,8 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem
}
tr := bs.Trackers[n.Hash]
gen := ic.Chain.CalculateClaimable(tr.Balance, tr.LastUpdatedBlock, end)
return stackitem.NewBigInteger(big.NewInt(gen))
gen := ic.Chain.CalculateClaimable(&tr.Balance, tr.LastUpdatedBlock, end)
return stackitem.NewBigInteger(gen)
}
func (n *NEO) registerValidator(ic *interop.Context, args []stackitem.Item) stackitem.Item {

View file

@ -214,7 +214,7 @@ func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item)
panic(err)
}
balance := bs.Trackers[c.Hash].Balance
return stackitem.NewBigInteger(big.NewInt(balance))
return stackitem.NewBigInteger(&balance)
}
func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) {

View file

@ -1,6 +1,9 @@
package state
import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
@ -8,7 +11,7 @@ import (
// NEP5Tracker contains info about a single account in a NEP5 contract.
type NEP5Tracker struct {
// Balance is the current balance of the account.
Balance int64
Balance big.Int
// LastUpdatedBlock is a number of block when last `transfer` to or from the
// account occured.
LastUpdatedBlock uint32
@ -31,7 +34,7 @@ type NEP5Transfer struct {
To util.Uint160
// Amount is the amount of tokens transferred.
// It is negative when tokens are sent and positive if they are received.
Amount int64
Amount big.Int
// Block is a number of block when the event occured.
Block uint32
// Timestamp is the timestamp of the block where transfer occured.
@ -118,13 +121,13 @@ func (lg *NEP5TransferLog) Size() int {
// EncodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
w.WriteU64LE(uint64(t.Balance))
w.WriteVarBytes(bigint.ToBytes(&t.Balance))
w.WriteU32LE(t.LastUpdatedBlock)
}
// DecodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
t.Balance = int64(r.ReadU64LE())
t.Balance = *bigint.FromBytes(r.ReadVarBytes())
t.LastUpdatedBlock = r.ReadU32LE()
}
@ -136,7 +139,9 @@ func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block)
w.WriteU64LE(t.Timestamp)
w.WriteU64LE(uint64(t.Amount))
amountBytes := bigint.ToBytes(&t.Amount)
w.WriteU64LE(uint64(len(amountBytes)))
w.WriteBytes(amountBytes)
}
// DecodeBinary implements io.Serializable interface.
@ -152,6 +157,9 @@ func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int {
r.ReadBytes(t.To[:])
t.Block = r.ReadU32LE()
t.Timestamp = r.ReadU64LE()
t.Amount = int64(r.ReadU64LE())
return util.Uint160Size*3 + 8 + 4 + 8 + util.Uint256Size
amountLen := r.ReadU64LE()
amountBytes := make([]byte, amountLen)
r.ReadBytes(amountBytes)
t.Amount = *bigint.FromBytes(amountBytes)
return util.Uint160Size*3 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size
}

View file

@ -1,6 +1,7 @@
package state
import (
"math/big"
"math/rand"
"testing"
"time"
@ -40,7 +41,7 @@ func TestNEP5TransferLog_Append(t *testing.T) {
func TestNEP5Tracker_EncodeBinary(t *testing.T) {
expected := &NEP5Tracker{
Balance: int64(rand.Uint64()),
Balance: *big.NewInt(int64(rand.Uint64())),
LastUpdatedBlock: rand.Uint32(),
}
@ -52,7 +53,7 @@ func TestNEP5Transfer_DecodeBinary(t *testing.T) {
Asset: util.Uint160{1, 2, 3},
From: util.Uint160{5, 6, 7},
To: util.Uint160{8, 9, 10},
Amount: 42,
Amount: *big.NewInt(42),
Block: 12345,
Timestamp: 54321,
Tx: util.Uint256{8, 5, 3},
@ -75,7 +76,7 @@ func TestNEP5TransferSize(t *testing.T) {
func randomTransfer(r *rand.Rand) *NEP5Transfer {
return &NEP5Transfer{
Amount: int64(r.Uint64()),
Amount: *big.NewInt(int64(r.Uint64())),
Block: r.Uint32(),
Asset: random.Uint160(),
From: random.Uint160(),

View file

@ -1,6 +1,7 @@
package network
import (
"math/big"
"math/rand"
"net"
"strconv"
@ -31,7 +32,7 @@ func (chain testChain) ApplyPolicyToTxSet([]*transaction.Transaction) []*transac
func (chain testChain) GetConfig() config.ProtocolConfiguration {
panic("TODO")
}
func (chain testChain) CalculateClaimable(int64, uint32, uint32) int64 {
func (chain testChain) CalculateClaimable(*big.Int, uint32, uint32) *big.Int {
panic("TODO")
}
@ -123,11 +124,11 @@ func (chain testChain) GetMemPool() *mempool.Pool {
panic("TODO")
}
func (chain testChain) GetGoverningTokenBalance(acc util.Uint160) (int64, uint32) {
func (chain testChain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) {
panic("TODO")
}
func (chain testChain) GetUtilityTokenBalance(uint160 util.Uint160) int64 {
func (chain testChain) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
panic("TODO")
}

View file

@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"math"
"math/big"
"net"
"net/http"
"strconv"
@ -512,7 +513,7 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Erro
if err != nil {
continue
}
amount := amountToString(bal.Balance, dec)
amount := amountToString(&bal.Balance, dec)
bs.Balances = append(bs.Balances, result.NEP5Balance{
Asset: h,
Amount: amount,
@ -547,8 +548,8 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
if err != nil {
return nil
}
if tr.Amount > 0 { // token was received
transfer.Amount = amountToString(tr.Amount, d)
if tr.Amount.Sign() > 0 { // token was received
transfer.Amount = amountToString(&tr.Amount, d)
if !tr.From.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.From)
}
@ -556,7 +557,7 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
return nil
}
transfer.Amount = amountToString(-tr.Amount, d)
transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d)
if !tr.To.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.To)
}
@ -569,15 +570,14 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
return bs, nil
}
func amountToString(amount int64, decimals int64) string {
func amountToString(amount *big.Int, decimals int64) string {
if decimals == 0 {
return strconv.FormatInt(amount, 10)
return amount.String()
}
pow := int64(math.Pow10(int(decimals)))
q := amount / pow
r := amount % pow
if r == 0 {
return strconv.FormatInt(q, 10)
q, r := new(big.Int).DivMod(amount, big.NewInt(pow), new(big.Int))
if r.Sign() == 0 {
return q.String()
}
fs := fmt.Sprintf("%%d.%%0%dd", decimals)
return fmt.Sprintf(fs, q, r)
@ -783,11 +783,11 @@ func (s *Server) getUnclaimedGas(ps request.Params) (interface{}, *response.Erro
}
neo, neoHeight := s.chain.GetGoverningTokenBalance(u)
if neo == 0 {
if neo.Sign() == 0 {
return "0", nil
}
gas := s.chain.CalculateClaimable(neo, neoHeight, s.chain.BlockHeight()+1) // +1 as in C#, for the next block.
return strconv.FormatInt(gas, 10), nil
return gas.String(), nil
}
// getValidators returns the current NEO consensus nodes information and voting status.

View file

@ -1,6 +1,7 @@
package server
import (
"math/big"
"net/http"
"net/http/httptest"
"os"
@ -90,6 +91,6 @@ func (fs *FeerStub) FeePerByte() int64 {
return 0
}
func (fs *FeerStub) GetUtilityTokenBalance(acc util.Uint160) int64 {
return 1000000 * native.GASFactor
func (fs *FeerStub) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
return big.NewInt(1000000 * native.GASFactor)
}

View file

@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
"reflect"
@ -270,7 +271,7 @@ var rpcTestCases = map[string][]rpcTestCase{
Timestamp: b.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn has empty receiver
Amount: amountToString(int64(amount), 8),
Amount: amountToString(big.NewInt(amount), 8),
Index: b.Index,
TxHash: b.Hash(),
})
@ -282,7 +283,7 @@ var rpcTestCases = map[string][]rpcTestCase{
Timestamp: b.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // minted from network fees.
Amount: amountToString(int64(netFee), 8),
Amount: amountToString(big.NewInt(netFee), 8),
Index: b.Index,
TxHash: b.Hash(),
})