From ab5128fb482a738c00c8b4bad21b981ee6ea101d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 24 Dec 2024 17:57:13 +0300 Subject: [PATCH 1/3] config: enable Echidna for tests Signed-off-by: Roman Khimov --- config/protocol.unit_testnet.yml | 1 + pkg/core/blockchain_neotest_test.go | 2 +- pkg/core/native/native_test/neo_test.go | 7 +++++++ pkg/services/rpcsrv/client_test.go | 1 + pkg/services/rpcsrv/server_test.go | 2 +- 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/config/protocol.unit_testnet.yml b/config/protocol.unit_testnet.yml index ff821d161d..84e26a4371 100644 --- a/config/protocol.unit_testnet.yml +++ b/config/protocol.unit_testnet.yml @@ -22,6 +22,7 @@ ProtocolConfiguration: Basilisk: 6 Cockatrice: 9 Domovoi: 12 + Echidna: 13 ApplicationConfiguration: SkipBlockVerification: false diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index 8b88ecf33d..79490f8386 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -272,7 +272,7 @@ func TestBlockchain_StartFromExistingDB(t *testing.T) { _, _, _, err = chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) require.Error(t, err) - require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native %s: version mismatch for the latest hardfork Domovoi (stored contract state differs from autogenerated one)", nativenames.CryptoLib)), err) + require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native %s: version mismatch for the latest hardfork Echidna (stored contract state differs from autogenerated one)", nativenames.CryptoLib)), err) }) t.Run("good", func(t *testing.T) { diff --git a/pkg/core/native/native_test/neo_test.go b/pkg/core/native/native_test/neo_test.go index 616208c9ab..49450362c2 100644 --- a/pkg/core/native/native_test/neo_test.go +++ b/pkg/core/native/native_test/neo_test.go @@ -740,6 +740,13 @@ func TestNEO_CalculateBonus(t *testing.T) { accH := acc.Signers[0].ScriptHash() rewardDistance := 10 + // We have 11 blocks made by transactions above and we need block 13 to get Echidna. + // Otherwise this happens: + // logger.go:146: 2024-12-24T17:52:18.160+0300 WARN contract invocation failed {"tx": "603d1b0e29e7aaf50513689be9d5bb946c7f7fec8836f0e90897c825fb762c13", "block": 13, "error": "at instruction 120 (SYSCALL): System.Contract.CallNative failed: gas limit exceeded"} + for range 3 { + e.AddNewBlock(t) + } + t.Run("Zero", func(t *testing.T) { initialGASBalance := e.Chain.GetUtilityTokenBalance(accH) for range rewardDistance { diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index 73a373928e..2a1731be3e 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -2461,6 +2461,7 @@ func TestClient_GetVersion_Hardforks(t *testing.T) { config.HFBasilisk: 6, config.HFCockatrice: 9, config.HFDomovoi: 12, + config.HFEchidna: 13, } require.InDeltaMapValues(t, expected, v.Protocol.Hardforks, 0) } diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index 117bd56f49..da4f7beeb2 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -121,7 +121,7 @@ const ( // not yet deployed to the testing basic chain. invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA" // block20StateRootLE is an LE stateroot of block #20 of basic testing chain. - block20StateRootLE = "570ba0814003f6e6a2a2e41d1b727f8af756e9c26d2453c8316868607b66da0a" + block20StateRootLE = "310acac4fd692ab7a90dbd7fcf6feaf1ac33aabeedf6592c4ddd08ff1dac15de" ) var ( From 4d45be8434f4f46da3f82269f727959f9844adec Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 24 Nov 2024 12:32:34 +0300 Subject: [PATCH 2/3] native: add candidate registration via onNEP17Payment Solves two problems: * inability to estimate GAS needed for registerCandidate in a regular way because of its very high fee (more than what normal RPC servers allow) * inability to have MaxBlockSystemFee lower than the registration price which is very high on its own (more than practically possible to execute) See https://github.com/neo-project/neo/issues/3552. Signed-off-by: Roman Khimov --- docs/node-configuration.md | 2 +- pkg/core/native/native_neo.go | 52 ++++++++++-- .../native/native_test/management_test.go | 1 + pkg/core/native/native_test/neo_test.go | 83 +++++++++++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) diff --git a/docs/node-configuration.md b/docs/node-configuration.md index 3897dda6e2..26565f9e7f 100644 --- a/docs/node-configuration.md +++ b/docs/node-configuration.md @@ -470,7 +470,7 @@ in development and can change in an incompatible way. | `Basilisk` | Enables strict smart contract script check against a set of JMP instructions and against method boundaries enabled on contract deploy or update. Increases `stackitem.Integer` JSON parsing precision up to the maximum value supported by the NeoVM. Enables strict check for notifications emitted by a contract to precisely match the events specified in the contract manifest. | https://github.com/nspcc-dev/neo-go/pull/3056
https://github.com/neo-project/neo/pull/2881
https://github.com/nspcc-dev/neo-go/pull/3080
https://github.com/neo-project/neo/pull/2883
https://github.com/nspcc-dev/neo-go/pull/3085
https://github.com/neo-project/neo/pull/2810 | | `Cockatrice` | Introduces the ability to update native contracts. Includes a couple of new native smart contract APIs: `keccak256` of native CryptoLib contract and `getCommitteeAddress` of native NeoToken contract. | https://github.com/nspcc-dev/neo-go/pull/3402
https://github.com/neo-project/neo/pull/2942
https://github.com/nspcc-dev/neo-go/pull/3301
https://github.com/neo-project/neo/pull/2925
https://github.com/nspcc-dev/neo-go/pull/3362
https://github.com/neo-project/neo/pull/3154 | | `Domovoi` | Makes node use executing contract state for the contract call permissions check instead of the state stored in the native Management contract. In C# also makes System.Runtime.GetNotifications interop properly count stack references of notification parameters which prevents users from creating objects that exceed MaxStackSize constraint, but NeoGo has never had this bug, thus proper behaviour is preserved even before HFDomovoi. It results in the fact that some T5 testnet transactions have different ApplicationLogs compared to the C# node, but the node states match. | https://github.com/nspcc-dev/neo-go/pull/3476
https://github.com/neo-project/neo/pull/3290
https://github.com/nspcc-dev/neo-go/pull/3473
https://github.com/neo-project/neo/pull/3290
https://github.com/neo-project/neo/pull/3301
https://github.com/nspcc-dev/neo-go/pull/3485 | -| `Echidna` | Introduces `Designation` event extension with `Old` and `New` roles data to native RoleManagement contract. Adds support for `base64UrlEncode` and `base64UrlDecode` methods to native StdLib contract. Extends the list of required call flags for `registerCandidate`, `unregisterCandidate`and `vote` methods of native NeoToken contract with AllowNotify flag. | https://github.com/nspcc-dev/neo-go/pull/3554
https://github.com/nspcc-dev/neo-go/pull/3761 | +| `Echidna` | Introduces `Designation` event extension with `Old` and `New` roles data to native RoleManagement contract. Adds support for `base64UrlEncode` and `base64UrlDecode` methods to native StdLib contract. Extends the list of required call flags for `registerCandidate`, `unregisterCandidate`and `vote` methods of native NeoToken contract with AllowNotify flag. Enables `onNEP17Payment` method of NEO contract for candidate registration. | https://github.com/nspcc-dev/neo-go/pull/3554
https://github.com/nspcc-dev/neo-go/pull/3761
https://github.com/nspcc-dev/neo-go/pull/3554
https://github.com/neo-project/neo/pull/3597
https://github.com/nspcc-dev/neo-go/pull/3700 | ## DB compatibility diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index d4e507826e..93352bd7a7 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -206,6 +206,13 @@ func newNEO(cfg config.ProtocolConfiguration) *NEO { md = newMethodAndPrice(n.unregisterCandidate, 1<<16, callflag.States|callflag.AllowNotify, config.HFEchidna) n.AddMethod(md, desc) + desc = newDescriptor("onNEP17Payment", smartcontract.VoidType, + manifest.NewParameter("from", smartcontract.Hash160Type), + manifest.NewParameter("amount", smartcontract.IntegerType), + manifest.NewParameter("data", smartcontract.AnyType)) + md = newMethodAndPrice(n.onNEP17Payment, 1<<15, callflag.States|callflag.AllowNotify, config.HFEchidna) + n.AddMethod(md, desc) + desc = newDescriptor("vote", smartcontract.BoolType, manifest.NewParameter("account", smartcontract.Hash160Type), manifest.NewParameter("voteTo", smartcontract.PublicKeyType)) @@ -824,17 +831,52 @@ func (n *NEO) CalculateNEOHolderReward(d *dao.Simple, value *big.Int, start, end func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item { pub := toPublicKey(args[0]) + if !ic.IsHardforkEnabled(config.HFEchidna) { + ok, err := runtime.CheckKeyedWitness(ic, pub) + if err != nil { + panic(err) + } else if !ok { + return stackitem.NewBool(false) + } + } + if !ic.VM.AddGas(n.getRegisterPriceInternal(ic.DAO)) { + panic("insufficient gas") + } + var err = n.RegisterCandidateInternal(ic, pub) + return stackitem.NewBool(err == nil) +} + +func (n *NEO) checkRegisterCandidate(ic *interop.Context, pub *keys.PublicKey) error { ok, err := runtime.CheckKeyedWitness(ic, pub) if err != nil { panic(err) } else if !ok { - return stackitem.NewBool(false) + return errors.New("not witnessed by the key owner") } - if !ic.VM.AddGas(n.getRegisterPriceInternal(ic.DAO)) { - panic("insufficient gas") + return n.RegisterCandidateInternal(ic, pub) +} + +func (n *NEO) onNEP17Payment(ic *interop.Context, args []stackitem.Item) stackitem.Item { + var ( + caller = ic.VM.GetCallingScriptHash() + _ = toUint160(args[0]) + amount = toBigInt(args[1]) + pub = toPublicKey(args[2]) + regPrice = n.getRegisterPriceInternal(ic.DAO) + ) + + if caller != n.GAS.Hash { + panic("only GAS is accepted") } - err = n.RegisterCandidateInternal(ic, pub) - return stackitem.NewBool(err == nil) + if !amount.IsInt64() || amount.Int64() != regPrice { + panic(fmt.Errorf("incorrect GAS amount for registration (expected %d)", regPrice)) + } + var err = n.checkRegisterCandidate(ic, pub) + if err != nil { + panic(err) + } + n.GAS.burn(ic, n.Hash, amount) + return stackitem.Null{} } // RegisterCandidateInternal registers pub as a new candidate. diff --git a/pkg/core/native/native_test/management_test.go b/pkg/core/native/native_test/management_test.go index 09ef1b2c12..77f5d71952 100644 --- a/pkg/core/native/native_test/management_test.go +++ b/pkg/core/native/native_test/management_test.go @@ -57,6 +57,7 @@ var ( nativenames.Neo: `{"id":-5,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1325686241},"manifest":{"name":"NeoToken","abi":{"methods":[{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"getAccountState","offset":14,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getAllCandidates","offset":21,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"getCandidateVote","offset":28,"parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","safe":true},{"name":"getCandidates","offset":35,"parameters":[],"returntype":"Array","safe":true},{"name":"getCommittee","offset":42,"parameters":[],"returntype":"Array","safe":true},{"name":"getCommitteeAddress","offset":49,"parameters":[],"returntype":"Hash160","safe":true},{"name":"getGasPerBlock","offset":56,"parameters":[],"returntype":"Integer","safe":true},{"name":"getNextBlockValidators","offset":63,"parameters":[],"returntype":"Array","safe":true},{"name":"getRegisterPrice","offset":70,"parameters":[],"returntype":"Integer","safe":true},{"name":"registerCandidate","offset":77,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"setGasPerBlock","offset":84,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRegisterPrice","offset":91,"parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","safe":false},{"name":"symbol","offset":98,"parameters":[],"returntype":"String","safe":true},{"name":"totalSupply","offset":105,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":112,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"unclaimedGas","offset":119,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"unregisterCandidate","offset":126,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"vote","offset":133,"parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-17"],"trusts":[],"extra":null},"updatecounter":0}`, } echidnaCSS = map[string]string{ + nativenames.Neo: `{"id":-5,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1991619121},"manifest":{"name":"NeoToken","abi":{"methods":[{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"getAccountState","offset":14,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getAllCandidates","offset":21,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"getCandidateVote","offset":28,"parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","safe":true},{"name":"getCandidates","offset":35,"parameters":[],"returntype":"Array","safe":true},{"name":"getCommittee","offset":42,"parameters":[],"returntype":"Array","safe":true},{"name":"getCommitteeAddress","offset":49,"parameters":[],"returntype":"Hash160","safe":true},{"name":"getGasPerBlock","offset":56,"parameters":[],"returntype":"Integer","safe":true},{"name":"getNextBlockValidators","offset":63,"parameters":[],"returntype":"Array","safe":true},{"name":"getRegisterPrice","offset":70,"parameters":[],"returntype":"Integer","safe":true},{"name":"onNEP17Payment","offset":77,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"registerCandidate","offset":84,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"setGasPerBlock","offset":91,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRegisterPrice","offset":98,"parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","safe":false},{"name":"symbol","offset":105,"parameters":[],"returntype":"String","safe":true},{"name":"totalSupply","offset":112,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":119,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"unclaimedGas","offset":126,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"unregisterCandidate","offset":133,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"vote","offset":140,"parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-17"],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.StdLib: `{"id":-2,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2681632925},"manifest":{"name":"StdLib","abi":{"methods":[{"name":"atoi","offset":0,"parameters":[{"name":"value","type":"String"}],"returntype":"Integer","safe":true},{"name":"atoi","offset":7,"parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"base58CheckDecode","offset":14,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base58CheckEncode","offset":21,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base58Decode","offset":28,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base58Encode","offset":35,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base64Decode","offset":42,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base64Encode","offset":49,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base64UrlDecode","offset":56,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base64UrlEncode","offset":63,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"deserialize","offset":70,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"itoa","offset":77,"parameters":[{"name":"value","type":"Integer"}],"returntype":"String","safe":true},{"name":"itoa","offset":84,"parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","safe":true},{"name":"jsonDeserialize","offset":91,"parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"jsonSerialize","offset":98,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"memoryCompare","offset":105,"parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":112,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":119,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":126,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","safe":true},{"name":"serialize","offset":133,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"strLen","offset":140,"parameters":[{"name":"str","type":"String"}],"returntype":"Integer","safe":true},{"name":"stringSplit","offset":147,"parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","safe":true},{"name":"stringSplit","offset":154,"parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.Designation: `{"id":-8,"hash":"0x49cf4e5378ffcd4dec034fd98a174c5491e395e2","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0A=","checksum":983638438},"manifest":{"name":"RoleManagement","abi":{"methods":[{"name":"designateAsRole","offset":0,"parameters":[{"name":"role","type":"Integer"},{"name":"nodes","type":"Array"}],"returntype":"Void","safe":false},{"name":"getDesignatedByRole","offset":7,"parameters":[{"name":"role","type":"Integer"},{"name":"index","type":"Integer"}],"returntype":"Array","safe":true}],"events":[{"name":"Designation","parameters":[{"name":"Role","type":"Integer"},{"name":"BlockIndex","type":"Integer"},{"name":"Old","type":"Array"},{"name":"New","type":"Array"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, } diff --git a/pkg/core/native/native_test/neo_test.go b/pkg/core/native/native_test/neo_test.go index 49450362c2..e5edf39192 100644 --- a/pkg/core/native/native_test/neo_test.go +++ b/pkg/core/native/native_test/neo_test.go @@ -907,3 +907,86 @@ func TestNEO_GetCandidates(t *testing.T) { neoCommitteeInvoker.Invoke(t, expected, "getCandidates") checkGetAllCandidates(t, expected) } + +func TestNEO_RegisterViaNEP27(t *testing.T) { + neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000) + neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator) + e := neoCommitteeInvoker.Executor + neoHash := e.NativeHash(t, nativenames.Neo) + + cfg := e.Chain.GetConfig() + candidatesCount := cfg.GetCommitteeSize(0) - 1 + + // Register a set of candidates and vote for them. + voters := make([]neotest.Signer, candidatesCount) + candidates := make([]neotest.Signer, candidatesCount) + for i := range candidatesCount { + voters[i] = e.NewAccount(t, 2000_0000_0000) // enough for one registration + candidates[i] = e.NewAccount(t, 2000_0000_0000) + } + + stack, err := neoCommitteeInvoker.TestInvoke(t, "getRegisterPrice") + require.NoError(t, err) + registrationPrice, err := stack.Pop().Item().TryInteger() + require.NoError(t, err) + + // We have 11 blocks made by transactions above and we need block 13 to get Echidna. + for range 3 { + e.AddNewBlock(t) + } + + gasValidatorsInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas)) + txes := make([]*transaction.Transaction, 0, candidatesCount*3) + for i := range candidatesCount { + transferTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(candidatesCount+1-i)*1000000, nil) + txes = append(txes, transferTx) + registerTx := gasValidatorsInvoker.WithSigners(candidates[i]).PrepareInvoke(t, "transfer", candidates[i].(neotest.SingleSigner).Account().ScriptHash(), neoHash, registrationPrice, candidates[i].(neotest.SingleSigner).Account().PublicKey().Bytes()) + txes = append(txes, registerTx) + voteTx := neoValidatorsInvoker.WithSigners(voters[i]).PrepareInvoke(t, "vote", voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[i].(neotest.SingleSigner).Account().PublicKey().Bytes()) + txes = append(txes, voteTx) + } + + neoValidatorsInvoker.AddNewBlock(t, txes...) + for _, tx := range txes { + e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) // luckily, both `transfer` and `vote` return boolean values + } + + // Ensure NEO holds no GAS. + stack, err = gasValidatorsInvoker.TestInvoke(t, "balanceOf", neoHash) + require.NoError(t, err) + balance, err := stack.Pop().Item().TryInteger() + require.NoError(t, err) + require.Equal(t, 0, balance.Sign()) + + var expected = make([]stackitem.Item, candidatesCount) + for i := range expected { + pub := candidates[i].(neotest.SingleSigner).Account().PublicKey().Bytes() + v := stackitem.NewBigInteger(big.NewInt(int64(candidatesCount-i+1) * 1000000)) + expected[i] = stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray(pub), + v, + }) + neoCommitteeInvoker.Invoke(t, v, "getCandidateVote", pub) + } + + slices.SortFunc(expected, func(a, b stackitem.Item) int { + return bytes.Compare(a.Value().([]stackitem.Item)[0].Value().([]byte), b.Value().([]stackitem.Item)[0].Value().([]byte)) + }) + + neoCommitteeInvoker.Invoke(t, stackitem.NewArray(expected), "getCandidates") + + // Invalid cases. + var newCand = voters[0] + + // Missing data. + gasValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "invalid conversion", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, registrationPrice, nil) + // Invalid data. + gasValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "unexpected EOF", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, registrationPrice, []byte{2, 2, 2}) + // NEO transfer. + neoValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "only GAS is accepted", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, 1, newCand.(neotest.SingleSigner).Account().PublicKey().Bytes()) + // Incorrect amount. + gasValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "incorrect GAS amount", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, 1, newCand.(neotest.SingleSigner).Account().PublicKey().Bytes()) + // Incorrect witness. + var anotherAcc = e.NewAccount(t, 2000_0000_0000) + gasValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "not witnessed by the key owner", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, registrationPrice, anotherAcc.(neotest.SingleSigner).Account().PublicKey().Bytes()) +} From 38b9b1309816f864fd155f6ea1fced4c14d562be Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 22 Dec 2024 19:31:53 +0300 Subject: [PATCH 3/3] blockfetcher: make UT work with AIO started Connection succeeds when AIO is running, some more obscure port is less likely to cause this problem. Signed-off-by: Roman Khimov --- pkg/services/blockfetcher/blockfetcher_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/blockfetcher/blockfetcher_test.go b/pkg/services/blockfetcher/blockfetcher_test.go index dedb07479c..970a5d456e 100644 --- a/pkg/services/blockfetcher/blockfetcher_test.go +++ b/pkg/services/blockfetcher/blockfetcher_test.go @@ -85,7 +85,7 @@ func TestServiceConstructor(t *testing.T) { InternalService: config.InternalService{ Enabled: true, }, - Addresses: []string{"localhost:8080"}, + Addresses: []string{"localhost:1"}, } service, err := New(ledger, cfg, logger, mockPut.putBlock, shutdownCallback) require.NoError(t, err)