diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 492e3cdef..39b005081 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1053,6 +1053,10 @@ func (c *codegen) convertSyscall(expr *ast.CallExpr, api, name string) { emit.Opcode(c.prog.BinWriter, opcode.PACK) } emit.Syscall(c.prog.BinWriter, api) + switch name { + case "GetTransaction", "GetBlock": + c.emitConvert(stackitem.StructT) + } // This NOP instruction is basically not needed, but if we do, we have a // one to one matching avm file with neo-python which is very nice for debugging. diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index ef1502cb8..f6bbf3ccf 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -7,10 +7,6 @@ var syscalls = map[string]map[string]string{ "GetVotes": "Neo.Account.GetVotes", "IsStandard": "Neo.Account.IsStandard", }, - "attribute": { - "GetUsage": "Neo.Attribute.GetUsage", - "GetData": "Neo.Attribute.GetData", - }, "crypto": { "ECDsaVerify": "Neo.Crypto.ECDsaVerify", }, @@ -39,14 +35,15 @@ var syscalls = map[string]map[string]string{ "Deserialize": "Neo.Runtime.Deserialize", }, "blockchain": { - "GetAccount": "Neo.Blockchain.GetAccount", - "GetBlock": "Neo.Blockchain.GetBlock", - "GetContract": "Neo.Blockchain.GetContract", - "GetHeader": "Neo.Blockchain.GetHeader", - "GetHeight": "Neo.Blockchain.GetHeight", - "GetTransaction": "Neo.Blockchain.GetTransaction", - "GetTransactionHeight": "Neo.Blockchain.GetTransactionHeight", - "GetValidators": "Neo.Blockchain.GetValidators", + "GetAccount": "Neo.Blockchain.GetAccount", + "GetBlock": "System.Blockchain.GetBlock", + "GetContract": "Neo.Blockchain.GetContract", + "GetHeader": "Neo.Blockchain.GetHeader", + "GetHeight": "Neo.Blockchain.GetHeight", + "GetTransaction": "System.Blockchain.GetTransaction", + "GetTransactionFromBlock": "System.Blockchain.GetTransactionFromBlock", + "GetTransactionHeight": "System.Blockchain.GetTransactionHeight", + "GetValidators": "Neo.Blockchain.GetValidators", }, "header": { "GetIndex": "Neo.Header.GetIndex", @@ -58,16 +55,6 @@ var syscalls = map[string]map[string]string{ "GetConsensusData": "Neo.Header.GetConsensusData", "GetNextConsensus": "Neo.Header.GetNextConsensus", }, - "block": { - "GetTransactionCount": "Neo.Block.GetTransactionCount", - "GetTransactions": "Neo.Block.GetTransactions", - "GetTransaction": "Neo.Block.GetTransaction", - }, - "transaction": { - "GetAttributes": "Neo.Transaction.GetAttributes", - "GetHash": "Neo.Transaction.GetHash", - "GetWitnesses": "Neo.Transaction.GetWitnesses", - }, "contract": { "GetScript": "Neo.Contract.GetScript", "IsPayable": "Neo.Contract.IsPayable", diff --git a/pkg/core/gas_price.go b/pkg/core/gas_price.go index 56094bc0b..4d67955d2 100644 --- a/pkg/core/gas_price.go +++ b/pkg/core/gas_price.go @@ -41,22 +41,19 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 { } const ( - neoContractCreate = 0x6ea56cf6 // Neo.Contract.Create - neoContractMigrate = 0x90621b47 // Neo.Contract.Migrate - antSharesContractCreate = 0x2a28d29b // AntShares.Contract.Create - antSharesContractMigrate = 0xa934c8bb // AntShares.Contract.Migrate - systemStoragePut = 0x84183fe6 // System.Storage.Put - systemStoragePutEx = 0x3a9be173 // System.Storage.PutEx - neoStoragePut = 0xf541a152 // Neo.Storage.Put - antSharesStoragePut = 0x5f300a9e // AntShares.Storage.Put + neoContractCreate = 0x6ea56cf6 // Neo.Contract.Create + neoContractMigrate = 0x90621b47 // Neo.Contract.Migrate + systemStoragePut = 0x84183fe6 // System.Storage.Put + systemStoragePutEx = 0x3a9be173 // System.Storage.PutEx + neoStoragePut = 0xf541a152 // Neo.Storage.Put ) estack := v.Estack() switch id { - case neoContractCreate, neoContractMigrate, antSharesContractCreate, antSharesContractMigrate: + case neoContractCreate, neoContractMigrate: return smartcontract.GetDeploymentPrice(smartcontract.PropertyState(estack.Peek(3).BigInt().Int64())) - case systemStoragePut, systemStoragePutEx, neoStoragePut, antSharesStoragePut: + case systemStoragePut, systemStoragePutEx, neoStoragePut: // price for storage PUT is 1 GAS per 1 KiB keySize := len(estack.Peek(1).Bytes()) valSize := len(estack.Peek(2).Bytes()) diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 5295d922b..d855890f8 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -57,42 +57,6 @@ func headerGetNextConsensus(ic *interop.Context, v *vm.VM) error { return nil } -// txGetAttributes returns current transaction attributes. -func txGetAttributes(ic *interop.Context, v *vm.VM) error { - txInterface := v.Estack().Pop().Value() - tx, ok := txInterface.(*transaction.Transaction) - if !ok { - return errors.New("value is not a transaction") - } - if len(tx.Attributes) > vm.MaxArraySize { - return errors.New("too many attributes") - } - attrs := make([]stackitem.Item, 0, len(tx.Attributes)) - for i := range tx.Attributes { - attrs = append(attrs, stackitem.NewInterop(&tx.Attributes[i])) - } - v.Estack().PushVal(attrs) - return nil -} - -// txGetWitnesses returns current transaction witnesses. -func txGetWitnesses(ic *interop.Context, v *vm.VM) error { - txInterface := v.Estack().Pop().Value() - tx, ok := txInterface.(*transaction.Transaction) - if !ok { - return errors.New("value is not a transaction") - } - if len(tx.Scripts) > vm.MaxArraySize { - return errors.New("too many outputs") - } - scripts := make([]stackitem.Item, 0, len(tx.Scripts)) - for i := range tx.Scripts { - scripts = append(scripts, stackitem.NewInterop(&tx.Scripts[i])) - } - v.Estack().PushVal(scripts) - return nil -} - // witnessGetVerificationScript returns current witness' script. func witnessGetVerificationScript(ic *interop.Context, v *vm.VM) error { witInterface := v.Estack().Pop().Value() @@ -107,28 +71,6 @@ func witnessGetVerificationScript(ic *interop.Context, v *vm.VM) error { return nil } -// attrGetData returns tx attribute data. -func attrGetData(ic *interop.Context, v *vm.VM) error { - attrInterface := v.Estack().Pop().Value() - attr, ok := attrInterface.(*transaction.Attribute) - if !ok { - return fmt.Errorf("%T is not an attribute", attr) - } - v.Estack().PushVal(attr.Data) - return nil -} - -// attrGetData returns tx attribute usage field. -func attrGetUsage(ic *interop.Context, v *vm.VM) error { - attrInterface := v.Estack().Pop().Value() - attr, ok := attrInterface.(*transaction.Attribute) - if !ok { - return fmt.Errorf("%T is not an attribute", attr) - } - v.Estack().PushVal(int(attr.Usage)) - return nil -} - // bcGetAccount returns or creates an account. func bcGetAccount(ic *interop.Context, v *vm.VM) error { accbytes := v.Estack().Pop().Bytes() diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 50cb0d9f4..626e0c5cd 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -178,16 +178,6 @@ func TestHeaderGetNextConsensus(t *testing.T) { require.Equal(t, block.NextConsensus.BytesBE(), value) } -func TestTxGetAttributes(t *testing.T) { - v, tx, context, chain := createVMAndPushTX(t) - defer chain.Close() - - err := txGetAttributes(context, v) - require.NoError(t, err) - value := v.Estack().Pop().Value().([]stackitem.Item) - require.Equal(t, tx.Attributes[0].Usage, value[0].Value().(*transaction.Attribute).Usage) -} - func TestWitnessGetVerificationScript(t *testing.T) { v := vm.New() script := []byte{byte(opcode.PUSHM1), byte(opcode.RET)} @@ -280,28 +270,6 @@ func TestECDSAVerify(t *testing.T) { }) } -func TestAttrGetData(t *testing.T) { - v, tx, context, chain := createVMAndTX(t) - defer chain.Close() - v.Estack().PushVal(stackitem.NewInterop(&tx.Attributes[0])) - - err := attrGetData(context, v) - require.NoError(t, err) - data := v.Estack().Pop().Value() - require.Equal(t, tx.Attributes[0].Data, data) -} - -func TestAttrGetUsage(t *testing.T) { - v, tx, context, chain := createVMAndTX(t) - defer chain.Close() - v.Estack().PushVal(stackitem.NewInterop(&tx.Attributes[0])) - - err := attrGetUsage(context, v) - require.NoError(t, err) - usage := v.Estack().Pop().Value() - require.Equal(t, big.NewInt(int64(tx.Attributes[0].Usage)), usage) -} - func TestAccountGetScriptHash(t *testing.T) { v, accState, context, chain := createVMAndAccState(t) defer chain.Close() @@ -337,12 +305,25 @@ func TestContractIsPayable(t *testing.T) { // Helper functions to create VM, InteropContext, TX, Account, Contract. +func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) { + v := vm.New() + chain := newTestChain(t) + context := chain.newInteropContext(trigger.Application, + dao.NewSimple(storage.NewMemoryStore()), nil, nil) + return v, context, chain +} + func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) { + v, block, context, chain := createVMAndBlock(t) + v.Estack().PushVal(stackitem.NewInterop(block)) + return v, block, context, chain +} + +func createVMAndBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) { v := vm.New() block := newDumbBlock() chain := newTestChain(t) context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), block, nil) - v.Estack().PushVal(stackitem.NewInterop(block)) return v, block, context, chain } diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 55b57fa09..9a30fbaf8 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math" + "math/big" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" @@ -22,6 +23,9 @@ import ( const ( // MaxStorageKeyLen is the maximum length of a key for storage items. MaxStorageKeyLen = 1024 + // MaxTraceableBlocks is the maximum number of blocks before current chain + // height we're able to give information about. + MaxTraceableBlocks = transaction.MaxValidUntilBlockIncrement ) // StorageContext contains storing script hash and read/write flag, it's used as @@ -49,6 +53,20 @@ func getBlockHashFromElement(bc blockchainer.Blockchainer, element *vm.Element) return hash, nil } +// blockToStackItem converts block.Block to stackitem.Item +func blockToStackItem(b *block.Block) stackitem.Item { + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(b.Hash().BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(b.Version))), + stackitem.NewByteArray(b.PrevHash.BytesBE()), + stackitem.NewByteArray(b.MerkleRoot.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(b.Timestamp))), + stackitem.NewBigInteger(big.NewInt(int64(b.Index))), + stackitem.NewByteArray(b.NextConsensus.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(len(b.Transactions)))), + }) +} + // bcGetBlock returns current block. func bcGetBlock(ic *interop.Context, v *vm.VM) error { hash, err := getBlockHashFromElement(ic.Chain, v.Estack().Pop()) @@ -56,10 +74,10 @@ func bcGetBlock(ic *interop.Context, v *vm.VM) error { return err } block, err := ic.Chain.GetBlock(hash) - if err != nil { - v.Estack().PushVal([]byte{}) + if err != nil || !isTraceableBlock(ic, block.Index) { + v.Estack().PushVal(stackitem.Null{}) } else { - v.Estack().PushVal(stackitem.NewInterop(block)) + v.Estack().PushVal(blockToStackItem(block)) } return nil } @@ -112,21 +130,65 @@ func getTransactionAndHeight(cd *dao.Cached, v *vm.VM) (*transaction.Transaction return cd.GetTransaction(hash) } +// isTraceableBlock defines whether we're able to give information about +// the block with index specified. +func isTraceableBlock(ic *interop.Context, index uint32) bool { + height := ic.Chain.BlockHeight() + return index <= height && index+MaxTraceableBlocks > height +} + +// transactionToStackItem converts transaction.Transaction to stackitem.Item +func transactionToStackItem(t *transaction.Transaction) stackitem.Item { + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(t.Hash().BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(t.Version))), + stackitem.NewBigInteger(big.NewInt(int64(t.Nonce))), + stackitem.NewByteArray(t.Sender.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(t.SystemFee))), + stackitem.NewBigInteger(big.NewInt(int64(t.NetworkFee))), + stackitem.NewBigInteger(big.NewInt(int64(t.ValidUntilBlock))), + stackitem.NewByteArray(t.Script), + }) +} + // bcGetTransaction returns transaction. func bcGetTransaction(ic *interop.Context, v *vm.VM) error { - tx, _, err := getTransactionAndHeight(ic.DAO, v) + tx, h, err := getTransactionAndHeight(ic.DAO, v) + if err != nil || !isTraceableBlock(ic, h) { + v.Estack().PushVal(stackitem.Null{}) + return nil + } + v.Estack().PushVal(transactionToStackItem(tx)) + return nil +} + +// bcGetTransactionFromBlock returns transaction with the given index from the +// block with height or hash specified. +func bcGetTransactionFromBlock(ic *interop.Context, v *vm.VM) error { + hash, err := getBlockHashFromElement(ic.Chain, v.Estack().Pop()) if err != nil { return err } - v.Estack().PushVal(stackitem.NewInterop(tx)) + block, err := ic.DAO.GetBlock(hash) + if err != nil || !isTraceableBlock(ic, block.Index) { + v.Estack().PushVal(stackitem.Null{}) + return nil + } + index := v.Estack().Pop().BigInt().Int64() + if index < 0 || index >= int64(len(block.Transactions)) { + return errors.New("wrong transaction index") + } + tx := block.Transactions[index] + v.Estack().PushVal(tx.Hash().BytesBE()) return nil } // bcGetTransactionHeight returns transaction height. func bcGetTransactionHeight(ic *interop.Context, v *vm.VM) error { _, h, err := getTransactionAndHeight(ic.DAO, v) - if err != nil { - return err + if err != nil || !isTraceableBlock(ic, h) { + v.Estack().PushVal(-1) + return nil } v.Estack().PushVal(h) return nil @@ -188,63 +250,6 @@ func headerGetTimestamp(ic *interop.Context, v *vm.VM) error { return nil } -// blockGetTransactionCount returns transactions count in the given block. -func blockGetTransactionCount(ic *interop.Context, v *vm.VM) error { - blockInterface := v.Estack().Pop().Value() - block, ok := blockInterface.(*block.Block) - if !ok { - return errors.New("value is not a block") - } - v.Estack().PushVal(len(block.Transactions)) - return nil -} - -// blockGetTransactions returns transactions from the given block. -func blockGetTransactions(ic *interop.Context, v *vm.VM) error { - blockInterface := v.Estack().Pop().Value() - block, ok := blockInterface.(*block.Block) - if !ok { - return errors.New("value is not a block") - } - if len(block.Transactions) > vm.MaxArraySize { - return errors.New("too many transactions") - } - txes := make([]stackitem.Item, 0, len(block.Transactions)) - for _, tx := range block.Transactions { - txes = append(txes, stackitem.NewInterop(tx)) - } - v.Estack().PushVal(txes) - return nil -} - -// blockGetTransaction returns transaction with the given number from the given -// block. -func blockGetTransaction(ic *interop.Context, v *vm.VM) error { - blockInterface := v.Estack().Pop().Value() - block, ok := blockInterface.(*block.Block) - if !ok { - return errors.New("value is not a block") - } - index := v.Estack().Pop().BigInt().Int64() - if index < 0 || index >= int64(len(block.Transactions)) { - return errors.New("wrong transaction index") - } - tx := block.Transactions[index] - v.Estack().PushVal(stackitem.NewInterop(tx)) - return nil -} - -// txGetHash returns transaction's hash. -func txGetHash(ic *interop.Context, v *vm.VM) error { - txInterface := v.Estack().Pop().Value() - tx, ok := txInterface.(*transaction.Transaction) - if !ok { - return errors.New("value is not a transaction") - } - v.Estack().PushVal(tx.Hash().BytesBE()) - return nil -} - // engineGetScriptContainer returns transaction that contains the script being // run. func engineGetScriptContainer(ic *interop.Context, v *vm.VM) error { diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go new file mode 100644 index 000000000..3a06fcb4a --- /dev/null +++ b/pkg/core/interop_system_test.go @@ -0,0 +1,147 @@ +package core + +import ( + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestBCGetTransaction(t *testing.T) { + v, tx, context, chain := createVMAndTX(t) + defer chain.Close() + + t.Run("success", func(t *testing.T) { + require.NoError(t, context.DAO.StoreAsTransaction(tx, 0)) + v.Estack().PushVal(tx.Hash().BytesBE()) + err := bcGetTransaction(context, v) + require.NoError(t, err) + + value := v.Estack().Pop().Value() + actual, ok := value.([]stackitem.Item) + require.True(t, ok) + require.Equal(t, 8, len(actual)) + require.Equal(t, tx.Hash().BytesBE(), actual[0].Value().([]byte)) + require.Equal(t, int64(tx.Version), actual[1].Value().(*big.Int).Int64()) + require.Equal(t, int64(tx.Nonce), actual[2].Value().(*big.Int).Int64()) + require.Equal(t, tx.Sender.BytesBE(), actual[3].Value().([]byte)) + require.Equal(t, int64(tx.SystemFee), actual[4].Value().(*big.Int).Int64()) + require.Equal(t, int64(tx.NetworkFee), actual[5].Value().(*big.Int).Int64()) + require.Equal(t, int64(tx.ValidUntilBlock), actual[6].Value().(*big.Int).Int64()) + require.Equal(t, tx.Script, actual[7].Value().([]byte)) + }) + + t.Run("isn't traceable", func(t *testing.T) { + require.NoError(t, context.DAO.StoreAsTransaction(tx, 1)) + v.Estack().PushVal(tx.Hash().BytesBE()) + err := bcGetTransaction(context, v) + require.NoError(t, err) + + _, ok := v.Estack().Pop().Item().(stackitem.Null) + require.True(t, ok) + }) + + t.Run("bad hash", func(t *testing.T) { + require.NoError(t, context.DAO.StoreAsTransaction(tx, 1)) + v.Estack().PushVal(tx.Hash().BytesLE()) + err := bcGetTransaction(context, v) + require.NoError(t, err) + + _, ok := v.Estack().Pop().Item().(stackitem.Null) + require.True(t, ok) + }) +} + +func TestBCGetTransactionFromBlock(t *testing.T) { + v, block, context, chain := createVMAndBlock(t) + defer chain.Close() + require.NoError(t, chain.AddBlock(chain.newBlock())) + require.NoError(t, context.DAO.StoreAsBlock(block)) + + t.Run("success", func(t *testing.T) { + v.Estack().PushVal(0) + v.Estack().PushVal(block.Hash().BytesBE()) + err := bcGetTransactionFromBlock(context, v) + require.NoError(t, err) + + value := v.Estack().Pop().Value() + actual, ok := value.([]byte) + require.True(t, ok) + require.Equal(t, block.Transactions[0].Hash().BytesBE(), actual) + }) + + t.Run("invalid block hash", func(t *testing.T) { + v.Estack().PushVal(0) + v.Estack().PushVal(block.Hash().BytesBE()[:10]) + err := bcGetTransactionFromBlock(context, v) + require.Error(t, err) + }) + + t.Run("isn't traceable", func(t *testing.T) { + block.Index = 2 + require.NoError(t, context.DAO.StoreAsBlock(block)) + v.Estack().PushVal(0) + v.Estack().PushVal(block.Hash().BytesBE()) + err := bcGetTransactionFromBlock(context, v) + require.NoError(t, err) + + _, ok := v.Estack().Pop().Item().(stackitem.Null) + require.True(t, ok) + }) + + t.Run("bad block hash", func(t *testing.T) { + block.Index = 1 + require.NoError(t, context.DAO.StoreAsBlock(block)) + v.Estack().PushVal(0) + v.Estack().PushVal(block.Hash().BytesLE()) + err := bcGetTransactionFromBlock(context, v) + require.NoError(t, err) + + _, ok := v.Estack().Pop().Item().(stackitem.Null) + require.True(t, ok) + }) + + t.Run("bad transaction index", func(t *testing.T) { + require.NoError(t, context.DAO.StoreAsBlock(block)) + v.Estack().PushVal(1) + v.Estack().PushVal(block.Hash().BytesBE()) + err := bcGetTransactionFromBlock(context, v) + require.Error(t, err) + }) +} + +func TestBCGetBlock(t *testing.T) { + v, context, chain := createVM(t) + defer chain.Close() + block := chain.newBlock() + require.NoError(t, chain.AddBlock(block)) + + t.Run("success", func(t *testing.T) { + v.Estack().PushVal(block.Hash().BytesBE()) + err := bcGetBlock(context, v) + require.NoError(t, err) + + value := v.Estack().Pop().Value() + actual, ok := value.([]stackitem.Item) + require.True(t, ok) + require.Equal(t, 8, len(actual)) + require.Equal(t, block.Hash().BytesBE(), actual[0].Value().([]byte)) + require.Equal(t, int64(block.Version), actual[1].Value().(*big.Int).Int64()) + require.Equal(t, block.PrevHash.BytesBE(), actual[2].Value().([]byte)) + require.Equal(t, block.MerkleRoot.BytesBE(), actual[3].Value().([]byte)) + require.Equal(t, int64(block.Timestamp), actual[4].Value().(*big.Int).Int64()) + require.Equal(t, int64(block.Index), actual[5].Value().(*big.Int).Int64()) + require.Equal(t, block.NextConsensus.BytesBE(), actual[6].Value().([]byte)) + require.Equal(t, int64(len(block.Transactions)), actual[7].Value().(*big.Int).Int64()) + }) + + t.Run("bad hash", func(t *testing.T) { + v.Estack().PushVal(block.Hash().BytesLE()) + err := bcGetTransaction(context, v) + require.NoError(t, err) + + _, ok := v.Estack().Pop().Item().(stackitem.Null) + require.True(t, ok) + }) +} diff --git a/pkg/core/interops.go b/pkg/core/interops.go index a781bcdb5..889742b3a 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -62,14 +62,12 @@ func getInteropFromSlice(ic *interop.Context, slice []interop.Function) func(uin // All lists are sorted, keep 'em this way, please. var systemInterops = []interop.Function{ - {Name: "System.Block.GetTransaction", Func: blockGetTransaction, Price: 1}, - {Name: "System.Block.GetTransactionCount", Func: blockGetTransactionCount, Price: 1}, - {Name: "System.Block.GetTransactions", Func: blockGetTransactions, Price: 1}, - {Name: "System.Blockchain.GetBlock", Func: bcGetBlock, Price: 200}, + {Name: "System.Blockchain.GetBlock", Func: bcGetBlock, Price: 250}, {Name: "System.Blockchain.GetContract", Func: bcGetContract, Price: 100}, {Name: "System.Blockchain.GetHeader", Func: bcGetHeader, Price: 100}, {Name: "System.Blockchain.GetHeight", Func: bcGetHeight, Price: 1}, - {Name: "System.Blockchain.GetTransaction", Func: bcGetTransaction, Price: 200}, + {Name: "System.Blockchain.GetTransaction", Func: bcGetTransaction, Price: 100}, + {Name: "System.Blockchain.GetTransactionFromBlock", Func: bcGetTransactionFromBlock, Price: 100}, {Name: "System.Blockchain.GetTransactionHeight", Func: bcGetTransactionHeight, Price: 100}, {Name: "System.Contract.Call", Func: contractCall, Price: 1}, {Name: "System.Contract.CallEx", Func: contractCallEx, Price: 1}, @@ -98,25 +96,16 @@ var systemInterops = []interop.Function{ {Name: "System.Storage.Put", Func: storagePut, Price: 0}, // These don't have static price in C# code. {Name: "System.Storage.PutEx", Func: storagePutEx, Price: 0}, {Name: "System.StorageContext.AsReadOnly", Func: storageContextAsReadOnly, Price: 1}, - {Name: "System.Transaction.GetHash", Func: txGetHash, Price: 1}, } var neoInterops = []interop.Function{ {Name: "Neo.Account.GetBalance", Func: accountGetBalance, Price: 1}, {Name: "Neo.Account.GetScriptHash", Func: accountGetScriptHash, Price: 1}, {Name: "Neo.Account.IsStandard", Func: accountIsStandard, Price: 100}, - {Name: "Neo.Attribute.GetData", Func: attrGetData, Price: 1}, - {Name: "Neo.Attribute.GetUsage", Func: attrGetUsage, Price: 1}, - {Name: "Neo.Block.GetTransaction", Func: blockGetTransaction, Price: 1}, - {Name: "Neo.Block.GetTransactionCount", Func: blockGetTransactionCount, Price: 1}, - {Name: "Neo.Block.GetTransactions", Func: blockGetTransactions, Price: 1}, {Name: "Neo.Blockchain.GetAccount", Func: bcGetAccount, Price: 100}, - {Name: "Neo.Blockchain.GetBlock", Func: bcGetBlock, Price: 200}, {Name: "Neo.Blockchain.GetContract", Func: bcGetContract, Price: 100}, {Name: "Neo.Blockchain.GetHeader", Func: bcGetHeader, Price: 100}, {Name: "Neo.Blockchain.GetHeight", Func: bcGetHeight, Price: 1}, - {Name: "Neo.Blockchain.GetTransaction", Func: bcGetTransaction, Price: 100}, - {Name: "Neo.Blockchain.GetTransactionHeight", Func: bcGetTransactionHeight, Price: 100}, {Name: "Neo.Contract.Create", Func: contractCreate, Price: 0}, {Name: "Neo.Contract.Destroy", Func: contractDestroy, Price: 1}, {Name: "Neo.Contract.GetScript", Func: contractGetScript, Price: 1}, @@ -157,49 +146,11 @@ var neoInterops = []interop.Function{ {Name: "Neo.Storage.GetReadOnlyContext", Func: storageGetReadOnlyContext, Price: 1}, {Name: "Neo.Storage.Put", Func: storagePut, Price: 0}, {Name: "Neo.StorageContext.AsReadOnly", Func: storageContextAsReadOnly, Price: 1}, - {Name: "Neo.Transaction.GetAttributes", Func: txGetAttributes, Price: 1}, - {Name: "Neo.Transaction.GetHash", Func: txGetHash, Price: 1}, - {Name: "Neo.Transaction.GetWitnesses", Func: txGetWitnesses, Price: 200}, {Name: "Neo.Witness.GetVerificationScript", Func: witnessGetVerificationScript, Price: 100}, // Aliases. {Name: "Neo.Iterator.Next", Func: enumerator.Next, Price: 1}, {Name: "Neo.Iterator.Value", Func: enumerator.Value, Price: 1}, - - // Old compatibility APIs. - {Name: "AntShares.Account.GetBalance", Func: accountGetBalance, Price: 1}, - {Name: "AntShares.Account.GetScriptHash", Func: accountGetScriptHash, Price: 1}, - {Name: "AntShares.Attribute.GetData", Func: attrGetData, Price: 1}, - {Name: "AntShares.Attribute.GetUsage", Func: attrGetUsage, Price: 1}, - {Name: "AntShares.Block.GetTransaction", Func: blockGetTransaction, Price: 1}, - {Name: "AntShares.Block.GetTransactionCount", Func: blockGetTransactionCount, Price: 1}, - {Name: "AntShares.Block.GetTransactions", Func: blockGetTransactions, Price: 1}, - {Name: "AntShares.Blockchain.GetAccount", Func: bcGetAccount, Price: 100}, - {Name: "AntShares.Blockchain.GetBlock", Func: bcGetBlock, Price: 200}, - {Name: "AntShares.Blockchain.GetContract", Func: bcGetContract, Price: 100}, - {Name: "AntShares.Blockchain.GetHeader", Func: bcGetHeader, Price: 100}, - {Name: "AntShares.Blockchain.GetHeight", Func: bcGetHeight, Price: 1}, - {Name: "AntShares.Blockchain.GetTransaction", Func: bcGetTransaction, Price: 100}, - {Name: "AntShares.Contract.Create", Func: contractCreate, Price: 0}, - {Name: "AntShares.Contract.Destroy", Func: contractDestroy, Price: 1}, - {Name: "AntShares.Contract.GetScript", Func: contractGetScript, Price: 1}, - {Name: "AntShares.Contract.GetStorageContext", Func: contractGetStorageContext, Price: 1}, - {Name: "AntShares.Contract.Migrate", Func: contractMigrate, Price: 0}, - {Name: "AntShares.Header.GetHash", Func: headerGetHash, Price: 1}, - {Name: "AntShares.Header.GetMerkleRoot", Func: headerGetMerkleRoot, Price: 1}, - {Name: "AntShares.Header.GetNextConsensus", Func: headerGetNextConsensus, Price: 1}, - {Name: "AntShares.Header.GetPrevHash", Func: headerGetPrevHash, Price: 1}, - {Name: "AntShares.Header.GetTimestamp", Func: headerGetTimestamp, Price: 1}, - {Name: "AntShares.Header.GetVersion", Func: headerGetVersion, Price: 1}, - {Name: "AntShares.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 200}, - {Name: "AntShares.Runtime.Log", Func: runtimeLog, Price: 1}, - {Name: "AntShares.Runtime.Notify", Func: runtimeNotify, Price: 1}, - {Name: "AntShares.Storage.Delete", Func: storageDelete, Price: 100}, - {Name: "AntShares.Storage.Get", Func: storageGet, Price: 100}, - {Name: "AntShares.Storage.GetContext", Func: storageGetContext, Price: 1}, - {Name: "AntShares.Storage.Put", Func: storagePut, Price: 0}, - {Name: "AntShares.Transaction.GetAttributes", Func: txGetAttributes, Price: 1}, - {Name: "AntShares.Transaction.GetHash", Func: txGetHash, Price: 1}, } // initIDinInteropsSlice initializes IDs from names in one given diff --git a/pkg/core/interops_test.go b/pkg/core/interops_test.go index ee78b1aa6..40d4db5ae 100644 --- a/pkg/core/interops_test.go +++ b/pkg/core/interops_test.go @@ -34,11 +34,6 @@ func TestUnexpectedNonInterops(t *testing.T) { funcs := []func(*interop.Context, *vm.VM) error{ accountGetBalance, accountGetScriptHash, - attrGetData, - attrGetUsage, - blockGetTransaction, - blockGetTransactionCount, - blockGetTransactions, contractGetScript, contractGetStorageContext, contractIsPayable, @@ -55,9 +50,6 @@ func TestUnexpectedNonInterops(t *testing.T) { storageGet, storagePut, storagePutEx, - txGetAttributes, - txGetHash, - txGetWitnesses, witnessGetVerificationScript, } for _, f := range funcs { diff --git a/pkg/interop/attribute/attribute.go b/pkg/interop/attribute/attribute.go deleted file mode 100644 index b4c0175c4..000000000 --- a/pkg/interop/attribute/attribute.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Package attribute provides getters for transaction attributes. -*/ -package attribute - -// Attribute represents transaction attribute in Neo, it's an opaque data -// structure that you can get data from only using functions from this package. -// It's similar in function to the TransactionAttribute class in the Neo .net -// framework. To use it you need to get is first using transaction.GetAttributes. -type Attribute struct{} - -// GetUsage returns the Usage field of the given attribute. It is an enumeration -// with the following possible values: -// ContractHash = 0x00 -// ECDH02 = 0x02 -// ECDH03 = 0x03 -// Script = 0x20 -// Vote = 0x30 -// CertURL = 0x80 -// DescriptionURL = 0x81 -// Description = 0x90 -// -// Hash1 = 0xa1 -// Hash2 = 0xa2 -// Hash3 = 0xa3 -// Hash4 = 0xa4 -// Hash5 = 0xa5 -// Hash6 = 0xa6 -// Hash7 = 0xa7 -// Hash8 = 0xa8 -// Hash9 = 0xa9 -// Hash10 = 0xaa -// Hash11 = 0xab -// Hash12 = 0xac -// Hash13 = 0xad -// Hash14 = 0xae -// Hash15 = 0xaf -// -// Remark = 0xf0 -// Remark1 = 0xf1 -// Remark2 = 0xf2 -// Remark3 = 0xf3 -// Remark4 = 0xf4 -// Remark5 = 0xf5 -// Remark6 = 0xf6 -// Remark7 = 0xf7 -// Remark8 = 0xf8 -// Remark9 = 0xf9 -// Remark10 = 0xfa -// Remark11 = 0xfb -// Remark12 = 0xfc -// Remark13 = 0xfd -// Remark14 = 0xfe -// Remark15 = 0xff -// This function uses `Neo.Attribute.GetUsage` syscall internally. -func GetUsage(attr Attribute) byte { - return 0x00 -} - -// GetData returns the data of the given attribute, exact interpretation of this -// data depends on attribute's Usage type. It uses `Neo.Attribute.GetData` -// syscall internally. -func GetData(attr Attribute) []byte { - return nil -} diff --git a/pkg/interop/block/block.go b/pkg/interop/block/block.go deleted file mode 100644 index 98cd3a50d..000000000 --- a/pkg/interop/block/block.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Package block provides getters for Neo Block structure. -*/ -package block - -import "github.com/nspcc-dev/neo-go/pkg/interop/transaction" - -// Block represents a NEO block, it's an opaque data structure that you can get -// data from only using functions from this package. It's similar in function to -// the Block class in the Neo .net framework. To use it you need to get it via -// blockchain.GetBlock function call. -type Block struct{} - -// GetTransactionCount returns the number of recorded transactions in the given -// block. It uses `Neo.Block.GetTransactionCount` syscall internally. -func GetTransactionCount(b Block) int { - return 0 -} - -// GetTransactions returns a slice of transactions recorded in the given block. -// It uses `Neo.Block.GetTransactions` syscall internally. -func GetTransactions(b Block) []transaction.Transaction { - return []transaction.Transaction{} -} - -// GetTransaction returns transaction from the given block by its index. It -// uses `Neo.Block.GetTransaction` syscall internally. -func GetTransaction(b Block, index int) transaction.Transaction { - return transaction.Transaction{} -} diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go index bba276c7a..5afb272a5 100644 --- a/pkg/interop/blockchain/blockchain.go +++ b/pkg/interop/blockchain/blockchain.go @@ -5,12 +5,60 @@ package blockchain import ( "github.com/nspcc-dev/neo-go/pkg/interop/account" - "github.com/nspcc-dev/neo-go/pkg/interop/block" "github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/header" - "github.com/nspcc-dev/neo-go/pkg/interop/transaction" ) +// Transaction represents a NEO transaction. It's similar to Transaction class +// in Neo .net framework. +type Transaction struct { + // Hash represents the hash (256 bit BE value in a 32 byte slice) of the + // given transaction (which also is its ID). + Hash []byte + // Version represents the transaction version. + Version int + // Nonce is a random number to avoid hash collision. + Nonce int + // Sender represents the sender (160 bit BE value in a 20 byte slice) of the + // given Transaction. + Sender []byte + // SysFee represents fee to be burned. + SysFee int + // NetFee represents fee to be distributed to consensus nodes. + NetFee int + // ValidUntilBlock is the maximum blockchain height exceeding which + // transaction should fail verification. + ValidUntilBlock int + // Script represents code to run in NeoVM for this transaction. + Script []byte +} + +// Block represents a NEO block, it's a data structure that you can get +// block-related data from. It's similar to the Block class in the Neo .net +// framework. To use it you need to get it via GetBlock function call. +type Block struct { + // Hash represents the hash (256 bit BE value in a 32 byte slice) of the + // given block. + Hash []byte + // Version of the block. + Version int + // PrevHash represents the hash (256 bit BE value in a 32 byte slice) of the + // previous block. + PrevHash []byte + // MerkleRoot represents the root hash (256 bit BE value in a 32 byte slice) + // of a transaction list. + MerkleRoot []byte + // Timestamp represents millisecond-precision block timestamp. + Timestamp int + // Index represents the height of the block. + Index int + // NextConsensus representes contract address of the next miner (160 bit BE + // value in a 20 byte slice). + NextConsensus []byte + // TransactionsLength represents the length of block's transactions array. + TransactionsLength int +} + // GetHeight returns current block height (index of the last accepted block). // Note that when transaction is being run as a part of new block this block is // considered as not yet accepted (persisted) and thus you'll get an index of @@ -29,23 +77,30 @@ func GetHeader(heightOrHash interface{}) header.Header { } // GetBlock returns block found by the given hash or index (with the same -// encoding as for GetHeader). Refer to the `block` package for possible uses -// of returned structure. This function uses `Neo.Blockchain.GetBlock` syscall. -func GetBlock(heightOrHash interface{}) block.Block { - return block.Block{} +// encoding as for GetHeader). This function uses `System.Blockchain.GetBlock` +// syscall. +func GetBlock(heightOrHash interface{}) Block { + return Block{} } -// GetTransaction returns transaction found by the given (256 bit in BE format -// represented as a slice of 32 bytes). Refer to the `transaction` package for -// possible uses of returned structure. This function uses -// `Neo.Blockchain.GetTransaction` syscall. -func GetTransaction(hash []byte) transaction.Transaction { - return transaction.Transaction{} +// GetTransaction returns transaction found by the given hash (256 bit in BE +// format represented as a slice of 32 bytes). This function uses +// `System.Blockchain.GetTransaction` syscall. +func GetTransaction(hash []byte) Transaction { + return Transaction{} +} + +// GetTransactionFromBlock returns transaction hash (256 bit in BE format +// represented as a slice of 32 bytes) from the block found by the given hash or +// index (with the same encoding as for GetHeader) by its index. This +// function uses `System.Blockchain.GetTransactionFromBlock` syscall. +func GetTransactionFromBlock(heightOrHash interface{}, index int) []byte { + return nil } // GetTransactionHeight returns transaction's height (index of the block that // includes it) by the given ID (256 bit in BE format represented as a slice of -// 32 bytes). This function uses `Neo.Blockchain.GetTransactionHeight` syscall. +// 32 bytes). This function uses `System.Blockchain.GetTransactionHeight` syscall. func GetTransactionHeight(hash []byte) int { return 0 } diff --git a/pkg/interop/engine/engine.go b/pkg/interop/engine/engine.go index c2ebda9dd..ace2ceff3 100644 --- a/pkg/interop/engine/engine.go +++ b/pkg/interop/engine/engine.go @@ -5,15 +5,14 @@ framework. */ package engine -import "github.com/nspcc-dev/neo-go/pkg/interop/transaction" +import "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" // GetScriptContainer returns the transaction that initially triggered current // execution context. It never changes in a single execution, no matter how deep -// this execution goes. See `transaction` package for details on how to use the -// returned value. This function uses `System.ExecutionEngine.GetScriptContainer` -// syscall. -func GetScriptContainer() transaction.Transaction { - return transaction.Transaction{} +// this execution goes. This function uses +// `System.ExecutionEngine.GetScriptContainer` syscall. +func GetScriptContainer() blockchain.Transaction { + return blockchain.Transaction{} } // GetExecutingScriptHash returns script hash (160 bit in BE form represented diff --git a/pkg/interop/transaction/transaction.go b/pkg/interop/transaction/transaction.go deleted file mode 100644 index fc44f37c4..000000000 --- a/pkg/interop/transaction/transaction.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Package transaction provides functions to work with transactions. -*/ -package transaction - -import ( - "github.com/nspcc-dev/neo-go/pkg/interop/attribute" - "github.com/nspcc-dev/neo-go/pkg/interop/witness" -) - -// Transaction represents a NEO transaction, it's an opaque data structure -// that can be used with functions from this package. It's similar to -// Transaction class in Neo .net framework. -type Transaction struct{} - -// GetHash returns the hash (256 bit BE value in a 32 byte slice) of the given -// transaction (which also is its ID). Is uses `Neo.Transaction.GetHash` syscall. -func GetHash(t Transaction) []byte { - return nil -} - -// GetAttributes returns a slice of attributes for agiven transaction. Refer to -// attribute package on how to use them. This function uses -// `Neo.Transaction.GetAttributes` syscall. -func GetAttributes(t Transaction) []attribute.Attribute { - return []attribute.Attribute{} -} - -// GetWitnesses returns a slice of witnesses of a given Transaction. Refer to -// witness package on how to use them. This function uses -// `Neo.Transaction.GetWitnesses` syscall. -func GetWitnesses(t Transaction) []witness.Witness { - return []witness.Witness{} -}