From 7a2d37cf7e8cfbfec644e74441cbe294c75a89b5 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 9 Jun 2020 13:18:32 +0300 Subject: [PATCH] core: update System.Blockchain.GetBlock interop closes #1025 Now we put on stack stackitem.Array instead of Interop, so we're able to use all available block properties without extra interop getters. Removed Neo.Blockchain.GetBlock interop as we don't need it anymore. --- pkg/compiler/codegen.go | 2 +- pkg/compiler/syscall.go | 2 +- pkg/core/interop_neo_test.go | 8 +++++++ pkg/core/interop_system.go | 20 +++++++++++++--- pkg/core/interop_system_test.go | 35 ++++++++++++++++++++++++++++ pkg/core/interops.go | 3 +-- pkg/interop/blockchain/blockchain.go | 35 ++++++++++++++++++++++++---- 7 files changed, 93 insertions(+), 12 deletions(-) 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