diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 7a59940b1..37580f779 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -197,7 +197,14 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L // SetOracle sets oracle module. It doesn't protected by mutex and // must be called before `bc.Run()` to avoid data race. func (bc *Blockchain) SetOracle(mod services.Oracle) { - bc.contracts.Oracle.Module.Store(mod) + orc := bc.contracts.Oracle + md, ok := orc.GetMethod(manifest.MethodVerify, -1) + if !ok { + panic(fmt.Errorf("%s method not found", manifest.MethodVerify)) + } + mod.UpdateNativeContract(orc.NEF.Script, orc.GetOracleResponseScript(), + orc.Hash, md.MD.Offset) + orc.Module.Store(mod) bc.contracts.Designate.OracleService.Store(mod) } diff --git a/pkg/core/blockchainer/services/oracle.go b/pkg/core/blockchainer/services/oracle.go index 77b534415..102ea390b 100644 --- a/pkg/core/blockchainer/services/oracle.go +++ b/pkg/core/blockchainer/services/oracle.go @@ -3,6 +3,7 @@ package services import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/util" ) // Oracle specifies oracle service interface. @@ -13,6 +14,8 @@ type Oracle interface { RemoveRequests([]uint64) // UpdateOracleNodes updates oracle nodes. UpdateOracleNodes(keys.PublicKeys) + // UpdateNativeContract updates oracle contract native script and hash. + UpdateNativeContract([]byte, []byte, util.Uint160, int) // Run runs oracle module. Must be invoked in a separate goroutine. Run() // Shutdown shutdowns oracle module. diff --git a/pkg/core/oracle_test.go b/pkg/core/oracle_test.go index c767871d6..84e82ff81 100644 --- a/pkg/core/oracle_test.go +++ b/pkg/core/oracle_test.go @@ -30,7 +30,6 @@ import ( const oracleModulePath = "../services/oracle/" func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string) oracle.Config { - m := bc.contracts.Oracle.Manifest.ABI.GetMethod(manifest.MethodVerify, 0) return oracle.Config{ Log: zaptest.NewLogger(t), Network: netmode.UnitTestNet, @@ -41,12 +40,8 @@ func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string) oracle.Config Password: pass, }, }, - Chain: bc, - Client: newDefaultHTTPClient(), - OracleScript: bc.contracts.Oracle.NEF.Script, - OracleResponse: bc.contracts.Oracle.GetOracleResponseScript(), - OracleHash: bc.contracts.Oracle.Hash, - VerifyOffset: m.Offset, + Chain: bc, + Client: newDefaultHTTPClient(), } } @@ -100,6 +95,7 @@ func TestCreateResponseTx(t *testing.T) { } require.NoError(t, bc.contracts.Oracle.PutRequestInternal(1, req, bc.dao)) orc.UpdateOracleNodes(keys.PublicKeys{acc.PrivateKey().PublicKey()}) + bc.SetOracle(orc) tx, err := orc.CreateResponseTx(int64(req.GasForResponse), 1, resp) require.NoError(t, err) assert.Equal(t, 167, tx.Size()) @@ -130,6 +126,12 @@ func TestOracle(t *testing.T) { orc1.UpdateOracleNodes(oracleNodes.Copy()) orc2.UpdateOracleNodes(oracleNodes.Copy()) + orcNative := bc.contracts.Oracle + md, ok := orcNative.GetMethod(manifest.MethodVerify, -1) + require.True(t, ok) + orc1.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) + orc2.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) + cs := getOracleContractState(bc.contracts.Oracle.Hash) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) diff --git a/pkg/services/oracle/oracle.go b/pkg/services/oracle/oracle.go index 85afffb56..5a6fe8e2c 100644 --- a/pkg/services/oracle/oracle.go +++ b/pkg/services/oracle/oracle.go @@ -24,6 +24,12 @@ type ( Oracle struct { Config + // This fields are readonly thus not protected by mutex. + oracleHash util.Uint160 + oracleResponse []byte + oracleScript []byte + verifyOffset int + // mtx protects setting callbacks. mtx sync.RWMutex @@ -56,10 +62,6 @@ type ( ResponseHandler Broadcaster OnTransaction TxCallback URIValidator URIValidator - OracleScript []byte - OracleResponse []byte - VerifyOffset int - OracleHash util.Uint160 } // HTTPClient is an interface capable of doing oracle requests. @@ -203,6 +205,18 @@ func (o *Oracle) Run() { } } +// UpdateNativeContract updates native oracle contract info for tx verification. +func (o *Oracle) UpdateNativeContract(script, resp []byte, h util.Uint160, verifyOffset int) { + o.oracleScript = make([]byte, len(script)) + copy(o.oracleScript, script) + + o.oracleResponse = make([]byte, len(resp)) + copy(o.oracleResponse, resp) + + o.oracleHash = h + o.verifyOffset = verifyOffset +} + func (o *Oracle) getOnTransaction() TxCallback { o.mtx.RLock() defer o.mtx.RUnlock() diff --git a/pkg/services/oracle/response.go b/pkg/services/oracle/response.go index c8d8b2d5c..de9a20e38 100644 --- a/pkg/services/oracle/response.go +++ b/pkg/services/oracle/response.go @@ -82,7 +82,7 @@ func readResponse(rc gio.ReadCloser, limit int) ([]byte, error) { // CreateResponseTx creates unsigned oracle response transaction. func (o *Oracle) CreateResponseTx(gasForResponse int64, height uint32, resp *transaction.OracleResponse) (*transaction.Transaction, error) { - tx := transaction.New(o.Network, o.OracleResponse, 0) + tx := transaction.New(o.Network, o.oracleResponse, 0) tx.Nonce = uint32(resp.ID) tx.ValidUntilBlock = height + transaction.MaxValidUntilBlockIncrement tx.Attributes = []transaction.Attribute{{ @@ -93,7 +93,7 @@ func (o *Oracle) CreateResponseTx(gasForResponse int64, height uint32, resp *tra oracleSignContract := o.getOracleSignContract() tx.Signers = []transaction.Signer{ { - Account: o.OracleHash, + Account: o.oracleHash, Scopes: transaction.None, }, { @@ -136,8 +136,8 @@ func (o *Oracle) CreateResponseTx(gasForResponse int64, height uint32, resp *tra func (o *Oracle) testVerify(tx *transaction.Transaction) (int64, bool) { v := o.Chain.GetTestVM(trigger.Verification, tx, nil) v.GasLimit = o.Chain.GetPolicer().GetMaxVerificationGAS() - v.LoadScriptWithHash(o.OracleScript, o.OracleHash, callflag.ReadOnly) - v.Jump(v.Context(), o.VerifyOffset) + v.LoadScriptWithHash(o.oracleScript, o.oracleHash, callflag.ReadOnly) + v.Jump(v.Context(), o.verifyOffset) ok := isVerifyOk(v) return v.GasConsumed(), ok