From c4a324db8f4ae71453b29e25c889bd72a61f3e73 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 5 Jun 2024 17:30:10 +0300 Subject: [PATCH] core: move Notary contract under Domovoi hardfork Close #3464. Adjust tests, enable all hardforks for RPC server tests starting from genesis. Signed-off-by: Anna Shaleva --- internal/basicchain/basic.go | 7 + pkg/core/blockchain_neotest_test.go | 2 +- .../native/native_test/management_test.go | 133 ++++++++++++++++-- pkg/core/native/notary.go | 7 +- pkg/services/rpcsrv/client_test.go | 9 +- pkg/services/rpcsrv/server_helper_test.go | 1 + pkg/services/rpcsrv/server_test.go | 30 ++-- pkg/services/rpcsrv/testdata/testblocks.acc | Bin 35787 -> 35697 bytes 8 files changed, 158 insertions(+), 31 deletions(-) diff --git a/internal/basicchain/basic.go b/internal/basicchain/basic.go index 812012aa0..fd16ca50b 100644 --- a/internal/basicchain/basic.go +++ b/internal/basicchain/basic.go @@ -8,6 +8,7 @@ import ( "path/filepath" "testing" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" @@ -81,6 +82,11 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) { t.Fatal("P2PSigExtensions should be enabled to init basic chain") } + const notaryDepositHeight uint32 = 8 + domovoiH, ok := e.Chain.GetConfig().Hardforks[config.HFDomovoi.String()] + require.Truef(t, ok, "%s hardfork should be enabled since basic chain uses Notary contract", config.HFDomovoi.String()) + require.LessOrEqualf(t, domovoiH, notaryDepositHeight-1, "%s hardfork should be enabled starting from height %d, got: %d", config.HFDomovoi.String(), notaryDepositHeight-1, domovoiH) + var ( // examplesPrefix is a prefix of the example smart-contracts. examplesPrefix = filepath.Join(rootpath, "examples") @@ -189,6 +195,7 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) { // Block #8: deposit some GAS to notary contract for priv0. transferTxH = gasPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, notaryHash, 10_0000_0000, []any{priv0ScriptHash, int64(e.Chain.BlockHeight() + 1000)}) t.Logf("notaryDepositTxPriv0: %v", transferTxH.StringLE()) + require.Equal(t, uint32(notaryDepositHeight), e.Chain.BlockHeight(), "notaryDepositHeight constant is out of date") // Block #9: designate new Notary node. ntr, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, "./testdata/notary1.json")) diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index 5d0593052..1dc1a5e5f 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -2572,7 +2572,7 @@ func TestBlockchain_GenesisTransactionExtension(t *testing.T) { // in the right order. func TestNativenames(t *testing.T) { bc, _ := chain.NewSingleWithCustomConfig(t, func(cfg *config.Blockchain) { - cfg.Hardforks = map[string]uint32{} + cfg.Hardforks = nil // default (all hardforks enabled) behaviour. cfg.P2PSigExtensions = true }) natives := bc.GetNatives() diff --git a/pkg/core/native/native_test/management_test.go b/pkg/core/native/native_test/management_test.go index 9870e81f0..6a4f79d0d 100644 --- a/pkg/core/native/native_test/management_test.go +++ b/pkg/core/native/native_test/management_test.go @@ -47,7 +47,6 @@ var ( nativenames.Policy: `{"id":-7,"hash":"0xcc5e4edd9f5f8dba8bb65734541df7a1c081c67b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"PolicyContract","abi":{"methods":[{"name":"blockAccount","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"getAttributeFee","offset":7,"parameters":[{"name":"attributeType","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"getExecFeeFactor","offset":14,"parameters":[],"returntype":"Integer","safe":true},{"name":"getFeePerByte","offset":21,"parameters":[],"returntype":"Integer","safe":true},{"name":"getStoragePrice","offset":28,"parameters":[],"returntype":"Integer","safe":true},{"name":"isBlocked","offset":35,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"setAttributeFee","offset":42,"parameters":[{"name":"attributeType","type":"Integer"},{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setExecFeeFactor","offset":49,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setFeePerByte","offset":56,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setStoragePrice","offset":63,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"unblockAccount","offset":70,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","safe":false}],"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"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.Oracle: `{"id":-9,"hash":"0xfe924b7cfe89ddd271abaf7210a80a7e11178758","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2663858513},"manifest":{"name":"OracleContract","abi":{"methods":[{"name":"finish","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"getPrice","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"request","offset":14,"parameters":[{"name":"url","type":"String"},{"name":"filter","type":"String"},{"name":"callback","type":"String"},{"name":"userData","type":"Any"},{"name":"gasForResponse","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setPrice","offset":21,"parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","safe":false},{"name":"verify","offset":28,"parameters":[],"returntype":"Boolean","safe":true}],"events":[{"name":"OracleRequest","parameters":[{"name":"Id","type":"Integer"},{"name":"RequestContract","type":"Hash160"},{"name":"Url","type":"String"},{"name":"Filter","type":"String"}]},{"name":"OracleResponse","parameters":[{"name":"Id","type":"Integer"},{"name":"OriginalTx","type":"Hash256"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, - nativenames.Notary: `{"id":-10,"hash":"0xc1e14f19c3e60d0b9244d06dd7ba9b113135ec3b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"Notary","abi":{"methods":[{"name":"balanceOf","offset":0,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"expirationOf","offset":7,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"getMaxNotValidBeforeDelta","offset":14,"parameters":[],"returntype":"Integer","safe":true},{"name":"lockDepositUntil","offset":21,"parameters":[{"name":"address","type":"Hash160"},{"name":"till","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"onNEP17Payment","offset":28,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"setMaxNotValidBeforeDelta","offset":35,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"verify","offset":42,"parameters":[{"name":"signature","type":"Signature"}],"returntype":"Boolean","safe":true},{"name":"withdraw","offset":49,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"}],"returntype":"Boolean","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, } // cockatriceCSS holds serialized native contract states built for genesis block (with UpdateCounter 0) // under assumption that hardforks from Aspidochelone to Cockatrice (included) are enabled. @@ -55,6 +54,11 @@ var ( nativenames.CryptoLib: `{"id":-3,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"CryptoLib","abi":{"methods":[{"name":"bls12381Add","offset":0,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Deserialize","offset":7,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Equal","offset":14,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","safe":true},{"name":"bls12381Mul","offset":21,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Pairing","offset":28,"parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Serialize","offset":35,"parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","safe":true},{"name":"keccak256","offset":42,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"murmur32","offset":49,"parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","safe":true},{"name":"ripemd160","offset":56,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"sha256","offset":63,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"verifyWithECDsa","offset":70,"parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curveHash","type":"Integer"}],"returntype":"Boolean","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, 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}`, } + // domovoiCSS holds serialized native contract states built for genesis block (with UpdateCounter 0) + // under assumption that hardforks from Aspidochelone to Domovoi (included) are enabled. + domovoiCSS = map[string]string{ + nativenames.Notary: `{"id":-10,"hash":"0xc1e14f19c3e60d0b9244d06dd7ba9b113135ec3b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"Notary","abi":{"methods":[{"name":"balanceOf","offset":0,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"expirationOf","offset":7,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"getMaxNotValidBeforeDelta","offset":14,"parameters":[],"returntype":"Integer","safe":true},{"name":"lockDepositUntil","offset":21,"parameters":[{"name":"address","type":"Hash160"},{"name":"till","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"onNEP17Payment","offset":28,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"setMaxNotValidBeforeDelta","offset":35,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"verify","offset":42,"parameters":[{"name":"signature","type":"Signature"}],"returntype":"Boolean","safe":true},{"name":"withdraw","offset":49,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"}],"returntype":"Boolean","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, + } ) func init() { @@ -63,6 +67,11 @@ func init() { cockatriceCSS[k] = v } } + for k, v := range cockatriceCSS { + if _, ok := domovoiCSS[k]; !ok { + domovoiCSS[k] = v + } + } } func newManagementClient(t *testing.T) *neotest.ContractInvoker { @@ -91,6 +100,10 @@ func TestManagement_GenesisNativeState(t *testing.T) { h := state.CreateNativeContractHash(name) c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { si := stack[0] + if _, ok := expected[name]; !ok { + require.Equal(t, stackitem.Null{}, si, fmt.Errorf("contract %s state found", name)) + return + } var cs = &state.Contract{} require.NoError(t, cs.FromStackItem(si), name) jBytes, err := ojson.Marshal(cs) @@ -113,6 +126,7 @@ func TestManagement_GenesisNativeState(t *testing.T) { config.HFAspidochelone.String(): 100500, config.HFBasilisk.String(): 100500, config.HFCockatrice.String(): 100500, + config.HFDomovoi.String(): 100500, } cfg.P2PSigExtensions = true }) @@ -148,15 +162,31 @@ func TestManagement_GenesisNativeState(t *testing.T) { }) check(t, mgmt, cockatriceCSS) }) + t.Run("Domovoi enabled", func(t *testing.T) { + mgmt := newCustomManagementClient(t, func(cfg *config.Blockchain) { + cfg.Hardforks = map[string]uint32{ + config.HFAspidochelone.String(): 0, + config.HFBasilisk.String(): 0, + config.HFCockatrice.String(): 0, + config.HFDomovoi.String(): 0, + } + cfg.P2PSigExtensions = true + }) + check(t, mgmt, domovoiCSS) + }) } func TestManagement_NativeDeployUpdateNotifications(t *testing.T) { - const cockatriceHeight = 3 + const ( + cockatriceHeight = 3 + domovoiHeight = 5 + ) mgmt := newCustomManagementClient(t, func(cfg *config.Blockchain) { cfg.Hardforks = map[string]uint32{ config.HFAspidochelone.String(): 0, config.HFBasilisk.String(): 0, config.HFCockatrice.String(): cockatriceHeight, + config.HFDomovoi.String(): domovoiHeight, } cfg.P2PSigExtensions = true }) @@ -169,6 +199,8 @@ func TestManagement_NativeDeployUpdateNotifications(t *testing.T) { var expected []state.NotificationEvent for _, name := range nativenames.All { switch name { + case nativenames.Notary: + continue case nativenames.Gas: expected = append(expected, state.NotificationEvent{ ScriptHash: nativehashes.GasToken, @@ -200,7 +232,7 @@ func TestManagement_NativeDeployUpdateNotifications(t *testing.T) { } require.Equal(t, expected, aer[0].Events) - // Generate some blocks and check Update notifications. + // Generate some blocks and check Update notifications for Cockatrice hardfork. cockatriceBlock := mgmt.GenerateNewBlocks(t, cockatriceHeight)[cockatriceHeight-1] aer, err = mgmt.Chain.GetAppExecResults(cockatriceBlock.Hash(), trigger.OnPersist) require.NoError(t, err) @@ -216,15 +248,35 @@ func TestManagement_NativeDeployUpdateNotifications(t *testing.T) { }) } require.Equal(t, expected, aer[0].Events) + + // Generate some blocks and check Deploy notifications for Domovoi hardfork. + mgmt.GenerateNewBlocks(t, domovoiHeight-int(mgmt.Chain.BlockHeight())) + aer, err = mgmt.Chain.GetAppExecResults(mgmt.Chain.GetHeaderHash(mgmt.Chain.BlockHeight()), trigger.OnPersist) + require.NoError(t, err) + require.Equal(t, 1, len(aer)) + expected = expected[:0] + expected = append(expected, state.NotificationEvent{ + ScriptHash: nativehashes.ContractManagement, + Name: "Deploy", + Item: stackitem.NewArray([]stackitem.Item{ + stackitem.Make(nativehashes.Notary), + }), + }) + require.Equal(t, expected, aer[0].Events) } func TestManagement_NativeUpdate(t *testing.T) { - const cockatriceHeight = 3 + const ( + cockatriceHeight = 3 + domovoiHeight = 6 + ) + c := newCustomManagementClient(t, func(cfg *config.Blockchain) { cfg.Hardforks = map[string]uint32{ config.HFAspidochelone.String(): 0, config.HFBasilisk.String(): 0, config.HFCockatrice.String(): cockatriceHeight, + config.HFDomovoi.String(): domovoiHeight, } cfg.P2PSigExtensions = true }) @@ -235,7 +287,12 @@ func TestManagement_NativeUpdate(t *testing.T) { for _, name := range nativenames.All { h := state.CreateNativeContractHash(name) cs := c.Chain.GetContractState(h) - require.NotNil(t, cs, name) + if name == nativenames.Notary { + require.Nil(t, cs, name) + continue + } else { + require.NotNil(t, cs, name) + } jBytes, err := ojson.Marshal(cs) require.NoError(t, err, name) require.Equal(t, defaultCSS[name], string(jBytes), fmt.Errorf("contract %s state mismatch", name)) @@ -247,16 +304,50 @@ func TestManagement_NativeUpdate(t *testing.T) { for _, name := range nativenames.All { h := state.CreateNativeContractHash(name) cs := c.Chain.GetContractState(h) - require.NotNil(t, cs, name) + if name == nativenames.Notary { + require.Nil(t, cs, name) + continue + } else { + require.NotNil(t, cs, name) + } + var actual = cs if name == nativenames.Neo || name == nativenames.CryptoLib { // A tiny hack to reuse cockatriceCSS map in the check below. require.Equal(t, uint16(1), cs.UpdateCounter, name) - cs.UpdateCounter-- + cp := *cs + actual = &cp // avoid Management cache corruption. + actual.UpdateCounter-- } - jBytes, err := ojson.Marshal(cs) + jBytes, err := ojson.Marshal(actual) require.NoError(t, err, name) require.Equal(t, cockatriceCSS[name], string(jBytes), fmt.Errorf("contract %s state mismatch", name)) } + + // Add some blocks up to the Domovoi enabling height and check the natives state. + for i := c.Chain.BlockHeight(); i < domovoiHeight-1; i++ { + c.AddNewBlock(t) + for _, name := range nativenames.All { + h := state.CreateNativeContractHash(name) + cs := c.Chain.GetContractState(h) + if name == nativenames.Notary { + require.Nil(t, cs, name) + continue + } else { + require.NotNil(t, cs, name) + } + var actual = cs + if name == nativenames.Neo || name == nativenames.CryptoLib { + // A tiny hack to reuse domovoiCSS map in the check below. + require.Equal(t, uint16(1), cs.UpdateCounter, name) + cp := *cs + actual = &cp // avoid Management cache corruption. + actual.UpdateCounter-- + } + jBytes, err := ojson.Marshal(actual) + require.NoError(t, err, name) + require.Equal(t, domovoiCSS[name], string(jBytes), fmt.Errorf("contract %s state mismatch", name)) + } + } } func TestManagement_NativeUpdate_Call(t *testing.T) { @@ -292,12 +383,16 @@ func TestManagement_NativeUpdate_Call(t *testing.T) { // different block heights depending on hardfork settings. This test is located here since it // depends on defaultCSS and cockatriceCSS. func TestBlockchain_GetNatives(t *testing.T) { - const cockatriceHeight = 3 + const ( + cockatriceHeight = 3 + domovoiHeight = 6 + ) bc, acc := chain.NewSingleWithCustomConfig(t, func(cfg *config.Blockchain) { cfg.Hardforks = map[string]uint32{ config.HFAspidochelone.String(): 0, config.HFBasilisk.String(): 0, config.HFCockatrice.String(): cockatriceHeight, + config.HFDomovoi.String(): domovoiHeight, } cfg.P2PSigExtensions = true }) @@ -305,7 +400,7 @@ func TestBlockchain_GetNatives(t *testing.T) { // Check genesis-based native contract states. natives := bc.GetNatives() - require.Equal(t, len(nativenames.All), len(natives)) + require.Equal(t, len(nativenames.All)-1, len(natives)) // Notary is deployed starting from D hardfork. for _, cs := range natives { csFull := state.Contract{ ContractBase: cs.ContractBase, @@ -316,10 +411,10 @@ func TestBlockchain_GetNatives(t *testing.T) { require.Equal(t, defaultCSS[cs.Manifest.Name], string(jBytes), fmt.Errorf("contract %s state mismatch", cs.Manifest.Name)) } - // Check native state after update. + // Check native state after Cockatrice. e.GenerateNewBlocks(t, cockatriceHeight) natives = bc.GetNatives() - require.Equal(t, len(nativenames.All), len(natives)) + require.Equal(t, len(nativenames.All)-1, len(natives)) // Notary is deployed starting from D hardfork. for _, cs := range natives { csFull := state.Contract{ ContractBase: cs.ContractBase, @@ -329,6 +424,20 @@ func TestBlockchain_GetNatives(t *testing.T) { require.NoError(t, err, cs.Manifest.Name) require.Equal(t, cockatriceCSS[cs.Manifest.Name], string(jBytes), fmt.Errorf("contract %s state mismatch", cs.Manifest.Name)) } + + // Check native state after Domovoi. + e.GenerateNewBlocks(t, domovoiHeight-cockatriceHeight) + natives = bc.GetNatives() + require.Equal(t, len(nativenames.All), len(natives)) + for _, cs := range natives { + csFull := state.Contract{ + ContractBase: cs.ContractBase, + UpdateCounter: 0, // Since we're comparing only state.NativeContract part, set the update counter to 0 to match the domovoiCSS. + } + jBytes, err := ojson.Marshal(csFull) + require.NoError(t, err, cs.Manifest.Name) + require.Equal(t, domovoiCSS[cs.Manifest.Name], string(jBytes), fmt.Errorf("contract %s state mismatch", cs.Manifest.Name)) + } } func TestManagement_ContractCache(t *testing.T) { diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index fa3cab436..7077e60c7 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -52,7 +52,10 @@ const ( defaultMaxNotValidBeforeDelta = 140 // 20 rounds for 7 validators, a little more than half an hour ) -var maxNotValidBeforeDeltaKey = []byte{10} +var ( + maxNotValidBeforeDeltaKey = []byte{10} + activeIn = config.HFDomovoi +) var ( _ interop.Contract = (*Notary)(nil) @@ -200,7 +203,7 @@ func (n *Notary) PostPersist(ic *interop.Context) error { // ActiveIn implements the Contract interface. func (n *Notary) ActiveIn() *config.Hardfork { - return nil + return &activeIn } // onPayment records the deposited amount as belonging to "from" address with a lock diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index 98e874567..8368f9b2d 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -465,6 +465,10 @@ func TestClientNEOContract(t *testing.T) { func TestClientNotary(t *testing.T) { chain, _, httpSrv := initServerWithInMemoryChain(t) + // Domovoi should be enabled since this test uses Notary contract. + _, ok := chain.GetConfig().Hardforks[config.HFDomovoi.String()] + require.True(t, ok) + c, err := rpcclient.New(context.Background(), httpSrv.URL, rpcclient.Options{}) require.NoError(t, err) t.Cleanup(c.Close) @@ -2446,7 +2450,10 @@ func TestClient_GetVersion_Hardforks(t *testing.T) { v, err := c.GetVersion() require.NoError(t, err) expected := map[config.Hardfork]uint32{ - config.HFAspidochelone: 25, + config.HFAspidochelone: 0, + config.HFBasilisk: 0, + config.HFCockatrice: 0, + config.HFDomovoi: 0, } require.InDeltaMapValues(t, expected, v.Protocol.Hardforks, 0) } diff --git a/pkg/services/rpcsrv/server_helper_test.go b/pkg/services/rpcsrv/server_helper_test.go index 3595bacd6..c488de8a9 100644 --- a/pkg/services/rpcsrv/server_helper_test.go +++ b/pkg/services/rpcsrv/server_helper_test.go @@ -54,6 +54,7 @@ func getUnitTestChain(t testing.TB, enableOracle bool, enableNotary bool, disabl Password: "one", } } + cfg.ProtocolConfiguration.Hardforks = nil }) } func getUnitTestChainWithCustomConfig(t testing.TB, enableOracle bool, enableNotary bool, customCfg func(configuration *config.Config)) (*core.Blockchain, OracleHandler, config.Config, *zap.Logger) { diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index e6223d602..108101998 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -74,22 +74,22 @@ type rpcTestCase struct { } const genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4" -const testContractHash = "565cff9508ebc75aadd7fe59f38dac610ab6093c" -const deploymentTxHash = "a14390941cc3a1d87393eff720a722e9cd350bd6ed233c5fe2001326c80eb68e" +const testContractHash = "449fe8fbd4523072f5e3a4dfa17a494c119d4c08" +const deploymentTxHash = "af170742f0f8a2bc064bdbdb2faa2b517e3df833d4d047da8a946c0b8d581b06" const ( verifyContractHash = "06ed5314c2e4cb103029a60b86d46afa2fb8f67c" verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A=" - verifyWithArgsContractHash = "4dc916254efd2947c93b11207e8ffc0bb56161c5" - nnsContractHash = "892429fcd47c30f8451781acc627e8b20e0d64f3" + verifyWithArgsContractHash = "6261b3bf753bdc3d24c1327a23fd891e1c8a7ccd" + nnsContractHash = "ebe47d5143bb8726b87b02efb5cd98e21174fd38" nnsToken1ID = "6e656f2e636f6d" - nfsoContractHash = "730ebe719ab8e3b69d11dafc95cdb9bf409db179" + nfsoContractHash = "2f5c1826bb4da1c764a8871427e4044cf3e82dbd" nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486" storageContractHash = "ebc0c16a76c808cd4dde6bcc063f09e45e331ec7" faultedTxHashLE = "82279bfe9bada282ca0f8cb8e0bb124b921af36f00c69a518320322c6f4fef60" faultedTxBlock uint32 = 23 invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA" - block20StateRootLE = "858c873539d6d24a70f2be13f9dafc61aef2b63c2aa16bb440676de6e44e3cf1" + block20StateRootLE = "b49a35fd3a749fc2f7f4e5fe1f288ef2b6188416f65fe5b691892e8209092082" ) var ( @@ -1381,7 +1381,7 @@ var rpcTestCases = map[string][]rpcTestCase{ script = append(script, 0x41, 0x62, 0x7d, 0x5b, 0x52) return &result.Invoke{ State: "HALT", - GasConsumed: 31922970, + GasConsumed: 31922730, Script: script, Stack: []stackitem.Item{stackitem.Make(true)}, Notifications: []state.NotificationEvent{{ @@ -1411,7 +1411,7 @@ var rpcTestCases = map[string][]rpcTestCase{ chg := []dboper.Operation{{ State: "Changed", Key: []byte{0xfa, 0xff, 0xff, 0xff, 0xb}, - Value: []byte{0x54, 0xb2, 0xd2, 0xa3, 0x51, 0x79, 0x12}, + Value: []byte{0x06, 0x44, 0xda, 0xa3, 0x51, 0x79, 0x12}, }, { State: "Added", Key: []byte{0xfb, 0xff, 0xff, 0xff, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb}, @@ -1423,7 +1423,7 @@ var rpcTestCases = map[string][]rpcTestCase{ }, { State: "Changed", Key: []byte{0xfa, 0xff, 0xff, 0xff, 0x14, 0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x8, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2}, - Value: []byte{0x41, 0x01, 0x21, 0x05, 0x0c, 0x76, 0x4f, 0xdf, 0x08}, + Value: []byte{0x41, 0x01, 0x21, 0x05, 0xf6, 0x64, 0x58, 0xdf, 0x08}, }} // Can be returned in any order. assert.ElementsMatch(t, chg, res.Diagnostics.Changes) @@ -1439,7 +1439,7 @@ var rpcTestCases = map[string][]rpcTestCase{ cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib) return &result.Invoke{ State: "HALT", - GasConsumed: 13970250, + GasConsumed: 13969530, Script: script, Stack: []stackitem.Item{stackitem.Make("1.2.3.4")}, Notifications: []state.NotificationEvent{}, @@ -1532,7 +1532,7 @@ var rpcTestCases = map[string][]rpcTestCase{ script = append(script, 0x41, 0x62, 0x7d, 0x5b, 0x52) return &result.Invoke{ State: "HALT", - GasConsumed: 31922970, + GasConsumed: 31922730, Script: script, Stack: []stackitem.Item{stackitem.Make(true)}, Notifications: []state.NotificationEvent{{ @@ -1558,7 +1558,7 @@ var rpcTestCases = map[string][]rpcTestCase{ cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib) return &result.Invoke{ State: "HALT", - GasConsumed: 13970250, + GasConsumed: 13969530, Script: script, Stack: []stackitem.Item{stackitem.Make("1.2.3.4")}, Notifications: []state.NotificationEvent{}, @@ -3260,7 +3260,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] t.Run("contract-based verification with parameters", func(t *testing.T) { verAcc, err := util.Uint160DecodeStringLE(verifyWithArgsContractHash) require.NoError(t, err) - checkContract(t, verAcc, []byte{}, 244130) // No C# match, but we believe it's OK and it differs from the one above. + checkContract(t, verAcc, []byte{}, 244010) // No C# match, but we believe it's OK and it differs from the one above. }) t.Run("contract-based verification with invocation script", func(t *testing.T) { verAcc, err := util.Uint160DecodeStringLE(verifyWithArgsContractHash) @@ -3270,7 +3270,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] emit.Int(invocWriter.BinWriter, 5) emit.String(invocWriter.BinWriter, "") invocScript := invocWriter.Bytes() - checkContract(t, verAcc, invocScript, 146960) // No C# match, but we believe it's OK and it has a specific invocation script overriding anything server-side. + checkContract(t, verAcc, invocScript, 146840) // No C# match, but we believe it's OK and it has a specific invocation script overriding anything server-side. }) t.Run("execution limit, ok", func(t *testing.T) { // 1_4000_0000 GAS with the default 1.5 allowed by Policy @@ -3575,7 +3575,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc any) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "37106285100", + Amount: "37106870550", LastUpdated: 23, Decimals: 8, Name: "GasToken", diff --git a/pkg/services/rpcsrv/testdata/testblocks.acc b/pkg/services/rpcsrv/testdata/testblocks.acc index 173d18a8ce8bfe5999e3e205b0cb30e1e021a1eb..9f97e713f7859859236b26fafc285a6f037adaca 100644 GIT binary patch delta 10410 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 JczZqXe*kwkPgei{