diff --git a/cli/testdata/chain50x2.acc b/cli/testdata/chain50x2.acc index 6c10dabc4b..43ee863186 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 a762830f13..c81f6ba5b6 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -600,18 +600,16 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error } writeBuf.Reset() - if block.Index > 0 { - aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist) - if err != nil { - return fmt.Errorf("onPersist failed: %w", err) - } - appExecResults = append(appExecResults, aer) - err = cache.PutAppExecResult(aer, writeBuf) - if err != nil { - return fmt.Errorf("failed to store onPersist exec result: %w", err) - } - writeBuf.Reset() + aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist) + if err != nil { + return fmt.Errorf("onPersist failed: %w", err) + } + appExecResults = append(appExecResults, aer) + err = cache.PutAppExecResult(aer, writeBuf) + if err != nil { + return fmt.Errorf("failed to store onPersist exec result: %w", err) } + writeBuf.Reset() for _, tx := range block.Transactions { if err := cache.StoreAsTransaction(tx, block.Index, writeBuf); err != nil { @@ -673,7 +671,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error } } - aer, err := bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist) + aer, err = bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist) if err != nil { return fmt.Errorf("postPersist failed: %w", err) } diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index d63e0a8033..785c18e79d 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -713,11 +713,18 @@ func TestVerifyTx(t *testing.T) { require.True(t, errors.Is(bc.VerifyTx(tx), ErrHasConflicts)) }) t.Run("attribute on-chain conflict", func(t *testing.T) { - b, err := bc.GetBlock(bc.GetHeaderHash(0)) - require.NoError(t, err) - conflictsHash := b.Transactions[0].Hash() - tx := getConflictsTx(conflictsHash) - require.Error(t, bc.VerifyTx(tx)) + tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0) + tx.ValidUntilBlock = 4242 + tx.Signers = []transaction.Signer{{ + Account: testchain.MultisigScriptHash(), + Scopes: transaction.None, + }} + require.NoError(t, testchain.SignTx(bc, tx)) + b := bc.newBlock(tx) + + require.NoError(t, bc.AddBlock(b)) + txConflict := getConflictsTx(tx.Hash()) + require.Error(t, bc.VerifyTx(txConflict)) }) t.Run("positive", func(t *testing.T) { tx := getConflictsTx(random.Uint256()) diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index b1fb35a7da..d7af83996b 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -163,7 +163,7 @@ func (dao *Simple) DeleteContractState(hash util.Uint160) error { // GetAndUpdateNextContractID returns id for the next contract and increases stored ID. func (dao *Simple) GetAndUpdateNextContractID() (int32, error) { - var id int32 + var id = int32(1) key := storage.SYSContractID.Bytes() data, err := dao.Store.Get(key) if err == nil { diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index a1a022eeac..b3971c74c4 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -73,13 +73,13 @@ func TestSimple_GetAndUpdateNextContractID(t *testing.T) { dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) id, err := dao.GetAndUpdateNextContractID() require.NoError(t, err) - require.EqualValues(t, 0, id) - id, err = dao.GetAndUpdateNextContractID() - require.NoError(t, err) require.EqualValues(t, 1, id) id, err = dao.GetAndUpdateNextContractID() require.NoError(t, err) require.EqualValues(t, 2, id) + id, err = dao.GetAndUpdateNextContractID() + require.NoError(t, err) + require.EqualValues(t, 3, id) } func TestPutGetAppExecResult(t *testing.T) { diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 44536d7063..ac60bca12c 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -69,7 +69,6 @@ const ( NeoCryptoSHA256 = "Neo.Crypto.SHA256" NeoCryptoRIPEMD160 = "Neo.Crypto.RIPEMD160" NeoNativeCall = "Neo.Native.Call" - NeoNativeDeploy = "Neo.Native.Deploy" ) var names = []string{ @@ -140,5 +139,4 @@ var names = []string{ NeoCryptoSHA256, NeoCryptoRIPEMD160, NeoNativeCall, - NeoNativeDeploy, } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index faef05bb6c..b8b2ab42cb 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -718,7 +718,7 @@ func TestContractCreate(t *testing.T) { ic.Tx = transaction.New(netmode.UnitTestNet, []byte{1}, 0) ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender}) - cs.ID = 0 + cs.ID = 1 cs.Hash = state.CreateContractHash(sender, cs.Script) t.Run("missing NEF", func(t *testing.T) { diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 12f4439bff..bb7eda1ed1 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -126,7 +126,6 @@ var neoInterops = []interop.Function{ {Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1000000, ParamCount: 1}, {Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1000000, ParamCount: 1}, {Name: interopnames.NeoNativeCall, Func: native.Call, Price: 0, RequiredFlags: smartcontract.AllowCall, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.NeoNativeDeploy, Func: native.Deploy, Price: 0, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, } // initIDinInteropsSlice initializes IDs from names in one given diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 0e925f450e..7dad9d2e83 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -15,13 +15,14 @@ const reservedContractID = -100 // Contracts is a set of registered native contracts. type Contracts struct { - NEO *NEO - GAS *GAS - Policy *Policy - Oracle *Oracle - Designate *Designate - Notary *Notary - Contracts []interop.Contract + Management *Management + NEO *NEO + GAS *GAS + Policy *Policy + Oracle *Oracle + Designate *Designate + Notary *Notary + Contracts []interop.Contract // persistScript is vm script which executes "onPersist" method of every native contract. persistScript []byte // postPersistScript is vm script which executes "postPersist" method of every native contract. @@ -54,6 +55,10 @@ func (cs *Contracts) ByName(name string) interop.Contract { func NewContracts(p2pSigExtensionsEnabled bool) *Contracts { cs := new(Contracts) + mgmt := newManagement() + cs.Management = mgmt + cs.Contracts = append(cs.Contracts, mgmt) + gas := newGAS() neo := newNEO() neo.GAS = gas diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index ecd529fc4c..78737da08e 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -5,36 +5,10 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" ) -// Deploy deploys native contract. -func Deploy(ic *interop.Context) error { - if ic.Block == nil || ic.Block.Index != 0 { - return errors.New("native contracts can be deployed only at 0 block") - } - - for _, native := range ic.Natives { - md := native.Metadata() - - cs := &state.Contract{ - ID: md.ContractID, - Hash: md.Hash, - Script: md.Script, - Manifest: md.Manifest, - } - if err := ic.DAO.PutContractState(cs); err != nil { - return err - } - if err := native.Initialize(ic); err != nil { - return fmt.Errorf("initializing %s native contract: %w", md.Name, err) - } - } - return nil -} - // Call calls specified native contract method. func Call(ic *interop.Context) error { name := ic.VM.Estack().Pop().String() diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go new file mode 100644 index 0000000000..a021128257 --- /dev/null +++ b/pkg/core/native/management.go @@ -0,0 +1,72 @@ +package native + +import ( + "fmt" + "sync" + + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/state" +) + +// Management is contract-managing native contract. +type Management struct { + interop.ContractMD + + lock sync.RWMutex + isValid bool + nextID int32 +} + +const ( + managementName = "Neo Contract Management" + prefixContract = 8 + prefixNextAvailableId = 15 +) + +// newManagement creates new Management native contract. +func newManagement() *Management { + var m = &Management{ContractMD: *interop.NewContractMD(managementName)} + + return m +} + +// Metadata implements Contract interface. +func (m *Management) Metadata() *interop.ContractMD { + return &m.ContractMD +} + +// OnPersist implements Contract interface. +func (m *Management) OnPersist(ic *interop.Context) error { + if ic.Block.Index != 0 { // We're only deploying at 0 at the moment. + return nil + } + + for _, native := range ic.Natives { + md := native.Metadata() + + cs := &state.Contract{ + ID: md.ContractID, + Hash: md.Hash, + Script: md.Script, + Manifest: md.Manifest, + } + if err := ic.DAO.PutContractState(cs); err != nil { + return err + } + if err := native.Initialize(ic); err != nil { + return fmt.Errorf("initializing %s native contract: %w", md.Name, err) + } + } + + return nil +} + +// PostPersist implements Contract interface. +func (m *Management) PostPersist(_ *interop.Context) error { + return nil +} + +// Initialize implements Contract interface. +func (m *Management) Initialize(_ *interop.Context) error { + return nil +} diff --git a/pkg/core/util.go b/pkg/core/util.go index eb85640b69..283c9d0507 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -5,16 +5,12 @@ import ( "time" "github.com/nspcc-dev/neo-go/pkg/config" - "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "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/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) @@ -56,10 +52,8 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) } b := &block.Block{ - Base: base, - Transactions: []*transaction.Transaction{ - deployNativeContracts(cfg.Magic), - }, + Base: base, + Transactions: []*transaction.Transaction{}, ConsensusData: block.ConsensusData{ PrimaryIndex: 0, Nonce: 2083236893, @@ -70,27 +64,6 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) return b, nil } -func deployNativeContracts(magic netmode.Magic) *transaction.Transaction { - buf := io.NewBufBinWriter() - emit.Syscall(buf.BinWriter, interopnames.NeoNativeDeploy) - script := buf.Bytes() - tx := transaction.New(magic, script, 0) - tx.Nonce = 0 - tx.Signers = []transaction.Signer{ - { - Account: hash.Hash160([]byte{byte(opcode.PUSH1)}), - Scopes: transaction.None, - }, - } - tx.Scripts = []transaction.Witness{ - { - InvocationScript: []byte{}, - VerificationScript: []byte{byte(opcode.PUSH1)}, - }, - } - return tx -} - func validatorsFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) { vs, err := committeeFromConfig(cfg) if err != nil { diff --git a/pkg/core/util_test.go b/pkg/core/util_test.go index dae606d641..ceec4237a7 100644 --- a/pkg/core/util_test.go +++ b/pkg/core/util_test.go @@ -17,7 +17,7 @@ func TestGenesisBlockMainNet(t *testing.T) { block, err := createGenesisBlock(cfg.ProtocolConfiguration) require.NoError(t, err) - expect := "ecaee33262f1bc7c7c28f2b25b54a5d61d50670871f45c0c6fe755a40cbde4a8" + expect := "00c6803707b564153d444bfcdf3a13325fc96dda55cc8a740bbd543a1d752fda" assert.Equal(t, expect, block.Hash().StringLE()) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 8f9e2cc08c..d8cf65ed55 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -57,8 +57,8 @@ type rpcTestCase struct { } const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444" -const deploymentTxHash = "9ecf1273fe0d8868cc024c8270b569a12edd7ea9d675c88554b937134efb03f8" -const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0" +const deploymentTxHash = "a72dfaebf9543964d74e803723dae6a86196e0915ae9d76b3cc57c3b2e3e8c49" +const genesisBlockHash = "0542f4350c6e236d0509bcd98188b0034bfbecc1a0c7fcdb8e4295310d468b70" const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a" const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740" @@ -89,8 +89,9 @@ var rpcTestCases = map[string][]rpcTestCase{ res, ok := acc.(*result.ApplicationLog) require.True(t, ok) assert.Equal(t, genesisBlockHash, res.Container.StringLE()) - assert.Equal(t, 1, len(res.Executions)) - assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) // no onPersist for genesis block + assert.Equal(t, 2, len(res.Executions)) + assert.Equal(t, trigger.OnPersist, res.Executions[0].Trigger) + assert.Equal(t, trigger.PostPersist, res.Executions[1].Trigger) assert.Equal(t, vm.HaltState, res.Executions[0].VMState) }, }, @@ -103,7 +104,7 @@ var rpcTestCases = map[string][]rpcTestCase{ require.True(t, ok) assert.Equal(t, genesisBlockHash, res.Container.StringLE()) assert.Equal(t, 1, len(res.Executions)) - assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) // no onPersist for genesis block + assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) assert.Equal(t, vm.HaltState, res.Executions[0].VMState) }, }, @@ -115,7 +116,9 @@ var rpcTestCases = map[string][]rpcTestCase{ res, ok := acc.(*result.ApplicationLog) require.True(t, ok) assert.Equal(t, genesisBlockHash, res.Container.StringLE()) - assert.Equal(t, 0, len(res.Executions)) // no onPersist for genesis block + assert.Equal(t, 1, len(res.Executions)) + assert.Equal(t, trigger.OnPersist, res.Executions[0].Trigger) + assert.Equal(t, vm.HaltState, res.Executions[0].VMState) }, }, { @@ -1075,7 +1078,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("getrawtransaction", func(t *testing.T) { - block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + block, _ := chain.GetBlock(chain.GetHeaderHash(1)) tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) @@ -1090,7 +1093,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("getrawtransaction 2 arguments", func(t *testing.T) { - block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + block, _ := chain.GetBlock(chain.GetHeaderHash(1)) tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) @@ -1105,7 +1108,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("getrawtransaction 2 arguments, verbose", func(t *testing.T) { - block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + block, _ := chain.GetBlock(chain.GetHeaderHash(1)) TXHash := block.Transactions[0].Hash() _ = block.Transactions[0].Size() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 1]}"`, TXHash.StringLE()) @@ -1116,7 +1119,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoErrorf(t, err, "could not parse response: %s", txOut) assert.Equal(t, *block.Transactions[0], actual.Transaction) - assert.Equal(t, 9, actual.Confirmations) + assert.Equal(t, 8, actual.Confirmations) assert.Equal(t, TXHash, actual.Transaction.Hash()) }) diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 4444465121..1d0bc04ede 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ