native: implement Designate contract
This commit is contained in:
parent
230352d99f
commit
c468c02ef5
8 changed files with 365 additions and 117 deletions
|
@ -651,7 +651,9 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bc.contracts.Policy.OnPersistEnd(bc.dao)
|
bc.contracts.Policy.OnPersistEnd(bc.dao)
|
||||||
bc.contracts.Oracle.OnPersistEnd(bc.dao)
|
if err := bc.contracts.Designate.OnPersistEnd(bc.dao); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
bc.dao.MPT.Flush()
|
bc.dao.MPT.Flush()
|
||||||
// Every persist cycle we also compact our in-memory MPT.
|
// Every persist cycle we also compact our in-memory MPT.
|
||||||
persistedHeight := atomic.LoadUint32(&bc.persistedHeight)
|
persistedHeight := atomic.LoadUint32(&bc.persistedHeight)
|
||||||
|
|
|
@ -443,8 +443,8 @@ func TestVerifyTx(t *testing.T) {
|
||||||
ic := bc.newInteropContext(trigger.All, bc.dao, nil, txSetOracle)
|
ic := bc.newInteropContext(trigger.All, bc.dao, nil, txSetOracle)
|
||||||
ic.SpawnVM()
|
ic.SpawnVM()
|
||||||
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
||||||
require.NoError(t, bc.contracts.Oracle.SetOracleNodes(ic, oraclePubs))
|
require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, native.RoleOracle, oraclePubs))
|
||||||
bc.contracts.Oracle.OnPersistEnd(ic.DAO)
|
require.NoError(t, bc.contracts.Designate.OnPersistEnd(ic.DAO))
|
||||||
_, err = ic.DAO.Persist()
|
_, err = ic.DAO.Persist()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ type Contracts struct {
|
||||||
GAS *GAS
|
GAS *GAS
|
||||||
Policy *Policy
|
Policy *Policy
|
||||||
Oracle *Oracle
|
Oracle *Oracle
|
||||||
|
Designate *Designate
|
||||||
Contracts []interop.Contract
|
Contracts []interop.Contract
|
||||||
// persistScript is vm script which executes "onPersist" method of every native contract.
|
// persistScript is vm script which executes "onPersist" method of every native contract.
|
||||||
persistScript []byte
|
persistScript []byte
|
||||||
|
@ -58,6 +59,13 @@ func NewContracts() *Contracts {
|
||||||
oracle.NEO = neo
|
oracle.NEO = neo
|
||||||
cs.Oracle = oracle
|
cs.Oracle = oracle
|
||||||
cs.Contracts = append(cs.Contracts, oracle)
|
cs.Contracts = append(cs.Contracts, oracle)
|
||||||
|
|
||||||
|
desig := newDesignate()
|
||||||
|
desig.NEO = neo
|
||||||
|
cs.Designate = desig
|
||||||
|
cs.Oracle.Desig = desig
|
||||||
|
cs.Contracts = append(cs.Contracts, desig)
|
||||||
|
|
||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +79,7 @@ func (cs *Contracts) GetPersistScript() []byte {
|
||||||
md := cs.Contracts[i].Metadata()
|
md := cs.Contracts[i].Metadata()
|
||||||
// Not every contract is persisted:
|
// Not every contract is persisted:
|
||||||
// https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L90
|
// https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L90
|
||||||
if md.ContractID == policyContractID || md.ContractID == oracleContractID {
|
if md.ContractID == policyContractID || md.ContractID == oracleContractID || md.ContractID == designateContractID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
emit.Int(w.BinWriter, 0)
|
emit.Int(w.BinWriter, 0)
|
||||||
|
@ -94,7 +102,7 @@ func (cs *Contracts) GetPostPersistScript() []byte {
|
||||||
md := cs.Contracts[i].Metadata()
|
md := cs.Contracts[i].Metadata()
|
||||||
// Not every contract is persisted:
|
// Not every contract is persisted:
|
||||||
// https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L103
|
// https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L103
|
||||||
if md.ContractID == policyContractID || md.ContractID == gasContractID {
|
if md.ContractID == policyContractID || md.ContractID == gasContractID || md.ContractID == designateContractID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
emit.Int(w.BinWriter, 0)
|
emit.Int(w.BinWriter, 0)
|
||||||
|
|
188
pkg/core/native/designate.go
Normal file
188
pkg/core/native/designate.go
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
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)
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
|
@ -4,14 +4,11 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"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"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||||
"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/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
@ -32,12 +29,7 @@ type Oracle struct {
|
||||||
GAS *GAS
|
GAS *GAS
|
||||||
NEO *NEO
|
NEO *NEO
|
||||||
|
|
||||||
// nodesChanged is true if `SetOracleNodes` was called.
|
Desig *Designate
|
||||||
nodesChanged atomic.Value
|
|
||||||
// nodes contains cached list of oracle nodes.
|
|
||||||
nodes atomic.Value
|
|
||||||
// oracleHash contains cached oracle script hash.
|
|
||||||
oracleHash atomic.Value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -81,7 +73,6 @@ var (
|
||||||
// Various validation errors.
|
// Various validation errors.
|
||||||
var (
|
var (
|
||||||
ErrBigArgument = errors.New("some of the arguments are invalid")
|
ErrBigArgument = errors.New("some of the arguments are invalid")
|
||||||
ErrEmptyNodeList = errors.New("oracle nodes list is empty")
|
|
||||||
ErrInvalidWitness = errors.New("witness check failed")
|
ErrInvalidWitness = errors.New("witness check failed")
|
||||||
ErrNotEnoughGas = errors.New("gas limit exceeded")
|
ErrNotEnoughGas = errors.New("gas limit exceeded")
|
||||||
ErrRequestNotFound = errors.New("oracle request not found")
|
ErrRequestNotFound = errors.New("oracle request not found")
|
||||||
|
@ -113,14 +104,6 @@ func newOracle() *Oracle {
|
||||||
md = newMethodAndPrice(o.finish, 0, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(o.finish, 0, smartcontract.AllowModifyStates)
|
||||||
o.AddMethod(md, desc, false)
|
o.AddMethod(md, desc, false)
|
||||||
|
|
||||||
desc = newDescriptor("getOracleNodes", smartcontract.ArrayType)
|
|
||||||
md = newMethodAndPrice(o.getOracleNodes, 100_0000, smartcontract.AllowStates)
|
|
||||||
o.AddMethod(md, desc, true)
|
|
||||||
|
|
||||||
desc = newDescriptor("setOracleNodes", smartcontract.VoidType)
|
|
||||||
md = newMethodAndPrice(o.setOracleNodes, 0, smartcontract.AllowModifyStates)
|
|
||||||
o.AddMethod(md, desc, false)
|
|
||||||
|
|
||||||
desc = newDescriptor("verify", smartcontract.BoolType)
|
desc = newDescriptor("verify", smartcontract.BoolType)
|
||||||
md = newMethodAndPrice(o.verify, 100_0000, smartcontract.NoneFlag)
|
md = newMethodAndPrice(o.verify, 100_0000, smartcontract.NoneFlag)
|
||||||
o.AddMethod(md, desc, false)
|
o.AddMethod(md, desc, false)
|
||||||
|
@ -130,9 +113,6 @@ func newOracle() *Oracle {
|
||||||
md = newMethodAndPrice(getOnPersistWrapper(pp), 0, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(getOnPersistWrapper(pp), 0, smartcontract.AllowModifyStates)
|
||||||
o.AddMethod(md, desc, false)
|
o.AddMethod(md, desc, false)
|
||||||
|
|
||||||
o.nodes.Store(keys.PublicKeys(nil))
|
|
||||||
o.nodesChanged.Store(false)
|
|
||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +156,10 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if nodes == nil {
|
if nodes == nil {
|
||||||
nodes = o.GetOracleNodes()
|
nodes, err = o.GetOracleNodes(ic.DAO)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
reward = make([]big.Int, len(nodes))
|
reward = make([]big.Int, len(nodes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,48 +319,18 @@ func (o *Oracle) PutRequestInternal(id uint64, req *OracleRequest, d dao.DAO) er
|
||||||
return d.PutStorageItem(o.ContractID, key, si)
|
return d.PutStorageItem(o.ContractID, key, si)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Oracle) getOracleNodes(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
||||||
pubs := o.GetOracleNodes()
|
|
||||||
return pubsToArray(pubs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOracleNodes returns public keys of oracle nodes.
|
|
||||||
func (o *Oracle) GetOracleNodes() keys.PublicKeys {
|
|
||||||
return o.nodes.Load().(keys.PublicKeys).Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetScriptHash returns script hash or oracle nodes.
|
// GetScriptHash returns script hash or oracle nodes.
|
||||||
func (o *Oracle) GetScriptHash() (util.Uint160, error) {
|
func (o *Oracle) GetScriptHash() (util.Uint160, error) {
|
||||||
h := o.oracleHash.Load()
|
h := o.Desig.oracleHash.Load()
|
||||||
if h == nil {
|
if h == nil {
|
||||||
return util.Uint160{}, storage.ErrKeyNotFound
|
return util.Uint160{}, storage.ErrKeyNotFound
|
||||||
}
|
}
|
||||||
return h.(util.Uint160), nil
|
return h.(util.Uint160), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Oracle) setOracleNodes(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
// GetOracleNodes returns public keys of oracle nodes.
|
||||||
var pubs keys.PublicKeys
|
func (o *Oracle) GetOracleNodes(d dao.DAO) (keys.PublicKeys, error) {
|
||||||
err := o.SetOracleNodes(ic, pubs)
|
return o.Desig.GetDesignatedByRole(d, RoleOracle)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return pubsToArray(pubs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOracleNodes sets oracle node public keys to pubs.
|
|
||||||
func (o *Oracle) SetOracleNodes(ic *interop.Context, pubs keys.PublicKeys) error {
|
|
||||||
if len(pubs) == 0 {
|
|
||||||
return ErrEmptyNodeList
|
|
||||||
}
|
|
||||||
h := o.NEO.GetCommitteeAddress()
|
|
||||||
if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok {
|
|
||||||
return ErrInvalidWitness
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(pubs)
|
|
||||||
o.nodesChanged.Store(true)
|
|
||||||
si := &state.StorageItem{Value: NodeList(pubs).Bytes()}
|
|
||||||
return ic.DAO.PutStorageItem(o.ContractID, prefixNodeList, si)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRequestInternal returns request by ID and key under which it is stored.
|
// GetRequestInternal returns request by ID and key under which it is stored.
|
||||||
|
@ -421,26 +374,5 @@ func makeIDListKey(url string) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Oracle) getSerializableFromDAO(d dao.DAO, key []byte, item io.Serializable) error {
|
func (o *Oracle) getSerializableFromDAO(d dao.DAO, key []byte, item io.Serializable) error {
|
||||||
si := d.GetStorageItem(o.ContractID, key)
|
return getSerializableFromDAO(o.ContractID, d, key, item)
|
||||||
if si == nil {
|
|
||||||
return storage.ErrKeyNotFound
|
|
||||||
}
|
|
||||||
r := io.NewBinReaderFromBuf(si.Value)
|
|
||||||
item.DecodeBinary(r)
|
|
||||||
return r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnPersistEnd updates cached Oracle values if they've been changed
|
|
||||||
func (o *Oracle) OnPersistEnd(d dao.DAO) {
|
|
||||||
if !o.nodesChanged.Load().(bool) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ns := new(NodeList)
|
|
||||||
_ = o.getSerializableFromDAO(d, prefixNodeList, ns)
|
|
||||||
o.nodes.Store(keys.PublicKeys(*ns))
|
|
||||||
script, _ := smartcontract.CreateMajorityMultiSigRedeemScript(keys.PublicKeys(*ns).Copy())
|
|
||||||
o.oracleHash.Store(hash.Hash160(script))
|
|
||||||
o.nodesChanged.Store(false)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
17
pkg/core/native/util.go
Normal file
17
pkg/core/native/util.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getSerializableFromDAO(id int32, d dao.DAO, key []byte, item io.Serializable) error {
|
||||||
|
si := d.GetStorageItem(id, key)
|
||||||
|
if si == nil {
|
||||||
|
return storage.ErrKeyNotFound
|
||||||
|
}
|
||||||
|
r := io.NewBinReaderFromBuf(si.Value)
|
||||||
|
item.DecodeBinary(r)
|
||||||
|
return r.Err
|
||||||
|
}
|
133
pkg/core/native_designate_test.go
Normal file
133
pkg/core/native_designate_test.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/internal/testchain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (bc *Blockchain) setNodesByRole(t *testing.T, ok bool, r native.Role, nodes keys.PublicKeys) {
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
for _, pub := range nodes {
|
||||||
|
emit.Bytes(w.BinWriter, pub.Bytes())
|
||||||
|
}
|
||||||
|
emit.Int(w.BinWriter, int64(len(nodes)))
|
||||||
|
emit.Opcode(w.BinWriter, opcode.PACK)
|
||||||
|
emit.Int(w.BinWriter, int64(r))
|
||||||
|
emit.Int(w.BinWriter, 2)
|
||||||
|
emit.Opcode(w.BinWriter, opcode.PACK)
|
||||||
|
emit.String(w.BinWriter, "designateAsRole")
|
||||||
|
emit.AppCall(w.BinWriter, bc.contracts.Designate.Hash)
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
tx := transaction.New(netmode.UnitTestNet, w.Bytes(), 0)
|
||||||
|
tx.NetworkFee = 10_000_000
|
||||||
|
tx.SystemFee = 10_000_000
|
||||||
|
tx.ValidUntilBlock = 100
|
||||||
|
tx.Signers = []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: testchain.MultisigScriptHash(),
|
||||||
|
Scopes: transaction.FeeOnly,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: testchain.CommitteeScriptHash(),
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, signTx(bc, tx))
|
||||||
|
tx.Scripts = append(tx.Scripts, transaction.Witness{
|
||||||
|
InvocationScript: testchain.SignCommittee(tx.GetSignedPart()),
|
||||||
|
VerificationScript: testchain.CommitteeVerificationScript(),
|
||||||
|
})
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
|
||||||
|
|
||||||
|
aer, err := bc.GetAppExecResult(tx.Hash())
|
||||||
|
require.NoError(t, err)
|
||||||
|
if ok {
|
||||||
|
require.Equal(t, vm.HaltState, aer.VMState)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, vm.FaultState, aer.VMState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDesignate_DesignateAsRoleTx(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
defer bc.Close()
|
||||||
|
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubs := keys.PublicKeys{priv.PublicKey()}
|
||||||
|
|
||||||
|
bc.setNodesByRole(t, false, 0xFF, pubs)
|
||||||
|
bc.setNodesByRole(t, true, native.RoleOracle, pubs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDesignate_DesignateAsRole(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
defer bc.Close()
|
||||||
|
|
||||||
|
des := bc.contracts.Designate
|
||||||
|
tx := transaction.New(netmode.UnitTestNet, []byte{}, 0)
|
||||||
|
ic := bc.newInteropContext(trigger.System, bc.dao, nil, tx)
|
||||||
|
ic.VM = vm.New()
|
||||||
|
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
||||||
|
|
||||||
|
pubs, err := des.GetDesignatedByRole(bc.dao, 0xFF)
|
||||||
|
require.True(t, errors.Is(err, native.ErrInvalidRole), "got: %v", err)
|
||||||
|
|
||||||
|
pubs, err = des.GetDesignatedByRole(bc.dao, native.RoleOracle)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, len(pubs))
|
||||||
|
|
||||||
|
err = des.DesignateAsRole(ic, native.RoleOracle, keys.PublicKeys{})
|
||||||
|
require.True(t, errors.Is(err, native.ErrEmptyNodeList), "got: %v", err)
|
||||||
|
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pub := priv.PublicKey()
|
||||||
|
|
||||||
|
err = des.DesignateAsRole(ic, 0xFF, keys.PublicKeys{pub})
|
||||||
|
require.True(t, errors.Is(err, native.ErrInvalidRole), "got: %v", err)
|
||||||
|
|
||||||
|
err = des.DesignateAsRole(ic, native.RoleOracle, keys.PublicKeys{pub})
|
||||||
|
require.True(t, errors.Is(err, native.ErrInvalidWitness), "got: %v", err)
|
||||||
|
|
||||||
|
setSigner(tx, testchain.CommitteeScriptHash())
|
||||||
|
err = des.DesignateAsRole(ic, native.RoleOracle, keys.PublicKeys{pub})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, des.OnPersistEnd(ic.DAO))
|
||||||
|
|
||||||
|
pubs, err = des.GetDesignatedByRole(ic.DAO, native.RoleOracle)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, keys.PublicKeys{pub}, pubs)
|
||||||
|
|
||||||
|
pubs, err = des.GetDesignatedByRole(ic.DAO, native.RoleStateValidator)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, len(pubs))
|
||||||
|
|
||||||
|
// Set another role.
|
||||||
|
_, err = keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pub1 := priv.PublicKey()
|
||||||
|
err = des.DesignateAsRole(ic, native.RoleStateValidator, keys.PublicKeys{pub1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, des.OnPersistEnd(ic.DAO))
|
||||||
|
|
||||||
|
pubs, err = des.GetDesignatedByRole(ic.DAO, native.RoleOracle)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, keys.PublicKeys{pub}, pubs)
|
||||||
|
|
||||||
|
pubs, err = des.GetDesignatedByRole(ic.DAO, native.RoleStateValidator)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, keys.PublicKeys{pub1}, pubs)
|
||||||
|
}
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
@ -141,9 +140,9 @@ func TestOracle_Request(t *testing.T) {
|
||||||
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
||||||
ic.SpawnVM()
|
ic.SpawnVM()
|
||||||
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
||||||
err = orc.SetOracleNodes(ic, keys.PublicKeys{pub})
|
err = bc.contracts.Designate.DesignateAsRole(ic, native.RoleOracle, keys.PublicKeys{pub})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
orc.OnPersistEnd(ic.DAO)
|
require.NoError(t, bc.contracts.Designate.OnPersistEnd(ic.DAO))
|
||||||
|
|
||||||
tx = transaction.New(netmode.UnitTestNet, native.GetOracleResponseScript(), 0)
|
tx = transaction.New(netmode.UnitTestNet, native.GetOracleResponseScript(), 0)
|
||||||
ic.Tx = tx
|
ic.Tx = tx
|
||||||
|
@ -215,34 +214,3 @@ func TestOracle_Request(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOracle_SetOracleNodes(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
defer bc.Close()
|
|
||||||
|
|
||||||
orc := bc.contracts.Oracle
|
|
||||||
tx := transaction.New(netmode.UnitTestNet, []byte{}, 0)
|
|
||||||
ic := bc.newInteropContext(trigger.System, bc.dao, nil, tx)
|
|
||||||
ic.VM = vm.New()
|
|
||||||
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
|
||||||
|
|
||||||
pubs := orc.GetOracleNodes()
|
|
||||||
require.Equal(t, 0, len(pubs))
|
|
||||||
|
|
||||||
err := orc.SetOracleNodes(ic, keys.PublicKeys{})
|
|
||||||
require.True(t, errors.Is(err, native.ErrEmptyNodeList), "got: %v", err)
|
|
||||||
|
|
||||||
priv, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
pub := priv.PublicKey()
|
|
||||||
err = orc.SetOracleNodes(ic, keys.PublicKeys{pub})
|
|
||||||
require.True(t, errors.Is(err, native.ErrInvalidWitness), "got: %v", err)
|
|
||||||
|
|
||||||
setSigner(tx, testchain.CommitteeScriptHash())
|
|
||||||
require.NoError(t, orc.SetOracleNodes(ic, keys.PublicKeys{pub}))
|
|
||||||
orc.OnPersistEnd(ic.DAO)
|
|
||||||
|
|
||||||
pubs = orc.GetOracleNodes()
|
|
||||||
require.Equal(t, keys.PublicKeys{pub}, pubs)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue