github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/policy.go (about)

     1  package native
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"math/big"
     7  	"sort"
     8  
     9  	"github.com/nspcc-dev/neo-go/pkg/config"
    10  	"github.com/nspcc-dev/neo-go/pkg/core/dao"
    11  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/storage"
    15  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    16  	"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
    17  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    18  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    19  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    20  	"github.com/nspcc-dev/neo-go/pkg/util"
    21  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    22  )
    23  
    24  const (
    25  	policyContractID = -7
    26  
    27  	defaultExecFeeFactor      = interop.DefaultBaseExecFee
    28  	defaultFeePerByte         = 1000
    29  	defaultMaxVerificationGas = 1_50000000
    30  	// defaultAttributeFee is a default fee for a transaction attribute those price wasn't set yet.
    31  	defaultAttributeFee = 0
    32  	// defaultNotaryAssistedFee is a default fee for a NotaryAssisted transaction attribute per key.
    33  	defaultNotaryAssistedFee = 1000_0000 // 0.1 GAS
    34  	// DefaultStoragePrice is the price to pay for 1 byte of storage.
    35  	DefaultStoragePrice = 100000
    36  
    37  	// maxExecFeeFactor is the maximum allowed execution fee factor.
    38  	maxExecFeeFactor = 100
    39  	// maxFeePerByte is the maximum allowed fee per byte value.
    40  	maxFeePerByte = 100_000_000
    41  	// maxStoragePrice is the maximum allowed price for a byte of storage.
    42  	maxStoragePrice = 10000000
    43  	// maxAttributeFee is the maximum allowed value for a transaction attribute fee.
    44  	maxAttributeFee = 10_00000000
    45  
    46  	// blockedAccountPrefix is a prefix used to store blocked account.
    47  	blockedAccountPrefix = 15
    48  	// attributeFeePrefix is a prefix used to store attribute fee.
    49  	attributeFeePrefix = 20
    50  )
    51  
    52  var (
    53  	// execFeeFactorKey is a key used to store execution fee factor.
    54  	execFeeFactorKey = []byte{18}
    55  	// feePerByteKey is a key used to store the minimum fee per byte for
    56  	// transaction.
    57  	feePerByteKey = []byte{10}
    58  	// storagePriceKey is a key used to store storage price.
    59  	storagePriceKey = []byte{19}
    60  )
    61  
    62  // Policy represents Policy native contract.
    63  type Policy struct {
    64  	interop.ContractMD
    65  	NEO *NEO
    66  
    67  	// p2pSigExtensionsEnabled defines whether the P2P signature extensions logic is relevant.
    68  	p2pSigExtensionsEnabled bool
    69  }
    70  
    71  type PolicyCache struct {
    72  	execFeeFactor      uint32
    73  	feePerByte         int64
    74  	maxVerificationGas int64
    75  	storagePrice       uint32
    76  	attributeFee       map[transaction.AttrType]uint32
    77  	blockedAccounts    []util.Uint160
    78  }
    79  
    80  var (
    81  	_ interop.Contract        = (*Policy)(nil)
    82  	_ dao.NativeContractCache = (*PolicyCache)(nil)
    83  )
    84  
    85  // Copy implements NativeContractCache interface.
    86  func (c *PolicyCache) Copy() dao.NativeContractCache {
    87  	cp := &PolicyCache{}
    88  	copyPolicyCache(c, cp)
    89  	return cp
    90  }
    91  
    92  func copyPolicyCache(src, dst *PolicyCache) {
    93  	*dst = *src
    94  	dst.attributeFee = make(map[transaction.AttrType]uint32, len(src.attributeFee))
    95  	for t, v := range src.attributeFee {
    96  		dst.attributeFee[t] = v
    97  	}
    98  	dst.blockedAccounts = make([]util.Uint160, len(src.blockedAccounts))
    99  	copy(dst.blockedAccounts, src.blockedAccounts)
   100  }
   101  
   102  // newPolicy returns Policy native contract.
   103  func newPolicy(p2pSigExtensionsEnabled bool) *Policy {
   104  	p := &Policy{
   105  		ContractMD:              *interop.NewContractMD(nativenames.Policy, policyContractID),
   106  		p2pSigExtensionsEnabled: p2pSigExtensionsEnabled,
   107  	}
   108  	defer p.BuildHFSpecificMD(p.ActiveIn())
   109  
   110  	desc := newDescriptor("getFeePerByte", smartcontract.IntegerType)
   111  	md := newMethodAndPrice(p.getFeePerByte, 1<<15, callflag.ReadStates)
   112  	p.AddMethod(md, desc)
   113  
   114  	desc = newDescriptor("isBlocked", smartcontract.BoolType,
   115  		manifest.NewParameter("account", smartcontract.Hash160Type))
   116  	md = newMethodAndPrice(p.isBlocked, 1<<15, callflag.ReadStates)
   117  	p.AddMethod(md, desc)
   118  
   119  	desc = newDescriptor("getExecFeeFactor", smartcontract.IntegerType)
   120  	md = newMethodAndPrice(p.getExecFeeFactor, 1<<15, callflag.ReadStates)
   121  	p.AddMethod(md, desc)
   122  
   123  	desc = newDescriptor("setExecFeeFactor", smartcontract.VoidType,
   124  		manifest.NewParameter("value", smartcontract.IntegerType))
   125  	md = newMethodAndPrice(p.setExecFeeFactor, 1<<15, callflag.States)
   126  	p.AddMethod(md, desc)
   127  
   128  	desc = newDescriptor("getStoragePrice", smartcontract.IntegerType)
   129  	md = newMethodAndPrice(p.getStoragePrice, 1<<15, callflag.ReadStates)
   130  	p.AddMethod(md, desc)
   131  
   132  	desc = newDescriptor("setStoragePrice", smartcontract.VoidType,
   133  		manifest.NewParameter("value", smartcontract.IntegerType))
   134  	md = newMethodAndPrice(p.setStoragePrice, 1<<15, callflag.States)
   135  	p.AddMethod(md, desc)
   136  
   137  	desc = newDescriptor("getAttributeFee", smartcontract.IntegerType,
   138  		manifest.NewParameter("attributeType", smartcontract.IntegerType))
   139  	md = newMethodAndPrice(p.getAttributeFee, 1<<15, callflag.ReadStates)
   140  	p.AddMethod(md, desc)
   141  
   142  	desc = newDescriptor("setAttributeFee", smartcontract.VoidType,
   143  		manifest.NewParameter("attributeType", smartcontract.IntegerType),
   144  		manifest.NewParameter("value", smartcontract.IntegerType))
   145  	md = newMethodAndPrice(p.setAttributeFee, 1<<15, callflag.States)
   146  	p.AddMethod(md, desc)
   147  
   148  	desc = newDescriptor("setFeePerByte", smartcontract.VoidType,
   149  		manifest.NewParameter("value", smartcontract.IntegerType))
   150  	md = newMethodAndPrice(p.setFeePerByte, 1<<15, callflag.States)
   151  	p.AddMethod(md, desc)
   152  
   153  	desc = newDescriptor("blockAccount", smartcontract.BoolType,
   154  		manifest.NewParameter("account", smartcontract.Hash160Type))
   155  	md = newMethodAndPrice(p.blockAccount, 1<<15, callflag.States)
   156  	p.AddMethod(md, desc)
   157  
   158  	desc = newDescriptor("unblockAccount", smartcontract.BoolType,
   159  		manifest.NewParameter("account", smartcontract.Hash160Type))
   160  	md = newMethodAndPrice(p.unblockAccount, 1<<15, callflag.States)
   161  	p.AddMethod(md, desc)
   162  
   163  	return p
   164  }
   165  
   166  // Metadata implements the Contract interface.
   167  func (p *Policy) Metadata() *interop.ContractMD {
   168  	return &p.ContractMD
   169  }
   170  
   171  // Initialize initializes Policy native contract and implements the Contract interface.
   172  func (p *Policy) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
   173  	if hf != p.ActiveIn() {
   174  		return nil
   175  	}
   176  
   177  	setIntWithKey(p.ID, ic.DAO, feePerByteKey, defaultFeePerByte)
   178  	setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor)
   179  	setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice)
   180  
   181  	cache := &PolicyCache{
   182  		execFeeFactor:      defaultExecFeeFactor,
   183  		feePerByte:         defaultFeePerByte,
   184  		maxVerificationGas: defaultMaxVerificationGas,
   185  		storagePrice:       DefaultStoragePrice,
   186  		attributeFee:       map[transaction.AttrType]uint32{},
   187  		blockedAccounts:    make([]util.Uint160, 0),
   188  	}
   189  	if p.p2pSigExtensionsEnabled {
   190  		setIntWithKey(p.ID, ic.DAO, []byte{attributeFeePrefix, byte(transaction.NotaryAssistedT)}, defaultNotaryAssistedFee)
   191  		cache.attributeFee[transaction.NotaryAssistedT] = defaultNotaryAssistedFee
   192  	}
   193  	ic.DAO.SetCache(p.ID, cache)
   194  
   195  	return nil
   196  }
   197  
   198  func (p *Policy) InitializeCache(blockHeight uint32, d *dao.Simple) error {
   199  	cache := &PolicyCache{}
   200  	err := p.fillCacheFromDAO(cache, d)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	d.SetCache(p.ID, cache)
   205  	return nil
   206  }
   207  
   208  func (p *Policy) fillCacheFromDAO(cache *PolicyCache, d *dao.Simple) error {
   209  	cache.execFeeFactor = uint32(getIntWithKey(p.ID, d, execFeeFactorKey))
   210  	cache.feePerByte = getIntWithKey(p.ID, d, feePerByteKey)
   211  	cache.maxVerificationGas = defaultMaxVerificationGas
   212  	cache.storagePrice = uint32(getIntWithKey(p.ID, d, storagePriceKey))
   213  
   214  	cache.blockedAccounts = make([]util.Uint160, 0)
   215  	var fErr error
   216  	d.Seek(p.ID, storage.SeekRange{Prefix: []byte{blockedAccountPrefix}}, func(k, _ []byte) bool {
   217  		hash, err := util.Uint160DecodeBytesBE(k)
   218  		if err != nil {
   219  			fErr = fmt.Errorf("failed to decode blocked account hash: %w", err)
   220  			return false
   221  		}
   222  		cache.blockedAccounts = append(cache.blockedAccounts, hash)
   223  		return true
   224  	})
   225  	if fErr != nil {
   226  		return fmt.Errorf("failed to initialize blocked accounts: %w", fErr)
   227  	}
   228  
   229  	cache.attributeFee = make(map[transaction.AttrType]uint32)
   230  	d.Seek(p.ID, storage.SeekRange{Prefix: []byte{attributeFeePrefix}}, func(k, v []byte) bool {
   231  		if len(k) != 1 {
   232  			fErr = fmt.Errorf("unexpected attribute type len %d (%s)", len(k), hex.EncodeToString(k))
   233  			return false
   234  		}
   235  		t := transaction.AttrType(k[0])
   236  		value := bigint.FromBytes(v)
   237  		if value == nil {
   238  			fErr = fmt.Errorf("unexpected attribute value format: key=%s, value=%s", hex.EncodeToString(k), hex.EncodeToString(v))
   239  			return false
   240  		}
   241  		cache.attributeFee[t] = uint32(value.Int64())
   242  		return true
   243  	})
   244  	if fErr != nil {
   245  		return fmt.Errorf("failed to initialize attribute fees: %w", fErr)
   246  	}
   247  	return nil
   248  }
   249  
   250  // OnPersist implements Contract interface.
   251  func (p *Policy) OnPersist(ic *interop.Context) error {
   252  	return nil
   253  }
   254  
   255  // PostPersist implements Contract interface.
   256  func (p *Policy) PostPersist(ic *interop.Context) error {
   257  	return nil
   258  }
   259  
   260  // ActiveIn implements the Contract interface.
   261  func (p *Policy) ActiveIn() *config.Hardfork {
   262  	return nil
   263  }
   264  
   265  // getFeePerByte is a Policy contract method that returns the required transaction's fee
   266  // per byte.
   267  func (p *Policy) getFeePerByte(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
   268  	return stackitem.NewBigInteger(big.NewInt(p.GetFeePerByteInternal(ic.DAO)))
   269  }
   270  
   271  // GetFeePerByteInternal returns required transaction's fee per byte.
   272  func (p *Policy) GetFeePerByteInternal(dao *dao.Simple) int64 {
   273  	cache := dao.GetROCache(p.ID).(*PolicyCache)
   274  	return cache.feePerByte
   275  }
   276  
   277  // GetMaxVerificationGas returns the maximum gas allowed to be burned during verification.
   278  func (p *Policy) GetMaxVerificationGas(dao *dao.Simple) int64 {
   279  	cache := dao.GetROCache(p.ID).(*PolicyCache)
   280  	return cache.maxVerificationGas
   281  }
   282  
   283  func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
   284  	return stackitem.NewBigInteger(big.NewInt(int64(p.GetExecFeeFactorInternal(ic.DAO))))
   285  }
   286  
   287  // GetExecFeeFactorInternal returns current execution fee factor.
   288  func (p *Policy) GetExecFeeFactorInternal(d *dao.Simple) int64 {
   289  	cache := d.GetROCache(p.ID).(*PolicyCache)
   290  	return int64(cache.execFeeFactor)
   291  }
   292  
   293  func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   294  	value := toUint32(args[0])
   295  	if value <= 0 || maxExecFeeFactor < value {
   296  		panic(fmt.Errorf("ExecFeeFactor must be between 1 and %d", maxExecFeeFactor))
   297  	}
   298  	if !p.NEO.checkCommittee(ic) {
   299  		panic("invalid committee signature")
   300  	}
   301  	setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(value))
   302  	cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
   303  	cache.execFeeFactor = value
   304  	return stackitem.Null{}
   305  }
   306  
   307  // isBlocked is Policy contract method that checks whether provided account is blocked.
   308  func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   309  	hash := toUint160(args[0])
   310  	_, blocked := p.isBlockedInternal(ic.DAO.GetROCache(p.ID).(*PolicyCache), hash)
   311  	return stackitem.NewBool(blocked)
   312  }
   313  
   314  // IsBlocked checks whether provided account is blocked. Normally it uses Policy
   315  // cache, falling back to the DB queries when Policy cache is not available yet
   316  // (the only case is native cache initialization pipeline, where native Neo cache
   317  // is being initialized before the Policy's one).
   318  func (p *Policy) IsBlocked(dao *dao.Simple, hash util.Uint160) bool {
   319  	cache := dao.GetROCache(p.ID)
   320  	if cache == nil {
   321  		key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
   322  		return dao.GetStorageItem(p.ID, key) != nil
   323  	}
   324  	_, isBlocked := p.isBlockedInternal(cache.(*PolicyCache), hash)
   325  	return isBlocked
   326  }
   327  
   328  // isBlockedInternal checks whether provided account is blocked. It returns position
   329  // of the blocked account in the blocked accounts list (or the position it should be
   330  // put at).
   331  func (p *Policy) isBlockedInternal(roCache *PolicyCache, hash util.Uint160) (int, bool) {
   332  	length := len(roCache.blockedAccounts)
   333  	i := sort.Search(length, func(i int) bool {
   334  		return !roCache.blockedAccounts[i].Less(hash)
   335  	})
   336  	if length != 0 && i != length && roCache.blockedAccounts[i].Equals(hash) {
   337  		return i, true
   338  	}
   339  	return i, false
   340  }
   341  
   342  func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
   343  	return stackitem.NewBigInteger(big.NewInt(p.GetStoragePriceInternal(ic.DAO)))
   344  }
   345  
   346  // GetStoragePriceInternal returns current execution fee factor.
   347  func (p *Policy) GetStoragePriceInternal(d *dao.Simple) int64 {
   348  	cache := d.GetROCache(p.ID).(*PolicyCache)
   349  	return int64(cache.storagePrice)
   350  }
   351  
   352  func (p *Policy) setStoragePrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   353  	value := toUint32(args[0])
   354  	if value <= 0 || maxStoragePrice < value {
   355  		panic(fmt.Errorf("StoragePrice must be between 1 and %d", maxStoragePrice))
   356  	}
   357  	if !p.NEO.checkCommittee(ic) {
   358  		panic("invalid committee signature")
   359  	}
   360  	setIntWithKey(p.ID, ic.DAO, storagePriceKey, int64(value))
   361  	cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
   362  	cache.storagePrice = value
   363  	return stackitem.Null{}
   364  }
   365  
   366  func (p *Policy) getAttributeFee(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   367  	t := transaction.AttrType(toUint8(args[0]))
   368  	if !transaction.IsValidAttrType(ic.Chain.GetConfig().ReservedAttributes, t) {
   369  		panic(fmt.Errorf("invalid attribute type: %d", t))
   370  	}
   371  	return stackitem.NewBigInteger(big.NewInt(p.GetAttributeFeeInternal(ic.DAO, t)))
   372  }
   373  
   374  // GetAttributeFeeInternal returns required transaction's attribute fee.
   375  func (p *Policy) GetAttributeFeeInternal(d *dao.Simple, t transaction.AttrType) int64 {
   376  	cache := d.GetROCache(p.ID).(*PolicyCache)
   377  	v, ok := cache.attributeFee[t]
   378  	if !ok {
   379  		// We may safely omit this part, but let it be here in case if defaultAttributeFee value is changed.
   380  		v = defaultAttributeFee
   381  	}
   382  	return int64(v)
   383  }
   384  
   385  func (p *Policy) setAttributeFee(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   386  	t := transaction.AttrType(toUint8(args[0]))
   387  	value := toUint32(args[1])
   388  	if !transaction.IsValidAttrType(ic.Chain.GetConfig().ReservedAttributes, t) {
   389  		panic(fmt.Errorf("invalid attribute type: %d", t))
   390  	}
   391  	if value > maxAttributeFee {
   392  		panic(fmt.Errorf("attribute value is out of range: %d", value))
   393  	}
   394  	if !p.NEO.checkCommittee(ic) {
   395  		panic("invalid committee signature")
   396  	}
   397  	setIntWithKey(p.ID, ic.DAO, []byte{attributeFeePrefix, byte(t)}, int64(value))
   398  	cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
   399  	cache.attributeFee[t] = value
   400  	return stackitem.Null{}
   401  }
   402  
   403  // setFeePerByte is a Policy contract method that sets transaction's fee per byte.
   404  func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   405  	value := toBigInt(args[0]).Int64()
   406  	if value < 0 || value > maxFeePerByte {
   407  		panic(fmt.Errorf("FeePerByte shouldn't be negative or greater than %d", maxFeePerByte))
   408  	}
   409  	if !p.NEO.checkCommittee(ic) {
   410  		panic("invalid committee signature")
   411  	}
   412  	setIntWithKey(p.ID, ic.DAO, feePerByteKey, value)
   413  	cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
   414  	cache.feePerByte = value
   415  	return stackitem.Null{}
   416  }
   417  
   418  // blockAccount is a Policy contract method that adds the given account hash to the list
   419  // of blocked accounts.
   420  func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   421  	if !p.NEO.checkCommittee(ic) {
   422  		panic("invalid committee signature")
   423  	}
   424  	hash := toUint160(args[0])
   425  	for i := range ic.Natives {
   426  		if ic.Natives[i].Metadata().Hash == hash {
   427  			panic("cannot block native contract")
   428  		}
   429  	}
   430  	return stackitem.NewBool(p.blockAccountInternal(ic.DAO, hash))
   431  }
   432  func (p *Policy) blockAccountInternal(d *dao.Simple, hash util.Uint160) bool {
   433  	i, blocked := p.isBlockedInternal(d.GetROCache(p.ID).(*PolicyCache), hash)
   434  	if blocked {
   435  		return false
   436  	}
   437  	key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
   438  	d.PutStorageItem(p.ID, key, state.StorageItem{})
   439  	cache := d.GetRWCache(p.ID).(*PolicyCache)
   440  	if len(cache.blockedAccounts) == i {
   441  		cache.blockedAccounts = append(cache.blockedAccounts, hash)
   442  	} else {
   443  		cache.blockedAccounts = append(cache.blockedAccounts[:i+1], cache.blockedAccounts[i:]...)
   444  		cache.blockedAccounts[i] = hash
   445  	}
   446  	return true
   447  }
   448  
   449  // unblockAccount is a Policy contract method that removes the given account hash from
   450  // the list of blocked accounts.
   451  func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   452  	if !p.NEO.checkCommittee(ic) {
   453  		panic("invalid committee signature")
   454  	}
   455  	hash := toUint160(args[0])
   456  	i, blocked := p.isBlockedInternal(ic.DAO.GetROCache(p.ID).(*PolicyCache), hash)
   457  	if !blocked {
   458  		return stackitem.NewBool(false)
   459  	}
   460  	key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
   461  	ic.DAO.DeleteStorageItem(p.ID, key)
   462  	cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
   463  	cache.blockedAccounts = append(cache.blockedAccounts[:i], cache.blockedAccounts[i+1:]...)
   464  	return stackitem.NewBool(true)
   465  }
   466  
   467  // CheckPolicy checks whether a transaction conforms to the current policy restrictions,
   468  // like not being signed by a blocked account or not exceeding the block-level system
   469  // fee limit.
   470  func (p *Policy) CheckPolicy(d *dao.Simple, tx *transaction.Transaction) error {
   471  	cache := d.GetROCache(p.ID).(*PolicyCache)
   472  	for _, signer := range tx.Signers {
   473  		if _, isBlocked := p.isBlockedInternal(cache, signer.Account); isBlocked {
   474  			return fmt.Errorf("account %s is blocked", signer.Account.StringLE())
   475  		}
   476  	}
   477  	return nil
   478  }