diff --git a/pkg/core/native/blocked_accounts.go b/pkg/core/native/blocked_accounts.go deleted file mode 100644 index fb27ce34f..000000000 --- a/pkg/core/native/blocked_accounts.go +++ /dev/null @@ -1,53 +0,0 @@ -package native - -import ( - "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" -) - -// BlockedAccounts represents a slice of blocked accounts hashes. -type BlockedAccounts []util.Uint160 - -// Bytes returns serialized BlockedAccounts. -func (ba *BlockedAccounts) Bytes() []byte { - w := io.NewBufBinWriter() - ba.EncodeBinary(w.BinWriter) - if w.Err != nil { - panic(w.Err) - } - return w.Bytes() -} - -// EncodeBinary implements io.Serializable interface. -func (ba *BlockedAccounts) EncodeBinary(w *io.BinWriter) { - w.WriteArray(*ba) -} - -// BlockedAccountsFromBytes converts serialized BlockedAccounts to structure. -func BlockedAccountsFromBytes(b []byte) (BlockedAccounts, error) { - ba := new(BlockedAccounts) - if len(b) == 0 { - return *ba, nil - } - r := io.NewBinReaderFromBuf(b) - ba.DecodeBinary(r) - if r.Err != nil { - return nil, r.Err - } - return *ba, nil -} - -// DecodeBinary implements io.Serializable interface. -func (ba *BlockedAccounts) DecodeBinary(r *io.BinReader) { - r.ReadArray(ba) -} - -// ToStackItem converts BlockedAccounts to stackitem.Item -func (ba *BlockedAccounts) ToStackItem() stackitem.Item { - result := make([]stackitem.Item, len(*ba)) - for i, account := range *ba { - result[i] = stackitem.NewByteArray(account.BytesLE()) - } - return stackitem.NewArray(result) -} diff --git a/pkg/core/native/blocked_accounts_test.go b/pkg/core/native/blocked_accounts_test.go deleted file mode 100644 index 76709395e..000000000 --- a/pkg/core/native/blocked_accounts_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package native - -import ( - "encoding/json" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/stretchr/testify/require" -) - -func TestEncodeDecodeBinary(t *testing.T) { - expected := &BlockedAccounts{ - util.Uint160{1, 2, 3}, - util.Uint160{4, 5, 6}, - } - actual := new(BlockedAccounts) - testserdes.EncodeDecodeBinary(t, expected, actual) - - expected = &BlockedAccounts{} - actual = new(BlockedAccounts) - testserdes.EncodeDecodeBinary(t, expected, actual) -} - -func TestBytesFromBytes(t *testing.T) { - expected := BlockedAccounts{ - util.Uint160{1, 2, 3}, - util.Uint160{4, 5, 6}, - } - actual, err := BlockedAccountsFromBytes(expected.Bytes()) - require.NoError(t, err) - require.Equal(t, expected, actual) - - expected = BlockedAccounts{} - actual, err = BlockedAccountsFromBytes(expected.Bytes()) - require.NoError(t, err) - require.Equal(t, expected, actual) -} - -func TestToStackItem(t *testing.T) { - u1 := util.Uint160{1, 2, 3} - u2 := util.Uint160{4, 5, 6} - expected := BlockedAccounts{u1, u2} - actual := stackitem.NewArray([]stackitem.Item{ - stackitem.NewByteArray(u1.BytesLE()), - stackitem.NewByteArray(u2.BytesLE()), - }) - require.Equal(t, expected.ToStackItem(), actual) - - expected = BlockedAccounts{} - actual = stackitem.NewArray([]stackitem.Item{}) - require.Equal(t, expected.ToStackItem(), actual) -} - -func TestMarshallJSON(t *testing.T) { - ba := &BlockedAccounts{} - p := smartcontract.ParameterFromStackItem(ba.ToStackItem(), make(map[stackitem.Item]bool)) - actual, err := json.Marshal(p) - require.NoError(t, err) - expected := `{"type":"Array","value":[]}` - require.Equal(t, expected, string(actual)) -} diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index 8383d5c4f..6225e8cd1 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -30,6 +30,9 @@ type Designate struct { const ( designateContractID = -5 designateName = "Designation" + + // maxNodeCount is the maximum number of nodes to set the role for. + maxNodeCount = 32 ) // Role represents type of participant. @@ -45,6 +48,7 @@ const ( var ( ErrInvalidRole = errors.New("invalid role") ErrEmptyNodeList = errors.New("node list is empty") + ErrLargeNodeList = errors.New("node list is too large") ) func isValidRole(r Role) bool { @@ -162,9 +166,13 @@ func (s *Designate) designateAsRole(ic *interop.Context, args []stackitem.Item) // DesignateAsRole sets nodes for role r. func (s *Designate) DesignateAsRole(ic *interop.Context, r Role, pubs keys.PublicKeys) error { - if len(pubs) == 0 { + length := len(pubs) + if length == 0 { return ErrEmptyNodeList } + if length > maxNodeCount { + return ErrLargeNodeList + } if !isValidRole(r) { return ErrInvalidRole } diff --git a/pkg/core/native/gas_record.go b/pkg/core/native/gas_record.go new file mode 100644 index 000000000..1c9121ac1 --- /dev/null +++ b/pkg/core/native/gas_record.go @@ -0,0 +1,13 @@ +package native + +import "math/big" + +// gasIndexPair contains block index together with generated gas per block. +// It is used to cache NEO GASRecords. +type gasIndexPair struct { + Index uint32 + GASPerBlock big.Int +} + +// gasRecord contains history of gas per block changes. It is used only by NEO cache. +type gasRecord []gasIndexPair diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 43830e1cc..941c33899 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -2,7 +2,9 @@ package native import ( "crypto/elliptic" + "encoding/binary" "errors" + "fmt" "math/big" "sort" "strings" @@ -189,13 +191,15 @@ func (n *NEO) Initialize(ic *interop.Context) error { } n.mint(ic, h, big.NewInt(NEOTotalSupply)) - gr := &state.GASRecord{{Index: 0, GASPerBlock: *big.NewInt(5 * GASFactor)}} - n.gasPerBlock.Store(*gr) - n.gasPerBlockChanged.Store(false) - err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixGASPerBlock}, &state.StorageItem{Value: gr.Bytes()}) + var index uint32 = 0 + value := big.NewInt(5 * GASFactor) + err = n.putGASRecord(ic.DAO, index, value) if err != nil { return err } + gr := &gasRecord{{Index: index, GASPerBlock: *value}} + n.gasPerBlock.Store(*gr) + n.gasPerBlockChanged.Store(false) err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixVotersCount}, &state.StorageItem{Value: []byte{}}) if err != nil { return err @@ -217,9 +221,8 @@ func (n *NEO) InitializeCache(bc blockchainer.Blockchainer, d dao.DAO) error { return err } - var gr state.GASRecord - si = d.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock}) - if err := gr.FromBytes(si.Value); err != nil { + gr, err := n.getSortedGASRecordFromDAO(d) + if err != nil { return err } n.gasPerBlock.Store(gr) @@ -293,7 +296,10 @@ func (n *NEO) PostPersist(ic *interop.Context) error { // OnPersistEnd updates cached values if they've been changed. func (n *NEO) OnPersistEnd(d dao.DAO) { if n.gasPerBlockChanged.Load().(bool) { - gr := n.getGASRecordFromDAO(d) + gr, err := n.getSortedGASRecordFromDAO(d) + if err != nil { + panic(err) + } n.gasPerBlock.Store(gr) n.gasPerBlockChanged.Store(false) } @@ -365,20 +371,41 @@ func (n *NEO) getGASPerBlock(ic *interop.Context, _ []stackitem.Item) stackitem. return stackitem.NewBigInteger(gas) } -func (n *NEO) getGASRecordFromDAO(d dao.DAO) state.GASRecord { - var gr state.GASRecord - si := d.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock}) - _ = gr.FromBytes(si.Value) - return gr +func (n *NEO) getSortedGASRecordFromDAO(d dao.DAO) (gasRecord, error) { + grMap, err := d.GetStorageItemsWithPrefix(n.ContractID, []byte{prefixGASPerBlock}) + if err != nil { + return gasRecord{}, fmt.Errorf("failed to get gas records from storage: %w", err) + } + var ( + i int + gr = make(gasRecord, len(grMap)) + ) + for indexBytes, gasValue := range grMap { + gr[i] = gasIndexPair{ + Index: binary.BigEndian.Uint32([]byte(indexBytes)), + GASPerBlock: *bigint.FromBytes(gasValue.Value), + } + i++ + } + sort.Slice(gr, func(i, j int) bool { + return gr[i].Index < gr[j].Index + }) + return gr, nil } // GetGASPerBlock returns gas generated for block with provided index. func (n *NEO) GetGASPerBlock(d dao.DAO, index uint32) *big.Int { - var gr state.GASRecord + var ( + gr gasRecord + err error + ) if n.gasPerBlockChanged.Load().(bool) { - gr = n.getGASRecordFromDAO(d) + gr, err = n.getSortedGASRecordFromDAO(d) + if err != nil { + panic(err) + } } else { - gr = n.gasPerBlock.Load().(state.GASRecord) + gr = n.gasPerBlock.Load().(gasRecord) } for i := len(gr) - 1; i >= 0; i-- { if gr[i].Index <= index { @@ -413,21 +440,8 @@ func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) (b if err != nil || !ok { return ok, err } - si := ic.DAO.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock}) - var gr state.GASRecord - if err := gr.FromBytes(si.Value); err != nil { - return false, err - } - if len(gr) > 0 && gr[len(gr)-1].Index == index { - gr[len(gr)-1].GASPerBlock = *gas - } else { - gr = append(gr, state.GASIndexPair{ - Index: index, - GASPerBlock: *gas, - }) - } n.gasPerBlockChanged.Store(true) - return true, ic.DAO.PutStorageItem(n.ContractID, []byte{prefixGASPerBlock}, &state.StorageItem{Value: gr.Bytes()}) + return true, n.putGASRecord(ic.DAO, index, gas) } // CalculateBonus calculates amount of gas generated for holding `value` NEO from start to end block. @@ -437,10 +451,17 @@ func (n *NEO) CalculateBonus(ic *interop.Context, value *big.Int, start, end uin } else if value.Sign() < 0 { return nil, errors.New("negative value") } - si := ic.DAO.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock}) - var gr state.GASRecord - if err := gr.FromBytes(si.Value); err != nil { - return nil, err + var ( + gr gasRecord + err error + ) + if !n.gasPerBlockChanged.Load().(bool) { + gr = n.gasPerBlock.Load().(gasRecord) + } else { + gr, err = n.getSortedGASRecordFromDAO(ic.DAO) + if err != nil { + return nil, err + } } var sum, tmp big.Int for i := len(gr) - 1; i >= 0; i-- { @@ -751,3 +772,15 @@ func toPublicKey(s stackitem.Item) *keys.PublicKey { } return pub } + +// putGASRecord is a helper which creates key and puts GASPerBlock value into the storage. +func (n *NEO) putGASRecord(dao dao.DAO, index uint32, value *big.Int) error { + key := make([]byte, 5) + key[0] = prefixGASPerBlock + binary.BigEndian.PutUint32(key[1:], index) + si := &state.StorageItem{ + Value: bigint.ToBytes(value), + IsConst: false, + } + return dao.PutStorageItem(n.ContractID, key, si) +} diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index abe3b7228..dfcc47287 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -3,6 +3,7 @@ package native import ( "encoding/binary" "errors" + "fmt" "math/big" "github.com/nspcc-dev/neo-go/pkg/core/dao" @@ -42,6 +43,8 @@ const ( maxFilterLength = 128 maxCallbackLength = 32 maxUserDataLength = 512 + // maxRequestsCount is the maximum number of requests per URL + maxRequestsCount = 256 oracleRequestPrice = 5000_0000 ) @@ -329,6 +332,9 @@ func (o *Oracle) PutRequestInternal(id uint64, req *state.OracleRequest, d dao.D if err := o.getSerializableFromDAO(d, key, lst); err != nil && !errors.Is(err, storage.ErrKeyNotFound) { return err } + if len(*lst) >= maxRequestsCount { + return fmt.Errorf("there are too many pending requests for %s url", req.URL) + } *lst = append(*lst, id) si := &state.StorageItem{Value: lst.Bytes()} return d.PutStorageItem(o.ContractID, key, si) diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index d0e8f7533..22a53cfbb 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -2,7 +2,6 @@ package native import ( "encoding/binary" - "errors" "fmt" "math/big" "sort" @@ -34,6 +33,9 @@ const ( minBlockSystemFee = 4007600 // maxFeePerByte is the maximum allowed fee per byte value. maxFeePerByte = 100_000_000 + + // blockedAccountPrefix is a prefix used to store blocked account. + blockedAccountPrefix = 15 ) var ( @@ -43,8 +45,6 @@ var ( // feePerByteKey is a key used to store the minimum fee per byte for // transaction. feePerByteKey = []byte{10} - // blockedAccountsKey is a key used to store the list of blocked accounts. - blockedAccountsKey = []byte{15} // maxBlockSizeKey is a key used to store the maximum block size value. maxBlockSizeKey = []byte{12} // maxBlockSystemFeeKey is a key used to store the maximum block system fee value. @@ -64,7 +64,7 @@ type Policy struct { feePerByte int64 maxBlockSystemFee int64 maxVerificationGas int64 - blockedAccounts BlockedAccounts + blockedAccounts []util.Uint160 } var _ interop.Contract = (*Policy)(nil) @@ -88,8 +88,9 @@ func newPolicy() *Policy { md = newMethodAndPrice(p.getFeePerByte, 1000000, smartcontract.AllowStates) p.AddMethod(md, desc, true) - desc = newDescriptor("getBlockedAccounts", smartcontract.ArrayType) - md = newMethodAndPrice(p.getBlockedAccounts, 1000000, smartcontract.AllowStates) + desc = newDescriptor("isBlocked", smartcontract.BoolType, + manifest.NewParameter("account", smartcontract.Hash160Type)) + md = newMethodAndPrice(p.isBlocked, 1000000, smartcontract.AllowStates) p.AddMethod(md, desc, true) desc = newDescriptor("getMaxBlockSystemFee", smartcontract.IntegerType) @@ -175,13 +176,6 @@ func (p *Policy) Initialize(ic *interop.Context) error { return err } - ba := new(BlockedAccounts) - si.Value = ba.Bytes() - err = ic.DAO.PutStorageItem(p.ContractID, blockedAccountsKey, si) - if err != nil { - return err - } - p.isValid = true p.maxTransactionsPerBlock = defaultMaxTransactionsPerBlock p.maxBlockSize = defaultMaxBlockSize @@ -220,15 +214,21 @@ func (p *Policy) OnPersistEnd(dao dao.DAO) error { p.maxVerificationGas = defaultMaxVerificationGas - si := dao.GetStorageItem(p.ContractID, blockedAccountsKey) - if si == nil { - return errors.New("BlockedAccounts uninitialized") - } - ba, err := BlockedAccountsFromBytes(si.Value) + p.blockedAccounts = make([]util.Uint160, 0) + siMap, err := dao.GetStorageItemsWithPrefix(p.ContractID, []byte{blockedAccountPrefix}) if err != nil { - return fmt.Errorf("failed to decode BlockedAccounts from bytes: %w", err) + return fmt.Errorf("failed to get blocked accounts from storage: %w", err) } - p.blockedAccounts = ba + for key := range siMap { + hash, err := util.Uint160DecodeBytesBE([]byte(key)) + if err != nil { + return fmt.Errorf("failed to decode blocked account hash: %w", err) + } + p.blockedAccounts = append(p.blockedAccounts, hash) + } + sort.Slice(p.blockedAccounts, func(i, j int) bool { + return p.blockedAccounts[i].Less(p.blockedAccounts[j]) + }) p.isValid = true return nil @@ -306,32 +306,28 @@ func (p *Policy) GetMaxBlockSystemFeeInternal(dao dao.DAO) int64 { return p.getInt64WithKey(dao, maxBlockSystemFeeKey) } -// getBlockedAccounts is Policy contract method and returns list of blocked -// accounts hashes. -func (p *Policy) getBlockedAccounts(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - ba, err := p.GetBlockedAccountsInternal(ic.DAO) - if err != nil { - panic(err) - } - return ba.ToStackItem() +// isBlocked is Policy contract method and checks whether provided account is blocked. +func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item { + hash := toUint160(args[0]) + return stackitem.NewBool(p.IsBlockedInternal(ic.DAO, hash)) } -// GetBlockedAccountsInternal returns list of blocked accounts hashes. -func (p *Policy) GetBlockedAccountsInternal(dao dao.DAO) (BlockedAccounts, error) { +// IsBlockedInternal checks whether provided account is blocked +func (p *Policy) IsBlockedInternal(dao dao.DAO, hash util.Uint160) bool { p.lock.RLock() defer p.lock.RUnlock() if p.isValid { - return p.blockedAccounts, nil + length := len(p.blockedAccounts) + i := sort.Search(length, func(i int) bool { + return !p.blockedAccounts[i].Less(hash) + }) + if length != 0 && i != length && p.blockedAccounts[i].Equals(hash) { + return true + } + return false } - si := dao.GetStorageItem(p.ContractID, blockedAccountsKey) - if si == nil { - return nil, errors.New("BlockedAccounts uninitialized") - } - ba, err := BlockedAccountsFromBytes(si.Value) - if err != nil { - return nil, err - } - return ba, nil + key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...) + return dao.GetStorageItem(p.ContractID, key) != nil } // setMaxTransactionsPerBlock is Policy contract method and sets the upper limit @@ -437,30 +433,15 @@ func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stacki if !ok { return stackitem.NewBool(false) } - value := toUint160(args[0]) - si := ic.DAO.GetStorageItem(p.ContractID, blockedAccountsKey) - if si == nil { - panic("BlockedAccounts uninitialized") - } - ba, err := BlockedAccountsFromBytes(si.Value) - if err != nil { - panic(err) - } - indexToInsert := sort.Search(len(ba), func(i int) bool { - return !ba[i].Less(value) - }) - ba = append(ba, value) - if indexToInsert != len(ba)-1 && ba[indexToInsert].Equals(value) { + hash := toUint160(args[0]) + if p.IsBlockedInternal(ic.DAO, hash) { return stackitem.NewBool(false) } - if len(ba) > 1 { - copy(ba[indexToInsert+1:], ba[indexToInsert:]) - ba[indexToInsert] = value - } + key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...) p.lock.Lock() defer p.lock.Unlock() - err = ic.DAO.PutStorageItem(p.ContractID, blockedAccountsKey, &state.StorageItem{ - Value: ba.Bytes(), + err = ic.DAO.PutStorageItem(p.ContractID, key, &state.StorageItem{ + Value: []byte{0x01}, }) if err != nil { panic(err) @@ -479,27 +460,14 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac if !ok { return stackitem.NewBool(false) } - value := toUint160(args[0]) - si := ic.DAO.GetStorageItem(p.ContractID, blockedAccountsKey) - if si == nil { - panic("BlockedAccounts uninitialized") - } - ba, err := BlockedAccountsFromBytes(si.Value) - if err != nil { - panic(err) - } - indexToRemove := sort.Search(len(ba), func(i int) bool { - return !ba[i].Less(value) - }) - if indexToRemove == len(ba) || !ba[indexToRemove].Equals(value) { + hash := toUint160(args[0]) + if !p.IsBlockedInternal(ic.DAO, hash) { return stackitem.NewBool(false) } - ba = append(ba[:indexToRemove], ba[indexToRemove+1:]...) + key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...) p.lock.Lock() defer p.lock.Unlock() - err = ic.DAO.PutStorageItem(p.ContractID, blockedAccountsKey, &state.StorageItem{ - Value: ba.Bytes(), - }) + err = ic.DAO.DeleteStorageItem(p.ContractID, key) if err != nil { panic(err) } @@ -555,18 +523,9 @@ func (p *Policy) checkValidators(ic *interop.Context) (bool, error) { // like not being signed by blocked account or not exceeding block-level system // fee limit. func (p *Policy) CheckPolicy(d dao.DAO, tx *transaction.Transaction) error { - ba, err := p.GetBlockedAccountsInternal(d) - if err != nil { - return fmt.Errorf("unable to get blocked accounts list: %w", err) - } - if len(ba) > 0 { - for _, signer := range tx.Signers { - i := sort.Search(len(ba), func(i int) bool { - return !ba[i].Less(signer.Account) - }) - if i != len(ba) && ba[i].Equals(signer.Account) { - return fmt.Errorf("account %s is blocked", signer.Account.StringLE()) - } + for _, signer := range tx.Signers { + if p.IsBlockedInternal(d, signer.Account) { + return fmt.Errorf("account %s is blocked", signer.Account.StringLE()) } } maxBlockSystemFee := p.GetMaxBlockSystemFeeInternal(d) diff --git a/pkg/core/native_designate_test.go b/pkg/core/native_designate_test.go index c15098a59..83d7e31c1 100644 --- a/pkg/core/native_designate_test.go +++ b/pkg/core/native_designate_test.go @@ -92,6 +92,9 @@ func TestDesignate_DesignateAsRole(t *testing.T) { err = des.DesignateAsRole(ic, native.RoleOracle, keys.PublicKeys{}) require.True(t, errors.Is(err, native.ErrEmptyNodeList), "got: %v", err) + err = des.DesignateAsRole(ic, native.RoleOracle, make(keys.PublicKeys, 32+1)) + require.True(t, errors.Is(err, native.ErrLargeNodeList), "got: %v", err) + priv, err := keys.NewPrivateKey() require.NoError(t, err) pub := priv.PublicKey() diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index 9a5c9f777..4312efdb4 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -2,7 +2,6 @@ package core import ( "math/big" - "sort" "testing" "github.com/nspcc-dev/neo-go/pkg/core/block" @@ -10,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" + "github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/network/payload" "github.com/nspcc-dev/neo-go/pkg/util" @@ -162,16 +162,15 @@ func TestBlockedAccounts(t *testing.T) { defer chain.Close() account := util.Uint160{1, 2, 3} - t.Run("get, internal method", func(t *testing.T) { - accounts, err := chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao) - require.NoError(t, err) - require.Equal(t, native.BlockedAccounts{}, accounts) + t.Run("isBlocked, internal method", func(t *testing.T) { + isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, random.Uint160()) + require.Equal(t, false, isBlocked) }) - t.Run("get, contract method", func(t *testing.T) { - res, err := invokeNativePolicyMethod(chain, "getBlockedAccounts") + t.Run("isBlocked, contract method", func(t *testing.T) { + res, err := invokeNativePolicyMethod(chain, "isBlocked", random.Uint160()) require.NoError(t, err) - checkResult(t, res, stackitem.NewArray([]stackitem.Item{})) + checkResult(t, res, stackitem.NewBool(false)) require.NoError(t, chain.persist()) }) @@ -180,18 +179,16 @@ func TestBlockedAccounts(t *testing.T) { require.NoError(t, err) checkResult(t, res, stackitem.NewBool(true)) - accounts, err := chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao) - require.NoError(t, err) - require.Equal(t, native.BlockedAccounts{account}, accounts) + isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, account) + require.Equal(t, isBlocked, true) require.NoError(t, chain.persist()) res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE()) require.NoError(t, err) checkResult(t, res, stackitem.NewBool(true)) - accounts, err = chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao) - require.NoError(t, err) - require.Equal(t, native.BlockedAccounts{}, accounts) + isBlocked = chain.contracts.Policy.IsBlockedInternal(chain.dao, account) + require.Equal(t, false, isBlocked) require.NoError(t, chain.persist()) }) @@ -220,28 +217,6 @@ func TestBlockedAccounts(t *testing.T) { checkResult(t, res, stackitem.NewBool(false)) require.NoError(t, chain.persist()) }) - - t.Run("sorted", func(t *testing.T) { - accounts := []util.Uint160{ - {2, 3, 4}, - {4, 5, 6}, - {3, 4, 5}, - {1, 2, 3}, - } - for _, acc := range accounts { - res, err := invokeNativePolicyMethod(chain, "blockAccount", acc.BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.NoError(t, chain.persist()) - } - - sort.Slice(accounts, func(i, j int) bool { - return accounts[i].Less(accounts[j]) - }) - actual, err := chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao) - require.NoError(t, err) - require.Equal(t, native.BlockedAccounts(accounts), actual) - }) } func invokeNativePolicyMethod(chain *Blockchain, method string, args ...interface{}) (*state.AppExecResult, error) { diff --git a/pkg/core/state/native_state.go b/pkg/core/state/native_state.go index c6cfcd1ff..408daddb5 100644 --- a/pkg/core/state/native_state.go +++ b/pkg/core/state/native_state.go @@ -2,7 +2,6 @@ package state import ( "crypto/elliptic" - "errors" "math/big" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -22,76 +21,6 @@ type NEOBalanceState struct { VoteTo *keys.PublicKey } -// GASIndexPair contains block index together with generated gas per block. -type GASIndexPair struct { - Index uint32 - GASPerBlock big.Int -} - -// GASRecord contains history of gas per block changes. -type GASRecord []GASIndexPair - -// Bytes serializes g to []byte. -func (g *GASRecord) Bytes() []byte { - w := io.NewBufBinWriter() - g.EncodeBinary(w.BinWriter) - return w.Bytes() -} - -// FromBytes deserializes g from data. -func (g *GASRecord) FromBytes(data []byte) error { - r := io.NewBinReaderFromBuf(data) - g.DecodeBinary(r) - return r.Err -} - -// DecodeBinary implements io.Serializable. -func (g *GASRecord) DecodeBinary(r *io.BinReader) { - item := stackitem.DecodeBinaryStackItem(r) - if r.Err == nil { - r.Err = g.fromStackItem(item) - } -} - -// EncodeBinary implements io.Serializable. -func (g *GASRecord) EncodeBinary(w *io.BinWriter) { - item := g.toStackItem() - stackitem.EncodeBinaryStackItem(item, w) -} - -// toStackItem converts GASRecord to a stack item. -func (g *GASRecord) toStackItem() stackitem.Item { - items := make([]stackitem.Item, len(*g)) - for i := range items { - items[i] = stackitem.NewStruct([]stackitem.Item{ - stackitem.NewBigInteger(big.NewInt(int64((*g)[i].Index))), - stackitem.NewBigInteger(&(*g)[i].GASPerBlock), - }) - } - return stackitem.NewArray(items) -} - -var errInvalidFormat = errors.New("invalid item format") - -// fromStackItem converts item to a GASRecord. -func (g *GASRecord) fromStackItem(item stackitem.Item) error { - arr, ok := item.Value().([]stackitem.Item) - if !ok { - return errInvalidFormat - } - for i := range arr { - s, ok := arr[i].Value().([]stackitem.Item) - if !ok || len(s) != 2 || s[0].Type() != stackitem.IntegerT || s[1].Type() != stackitem.IntegerT { - return errInvalidFormat - } - *g = append(*g, GASIndexPair{ - Index: uint32(s[0].Value().(*big.Int).Uint64()), - GASPerBlock: *s[1].Value().(*big.Int), - }) - } - return nil -} - // NEP5BalanceStateFromBytes converts serialized NEP5BalanceState to structure. func NEP5BalanceStateFromBytes(b []byte) (*NEP5BalanceState, error) { balance := new(NEP5BalanceState) diff --git a/pkg/core/state/native_state_test.go b/pkg/core/state/native_state_test.go deleted file mode 100644 index 920a7f331..000000000 --- a/pkg/core/state/native_state_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package state - -import ( - "math/big" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/stretchr/testify/require" -) - -func TestGASRecord_EncodeBinary(t *testing.T) { - expected := &GASRecord{ - GASIndexPair{ - Index: 1, - GASPerBlock: *big.NewInt(123), - }, - GASIndexPair{ - Index: 2, - GASPerBlock: *big.NewInt(7), - }, - } - testserdes.EncodeDecodeBinary(t, expected, new(GASRecord)) -} - -func TestGASRecord_fromStackItem(t *testing.T) { - t.Run("NotArray", func(t *testing.T) { - item := stackitem.Null{} - require.Error(t, new(GASRecord).fromStackItem(item)) - }) - t.Run("InvalidFormat", func(t *testing.T) { - item := stackitem.NewArray([]stackitem.Item{ - stackitem.NewStruct([]stackitem.Item{ - stackitem.NewBigInteger(big.NewInt(1)), - stackitem.NewBool(true), - }), - }) - require.Error(t, new(GASRecord).fromStackItem(item)) - }) -} diff --git a/pkg/rpc/client/policy.go b/pkg/rpc/client/policy.go index 7e6aa59bf..efffcd492 100644 --- a/pkg/rpc/client/policy.go +++ b/pkg/rpc/client/policy.go @@ -3,7 +3,6 @@ package client import ( "fmt" - "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -41,39 +40,28 @@ func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) { return topIntFromStack(result.Stack) } -// GetBlockedAccounts invokes `getBlockedAccounts` method on a native Policy contract. -func (c *Client) GetBlockedAccounts() (native.BlockedAccounts, error) { - result, err := c.InvokeFunction(PolicyContractHash, "getBlockedAccounts", []smartcontract.Parameter{}, nil) +// IsBlocked invokes `isBlocked` method on native Policy contract. +func (c *Client) IsBlocked(hash util.Uint160) (bool, error) { + result, err := c.InvokeFunction(PolicyContractHash, "isBlocked", []smartcontract.Parameter{{ + Type: smartcontract.Hash160Type, + Value: hash, + }}, nil) if err != nil { - return nil, err + return false, err } err = getInvocationError(result) if err != nil { - return nil, fmt.Errorf("failed to get blocked accounts: %w", err) + return false, fmt.Errorf("failed to check if account is blocked: %w", err) } - return topBlockedAccountsFromStack(result.Stack) + return topBoolFromStack(result.Stack) } -func topBlockedAccountsFromStack(st []stackitem.Item) (native.BlockedAccounts, error) { +// topBoolFromStack returns the top boolean value from stack +func topBoolFromStack(st []stackitem.Item) (bool, error) { index := len(st) - 1 // top stack element is last in the array - var ( - ba native.BlockedAccounts - err error - ) - items, ok := st[index].Value().([]stackitem.Item) + result, ok := st[index].Value().(bool) if !ok { - return nil, fmt.Errorf("invalid stack item type: %s", st[index].Type()) + return false, fmt.Errorf("invalid stack item type: %s", st[index].Type()) } - ba = make(native.BlockedAccounts, len(items)) - for i, account := range items { - val, ok := account.Value().([]byte) - if !ok { - return nil, fmt.Errorf("invalid array element: %s", account.Type()) - } - ba[i], err = util.Uint160DecodeBytesLE(val) - if err != nil { - return nil, err - } - } - return ba, nil + return result, nil } diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 1be95bba9..d1913a12f 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -16,7 +16,6 @@ import ( "github.com/gorilla/websocket" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" - "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" @@ -378,15 +377,15 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, }, }, - "getBlockedAccounts": { + "isBlocked": { { name: "positive", invoke: func(c *Client) (interface{}, error) { - return c.GetBlockedAccounts() + return c.IsBlocked(util.Uint160{1, 2, 3}) }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"EMAMEmdldEJsb2NrZWRBY2NvdW50cwwUmmGkbuyXuJMG186B8VtGIJHQCTJBYn1bUg==","stack":[{"type":"Array","value":[]}],"tx":null}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"EMAMEmdldEJsb2NrZWRBY2NvdW50cwwUmmGkbuyXuJMG186B8VtGIJHQCTJBYn1bUg==","stack":[{"type":"Boolean","value":false}],"tx":null}}`, result: func(c *Client) interface{} { - return native.BlockedAccounts{} + return false }, }, },