diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 247ced9d2..39b005081 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1054,7 +1054,7 @@ func (c *codegen) convertSyscall(expr *ast.CallExpr, api, name string) { } emit.Syscall(c.prog.BinWriter, api) switch name { - case "GetTransaction": + case "GetTransaction", "GetBlock": c.emitConvert(stackitem.StructT) } diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 0c4135b59..0ea6e1158 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -36,7 +36,7 @@ var syscalls = map[string]map[string]string{ }, "blockchain": { "GetAccount": "Neo.Blockchain.GetAccount", - "GetBlock": "Neo.Blockchain.GetBlock", + "GetBlock": "System.Blockchain.GetBlock", "GetContract": "Neo.Blockchain.GetContract", "GetHeader": "Neo.Blockchain.GetHeader", "GetHeight": "Neo.Blockchain.GetHeight", diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 3796ca8da..626e0c5cd 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -305,6 +305,14 @@ 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)) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index de1503a21..a571a09cf 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -53,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()) @@ -60,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 } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index caed6569c..3a06fcb4a 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -110,3 +110,38 @@ func TestBCGetTransactionFromBlock(t *testing.T) { 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 068534d19..97bc2a6af 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -64,7 +64,7 @@ func getInteropFromSlice(ic *interop.Context, slice []interop.Function) func(uin var systemInterops = []interop.Function{ {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}, @@ -107,7 +107,6 @@ var neoInterops = []interop.Function{ {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}, diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go index db7b5c7db..5afb272a5 100644 --- a/pkg/interop/blockchain/blockchain.go +++ b/pkg/interop/blockchain/blockchain.go @@ -5,7 +5,6 @@ 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" ) @@ -34,6 +33,32 @@ type Transaction struct { 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 @@ -52,10 +77,10 @@ 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 hash (256 bit in BE