diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 055008e86..68d849882 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -742,8 +742,19 @@ func parseUint160(addr []byte) util.Uint160 { 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) + var id int32 + nativeContract := bc.contracts.ByHash(sc) + if nativeContract != nil { + id = nativeContract.Metadata().ContractID + } else { + assetContract := bc.GetContractState(sc) + if assetContract == nil { + return + } + id = assetContract.ID + } transfer := &state.NEP5Transfer{ - Asset: sc, + Asset: id, From: fromAddr, To: toAddr, Block: b.Index, @@ -755,10 +766,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b * if err != nil { return } - bs := balances.Trackers[sc] + bs := balances.Trackers[id] bs.Balance = *new(big.Int).Sub(&bs.Balance, amount) bs.LastUpdatedBlock = b.Index - balances.Trackers[sc] = bs + balances.Trackers[id] = bs transfer.Amount = *new(big.Int).Sub(&transfer.Amount, amount) isBig, err := cache.AppendNEP5Transfer(fromAddr, balances.NextTransferBatch, transfer) if err != nil { @@ -776,10 +787,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b * if err != nil { return } - bs := balances.Trackers[sc] + bs := balances.Trackers[id] bs.Balance = *new(big.Int).Add(&bs.Balance, amount) bs.LastUpdatedBlock = b.Index - balances.Trackers[sc] = bs + balances.Trackers[id] = bs transfer.Amount = *amount isBig, err := cache.AppendNEP5Transfer(toAddr, balances.NextTransferBatch, transfer) @@ -827,7 +838,7 @@ func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) *big.Int { if err != nil { return big.NewInt(0) } - balance := bs.Trackers[bc.contracts.GAS.Hash].Balance + balance := bs.Trackers[bc.contracts.GAS.ContractID].Balance return &balance } @@ -838,7 +849,7 @@ func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint if err != nil { return big.NewInt(0), 0 } - neo := bs.Trackers[bc.contracts.NEO.Hash] + neo := bs.Trackers[bc.contracts.NEO.ContractID] return &neo.Balance, neo.LastUpdatedBlock } @@ -1017,6 +1028,11 @@ func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract { return contract } +// GetContractScriptHash returns contract script hash by its ID. +func (bc *Blockchain) GetContractScriptHash(id int32) (util.Uint160, error) { + return bc.dao.GetContractScriptHash(id) +} + // GetAccountState returns the account state from its script hash. func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *state.Account { as, err := bc.dao.GetAccountState(scriptHash) diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index 1086c6bee..fc9ca0110 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -27,6 +27,7 @@ type Blockchainer interface { HeaderHeight() uint32 GetBlock(hash util.Uint256) (*block.Block, error) GetContractState(hash util.Uint160) *state.Contract + GetContractScriptHash(id int32) (util.Uint160, error) GetEnrollments() ([]state.Validator, error) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) GetHeaderHash(int) util.Uint256 diff --git a/pkg/core/dao/cacheddao.go b/pkg/core/dao/cacheddao.go index 3862ed767..123f8eda8 100644 --- a/pkg/core/dao/cacheddao.go +++ b/pkg/core/dao/cacheddao.go @@ -1,11 +1,9 @@ package dao import ( - "bytes" "errors" "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -125,65 +123,6 @@ func (cd *Cached) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.N return lg.Size() >= nep5TransferBatchSize, cd.PutNEP5TransferLog(acc, index, lg) } -// MigrateNEP5Balances migrates NEP5 balances from old contract to the new one. -func (cd *Cached) MigrateNEP5Balances(from, to util.Uint160) error { - var ( - simpleDAO *Simple - cachedDAO = cd - ok bool - w = io.NewBufBinWriter() - ) - for simpleDAO == nil { - simpleDAO, ok = cachedDAO.DAO.(*Simple) - if !ok { - cachedDAO, ok = cachedDAO.DAO.(*Cached) - if !ok { - panic("uknown DAO") - } - } - } - for acc, bs := range cd.balances { - err := simpleDAO.putNEP5Balances(acc, bs, w) - if err != nil { - return err - } - w.Reset() - } - cd.dropNEP5Cache = true - var store = simpleDAO.Store - // Create another layer of cache because we can't change original storage - // while seeking. - var upStore = storage.NewMemCachedStore(store) - store.Seek([]byte{byte(storage.STNEP5Balances)}, func(k, v []byte) { - if !bytes.Contains(v, from[:]) { - return - } - bs := state.NewNEP5Balances() - reader := io.NewBinReaderFromBuf(v) - bs.DecodeBinary(reader) - if reader.Err != nil { - panic("bad nep5 balances") - } - tr, ok := bs.Trackers[from] - if !ok { - return - } - delete(bs.Trackers, from) - bs.Trackers[to] = tr - w.Reset() - bs.EncodeBinary(w.BinWriter) - if w.Err != nil { - panic("error on nep5 balance encoding") - } - err := upStore.Put(k, w.Bytes()) - if err != nil { - panic("can't put value in the DB") - } - }) - _, err := upStore.Persist() - return err -} - // Persist flushes all the changes made into the (supposedly) persistent // underlying store. func (cd *Cached) Persist() (int, error) { diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 006b1ecdb..161e63ed4 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -28,6 +28,7 @@ type DAO interface { GetBatch() *storage.MemBatch GetBlock(hash util.Uint256) (*block.Block, error) GetContractState(hash util.Uint160) (*state.Contract, error) + GetContractScriptHash(id int32) (util.Uint160, error) GetCurrentBlockHeight() (uint32, error) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) GetCurrentStateRootHeight() (uint32, error) @@ -171,7 +172,10 @@ func (dao *Simple) GetContractState(hash util.Uint160) (*state.Contract, error) // PutContractState puts given contract state into the given store. func (dao *Simple) PutContractState(cs *state.Contract) error { key := storage.AppendPrefix(storage.STContract, cs.ScriptHash().BytesBE()) - return dao.Put(cs, key) + if err := dao.Put(cs, key); err != nil { + return err + } + return dao.putContractScriptHash(cs) } // DeleteContractState deletes given contract state in the given store. @@ -195,6 +199,29 @@ func (dao *Simple) GetAndUpdateNextContractID() (int32, error) { return id, dao.Store.Put(key, data) } +// putContractScriptHash puts given contract script hash into the given store. +// It's a private method because it should be used after PutContractState to keep +// ID-Hash pair always up-to-date. +func (dao *Simple) putContractScriptHash(cs *state.Contract) error { + key := make([]byte, 5) + key[0] = byte(storage.STContractID) + binary.LittleEndian.PutUint32(key[1:], uint32(cs.ID)) + return dao.Store.Put(key, cs.ScriptHash().BytesBE()) +} + +// GetContractScriptHash returns script hash of the contract with the specified ID. +// Contract with the script hash may be destroyed. +func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) { + key := make([]byte, 5) + key[0] = byte(storage.STContractID) + binary.LittleEndian.PutUint32(key[1:], uint32(id)) + data := &util.Uint160{} + if err := dao.GetAndDecode(data, key); err != nil { + return *data, err + } + return *data, nil +} + // -- end contracts. // -- start nep5 balances. diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 43e46dd6e..5f5ffb943 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -154,7 +154,6 @@ func contractUpdate(ic *interop.Context, v *vm.VM) error { if err := ic.DAO.DeleteContractState(oldHash); err != nil { return fmt.Errorf("failed to update script: %v", err) } - ic.DAO.MigrateNEP5Balances(oldHash, newHash) } // if manifest was provided, update the old contract manifest and check associated // storage items if needed diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 731e4e4f7..04beb261e 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -210,7 +210,7 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem if err != nil { panic(err) } - tr := bs.Trackers[n.Hash] + tr := bs.Trackers[n.ContractID] gen := ic.Chain.CalculateClaimable(&tr.Balance, tr.LastUpdatedBlock, end) return stackitem.NewBigInteger(gen) diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index af743023c..589b4144d 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -210,7 +210,7 @@ func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item) if err != nil { panic(err) } - balance := bs.Trackers[c.Hash].Balance + balance := bs.Trackers[c.ContractID].Balance return stackitem.NewBigInteger(&balance) } diff --git a/pkg/core/state/nep5.go b/pkg/core/state/nep5.go index ad89ffac9..36ac13ec3 100644 --- a/pkg/core/state/nep5.go +++ b/pkg/core/state/nep5.go @@ -26,8 +26,8 @@ type NEP5TransferLog struct { // NEP5Transfer represents a single NEP5 Transfer event. type NEP5Transfer struct { - // Asset is a NEP5 contract hash. - Asset util.Uint160 + // Asset is a NEP5 contract ID. + Asset int32 // Address is the address of the sender. From util.Uint160 // To is the address of the receiver. @@ -43,10 +43,10 @@ type NEP5Transfer struct { Tx util.Uint256 } -// NEP5Balances is a map of the NEP5 contract hashes +// NEP5Balances is a map of the NEP5 contract IDs // to the corresponding structures. type NEP5Balances struct { - Trackers map[util.Uint160]NEP5Tracker + Trackers map[int32]NEP5Tracker // NextTransferBatch stores an index of the next transfer batch. NextTransferBatch uint32 } @@ -54,7 +54,7 @@ type NEP5Balances struct { // NewNEP5Balances returns new NEP5Balances. func NewNEP5Balances() *NEP5Balances { return &NEP5Balances{ - Trackers: make(map[util.Uint160]NEP5Tracker), + Trackers: make(map[int32]NEP5Tracker), } } @@ -62,11 +62,10 @@ func NewNEP5Balances() *NEP5Balances { func (bs *NEP5Balances) DecodeBinary(r *io.BinReader) { bs.NextTransferBatch = r.ReadU32LE() lenBalances := r.ReadVarUint() - m := make(map[util.Uint160]NEP5Tracker, lenBalances) + m := make(map[int32]NEP5Tracker, lenBalances) for i := 0; i < int(lenBalances); i++ { - var key util.Uint160 + key := int32(r.ReadU32LE()) var tr NEP5Tracker - r.ReadBytes(key[:]) tr.DecodeBinary(r) m[key] = tr } @@ -78,7 +77,7 @@ func (bs *NEP5Balances) EncodeBinary(w *io.BinWriter) { w.WriteU32LE(bs.NextTransferBatch) w.WriteVarUint(uint64(len(bs.Trackers))) for k, v := range bs.Trackers { - w.WriteBytes(k[:]) + w.WriteU32LE(uint32(k)) v.EncodeBinary(w) } } @@ -133,7 +132,7 @@ func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) { // EncodeBinary implements io.Serializable interface. func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) { - w.WriteBytes(t.Asset[:]) + w.WriteU32LE(uint32(t.Asset)) w.WriteBytes(t.Tx[:]) w.WriteBytes(t.From[:]) w.WriteBytes(t.To[:]) @@ -151,7 +150,7 @@ func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) { // DecodeBinaryReturnCount decodes NEP5Transfer and returns the number of bytes read. func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int { - r.ReadBytes(t.Asset[:]) + t.Asset = int32(r.ReadU32LE()) r.ReadBytes(t.Tx[:]) r.ReadBytes(t.From[:]) r.ReadBytes(t.To[:]) @@ -161,5 +160,5 @@ func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int { amountBytes := make([]byte, amountLen) r.ReadBytes(amountBytes) t.Amount = *bigint.FromBytes(amountBytes) - return util.Uint160Size*3 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size + return 4 + util.Uint160Size*2 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size } diff --git a/pkg/core/state/nep5_test.go b/pkg/core/state/nep5_test.go index 9681fe2ec..507e848c0 100644 --- a/pkg/core/state/nep5_test.go +++ b/pkg/core/state/nep5_test.go @@ -50,7 +50,7 @@ func TestNEP5Tracker_EncodeBinary(t *testing.T) { func TestNEP5Transfer_DecodeBinary(t *testing.T) { expected := &NEP5Transfer{ - Asset: util.Uint160{1, 2, 3}, + Asset: 123, From: util.Uint160{5, 6, 7}, To: util.Uint160{8, 9, 10}, Amount: *big.NewInt(42), @@ -78,7 +78,7 @@ func randomTransfer(r *rand.Rand) *NEP5Transfer { return &NEP5Transfer{ Amount: *big.NewInt(int64(r.Uint64())), Block: r.Uint32(), - Asset: random.Uint160(), + Asset: int32(random.Int(10, 10000000)), From: random.Uint160(), To: random.Uint160(), Tx: random.Uint256(), diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index 575c42ba3..a01725453 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -13,6 +13,7 @@ const ( STAccount KeyPrefix = 0x40 STNotification KeyPrefix = 0x4d STContract KeyPrefix = 0x50 + STContractID KeyPrefix = 0x51 STStorage KeyPrefix = 0x70 STNEP5Transfers KeyPrefix = 0x72 STNEP5Balances KeyPrefix = 0x73 diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 61cf1939e..2e7d336e6 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -70,6 +70,9 @@ func (chain testChain) GetBlock(hash util.Uint256) (*block.Block, error) { func (chain testChain) GetContractState(hash util.Uint160) *state.Contract { panic("TODO") } +func (chain testChain) GetContractScriptHash(id int32) (util.Uint160, error) { + panic("TODO") +} func (chain testChain) GetHeaderHash(int) util.Uint256 { return util.Uint256{} } diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 9d17c71c5..5e661ac71 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -514,15 +514,15 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Erro Balances: []result.NEP5Balance{}, } if as != nil { - cache := make(map[util.Uint160]int64) - for h, bal := range as.Trackers { - dec, err := s.getDecimals(h, cache) + cache := make(map[int32]decimals) + for id, bal := range as.Trackers { + dec, err := s.getDecimals(id, cache) if err != nil { continue } - amount := amountToString(&bal.Balance, dec) + amount := amountToString(&bal.Balance, dec.Value) bs.Balances = append(bs.Balances, result.NEP5Balance{ - Asset: h, + Asset: dec.Hash, Amount: amount, LastUpdated: bal.LastUpdatedBlock, }) @@ -543,20 +543,20 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err Sent: []result.NEP5Transfer{}, } lg := s.chain.GetNEP5TransferLog(u) - cache := make(map[util.Uint160]int64) + cache := make(map[int32]decimals) err = lg.ForEach(func(tr *state.NEP5Transfer) error { - transfer := result.NEP5Transfer{ - Timestamp: tr.Timestamp, - Asset: tr.Asset, - Index: tr.Block, - TxHash: tr.Tx, - } d, err := s.getDecimals(tr.Asset, cache) if err != nil { return nil } + transfer := result.NEP5Transfer{ + Timestamp: tr.Timestamp, + Asset: d.Hash, + Index: tr.Block, + TxHash: tr.Tx, + } if tr.Amount.Sign() > 0 { // token was received - transfer.Amount = amountToString(&tr.Amount, d) + transfer.Amount = amountToString(&tr.Amount, d.Value) if !tr.From.Equals(util.Uint160{}) { transfer.Address = address.Uint160ToString(tr.From) } @@ -564,7 +564,7 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err return nil } - transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d) + transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d.Value) if !tr.To.Equals(util.Uint160{}) { transfer.Address = address.Uint160ToString(tr.To) } @@ -590,10 +590,20 @@ func amountToString(amount *big.Int, decimals int64) string { return fmt.Sprintf(fs, q, r) } -func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int64, *response.Error) { - if d, ok := cache[h]; ok { +// decimals represents decimals value for the contract with the specified scripthash. +type decimals struct { + Hash util.Uint160 + Value int64 +} + +func (s *Server) getDecimals(contractID int32, cache map[int32]decimals) (decimals, error) { + if d, ok := cache[contractID]; ok { return d, nil } + h, err := s.chain.GetContractScriptHash(contractID) + if err != nil { + return decimals{}, err + } script, err := request.CreateFunctionInvocationScript(h, request.Params{ { Type: request.StringT, @@ -605,26 +615,26 @@ func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int6 }, }) if err != nil { - return 0, response.NewInternalServerError("Can't create script", err) + return decimals{}, fmt.Errorf("can't create script: %v", err) } res := s.runScriptInVM(script, nil) if res == nil || res.State != "HALT" || len(res.Stack) == 0 { - return 0, response.NewInternalServerError("execution error", errors.New("no result")) + return decimals{}, errors.New("execution error : no result") } - var d int64 + d := decimals{Hash: h} switch item := res.Stack[len(res.Stack)-1]; item.Type { case smartcontract.IntegerType: - d = item.Value.(int64) + d.Value = item.Value.(int64) case smartcontract.ByteArrayType: - d = bigint.FromBytes(item.Value.([]byte)).Int64() + d.Value = bigint.FromBytes(item.Value.([]byte)).Int64() default: - return 0, response.NewInternalServerError("invalid result", errors.New("not an integer")) + return d, errors.New("invalid result: not an integer") } - if d < 0 { - return 0, response.NewInternalServerError("incorrect result", errors.New("negative result")) + if d.Value < 0 { + return d, errors.New("incorrect result: negative result") } - cache[h] = d + cache[contractID] = d return d, nil }