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  }