diff --git a/cli/testdata/chain50x2.acc b/cli/testdata/chain50x2.acc index 93a3cc46f..1c8067f4e 100644 Binary files a/cli/testdata/chain50x2.acc and b/cli/testdata/chain50x2.acc differ diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 768c91367..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) } @@ -1753,7 +1760,6 @@ var ( // initVerificationVM initializes VM for witness check. func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, witness *transaction.Witness) error { - isNative := false v := ic.VM if len(witness.VerificationScript) != 0 { if witness.ScriptHash() != hash { @@ -1781,8 +1787,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, v.Context().NEF = &cs.NEF v.Jump(v.Context(), md.Offset) - isNative = cs.ID <= 0 - if !isNative && initMD != nil { + if initMD != nil { v.Call(v.Context(), initMD.Offset) } } @@ -1792,14 +1797,6 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, return fmt.Errorf("%w: %v", ErrInvalidInvocation, err) } v.LoadScript(witness.InvocationScript) - if isNative { - if err := v.StepOut(); err != nil { - return err - } - } - } - if isNative { - v.Estack().PushVal(manifest.MethodVerify) } return nil } diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 6c5ca3166..35cfa850a 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -25,6 +25,7 @@ import ( "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" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" @@ -586,7 +587,7 @@ func TestVerifyTx(t *testing.T) { w := io.NewBufBinWriter() emit.Opcodes(w.BinWriter, opcode.ABORT) emit.Bytes(w.BinWriter, util.Uint160{}.BytesBE()) - emit.Int(w.BinWriter, int64(orc.NEF.Checksum)) + emit.Int(w.BinWriter, 0) emit.String(w.BinWriter, orc.Manifest.Name) tx.Scripts[len(tx.Scripts)-1].VerificationScript = w.Bytes() err := bc.VerifyTx(tx) @@ -1000,7 +1001,7 @@ func TestVerifyTx(t *testing.T) { transaction.NotaryServiceFeePerKey + // fee for Notary attribute fee.Opcode(bc.GetBaseExecFee(), // Notary verification script opcode.PUSHDATA1, opcode.RET, // invocation script - opcode.PUSHINT8, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call + opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call native.NotaryVerificationPrice // Notary witness verification price tx.Scripts = []transaction.Witness{ { @@ -1197,7 +1198,8 @@ func TestIsTxStillRelevant(t *testing.T) { "github.com/nspcc-dev/neo-go/pkg/interop/util" ) func Verify() bool { - currentHeight := contract.Call(util.FromAddress("NV5WuMGkwhQexQ4afTwuRojMeWwfWrEAdv"), "currentIndex", contract.ReadStates) + addr := util.FromAddress("`+address.Uint160ToString(bc.contracts.Ledger.Hash)+`") + currentHeight := contract.Call(addr, "currentIndex", contract.ReadStates) return currentHeight.(int) < %d }`, bc.BlockHeight()+2) // deploy + next block txDeploy, h, err := testchain.NewDeployTx(bc, "TestVerify", neoOwner, strings.NewReader(src)) 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/interop/context.go b/pkg/core/interop/context.go index 8ce2cf65b..121cf23d5 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -4,19 +4,24 @@ import ( "errors" "fmt" "sort" + "strings" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/dao" + "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "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/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "go.uber.org/zap" ) @@ -86,6 +91,7 @@ type MethodAndPrice struct { Func Method MD *manifest.Method Price int64 + SyscallOffset int RequiredFlags callflag.CallFlag } @@ -101,21 +107,12 @@ type Contract interface { type ContractMD struct { state.NativeContract Name string - Methods map[MethodAndArgCount]MethodAndPrice -} - -// MethodAndArgCount represents method's signature. -type MethodAndArgCount struct { - Name string - ArgCount int + Methods []MethodAndPrice } // NewContractMD returns Contract with the specified list of methods. func NewContractMD(name string, id int32) *ContractMD { - c := &ContractMD{ - Name: name, - Methods: make(map[MethodAndArgCount]MethodAndPrice), - } + c := &ContractMD{Name: name} c.ID = id @@ -123,14 +120,32 @@ func NewContractMD(name string, id int32) *ContractMD { // Therefore values are taken from C# node. c.NEF.Header.Compiler = "neo-core-v3.0" c.NEF.Header.Magic = nef.Magic - c.NEF.Script = state.CreateNativeContractScript(id) - c.NEF.Checksum = c.NEF.CalculateChecksum() - c.Hash = state.CreateContractHash(util.Uint160{}, c.NEF.Checksum, name) + c.Hash = state.CreateContractHash(util.Uint160{}, 0, c.Name) c.Manifest = *manifest.DefaultManifest(name) return c } +// UpdateHash creates native contract script and updates hash. +func (c *ContractMD) UpdateHash() { + w := io.NewBufBinWriter() + for i := range c.Methods { + offset := w.Len() + c.Methods[i].MD.Offset = offset + c.Manifest.ABI.Methods[i].Offset = offset + emit.Int(w.BinWriter, 0) + c.Methods[i].SyscallOffset = w.Len() + emit.Syscall(w.BinWriter, interopnames.SystemContractCallNative) + emit.Opcodes(w.BinWriter, opcode.RET) + } + if w.Err != nil { + panic(fmt.Errorf("can't create native contract script: %w", w.Err)) + } + + c.NEF.Script = w.Bytes() + c.NEF.Checksum = c.NEF.CalculateChecksum() +} + // AddMethod adds new method to a native contract. func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method) { md.MD = desc @@ -147,21 +162,42 @@ func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method) { copy(c.Manifest.ABI.Methods[index+1:], c.Manifest.ABI.Methods[index:]) c.Manifest.ABI.Methods[index] = *desc - key := MethodAndArgCount{ - Name: desc.Name, - ArgCount: len(desc.Parameters), + // Cache follows the same order. + c.Methods = append(c.Methods, MethodAndPrice{}) + copy(c.Methods[index+1:], c.Methods[index:]) + c.Methods[index] = *md +} + +// GetMethodByOffset returns with the provided offset. +// Offset is offset of `System.Contract.CallNative` syscall. +func (c *ContractMD) GetMethodByOffset(offset int) (MethodAndPrice, bool) { + for k := range c.Methods { + if c.Methods[k].SyscallOffset == offset { + return c.Methods[k], true + } } - c.Methods[key] = *md + return MethodAndPrice{}, false } // GetMethod returns method `name` with specified number of parameters. func (c *ContractMD) GetMethod(name string, paramCount int) (MethodAndPrice, bool) { - key := MethodAndArgCount{ - Name: name, - ArgCount: paramCount, + index := sort.Search(len(c.Methods), func(i int) bool { + md := c.Methods[i] + res := strings.Compare(name, md.MD.Name) + switch res { + case -1, 1: + return res == -1 + default: + return paramCount <= len(md.MD.Parameters) + } + }) + if index < len(c.Methods) { + md := c.Methods[index] + if md.MD.Name == name && (paramCount == -1 || len(md.MD.Parameters) == paramCount) { + return md, true + } } - mp, ok := c.Methods[key] - return mp, ok + return MethodAndPrice{}, false } // AddEvent adds new event to a native contract. diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index c84dcfc64..2bb689138 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -98,24 +98,13 @@ func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contra } ic.VM.Invocations[cs.Hash]++ - ic.VM.LoadScriptWithCallingHash(caller, cs.NEF.Script, cs.Hash, ic.VM.Context().GetCallFlags()&f, true, uint16(len(args))) + ic.VM.LoadScriptWithCallingHash(caller, cs.NEF.Script, cs.Hash, ic.VM.Context().GetCallFlags()&f, hasReturn, uint16(len(args))) ic.VM.Context().NEF = &cs.NEF - var isNative bool - for i := range ic.Natives { - if ic.Natives[i].Metadata().Hash.Equals(cs.Hash) { - isNative = true - break - } - } for i := len(args) - 1; i >= 0; i-- { ic.VM.Estack().PushVal(args[i]) } - if isNative { - ic.VM.Estack().PushVal(name) - } else { - // use Jump not Call here because context was loaded in LoadScript above. - ic.VM.Jump(ic.VM.Context(), md.Offset) - } + // use Jump not Call here because context was loaded in LoadScript above. + ic.VM.Jump(ic.VM.Context(), md.Offset) if hasReturn { ic.VM.Context().RetCount = 1 } else { diff --git a/pkg/core/native/compatibility_test.go b/pkg/core/native/compatibility_test.go index d055628be..1b8e3c37c 100644 --- a/pkg/core/native/compatibility_test.go +++ b/pkg/core/native/compatibility_test.go @@ -12,8 +12,8 @@ func TestNamesASCII(t *testing.T) { cs := NewContracts(true) for _, c := range cs.Contracts { require.True(t, isASCII(c.Metadata().Name)) - for m := range c.Metadata().Methods { - require.True(t, isASCII(m.Name)) + for _, m := range c.Metadata().Methods { + require.True(t, isASCII(m.MD.Name)) } for _, e := range c.Metadata().Manifest.ABI.Events { require.True(t, isASCII(e.Name)) diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index 3b6dfa391..490376e92 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -81,6 +81,7 @@ func (s *Designate) isValidRole(r Role) bool { func newDesignate(p2pSigExtensionsEnabled bool) *Designate { s := &Designate{ContractMD: *interop.NewContractMD(nativenames.Designation, designateContractID)} s.p2pSigExtensionsEnabled = p2pSigExtensionsEnabled + defer s.UpdateHash() desc := newDescriptor("getDesignatedByRole", smartcontract.ArrayType, manifest.NewParameter("role", smartcontract.IntegerType), diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 340b0d3c9..99853eded 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -12,28 +12,27 @@ import ( // Call calls specified native contract method. func Call(ic *interop.Context) error { - id := int32(ic.VM.Estack().Pop().BigInt().Int64()) + version := ic.VM.Estack().Pop().BigInt().Int64() + if version != 0 { + return fmt.Errorf("native contract of version %d is not active", version) + } var c interop.Contract for _, ctr := range ic.Natives { - if ctr.Metadata().ID == id { + if ctr.Metadata().Hash == ic.VM.GetCurrentScriptHash() { c = ctr break } } if c == nil { - return fmt.Errorf("native contract %d not found", id) + return fmt.Errorf("native contract %d not found", version) } - h := ic.VM.GetCurrentScriptHash() - if !h.Equals(c.Metadata().Hash) { - return errors.New("it is not allowed to use Neo.Native.Call directly to call native contracts. System.Contract.Call should be used") - } - operation := ic.VM.Estack().Pop().String() - m, ok := c.Metadata().GetMethod(operation, ic.VM.Estack().Len()) + m, ok := c.Metadata().GetMethodByOffset(ic.VM.Context().IP()) if !ok { - return fmt.Errorf("method %s not found", operation) + return fmt.Errorf("method not found") } if !ic.VM.Context().GetCallFlags().Has(m.RequiredFlags) { - return fmt.Errorf("missing call flags for native %d `%s` operation call: %05b vs %05b", id, operation, ic.VM.Context().GetCallFlags(), m.RequiredFlags) + return fmt.Errorf("missing call flags for native %d `%s` operation call: %05b vs %05b", + version, m.MD.Name, ic.VM.Context().GetCallFlags(), m.RequiredFlags) } // Native contract prices are not multiplied by `BaseExecFee`. if !ic.VM.AddGas(m.Price) { diff --git a/pkg/core/native/ledger.go b/pkg/core/native/ledger.go index 15817910d..60ea77594 100644 --- a/pkg/core/native/ledger.go +++ b/pkg/core/native/ledger.go @@ -39,6 +39,8 @@ func newLedger() *Ledger { var l = &Ledger{ ContractMD: *interop.NewContractMD(nativenames.Ledger, ledgerContractID), } + defer l.UpdateHash() + desc := newDescriptor("currentHash", smartcontract.Hash256Type) md := newMethodAndPrice(l.currentHash, 1000000, callflag.ReadStates) l.AddMethod(md, desc) diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 2a53f85fd..eb4582df2 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -65,6 +65,7 @@ func newManagement() *Management { ContractMD: *interop.NewContractMD(nativenames.Management, managementContractID), contracts: make(map[util.Uint160]*state.Contract), } + defer m.UpdateHash() desc := newDescriptor("getContract", smartcontract.ArrayType, manifest.NewParameter("hash", smartcontract.Hash160Type)) diff --git a/pkg/core/native/name_service.go b/pkg/core/native/name_service.go index 2a84c7599..ab724eb7c 100644 --- a/pkg/core/native/name_service.go +++ b/pkg/core/native/name_service.go @@ -100,6 +100,7 @@ func newNameService() *NameService { } n := &NameService{nonfungible: *nf} + defer n.UpdateHash() desc := newDescriptor("addRoot", smartcontract.VoidType, manifest.NewParameter("root", smartcontract.StringType)) diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 00111cea1..719706eb1 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -27,6 +27,8 @@ const initialGAS = 30000000 // newGAS returns GAS native contract. func newGAS() *GAS { g := &GAS{} + defer g.UpdateHash() + nep17 := newNEP17Native(nativenames.Gas, gasContractID) nep17.symbol = "GAS" nep17.decimals = 8 diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 546e11094..24ec29dd9 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -94,6 +94,8 @@ func makeValidatorKey(key *keys.PublicKey) []byte { // newNEO returns NEO native contract. func newNEO() *NEO { n := &NEO{} + defer n.UpdateHash() + nep17 := newNEP17Native(nativenames.Neo, neoContractID) nep17.symbol = "NEO" nep17.decimals = 0 diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index a1b7e3f86..8bfeecd68 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -54,6 +54,7 @@ var maxNotValidBeforeDeltaKey = []byte{10} // newNotary returns Notary native contract. func newNotary() *Notary { n := &Notary{ContractMD: *interop.NewContractMD(nativenames.Notary, notaryContractID)} + defer n.UpdateHash() desc := newDescriptor("onNEP17Payment", smartcontract.VoidType, manifest.NewParameter("from", smartcontract.Hash160Type), diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 12d1d7da4..cd6fce0ad 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -75,6 +75,7 @@ var ( func newOracle() *Oracle { o := &Oracle{ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID)} + defer o.UpdateHash() w := io.NewBufBinWriter() emit.Opcodes(w.BinWriter, opcode.NEWARRAY0) diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 91ed2f521..3b14d693f 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -86,6 +86,7 @@ var _ interop.Contract = (*Policy)(nil) // newPolicy returns Policy native contract. func newPolicy() *Policy { p := &Policy{ContractMD: *interop.NewContractMD(nativenames.Policy, policyContractID)} + defer p.UpdateHash() desc := newDescriptor("getMaxTransactionsPerBlock", smartcontract.IntegerType) md := newMethodAndPrice(p.getMaxTransactionsPerBlock, 1000000, callflag.ReadStates) diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index eea43082d..8032bfa35 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -10,7 +10,6 @@ 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/interop/contract" - "github.com/nspcc-dev/neo-go/pkg/core/native" "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/smartcontract" @@ -64,6 +63,8 @@ func newTestNative() *testNative { meta: *interop.NewContractMD("Test.Native.Sum", 0), blocks: make(chan uint32, 1), } + defer tn.meta.UpdateHash() + desc := &manifest.Method{ Name: "sum", Parameters: []manifest.Parameter{ @@ -220,27 +221,33 @@ func TestNativeContract_InvokeInternal(t *testing.T) { d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, chain.config.StateRootInHeader) ic := chain.newInteropContext(trigger.Application, d, nil, nil) - v := ic.SpawnVM() + + sumOffset := 0 + for _, md := range tn.Metadata().Methods { + if md.MD.Name == "sum" { + sumOffset = md.MD.Offset + break + } + } t.Run("fail, bad current script hash", func(t *testing.T) { - v.LoadScriptWithHash([]byte{1}, util.Uint160{1, 2, 3}, callflag.All) + v := ic.SpawnVM() + v.LoadScriptWithHash(tn.Metadata().NEF.Script, util.Uint160{1, 2, 3}, callflag.All) v.Estack().PushVal(14) v.Estack().PushVal(28) - v.Estack().PushVal("sum") - v.Estack().PushVal(tn.Metadata().Name) + v.Jump(v.Context(), sumOffset) // it's prohibited to call natives directly - require.Error(t, native.Call(ic)) + require.Error(t, v.Run()) }) t.Run("success", func(t *testing.T) { - v.LoadScriptWithHash([]byte{1}, tn.Metadata().Hash, callflag.All) + v := ic.SpawnVM() + v.LoadScriptWithHash(tn.Metadata().NEF.Script, tn.Metadata().Hash, callflag.All) v.Estack().PushVal(14) v.Estack().PushVal(28) - v.Estack().PushVal("sum") - v.Estack().PushVal(tn.Metadata().ID) - - require.NoError(t, native.Call(ic)) + v.Jump(v.Context(), sumOffset) + require.NoError(t, v.Run()) value := v.Estack().Pop().BigInt() require.Equal(t, int64(42), value.Int64()) diff --git a/pkg/core/oracle_test.go b/pkg/core/oracle_test.go index d7afc1f06..6cb63922c 100644 --- a/pkg/core/oracle_test.go +++ b/pkg/core/oracle_test.go @@ -19,6 +19,7 @@ import ( "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/services/oracle" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/assert" @@ -39,11 +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, + Chain: bc, + Client: newDefaultHTTPClient(), } } @@ -96,6 +94,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()) @@ -125,6 +124,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/core/state/contract.go b/pkg/core/state/contract.go index 7e0dd435c..8d918dd90 100644 --- a/pkg/core/state/contract.go +++ b/pkg/core/state/contract.go @@ -5,7 +5,6 @@ import ( "math" "math/big" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" @@ -135,14 +134,3 @@ func CreateContractHash(sender util.Uint160, checksum uint32, name string) util. } return hash.Hash160(w.Bytes()) } - -// CreateNativeContractScript returns script for the native contract. -func CreateNativeContractScript(id int32) []byte { - w := io.NewBufBinWriter() - emit.Int(w.BinWriter, int64(id)) - emit.Syscall(w.BinWriter, interopnames.SystemContractCallNative) - if w.Err != nil { - panic(w.Err) - } - return w.Bytes() -} diff --git a/pkg/interop/native/gas/gas.go b/pkg/interop/native/gas/gas.go index 226961c68..e93941429 100644 --- a/pkg/interop/native/gas/gas.go +++ b/pkg/interop/native/gas/gas.go @@ -6,7 +6,7 @@ import ( ) // Hash represents GAS contract hash. -const Hash = "\x28\xb3\xad\xab\x72\x69\xf9\xc2\x18\x1d\xb3\xcb\x74\x1e\xbf\x55\x19\x30\xe2\x70" +const Hash = "\xcf\x76\xe2\x8b\xd0\x06\x2c\x4a\x47\x8e\xe3\x55\x61\x01\x13\x19\xf3\xcf\xa4\xd2" // Symbol represents `symbol` method of GAS native contract. func Symbol() string { diff --git a/pkg/interop/native/ledger/ledger.go b/pkg/interop/native/ledger/ledger.go index fe9787c30..1f3a124ba 100644 --- a/pkg/interop/native/ledger/ledger.go +++ b/pkg/interop/native/ledger/ledger.go @@ -6,7 +6,7 @@ import ( ) // Hash represents Ledger contract hash. -const Hash = "\x64\x87\x5a\x12\xc6\x03\xc6\x1d\xec\xff\xdf\xe7\x88\xce\x10\xdd\xc6\x69\x1d\x97" +const Hash = "\xbe\xf2\x04\x31\x40\x36\x2a\x77\xc1\x50\x99\xc7\xe6\x4c\x12\xf7\x00\xb6\x65\xda" // CurrentHash represents `currentHash` method of Ledger native contract. func CurrentHash() interop.Hash256 { diff --git a/pkg/interop/native/management/management.go b/pkg/interop/native/management/management.go index a68949cb6..d65e9a1fe 100644 --- a/pkg/interop/native/management/management.go +++ b/pkg/interop/native/management/management.go @@ -6,7 +6,7 @@ import ( ) // Hash represents Management contract hash. -const Hash = "\x43\x0e\x9f\x6f\xb3\x13\xa8\xd3\xa2\xb7\x61\x3b\x67\x83\x09\xd1\xd7\xd7\x01\xa5" +const Hash = "\xfd\xa3\xfa\x43\x46\xea\x53\x2a\x25\x8f\xc4\x97\xdd\xad\xdb\x64\x37\xc9\xfd\xff" // Deploy represents `deploy` method of Management native contract. func Deploy(script, manifest []byte) *Contract { diff --git a/pkg/interop/native/nameservice/name_service.go b/pkg/interop/native/nameservice/name_service.go index 5374d040d..ebb24fd26 100644 --- a/pkg/interop/native/nameservice/name_service.go +++ b/pkg/interop/native/nameservice/name_service.go @@ -18,7 +18,7 @@ const ( ) // Hash represents NameService contract hash. -const Hash = "\x8c\x02\xb8\x43\x98\x6b\x3c\x44\x4f\xf8\x6a\xd5\xa9\x43\xfe\x8d\xb6\x24\xb5\xa2" +const Hash = "\x6b\x59\x2b\x87\x66\xcc\x45\x8e\xfa\x7a\x90\x47\x56\x62\xcd\x92\x03\xcf\x8f\x7a" // Symbol represents `symbol` method of NameService native contract. func Symbol() string { diff --git a/pkg/interop/native/neo/neo.go b/pkg/interop/native/neo/neo.go index 731ae088d..98c94a223 100644 --- a/pkg/interop/native/neo/neo.go +++ b/pkg/interop/native/neo/neo.go @@ -6,7 +6,7 @@ import ( ) // Hash represents NEO contract hash. -const Hash = "\x83\xab\x06\x79\xad\x55\xc0\x50\xa1\x3a\xd4\x3f\x59\x36\xea\x73\xf5\xeb\x1e\xf6" +const Hash = "\xf5\x63\xea\x40\xbc\x28\x3d\x4d\x0e\x05\xc4\x8e\xa3\x05\xb3\xf2\xa0\x73\x40\xef" // Symbol represents `symbol` method of NEO native contract. func Symbol() string { diff --git a/pkg/interop/native/notary/notary.go b/pkg/interop/native/notary/notary.go index f94c7159f..ed83dff4d 100644 --- a/pkg/interop/native/notary/notary.go +++ b/pkg/interop/native/notary/notary.go @@ -6,7 +6,7 @@ import ( ) // Hash represents Notary contract hash. -const Hash = "\x0c\xcf\x26\x94\x3f\xb5\xc9\xb6\x05\xe2\x06\xd2\xa2\x75\xbe\x3e\xa6\xa4\x75\xf4" +const Hash = "\x3b\xec\x35\x31\x11\x9b\xba\xd7\x6d\xd0\x44\x92\x0b\x0d\xe6\xc3\x19\x4f\xe1\xc1" // LockDepositUntil represents `lockDepositUntil` method of Notary native contract. func LockDepositUntil(addr interop.Hash160, till int) bool { diff --git a/pkg/interop/native/oracle/oracle.go b/pkg/interop/native/oracle/oracle.go index 356096d6d..06a8f438c 100644 --- a/pkg/interop/native/oracle/oracle.go +++ b/pkg/interop/native/oracle/oracle.go @@ -6,7 +6,7 @@ import ( ) // Hash represents Oracle contract hash. -const Hash = "\xee\x80\x4c\x14\x29\x68\xd4\x78\x8b\x8a\xff\x51\xda\xde\xdf\xcb\x42\xe7\xc0\x8d" +const Hash = "\x58\x87\x17\x11\x7e\x0a\xa8\x10\x72\xaf\xab\x71\xd2\xdd\x89\xfe\x7c\x4b\x92\xfe" // Request represents `request` method of Oracle native contract. func Request(url string, filter []byte, cb string, userData interface{}, gasForResponse int) { diff --git a/pkg/interop/native/policy/policy.go b/pkg/interop/native/policy/policy.go index 63184f70f..0539f23af 100644 --- a/pkg/interop/native/policy/policy.go +++ b/pkg/interop/native/policy/policy.go @@ -6,7 +6,7 @@ import ( ) // Hash represents Policy contract hash. -const Hash = "\xf2\xe2\x08\xed\xcd\x14\x6c\xbe\xe4\x67\x6e\xdf\x79\xb7\x5e\x50\x98\xd3\xbc\x79" +const Hash = "\x7b\xc6\x81\xc0\xa1\xf7\x1d\x54\x34\x57\xb6\x8b\xba\x8d\x5f\x9f\xdd\x4e\x5e\xcc" // GetMaxTransactionsPerBlock represents `getMaxTransactionsPerBlock` method of Policy native contract. func GetMaxTransactionsPerBlock() int { diff --git a/pkg/interop/native/roles/roles.go b/pkg/interop/native/roles/roles.go index daaa564a6..b095466fc 100644 --- a/pkg/interop/native/roles/roles.go +++ b/pkg/interop/native/roles/roles.go @@ -6,7 +6,7 @@ import ( ) // Hash represents RoleManagement contract hash. -const Hash = "\x02\x8b\x00\x50\x70\xb6\x0d\xf1\xc8\xe2\x09\x78\x7b\x49\xce\xbb\x71\x14\x7b\x59" +const Hash = "\xe2\x95\xe3\x91\x54\x4c\x17\x8a\xd9\x4f\x03\xec\x4d\xcd\xff\x78\x53\x4e\xcf\x49" // Role represents node role. type Role byte diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 78fab452a..c699832b0 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -685,7 +685,7 @@ func (c *Client) CalculateNotaryFee(nKeys uint8) (int64, error) { return int64((nKeys+1))*transaction.NotaryServiceFeePerKey + // fee for NotaryAssisted attribute fee.Opcode(baseExecFee, // Notary node witness opcode.PUSHDATA1, opcode.RET, // invocation script - opcode.PUSHINT8, opcode.SYSCALL, opcode.RET) + // System.Contract.CallNative + opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // System.Contract.CallNative native.NotaryVerificationPrice + // Notary witness verification price feePerByte*int64(io.GetVarSize(make([]byte, 66))) + // invocation script per-byte fee feePerByte*int64(io.GetVarSize([]byte{})), // verification script per-byte fee diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index fbeebd2a2..c643aa790 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -60,8 +60,8 @@ type rpcTestCase struct { } const testContractHash = "c6436aab21ebd15279b85af8d7b5808d38455b0a" -const deploymentTxHash = "d0de42d5d23211174a50d74fbd4a919631236a63f16431a5a7e7126759e7ba23" -const genesisBlockHash = "0542f4350c6e236d0509bcd98188b0034bfbecc1a0c7fcdb8e4295310d468b70" +const deploymentTxHash = "050e2189d7cd7b719d9c4bbc525d3edcd89ffedb28cc974862d17dda14377612" +const genesisBlockHash = "41d1099030004d60f3b4b7ffe00bf184e84fac3a39ee7136c761e005f0186d93" const verifyContractHash = "03ffc0897543b9b709e0f8cab4a7682dae0ba943" const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740" diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 48120fe74..b7bdeaecc 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ diff --git a/pkg/services/oracle/oracle.go b/pkg/services/oracle/oracle.go index 58e9d4b89..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,9 +62,6 @@ type ( ResponseHandler Broadcaster OnTransaction TxCallback URIValidator URIValidator - OracleScript []byte - OracleResponse []byte - OracleHash util.Uint160 } // HTTPClient is an interface capable of doing oracle requests. @@ -202,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 3991b0aef..de9a20e38 100644 --- a/pkg/services/oracle/response.go +++ b/pkg/services/oracle/response.go @@ -11,7 +11,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" - "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/vm" "go.uber.org/zap" @@ -83,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{{ @@ -94,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, }, { @@ -137,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.ReadStates) - v.Estack().PushVal(manifest.MethodVerify) + v.LoadScriptWithHash(o.oracleScript, o.oracleHash, callflag.ReadOnly) + v.Jump(v.Context(), o.verifyOffset) ok := isVerifyOk(v) return v.GasConsumed(), ok