From c337b00ea84e7659a2e553f2d755e7750a4b27c9 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 9 Apr 2024 14:39:19 +0300 Subject: [PATCH] native: make Oracle service handle native Oracle updates A part of #3213. Signed-off-by: Anna Shaleva --- pkg/core/interop/context.go | 2 +- pkg/core/native/crypto.go | 2 +- pkg/core/native/designate.go | 2 +- pkg/core/native/ledger.go | 2 +- pkg/core/native/management.go | 9 +++++---- pkg/core/native/management_test.go | 8 ++++---- pkg/core/native/native_gas.go | 2 +- pkg/core/native/native_neo.go | 2 +- pkg/core/native/notary.go | 2 +- pkg/core/native/oracle.go | 30 ++++++++++++++++++++---------- pkg/core/native/policy.go | 2 +- pkg/core/native/std.go | 2 +- pkg/services/oracle/oracle.go | 10 ++++++---- pkg/services/oracle/response.go | 15 ++++++++++++--- 14 files changed, 56 insertions(+), 34 deletions(-) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index c3e2af909f..dd2ab9b336 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -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 diff --git a/pkg/core/native/crypto.go b/pkg/core/native/crypto.go index 2f43a328b0..81979bfbd3 100644 --- a/pkg/core/native/crypto.go +++ b/pkg/core/native/crypto.go @@ -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 } diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index 64a2692f0c..095200d9c8 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -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 } diff --git a/pkg/core/native/ledger.go b/pkg/core/native/ledger.go index 79de8e832f..a672226666 100644 --- a/pkg/core/native/ledger.go +++ b/pkg/core/native/ledger.go @@ -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 } diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 3f2e2dd850..a17fca47d2 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -630,7 +630,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: @@ -656,14 +657,14 @@ 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 { cache = ic.DAO.GetRWCache(m.ID).(*ManagementCache) } updateContractCache(cache, cs) - + ntfName := contractDeployNotificationName if isUpdate { ntfName = contractUpdateNotificationName @@ -731,7 +732,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 } diff --git a/pkg/core/native/management_test.go b/pkg/core/native/management_test.go index d3fb8872d3..73859507b1 100644 --- a/pkg/core/native/management_test.go +++ b/pkg/core/native/management_test.go @@ -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) diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index e5eb46a3e8..92f1aed7fb 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -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 } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index bf23753ef5..a720b42e50 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -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 } diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index b93557e861..fa3cab4361 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -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 } diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 2e1309caca..3f4b08acaa 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -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 } diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 77217d7327..40133e9617 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -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 } diff --git a/pkg/core/native/std.go b/pkg/core/native/std.go index caa6735e49..bd5a3ed176 100644 --- a/pkg/core/native/std.go +++ b/pkg/core/native/std.go @@ -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 } diff --git a/pkg/services/oracle/oracle.go b/pkg/services/oracle/oracle.go index 0868adeda2..649677bea6 100644 --- a/pkg/services/oracle/oracle.go +++ b/pkg/services/oracle/oracle.go @@ -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 } diff --git a/pkg/services/oracle/response.go b/pkg/services/oracle/response.go index 8cbd70d77d..b80520f1c2 100644 --- a/pkg/services/oracle/response.go +++ b/pkg/services/oracle/response.go @@ -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