Merge pull request #1058 from nspcc-dev/neo3/smartcontract/policy
core: add native policy contract
This commit is contained in:
commit
0fdeafb8f7
23 changed files with 1058 additions and 43 deletions
|
@ -18,7 +18,6 @@ ProtocolConfiguration:
|
||||||
- seed5.neo.org:10333
|
- seed5.neo.org:10333
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
VerifyTransactions: false
|
VerifyTransactions: false
|
||||||
MaxTransactionsPerBlock: 500
|
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -18,7 +18,6 @@ ProtocolConfiguration:
|
||||||
- seed5t.neo.org:20333
|
- seed5t.neo.org:20333
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
VerifyTransactions: false
|
VerifyTransactions: false
|
||||||
MaxTransactionsPerBlock: 500
|
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -9,7 +9,7 @@ require (
|
||||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/mr-tron/base58 v1.1.2
|
github.com/mr-tron/base58 v1.1.2
|
||||||
github.com/nspcc-dev/dbft v0.0.0-20200531081613-7a39e7b757ac
|
github.com/nspcc-dev/dbft v0.0.0-20200623100921-5a182c20965e
|
||||||
github.com/nspcc-dev/rfc6979 v0.2.0
|
github.com/nspcc-dev/rfc6979 v0.2.0
|
||||||
github.com/pierrec/lz4 v2.5.2+incompatible
|
github.com/pierrec/lz4 v2.5.2+incompatible
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -137,8 +137,8 @@ github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a h1:ajvxgEe9qY4vvoSm
|
||||||
github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a/go.mod h1:/YFK+XOxxg0Bfm6P92lY5eDSLYfp06XOdL8KAVgXjVk=
|
github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a/go.mod h1:/YFK+XOxxg0Bfm6P92lY5eDSLYfp06XOdL8KAVgXjVk=
|
||||||
github.com/nspcc-dev/dbft v0.0.0-20200219114139-199d286ed6c1 h1:yEx9WznS+rjE0jl0dLujCxuZSIb+UTjF+005TJu/nNI=
|
github.com/nspcc-dev/dbft v0.0.0-20200219114139-199d286ed6c1 h1:yEx9WznS+rjE0jl0dLujCxuZSIb+UTjF+005TJu/nNI=
|
||||||
github.com/nspcc-dev/dbft v0.0.0-20200219114139-199d286ed6c1/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ=
|
github.com/nspcc-dev/dbft v0.0.0-20200219114139-199d286ed6c1/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ=
|
||||||
github.com/nspcc-dev/dbft v0.0.0-20200531081613-7a39e7b757ac h1:cXPgsp4avJ7cR1nPRdpFRHmWoMSRZ41FSvlNjpsyTiA=
|
github.com/nspcc-dev/dbft v0.0.0-20200623100921-5a182c20965e h1:QOT9slflIkEKb5wY0ZUC0dCmCgoqGlhOAh9+xWMIxfg=
|
||||||
github.com/nspcc-dev/dbft v0.0.0-20200531081613-7a39e7b757ac/go.mod h1:1FYQXSbb6/9HQIkoF8XO7W/S8N7AZRkBsgwbcXRvk0E=
|
github.com/nspcc-dev/dbft v0.0.0-20200623100921-5a182c20965e/go.mod h1:1FYQXSbb6/9HQIkoF8XO7W/S8N7AZRkBsgwbcXRvk0E=
|
||||||
github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg=
|
github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg=
|
||||||
github.com/nspcc-dev/neofs-crypto v0.2.0 h1:ftN+59WqxSWz/RCgXYOfhmltOOqU+udsNQSvN6wkFck=
|
github.com/nspcc-dev/neofs-crypto v0.2.0 h1:ftN+59WqxSWz/RCgXYOfhmltOOqU+udsNQSvN6wkFck=
|
||||||
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
|
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
|
||||||
|
|
|
@ -7,9 +7,8 @@ import (
|
||||||
// ProtocolConfiguration represents the protocol config.
|
// ProtocolConfiguration represents the protocol config.
|
||||||
type (
|
type (
|
||||||
ProtocolConfiguration struct {
|
ProtocolConfiguration struct {
|
||||||
Magic netmode.Magic `yaml:"Magic"`
|
Magic netmode.Magic `yaml:"Magic"`
|
||||||
MaxTransactionsPerBlock int `yaml:"MaxTransactionsPerBlock"`
|
MemPoolSize int `yaml:"MemPoolSize"`
|
||||||
MemPoolSize int `yaml:"MemPoolSize"`
|
|
||||||
// SaveStorageBatch enables storage batch saving before every persist.
|
// SaveStorageBatch enables storage batch saving before every persist.
|
||||||
SaveStorageBatch bool `yaml:"SaveStorageBatch"`
|
SaveStorageBatch bool `yaml:"SaveStorageBatch"`
|
||||||
SecondsPerBlock int `yaml:"SecondsPerBlock"`
|
SecondsPerBlock int `yaml:"SecondsPerBlock"`
|
||||||
|
|
|
@ -121,7 +121,6 @@ func NewService(cfg Config) (Service, error) {
|
||||||
dbft.WithLogger(srv.log),
|
dbft.WithLogger(srv.log),
|
||||||
dbft.WithSecondsPerBlock(cfg.TimePerBlock),
|
dbft.WithSecondsPerBlock(cfg.TimePerBlock),
|
||||||
dbft.WithGetKeyPair(srv.getKeyPair),
|
dbft.WithGetKeyPair(srv.getKeyPair),
|
||||||
dbft.WithTxPerBlock(10000),
|
|
||||||
dbft.WithRequestTx(cfg.RequestTx),
|
dbft.WithRequestTx(cfg.RequestTx),
|
||||||
dbft.WithGetTx(srv.getTx),
|
dbft.WithGetTx(srv.getTx),
|
||||||
dbft.WithGetVerified(srv.getVerifiedTx),
|
dbft.WithGetVerified(srv.getVerifiedTx),
|
||||||
|
@ -392,7 +391,7 @@ func (s *service) getBlock(h util.Uint256) block.Block {
|
||||||
return &neoBlock{Block: *b}
|
return &neoBlock{Block: *b}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) getVerifiedTx(count int) []block.Transaction {
|
func (s *service) getVerifiedTx() []block.Transaction {
|
||||||
pool := s.Config.Chain.GetMemPool()
|
pool := s.Config.Chain.GetMemPool()
|
||||||
|
|
||||||
var txx []*transaction.Transaction
|
var txx []*transaction.Transaction
|
||||||
|
|
|
@ -31,7 +31,7 @@ func TestNewService(t *testing.T) {
|
||||||
require.NoError(t, srv.Chain.PoolTx(tx))
|
require.NoError(t, srv.Chain.PoolTx(tx))
|
||||||
|
|
||||||
var txx []block.Transaction
|
var txx []block.Transaction
|
||||||
require.NotPanics(t, func() { txx = srv.getVerifiedTx(1) })
|
require.NotPanics(t, func() { txx = srv.getVerifiedTx() })
|
||||||
require.Len(t, txx, 1)
|
require.Len(t, txx, 1)
|
||||||
require.Equal(t, tx, txx[0])
|
require.Equal(t, tx, txx[0])
|
||||||
srv.Chain.Close()
|
srv.Chain.Close()
|
||||||
|
@ -69,7 +69,7 @@ func TestService_GetVerified(t *testing.T) {
|
||||||
srv.dbft.ViewNumber = 1
|
srv.dbft.ViewNumber = 1
|
||||||
|
|
||||||
t.Run("new transactions will be proposed in case of failure", func(t *testing.T) {
|
t.Run("new transactions will be proposed in case of failure", func(t *testing.T) {
|
||||||
txx := srv.getVerifiedTx(10)
|
txx := srv.getVerifiedTx()
|
||||||
require.Equal(t, 1, len(txx), "there is only 1 tx in mempool")
|
require.Equal(t, 1, len(txx), "there is only 1 tx in mempool")
|
||||||
require.Equal(t, txs[3], txx[0])
|
require.Equal(t, txs[3], txx[0])
|
||||||
})
|
})
|
||||||
|
@ -79,7 +79,7 @@ func TestService_GetVerified(t *testing.T) {
|
||||||
require.NoError(t, srv.Chain.PoolTx(tx))
|
require.NoError(t, srv.Chain.PoolTx(tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
txx := srv.getVerifiedTx(10)
|
txx := srv.getVerifiedTx()
|
||||||
require.Contains(t, txx, txs[0])
|
require.Contains(t, txx, txs[0])
|
||||||
require.Contains(t, txx, txs[1])
|
require.Contains(t, txx, txs[1])
|
||||||
require.NotContains(t, txx, txs[2])
|
require.NotContains(t, txx, txs[2])
|
||||||
|
|
|
@ -152,10 +152,6 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
||||||
cfg.MemPoolSize = defaultMemPoolSize
|
cfg.MemPoolSize = defaultMemPoolSize
|
||||||
log.Info("mempool size is not set or wrong, setting default value", zap.Int("MemPoolSize", cfg.MemPoolSize))
|
log.Info("mempool size is not set or wrong, setting default value", zap.Int("MemPoolSize", cfg.MemPoolSize))
|
||||||
}
|
}
|
||||||
if cfg.MaxTransactionsPerBlock <= 0 {
|
|
||||||
cfg.MaxTransactionsPerBlock = 0
|
|
||||||
log.Info("MaxTransactionsPerBlock is not set or wrong, setting default value (unlimited)", zap.Int("MaxTransactionsPerBlock", cfg.MaxTransactionsPerBlock))
|
|
||||||
}
|
|
||||||
bc := &Blockchain{
|
bc := &Blockchain{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
dao: dao.NewSimple(s, cfg.Magic),
|
dao: dao.NewSimple(s, cfg.Magic),
|
||||||
|
@ -640,6 +636,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
bc.lock.Unlock()
|
bc.lock.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
bc.contracts.Policy.OnPersistEnd(bc.dao)
|
||||||
bc.topBlock.Store(block)
|
bc.topBlock.Store(block)
|
||||||
atomic.StoreUint32(&bc.blockHeight, block.Index)
|
atomic.StoreUint32(&bc.blockHeight, block.Index)
|
||||||
bc.memPool.RemoveStale(bc.isTxStillRelevant, bc)
|
bc.memPool.RemoveStale(bc.isTxStillRelevant, bc)
|
||||||
|
@ -1088,9 +1085,8 @@ func (bc *Blockchain) CalculateClaimable(value int64, startHeight, endHeight uin
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeePerByte returns transaction network fee per byte.
|
// FeePerByte returns transaction network fee per byte.
|
||||||
// TODO: should be implemented as part of PolicyContract
|
|
||||||
func (bc *Blockchain) FeePerByte() util.Fixed8 {
|
func (bc *Blockchain) FeePerByte() util.Fixed8 {
|
||||||
return util.Fixed8(1000)
|
return util.Fixed8(bc.contracts.Policy.GetFeePerByteInternal(bc.dao))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMemPool returns the memory pool of the blockchain.
|
// GetMemPool returns the memory pool of the blockchain.
|
||||||
|
@ -1101,8 +1097,9 @@ func (bc *Blockchain) GetMemPool() *mempool.Pool {
|
||||||
// ApplyPolicyToTxSet applies configured policies to given transaction set. It
|
// ApplyPolicyToTxSet applies configured policies to given transaction set. It
|
||||||
// expects slice to be ordered by fee and returns a subslice of it.
|
// expects slice to be ordered by fee and returns a subslice of it.
|
||||||
func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*transaction.Transaction {
|
func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*transaction.Transaction {
|
||||||
if bc.config.MaxTransactionsPerBlock != 0 && len(txes) > bc.config.MaxTransactionsPerBlock {
|
maxTx := bc.contracts.Policy.GetMaxTransactionsPerBlockInternal(bc.dao)
|
||||||
txes = txes[:bc.config.MaxTransactionsPerBlock]
|
if maxTx != 0 && len(txes) > int(maxTx) {
|
||||||
|
txes = txes[:maxTx]
|
||||||
}
|
}
|
||||||
return txes
|
return txes
|
||||||
}
|
}
|
||||||
|
@ -1126,6 +1123,22 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
|
||||||
if t.ValidUntilBlock <= height || t.ValidUntilBlock > height+transaction.MaxValidUntilBlockIncrement {
|
if t.ValidUntilBlock <= height || t.ValidUntilBlock > height+transaction.MaxValidUntilBlockIncrement {
|
||||||
return errors.Errorf("transaction has expired. ValidUntilBlock = %d, current height = %d", t.ValidUntilBlock, height)
|
return errors.Errorf("transaction has expired. ValidUntilBlock = %d, current height = %d", t.ValidUntilBlock, height)
|
||||||
}
|
}
|
||||||
|
hashes, err := bc.GetScriptHashesForVerifying(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blockedAccounts, err := bc.contracts.Policy.GetBlockedAccountsInternal(bc.dao)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, h := range hashes {
|
||||||
|
i := sort.Search(len(blockedAccounts), func(i int) bool {
|
||||||
|
return !blockedAccounts[i].Less(h)
|
||||||
|
})
|
||||||
|
if i != len(blockedAccounts) && blockedAccounts[i].Equals(h) {
|
||||||
|
return errors.Errorf("policy check failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
balance := bc.GetUtilityTokenBalance(t.Sender)
|
balance := bc.GetUtilityTokenBalance(t.Sender)
|
||||||
need := t.SystemFee.Add(t.NetworkFee)
|
need := t.SystemFee.Add(t.NetworkFee)
|
||||||
if balance.LessThan(need) {
|
if balance.LessThan(need) {
|
||||||
|
@ -1203,6 +1216,11 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Policying.
|
// Policying.
|
||||||
|
if ok, err := bc.contracts.Policy.CheckPolicy(bc.newInteropContext(trigger.Application, bc.dao, nil, t), t); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ok {
|
||||||
|
return ErrPolicy
|
||||||
|
}
|
||||||
if err := bc.memPool.Add(t, bc); err != nil {
|
if err := bc.memPool.Add(t, bc); err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case mempool.ErrOOM:
|
case mempool.ErrOOM:
|
||||||
|
|
|
@ -387,6 +387,17 @@ func addSender(txs ...*transaction.Transaction) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addCosigners(txs ...*transaction.Transaction) {
|
||||||
|
for _, tx := range txs {
|
||||||
|
tx.Cosigners = []transaction.Cosigner{{
|
||||||
|
Account: neoOwner,
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
AllowedContracts: nil,
|
||||||
|
AllowedGroups: nil,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func signTx(bc *Blockchain, txs ...*transaction.Transaction) error {
|
func signTx(bc *Blockchain, txs ...*transaction.Transaction) error {
|
||||||
validators, err := getValidators(bc.config)
|
validators, err := getValidators(bc.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -46,7 +46,8 @@ type Pool struct {
|
||||||
verifiedTxes items
|
verifiedTxes items
|
||||||
fees map[util.Uint160]utilityBalanceAndFees
|
fees map[util.Uint160]utilityBalanceAndFees
|
||||||
|
|
||||||
capacity int
|
capacity int
|
||||||
|
feePerByte util.Fixed8
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p items) Len() int { return len(p) }
|
func (p items) Len() int { return len(p) }
|
||||||
|
@ -218,12 +219,13 @@ func (mp *Pool) Remove(hash util.Uint256) {
|
||||||
// drop part of the mempool that is now invalid after the block acceptance.
|
// drop part of the mempool that is now invalid after the block acceptance.
|
||||||
func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer) {
|
func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer) {
|
||||||
mp.lock.Lock()
|
mp.lock.Lock()
|
||||||
|
policyChanged := mp.loadPolicy(feer)
|
||||||
// We can reuse already allocated slice
|
// We can reuse already allocated slice
|
||||||
// because items are iterated one-by-one in increasing order.
|
// because items are iterated one-by-one in increasing order.
|
||||||
newVerifiedTxes := mp.verifiedTxes[:0]
|
newVerifiedTxes := mp.verifiedTxes[:0]
|
||||||
mp.fees = make(map[util.Uint160]utilityBalanceAndFees) // it'd be nice to reuse existing map, but we can't easily clear it
|
mp.fees = make(map[util.Uint160]utilityBalanceAndFees) // it'd be nice to reuse existing map, but we can't easily clear it
|
||||||
for _, itm := range mp.verifiedTxes {
|
for _, itm := range mp.verifiedTxes {
|
||||||
if isOK(itm.txn) && mp.tryAddSendersFee(itm.txn, feer) {
|
if isOK(itm.txn) && mp.checkPolicy(itm.txn, policyChanged) && mp.tryAddSendersFee(itm.txn, feer) {
|
||||||
newVerifiedTxes = append(newVerifiedTxes, itm)
|
newVerifiedTxes = append(newVerifiedTxes, itm)
|
||||||
} else {
|
} else {
|
||||||
delete(mp.verifiedMap, itm.txn.Hash())
|
delete(mp.verifiedMap, itm.txn.Hash())
|
||||||
|
@ -233,6 +235,25 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
|
||||||
mp.lock.Unlock()
|
mp.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadPolicy updates feePerByte field and returns whether policy has been
|
||||||
|
// changed.
|
||||||
|
func (mp *Pool) loadPolicy(feer Feer) bool {
|
||||||
|
newFeePerByte := feer.FeePerByte()
|
||||||
|
if newFeePerByte.GreaterThan(mp.feePerByte) {
|
||||||
|
mp.feePerByte = newFeePerByte
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPolicy checks whether transaction fits policy.
|
||||||
|
func (mp *Pool) checkPolicy(tx *transaction.Transaction, policyChanged bool) bool {
|
||||||
|
if !policyChanged || tx.FeePerByte() >= mp.feePerByte {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// NewMemPool returns a new Pool struct.
|
// NewMemPool returns a new Pool struct.
|
||||||
func NewMemPool(capacity int) Pool {
|
func NewMemPool(capacity int) Pool {
|
||||||
return Pool{
|
return Pool{
|
||||||
|
|
53
pkg/core/native/blocked_accounts.go
Normal file
53
pkg/core/native/blocked_accounts.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
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)
|
||||||
|
}
|
64
pkg/core/native/blocked_accounts_test.go
Normal file
64
pkg/core/native/blocked_accounts_test.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
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))
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import (
|
||||||
type Contracts struct {
|
type Contracts struct {
|
||||||
NEO *NEO
|
NEO *NEO
|
||||||
GAS *GAS
|
GAS *GAS
|
||||||
|
Policy *Policy
|
||||||
Contracts []interop.Contract
|
Contracts []interop.Contract
|
||||||
// persistScript is vm script which executes "onPersist" method of every native contract.
|
// persistScript is vm script which executes "onPersist" method of every native contract.
|
||||||
persistScript []byte
|
persistScript []byte
|
||||||
|
@ -41,7 +42,7 @@ func (cs *Contracts) ByID(id uint32) interop.Contract {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContracts returns new set of native contracts with new GAS and NEO
|
// NewContracts returns new set of native contracts with new GAS, NEO and Policy
|
||||||
// contracts.
|
// contracts.
|
||||||
func NewContracts() *Contracts {
|
func NewContracts() *Contracts {
|
||||||
cs := new(Contracts)
|
cs := new(Contracts)
|
||||||
|
@ -55,6 +56,10 @@ func NewContracts() *Contracts {
|
||||||
cs.Contracts = append(cs.Contracts, gas)
|
cs.Contracts = append(cs.Contracts, gas)
|
||||||
cs.NEO = neo
|
cs.NEO = neo
|
||||||
cs.Contracts = append(cs.Contracts, neo)
|
cs.Contracts = append(cs.Contracts, neo)
|
||||||
|
|
||||||
|
policy := newPolicy()
|
||||||
|
cs.Policy = policy
|
||||||
|
cs.Contracts = append(cs.Contracts, policy)
|
||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
452
pkg/core/native/policy.go
Normal file
452
pkg/core/native/policy.go
Normal file
|
@ -0,0 +1,452 @@
|
||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||||
|
"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/network/payload"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
policySyscallName = "Neo.Native.Policy"
|
||||||
|
policyContractID = -3
|
||||||
|
|
||||||
|
defaultMaxBlockSize = 1024 * 256
|
||||||
|
defaultMaxTransactionsPerBlock = 512
|
||||||
|
defaultFeePerByte = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// maxTransactionsPerBlockKey is a key used to store the maximum number of
|
||||||
|
// transactions allowed in block.
|
||||||
|
maxTransactionsPerBlockKey = []byte{23}
|
||||||
|
// 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{16}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Policy represents Policy native contract.
|
||||||
|
type Policy struct {
|
||||||
|
interop.ContractMD
|
||||||
|
lock sync.RWMutex
|
||||||
|
// isValid defies whether cached values were changed during the current
|
||||||
|
// consensus iteration. If false, these values will be updated after
|
||||||
|
// blockchain DAO persisting. If true, we can safely use cached values.
|
||||||
|
isValid bool
|
||||||
|
maxTransactionsPerBlock uint32
|
||||||
|
maxBlockSize uint32
|
||||||
|
feePerByte int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ interop.Contract = (*Policy)(nil)
|
||||||
|
|
||||||
|
// newPolicy returns Policy native contract.
|
||||||
|
func newPolicy() *Policy {
|
||||||
|
p := &Policy{ContractMD: *interop.NewContractMD(policySyscallName)}
|
||||||
|
|
||||||
|
p.ContractID = policyContractID
|
||||||
|
p.Manifest.Features |= smartcontract.HasStorage
|
||||||
|
|
||||||
|
desc := newDescriptor("getMaxTransactionsPerBlock", smartcontract.IntegerType)
|
||||||
|
md := newMethodAndPrice(p.getMaxTransactionsPerBlock, 1000000, smartcontract.NoneFlag)
|
||||||
|
p.AddMethod(md, desc, true)
|
||||||
|
|
||||||
|
desc = newDescriptor("getMaxBlockSize", smartcontract.IntegerType)
|
||||||
|
md = newMethodAndPrice(p.getMaxBlockSize, 1000000, smartcontract.NoneFlag)
|
||||||
|
p.AddMethod(md, desc, true)
|
||||||
|
|
||||||
|
desc = newDescriptor("getFeePerByte", smartcontract.IntegerType)
|
||||||
|
md = newMethodAndPrice(p.getFeePerByte, 1000000, smartcontract.NoneFlag)
|
||||||
|
p.AddMethod(md, desc, true)
|
||||||
|
|
||||||
|
desc = newDescriptor("getBlockedAccounts", smartcontract.ArrayType)
|
||||||
|
md = newMethodAndPrice(p.getBlockedAccounts, 1000000, smartcontract.NoneFlag)
|
||||||
|
p.AddMethod(md, desc, true)
|
||||||
|
|
||||||
|
desc = newDescriptor("setMaxBlockSize", smartcontract.BoolType,
|
||||||
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
||||||
|
md = newMethodAndPrice(p.setMaxBlockSize, 3000000, smartcontract.NoneFlag)
|
||||||
|
p.AddMethod(md, desc, false)
|
||||||
|
|
||||||
|
desc = newDescriptor("setMaxTransactionsPerBlock", smartcontract.BoolType,
|
||||||
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
||||||
|
md = newMethodAndPrice(p.setMaxTransactionsPerBlock, 3000000, smartcontract.NoneFlag)
|
||||||
|
p.AddMethod(md, desc, false)
|
||||||
|
|
||||||
|
desc = newDescriptor("setFeePerByte", smartcontract.BoolType,
|
||||||
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
||||||
|
md = newMethodAndPrice(p.setFeePerByte, 3000000, smartcontract.NoneFlag)
|
||||||
|
p.AddMethod(md, desc, false)
|
||||||
|
|
||||||
|
desc = newDescriptor("blockAccount", smartcontract.BoolType,
|
||||||
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
||||||
|
md = newMethodAndPrice(p.blockAccount, 3000000, smartcontract.NoneFlag)
|
||||||
|
p.AddMethod(md, desc, false)
|
||||||
|
|
||||||
|
desc = newDescriptor("unblockAccount", smartcontract.BoolType,
|
||||||
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
||||||
|
md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.NoneFlag)
|
||||||
|
p.AddMethod(md, desc, false)
|
||||||
|
|
||||||
|
desc = newDescriptor("onPersist", smartcontract.BoolType)
|
||||||
|
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||||
|
p.AddMethod(md, desc, false)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata implements Contract interface.
|
||||||
|
func (p *Policy) Metadata() *interop.ContractMD {
|
||||||
|
return &p.ContractMD
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initializes Policy native contract and implements Contract interface.
|
||||||
|
func (p *Policy) Initialize(ic *interop.Context) error {
|
||||||
|
si := &state.StorageItem{
|
||||||
|
Value: make([]byte, 4, 8),
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint32(si.Value, defaultMaxBlockSize)
|
||||||
|
err := ic.DAO.PutStorageItem(p.ContractID, maxBlockSizeKey, si)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(si.Value, defaultMaxTransactionsPerBlock)
|
||||||
|
err = ic.DAO.PutStorageItem(p.ContractID, maxTransactionsPerBlockKey, si)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
si.Value = si.Value[:8]
|
||||||
|
binary.LittleEndian.PutUint64(si.Value, defaultFeePerByte)
|
||||||
|
err = ic.DAO.PutStorageItem(p.ContractID, feePerByteKey, si)
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
|
p.feePerByte = defaultFeePerByte
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnPersist implements Contract interface.
|
||||||
|
func (p *Policy) OnPersist(ic *interop.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnPersistEnd updates cached Policy values if they've been changed
|
||||||
|
func (p *Policy) OnPersistEnd(dao dao.DAO) {
|
||||||
|
if p.isValid {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
maxTxPerBlock := p.getUint32WithKey(dao, maxTransactionsPerBlockKey)
|
||||||
|
p.maxTransactionsPerBlock = maxTxPerBlock
|
||||||
|
|
||||||
|
maxBlockSize := p.getUint32WithKey(dao, maxBlockSizeKey)
|
||||||
|
p.maxBlockSize = maxBlockSize
|
||||||
|
|
||||||
|
feePerByte := p.getInt64WithKey(dao, feePerByteKey)
|
||||||
|
p.feePerByte = feePerByte
|
||||||
|
|
||||||
|
p.isValid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMaxTransactionsPerBlock is Policy contract method and returns the upper
|
||||||
|
// limit of transactions per block.
|
||||||
|
func (p *Policy) getMaxTransactionsPerBlock(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(p.GetMaxTransactionsPerBlockInternal(ic.DAO))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxTransactionsPerBlockInternal returns the upper limit of transactions per
|
||||||
|
// block.
|
||||||
|
func (p *Policy) GetMaxTransactionsPerBlockInternal(dao dao.DAO) uint32 {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
if p.isValid {
|
||||||
|
return p.maxTransactionsPerBlock
|
||||||
|
}
|
||||||
|
return p.getUint32WithKey(dao, maxTransactionsPerBlockKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMaxBlockSize is Policy contract method and returns maximum block size.
|
||||||
|
func (p *Policy) getMaxBlockSize(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
if p.isValid {
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(p.maxBlockSize)))
|
||||||
|
}
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(p.getUint32WithKey(ic.DAO, maxBlockSizeKey))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFeePerByte is Policy contract method and returns required transaction's fee
|
||||||
|
// per byte.
|
||||||
|
func (p *Policy) getFeePerByte(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(p.GetFeePerByteInternal(ic.DAO)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeePerByteInternal returns required transaction's fee per byte.
|
||||||
|
func (p *Policy) GetFeePerByteInternal(dao dao.DAO) int64 {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
if p.isValid {
|
||||||
|
return p.feePerByte
|
||||||
|
}
|
||||||
|
return p.getInt64WithKey(dao, feePerByteKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockedAccountsInternal returns list of blocked accounts hashes.
|
||||||
|
func (p *Policy) GetBlockedAccountsInternal(dao dao.DAO) (BlockedAccounts, error) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMaxTransactionsPerBlock is Policy contract method and sets the upper limit
|
||||||
|
// of transactions per block.
|
||||||
|
func (p *Policy) setMaxTransactionsPerBlock(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
ok, err := p.checkValidators(ic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return stackitem.NewBool(false)
|
||||||
|
}
|
||||||
|
value := uint32(toBigInt(args[0]).Int64())
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
err = p.setUint32WithKey(ic.DAO, maxTransactionsPerBlockKey, value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
p.isValid = false
|
||||||
|
return stackitem.NewBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMaxBlockSize is Policy contract method and sets maximum block size.
|
||||||
|
func (p *Policy) setMaxBlockSize(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
ok, err := p.checkValidators(ic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return stackitem.NewBool(false)
|
||||||
|
}
|
||||||
|
value := uint32(toBigInt(args[0]).Int64())
|
||||||
|
if payload.MaxSize <= value {
|
||||||
|
return stackitem.NewBool(false)
|
||||||
|
}
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
err = p.setUint32WithKey(ic.DAO, maxBlockSizeKey, value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
p.isValid = false
|
||||||
|
return stackitem.NewBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setFeePerByte is Policy contract method and sets transaction's fee per byte.
|
||||||
|
func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
ok, err := p.checkValidators(ic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return stackitem.NewBool(false)
|
||||||
|
}
|
||||||
|
value := toBigInt(args[0]).Int64()
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
err = p.setInt64WithKey(ic.DAO, feePerByteKey, value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
p.isValid = false
|
||||||
|
return stackitem.NewBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockAccount is Policy contract method and adds given account hash to the list
|
||||||
|
// of blocked accounts.
|
||||||
|
func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
ok, err := p.checkValidators(ic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
return stackitem.NewBool(false)
|
||||||
|
}
|
||||||
|
if len(ba) > 1 {
|
||||||
|
copy(ba[indexToInsert+1:], ba[indexToInsert:])
|
||||||
|
ba[indexToInsert] = value
|
||||||
|
}
|
||||||
|
err = ic.DAO.PutStorageItem(p.ContractID, blockedAccountsKey, &state.StorageItem{
|
||||||
|
Value: ba.Bytes(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return stackitem.NewBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unblockAccount is Policy contract method and removes given account hash from
|
||||||
|
// the list of blocked accounts.
|
||||||
|
func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
ok, err := p.checkValidators(ic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
return stackitem.NewBool(false)
|
||||||
|
}
|
||||||
|
ba = append(ba[:indexToRemove], ba[indexToRemove+1:]...)
|
||||||
|
err = ic.DAO.PutStorageItem(p.ContractID, blockedAccountsKey, &state.StorageItem{
|
||||||
|
Value: ba.Bytes(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return stackitem.NewBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) getUint32WithKey(dao dao.DAO, key []byte) uint32 {
|
||||||
|
si := dao.GetStorageItem(p.ContractID, key)
|
||||||
|
if si == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return binary.LittleEndian.Uint32(si.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) setUint32WithKey(dao dao.DAO, key []byte, value uint32) error {
|
||||||
|
si := dao.GetStorageItem(p.ContractID, key)
|
||||||
|
binary.LittleEndian.PutUint32(si.Value, value)
|
||||||
|
err := dao.PutStorageItem(p.ContractID, key, si)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) getInt64WithKey(dao dao.DAO, key []byte) int64 {
|
||||||
|
si := dao.GetStorageItem(p.ContractID, key)
|
||||||
|
if si == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return int64(binary.LittleEndian.Uint64(si.Value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) setInt64WithKey(dao dao.DAO, key []byte, value int64) error {
|
||||||
|
si := dao.GetStorageItem(p.ContractID, key)
|
||||||
|
binary.LittleEndian.PutUint64(si.Value, uint64(value))
|
||||||
|
err := dao.PutStorageItem(p.ContractID, key, si)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) checkValidators(ic *interop.Context) (bool, error) {
|
||||||
|
prevBlock, err := ic.Chain.GetBlock(ic.Block.PrevHash)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return runtime.CheckHashedWitness(ic, nep5ScriptHash{
|
||||||
|
callingScriptHash: p.Hash,
|
||||||
|
entryScriptHash: p.Hash,
|
||||||
|
currentScriptHash: p.Hash,
|
||||||
|
}, prevBlock.NextConsensus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPolicy checks whether transaction's script hashes for verifying are
|
||||||
|
// included into blocked accounts list.
|
||||||
|
func (p *Policy) CheckPolicy(ic *interop.Context, tx *transaction.Transaction) (bool, error) {
|
||||||
|
ba, err := p.GetBlockedAccountsInternal(ic.DAO)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
scriptHashes, err := ic.Chain.GetScriptHashesForVerifying(tx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, acc := range ba {
|
||||||
|
for _, hash := range scriptHashes {
|
||||||
|
if acc.Equals(hash) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
260
pkg/core/native_policy_test.go
Normal file
260
pkg/core/native_policy_test.go
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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/encoding/bigint"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMaxTransactionsPerBlock(t *testing.T) {
|
||||||
|
chain := newTestChain(t)
|
||||||
|
defer chain.Close()
|
||||||
|
|
||||||
|
t.Run("get, internal method", func(t *testing.T) {
|
||||||
|
n := chain.contracts.Policy.GetMaxTransactionsPerBlockInternal(chain.dao)
|
||||||
|
require.Equal(t, 512, int(n))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get, contract method", func(t *testing.T) {
|
||||||
|
res, err := invokeNativePolicyMethod(chain, "getMaxTransactionsPerBlock")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.IntegerType,
|
||||||
|
Value: 512,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set", func(t *testing.T) {
|
||||||
|
res, err := invokeNativePolicyMethod(chain, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
n := chain.contracts.Policy.GetMaxTransactionsPerBlockInternal(chain.dao)
|
||||||
|
require.Equal(t, 1024, int(n))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxBlockSize(t *testing.T) {
|
||||||
|
chain := newTestChain(t)
|
||||||
|
defer chain.Close()
|
||||||
|
|
||||||
|
t.Run("get", func(t *testing.T) {
|
||||||
|
res, err := invokeNativePolicyMethod(chain, "getMaxBlockSize")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.IntegerType,
|
||||||
|
Value: 1024 * 256,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set", func(t *testing.T) {
|
||||||
|
res, err := invokeNativePolicyMethod(chain, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
res, err = invokeNativePolicyMethod(chain, "getMaxBlockSize")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.IntegerType,
|
||||||
|
Value: 102400,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFeePerByte(t *testing.T) {
|
||||||
|
chain := newTestChain(t)
|
||||||
|
defer chain.Close()
|
||||||
|
|
||||||
|
t.Run("get, internal method", func(t *testing.T) {
|
||||||
|
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao)
|
||||||
|
require.Equal(t, 1000, int(n))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get, contract method", func(t *testing.T) {
|
||||||
|
res, err := invokeNativePolicyMethod(chain, "getFeePerByte")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.IntegerType,
|
||||||
|
Value: 1000,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set", func(t *testing.T) {
|
||||||
|
res, err := invokeNativePolicyMethod(chain, "setFeePerByte", bigint.ToBytes(big.NewInt(1024)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao)
|
||||||
|
require.Equal(t, 1024, int(n))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockedAccounts(t *testing.T) {
|
||||||
|
chain := newTestChain(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("get, contract method", func(t *testing.T) {
|
||||||
|
res, err := invokeNativePolicyMethod(chain, "getBlockedAccounts")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{},
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("block-unblock account", func(t *testing.T) {
|
||||||
|
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
accounts, err := chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, native.BlockedAccounts{account}, accounts)
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
|
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
accounts, err = chain.contracts.Policy.GetBlockedAccountsInternal(chain.dao)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, native.BlockedAccounts{}, accounts)
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("double-block", func(t *testing.T) {
|
||||||
|
// block
|
||||||
|
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
|
// double-block should fail
|
||||||
|
res, err = invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: false,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
|
// unblock
|
||||||
|
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
|
// unblock the same account should fail as we don't have it blocked
|
||||||
|
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
|
||||||
|
checkResult(t, res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: 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, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: 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) {
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.AppCallWithOperationAndArgs(w.BinWriter, chain.contracts.Policy.Metadata().Hash, method, args...)
|
||||||
|
if w.Err != nil {
|
||||||
|
return nil, w.Err
|
||||||
|
}
|
||||||
|
script := w.Bytes()
|
||||||
|
tx := transaction.New(chain.GetConfig().Magic, script, 0)
|
||||||
|
validUntil := chain.blockHeight + 1
|
||||||
|
tx.ValidUntilBlock = validUntil
|
||||||
|
err := addSender(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addCosigners(tx)
|
||||||
|
err = signTx(chain, tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b := chain.newBlock(tx)
|
||||||
|
err = chain.AddBlock(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := chain.GetAppExecResult(tx.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkResult(t *testing.T, result *state.AppExecResult, expected smartcontract.Parameter) {
|
||||||
|
require.Equal(t, "HALT", result.VMState)
|
||||||
|
require.Equal(t, 1, len(result.Stack))
|
||||||
|
require.Equal(t, expected.Type, result.Stack[0].Type)
|
||||||
|
require.EqualValues(t, expected.Value, result.Stack[0].Value)
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
"github.com/pierrec/lz4"
|
"github.com/pierrec/lz4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,8 +18,8 @@ func compress(source []byte) ([]byte, error) {
|
||||||
// decompress decompresses bytes using lz4.
|
// decompress decompresses bytes using lz4.
|
||||||
func decompress(source []byte) ([]byte, error) {
|
func decompress(source []byte) ([]byte, error) {
|
||||||
maxSize := len(source) * 255
|
maxSize := len(source) * 255
|
||||||
if maxSize > PayloadMaxSize {
|
if maxSize > payload.MaxSize {
|
||||||
maxSize = PayloadMaxSize
|
maxSize = payload.MaxSize
|
||||||
}
|
}
|
||||||
dest := make([]byte, maxSize)
|
dest := make([]byte, maxSize)
|
||||||
size, err := lz4.UncompressBlock(source, dest)
|
size, err := lz4.UncompressBlock(source, dest)
|
||||||
|
|
|
@ -14,12 +14,8 @@ import (
|
||||||
|
|
||||||
//go:generate stringer -type=CommandType
|
//go:generate stringer -type=CommandType
|
||||||
|
|
||||||
const (
|
// CompressionMinSize is the lower bound to apply compression.
|
||||||
// PayloadMaxSize is maximum payload size in decompressed form.
|
const CompressionMinSize = 1024
|
||||||
PayloadMaxSize = 0x02000000
|
|
||||||
// CompressionMinSize is the lower bound to apply compression.
|
|
||||||
CompressionMinSize = 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
// Message is the complete message send between nodes.
|
// Message is the complete message send between nodes.
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
@ -114,7 +110,7 @@ func (m *Message) Decode(br *io.BinReader) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if l > PayloadMaxSize {
|
if l > payload.MaxSize {
|
||||||
return errors.New("invalid payload size")
|
return errors.New("invalid payload size")
|
||||||
}
|
}
|
||||||
m.compressedPayload = make([]byte, l)
|
m.compressedPayload = make([]byte, l)
|
||||||
|
|
|
@ -2,6 +2,9 @@ package payload
|
||||||
|
|
||||||
import "github.com/nspcc-dev/neo-go/pkg/io"
|
import "github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
|
||||||
|
// MaxSize is maximum payload size in decompressed form.
|
||||||
|
const MaxSize = 0x02000000
|
||||||
|
|
||||||
// Payload is anything that can be binary encoded/decoded.
|
// Payload is anything that can be binary encoded/decoded.
|
||||||
type Payload interface {
|
type Payload interface {
|
||||||
io.Serializable
|
io.Serializable
|
||||||
|
|
77
pkg/rpc/client/policy.go
Normal file
77
pkg/rpc/client/policy.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
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/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PolicyContractHash represents BE hash of native Policy contract.
|
||||||
|
var PolicyContractHash = util.Uint160{154, 97, 164, 110, 236, 151, 184, 147, 6, 215, 206, 129, 241, 91, 70, 32, 145, 208, 9, 50}
|
||||||
|
|
||||||
|
// GetMaxTransactionsPerBlock invokes `getMaxTransactionsPerBlock` method on a
|
||||||
|
// native Policy contract.
|
||||||
|
func (c *Client) GetMaxTransactionsPerBlock() (int64, error) {
|
||||||
|
return c.invokeNativePolicyMethod("getMaxTransactionsPerBlock")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxBlockSize invokes `getMaxBlockSize` method on a native Policy contract.
|
||||||
|
func (c *Client) GetMaxBlockSize() (int64, error) {
|
||||||
|
return c.invokeNativePolicyMethod("getMaxBlockSize")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeePerByte invokes `getFeePerByte` method on a native Policy contract.
|
||||||
|
func (c *Client) GetFeePerByte() (int64, error) {
|
||||||
|
return c.invokeNativePolicyMethod("getFeePerByte")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) {
|
||||||
|
result, err := c.InvokeFunction(PolicyContractHash.StringLE(), operation, []smartcontract.Parameter{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||||
|
return 0, errors.New("invalid VM state")
|
||||||
|
}
|
||||||
|
|
||||||
|
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.StringLE(), "getBlockedAccounts", []smartcontract.Parameter{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||||
|
return nil, errors.New("invalid VM state")
|
||||||
|
}
|
||||||
|
|
||||||
|
return topBlockedAccountsFromStack(result.Stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func topBlockedAccountsFromStack(st []smartcontract.Parameter) (native.BlockedAccounts, error) {
|
||||||
|
index := len(st) - 1 // top stack element is last in the array
|
||||||
|
var (
|
||||||
|
ba native.BlockedAccounts
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch typ := st[index].Type; typ {
|
||||||
|
case smartcontract.ArrayType:
|
||||||
|
data, ok := st[index].Value.([]smartcontract.Parameter)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid Array item")
|
||||||
|
}
|
||||||
|
ba = make(native.BlockedAccounts, len(data))
|
||||||
|
for i, account := range data {
|
||||||
|
ba[i], err = util.Uint160DecodeBytesLE(account.Value.([]byte))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid stack item type: %s", typ)
|
||||||
|
}
|
||||||
|
return ba, nil
|
||||||
|
}
|
|
@ -528,12 +528,10 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, acc *wallet.Account)
|
||||||
tx.NetworkFee += netFee
|
tx.NetworkFee += netFee
|
||||||
size += sizeDelta
|
size += sizeDelta
|
||||||
}
|
}
|
||||||
tx.NetworkFee += util.Fixed8(int64(size) * int64(c.GetFeePerByte()))
|
fee, err := c.GetFeePerByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx.NetworkFee += util.Fixed8(int64(size) * fee)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFeePerByte returns transaction network fee per byte
|
|
||||||
func (c *Client) GetFeePerByte() util.Fixed8 {
|
|
||||||
// TODO: make it a part of policy contract
|
|
||||||
return util.Fixed8(1000)
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"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/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/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
|
@ -318,6 +319,54 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"getFeePerByte": {
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
|
return c.GetFeePerByte()
|
||||||
|
},
|
||||||
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gas_consumed":"0.0200739","script":"10c00c0d676574466565506572427974650c149a61a46eec97b89306d7ce81f15b462091d0093241627d5b52","stack":[{"type":"Integer","value":"1000"}]}}`,
|
||||||
|
result: func(c *Client) interface{} {
|
||||||
|
return int64(1000)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"getMaxTransacctionsPerBlock": {
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
|
return c.GetMaxTransactionsPerBlock()
|
||||||
|
},
|
||||||
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gas_consumed":"0.0200739","script":"10c00c1a6765744d61785472616e73616374696f6e73506572426c6f636b0c149a61a46eec97b89306d7ce81f15b462091d0093241627d5b52","stack":[{"type":"Integer","value":"512"}]}}`,
|
||||||
|
result: func(c *Client) interface{} {
|
||||||
|
return int64(512)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"getMaxBlockSize": {
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
|
return c.GetMaxBlockSize()
|
||||||
|
},
|
||||||
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gas_consumed":"0.0200739","script":"10c00c0f6765744d6178426c6f636b53697a650c149a61a46eec97b89306d7ce81f15b462091d0093241627d5b52","stack":[{"type":"Integer","value":"262144"}]}}`,
|
||||||
|
result: func(c *Client) interface{} {
|
||||||
|
return int64(262144)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"getBlockedAccounts": {
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
|
return c.GetBlockedAccounts()
|
||||||
|
},
|
||||||
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gas_consumed":"0.0200739","script":"10c00c12676574426c6f636b65644163636f756e74730c149a61a46eec97b89306d7ce81f15b462091d0093241627d5b52","stack":[{"type":"Array","value":[]}]}}`,
|
||||||
|
result: func(c *Client) interface{} {
|
||||||
|
return native.BlockedAccounts{}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"getnep5balances": {
|
"getnep5balances": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
|
|
@ -57,7 +57,7 @@ type rawParameter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements Marshaler interface.
|
// MarshalJSON implements Marshaler interface.
|
||||||
func (p *Parameter) MarshalJSON() ([]byte, error) {
|
func (p Parameter) MarshalJSON() ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
resultRawValue json.RawMessage
|
resultRawValue json.RawMessage
|
||||||
resultErr error
|
resultErr error
|
||||||
|
@ -83,7 +83,11 @@ func (p *Parameter) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
case ArrayType:
|
case ArrayType:
|
||||||
var value = p.Value.([]Parameter)
|
var value = p.Value.([]Parameter)
|
||||||
resultRawValue, resultErr = json.Marshal(value)
|
if value == nil {
|
||||||
|
resultRawValue, resultErr = json.Marshal([]Parameter{})
|
||||||
|
} else {
|
||||||
|
resultRawValue, resultErr = json.Marshal(value)
|
||||||
|
}
|
||||||
case MapType:
|
case MapType:
|
||||||
ppair := p.Value.([]ParameterPair)
|
ppair := p.Value.([]ParameterPair)
|
||||||
resultRawValue, resultErr = json.Marshal(ppair)
|
resultRawValue, resultErr = json.Marshal(ppair)
|
||||||
|
|
|
@ -131,6 +131,13 @@ var marshalJSONTestCases = []struct {
|
||||||
},
|
},
|
||||||
result: `{"type":"InteropInterface","value":null}`,
|
result: `{"type":"InteropInterface","value":null}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: Parameter{
|
||||||
|
Type: ArrayType,
|
||||||
|
Value: []Parameter{},
|
||||||
|
},
|
||||||
|
result: `{"type":"Array","value":[]}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var marshalJSONErrorCases = []Parameter{
|
var marshalJSONErrorCases = []Parameter{
|
||||||
|
@ -146,7 +153,7 @@ var marshalJSONErrorCases = []Parameter{
|
||||||
|
|
||||||
func TestParam_MarshalJSON(t *testing.T) {
|
func TestParam_MarshalJSON(t *testing.T) {
|
||||||
for _, tc := range marshalJSONTestCases {
|
for _, tc := range marshalJSONTestCases {
|
||||||
res, err := json.Marshal(&tc.input)
|
res, err := json.Marshal(tc.input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
var actual, expected Parameter
|
var actual, expected Parameter
|
||||||
assert.NoError(t, json.Unmarshal(res, &actual))
|
assert.NoError(t, json.Unmarshal(res, &actual))
|
||||||
|
|
Loading…
Reference in a new issue