Merge pull request #1077 from nspcc-dev/neo3/smartcontract/id

core: store smartcontract items by id
This commit is contained in:
Roman Khimov 2020-06-20 00:12:07 +03:00 committed by GitHub
commit 36a65e3847
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 168 additions and 140 deletions

View file

@ -869,13 +869,13 @@ func (bc *Blockchain) GetAppExecResult(hash util.Uint256) (*state.AppExecResult,
} }
// GetStorageItem returns an item from storage. // GetStorageItem returns an item from storage.
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem { func (bc *Blockchain) GetStorageItem(id int32, key []byte) *state.StorageItem {
return bc.dao.GetStorageItem(scripthash, key) return bc.dao.GetStorageItem(id, key)
} }
// GetStorageItems returns all storage items for a given scripthash. // GetStorageItems returns all storage items for a given contract id.
func (bc *Blockchain) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) { func (bc *Blockchain) GetStorageItems(id int32) (map[string]*state.StorageItem, error) {
return bc.dao.GetStorageItems(hash) return bc.dao.GetStorageItems(id)
} }
// GetBlock returns a Block by the given hash. // GetBlock returns a Block by the given hash.

View file

@ -39,8 +39,8 @@ type Blockchainer interface {
GetValidators() ([]*keys.PublicKey, error) GetValidators() ([]*keys.PublicKey, error)
GetStandByValidators() (keys.PublicKeys, error) GetStandByValidators() (keys.PublicKeys, error)
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItem(id int32, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItems(id int32) (map[string]*state.StorageItem, error)
GetTestVM(tx *transaction.Transaction) *vm.VM GetTestVM(tx *transaction.Transaction) *vm.VM
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
mempool.Feer // fee interface mempool.Feer // fee interface

View file

@ -114,29 +114,29 @@ func TestCachedCachedDao(t *testing.T) {
assert.NotEqual(t, pdao.Store, intDao.Store) assert.NotEqual(t, pdao.Store, intDao.Store)
assert.NotEqual(t, cdaoDao.Store, intDao.Store) assert.NotEqual(t, cdaoDao.Store, intDao.Store)
hash := random.Uint160() id := int32(random.Int(0, 1024))
key := []byte("qwerty") key := []byte("qwerty")
si := &state.StorageItem{Value: []byte("poiuyt")} si := &state.StorageItem{Value: []byte("poiuyt")}
require.NoError(t, ccdao.PutStorageItem(hash, key, si)) require.NoError(t, ccdao.PutStorageItem(id, key, si))
resi := ccdao.GetStorageItem(hash, key) resi := ccdao.GetStorageItem(id, key)
assert.Equal(t, si, resi) assert.Equal(t, si, resi)
resi = cdao.GetStorageItem(hash, key) resi = cdao.GetStorageItem(id, key)
assert.Equal(t, (*state.StorageItem)(nil), resi) assert.Equal(t, (*state.StorageItem)(nil), resi)
resi = pdao.GetStorageItem(hash, key) resi = pdao.GetStorageItem(id, key)
assert.Equal(t, (*state.StorageItem)(nil), resi) assert.Equal(t, (*state.StorageItem)(nil), resi)
cnt, err := ccdao.Persist() cnt, err := ccdao.Persist()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, cnt) assert.Equal(t, 1, cnt)
resi = cdao.GetStorageItem(hash, key) resi = cdao.GetStorageItem(id, key)
assert.Equal(t, si, resi) assert.Equal(t, si, resi)
resi = pdao.GetStorageItem(hash, key) resi = pdao.GetStorageItem(id, key)
assert.Equal(t, (*state.StorageItem)(nil), resi) assert.Equal(t, (*state.StorageItem)(nil), resi)
cnt, err = cdao.Persist() cnt, err = cdao.Persist()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, cnt) assert.Equal(t, 1, cnt)
resi = pdao.GetStorageItem(hash, key) resi = pdao.GetStorageItem(id, key)
assert.Equal(t, si, resi) assert.Equal(t, si, resi)
} }

View file

@ -19,7 +19,7 @@ import (
type DAO interface { type DAO interface {
AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error)
DeleteContractState(hash util.Uint160) error DeleteContractState(hash util.Uint160) error
DeleteStorageItem(scripthash util.Uint160, key []byte) error DeleteStorageItem(id int32, key []byte) error
GetAccountState(hash util.Uint160) (*state.Account, error) GetAccountState(hash util.Uint160) (*state.Account, error)
GetAccountStateOrNew(hash util.Uint160) (*state.Account, error) GetAccountStateOrNew(hash util.Uint160) (*state.Account, error)
GetAndDecode(entity io.Serializable, key []byte) error GetAndDecode(entity io.Serializable, key []byte) error
@ -33,9 +33,9 @@ type DAO interface {
GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error)
GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error) GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error)
GetNextContractID() (int32, error) GetNextContractID() (int32, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItem(id int32, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItems(id int32) (map[string]*state.StorageItem, error)
GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) GetStorageItemsWithPrefix(id int32, prefix []byte) (map[string]*state.StorageItem, error)
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
GetVersion() (string, error) GetVersion() (string, error)
GetWrapped() DAO GetWrapped() DAO
@ -48,7 +48,7 @@ type DAO interface {
PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
PutNextContractID(id int32) error PutNextContractID(id int32) error
PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error PutStorageItem(id int32, key []byte, si *state.StorageItem) error
PutVersion(v string) error PutVersion(v string) error
StoreAsBlock(block *block.Block) error StoreAsBlock(block *block.Block) error
StoreAsCurrentBlock(block *block.Block) error StoreAsCurrentBlock(block *block.Block) error
@ -296,8 +296,8 @@ func (dao *Simple) PutAppExecResult(aer *state.AppExecResult) error {
// -- start storage item. // -- start storage item.
// GetStorageItem returns StorageItem if it exists in the given store. // GetStorageItem returns StorageItem if it exists in the given store.
func (dao *Simple) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem { func (dao *Simple) GetStorageItem(id int32, key []byte) *state.StorageItem {
b, err := dao.Store.Get(makeStorageItemKey(scripthash, key)) b, err := dao.Store.Get(makeStorageItemKey(id, key))
if err != nil { if err != nil {
return nil return nil
} }
@ -312,30 +312,30 @@ func (dao *Simple) GetStorageItem(scripthash util.Uint160, key []byte) *state.St
return si return si
} }
// PutStorageItem puts given StorageItem for given script with given // PutStorageItem puts given StorageItem for given id with given
// key into the given store. // key into the given store.
func (dao *Simple) PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error { func (dao *Simple) PutStorageItem(id int32, key []byte, si *state.StorageItem) error {
return dao.Put(si, makeStorageItemKey(scripthash, key)) return dao.Put(si, makeStorageItemKey(id, key))
} }
// DeleteStorageItem drops storage item for the given script with the // DeleteStorageItem drops storage item for the given id with the
// given key from the store. // given key from the store.
func (dao *Simple) DeleteStorageItem(scripthash util.Uint160, key []byte) error { func (dao *Simple) DeleteStorageItem(id int32, key []byte) error {
return dao.Store.Delete(makeStorageItemKey(scripthash, key)) return dao.Store.Delete(makeStorageItemKey(id, key))
} }
// GetStorageItems returns all storage items for a given scripthash. // GetStorageItems returns all storage items for a given id.
func (dao *Simple) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) { func (dao *Simple) GetStorageItems(id int32) (map[string]*state.StorageItem, error) {
return dao.GetStorageItemsWithPrefix(hash, nil) return dao.GetStorageItemsWithPrefix(id, nil)
} }
// GetStorageItemsWithPrefix returns all storage items with given prefix for a // GetStorageItemsWithPrefix returns all storage items with given id for a
// given scripthash. // given scripthash.
func (dao *Simple) GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) { func (dao *Simple) GetStorageItemsWithPrefix(id int32, prefix []byte) (map[string]*state.StorageItem, error) {
var siMap = make(map[string]*state.StorageItem) var siMap = make(map[string]*state.StorageItem)
var err error var err error
lookupKey := storage.AppendPrefix(storage.STStorage, hash.BytesLE()) lookupKey := makeStorageItemKey(id, nil)
if prefix != nil { if prefix != nil {
lookupKey = append(lookupKey, prefix...) lookupKey = append(lookupKey, prefix...)
} }
@ -362,8 +362,13 @@ func (dao *Simple) GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (
} }
// makeStorageItemKey returns a key used to store StorageItem in the DB. // makeStorageItemKey returns a key used to store StorageItem in the DB.
func makeStorageItemKey(scripthash util.Uint160, key []byte) []byte { func makeStorageItemKey(id int32, key []byte) []byte {
return storage.AppendPrefix(storage.STStorage, append(scripthash.BytesLE(), key...)) // 1 for prefix + 4 for Uint32 + len(key) for key
buf := make([]byte, 5+len(key))
buf[0] = byte(storage.STStorage)
binary.LittleEndian.PutUint32(buf[1:], uint32(id))
copy(buf[5:], key)
return buf
} }
// -- end storage item. // -- end storage item.

View file

@ -1,6 +1,7 @@
package dao package dao
import ( import (
"encoding/binary"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
@ -111,25 +112,25 @@ func TestPutGetAppExecResult(t *testing.T) {
func TestPutGetStorageItem(t *testing.T) { func TestPutGetStorageItem(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet)
hash := random.Uint160() id := int32(random.Int(0, 1024))
key := []byte{0} key := []byte{0}
storageItem := &state.StorageItem{Value: []uint8{}} storageItem := &state.StorageItem{Value: []uint8{}}
err := dao.PutStorageItem(hash, key, storageItem) err := dao.PutStorageItem(id, key, storageItem)
require.NoError(t, err) require.NoError(t, err)
gotStorageItem := dao.GetStorageItem(hash, key) gotStorageItem := dao.GetStorageItem(id, key)
require.Equal(t, storageItem, gotStorageItem) require.Equal(t, storageItem, gotStorageItem)
} }
func TestDeleteStorageItem(t *testing.T) { func TestDeleteStorageItem(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet)
hash := random.Uint160() id := int32(random.Int(0, 1024))
key := []byte{0} key := []byte{0}
storageItem := &state.StorageItem{Value: []uint8{}} storageItem := &state.StorageItem{Value: []uint8{}}
err := dao.PutStorageItem(hash, key, storageItem) err := dao.PutStorageItem(id, key, storageItem)
require.NoError(t, err) require.NoError(t, err)
err = dao.DeleteStorageItem(hash, key) err = dao.DeleteStorageItem(id, key)
require.NoError(t, err) require.NoError(t, err)
gotStorageItem := dao.GetStorageItem(hash, key) gotStorageItem := dao.GetStorageItem(id, key)
require.Nil(t, gotStorageItem) require.Nil(t, gotStorageItem)
} }
@ -208,3 +209,16 @@ func TestStoreAsTransaction(t *testing.T) {
hasTransaction := dao.HasTransaction(hash) hasTransaction := dao.HasTransaction(hash)
require.True(t, hasTransaction) require.True(t, hasTransaction)
} }
func TestMakeStorageItemKey(t *testing.T) {
var id int32 = 5
expected := []byte{byte(storage.STStorage), 0, 0, 0, 0, 1, 2, 3}
binary.LittleEndian.PutUint32(expected[1:5], uint32(id))
actual := makeStorageItemKey(id, []byte{1, 2, 3})
require.Equal(t, expected, actual)
expected = expected[0:5]
actual = makeStorageItemKey(id, nil)
require.Equal(t, expected, actual)
}

View file

@ -34,12 +34,8 @@ func storageFind(ic *interop.Context, v *vm.VM) error {
if !ok { if !ok {
return fmt.Errorf("%T is not a StorageContext", stcInterface) return fmt.Errorf("%T is not a StorageContext", stcInterface)
} }
err := checkStorageContext(ic, stc)
if err != nil {
return err
}
prefix := v.Estack().Pop().Bytes() prefix := v.Estack().Pop().Bytes()
siMap, err := ic.DAO.GetStorageItemsWithPrefix(stc.ScriptHash, prefix) siMap, err := ic.DAO.GetStorageItemsWithPrefix(stc.ID, prefix)
if err != nil { if err != nil {
return err return err
} }
@ -138,19 +134,13 @@ func contractUpdate(ic *interop.Context, v *vm.VM) error {
return err return err
} }
} }
if contract.HasStorage() { if !newcontract.HasStorage() {
// TODO store items by ID #1037 siMap, err := ic.DAO.GetStorageItems(contract.ID)
hash := v.GetCurrentScriptHash()
siMap, err := ic.DAO.GetStorageItems(hash)
if err != nil {
return err
}
for k, v := range siMap {
v.IsConst = false
err = ic.DAO.PutStorageItem(contract.ScriptHash(), []byte(k), v)
if err != nil { if err != nil {
return err return err
} }
if len(siMap) != 0 {
return errors.New("old contract shouldn't have storage")
} }
} }
v.Estack().PushVal(stackitem.NewInterop(contract)) v.Estack().PushVal(stackitem.NewInterop(contract))

View file

@ -63,16 +63,16 @@ func TestStorageFind(t *testing.T) {
require.NoError(t, context.DAO.PutContractState(contractState)) require.NoError(t, context.DAO.PutContractState(contractState))
scriptHash := contractState.ScriptHash() id := contractState.ID
for i := range skeys { for i := range skeys {
err := context.DAO.PutStorageItem(scriptHash, skeys[i], items[i]) err := context.DAO.PutStorageItem(id, skeys[i], items[i])
require.NoError(t, err) require.NoError(t, err)
} }
t.Run("normal invocation", func(t *testing.T) { t.Run("normal invocation", func(t *testing.T) {
v.Estack().PushVal([]byte{0x01}) v.Estack().PushVal([]byte{0x01})
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ScriptHash: scriptHash})) v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
err := storageFind(context, v) err := storageFind(context, v)
require.NoError(t, err) require.NoError(t, err)
@ -110,7 +110,7 @@ func TestStorageFind(t *testing.T) {
t.Run("normal invocation, empty result", func(t *testing.T) { t.Run("normal invocation, empty result", func(t *testing.T) {
v.Estack().PushVal([]byte{0x03}) v.Estack().PushVal([]byte{0x03})
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ScriptHash: scriptHash})) v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
err := storageFind(context, v) err := storageFind(context, v)
require.NoError(t, err) require.NoError(t, err)
@ -126,14 +126,15 @@ func TestStorageFind(t *testing.T) {
require.Error(t, storageFind(context, v)) require.Error(t, storageFind(context, v))
}) })
t.Run("invalid script hash", func(t *testing.T) { t.Run("invalid id", func(t *testing.T) {
invalidHash := scriptHash invalidID := id + 1
invalidHash[0] = ^invalidHash[0]
v.Estack().PushVal([]byte{0x01}) v.Estack().PushVal([]byte{0x01})
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ScriptHash: invalidHash})) v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: invalidID}))
require.Error(t, storageFind(context, v)) require.NoError(t, storageFind(context, v))
require.NoError(t, enumerator.Next(context, v))
require.False(t, v.Estack().Pop().Bool())
}) })
} }
@ -257,6 +258,7 @@ func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.C
contractState := &state.Contract{ contractState := &state.Contract{
Script: script, Script: script,
Manifest: *m, Manifest: *m,
ID: 123,
} }
chain := newTestChain(t) chain := newTestChain(t)

View file

@ -28,10 +28,10 @@ const (
MaxTraceableBlocks = transaction.MaxValidUntilBlockIncrement MaxTraceableBlocks = transaction.MaxValidUntilBlockIncrement
) )
// StorageContext contains storing script hash and read/write flag, it's used as // StorageContext contains storing id and read/write flag, it's used as
// a context for storage manipulation functions. // a context for storage manipulation functions.
type StorageContext struct { type StorageContext struct {
ScriptHash util.Uint160 ID int32
ReadOnly bool ReadOnly bool
} }
@ -259,17 +259,6 @@ func runtimeGetTime(ic *interop.Context, v *vm.VM) error {
return nil return nil
} }
func checkStorageContext(ic *interop.Context, stc *StorageContext) error {
contract, err := ic.DAO.GetContractState(stc.ScriptHash)
if err != nil {
return errors.New("no contract found")
}
if !contract.HasStorage() {
return fmt.Errorf("contract %s can't use storage", stc.ScriptHash)
}
return nil
}
// storageDelete deletes stored key-value pair. // storageDelete deletes stored key-value pair.
func storageDelete(ic *interop.Context, v *vm.VM) error { func storageDelete(ic *interop.Context, v *vm.VM) error {
stcInterface := v.Estack().Pop().Value() stcInterface := v.Estack().Pop().Value()
@ -280,16 +269,12 @@ func storageDelete(ic *interop.Context, v *vm.VM) error {
if stc.ReadOnly { if stc.ReadOnly {
return errors.New("StorageContext is read only") return errors.New("StorageContext is read only")
} }
err := checkStorageContext(ic, stc)
if err != nil {
return err
}
key := v.Estack().Pop().Bytes() key := v.Estack().Pop().Bytes()
si := ic.DAO.GetStorageItem(stc.ScriptHash, key) si := ic.DAO.GetStorageItem(stc.ID, key)
if si != nil && si.IsConst { if si != nil && si.IsConst {
return errors.New("storage item is constant") return errors.New("storage item is constant")
} }
return ic.DAO.DeleteStorageItem(stc.ScriptHash, key) return ic.DAO.DeleteStorageItem(stc.ID, key)
} }
// storageGet returns stored key-value pair. // storageGet returns stored key-value pair.
@ -299,12 +284,8 @@ func storageGet(ic *interop.Context, v *vm.VM) error {
if !ok { if !ok {
return fmt.Errorf("%T is not a StorageContext", stcInterface) return fmt.Errorf("%T is not a StorageContext", stcInterface)
} }
err := checkStorageContext(ic, stc)
if err != nil {
return err
}
key := v.Estack().Pop().Bytes() key := v.Estack().Pop().Bytes()
si := ic.DAO.GetStorageItem(stc.ScriptHash, key) si := ic.DAO.GetStorageItem(stc.ID, key)
if si != nil && si.Value != nil { if si != nil && si.Value != nil {
v.Estack().PushVal(si.Value) v.Estack().PushVal(si.Value)
} else { } else {
@ -315,8 +296,15 @@ func storageGet(ic *interop.Context, v *vm.VM) error {
// storageGetContext returns storage context (scripthash). // storageGetContext returns storage context (scripthash).
func storageGetContext(ic *interop.Context, v *vm.VM) error { func storageGetContext(ic *interop.Context, v *vm.VM) error {
contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash())
if err != nil {
return err
}
if !contract.HasStorage() {
return errors.New("contract is not allowed to use storage")
}
sc := &StorageContext{ sc := &StorageContext{
ScriptHash: v.GetCurrentScriptHash(), ID: contract.ID,
ReadOnly: false, ReadOnly: false,
} }
v.Estack().PushVal(stackitem.NewInterop(sc)) v.Estack().PushVal(stackitem.NewInterop(sc))
@ -325,8 +313,15 @@ func storageGetContext(ic *interop.Context, v *vm.VM) error {
// storageGetReadOnlyContext returns read-only context (scripthash). // storageGetReadOnlyContext returns read-only context (scripthash).
func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error { func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error {
contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash())
if err != nil {
return err
}
if !contract.HasStorage() {
return err
}
sc := &StorageContext{ sc := &StorageContext{
ScriptHash: v.GetCurrentScriptHash(), ID: contract.ID,
ReadOnly: true, ReadOnly: true,
} }
v.Estack().PushVal(stackitem.NewInterop(sc)) v.Estack().PushVal(stackitem.NewInterop(sc))
@ -340,11 +335,7 @@ func putWithContextAndFlags(ic *interop.Context, v *vm.VM, stc *StorageContext,
if stc.ReadOnly { if stc.ReadOnly {
return errors.New("StorageContext is read only") return errors.New("StorageContext is read only")
} }
err := checkStorageContext(ic, stc) si := ic.DAO.GetStorageItem(stc.ID, key)
if err != nil {
return err
}
si := ic.DAO.GetStorageItem(stc.ScriptHash, key)
if si == nil { if si == nil {
si = &state.StorageItem{} si = &state.StorageItem{}
} }
@ -360,7 +351,7 @@ func putWithContextAndFlags(ic *interop.Context, v *vm.VM, stc *StorageContext,
} }
si.Value = value si.Value = value
si.IsConst = isConst si.IsConst = isConst
return ic.DAO.PutStorageItem(stc.ScriptHash, key, si) return ic.DAO.PutStorageItem(stc.ID, key, si)
} }
// storagePutInternal is a unified implementation of storagePut and storagePutEx. // storagePutInternal is a unified implementation of storagePut and storagePutEx.
@ -398,7 +389,7 @@ func storageContextAsReadOnly(ic *interop.Context, v *vm.VM) error {
} }
if !stc.ReadOnly { if !stc.ReadOnly {
stx := &StorageContext{ stx := &StorageContext{
ScriptHash: stc.ScriptHash, ID: stc.ID,
ReadOnly: true, ReadOnly: true,
} }
stc = stx stc = stx
@ -462,12 +453,12 @@ func contractDestroy(ic *interop.Context, v *vm.VM) error {
return err return err
} }
if cs.HasStorage() { if cs.HasStorage() {
siMap, err := ic.DAO.GetStorageItems(hash) siMap, err := ic.DAO.GetStorageItems(cs.ID)
if err != nil { if err != nil {
return err return err
} }
for k := range siMap { for k := range siMap {
_ = ic.DAO.DeleteStorageItem(hash, []byte(k)) _ = ic.DAO.DeleteStorageItem(cs.ID, []byte(k))
} }
} }
return nil return nil

View file

@ -34,7 +34,7 @@ type keyWithVotes struct {
const ( const (
neoSyscallName = "Neo.Native.Tokens.NEO" neoSyscallName = "Neo.Native.Tokens.NEO"
neoContractID = -2 neoContractID = -1
// NEOTotalSupply is the total amount of NEO in the system. // NEOTotalSupply is the total amount of NEO in the system.
NEOTotalSupply = 100000000 NEOTotalSupply = 100000000
// prefixValidator is a prefix used to store validator's data. // prefixValidator is a prefix used to store validator's data.
@ -124,7 +124,7 @@ func (n *NEO) Initialize(ic *interop.Context) error {
vc := new(ValidatorsCount) vc := new(ValidatorsCount)
si.Value = vc.Bytes() si.Value = vc.Bytes()
if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, &si); err != nil { if err := ic.DAO.PutStorageItem(n.ContractID, validatorsCountKey, &si); err != nil {
return err return err
} }
h, vs, err := getStandbyValidatorsHash(ic) h, vs, err := getStandbyValidatorsHash(ic)
@ -150,7 +150,7 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
} }
si := new(state.StorageItem) si := new(state.StorageItem)
si.Value = pubs.Bytes() si.Value = pubs.Bytes()
return ic.DAO.PutStorageItem(n.Hash, nextValidatorsKey, si) return ic.DAO.PutStorageItem(n.ContractID, nextValidatorsKey, si)
} }
func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error { func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error {
@ -171,7 +171,7 @@ func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.Sto
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil { if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil {
return err return err
} }
siVC := ic.DAO.GetStorageItem(n.Hash, validatorsCountKey) siVC := ic.DAO.GetStorageItem(n.ContractID, validatorsCountKey)
if siVC == nil { if siVC == nil {
return errors.New("validators count uninitialized") return errors.New("validators count uninitialized")
} }
@ -181,7 +181,7 @@ func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.Sto
} }
vc[len(acc.Votes)-1].Add(&vc[len(acc.Votes)-1], amount) vc[len(acc.Votes)-1].Add(&vc[len(acc.Votes)-1], amount)
siVC.Value = vc.Bytes() siVC.Value = vc.Bytes()
if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, siVC); err != nil { if err := ic.DAO.PutStorageItem(n.ContractID, validatorsCountKey, siVC); err != nil {
return err return err
} }
} }
@ -220,7 +220,7 @@ func (n *NEO) registerValidator(ic *interop.Context, args []stackitem.Item) stac
func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey) error { func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey) error {
key := makeValidatorKey(pub) key := makeValidatorKey(pub)
si := ic.DAO.GetStorageItem(n.Hash, key) si := ic.DAO.GetStorageItem(n.ContractID, key)
if si != nil { if si != nil {
return errors.New("already registered") return errors.New("already registered")
} }
@ -229,7 +229,7 @@ func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey
// doesn't help a lot. // doesn't help a lot.
votes := state.NEP5BalanceState{} votes := state.NEP5BalanceState{}
si.Value = votes.Bytes() si.Value = votes.Bytes()
return ic.DAO.PutStorageItem(n.Hash, key, si) return ic.DAO.PutStorageItem(n.ContractID, key, si)
} }
func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -263,7 +263,7 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public
return errors.New("invalid signature") return errors.New("invalid signature")
} }
key := makeAccountKey(h) key := makeAccountKey(h)
si := ic.DAO.GetStorageItem(n.Hash, key) si := ic.DAO.GetStorageItem(n.ContractID, key)
if si == nil { if si == nil {
return errors.New("invalid account") return errors.New("invalid account")
} }
@ -278,13 +278,13 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public
// Check validators registration. // Check validators registration.
var newPubs keys.PublicKeys var newPubs keys.PublicKeys
for _, pub := range pubs { for _, pub := range pubs {
if ic.DAO.GetStorageItem(n.Hash, makeValidatorKey(pub)) == nil { if ic.DAO.GetStorageItem(n.ContractID, makeValidatorKey(pub)) == nil {
continue continue
} }
newPubs = append(newPubs, pub) newPubs = append(newPubs, pub)
} }
if lp, lv := len(newPubs), len(acc.Votes); lp != lv { if lp, lv := len(newPubs), len(acc.Votes); lp != lv {
si := ic.DAO.GetStorageItem(n.Hash, validatorsCountKey) si := ic.DAO.GetStorageItem(n.ContractID, validatorsCountKey)
if si == nil { if si == nil {
return errors.New("validators count uninitialized") return errors.New("validators count uninitialized")
} }
@ -299,7 +299,7 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public
vc[lp-1].Add(&vc[lp-1], &acc.Balance) vc[lp-1].Add(&vc[lp-1], &acc.Balance)
} }
si.Value = vc.Bytes() si.Value = vc.Bytes()
if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, si); err != nil { if err := ic.DAO.PutStorageItem(n.ContractID, validatorsCountKey, si); err != nil {
return err return err
} }
} }
@ -308,14 +308,14 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public
return err return err
} }
si.Value = acc.Bytes() si.Value = acc.Bytes()
return ic.DAO.PutStorageItem(n.Hash, key, si) return ic.DAO.PutStorageItem(n.ContractID, key, si)
} }
// ModifyAccountVotes modifies votes of the specified account by value (can be negative). // ModifyAccountVotes modifies votes of the specified account by value (can be negative).
func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *big.Int) error { func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *big.Int) error {
for _, vote := range acc.Votes { for _, vote := range acc.Votes {
key := makeValidatorKey(vote) key := makeValidatorKey(vote)
si := d.GetStorageItem(n.Hash, key) si := d.GetStorageItem(n.ContractID, key)
if si == nil { if si == nil {
return errors.New("invalid validator") return errors.New("invalid validator")
} }
@ -325,7 +325,7 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *b
} }
votes.Balance.Add(&votes.Balance, value) votes.Balance.Add(&votes.Balance, value)
si.Value = votes.Bytes() si.Value = votes.Bytes()
if err := d.PutStorageItem(n.Hash, key, si); err != nil { if err := d.PutStorageItem(n.ContractID, key, si); err != nil {
return err return err
} }
} }
@ -333,7 +333,7 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *b
} }
func (n *NEO) getRegisteredValidators(d dao.DAO) ([]keyWithVotes, error) { func (n *NEO) getRegisteredValidators(d dao.DAO) ([]keyWithVotes, error) {
siMap, err := d.GetStorageItemsWithPrefix(n.Hash, []byte{prefixValidator}) siMap, err := d.GetStorageItemsWithPrefix(n.ContractID, []byte{prefixValidator})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -384,7 +384,7 @@ func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []stackitem.Ite
// GetValidatorsInternal returns a list of current validators. // GetValidatorsInternal returns a list of current validators.
func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
si := d.GetStorageItem(n.Hash, validatorsCountKey) si := d.GetStorageItem(n.ContractID, validatorsCountKey)
if si == nil { if si == nil {
return nil, errors.New("validators count uninitialized") return nil, errors.New("validators count uninitialized")
} }
@ -454,7 +454,7 @@ func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) st
// GetNextBlockValidatorsInternal returns next block validators. // GetNextBlockValidatorsInternal returns next block validators.
func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
si := d.GetStorageItem(n.Hash, nextValidatorsKey) si := d.GetStorageItem(n.ContractID, nextValidatorsKey)
if si == nil { if si == nil {
return n.GetValidatorsInternal(bc, d) return n.GetValidatorsInternal(bc, d)
} }

View file

@ -108,7 +108,7 @@ func (c *nep5TokenNative) TotalSupply(ic *interop.Context, _ []stackitem.Item) s
} }
func (c *nep5TokenNative) getTotalSupply(ic *interop.Context) *big.Int { func (c *nep5TokenNative) getTotalSupply(ic *interop.Context) *big.Int {
si := ic.DAO.GetStorageItem(c.Hash, totalSupplyKey) si := ic.DAO.GetStorageItem(c.ContractID, totalSupplyKey)
if si == nil { if si == nil {
return big.NewInt(0) return big.NewInt(0)
} }
@ -117,7 +117,7 @@ func (c *nep5TokenNative) getTotalSupply(ic *interop.Context) *big.Int {
func (c *nep5TokenNative) saveTotalSupply(ic *interop.Context, supply *big.Int) error { func (c *nep5TokenNative) saveTotalSupply(ic *interop.Context, supply *big.Int) error {
si := &state.StorageItem{Value: bigint.ToBytes(supply)} si := &state.StorageItem{Value: bigint.ToBytes(supply)}
return ic.DAO.PutStorageItem(c.Hash, totalSupplyKey, si) return ic.DAO.PutStorageItem(c.ContractID, totalSupplyKey, si)
} }
func (c *nep5TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (c *nep5TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -165,7 +165,7 @@ func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, a
} }
keyFrom := makeAccountKey(from) keyFrom := makeAccountKey(from)
siFrom := ic.DAO.GetStorageItem(c.Hash, keyFrom) siFrom := ic.DAO.GetStorageItem(c.ContractID, keyFrom)
if siFrom == nil { if siFrom == nil {
return errors.New("insufficient funds") return errors.New("insufficient funds")
} }
@ -180,20 +180,20 @@ func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, a
if err := c.incBalance(ic, from, siFrom, inc); err != nil { if err := c.incBalance(ic, from, siFrom, inc); err != nil {
return err return err
} }
if err := ic.DAO.PutStorageItem(c.Hash, keyFrom, siFrom); err != nil { if err := ic.DAO.PutStorageItem(c.ContractID, keyFrom, siFrom); err != nil {
return err return err
} }
if !isEmpty { if !isEmpty {
keyTo := makeAccountKey(to) keyTo := makeAccountKey(to)
siTo := ic.DAO.GetStorageItem(c.Hash, keyTo) siTo := ic.DAO.GetStorageItem(c.ContractID, keyTo)
if siTo == nil { if siTo == nil {
siTo = new(state.StorageItem) siTo = new(state.StorageItem)
} }
if err := c.incBalance(ic, to, siTo, amount); err != nil { if err := c.incBalance(ic, to, siTo, amount); err != nil {
return err return err
} }
if err := ic.DAO.PutStorageItem(c.Hash, keyTo, siTo); err != nil { if err := ic.DAO.PutStorageItem(c.ContractID, keyTo, siTo); err != nil {
return err return err
} }
} }
@ -234,14 +234,14 @@ func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount
} }
key := makeAccountKey(h) key := makeAccountKey(h)
si := ic.DAO.GetStorageItem(c.Hash, key) si := ic.DAO.GetStorageItem(c.ContractID, key)
if si == nil { if si == nil {
si = new(state.StorageItem) si = new(state.StorageItem)
} }
if err := c.incBalance(ic, h, si, amount); err != nil { if err := c.incBalance(ic, h, si, amount); err != nil {
panic(err) panic(err)
} }
if err := ic.DAO.PutStorageItem(c.Hash, key, si); err != nil { if err := ic.DAO.PutStorageItem(c.ContractID, key, si); err != nil {
panic(err) panic(err)
} }

View file

@ -94,13 +94,13 @@ func (chain testChain) GetEnrollments() ([]state.Validator, error) {
func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) { func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem { func (chain testChain) GetStorageItem(id int32, key []byte) *state.StorageItem {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetTestVM(tx *transaction.Transaction) *vm.VM { func (chain testChain) GetTestVM(tx *transaction.Transaction) *vm.VM {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) { func (chain testChain) GetStorageItems(id int32) (map[string]*state.StorageItem, error) {
panic("TODO") panic("TODO")
} }
func (chain testChain) CurrentHeaderHash() util.Uint256 { func (chain testChain) CurrentHeaderHash() util.Uint256 {

View file

@ -634,18 +634,44 @@ func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int6
return d, nil return d, nil
} }
func (s *Server) contractIDFromParam(param *request.Param) (int32, *response.Error) {
var result int32
switch param.Type {
case request.StringT:
var err error
scriptHash, err := param.GetUint160FromHex()
if err != nil {
return 0, response.ErrInvalidParams
}
cs := s.chain.GetContractState(scriptHash)
if cs == nil {
return 0, response.ErrUnknown
}
result = cs.ID
case request.NumberT:
id, err := param.GetInt()
if err != nil {
return 0, response.ErrInvalidParams
}
result = int32(id)
default:
return 0, response.ErrInvalidParams
}
return result, nil
}
func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) { func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) {
param, ok := ps.Value(0) param, ok := ps.Value(0)
if !ok { if !ok {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
id, rErr := s.contractIDFromParam(param)
scriptHash, err := param.GetUint160FromHex() if rErr == response.ErrUnknown {
if err != nil { return nil, nil
return nil, response.ErrInvalidParams }
if rErr != nil {
return nil, rErr
} }
scriptHash = scriptHash.Reverse()
param, ok = ps.Value(1) param, ok = ps.Value(1)
if !ok { if !ok {
@ -657,7 +683,7 @@ func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
item := s.chain.GetStorageItem(scriptHash.Reverse(), key) item := s.chain.GetStorageItem(id, key)
if item == nil { if item == nil {
return nil, nil return nil, nil
} }