github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/notary.go (about) 1 package native 2 3 import ( 4 "errors" 5 "fmt" 6 "math" 7 "math/big" 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/interop/contract" 13 "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" 14 "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" 15 "github.com/nspcc-dev/neo-go/pkg/core/native/nativeprices" 16 "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" 17 "github.com/nspcc-dev/neo-go/pkg/core/state" 18 "github.com/nspcc-dev/neo-go/pkg/core/storage" 19 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 20 "github.com/nspcc-dev/neo-go/pkg/crypto/hash" 21 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 22 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 23 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 24 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 25 "github.com/nspcc-dev/neo-go/pkg/util" 26 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 27 ) 28 29 // Notary represents Notary native contract. 30 type Notary struct { 31 interop.ContractMD 32 GAS *GAS 33 NEO *NEO 34 Desig *Designate 35 Policy *Policy 36 } 37 38 type NotaryCache struct { 39 maxNotValidBeforeDelta uint32 40 } 41 42 // NotaryService is a Notary module interface. 43 type NotaryService interface { 44 UpdateNotaryNodes(pubs keys.PublicKeys) 45 } 46 47 const ( 48 notaryContractID = -10 49 // prefixDeposit is a prefix for storing Notary deposits. 50 prefixDeposit = 1 51 defaultDepositDeltaTill = 5760 52 defaultMaxNotValidBeforeDelta = 140 // 20 rounds for 7 validators, a little more than half an hour 53 ) 54 55 var maxNotValidBeforeDeltaKey = []byte{10} 56 57 var ( 58 _ interop.Contract = (*Notary)(nil) 59 _ dao.NativeContractCache = (*NotaryCache)(nil) 60 ) 61 62 // Copy implements NativeContractCache interface. 63 func (c *NotaryCache) Copy() dao.NativeContractCache { 64 cp := &NotaryCache{} 65 copyNotaryCache(c, cp) 66 return cp 67 } 68 69 func copyNotaryCache(src, dst *NotaryCache) { 70 *dst = *src 71 } 72 73 // newNotary returns Notary native contract. 74 func newNotary() *Notary { 75 n := &Notary{ContractMD: *interop.NewContractMD(nativenames.Notary, notaryContractID)} 76 defer n.BuildHFSpecificMD(n.ActiveIn()) 77 78 desc := newDescriptor("onNEP17Payment", smartcontract.VoidType, 79 manifest.NewParameter("from", smartcontract.Hash160Type), 80 manifest.NewParameter("amount", smartcontract.IntegerType), 81 manifest.NewParameter("data", smartcontract.AnyType)) 82 md := newMethodAndPrice(n.onPayment, 1<<15, callflag.States) 83 n.AddMethod(md, desc) 84 85 desc = newDescriptor("lockDepositUntil", smartcontract.BoolType, 86 manifest.NewParameter("address", smartcontract.Hash160Type), 87 manifest.NewParameter("till", smartcontract.IntegerType)) 88 md = newMethodAndPrice(n.lockDepositUntil, 1<<15, callflag.States) 89 n.AddMethod(md, desc) 90 91 desc = newDescriptor("withdraw", smartcontract.BoolType, 92 manifest.NewParameter("from", smartcontract.Hash160Type), 93 manifest.NewParameter("to", smartcontract.Hash160Type)) 94 md = newMethodAndPrice(n.withdraw, 1<<15, callflag.All) 95 n.AddMethod(md, desc) 96 97 desc = newDescriptor("balanceOf", smartcontract.IntegerType, 98 manifest.NewParameter("addr", smartcontract.Hash160Type)) 99 md = newMethodAndPrice(n.balanceOf, 1<<15, callflag.ReadStates) 100 n.AddMethod(md, desc) 101 102 desc = newDescriptor("expirationOf", smartcontract.IntegerType, 103 manifest.NewParameter("addr", smartcontract.Hash160Type)) 104 md = newMethodAndPrice(n.expirationOf, 1<<15, callflag.ReadStates) 105 n.AddMethod(md, desc) 106 107 desc = newDescriptor("verify", smartcontract.BoolType, 108 manifest.NewParameter("signature", smartcontract.SignatureType)) 109 md = newMethodAndPrice(n.verify, nativeprices.NotaryVerificationPrice, callflag.ReadStates) 110 n.AddMethod(md, desc) 111 112 desc = newDescriptor("getMaxNotValidBeforeDelta", smartcontract.IntegerType) 113 md = newMethodAndPrice(n.getMaxNotValidBeforeDelta, 1<<15, callflag.ReadStates) 114 n.AddMethod(md, desc) 115 116 desc = newDescriptor("setMaxNotValidBeforeDelta", smartcontract.VoidType, 117 manifest.NewParameter("value", smartcontract.IntegerType)) 118 md = newMethodAndPrice(n.setMaxNotValidBeforeDelta, 1<<15, callflag.States) 119 n.AddMethod(md, desc) 120 121 return n 122 } 123 124 // Metadata implements the Contract interface. 125 func (n *Notary) Metadata() *interop.ContractMD { 126 return &n.ContractMD 127 } 128 129 // Initialize initializes Notary native contract and implements the Contract interface. 130 func (n *Notary) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { 131 if hf != n.ActiveIn() { 132 return nil 133 } 134 135 setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta) 136 137 cache := &NotaryCache{ 138 maxNotValidBeforeDelta: defaultMaxNotValidBeforeDelta, 139 } 140 ic.DAO.SetCache(n.ID, cache) 141 return nil 142 } 143 144 func (n *Notary) InitializeCache(blockHeight uint32, d *dao.Simple) error { 145 cache := &NotaryCache{ 146 maxNotValidBeforeDelta: uint32(getIntWithKey(n.ID, d, maxNotValidBeforeDeltaKey)), 147 } 148 149 d.SetCache(n.ID, cache) 150 return nil 151 } 152 153 // OnPersist implements the Contract interface. 154 func (n *Notary) OnPersist(ic *interop.Context) error { 155 var ( 156 nFees int64 157 notaries keys.PublicKeys 158 err error 159 ) 160 for _, tx := range ic.Block.Transactions { 161 if tx.HasAttribute(transaction.NotaryAssistedT) { 162 if notaries == nil { 163 notaries, err = n.GetNotaryNodes(ic.DAO) 164 if err != nil { 165 return fmt.Errorf("failed to get notary nodes: %w", err) 166 } 167 } 168 nKeys := tx.GetAttributes(transaction.NotaryAssistedT)[0].Value.(*transaction.NotaryAssisted).NKeys 169 nFees += int64(nKeys) + 1 170 if tx.Sender() == n.Hash { 171 payer := tx.Signers[1] 172 balance := n.GetDepositFor(ic.DAO, payer.Account) 173 balance.Amount.Sub(balance.Amount, big.NewInt(tx.SystemFee+tx.NetworkFee)) 174 if balance.Amount.Sign() == 0 { 175 n.removeDepositFor(ic.DAO, payer.Account) 176 } else { 177 err := n.putDepositFor(ic.DAO, balance, payer.Account) 178 if err != nil { 179 return fmt.Errorf("failed to update deposit for %s: %w", payer.Account.StringBE(), err) 180 } 181 } 182 } 183 } 184 } 185 if nFees == 0 { 186 return nil 187 } 188 feePerKey := n.Policy.GetAttributeFeeInternal(ic.DAO, transaction.NotaryAssistedT) 189 singleReward := calculateNotaryReward(nFees, feePerKey, len(notaries)) 190 for _, notary := range notaries { 191 n.GAS.mint(ic, notary.GetScriptHash(), singleReward, false) 192 } 193 return nil 194 } 195 196 // PostPersist implements the Contract interface. 197 func (n *Notary) PostPersist(ic *interop.Context) error { 198 return nil 199 } 200 201 // ActiveIn implements the Contract interface. 202 func (n *Notary) ActiveIn() *config.Hardfork { 203 return nil 204 } 205 206 // onPayment records the deposited amount as belonging to "from" address with a lock 207 // till the specified chain's height. 208 func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item { 209 if h := ic.VM.GetCallingScriptHash(); h != n.GAS.Hash { 210 panic(fmt.Errorf("only GAS can be accepted for deposit, got %s", h.StringBE())) 211 } 212 from := toUint160(args[0]) 213 to := from 214 amount := toBigInt(args[1]) 215 data, ok := args[2].(*stackitem.Array) 216 if !ok || len(data.Value().([]stackitem.Item)) != 2 { 217 panic(errors.New("`data` parameter should be an array of 2 elements")) 218 } 219 additionalParams := data.Value().([]stackitem.Item) 220 if !additionalParams[0].Equals(stackitem.Null{}) { 221 to = toUint160(additionalParams[0]) 222 } 223 224 allowedChangeTill := ic.Tx.Sender() == to 225 currentHeight := ic.BlockHeight() 226 deposit := n.GetDepositFor(ic.DAO, to) 227 till := toUint32(additionalParams[1]) 228 if till < currentHeight+2 { 229 panic(fmt.Errorf("`till` shouldn't be less than the chain's height + 1 (%d at min)", currentHeight+2)) 230 } 231 if deposit != nil && till < deposit.Till { 232 panic(fmt.Errorf("`till` shouldn't be less than the previous value %d", deposit.Till)) 233 } 234 feePerKey := n.Policy.GetAttributeFeeInternal(ic.DAO, transaction.NotaryAssistedT) 235 if deposit == nil { 236 if amount.Cmp(big.NewInt(2*feePerKey)) < 0 { 237 panic(fmt.Errorf("first deposit can not be less than %d, got %d", 2*feePerKey, amount.Int64())) 238 } 239 deposit = &state.Deposit{ 240 Amount: new(big.Int), 241 } 242 if !allowedChangeTill { 243 till = currentHeight + defaultDepositDeltaTill 244 } 245 } else if !allowedChangeTill { // only deposit's owner is allowed to set or update `till` 246 till = deposit.Till 247 } 248 deposit.Amount.Add(deposit.Amount, amount) 249 deposit.Till = till 250 251 if err := n.putDepositFor(ic.DAO, deposit, to); err != nil { 252 panic(fmt.Errorf("failed to put deposit for %s into the storage: %w", from.StringBE(), err)) 253 } 254 return stackitem.Null{} 255 } 256 257 // lockDepositUntil updates the chain's height until which the deposit is locked. 258 func (n *Notary) lockDepositUntil(ic *interop.Context, args []stackitem.Item) stackitem.Item { 259 addr := toUint160(args[0]) 260 ok, err := runtime.CheckHashedWitness(ic, addr) 261 if err != nil { 262 panic(fmt.Errorf("failed to check witness for %s: %w", addr.StringBE(), err)) 263 } 264 if !ok { 265 return stackitem.NewBool(false) 266 } 267 till := toUint32(args[1]) 268 if till < (ic.BlockHeight() + 1 + 1) { // deposit can't expire at the current persisting block. 269 return stackitem.NewBool(false) 270 } 271 deposit := n.GetDepositFor(ic.DAO, addr) 272 if deposit == nil { 273 return stackitem.NewBool(false) 274 } 275 if till < deposit.Till { 276 return stackitem.NewBool(false) 277 } 278 deposit.Till = till 279 err = n.putDepositFor(ic.DAO, deposit, addr) 280 if err != nil { 281 panic(fmt.Errorf("failed to put deposit for %s into the storage: %w", addr.StringBE(), err)) 282 } 283 return stackitem.NewBool(true) 284 } 285 286 // withdraw sends all deposited GAS for "from" address to "to" address. 287 func (n *Notary) withdraw(ic *interop.Context, args []stackitem.Item) stackitem.Item { 288 from := toUint160(args[0]) 289 ok, err := runtime.CheckHashedWitness(ic, from) 290 if err != nil { 291 panic(fmt.Errorf("failed to check witness for %s: %w", from.StringBE(), err)) 292 } 293 if !ok { 294 return stackitem.NewBool(false) 295 } 296 to := from 297 if !args[1].Equals(stackitem.Null{}) { 298 to = toUint160(args[1]) 299 } 300 deposit := n.GetDepositFor(ic.DAO, from) 301 if deposit == nil { 302 return stackitem.NewBool(false) 303 } 304 // Allow withdrawal only after `till` block was persisted, thus, use ic.BlockHeight(). 305 if ic.BlockHeight() < deposit.Till { 306 return stackitem.NewBool(false) 307 } 308 cs, err := ic.GetContract(n.GAS.Hash) 309 if err != nil { 310 panic(fmt.Errorf("failed to get GAS contract state: %w", err)) 311 } 312 313 // Remove deposit before GAS transfer processing to avoid double-withdrawal. 314 // In case if Gas contract call fails, state will be rolled back. 315 n.removeDepositFor(ic.DAO, from) 316 317 transferArgs := []stackitem.Item{stackitem.NewByteArray(n.Hash.BytesBE()), stackitem.NewByteArray(to.BytesBE()), stackitem.NewBigInteger(deposit.Amount), stackitem.Null{}} 318 err = contract.CallFromNative(ic, n.Hash, cs, "transfer", transferArgs, true) 319 if err != nil { 320 panic(fmt.Errorf("failed to transfer GAS from Notary account: %w", err)) 321 } 322 if !ic.VM.Estack().Pop().Bool() { 323 panic("failed to transfer GAS from Notary account: `transfer` returned false") 324 } 325 return stackitem.NewBool(true) 326 } 327 328 // balanceOf returns the deposited GAS amount for the specified address. 329 func (n *Notary) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item { 330 acc := toUint160(args[0]) 331 return stackitem.NewBigInteger(n.BalanceOf(ic.DAO, acc)) 332 } 333 334 // BalanceOf is an internal representation of `balanceOf` Notary method. 335 func (n *Notary) BalanceOf(dao *dao.Simple, acc util.Uint160) *big.Int { 336 deposit := n.GetDepositFor(dao, acc) 337 if deposit == nil { 338 return big.NewInt(0) 339 } 340 return deposit.Amount 341 } 342 343 // expirationOf returns the deposit lock height for the specified address. 344 func (n *Notary) expirationOf(ic *interop.Context, args []stackitem.Item) stackitem.Item { 345 acc := toUint160(args[0]) 346 return stackitem.Make(n.ExpirationOf(ic.DAO, acc)) 347 } 348 349 // ExpirationOf is an internal representation of `expirationOf` Notary method. 350 func (n *Notary) ExpirationOf(dao *dao.Simple, acc util.Uint160) uint32 { 351 deposit := n.GetDepositFor(dao, acc) 352 if deposit == nil { 353 return 0 354 } 355 return deposit.Till 356 } 357 358 // verify checks whether the transaction was signed by one of the notaries. 359 func (n *Notary) verify(ic *interop.Context, args []stackitem.Item) stackitem.Item { 360 sig, err := args[0].TryBytes() 361 if err != nil { 362 panic(fmt.Errorf("failed to get signature bytes: %w", err)) 363 } 364 tx := ic.Tx 365 if len(tx.GetAttributes(transaction.NotaryAssistedT)) == 0 { 366 return stackitem.NewBool(false) 367 } 368 for _, signer := range tx.Signers { 369 if signer.Account == n.Hash { 370 if signer.Scopes != transaction.None { 371 return stackitem.NewBool(false) 372 } 373 break 374 } 375 } 376 if tx.Sender() == n.Hash { 377 if len(tx.Signers) != 2 { 378 return stackitem.NewBool(false) 379 } 380 payer := tx.Signers[1].Account 381 balance := n.GetDepositFor(ic.DAO, payer) 382 if balance == nil || balance.Amount.Cmp(big.NewInt(tx.NetworkFee+tx.SystemFee)) < 0 { 383 return stackitem.NewBool(false) 384 } 385 } 386 notaries, err := n.GetNotaryNodes(ic.DAO) 387 if err != nil { 388 panic(fmt.Errorf("failed to get notary nodes: %w", err)) 389 } 390 shash := hash.NetSha256(uint32(ic.Network), tx) 391 var verified bool 392 for _, n := range notaries { 393 if n.Verify(sig, shash[:]) { 394 verified = true 395 break 396 } 397 } 398 return stackitem.NewBool(verified) 399 } 400 401 // GetNotaryNodes returns public keys of notary nodes. 402 func (n *Notary) GetNotaryNodes(d *dao.Simple) (keys.PublicKeys, error) { 403 nodes, _, err := n.Desig.GetDesignatedByRole(d, noderoles.P2PNotary, math.MaxUint32) 404 return nodes, err 405 } 406 407 // getMaxNotValidBeforeDelta is a Notary contract method and returns the maximum NotValidBefore delta. 408 func (n *Notary) getMaxNotValidBeforeDelta(ic *interop.Context, _ []stackitem.Item) stackitem.Item { 409 return stackitem.NewBigInteger(big.NewInt(int64(n.GetMaxNotValidBeforeDelta(ic.DAO)))) 410 } 411 412 // GetMaxNotValidBeforeDelta is an internal representation of Notary getMaxNotValidBeforeDelta method. 413 func (n *Notary) GetMaxNotValidBeforeDelta(dao *dao.Simple) uint32 { 414 cache := dao.GetROCache(n.ID).(*NotaryCache) 415 return cache.maxNotValidBeforeDelta 416 } 417 418 // setMaxNotValidBeforeDelta is a Notary contract method and sets the maximum NotValidBefore delta. 419 func (n *Notary) setMaxNotValidBeforeDelta(ic *interop.Context, args []stackitem.Item) stackitem.Item { 420 value := toUint32(args[0]) 421 cfg := ic.Chain.GetConfig() 422 maxInc := cfg.MaxValidUntilBlockIncrement 423 if value > maxInc/2 || value < uint32(cfg.GetNumOfCNs(ic.BlockHeight())) { 424 panic(fmt.Errorf("MaxNotValidBeforeDelta cannot be more than %d or less than %d", maxInc/2, cfg.GetNumOfCNs(ic.BlockHeight()))) 425 } 426 if !n.NEO.checkCommittee(ic) { 427 panic("invalid committee signature") 428 } 429 setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, int64(value)) 430 cache := ic.DAO.GetRWCache(n.ID).(*NotaryCache) 431 cache.maxNotValidBeforeDelta = value 432 return stackitem.Null{} 433 } 434 435 // GetDepositFor returns state.Deposit for the account specified. It returns nil in case 436 // the deposit is not found in the storage and panics in case of any other error. 437 func (n *Notary) GetDepositFor(dao *dao.Simple, acc util.Uint160) *state.Deposit { 438 key := append([]byte{prefixDeposit}, acc.BytesBE()...) 439 deposit := new(state.Deposit) 440 err := getConvertibleFromDAO(n.ID, dao, key, deposit) 441 if err == nil { 442 return deposit 443 } 444 if errors.Is(err, storage.ErrKeyNotFound) { 445 return nil 446 } 447 panic(fmt.Errorf("failed to get deposit for %s from storage: %w", acc.StringBE(), err)) 448 } 449 450 // putDepositFor puts the deposit on the balance of the specified account in the storage. 451 func (n *Notary) putDepositFor(dao *dao.Simple, deposit *state.Deposit, acc util.Uint160) error { 452 key := append([]byte{prefixDeposit}, acc.BytesBE()...) 453 return putConvertibleToDAO(n.ID, dao, key, deposit) 454 } 455 456 // removeDepositFor removes the deposit from the storage. 457 func (n *Notary) removeDepositFor(dao *dao.Simple, acc util.Uint160) { 458 key := append([]byte{prefixDeposit}, acc.BytesBE()...) 459 dao.DeleteStorageItem(n.ID, key) 460 } 461 462 // calculateNotaryReward calculates the reward for a single notary node based on FEE's count and Notary nodes count. 463 func calculateNotaryReward(nFees int64, feePerKey int64, notariesCount int) *big.Int { 464 return big.NewInt(nFees * feePerKey / int64(notariesCount)) 465 }