package native import ( "errors" "math" "sort" "sync/atomic" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Designate represents designation contract. type Designate struct { interop.ContractMD NEO *NEO rolesChangedFlag atomic.Value oracleNodes atomic.Value oracleHash atomic.Value } const ( designateContractID = -5 designateName = "Designation" ) // Role represents type of participant. type Role byte // Role enumeration. const ( RoleStateValidator Role = 4 RoleOracle Role = 8 ) // Various errors. var ( ErrInvalidRole = errors.New("invalid role") ErrEmptyNodeList = errors.New("node list is empty") ) func isValidRole(r Role) bool { return r == RoleOracle || r == RoleStateValidator } func newDesignate() *Designate { s := &Designate{ContractMD: *interop.NewContractMD(designateName)} s.ContractID = designateContractID s.Manifest.Features = smartcontract.HasStorage desc := newDescriptor("getDesignatedByRole", smartcontract.ArrayType, manifest.NewParameter("role", smartcontract.IntegerType)) md := newMethodAndPrice(s.getDesignatedByRole, 0, smartcontract.AllowStates) s.AddMethod(md, desc, false) desc = newDescriptor("designateAsRole", smartcontract.VoidType, manifest.NewParameter("role", smartcontract.IntegerType), manifest.NewParameter("nodes", smartcontract.ArrayType)) md = newMethodAndPrice(s.designateAsRole, 0, smartcontract.AllowModifyStates) s.AddMethod(md, desc, false) desc = newDescriptor("name", smartcontract.StringType) md = newMethodAndPrice(nameMethod(designateName), 0, smartcontract.NoneFlag) s.AddMethod(md, desc, true) return s } // Initialize initializes Oracle contract. func (s *Designate) Initialize(ic *interop.Context) error { roles := []Role{RoleStateValidator, RoleOracle} for _, r := range roles { si := &state.StorageItem{Value: new(NodeList).Bytes()} if err := ic.DAO.PutStorageItem(s.ContractID, []byte{byte(r)}, si); err != nil { return err } } s.oracleNodes.Store(keys.PublicKeys(nil)) s.rolesChangedFlag.Store(true) return nil } // OnPersistEnd updates cached values if they've been changed. func (s *Designate) OnPersistEnd(d dao.DAO) error { if !s.rolesChanged() { return nil } var ns NodeList err := getSerializableFromDAO(s.ContractID, d, []byte{byte(RoleOracle)}, &ns) if err != nil { return err } s.oracleNodes.Store(keys.PublicKeys(ns)) script, _ := smartcontract.CreateMajorityMultiSigRedeemScript(keys.PublicKeys(ns).Copy()) s.oracleHash.Store(hash.Hash160(script)) s.rolesChangedFlag.Store(false) return nil } // Metadata returns contract metadata. func (s *Designate) Metadata() *interop.ContractMD { return &s.ContractMD } func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.Item) stackitem.Item { r, ok := getRole(args[0]) if !ok { panic(ErrInvalidRole) } pubs, err := s.GetDesignatedByRole(ic.DAO, r) if err != nil { panic(err) } return pubsToArray(pubs) } func (s *Designate) rolesChanged() bool { rc := s.rolesChangedFlag.Load() return rc == nil || rc.(bool) } // GetDesignatedByRole returns nodes for role r. func (s *Designate) GetDesignatedByRole(d dao.DAO, r Role) (keys.PublicKeys, error) { if !isValidRole(r) { return nil, ErrInvalidRole } if r == RoleOracle && !s.rolesChanged() { return s.oracleNodes.Load().(keys.PublicKeys), nil } var ns NodeList err := getSerializableFromDAO(s.ContractID, d, []byte{byte(r)}, &ns) return keys.PublicKeys(ns), err } func (s *Designate) designateAsRole(ic *interop.Context, args []stackitem.Item) stackitem.Item { r, ok := getRole(args[0]) if !ok { panic(ErrInvalidRole) } var ns NodeList if err := ns.fromStackItem(args[1]); err != nil { panic(err) } err := s.DesignateAsRole(ic, r, keys.PublicKeys(ns)) if err != nil { panic(err) } return pubsToArray(keys.PublicKeys(ns)) } // DesignateAsRole sets nodes for role r. func (s *Designate) DesignateAsRole(ic *interop.Context, r Role, pubs keys.PublicKeys) error { if len(pubs) == 0 { return ErrEmptyNodeList } if !isValidRole(r) { return ErrInvalidRole } h := s.NEO.GetCommitteeAddress() if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok { return ErrInvalidWitness } sort.Sort(pubs) s.rolesChangedFlag.Store(true) si := &state.StorageItem{Value: NodeList(pubs).Bytes()} return ic.DAO.PutStorageItem(s.ContractID, []byte{byte(r)}, si) } func getRole(item stackitem.Item) (Role, bool) { bi, err := item.TryInteger() if err != nil { return 0, false } if !bi.IsUint64() { return 0, false } u := bi.Uint64() return Role(u), u <= math.MaxUint8 && isValidRole(Role(u)) }