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 }