diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index efd9c6daf..9d719dec4 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -35,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": "System.Blockchain.GetTransaction", - "GetTransactionHeight": "System.Blockchain.GetTransactionHeight", - "GetValidators": "Neo.Blockchain.GetValidators", + "GetAccount": "Neo.Blockchain.GetAccount", + "GetBlock": "Neo.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", diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index ddb302ced..3796ca8da 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -306,11 +306,16 @@ func TestContractIsPayable(t *testing.T) { // Helper functions to create VM, InteropContext, TX, Account, Contract. 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 8edb9fa4a..18be5a935 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -148,6 +148,27 @@ func bcGetTransaction(ic *interop.Context, v *vm.VM) error { 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 + } + 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) diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index ad7a93091..caed6569c 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -52,3 +52,61 @@ func TestBCGetTransaction(t *testing.T) { 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) + }) +} diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 14d6b8fee..104df3834 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -70,6 +70,7 @@ var systemInterops = []interop.Function{ {Name: "System.Blockchain.GetHeader", Func: bcGetHeader, Price: 100}, {Name: "System.Blockchain.GetHeight", Func: bcGetHeight, Price: 1}, {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}, diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go index fbe2bbe2d..db7b5c7db 100644 --- a/pkg/interop/blockchain/blockchain.go +++ b/pkg/interop/blockchain/blockchain.go @@ -65,6 +65,14 @@ 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 `System.Blockchain.GetTransactionHeight` syscall.