forked from TrueCloudLab/neoneo-go
394 lines
12 KiB
Go
394 lines
12 KiB
Go
|
package native
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"math/big"
|
||
|
"sort"
|
||
|
|
||
|
"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
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// OnPersist implements Contract interface.
|
||
|
func (p *Policy) OnPersist(ic *interop.Context) error {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
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 {
|
||
|
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 {
|
||
|
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())
|
||
|
err = p.setUint32WithKey(ic.DAO, maxTransactionsPerBlockKey, value)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
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)
|
||
|
}
|
||
|
err = p.setUint32WithKey(ic.DAO, maxBlockSizeKey, value)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
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()
|
||
|
err = p.setInt64WithKey(ic.DAO, feePerByteKey, value)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
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
|
||
|
}
|