diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 492e3cdef..247ced9d2 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": + 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..b4ae7cd96 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", }, @@ -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", 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..ddb302ced 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() diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 55b57fa09..7254177f4 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 @@ -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 { diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go new file mode 100644 index 000000000..ad7a93091 --- /dev/null +++ b/pkg/core/interop_system_test.go @@ -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) + }) +} diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 7173fce5e..091316dfe 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -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. diff --git a/pkg/core/interops_test.go b/pkg/core/interops_test.go index ee78b1aa6..20f818c8e 100644 --- a/pkg/core/interops_test.go +++ b/pkg/core/interops_test.go @@ -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 { 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 index 98cd3a50d..c3295163f 100644 --- a/pkg/interop/block/block.go +++ b/pkg/interop/block/block.go @@ -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{} } diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go index bba276c7a..a18ff2ff6 100644 --- a/pkg/interop/blockchain/blockchain.go +++ b/pkg/interop/blockchain/blockchain.go @@ -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 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{} -}