core: implement new System.Blockchain.GetTransaction interop

closes #1023

Now we put on stack stackitem.Array instead of Interop, so we don't
need old transaction-related interops anymore. Removed the following
interops:
	System.Transaction.GetHash
	Neo.Transaction.GetAttributes
	Neo.Transaction.GetHash
	Neo.Transaction.GetWitnesses
	Neo.Attribute.GetData
	Neo.Attribute.GetUsage

Also removed the following duplicated NEO interop:
	Neo.Blockchain.GetTransaction
This commit is contained in:
Anna Shaleva 2020-06-08 18:36:19 +03:00
parent f2e3be6fef
commit 53655c5ac2
13 changed files with 129 additions and 245 deletions

View file

@ -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":
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.

View file

@ -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",
},
@ -44,7 +40,7 @@ var syscalls = map[string]map[string]string{
"GetContract": "Neo.Blockchain.GetContract",
"GetHeader": "Neo.Blockchain.GetHeader",
"GetHeight": "Neo.Blockchain.GetHeight",
"GetTransaction": "Neo.Blockchain.GetTransaction",
"GetTransaction": "System.Blockchain.GetTransaction",
"GetTransactionHeight": "Neo.Blockchain.GetTransactionHeight",
"GetValidators": "Neo.Blockchain.GetValidators",
},
@ -63,11 +59,6 @@ var syscalls = map[string]map[string]string{
"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",

View file

@ -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()

View file

@ -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()

View file

@ -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
@ -112,13 +116,35 @@ 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)
if err != nil {
return err
tx, h, err := getTransactionAndHeight(ic.DAO, v)
if err != nil || !isTraceableBlock(ic, h) {
v.Estack().PushVal(stackitem.Null{})
return nil
}
v.Estack().PushVal(stackitem.NewInterop(tx))
v.Estack().PushVal(transactionToStackItem(tx))
return nil
}
@ -234,17 +260,6 @@ func blockGetTransaction(ic *interop.Context, v *vm.VM) error {
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 {

View file

@ -0,0 +1,54 @@
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)
})
}

View file

@ -69,7 +69,7 @@ var systemInterops = []interop.Function{
{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.GetTransactionHeight", Func: bcGetTransactionHeight, Price: 100},
{Name: "System.Contract.Call", Func: contractCall, Price: 1},
{Name: "System.Contract.CallEx", Func: contractCallEx, Price: 1},
@ -98,15 +98,12 @@ 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},
@ -115,7 +112,6 @@ var neoInterops = []interop.Function{
{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},
@ -157,9 +153,6 @@ 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.

View file

@ -34,8 +34,6 @@ func TestUnexpectedNonInterops(t *testing.T) {
funcs := []func(*interop.Context, *vm.VM) error{
accountGetBalance,
accountGetScriptHash,
attrGetData,
attrGetUsage,
blockGetTransaction,
blockGetTransactionCount,
blockGetTransactions,
@ -55,9 +53,6 @@ func TestUnexpectedNonInterops(t *testing.T) {
storageGet,
storagePut,
storagePutEx,
txGetAttributes,
txGetHash,
txGetWitnesses,
witnessGetVerificationScript,
}
for _, f := range funcs {

View file

@ -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
}

View file

@ -3,7 +3,7 @@ Package block provides getters for Neo Block structure.
*/
package block
import "github.com/nspcc-dev/neo-go/pkg/interop/transaction"
import "github.com/nspcc-dev/neo-go/pkg/interop/blockchain"
// 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
@ -19,12 +19,12 @@ func GetTransactionCount(b Block) int {
// 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{}
func GetTransactions(b Block) []blockchain.Transaction {
return []blockchain.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{}
func GetTransaction(b Block, index int) blockchain.Transaction {
return blockchain.Transaction{}
}

View file

@ -8,9 +8,32 @@ import (
"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
}
// 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
@ -35,12 +58,11 @@ func GetBlock(heightOrHash interface{}) block.Block {
return block.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{}
}
// GetTransactionHeight returns transaction's height (index of the block that

View file

@ -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

View file

@ -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{}
}