github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/designate.go (about) 1 package native 2 3 import ( 4 "encoding/binary" 5 "errors" 6 "fmt" 7 "math" 8 "math/big" 9 "sort" 10 "sync/atomic" 11 12 "github.com/nspcc-dev/neo-go/pkg/config" 13 "github.com/nspcc-dev/neo-go/pkg/core/dao" 14 "github.com/nspcc-dev/neo-go/pkg/core/interop" 15 "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" 16 "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" 17 "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" 18 "github.com/nspcc-dev/neo-go/pkg/core/stateroot" 19 "github.com/nspcc-dev/neo-go/pkg/core/storage" 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/smartcontract/trigger" 26 "github.com/nspcc-dev/neo-go/pkg/util" 27 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 28 ) 29 30 // Designate represents a designation contract. 31 type Designate struct { 32 interop.ContractMD 33 NEO *NEO 34 35 // p2pSigExtensionsEnabled defines whether the P2P signature extensions logic is relevant. 36 p2pSigExtensionsEnabled bool 37 // initialNodeRoles defines a set of node roles that should be defined at the contract 38 // deployment (initialization). 39 initialNodeRoles map[noderoles.Role]keys.PublicKeys 40 41 OracleService atomic.Value 42 // NotaryService represents a Notary node module. 43 NotaryService atomic.Value 44 // StateRootService represents a StateRoot node module. 45 StateRootService *stateroot.Module 46 } 47 48 type roleData struct { 49 nodes keys.PublicKeys 50 addr util.Uint160 51 height uint32 52 } 53 54 type DesignationCache struct { 55 // rolesChangedFlag shows whether any of designated nodes were changed within the current block. 56 // It is used to notify dependant services about updated node roles during PostPersist. 57 rolesChangedFlag bool 58 oracles roleData 59 stateVals roleData 60 neofsAlphabet roleData 61 notaries roleData 62 } 63 64 const ( 65 designateContractID = -8 66 67 // maxNodeCount is the maximum number of nodes to set the role for. 68 maxNodeCount = 32 69 70 // DesignationEventName is the name of the designation event. 71 DesignationEventName = "Designation" 72 ) 73 74 // Various errors. 75 var ( 76 ErrAlreadyDesignated = errors.New("already designated given role at current block") 77 ErrEmptyNodeList = errors.New("node list is empty") 78 ErrInvalidIndex = errors.New("invalid index") 79 ErrInvalidRole = errors.New("invalid role") 80 ErrLargeNodeList = errors.New("node list is too large") 81 ErrNoBlock = errors.New("no persisting block in the context") 82 ) 83 84 var ( 85 _ interop.Contract = (*Designate)(nil) 86 _ dao.NativeContractCache = (*DesignationCache)(nil) 87 ) 88 89 // Copy implements NativeContractCache interface. 90 func (c *DesignationCache) Copy() dao.NativeContractCache { 91 cp := &DesignationCache{} 92 copyDesignationCache(c, cp) 93 return cp 94 } 95 96 func copyDesignationCache(src, dst *DesignationCache) { 97 *dst = *src 98 } 99 100 func (s *Designate) isValidRole(r noderoles.Role) bool { 101 return r == noderoles.Oracle || r == noderoles.StateValidator || 102 r == noderoles.NeoFSAlphabet || (s.p2pSigExtensionsEnabled && r == noderoles.P2PNotary) 103 } 104 105 func newDesignate(p2pSigExtensionsEnabled bool, initialNodeRoles map[noderoles.Role]keys.PublicKeys) *Designate { 106 s := &Designate{ContractMD: *interop.NewContractMD(nativenames.Designation, designateContractID)} 107 defer s.BuildHFSpecificMD(s.ActiveIn()) 108 109 s.p2pSigExtensionsEnabled = p2pSigExtensionsEnabled 110 s.initialNodeRoles = initialNodeRoles 111 112 desc := newDescriptor("getDesignatedByRole", smartcontract.ArrayType, 113 manifest.NewParameter("role", smartcontract.IntegerType), 114 manifest.NewParameter("index", smartcontract.IntegerType)) 115 md := newMethodAndPrice(s.getDesignatedByRole, 1<<15, callflag.ReadStates) 116 s.AddMethod(md, desc) 117 118 desc = newDescriptor("designateAsRole", smartcontract.VoidType, 119 manifest.NewParameter("role", smartcontract.IntegerType), 120 manifest.NewParameter("nodes", smartcontract.ArrayType)) 121 md = newMethodAndPrice(s.designateAsRole, 1<<15, callflag.States|callflag.AllowNotify) 122 s.AddMethod(md, desc) 123 124 eDesc := newEventDescriptor(DesignationEventName, 125 manifest.NewParameter("Role", smartcontract.IntegerType), 126 manifest.NewParameter("BlockIndex", smartcontract.IntegerType)) 127 eMD := newEvent(eDesc) 128 s.AddEvent(eMD) 129 130 return s 131 } 132 133 // Initialize initializes Designation contract. It is called once at native Management's OnPersist 134 // at the genesis block, and we can't properly fill the cache at this point, as there are no roles 135 // data in the storage. 136 func (s *Designate) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { 137 if hf != s.ActiveIn() { 138 return nil 139 } 140 141 cache := &DesignationCache{} 142 ic.DAO.SetCache(s.ID, cache) 143 144 if len(s.initialNodeRoles) != 0 { 145 for _, r := range noderoles.Roles { 146 pubs, ok := s.initialNodeRoles[r] 147 if !ok { 148 continue 149 } 150 err := s.DesignateAsRole(ic, r, pubs) 151 if err != nil { 152 return fmt.Errorf("failed to initialize Designation role data for role %s: %w", r, err) 153 } 154 } 155 } 156 return nil 157 } 158 159 // InitializeCache fills native Designate cache from DAO. It is called at non-zero height, thus 160 // we can fetch the roles data right from the storage. 161 func (s *Designate) InitializeCache(blockHeight uint32, d *dao.Simple) error { 162 cache := &DesignationCache{} 163 roles := []noderoles.Role{noderoles.Oracle, noderoles.NeoFSAlphabet, noderoles.StateValidator} 164 if s.p2pSigExtensionsEnabled { 165 roles = append(roles, noderoles.P2PNotary) 166 } 167 for _, r := range roles { 168 err := s.updateCachedRoleData(cache, d, r) 169 if err != nil { 170 return fmt.Errorf("failed to get nodes from storage for %d role: %w", r, err) 171 } 172 } 173 d.SetCache(s.ID, cache) 174 return nil 175 } 176 177 // OnPersist implements the Contract interface. 178 func (s *Designate) OnPersist(ic *interop.Context) error { 179 return nil 180 } 181 182 // PostPersist implements the Contract interface. 183 func (s *Designate) PostPersist(ic *interop.Context) error { 184 cache := ic.DAO.GetRWCache(s.ID).(*DesignationCache) 185 if !cache.rolesChangedFlag { 186 return nil 187 } 188 189 s.notifyRoleChanged(&cache.oracles, noderoles.Oracle) 190 s.notifyRoleChanged(&cache.stateVals, noderoles.StateValidator) 191 s.notifyRoleChanged(&cache.neofsAlphabet, noderoles.NeoFSAlphabet) 192 if s.p2pSigExtensionsEnabled { 193 s.notifyRoleChanged(&cache.notaries, noderoles.P2PNotary) 194 } 195 196 cache.rolesChangedFlag = false 197 return nil 198 } 199 200 // Metadata returns contract metadata. 201 func (s *Designate) Metadata() *interop.ContractMD { 202 return &s.ContractMD 203 } 204 205 // ActiveIn implements the Contract interface. 206 func (s *Designate) ActiveIn() *config.Hardfork { 207 return nil 208 } 209 210 func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.Item) stackitem.Item { 211 r, ok := s.getRole(args[0]) 212 if !ok { 213 panic(ErrInvalidRole) 214 } 215 ind, err := args[1].TryInteger() 216 if err != nil || !ind.IsUint64() { 217 panic(ErrInvalidIndex) 218 } 219 index := ind.Uint64() 220 if index > uint64(ic.BlockHeight()+1) { // persisting block should be taken into account. 221 panic(ErrInvalidIndex) 222 } 223 pubs, _, err := s.GetDesignatedByRole(ic.DAO, r, uint32(index)) 224 if err != nil { 225 panic(err) 226 } 227 return pubsToArray(pubs) 228 } 229 230 func (s *Designate) hashFromNodes(r noderoles.Role, nodes keys.PublicKeys) util.Uint160 { 231 if len(nodes) == 0 { 232 return util.Uint160{} 233 } 234 var script []byte 235 switch r { 236 case noderoles.P2PNotary: 237 script, _ = smartcontract.CreateMultiSigRedeemScript(1, nodes.Copy()) 238 default: 239 script, _ = smartcontract.CreateDefaultMultiSigRedeemScript(nodes.Copy()) 240 } 241 return hash.Hash160(script) 242 } 243 244 // updateCachedRoleData fetches the most recent role data from the storage and 245 // updates the given cache. 246 func (s *Designate) updateCachedRoleData(cache *DesignationCache, d *dao.Simple, r noderoles.Role) error { 247 var v *roleData 248 switch r { 249 case noderoles.Oracle: 250 v = &cache.oracles 251 case noderoles.StateValidator: 252 v = &cache.stateVals 253 case noderoles.NeoFSAlphabet: 254 v = &cache.neofsAlphabet 255 case noderoles.P2PNotary: 256 v = &cache.notaries 257 } 258 nodeKeys, height, err := s.getDesignatedByRoleFromStorage(d, r, math.MaxUint32) 259 if err != nil { 260 return err 261 } 262 v.nodes = nodeKeys 263 v.addr = s.hashFromNodes(r, nodeKeys) 264 v.height = height 265 cache.rolesChangedFlag = true 266 return nil 267 } 268 269 func (s *Designate) notifyRoleChanged(v *roleData, r noderoles.Role) { 270 switch r { 271 case noderoles.Oracle: 272 if orc, _ := s.OracleService.Load().(*OracleService); orc != nil && *orc != nil { 273 (*orc).UpdateOracleNodes(v.nodes.Copy()) 274 } 275 case noderoles.P2PNotary: 276 if ntr, _ := s.NotaryService.Load().(*NotaryService); ntr != nil && *ntr != nil { 277 (*ntr).UpdateNotaryNodes(v.nodes.Copy()) 278 } 279 case noderoles.StateValidator: 280 if s.StateRootService != nil { 281 s.StateRootService.UpdateStateValidators(v.height, v.nodes.Copy()) 282 } 283 } 284 } 285 286 func getCachedRoleData(cache *DesignationCache, r noderoles.Role) *roleData { 287 switch r { 288 case noderoles.Oracle: 289 return &cache.oracles 290 case noderoles.StateValidator: 291 return &cache.stateVals 292 case noderoles.NeoFSAlphabet: 293 return &cache.neofsAlphabet 294 case noderoles.P2PNotary: 295 return &cache.notaries 296 } 297 return nil 298 } 299 300 // GetLastDesignatedHash returns the last designated hash of the given role. 301 func (s *Designate) GetLastDesignatedHash(d *dao.Simple, r noderoles.Role) (util.Uint160, error) { 302 if !s.isValidRole(r) { 303 return util.Uint160{}, ErrInvalidRole 304 } 305 cache := d.GetROCache(s.ID).(*DesignationCache) 306 if val := getCachedRoleData(cache, r); val != nil { 307 return val.addr, nil 308 } 309 return util.Uint160{}, nil 310 } 311 312 // GetDesignatedByRole returns nodes for role r. 313 func (s *Designate) GetDesignatedByRole(d *dao.Simple, r noderoles.Role, index uint32) (keys.PublicKeys, uint32, error) { 314 if !s.isValidRole(r) { 315 return nil, 0, ErrInvalidRole 316 } 317 cache := d.GetROCache(s.ID).(*DesignationCache) 318 if val := getCachedRoleData(cache, r); val != nil { 319 if val.height <= index { 320 return val.nodes.Copy(), val.height, nil 321 } 322 } else { 323 // Cache is always valid, thus if there's no cache then there's no designated nodes for this role. 324 return nil, 0, nil 325 } 326 // Cache stores only latest designated nodes, so if the old info is requested, then we still need 327 // to search in the storage. 328 return s.getDesignatedByRoleFromStorage(d, r, index) 329 } 330 331 // getDesignatedByRoleFromStorage returns nodes for role r from the storage. 332 func (s *Designate) getDesignatedByRoleFromStorage(d *dao.Simple, r noderoles.Role, index uint32) (keys.PublicKeys, uint32, error) { 333 var ( 334 ns NodeList 335 bestIndex uint32 336 resVal []byte 337 start = make([]byte, 4) 338 ) 339 340 binary.BigEndian.PutUint32(start, index) 341 d.Seek(s.ID, storage.SeekRange{ 342 Prefix: []byte{byte(r)}, 343 Start: start, 344 Backwards: true, 345 }, func(k, v []byte) bool { 346 bestIndex = binary.BigEndian.Uint32(k) // If len(k) < 4 the DB is broken and it deserves a panic. 347 resVal = v 348 // Take just the latest item, it's the one we need. 349 return false 350 }) 351 if resVal != nil { 352 err := stackitem.DeserializeConvertible(resVal, &ns) 353 if err != nil { 354 return nil, 0, err 355 } 356 } 357 return keys.PublicKeys(ns), bestIndex, nil 358 } 359 360 func (s *Designate) designateAsRole(ic *interop.Context, args []stackitem.Item) stackitem.Item { 361 r, ok := s.getRole(args[0]) 362 if !ok { 363 panic(ErrInvalidRole) 364 } 365 var ns NodeList 366 if err := ns.FromStackItem(args[1]); err != nil { 367 panic(err) 368 } 369 370 err := s.DesignateAsRole(ic, r, keys.PublicKeys(ns)) 371 if err != nil { 372 panic(err) 373 } 374 return stackitem.Null{} 375 } 376 377 // DesignateAsRole sets nodes for role r. 378 func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs keys.PublicKeys) error { 379 length := len(pubs) 380 if length == 0 { 381 return ErrEmptyNodeList 382 } 383 if length > maxNodeCount { 384 return ErrLargeNodeList 385 } 386 if !s.isValidRole(r) { 387 return ErrInvalidRole 388 } 389 390 if ic.Trigger != trigger.OnPersist { 391 h := s.NEO.GetCommitteeAddress(ic.DAO) 392 if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok { 393 return ErrInvalidWitness 394 } 395 } 396 397 if ic.Block == nil { 398 return ErrNoBlock 399 } 400 var key = make([]byte, 5) 401 key[0] = byte(r) 402 binary.BigEndian.PutUint32(key[1:], ic.Block.Index+1) 403 404 si := ic.DAO.GetStorageItem(s.ID, key) 405 if si != nil { 406 return ErrAlreadyDesignated 407 } 408 sort.Sort(pubs) 409 nl := NodeList(pubs) 410 411 err := putConvertibleToDAO(s.ID, ic.DAO, key, &nl) 412 if err != nil { 413 return err 414 } 415 416 cache := ic.DAO.GetRWCache(s.ID).(*DesignationCache) 417 err = s.updateCachedRoleData(cache, ic.DAO, r) 418 if err != nil { 419 return fmt.Errorf("failed to update Designation role data cache: %w", err) 420 } 421 422 ic.AddNotification(s.Hash, DesignationEventName, stackitem.NewArray([]stackitem.Item{ 423 stackitem.NewBigInteger(big.NewInt(int64(r))), 424 stackitem.NewBigInteger(big.NewInt(int64(ic.Block.Index))), 425 })) 426 return nil 427 } 428 429 func (s *Designate) getRole(item stackitem.Item) (noderoles.Role, bool) { 430 bi, err := item.TryInteger() 431 if err != nil { 432 return 0, false 433 } 434 if !bi.IsUint64() { 435 return 0, false 436 } 437 u := bi.Uint64() 438 return noderoles.Role(u), u <= math.MaxUint8 && s.isValidRole(noderoles.Role(u)) 439 }