native: make Oracle service handle native Oracle updates

A part of #3213.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2024-04-09 14:39:19 +03:00
parent ec6fc54bc6
commit 235f4398c6
14 changed files with 55 additions and 33 deletions

View file

@ -171,7 +171,7 @@ type HFSpecificEvent struct {
type Contract interface {
// Initialize performs native contract initialization on contract deploy or update.
// Active hardfork is passed as the second argument.
Initialize(*Context, *config.Hardfork) error
Initialize(*Context, *config.Hardfork, *HFSpecificContractMD) error
// ActiveIn returns the hardfork native contract is active starting from or nil in case
// it's always active.
ActiveIn() *config.Hardfork

View file

@ -310,7 +310,7 @@ func (c *Crypto) Metadata() *interop.ContractMD {
}
// Initialize implements the Contract interface.
func (c *Crypto) Initialize(ic *interop.Context, hf *config.Hardfork) error {
func (c *Crypto) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
return nil
}

View file

@ -133,7 +133,7 @@ func newDesignate(p2pSigExtensionsEnabled bool, initialNodeRoles map[noderoles.R
// Initialize initializes Designation contract. It is called once at native Management's OnPersist
// at the genesis block, and we can't properly fill the cache at this point, as there are no roles
// data in the storage.
func (s *Designate) Initialize(ic *interop.Context, hf *config.Hardfork) error {
func (s *Designate) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != s.ActiveIn() {
return nil
}

View file

@ -81,7 +81,7 @@ func (l *Ledger) Metadata() *interop.ContractMD {
}
// Initialize implements the Contract interface.
func (l *Ledger) Initialize(ic *interop.Context, hf *config.Hardfork) error {
func (l *Ledger) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
return nil
}

View file

@ -627,7 +627,8 @@ func (m *Management) OnPersist(ic *interop.Context) error {
continue
}
md := native.Metadata()
base := md.HFSpecificContractMD(&latestHF).ContractBase
hfSpecificMD := md.HFSpecificContractMD(&latestHF)
base := hfSpecificMD.ContractBase
var cs *state.Contract
switch {
case isDeploy:
@ -653,7 +654,7 @@ func (m *Management) OnPersist(ic *interop.Context) error {
if err != nil {
return fmt.Errorf("failed to put contract state: %w", err)
}
if err := native.Initialize(ic, activeIn); err != nil {
if err := native.Initialize(ic, activeIn, hfSpecificMD); err != nil {
return fmt.Errorf("initializing %s native contract at HF %d: %w", md.Name, activeIn, err)
}
if cache == nil {
@ -728,7 +729,7 @@ func (m *Management) GetNEP17Contracts(d *dao.Simple) []util.Uint160 {
}
// Initialize implements the Contract interface.
func (m *Management) Initialize(ic *interop.Context, hf *config.Hardfork) error {
func (m *Management) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != m.ActiveIn() {
return nil
}

View file

@ -20,9 +20,9 @@ func TestDeployGetUpdateDestroyContract(t *testing.T) {
mgmt.Policy = newPolicy(false)
d := dao.NewSimple(storage.NewMemoryStore(), false)
ic := &interop.Context{DAO: d}
err := mgmt.Initialize(ic, nil)
err := mgmt.Initialize(ic, nil, nil)
require.NoError(t, err)
require.NoError(t, mgmt.Policy.Initialize(&interop.Context{DAO: d}, nil))
require.NoError(t, mgmt.Policy.Initialize(&interop.Context{DAO: d}, nil, nil))
script := []byte{byte(opcode.RET)}
sender := util.Uint160{1, 2, 3}
ne, err := nef.NewFile(script)
@ -97,9 +97,9 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
mgmt := newManagement()
mgmt.Policy = newPolicy(false)
d := dao.NewSimple(storage.NewMemoryStore(), false)
err := mgmt.Initialize(&interop.Context{DAO: d}, nil)
err := mgmt.Initialize(&interop.Context{DAO: d}, nil, nil)
require.NoError(t, err)
require.NoError(t, mgmt.Policy.Initialize(&interop.Context{DAO: d}, nil))
require.NoError(t, mgmt.Policy.Initialize(&interop.Context{DAO: d}, nil, nil))
err = mgmt.InitializeCache(0, d)
require.NoError(t, err)

View file

@ -83,7 +83,7 @@ func (g *GAS) balanceFromBytes(si *state.StorageItem) (*big.Int, error) {
}
// Initialize initializes a GAS contract.
func (g *GAS) Initialize(ic *interop.Context, hf *config.Hardfork) error {
func (g *GAS) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != g.ActiveIn() {
return nil
}

View file

@ -286,7 +286,7 @@ func newNEO(cfg config.ProtocolConfiguration) *NEO {
}
// Initialize initializes a NEO contract.
func (n *NEO) Initialize(ic *interop.Context, hf *config.Hardfork) error {
func (n *NEO) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != n.ActiveIn() {
return nil
}

View file

@ -127,7 +127,7 @@ func (n *Notary) Metadata() *interop.ContractMD {
}
// Initialize initializes Notary native contract and implements the Contract interface.
func (n *Notary) Initialize(ic *interop.Context, hf *config.Hardfork) error {
func (n *Notary) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != n.ActiveIn() {
return nil
}

View file

@ -246,18 +246,28 @@ func (o *Oracle) Metadata() *interop.ContractMD {
}
// Initialize initializes an Oracle contract.
func (o *Oracle) Initialize(ic *interop.Context, hf *config.Hardfork) error {
if hf != o.ActiveIn() {
return nil
func (o *Oracle) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
switch hf {
case o.ActiveIn():
setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0)
setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, DefaultOracleRequestPrice)
cache := &OracleCache{
requestPrice: int64(DefaultOracleRequestPrice),
}
ic.DAO.SetCache(o.ID, cache)
default:
orc, _ := o.Module.Load().(*OracleService)
if orc != nil && *orc != nil {
md, ok := newMD.GetMethod(manifest.MethodVerify, -1)
if !ok {
panic(fmt.Errorf("%s method not found", manifest.MethodVerify))
}
(*orc).UpdateNativeContract(newMD.NEF.Script, o.GetOracleResponseScript(),
o.Hash, md.MD.Offset)
}
}
setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0)
setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, DefaultOracleRequestPrice)
cache := &OracleCache{
requestPrice: int64(DefaultOracleRequestPrice),
}
ic.DAO.SetCache(o.ID, cache)
return nil
}

View file

@ -169,7 +169,7 @@ func (p *Policy) Metadata() *interop.ContractMD {
}
// Initialize initializes Policy native contract and implements the Contract interface.
func (p *Policy) Initialize(ic *interop.Context, hf *config.Hardfork) error {
func (p *Policy) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
if hf != p.ActiveIn() {
return nil
}

View file

@ -439,7 +439,7 @@ func (s *Std) Metadata() *interop.ContractMD {
}
// Initialize implements the Contract interface.
func (s *Std) Initialize(ic *interop.Context, hf *config.Hardfork) error {
func (s *Std) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
return nil
}

View file

@ -38,8 +38,9 @@ type (
Oracle struct {
Config
// This fields are readonly thus not protected by mutex.
oracleHash util.Uint160
// Native Oracle contract related information that may be updated on Oracle contract
// update.
oracleInfoLock sync.RWMutex
oracleResponse []byte
oracleScript []byte
verifyOffset int
@ -277,10 +278,11 @@ drain:
// UpdateNativeContract updates native oracle contract info for tx verification.
func (o *Oracle) UpdateNativeContract(script, resp []byte, h util.Uint160, verifyOffset int) {
o.oracleInfoLock.Lock()
defer o.oracleInfoLock.Unlock()
o.oracleScript = bytes.Clone(script)
o.oracleResponse = bytes.Clone(resp)
o.oracleHash = h
o.verifyOffset = verifyOffset
}

View file

@ -8,6 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativehashes"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -102,7 +103,12 @@ func checkUTF8(v []byte) ([]byte, error) {
// CreateResponseTx creates an unsigned oracle response transaction.
func (o *Oracle) CreateResponseTx(gasForResponse int64, vub uint32, resp *transaction.OracleResponse) (*transaction.Transaction, error) {
tx := transaction.New(o.oracleResponse, 0)
var respScript []byte
o.oracleInfoLock.RLock()
respScript = o.oracleResponse
o.oracleInfoLock.RUnlock()
tx := transaction.New(respScript, 0)
tx.Nonce = uint32(resp.ID)
tx.ValidUntilBlock = vub
tx.Attributes = []transaction.Attribute{{
@ -113,7 +119,7 @@ func (o *Oracle) CreateResponseTx(gasForResponse int64, vub uint32, resp *transa
oracleSignContract := o.getOracleSignContract()
tx.Signers = []transaction.Signer{
{
Account: o.oracleHash,
Account: nativehashes.Oracle,
Scopes: transaction.None,
},
{
@ -166,8 +172,11 @@ func (o *Oracle) testVerify(tx *transaction.Transaction) (int64, bool, error) {
return 0, false, fmt.Errorf("failed to create test VM: %w", err)
}
ic.VM.GasLimit = o.Chain.GetMaxVerificationGAS()
ic.VM.LoadScriptWithHash(o.oracleScript, o.oracleHash, callflag.ReadOnly)
o.oracleInfoLock.RLock()
ic.VM.LoadScriptWithHash(o.oracleScript, nativehashes.Oracle, callflag.ReadOnly)
ic.VM.Context().Jump(o.verifyOffset)
o.oracleInfoLock.RUnlock()
ok := isVerifyOk(ic)
return ic.VM.GasConsumed(), ok, nil