From 6dff01672c145a798dae621aa9b4ecb4c7011aa8 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 17:45:37 +0300 Subject: [PATCH 01/35] interop/account: update and extend documentation --- pkg/interop/account/account.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pkg/interop/account/account.go b/pkg/interop/account/account.go index c2e4cb19c..c18172eb7 100644 --- a/pkg/interop/account/account.go +++ b/pkg/interop/account/account.go @@ -1,23 +1,36 @@ +/* +Package account provides getter functions for Account interop structure. +To use these functions you need to get an Account first via blockchain.GetAccount +call. +*/ package account -// Package account provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Account stubs a NEO account type. +// Account represents NEO account type that is used in interop functions, 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 Account class in the Neo .net +// framework. type Account struct{} -// GetScriptHash returns the script hash of the given account. +// GetScriptHash returns the script hash of the given Account (20 bytes in BE +// representation). It uses `Neo.Account.GetBalance` syscall internally. func GetScriptHash(a Account) []byte { return nil } -// GetVotes returns the votes of the given account which should be a slice of -// public key raw bytes. +// GetVotes returns current votes of the given account represented as a slice of +// public keys. Keys are serialized into byte slices in their compressed form (33 +// bytes long each). This function uses `Neo.Account.GetVotes` syscall +// internally. func GetVotes(a Account) [][]byte { return nil } -// GetBalance returns the balance of for the given account and asset id. +// GetBalance returns current balance of the given asset (by its ID, 256 bit +// hash in BE form) for the given account. Only native UTXO assets can be +// queiried via this function, for NEP-5 ones use respective contract calls. +// The value returned is represented as an integer with original value multiplied +// by 10⁸ so you can work with fractional parts of the balance too. This function +// uses `Neo.Account.GetBalance` syscall internally. func GetBalance(a Account, assetID []byte) int { return 0 } From 83df376d17c663c96c1aed8fbdda23c527389a17 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 18:16:16 +0300 Subject: [PATCH 02/35] account: add missing IsStandard interop function There is a Neo.Account.IsStandard syscall, but we didn't have a wrapper for it. --- pkg/interop/account/account.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/interop/account/account.go b/pkg/interop/account/account.go index c18172eb7..f0595eec0 100644 --- a/pkg/interop/account/account.go +++ b/pkg/interop/account/account.go @@ -34,3 +34,10 @@ func GetVotes(a Account) [][]byte { func GetBalance(a Account, assetID []byte) int { return 0 } + +// IsStandard checks whether given account uses standard (CHECKSIG or +// CHECKMULTISIG) contract. It only works for deployed contracts and uses +// `Neo.Account.IsStandard` syscall internally. +func IsStandard(a Account) bool { + return false +} From 9c46e797456a75fb54b2556d2a1508f588d83465 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 18:50:37 +0300 Subject: [PATCH 03/35] compiler: add support for account syscalls Turns out, they never functioned correctly. --- pkg/compiler/syscall.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 01234955b..c0baaaa03 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -1,6 +1,12 @@ package compiler var syscalls = map[string]map[string]string{ + "account": { + "GetBalance": "Neo.Account.GetBalance", + "GetScriptHash": "Neo.Account.GetScriptHash", + "GetVotes": "Neo.Account.GetVotes", + "IsStandard": "Neo.Account.IsStandard", + }, "storage": { "GetContext": "Neo.Storage.GetContext", "Put": "Neo.Storage.Put", From cbcc8e160f133526920da61722b542925d542062 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 21:13:28 +0300 Subject: [PATCH 04/35] asset: update documentation and fix Create/Renew Both Create and Renew have things returned from them. --- pkg/interop/asset/asset.go | 76 ++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/pkg/interop/asset/asset.go b/pkg/interop/asset/asset.go index 3c84ea367..01547f64c 100644 --- a/pkg/interop/asset/asset.go +++ b/pkg/interop/asset/asset.go @@ -1,53 +1,97 @@ +/* +Package asset provides functions to work with regular UTXO assets (like NEO or GAS). +Mostly these are getters for Asset structure, but you can also create new assets +and renew them (although it's recommended to use NEP-5 standard for new tokens). +*/ package asset -// Package asset provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Asset stubs a NEO asset type. +// Asset represents NEO asset type that is used in interop functions, 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 Asset class in the Neo .net +// framework. To be able to use it you either need to get an existing Asset via +// blockchain.GetAsset function or create a new one via Create. type Asset struct{} -// GetAssetID returns the id of the given asset. +// GetAssetID returns ID (256-bit ID of Register transaction for this asset in BE +// representation) of the given asset. It uses `Neo.Asset.GetAssetId` syscall +// internally. func GetAssetID(a Asset) []byte { return nil } -// GetAssetType returns the type of the given asset. +// GetAssetType returns type of the given asset as a byte value. The value +// returned can be interpreted as a bit field with the following meaning: +// CreditFlag = 0x40 +// DutyFlag = 0x80 +// SystemShare = 0x00 +// SystemCoin = 0x01 +// Currency = 0x08 +// Share = DutyFlag | 0x10 +// Invoice = DutyFlag | 0x18 +// Token = CreditFlag | 0x20 +// It uses `Neo.Asset.GetAssetType` syscall internally. func GetAssetType(a Asset) byte { return 0x00 } -// GetAmount returns the amount of the given asset. +// GetAmount returns the total amount of the given asset as an integer +// multiplied by 10⁸. This value is the maximum possible circulating quantity of +// Asset. The function uses `Neo.Asset.GetAmount` syscall internally. func GetAmount(a Asset) int { return 0 } -// GetAvailable returns the available of the given asset. +// GetAvailable returns the amount of Asset currently available on the +// blockchain. It uses the same encoding as the result of GetAmount and its +// value can never exceed the value returned by GetAmount. This function uses +// `Neo.Asset.GetAvailable` syscall internally. func GetAvailable(a Asset) int { return 0 } -// GetPrecision returns the precision of the given asset. +// GetPrecision returns precision of the given Asset. It uses +// `Neo.Asset.GetPrecision` syscall internally. func GetPrecision(a Asset) byte { return 0x00 } -// GetOwner returns the owner of the given asset. +// GetOwner returns the owner of the given Asset. It's represented as a +// serialized (in compressed form) public key (33 bytes long). This function +// uses `Neo.Asset.GetOwner` syscall internally. func GetOwner(a Asset) []byte { return nil } -// GetAdmin returns the admin of the given asset. +// GetAdmin returns the admin of the given Asset represented as a 160 bit hash +// in BE form (contract script hash). Admin can modify attributes of this Asset. +// This function uses `Neo.Asset.GetAdmin` syscall internally. func GetAdmin(a Asset) []byte { return nil } -// GetIssuer returns the issuer of the given asset. +// GetIssuer returns the issuer of the given Asset represented as a 160 bit hash +// in BE form (contract script hash). Issuer can issue new tokens for this Asset. +// This function uses `Neo.Asset.GetIssuer` syscall internally. func GetIssuer(a Asset) []byte { return nil } -// Create registers a new asset on the blockchain. -func Create(assetType byte, name string, amount int, precision byte, owner, admin, issuer []byte) {} +// Create registers a new asset on the blockchain (similar to old Register +// transaction). `assetType` parameter has the same set of possible values as +// GetAssetType result, `amount` must be multiplied by 10⁸, `precision` limits +// the smallest possible amount of new Asset to 10⁻ⁿ (where n is precision which +// can't exceed 8), `owner` is a public key of the owner in compressed serialized +// form (33 bytes), `admin` and `issuer` should be represented as 20-byte slices +// storing 160-bit hash in BE form. Created Asset is set to expire in one year, +// so you need to renew it in time. If successful, this function returns a new +// Asset. It uses `Neo.Asset.Create` syscall internally. +func Create(assetType byte, name string, amount int, precision byte, owner, admin, issuer []byte) Asset { + return Asset{} +} -// Renew renews the existence of an asset by the given years. -func Renew(asset Asset, years int) {} +// Renew renews (make available for use) existing asset by the specified number +// of years. It returns the last block number when this asset will be active. +// It uses `Neo.Asset.Renew` syscall internally. +func Renew(asset Asset, years int) int { + return 0 +} From f69e654260b0982a5c721a78869d1b677f14d05c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 21:16:53 +0300 Subject: [PATCH 05/35] compiler: add missing asset syscalls, sort them --- pkg/compiler/syscall.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index c0baaaa03..f934700fb 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -59,10 +59,15 @@ var syscalls = map[string]map[string]string{ "GetScript": "Neo.Transaction.GetScript", }, "asset": { + "Create": "Neo.Asset.Create", + "GetAdmin": "Neo.Asset.GetAdmin", + "GetAmount": "Neo.Asset.GetAmount", "GetAssetID": "Neo.Asset.GetAssetID", "GetAssetType": "Neo.Asset.GetAssetType", - "GetAmount": "Neo.Asset.GetAmount", - "Create": "Neo.Asset.Create", + "GetAvailable": "Neo.Asset.GetAvailable", + "GetIssuer": "Neo.Asset.GetIssuer", + "GetOwner": "Neo.Asset.GetOwner", + "GetPrecision": "Neo.Asset.GetPrecision", "Renew": "Neo.Asset.Renew", }, "contract": { From 77c5f28b09c414d87195a54b71670d8313603c3d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 21:29:05 +0300 Subject: [PATCH 06/35] interop/attribute: update documentation --- pkg/interop/attribute/attribute.go | 60 +++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/pkg/interop/attribute/attribute.go b/pkg/interop/attribute/attribute.go index 6147b5be2..b4c0175c4 100644 --- a/pkg/interop/attribute/attribute.go +++ b/pkg/interop/attribute/attribute.go @@ -1,17 +1,65 @@ +/* +Package attribute provides getters for transaction attributes. +*/ package attribute -// Package attribute provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Attribute stubs a NEO transaction attribute type. +// 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 of the given attribute. +// 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. +// 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 } From 7abd35b358330b40dbea8a926b5d33beb577acd6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 21:32:07 +0300 Subject: [PATCH 07/35] compiler: add support for attribute syscalls --- pkg/compiler/syscall.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index f934700fb..66336a141 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -7,6 +7,10 @@ var syscalls = map[string]map[string]string{ "GetVotes": "Neo.Account.GetVotes", "IsStandard": "Neo.Account.IsStandard", }, + "attribute": { + "GetUsage": "Neo.Attribute.GetUsage", + "GetData": "Neo.Attribute.GetData", + }, "storage": { "GetContext": "Neo.Storage.GetContext", "Put": "Neo.Storage.Put", From d09c3b1e27d438a68596d88aca78c40b8bc169e5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 21:59:20 +0300 Subject: [PATCH 08/35] interop/block: update documentation, fix GetTransaction GetTransaction never worked with hash, it works with indexes. --- pkg/interop/block/block.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/interop/block/block.go b/pkg/interop/block/block.go index e7171614a..98cd3a50d 100644 --- a/pkg/interop/block/block.go +++ b/pkg/interop/block/block.go @@ -1,25 +1,30 @@ +/* +Package block provides getters for Neo Block structure. +*/ package block import "github.com/nspcc-dev/neo-go/pkg/interop/transaction" -// Package block provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Block stubs a NEO block type. +// 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 +// the Block class in the Neo .net framework. To use it you need to get it via +// blockchain.GetBlock function call. type Block struct{} -// GetTransactionCount returns the number of recorded transactions in the given block. +// GetTransactionCount returns the number of recorded transactions in the given +// block. It uses `Neo.Block.GetTransactionCount` syscall internally. func GetTransactionCount(b Block) int { return 0 } // 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{} } -// GetTransaction returns a transaction from the given a block hash of the -// transaction. -func GetTransaction(b Block, hash []byte) transaction.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{} } From 9e94895bb008501b36a4020a1ef3a736c80ac6fd Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 23:30:16 +0300 Subject: [PATCH 09/35] interop/blockchain: update documentation --- pkg/interop/blockchain/blockchain.go | 45 +++++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go index 18ae7216b..120b8cec6 100644 --- a/pkg/interop/blockchain/blockchain.go +++ b/pkg/interop/blockchain/blockchain.go @@ -1,3 +1,6 @@ +/* +Package blockchain provides functions to access various blockchain data. +*/ package blockchain import ( @@ -9,45 +12,65 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/transaction" ) -// Package blockchain provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// GetHeight returns the height of te block recorded in the current execution scope. +// 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 +// the previous (already accepted) block. This function uses +// `Neo.Blockchain.GetHeight` syscall. func GetHeight() int { return 0 } -// GetHeader returns the header found by the given hash or index. +// GetHeader returns header found by the given hash (256 bit hash in BE format +// represented as a slice of 32 bytes) or index (integer). Refer to the `header` +// package for possible uses of returned structure. This function uses +// `Neo.Blockchain.GetHeader` syscall. func GetHeader(heightOrHash interface{}) header.Header { return header.Header{} } -// GetBlock returns the block found by the given hash or index. +// 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{} } -// GetTransaction returns the transaction found by the given hash. +// 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{} } -// GetContract returns the contract found by the given script hash. +// GetContract returns contract found by the given script hash (160 bit in BE +// format represented as a slice of 20 bytes). Refer to the `contract` package +// for details on how to use the returned structure. This function uses +// `Neo.Blockchain.GetContract` syscall. func GetContract(scriptHash []byte) contract.Contract { return contract.Contract{} } -// GetAccount returns the account found by the given script hash. +// GetAccount returns account found by the given script hash (160 bit in BE +// format represented as a slice of 20 bytes). Refer to the `account` package +// for details on how to use the returned structure. This function uses +// `Neo.Blockchain.GetAccount` syscall. func GetAccount(scriptHash []byte) account.Account { return account.Account{} } -// GetValidators returns a slice of validator addresses. +// GetValidators returns a slice of current validators public keys represented +// as a compressed serialized byte slice (33 bytes long). This function uses +// `Neo.Blockchain.GetValidators` syscall. func GetValidators() [][]byte { return nil } -// GetAsset returns the asset found by the given asset id. +// GetAsset returns asset found by the given asset ID (256 bit in BE format +// represented as a slice of 32 bytes). Refer to the `asset` package for +// possible uses of returned structure. This function uses +// `Neo.Blockchain.GetAsset` syscall. func GetAsset(assetID []byte) asset.Asset { return asset.Asset{} } From 80b8b50f0275b87aa0f038b76dd308cc67a8e57f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 23:31:08 +0300 Subject: [PATCH 10/35] core: fix Neo.Blockchain.GetValidators implementation It should return keys, attempting to push []*state.Validator to the stack would probably lead to failure. --- pkg/core/interop_neo.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 277e06620..052166555 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -236,7 +236,14 @@ func (ic *interopContext) witnessGetVerificationScript(v *vm.VM) error { // bcGetValidators returns validators. func (ic *interopContext) bcGetValidators(v *vm.VM) error { - validators := ic.dao.GetValidators() + valStates := ic.dao.GetValidators() + if len(valStates) > vm.MaxArraySize { + return errors.New("too many validators") + } + validators := make([]vm.StackItem, 0, len(valStates)) + for _, val := range valStates { + validators = append(validators, vm.NewByteArrayItem(val.PublicKey.Bytes())) + } v.Estack().PushVal(validators) return nil } From 5d941364bdd3694074093842c76c458083ec61a8 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 23:35:56 +0300 Subject: [PATCH 11/35] compiler/interop: expose GetTransactionHeight And sort syscall names as they change the indentation anyway. --- pkg/compiler/syscall.go | 17 +++++++++-------- pkg/interop/blockchain/blockchain.go | 7 +++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 66336a141..77367a514 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -28,14 +28,15 @@ var syscalls = map[string]map[string]string{ "Deserialize": "Neo.Runtime.Deserialize", }, "blockchain": { - "GetHeight": "Neo.Blockchain.GetHeight", - "GetHeader": "Neo.Blockchain.GetHeader", - "GetBlock": "Neo.Blockchain.GetBlock", - "GetTransaction": "Neo.Blockchain.GetTransaction", - "GetContract": "Neo.Blockchain.GetContract", - "GetAccount": "Neo.Blockchain.GetAccount", - "GetValidators": "Neo.Blockchain.GetValidators", - "GetAsset": "Neo.Blockchain.GetAsset", + "GetAccount": "Neo.Blockchain.GetAccount", + "GetAsset": "Neo.Blockchain.GetAsset", + "GetBlock": "Neo.Blockchain.GetBlock", + "GetContract": "Neo.Blockchain.GetContract", + "GetHeader": "Neo.Blockchain.GetHeader", + "GetHeight": "Neo.Blockchain.GetHeight", + "GetTransaction": "Neo.Blockchain.GetTransaction", + "GetTransactionHeight": "Neo.Blockchain.GetTransactionHeight", + "GetValidators": "Neo.Blockchain.GetValidators", }, "header": { "GetIndex": "Neo.Header.GetIndex", diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go index 120b8cec6..c38e4dd7d 100644 --- a/pkg/interop/blockchain/blockchain.go +++ b/pkg/interop/blockchain/blockchain.go @@ -44,6 +44,13 @@ func GetTransaction(hash []byte) transaction.Transaction { return transaction.Transaction{} } +// 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 `Neo.Blockchain.GetTransactionHeight` syscall. +func GetTransactionHeight(hash []byte) int { + return 0 +} + // GetContract returns contract found by the given script hash (160 bit in BE // format represented as a slice of 20 bytes). Refer to the `contract` package // for details on how to use the returned structure. This function uses From 541d56a874d2bafe459082996ff35e1882e27996 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 23:56:52 +0300 Subject: [PATCH 12/35] gitignore: don't ignore blockchain(s) directories It interferes with interop/blockchain and it's not a default directory for chain DBs. --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index cb8e5a620..6fe160927 100644 --- a/.gitignore +++ b/.gitignore @@ -31,8 +31,6 @@ TAGS # leveldb chains/ chain/ -blockchain/ -blockchains/ # patch *.orig From a43f2234dd62f223948e6aea140bb588cd021077 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 17 May 2020 23:58:23 +0300 Subject: [PATCH 13/35] core: fix Neo.Contract.GetStorageContext security check This syscall should only work for contracts created by current transaction and that is what is supposed to be checked here. Do so by looking at the differences between ic.dao and original lower DAO. --- pkg/core/interop_system.go | 8 ++++++-- pkg/core/interops.go | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index f07da9905..9c4da985f 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -575,8 +575,12 @@ func (ic *interopContext) contractGetStorageContext(v *vm.VM) error { if !ok { return fmt.Errorf("%T is not a contract state", cs) } - contractState, err := ic.dao.GetContractState(cs.ScriptHash()) - if contractState == nil || err != nil { + _, err := ic.dao.GetContractState(cs.ScriptHash()) + if err != nil { + return fmt.Errorf("non-existent contract") + } + _, err = ic.lowerDao.GetContractState(cs.ScriptHash()) + if err == nil { return fmt.Errorf("contract was not created in this transaction") } stc := &StorageContext{ diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 445be4d76..25040bce6 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -27,6 +27,7 @@ type interopContext struct { block *block.Block tx *transaction.Transaction dao *dao.Cached + lowerDao dao.DAO notifications []state.NotificationEvent log *zap.Logger } @@ -34,7 +35,7 @@ type interopContext struct { func newInteropContext(trigger trigger.Type, bc Blockchainer, d dao.DAO, block *block.Block, tx *transaction.Transaction, log *zap.Logger) *interopContext { dao := dao.NewCached(d) nes := make([]state.NotificationEvent, 0) - return &interopContext{bc, trigger, block, tx, dao, nes, log} + return &interopContext{bc, trigger, block, tx, dao, d, nes, log} } // SpawnVM returns a VM with script getter and interop functions set From f0047b4055ad73f71a92547316f15afc98bded13 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 10:42:44 +0300 Subject: [PATCH 14/35] interop/contract: update documentation, fix some interfaces Some functions were just not correct in their interfaces. --- pkg/interop/contract/contract.go | 68 +++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/pkg/interop/contract/contract.go b/pkg/interop/contract/contract.go index f1d6af376..d220b85e2 100644 --- a/pkg/interop/contract/contract.go +++ b/pkg/interop/contract/contract.go @@ -1,55 +1,85 @@ +/* +Package contract provides functions to work with contracts. +*/ package contract import "github.com/nspcc-dev/neo-go/pkg/interop/storage" -// Package contract provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Contract stubs a NEO contract type. +// Contract represents a Neo contract and is used in interop functions. It's +// an opaque data structure that you can manipulate with using functions from +// this package. It's similar in function to the Contract class in the Neo .net +// framework. type Contract struct{} -// GetScript returns the script of the given contract. +// GetScript returns the script of the given contract. It uses +// `Neo.Contract.GetScript` syscall. func GetScript(c Contract) []byte { return nil } -// IsPayable returns whether the given contract is payable. +// IsPayable returns whether the given contract is payable (able to accept +// asset transfers to its address). It uses `Neo.Contract.IsPayable` syscall. func IsPayable(c Contract) bool { return false } -// GetStorageContext returns the storage context for the given contract. +// GetStorageContext returns storage context for the given contract. It only +// works for contracts created in this transaction (so you can't take a storage +// context for arbitrary contract). Refer to the `storage` package on how to +// use this context. This function uses `Neo.Contract.GetStorageContext` syscall. func GetStorageContext(c Contract) storage.Context { return storage.Context{} } -// Create creates a new contract. -// @FIXME What is the type of the returnType here? +// Create creates a new contract using a set of input parameters: +// script contract's bytecode (limited in length by 1M) +// params contract's input parameter types, one byte per parameter, see +// ParamType in the `smartcontract` package for value +// definitions. Maximum number of parameters: 252. +// returnType return value type, also a ParamType constant +// properties bit field with contract's permissions (storage, dynamic +// invoke, payable), see PropertyState in the `smartcontract` +// package +// name human-readable contract name (no longer than 252 bytes) +// version human-readable contract version (no longer than 252 bytes) +// author contract's author (no longer than 252 bytes) +// email contract's author/support e-mail (no longer than 252 bytes) +// description human-readable contract description (no longer than 64K bytes) +// It returns this new created Contract when successful (and fails transaction +// if not). It uses `Neo.Contract.Create` syscall. func Create( script []byte, - params []interface{}, + params []byte, returnType byte, - properties interface{}, + properties byte, name, version, author, email, - description string) { + description string) Contract { + return Contract{} } -// Migrate migrates a new contract. -// @FIXME What is the type of the returnType here? +// Migrate migrates calling contract (that is the one that calls Migrate) to +// the new contract. Its parameters have exactly the same semantics as for +// Create. The old contract will be deleted by this call, if it has any storage +// associated it will be migrated to the new contract. New contract is returned. +// This function uses `Neo.Contract.Migrate` syscall. func Migrate( script []byte, - params []interface{}, + params []byte, returnType byte, - properties interface{}, + properties byte, name, version, author, email, - description string) { + description string) Contract { + return Contract{} } -// Destroy deletes a contract that is registered on the blockchain. -func Destroy(c Contract) {} +// Destroy deletes calling contract (the one that calls Destroy) from the +// blockchain, so it's only possible to do that from the contract itself and +// not by any outside code. When contract is deleted all associated storage +// items are deleted too. This function uses `Neo.Contract.Destroy` syscall. +func Destroy() {} From 10abac43623e2a157ce69182fbd3c0d5523b8232 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 10:58:27 +0300 Subject: [PATCH 15/35] interop/crypto: update documentation --- pkg/interop/crypto/crypto.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/interop/crypto/crypto.go b/pkg/interop/crypto/crypto.go index abfe586e8..5dbee46ed 100644 --- a/pkg/interop/crypto/crypto.go +++ b/pkg/interop/crypto/crypto.go @@ -1,29 +1,32 @@ +/* +Package crypto provides an interface to VM cryptographic instructions. +*/ package crypto -// Package crypto provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// SHA1 computes the sha1 hash of b. +// SHA1 computes SHA1 hash of b. It uses `SHA1` VM instruction. func SHA1(b []byte) []byte { return nil } -// SHA256 computes the sha256 hash of b. +// SHA256 computes SHA256 hash of b. It uses `SHA256` VM instruction. func SHA256(b []byte) []byte { return nil } -// Hash160 computes the sha256 + ripemd160 of b. +// Hash160 computes SHA256 + RIPEMD-160 of b, which is commonly used for +// script hashing and address generation. It uses `HASH160` VM instruction. func Hash160(b []byte) []byte { return nil } -// Hash256 computes the sha256^2 hash of b. +// Hash256 computes double SHA256 hash of b (SHA256(SHA256(b))) which is used +// as ID for transactions, blocks and assets. It uses `HASH256` VM instruction. func Hash256(b []byte) []byte { return nil } -// VerifySignature checks that sig is msg's signature with pub. +// VerifySignature checks that sig is correct msg's signature for a given pub +// (serialized public key). It uses `VERIFY` VM instruction. func VerifySignature(msg []byte, sig []byte, pub []byte) bool { return false } From 78b23876407d2a80e4313183a40d36cfa1be32e9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 12:01:00 +0300 Subject: [PATCH 16/35] interop/engine: update documentation --- pkg/interop/engine/engine.go | 44 +++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/pkg/interop/engine/engine.go b/pkg/interop/engine/engine.go index 06a54e3fc..e4d303067 100644 --- a/pkg/interop/engine/engine.go +++ b/pkg/interop/engine/engine.go @@ -1,34 +1,56 @@ +/* +Package engine provides access to VM execution metadata and allows to make contract calls. +It's roughly similar in function to ExecutionEngine class in the Neo .net +framework. +*/ package engine import "github.com/nspcc-dev/neo-go/pkg/interop/transaction" -// Package engine provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// GetScriptContainer returns the transaction that is in the execution context. +// 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{} } -// GetExecutingScriptHash returns the script hash of the contract that is -// currently being executed. +// GetExecutingScriptHash returns script hash (160 bit in BE form represented +// as 20-byte slice) of the contract that is currently being executed. Any +// AppCall can change the value returned by this function if it calls a +// different contract. This function uses +// `System.ExecutionEngine.GetExecutingScriptHash` syscall. func GetExecutingScriptHash() []byte { return nil } -// GetCallingScriptHash returns the script hash of the contract that started -// the execution of the current script. +// GetCallingScriptHash returns script hash (160 bit in BE form represented +// as 20-byte slice) of the contract that started the execution of the currently +// running context (caller of current contract or function), so it's one level +// above the GetExecutingScriptHash in the call stack. It uses +// `System.ExecutionEngine.GetCallingScriptHash` syscall. func GetCallingScriptHash() []byte { return nil } -// GetEntryScriptHash returns the script hash of the contract that started the -// execution from the start. +// GetEntryScriptHash returns script hash (160 bit in BE form represented +// as 20-byte slice) of the contract that initially started current execution +// (this is a script that is contained in a transaction returned by +// GetScriptContainer) execution from the start. This function uses +// `System.ExecutionEngine.GetEntryScriptHash` syscall. func GetEntryScriptHash() []byte { return nil } -// AppCall executes script with specified hash using provided arguments. +// AppCall executes previously deployed blockchain contract with specified hash +// (160 bit in BE form represented as 20-byte slice) using provided arguments. +// It returns whatever this contract returns. Even though this function accepts +// a slice for scriptHash you can only use it for contracts known at +// compile time, because there is a significant difference between static and +// dynamic calls in Neo (contracts should have a special property declared +// and paid for to be able to use dynamic calls). This function uses `APPCALL` +// opcode. func AppCall(scriptHash []byte, args ...interface{}) interface{} { return nil } From 5cebd4a7a2e3bf8e45a410e2dd6b47aa05dec9c9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 12:37:33 +0300 Subject: [PATCH 17/35] compiler/engine: add dynamic APPCALL generation, fix #914 Previously we could generate dynamic appcall with a kludge of AppCall([]byte{/* 20 zeroes */, realScriptHash, args...) Now there is a separate function for this. --- pkg/compiler/analysis.go | 2 +- pkg/compiler/codegen.go | 19 ++++++++--- pkg/compiler/interop_test.go | 63 ++++++++++++++++++++++++++++++++++++ pkg/interop/engine/engine.go | 10 ++++++ 4 files changed, 88 insertions(+), 6 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 34134c6d5..bf4430c74 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -16,7 +16,7 @@ var ( "SHA1", "Hash256", "Hash160", "VerifySignature", "AppCall", "FromAddress", "Equals", - "panic", + "panic", "DynAppCall", } ) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index a88f56cf6..c2c312c6b 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1085,14 +1085,23 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { emit.Opcode(c.prog.BinWriter, opcode.HASH160) case "VerifySignature": emit.Opcode(c.prog.BinWriter, opcode.VERIFY) - case "AppCall": - numArgs := len(expr.Args) - 1 + case "AppCall", "DynAppCall": + numArgs := len(expr.Args) + if name == "AppCall" { + numArgs-- + } c.emitReverse(numArgs) emit.Opcode(c.prog.BinWriter, opcode.APPCALL) - buf := c.getByteArray(expr.Args[0]) - if len(buf) != 20 { - c.prog.Err = errors.New("invalid script hash") + var buf []byte + if name == "AppCall" { + buf = c.getByteArray(expr.Args[0]) + if len(buf) != 20 { + c.prog.Err = errors.New("invalid script hash") + } + } else { + // Zeroes for DynAppCall. + buf = make([]byte, 20) } c.prog.WriteBytes(buf) diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index 39435c243..e335f5882 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/stretchr/testify/require" ) @@ -52,6 +53,19 @@ func TestFromAddress(t *testing.T) { } func TestAppCall(t *testing.T) { + const srcDynApp = ` + package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/engine" + func Main(h []byte) []byte { + x := []byte{1, 2} + y := []byte{3, 4} + result := engine.DynAppCall(h, x, y) + return result.([]byte) + } +` + + var hasDynamicInvoke bool + srcInner := ` package foo func Main(a []byte, b []byte) []byte { @@ -62,14 +76,31 @@ func TestAppCall(t *testing.T) { inner, err := compiler.Compile(strings.NewReader(srcInner)) require.NoError(t, err) + dynapp, err := compiler.Compile(strings.NewReader(srcDynApp)) + require.NoError(t, err) + ih := hash.Hash160(inner) + dh := hash.Hash160(dynapp) getScript := func(u util.Uint160) ([]byte, bool) { if u.Equals(ih) { return inner, true } + if u.Equals(dh) { + return dynapp, hasDynamicInvoke + } return nil, false } + dynEntryScript := ` + package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/engine" + func Main(h []byte) interface{} { + return engine.AppCall(` + fmt.Sprintf("%#v", dh.BytesBE()) + `, h) + } +` + dynentry, err := compiler.Compile(strings.NewReader(dynEntryScript)) + require.NoError(t, err) + t.Run("valid script", func(t *testing.T) { src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) v := vmAndCompile(t, src) @@ -118,6 +149,38 @@ func TestAppCall(t *testing.T) { assertResult(t, v, []byte{1, 2, 3, 4}) }) + + t.Run("dynamic", func(t *testing.T) { + t.Run("valid script", func(t *testing.T) { + hasDynamicInvoke = true + v := vm.New() + v.Load(dynentry) + v.SetScriptGetter(getScript) + v.Estack().PushVal(ih.BytesBE()) + + require.NoError(t, v.Run()) + + assertResult(t, v, []byte{1, 2, 3, 4}) + }) + t.Run("invalid script", func(t *testing.T) { + hasDynamicInvoke = true + v := vm.New() + v.Load(dynentry) + v.SetScriptGetter(getScript) + v.Estack().PushVal([]byte{1}) + + require.Error(t, v.Run()) + }) + t.Run("no dynamic invoke", func(t *testing.T) { + hasDynamicInvoke = false + v := vm.New() + v.Load(dynentry) + v.SetScriptGetter(getScript) + v.Estack().PushVal(ih.BytesBE()) + + require.Error(t, v.Run()) + }) + }) } func getAppCallScript(h string) string { diff --git a/pkg/interop/engine/engine.go b/pkg/interop/engine/engine.go index e4d303067..92ec895f0 100644 --- a/pkg/interop/engine/engine.go +++ b/pkg/interop/engine/engine.go @@ -54,3 +54,13 @@ func GetEntryScriptHash() []byte { func AppCall(scriptHash []byte, args ...interface{}) interface{} { return nil } + +// DynAppCall executes previously deployed blockchain contract with specified +// hash (160 bit in BE form represented as 20-byte slice) using provided +// arguments. It returns whatever this contract returns. It differs from AppCall +// in that you can use it for truly dynamic scriptHash values, but at the same +// time using it requires HasDynamicInvoke property set for a contract doing +// this call. This function uses `APPCALL` opcode. +func DynAppCall(scriptHash []byte, args ...interface{}) interface{} { + return nil +} From 8fb4bbca4ee46183c478b07ea82ae50020b0fa09 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 13:19:01 +0300 Subject: [PATCH 18/35] interop/enumerator: update doc, fix Next return value --- pkg/interop/enumerator/enumerator.go | 36 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/pkg/interop/enumerator/enumerator.go b/pkg/interop/enumerator/enumerator.go index 0d5531757..a47bff76f 100644 --- a/pkg/interop/enumerator/enumerator.go +++ b/pkg/interop/enumerator/enumerator.go @@ -1,29 +1,41 @@ +/* +Package enumerator provides functions to work with enumerators. +*/ package enumerator -// Package enumerator provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// TODO: Check enumerator use cases and add them to the examples folder. - -// Enumerator stubs a NEO enumerator type. +// Enumerator represents NEO enumerator type, it's an opaque data structure +// that can be used with functions from this package. It's similar to more +// widely used Iterator (see `iterator` package), but ranging over arrays +// or structures that have values with no explicit keys. type Enumerator struct{} -// Create creates a new enumerator from the given items. +// Create creates a new enumerator from the given items (slice or structure). +// New enumerator points at index -1 of its items, so the user of it has to +// advance it first with Next. This function uses `Neo.Enumerator.Create` +// syscall. func Create(items []interface{}) Enumerator { return Enumerator{} } -// Next returns the next item in the iteration. -func Next(e Enumerator) interface{} { - return nil +// Next moves position of the given enumerator by one and returns a bool that +// tells whether there is a new value present in this new position. If it is, +// you can use Value to get it, if not then there are no more values in this +// enumerator. This function uses `Neo.Enumerator.Next` syscall. +func Next(e Enumerator) bool { + return true } -// Value returns the enumerator value. +// Value returns current enumerator's item value, it's only valid to call it +// after Next returning true. This function uses `Neo.Enumerator.Value` syscall. func Value(e Enumerator) interface{} { return nil } -// Concat concatenates the 2 given enumerators. +// Concat concatenates two given enumerators returning one that will range on +// a first and then continue with b. Enumerator positions are not reset for a +// and b, so if any of them was already advanced by Next the resulting +// Enumerator will point at this new position and never go back to previous +// values. This function uses `Neo.Enumerator.Concat` syscall. func Concat(a, b Enumerator) Enumerator { return Enumerator{} } From d5d497ccd78a418394d96e3f87cbd2adf4fcc3e9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 13:20:49 +0300 Subject: [PATCH 19/35] compiler: add support for enumerator interop package --- pkg/compiler/syscall.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 77367a514..82d49a16e 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -11,6 +11,12 @@ var syscalls = map[string]map[string]string{ "GetUsage": "Neo.Attribute.GetUsage", "GetData": "Neo.Attribute.GetData", }, + "enumerator": { + "Concat": "Neo.Enumerator.Concat", + "Create": "Neo.Enumerator.Create", + "Next": "Neo.Enumerator.Next", + "Value": "Neo.Enumerator.Value", + }, "storage": { "GetContext": "Neo.Storage.GetContext", "Put": "Neo.Storage.Put", From 6c0553da474fbb6bf70cb917187cbcfb0e2e548c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 14:23:55 +0300 Subject: [PATCH 20/35] interop/header: update documentation --- pkg/interop/header/header.go | 38 ++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/pkg/interop/header/header.go b/pkg/interop/header/header.go index 44fc2d9e6..37abe9676 100644 --- a/pkg/interop/header/header.go +++ b/pkg/interop/header/header.go @@ -1,47 +1,61 @@ +/* +Package header contains functions working with block headers. +*/ package header -// Package header provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Header stubs a NEO block header type. +// Header represents Neo block header type, it's an opaque data structure that +// can be used by functions from this package. You can create it with +// blockchain.GetHeader. In its function it's similar to the Header class +// of the Neo .net framework. type Header struct{} -// GetIndex returns the index of the given header. +// GetIndex returns the index (height) of the given header. It uses +// `Neo.Header.GetIndex` syscall. func GetIndex(h Header) int { return 0 } -// GetHash returns the hash of the given header. +// GetHash returns the hash (256-bit BE value packed into 32 byte slice) of the +// given header (which also is a hash of the block). It uses `Neo.Header.GetHash` +// syscall. func GetHash(h Header) []byte { return nil } -// GetPrevHash returns the previous hash of the given header. +// GetPrevHash returns the hash (256-bit BE value packed into 32 byte slice) of +// the previous block stored in the given header. It uses `Neo.Header.GetPrevHash` +// syscall. func GetPrevHash(h Header) []byte { return nil } -// GetTimestamp returns the timestamp of the given header. +// GetTimestamp returns the timestamp of the given header. It uses +// `Neo.Header.GetTimestamp` syscall. func GetTimestamp(h Header) int { return 0 } -// GetVersion returns the version of the given header. +// GetVersion returns the version of the given header. It uses +// `Neo.Header.GetVersion` syscall. func GetVersion(h Header) int { return 0 } -// GetMerkleRoot returns the merkle root of the given header. +// GetMerkleRoot returns the Merkle root (256-bit BE value packed into 32 byte +// slice) of the given header. It uses `Neo.Header.GetMerkleRoot` syscall. func GetMerkleRoot(h Header) []byte { return nil } -// GetConsensusData returns the consensus data of the given header. +// GetConsensusData returns the consensus data (nonce) of the given header. +// It uses `Neo.Header.GetConsensusData` syscall. func GetConsensusData(h Header) int { return 0 } -// GetNextConsensus returns the next consensus of the given header. +// GetNextConsensus returns the next consensus field (verification script hash, +// 160-bit BE value packed into 20 byte slice) of the given header. It uses +// `Neo.Header.GetNextConsensus` syscall. func GetNextConsensus(h Header) []byte { return nil } From 31d7b07eb54a34a796ba5fc0ff6587788563e2c5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 14:37:38 +0300 Subject: [PATCH 21/35] interop/input|output: update documentation --- pkg/interop/input/input.go | 17 +++++++++++------ pkg/interop/output/output.go | 20 +++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/pkg/interop/input/input.go b/pkg/interop/input/input.go index 047637226..e577f2815 100644 --- a/pkg/interop/input/input.go +++ b/pkg/interop/input/input.go @@ -1,17 +1,22 @@ +/* +Package input provides functions dealing with transaction inputs. +*/ package input -// Package input provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Input stubs the input of a NEO transaction. +// Input is an opaque data structure that can only be created by +// transaction.GetInputs and it represents transaction's input. It's similar +// to Neo .net framework's TransactionInput. type Input struct{} -// GetHash returns the hash of the given input. +// GetHash returns the hash stored in the given input (which also is a +// transaction ID represented as 32 byte slice containing 256 bit BE value). +// It uses `Neo.Input.GetHash` syscall. func GetHash(in Input) []byte { return nil } -// GetIndex returns the index of the given input. +// GetIndex returns the index stored in the given input (which is a +// transaction's output number). It uses `Neo.Input.GetIndex` syscall. func GetIndex(in Input) int { return 0 } diff --git a/pkg/interop/output/output.go b/pkg/interop/output/output.go index 6f40ac544..01ad0c828 100644 --- a/pkg/interop/output/output.go +++ b/pkg/interop/output/output.go @@ -1,22 +1,28 @@ +/* +Package output provides functions dealing with transaction outputs. +*/ package output -// Package output provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Output stubs the output of a NEO transaction. +// Output is an opaque data structure that can only be created by +// transaction.GetOutputs and it represents transaction's output. It's similar +// to Neo .net framework's TransactionOutput. type Output struct{} -// GetAssetID returns the asset id of the given output. +// GetAssetID returns the asset ID (256 bit BE value in a 32 byte slice) of the +// given output. It uses `Neo.Output.GetAssetId` syscall. func GetAssetID(out Output) []byte { return nil } -// GetValue returns the value of the given output. +// GetValue returns the value (asset quantity) of the given output. It uses +// `Neo.Output.GetValue` syscall. func GetValue(out Output) int { return 0 } -// GetScriptHash returns the script hash of the given output. +// GetScriptHash returns the script hash (receiver's address represented as +// 20 byte slice containing 160 bit BE value) of the given output. It uses +// `Neo.Output.GetScriptHash` syscall. func GetScriptHash(out Output) []byte { return nil } From a17bd6176f0c53444677cc3f3c11651eeb39ffbf Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 15:30:44 +0300 Subject: [PATCH 22/35] interop/iterator: documentation update --- pkg/interop/iterator/iterator.go | 45 +++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/pkg/interop/iterator/iterator.go b/pkg/interop/iterator/iterator.go index 84e85310e..b4bd281da 100644 --- a/pkg/interop/iterator/iterator.go +++ b/pkg/interop/iterator/iterator.go @@ -1,39 +1,54 @@ +/* +Package iterator provides functions to work with Neo iterators. +*/ package iterator -// Package iterator provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. +import "github.com/nspcc-dev/neo-go/pkg/interop/enumerator" -// Iterator stubs a NEO iterator object type. +// Iterator represents a Neo iterator, it's an opaque data structure that can +// be properly created by Create or storage.Find. Unlike enumerators, iterators +// range over key-value pairs, so it's convenient to use them for maps. This +// structure is similar in function to Neo .net framework's Iterator. type Iterator struct{} -// Create creates an iterator from the given items. +// Create creates an iterator from the given items (array, struct or map). A new +// iterator is set to point at element -1, so to access its first element you +// need to call Next first. This function uses `Neo.Iterator.Create` syscall. func Create(items []interface{}) Iterator { return Iterator{} } -// Key returns the iterator key. -// TODO: Better description for this. +// Key returns iterator's key at current position. It's only valid to call after +// successful Next call. This function uses `Neo.Iterator.Key` syscall. func Key(it Iterator) interface{} { return nil } -// Keys returns the iterator keys. -func Keys(it Iterator) []interface{} { - return nil +// Keys returns Enumerator ranging over keys or the given Iterator. Note that +// this Enumerator is actually directly tied to the underlying Iterator, so that +// advancing it with Next will actually advance the Iterator too. This function +// uses `Neo.Iterator.Keys` syscall. +func Keys(it Iterator) enumerator.Enumerator { + return enumerator.Enumerator{} } -// Next advances the iterator, return true if it is was successful -// and false otherwise. +// Next advances the iterator returning true if it is was successful (and you +// can use Key or Value) and false otherwise (and there are no more elements in +// this Iterator). This function uses `Neo.Iterator.Next` syscall. func Next(it Iterator) bool { return true } -// Value returns the current iterator value. +// Value returns iterator's current value. It's only valid to call after +// successful Next call. This function uses `Neo.Iterator.Value` syscall. func Value(it Iterator) interface{} { return nil } -// Values returns the iterator values. -func Values(it Iterator) []interface{} { - return nil +// Values returns Enumerator ranging over values or the given Iterator. Note that +// this Enumerator is actually directly tied to the underlying Iterator, so that +// advancing it with Next will actually advance the Iterator too. This function +// uses `Neo.Iterator.Values` syscall. +func Values(it Iterator) enumerator.Enumerator { + return enumerator.Enumerator{} } From eb82661f6b43322f3cd18ff3b7f3aa356508ee1b Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 15:38:11 +0300 Subject: [PATCH 23/35] compiler/iterator: add missing iterator.Concat function We have a syscall for it, so it should be exposed. --- pkg/compiler/syscall.go | 1 + pkg/interop/iterator/iterator.go | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 82d49a16e..2c990c87b 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -105,6 +105,7 @@ var syscalls = map[string]map[string]string{ "GetExecutingScriptHash": "System.ExecutionEngine.GetExecutingScriptHash", }, "iterator": { + "Concat": "Neo.Iterator.Concat", "Create": "Neo.Iterator.Create", "Key": "Neo.Iterator.Key", "Keys": "Neo.Iterator.Keys", diff --git a/pkg/interop/iterator/iterator.go b/pkg/interop/iterator/iterator.go index b4bd281da..5ca5947cc 100644 --- a/pkg/interop/iterator/iterator.go +++ b/pkg/interop/iterator/iterator.go @@ -18,6 +18,15 @@ func Create(items []interface{}) Iterator { return Iterator{} } +// Concat concatenates two given iterators returning one that will range on +// a first and then continue with b. Iterator positions are not reset for a +// and b, so if any of them was already advanced by Next the resulting +// Iterator will point at this new position and never go back to previous +// key-value pairs. This function uses `Neo.Iterator.Concat` syscall. +func Concat(a, b Iterator) Iterator { + return Iterator{} +} + // Key returns iterator's key at current position. It's only valid to call after // successful Next call. This function uses `Neo.Iterator.Key` syscall. func Key(it Iterator) interface{} { From dff0f724cdb28ab7bc051187e30717d3e2e23cab Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 17:11:27 +0300 Subject: [PATCH 24/35] interop/runtime: update documentation, fix Notify Notify doesn't return anything! --- pkg/interop/runtime/runtime.go | 56 +++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/pkg/interop/runtime/runtime.go b/pkg/interop/runtime/runtime.go index fd07d7bd6..9b7e837d3 100644 --- a/pkg/interop/runtime/runtime.go +++ b/pkg/interop/runtime/runtime.go @@ -1,48 +1,70 @@ +/* +Package runtime provides various service functions related to execution environment. +It has similar function to Runtime class in .net framwork for Neo. +*/ package runtime -// Package runtime provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// CheckWitness verifies if the given hash is the invoker of the contract. -func CheckWitness(hash []byte) bool { +// CheckWitness verifies if the given script hash (160-bit BE value in a 20 byte +// slice) or key (compressed serialized 33-byte form) is one of the signers of +// this invocation. It uses `Neo.Runtime.CheckWitness` syscall. +func CheckWitness(hashOrKey []byte) bool { return true } -// Log instucts the VM to log the given message. +// Log instructs VM to log the given message. It's mostly used for debugging +// purposes as these messages are not saved anywhere normally and usually are +// only visible in the VM logs. This function uses `Neo.Runtime.Log` syscall. func Log(message string) {} -// Notify an event to the VM. -func Notify(arg ...interface{}) int { - return 0 -} +// Notify sends a notification (collecting all arguments in an array) to the +// executing environment. Unlike Log it can accept any data and resulting +// notification is saved in application log. It's intended to be used as a +// part of contract's API to external systems, these events can be monitored +// from outside and act upon accordingly. This function uses +// `Neo.Runtime.Notify` syscall. +func Notify(arg ...interface{}) {} -// GetTime returns the timestamp of the most recent block. +// GetTime returns the timestamp of the most recent block. Note that when running +// script in test mode this would be the last accepted (persisted) block in the +// chain, but when running as a part of the new block the time returned is the +// time of this (currently being processed) block. This function uses +// `Neo.Runtime.GetTime` syscall. func GetTime() int { return 0 } -// GetTrigger returns the smart contract invoke trigger which can be either -// verification or application. +// GetTrigger returns the smart contract invocation trigger which can be either +// verification or application. It can be used to differentiate running contract +// as a part of verification process from running it as a regular application. +// Some interop functions (especially ones that change the state in any way) are +// not available when running with verification trigger. This function uses +// `Neo.Runtime.GetTrigger` syscall. func GetTrigger() byte { return 0x00 } -// Application returns the Application trigger type +// Application returns the Application trigger type value to compare with +// GetTrigger return value. func Application() byte { return 0x10 } -// Verification returns the Verification trigger type +// Verification returns the Verification trigger type value to compare with +// GetTrigger return value. func Verification() byte { return 0x00 } -// Serialize serializes and item into a bytearray. +// Serialize serializes any given item into a byte slice. It works for all +// regular VM types (not ones from interop package) and allows to save them in +// storage or pass into Notify and then Deserialize them on the next run or in +// the external event receiver. It uses `Neo.Runtime.Serialize` syscall. func Serialize(item interface{}) []byte { return nil } -// Deserialize an item from a bytearray. +// Deserialize unpacks previously serialized value from a byte slice, it's the +// opposite of Serialize. It uses `Neo.Runtime.Deserialize` syscall. func Deserialize(b []byte) interface{} { return nil } From 07742bdf466a833b5ac3f63e818cb3ddee758b8e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 18:06:12 +0300 Subject: [PATCH 25/35] interop/storage: update documentation --- pkg/interop/storage/storage.go | 35 +++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/pkg/interop/storage/storage.go b/pkg/interop/storage/storage.go index be0f10bc0..aade7e1e3 100644 --- a/pkg/interop/storage/storage.go +++ b/pkg/interop/storage/storage.go @@ -1,24 +1,41 @@ +/* +Package storage provides functions to access and modify contract's storage. +Neo storage's model follows simple key-value DB pattern, this storage is a part +of blockchain state, so you can use it between various invocations of the same +contract. +*/ package storage import "github.com/nspcc-dev/neo-go/pkg/interop/iterator" -// Package storage provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Context represents the storage context. +// Context represents storage context that is mandatory for Put/Get/Delete +// operations. It's an opaque type that can only be created properly by +// GetContext. It's similar to Neo .net framework's StorageContext class. type Context struct{} -// GetContext returns the storage context. +// GetContext returns current contract's (that invokes this function) storage +// context. It uses `Neo.Storage.GetContext` syscall. func GetContext() Context { return Context{} } -// Put value at given key. +// Put saves given value with given key in the storage using given Context. +// Even though it accepts interface{} for both, you can only pass simple types +// there like string, []byte, int or bool (not structures or slices of more +// complex types). To put more complex types there serialize them first using +// runtime.Serialize. This function uses `Neo.Storage.Put` syscall. func Put(ctx Context, key interface{}, value interface{}) {} -// Get value matching given key. +// Get retrieves value stored for the given key using given Context. See Put +// documentation on possible key and value types. This function uses +// `Neo.Storage.Get` syscall. func Get(ctx Context, key interface{}) interface{} { return 0 } -// Delete key value pair from storage. +// Delete removes key-value pair from storage by the given key using given +// Context. See Put documentation on possible key types. This function uses +// `Neo.Storage.Delete` syscall. func Delete(ctx Context, key interface{}) {} -// Find returns an iterator.Iterator over the keys that matched the given key. +// Find returns an iterator.Iterator over key-value pairs in the given Context +// that match the given key (contain it as a prefix). See Put documentation on +// possible key types and iterator package documentation on how to use the +// returned value. This function uses `Neo.Storage.Find` syscall. func Find(ctx Context, key interface{}) iterator.Iterator { return iterator.Iterator{} } From d0a3ce25ffbc1bf144ebdb7181c49aaa1d14ca73 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 18:13:45 +0300 Subject: [PATCH 26/35] compiler/storage: add read-only related interops --- pkg/compiler/syscall.go | 12 +++++++----- pkg/interop/storage/storage.go | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 2c990c87b..a470f34fb 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -18,11 +18,13 @@ var syscalls = map[string]map[string]string{ "Value": "Neo.Enumerator.Value", }, "storage": { - "GetContext": "Neo.Storage.GetContext", - "Put": "Neo.Storage.Put", - "Get": "Neo.Storage.Get", - "Delete": "Neo.Storage.Delete", - "Find": "Neo.Storage.Find", + "ConvertContextToReadOnly": "Neo.StorageContext.AsReadOnly", + "Delete": "Neo.Storage.Delete", + "Find": "Neo.Storage.Find", + "Get": "Neo.Storage.Get", + "GetContext": "Neo.Storage.GetContext", + "GetReadOnlyContext": "Neo.Storage.GetReadOnlyContext", + "Put": "Neo.Storage.Put", }, "runtime": { "GetTrigger": "Neo.Runtime.GetTrigger", diff --git a/pkg/interop/storage/storage.go b/pkg/interop/storage/storage.go index aade7e1e3..a047eebb1 100644 --- a/pkg/interop/storage/storage.go +++ b/pkg/interop/storage/storage.go @@ -10,13 +10,26 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/iterator" // Context represents storage context that is mandatory for Put/Get/Delete // operations. It's an opaque type that can only be created properly by -// GetContext. It's similar to Neo .net framework's StorageContext class. +// GetContext, GetReadOnlyContext or ConvertContextToReadOnly. It's similar +// to Neo .net framework's StorageContext class. type Context struct{} +// ConvertContextToReadOnly returns new context from the given one, but with +// writing capability turned off, so that you could only invoke Get and Find +// using this new Context. If Context is already read-only this function is a +// no-op. It uses `Neo.StorageContext.AsReadOnly` syscall. +func ConvertContextToReadOnly(ctx Context) Context { return Context{} } + // GetContext returns current contract's (that invokes this function) storage // context. It uses `Neo.Storage.GetContext` syscall. func GetContext() Context { return Context{} } +// GetReadOnlyContext returns current contract's (that invokes this function) +// storage context in read-only mode, you can use this context for Get and Find +// functions, but using it for Put and Delete will fail. It uses +// `Neo.Storage.GetReadOnlyContext` syscall. +func GetReadOnlyContext() Context { return Context{} } + // Put saves given value with given key in the storage using given Context. // Even though it accepts interface{} for both, you can only pass simple types // there like string, []byte, int or bool (not structures or slices of more From 514f862b81e0b30aca20050869660f469e957d95 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 18:22:07 +0300 Subject: [PATCH 27/35] compiler|transaction: fix transaction.GetScript build, add to interop There is no such syscall as Neo.Transaction.GetScript and GetScript should be available for contract's use. --- pkg/compiler/syscall.go | 2 +- pkg/interop/transaction/transaction.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index a470f34fb..2459a49e6 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -69,7 +69,7 @@ var syscalls = map[string]map[string]string{ "GetOutputs": "Neo.Transaction.GetOutputs", "GetReferences": "Neo.Transaction.GetReferences", "GetUnspentCoins": "Neo.Transaction.GetUnspentCoins", - "GetScript": "Neo.Transaction.GetScript", + "GetScript": "Neo.InvocationTransaction.GetScript", }, "asset": { "Create": "Neo.Asset.Create", diff --git a/pkg/interop/transaction/transaction.go b/pkg/interop/transaction/transaction.go index 1d863f5c3..354fcf019 100644 --- a/pkg/interop/transaction/transaction.go +++ b/pkg/interop/transaction/transaction.go @@ -48,3 +48,10 @@ func GetInputs(t Transaction) []input.Input { func GetOutputs(t Transaction) []output.Output { return []output.Output{} } + +// GetScript returns the script stored in a given Invocation transaction. +// Calling it for any other Transaction type would lead to failure. It uses +// `Neo.InvocationTransaction.GetScript` syscall. +func GetScript(t Transaction) []byte { + return nil +} From a5872743512c34bdcd311d2082363fdffb856c52 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 18:33:55 +0300 Subject: [PATCH 28/35] compiler|transaction: remove transaction.GetUnspentCoins support It's useless. Even though there is Neo.Transaction.GetUnspentCoins syscall that can be used, its return type is an interop structure that's not accepted by any other syscall, so you can't really do anything with it. And there is no such interface for the .net Framework. --- docs/runtime.md | 6 ------ pkg/compiler/syscall.go | 15 +++++++-------- pkg/interop/transaction/transaction.go | 6 ------ 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/docs/runtime.md b/docs/runtime.md index e98b3af20..7f047db2f 100644 --- a/docs/runtime.md +++ b/docs/runtime.md @@ -504,12 +504,6 @@ GetReferences(t Transacfion) interface{} ``` Returns the references of the given transaction. -#### GetUnspentCoins -``` -GetUnspentCoins(t Transacfion) interface{} -``` -Returns the unspent coins of the given transaction. - #### GetOutputs ``` GetOutputs(t Transacfion) []output.Output diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 2459a49e6..bb6429f10 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -62,14 +62,13 @@ var syscalls = map[string]map[string]string{ "GetTransaction": "Neo.Block.GetTransaction", }, "transaction": { - "GetHash": "Neo.Transaction.GetHash", - "GetType": "Neo.Transaction.GetType", - "GetAttributes": "Neo.Transaction.GetAttributes", - "GetInputs": "Neo.Transaction.GetInputs", - "GetOutputs": "Neo.Transaction.GetOutputs", - "GetReferences": "Neo.Transaction.GetReferences", - "GetUnspentCoins": "Neo.Transaction.GetUnspentCoins", - "GetScript": "Neo.InvocationTransaction.GetScript", + "GetAttributes": "Neo.Transaction.GetAttributes", + "GetHash": "Neo.Transaction.GetHash", + "GetInputs": "Neo.Transaction.GetInputs", + "GetOutputs": "Neo.Transaction.GetOutputs", + "GetReferences": "Neo.Transaction.GetReferences", + "GetScript": "Neo.InvocationTransaction.GetScript", + "GetType": "Neo.Transaction.GetType", }, "asset": { "Create": "Neo.Asset.Create", diff --git a/pkg/interop/transaction/transaction.go b/pkg/interop/transaction/transaction.go index 354fcf019..b412fe92f 100644 --- a/pkg/interop/transaction/transaction.go +++ b/pkg/interop/transaction/transaction.go @@ -33,12 +33,6 @@ func GetReferences(t Transaction) []interface{} { return []interface{}{} } -// GetUnspentCoins returns the unspent coins for the given transaction. -// FIXME: What is the correct return type for this? -func GetUnspentCoins(t Transaction) interface{} { - return 0 -} - // GetInputs returns the inputs of the given transaction. func GetInputs(t Transaction) []input.Input { return []input.Input{} From ec2bf7d52e9dc7d76fa213d291f5371a4c032fad Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 18:52:30 +0300 Subject: [PATCH 29/35] interop/transaction: update documentation --- pkg/interop/transaction/transaction.go | 45 +++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/pkg/interop/transaction/transaction.go b/pkg/interop/transaction/transaction.go index b412fe92f..0969da5b7 100644 --- a/pkg/interop/transaction/transaction.go +++ b/pkg/interop/transaction/transaction.go @@ -1,3 +1,6 @@ +/* +Package transaction provides functions to work with transactions. +*/ package transaction import ( @@ -6,39 +9,59 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/output" ) -// Package transaction provides function signatures that can be used inside -// smart contracts that are written in the neo-go framework. - -// Transaction stubs a NEO transaction type. +// 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 of the given transaction. +// 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 } -// GetType returns the type of the given transaction. +// GetType returns the type of the given transaction. Possible values: +// MinerTransaction = 0x00 +// IssueTransaction = 0x01 +// ClaimTransaction = 0x02 +// EnrollmentTransaction = 0x20 +// RegisterTransaction = 0x40 +// ContractTransaction = 0x80 +// StateType = 0x90 +// AgencyTransaction = 0xb0 +// PublishTransaction = 0xd0 +// InvocationTransaction = 0xd1 +// It uses `Neo.Transaction.GetType` syscall. func GetType(t Transaction) byte { return 0x00 } -// GetAttributes returns a slice of attributes for the given transaction. +// 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{} } -// GetReferences returns a slice of references for the given transaction. -// FIXME: What is the correct return type for this? +// GetReferences returns a slice of references for a given Transaction. Elements +// of this slice can be casted to any of input.Input or output.Output, depending +// on which information you're interested in (as reference technically contains +// both input and corresponding output), refer to input and output package on +// how to use them. This function uses `Neo.Transaction.GetReferences` syscall. func GetReferences(t Transaction) []interface{} { return []interface{}{} } -// GetInputs returns the inputs of the given transaction. +// GetInputs returns a slice of inputs of a given Transaction. Refer to input +// package on how to use them. This function uses `Neo.Transaction.GetInputs` +// syscall. func GetInputs(t Transaction) []input.Input { return []input.Input{} } -// GetOutputs returns the outputs of the given transaction. +// GetOutputs returns a slice of outputs of a given Transaction. Refer to output +// package on how to use them. This function uses `Neo.Transaction.GetOutputs` +// syscall. func GetOutputs(t Transaction) []output.Output { return []output.Output{} } From 085d50b4303c7cc4d51845a1815a7dfdca43bcf6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 18:57:22 +0300 Subject: [PATCH 30/35] interop/util: extend documentation --- pkg/interop/util/util.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/interop/util/util.go b/pkg/interop/util/util.go index 1f05d4941..1aba144cb 100644 --- a/pkg/interop/util/util.go +++ b/pkg/interop/util/util.go @@ -1,12 +1,19 @@ +/* +Package util contains some special useful functions that are provided by compiler and VM. +*/ package util -// FromAddress is an utility function that converts an NEO address to its hash. +// FromAddress is an utility function that converts a Neo address to its hash +// (160 bit BE value in a 20 byte slice). It can only be used for strings known +// at compilation time, because the convertion is actually being done by the +// compiler. func FromAddress(address string) []byte { return nil } -// Equals compares a with b and will return true whether a and b -// are equal. +// Equals compares a with b and will return true when a and b are equal. It's +// implemented as an EQUAL VM opcode, so the rules of comparison are those +// of EQUAL. func Equals(a, b interface{}) bool { return false } From 8c19b8f2a19e3e7005347c3c51642663274e7744 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 19:04:20 +0300 Subject: [PATCH 31/35] compiler/interop: add support for working with witnesses --- pkg/compiler/syscall.go | 4 ++++ pkg/interop/transaction/transaction.go | 8 ++++++++ pkg/interop/witness/witness.go | 14 ++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 pkg/interop/witness/witness.go diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index bb6429f10..26307270d 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -69,6 +69,7 @@ var syscalls = map[string]map[string]string{ "GetReferences": "Neo.Transaction.GetReferences", "GetScript": "Neo.InvocationTransaction.GetScript", "GetType": "Neo.Transaction.GetType", + "GetWitnesses": "Neo.Transaction.GetWitnesses", }, "asset": { "Create": "Neo.Asset.Create", @@ -114,4 +115,7 @@ var syscalls = map[string]map[string]string{ "Value": "Neo.Iterator.Value", "Values": "Neo.Iterator.Values", }, + "witness": { + "GetVerificationScript": "Neo.Witness.GetVerificationScript", + }, } diff --git a/pkg/interop/transaction/transaction.go b/pkg/interop/transaction/transaction.go index 0969da5b7..00d06c25e 100644 --- a/pkg/interop/transaction/transaction.go +++ b/pkg/interop/transaction/transaction.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/attribute" "github.com/nspcc-dev/neo-go/pkg/interop/input" "github.com/nspcc-dev/neo-go/pkg/interop/output" + "github.com/nspcc-dev/neo-go/pkg/interop/witness" ) // Transaction represents a NEO transaction, it's an opaque data structure @@ -72,3 +73,10 @@ func GetOutputs(t Transaction) []output.Output { func GetScript(t Transaction) []byte { return nil } + +// 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{} +} diff --git a/pkg/interop/witness/witness.go b/pkg/interop/witness/witness.go new file mode 100644 index 000000000..5bf742f83 --- /dev/null +++ b/pkg/interop/witness/witness.go @@ -0,0 +1,14 @@ +/* +Package witness provides functions dealing with transaction's witnesses. +*/ +package witness + +// Witness is an opaque data structure that can only be created by +// transaction.GetWitnesses and representing transaction's witness. +type Witness struct{} + +// GetVerificationScript returns verification script stored in the given +// witness. It uses `Neo.Witness.GetVerificationScript` syscall. +func GetVerificationScript(w Witness) []byte { + return nil +} From 2c921f52778fda739e526e5fa7b19bd2918a42cd Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 21:59:26 +0300 Subject: [PATCH 32/35] docs: drop runtime.md We have now way better godoc for interop functions, so this document makes little sense and it's not referenced anywhere, so it's safe to drop it. --- docs/runtime.md | 517 ------------------------------------------------ 1 file changed, 517 deletions(-) delete mode 100644 docs/runtime.md diff --git a/docs/runtime.md b/docs/runtime.md deleted file mode 100644 index 7f047db2f..000000000 --- a/docs/runtime.md +++ /dev/null @@ -1,517 +0,0 @@ -# Runtime -A brief overview of NEO smart contract API's that can be used in the neo-go framework. - -# Overview -1. [Account]() -2. [Asset]() -3. [Attribute]() -4. [Block]() -5. [Blockchain]() -6. [Contract]() -7. [Crypto]() -8. [Engine]() -9. [Enumerator]() -10. [Iterator]() -11. [Header]() -12. [Input]() -13. [Output]() -14. [Runtime]() -15. [Storage]() -16. [Transaction]() -17. [Util]() - -## Account -#### GetScriptHash -``` -GetScriptHash(a Account) []byte -``` -Returns the script hash of the given account. - -#### GetVotes -``` -GetVotes(a Account) [][]byte -``` -Returns the the votes (a slice of public keys) of the given account. - -#### GetBalance -``` -GetBalance(a Account, assetID []byte) int -``` -Returns the balance of the given asset id for the given account. - -## Asset -#### GetAssetID -``` -GetAssetID(a Asset) []byte -``` -Returns the id of the given asset. - -#### GetAmount -``` -GetAmount(a Asset) int -``` -Returns the amount of the given asset id. - -#### GetAvailable -``` -GetAvailable(a Asset) int -``` -Returns the available amount of the given asset. - -#### GetPrecision -``` -GetPrecision(a Asset) byte -``` -Returns the precision of the given Asset. - -#### GetOwner -``` -GetOwner(a Asset) []byte -``` -Returns the owner of the given asset. - -#### GetAdmin -``` -GetAdmin(a Asset) []byte -``` -Returns the admin of the given asset. - -#### GetIssuer -``` -GetIssuer(a Asset) []byte -``` -Returns the issuer of the given asset. - -#### Create -``` -Create(type byte, name string, amount int, precision byte, owner, admin, issuer []byte) -``` -Creates a new asset on the blockchain. - -#### Renew -``` -Renew(asset Asset, years int) -``` -Renews the given asset as long as the given years. - -## Attribute -#### GetUsage -``` -GetUsage(attr Attribute) []byte -``` -Returns the usage of the given attribute. - -#### GetData -``` -GetData(attr Attribute) []byte -``` -Returns the data of the given attribute. - -## Block -#### GetTransactionCount -``` -GetTransactionCount(b Block) int -``` -Returns the number of transactions that are recorded in the given block. - -#### GetTransactions -``` -GetTransactions(b Block) []transaction.Transaction -``` -Returns a slice of the transactions that are recorded in the given block. - -#### GetTransaction -``` -GetTransaction(b Block, hash []byte) transaction.Transaction -``` -Returns the transaction by the given hash that is recorded in the given block. - -## Blockchain -#### GetHeight -``` -GetHeight() int -``` -Returns the current height of the blockchain. - -#### GetHeader -``` -GetHeader(heightOrHash []interface{}) header.Header -``` -Return the header by the given hash or index. - -#### GetBlock -``` -GetBlock(heightOrHash interface{}) block.Block -``` -Returns the block by the given hash or index. - -#### GetTransaction -``` -GetTransaction(hash []byte) transaction.Transaction -``` -Returns a transaction by the given hash. - -#### GetContract -``` -GetContract(scriptHash []byte) contract.Contract -``` -Returns the contract found by the given script hash. - -#### GetAccount -``` -GetAccount(scriptHash []byte) account.Account -``` -Returns the account found by the given script hash. - -#### GetValiditors -``` -GetValidators() [][]byte -``` -Returns a list of validators public keys. - -#### GetAsset -``` -GetAsset(assetID []byte) asset.Asset -``` -Returns the asset found by the given asset id. - -## Contract -#### GetScript -``` -GetScript(c Contract) []byte -``` -Return the script of the given contract. - -#### IsPayable -``` -IsPayable(c Contract) bool -``` -Returns whether the given contract is payable. - -#### GetStorageContext -``` -GetStorageContext(c Contract) -``` -Returns the storage context of the given contract. - -#### Create -``` -Create( - script []byte, - params []interface{}, - returnType byte, - properties interface{}, - name, - version, - author, - email, - description string) -``` -Creates a new contract on the blockchain. - -#### Migrate -``` -Migrate( - script []byte, - params []interface{}, - returnType byte, - properties interface{}, - name, - version, - author, - email, - description string) -``` -Migrates a contract on the blockchain. - -#### Destroy -``` -Destroy(c Contract) -``` -Deletes the given contract from the blockchain. - -## Crypto -#### SHA1 -``` -SHA1(data []byte) []byte -``` -Computes the sha1 hash of the given bytes - -#### SHA256 -``` -SHA256(data []byte) []byte -``` -Computes the sha256 hash of the given bytes - -#### Hash256 -``` -Hash256(data []byte) []byte -``` -Computes the sha256^2 of the given data. - -#### Hash160 -``` -Hash160(data []byte) []byte) []byte -``` -Computes the ripemd160 over the sha256 hash of the given data. - -## Engine -#### GetScriptContainer -``` -GetScriptContainer() transaction.Transaction -``` -Returns the transaction that is in the context of the VM execution. - -#### GetExecutingScriptHash -``` -GetExecutingScriptHash() []byte -``` -Returns the script hash of the contract that is currently being executed. - -#### GetCallingScriptHash -``` -GetCallingScriptHash() []byte -``` -Returns the script hash of the contract that has started the execution of the current script. - -#### GetEntryScriptHash -``` -GetEntryScriptHash() []byte -``` -Returns the script hash of the contract that started the execution from the start. - -## Enumerator -#### Create -``` -Create(items []inteface{}) Enumerator -``` -Create a enumerator from the given items. - -#### Next -``` -Next(e Enumerator) interface{} -``` -Returns the next item from the given enumerator. - -#### Value -``` -Value(e Enumerator) interface{} -``` -Returns the enumerator value. - -## Iterator -#### Create -``` -Create(items []inteface{}) Iterator -``` -Creates an iterator from the given items. - -#### Key -``` -Key(it Iterator) interface{} -``` -Return the key from the given iterator. - -#### Keys -``` -Keys(it Iterator) []interface{} -``` -Returns the iterator's keys - -#### Values -``` -Values(it Iterator) []interface{} -``` -Returns the iterator's values - -## Header -#### GetIndex -``` -GetIndex(h Header) int -``` -Returns the height of the given header. - -#### GetHash -``` -GetHash(h Header) []byte -``` -Returns the hash of the given header. - -#### GetPrevHash -``` -GetPrevhash(h Header) []byte -``` -Returns the previous hash of the given header. - -#### GetTimestamp -``` -GetTimestamp(h Header) int -``` -Returns the timestamp of the given header. - -#### GetVersion -``` -GetVersion(h Header) int -``` -Returns the version of the given header. - -#### GetMerkleroot -``` -GetMerkleRoot(h Header) []byte -``` -Returns the merkle root of the given header. - -#### GetConsensusData -``` -GetConsensusData(h Header) int -``` -Returns the consensus data of the given header. - -#### GetNextConsensus -``` -GetNextConsensus(h Header) []byte -``` -Returns the next consensus of the given header. - -## Input -#### GetHash -``` -GetHash(in Input) []byte -``` -Returns the hash field of the given input. - -#### GetIndex -``` -GetIndex(in Input) int -``` -Returns the index field of the given input. - -## Output -#### GetAssetID -``` -GetAssetId(out Output) []byte -``` -Returns the asset id field of the given output. - -#### GetValue -``` -GetValue(out Output) int -``` -Returns the value field of the given output. - -#### GetScriptHash -``` -GetScriptHash(out Output) []byte -``` -Returns the script hash field of the given output. - -## Runtime -#### CheckWitness -``` -CheckWitness(hash []byte) bool -``` -Verifies if the given hash is the hash of the contract owner. - -#### Log -``` -Log(message string) -``` -Logs the given message. - -#### Notify -``` -Notify(args ...interface{}) int -``` -Notify any number of arguments to the VM. - -#### GetTime -``` -GetTime() int -``` -Returns the current time based on the highest block in the chain. - -#### GetTrigger -``` -GetTrigger() byte -``` -Returns the trigger type of the execution. - -#### Serialize -``` -Serialize(item interface{}) []byte -``` -Serialize the given stack item to a slice of bytes. - -#### Deserialize -``` -Deserialize(data []byte) interface{} -``` -Deserializes the given data to a stack item. - -## Storage -#### GetContext -``` -GetContext() Context -``` -Returns the current storage context. - -#### Put -``` -Put(ctx Context, key, value []interface{}) -``` -Stores the given value at the given key. - -#### Get -``` -Get(ctx Context, key interface{}) interface{} -``` -Returns the value found at the given key. - -#### Delete -``` -Delete(ctx Context, key interface{}) -``` -Delete's the given key from storage. - -#### Find -``` -Find(ctx Context, key interface{}) iterator.Iterator -``` -Find returns an iterator key-values that match the given key. - -## Transaction -#### GetHash -``` -GetHash(t Transacfion) []byte -``` -Returns the hash for the given transaction. - -#### GetType -``` -GetType(t Transacfion) byte -``` -Returns the type of the given transaction. - -#### GetAttributes -``` -GetAttributes(t Transacfion) []attribute.Attribute -``` -Returns the attributes of the given transaction. - -#### GetReferences -``` -GetReferences(t Transacfion) interface{} -``` -Returns the references of the given transaction. - -#### GetOutputs -``` -GetOutputs(t Transacfion) []output.Output -``` -Returns the outputs of the given transaction - -#### GetInputs -``` -GetInputs(t Transacfion) []input.Input -``` -Returns the inputs of the given transaction From 0079dfb695b1b158e6aaf286412fd5db86a7f98d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 18 May 2020 22:37:03 +0300 Subject: [PATCH 33/35] interop: add some top-level doc.go --- pkg/interop/doc.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 pkg/interop/doc.go diff --git a/pkg/interop/doc.go b/pkg/interop/doc.go new file mode 100644 index 000000000..56ddc3081 --- /dev/null +++ b/pkg/interop/doc.go @@ -0,0 +1,14 @@ +/* +Package interop contains smart contract API functions. +Its subpackages can be imported into smart contracts written in Go to provide +various functionality. Upon compilation, functions from these packages will +be substituted with appropriate NeoVM system calls implemented by Neo. Usually +these system calls have additional price in NeoVM, so they're explicitly written +in the documentation of respective functions. + +Note that unless written otherwise structures defined in this packages can't be +correctly created by new() or composite literals, they should be received from +some interop functions (and then used as parameters for some other interop +functions). +*/ +package interop From 94d6b5466f40e3a14195d39d056d78de9b1551d2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 19 May 2020 00:17:05 +0300 Subject: [PATCH 34/35] docs: update compiler.md, bring it up to date And add one more reference to it into the main README. --- README.md | 4 +- docs/compiler.md | 173 +++++++++++++++++++++++++++-------------------- 2 files changed, 104 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index aa1aa2629..29e761596 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,9 @@ mode, after it ends the node can be started normally. Please refer to [neo-go smart contract development workshop](https://github.com/nspcc-dev/neo-go-sc-wrkshp) that shows some simple contracts that can be compiled/deployed/run using neo-go compiler, SDK -and private network. +and private network. For details on how Go code is translated to Neo VM +bytecode and what you can and can not do in smart contract please refer to the +[compiler documentation](docs/compiler.md). # Developer notes Nodes have such features as [Prometheus](https://prometheus.io/docs/guides/go-application) and diff --git a/docs/compiler.md b/docs/compiler.md index a7e59652c..593e5cfa2 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -2,66 +2,39 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machine can understand. -## Currently supported +## Language compatibility -### Go internals -- type checking -- multiple assignments -- global variables -- types int, string, byte and booleans -- struct types + method receives -- functions -- composite literals `[]int, []string, []byte` -- basic if statements -- binary expressions -- return statements -- for loops -- imports +The compiler is mostly compatible with regular Go language specification, but +there are some important deviations that you need to be aware of that make it +a dialect of Go rather than a complete port of the language: + * `make()` ane `new()` are not supported, most of the time you can substitute + them with composite literals + * there is no real distinction between different integer types, all of them + work as big.Int in Go with a limit of 256 bit in width, so you can use + `int` for just about anything. This is the way integers work in Neo VM and + adding proper Go types emulation is considered to be too costly. + * goroutines, channels and garbage collection are not supported and will + never be because emulating that aspects of Go runtime on top of Neo VM is + close to impossible + * even though `panic()` is supported, `recover()` is not, `panic` shuts the + VM down + * lambdas are not supported + * global variables can't be changed in functions (#638) + * it's not possible to rename imported interop packages, they won't work this + way (#397, #913) + * nested selectors are not yet supported (#957) + * using value variable in range-based loops is not yet supported (#958) -### Go builtins -- len -- append - -### VM API (interop layer) +## VM API (interop layer) Compiler translates interop function calls into NEO VM syscalls or (for custom -functions) into NEO VM instructions. [Refer to GoDoc](https://godoc.org/github.com/nspcc-dev/neo-go/pkg/interop) for full API documentation. - -#### Standard NEO Smart Contract API -- account -- asset -- attribute -- block -- blockchain -- contract -- engine -- header -- input -- iterator -- output -- runtime -- storage -- transaction - -#### Custom VM utility helper functions -- crypto: - - `SHA1` - - `SHA256` - - `Hash256` - - `Hash160` -- enumerator -- util: - - `Equals` (to emit `EQUALS` opcode, not needed usually) - - `FromAddress(address string) []byte` - -## Not supported -Due to the limitations of the NEO virtual machine, features listed below will not be supported. -- channels -- goroutines -- returning multiple values from functions +functions) into NEO VM instructions. [Refer to +pkg.go.dev](https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/interop) +for full API documentation. In general it provides the same level of +functionality as Neo .net Framework library. ## Quick start -### Compile a smart contract +### Compiling ``` ./bin/neo-go contract compile -i mycontract.go @@ -73,7 +46,7 @@ By default the filename will be the name of your .go file with the .avm extensio ./bin/neo-go contract compile -i mycontract.go --out /Users/foo/bar/contract.avm ``` -### Debugging your smart contract +### Debugging You can dump the opcodes generated by the compiler with the following command: ``` @@ -112,33 +85,89 @@ INDEX OPCODE DESC 25 0x66 RET ``` -### Test invoke a compiled contract -You can simulate a test invocation of your compiled contract by the VM, to know the total gas cost for example, with the following command: +#### Neo Smart Contract Debugger support + +It's possible to debug contracts written in Go using standard [Neo Smart +Contract Debugger](https://github.com/neo-project/neo-debugger/) which is a +part of [Neo Blockchain +Toolkit](https://github.com/neo-project/neo-blockchain-toolkit/). To do that +you need to generate debug information using `--debug` option, like this: ``` -./bin/neo-go contract testinvoke -i mycompiledcontract.avm +$ ./bin/neo-go contract compile -i contract.go -o contract.avm --debug contract.debug.json ``` -Will output something like: -``` -{ - "state": "HALT, BREAK", - "gas_consumed": "0.006", - "Stack": [ - { - "type": "Integer", - "value": "9" - } - ] -} +This file can then be used by debugger and set up to work just like for any +other supported language. + +### Deploying + +Deploying a contract to blockchain with neo-go requires a configuration file +with contract's metadata in YAML format, like the following: ``` +project: + author: Jack Smith + email: jack@example.com + version: 1.0 + name: 'Smart contract' + description: 'Even smarter than Jack himself' + hasstorage: true + hasdynamicinvocation: false + ispayable: false + returntype: ByteArray + parameters: ['String', 'Array'] +``` -At the moment this is implemented via RPC call to the remote server. +It's passed to the `deploy` command via `-c` option: + +``` +$ ./bin/neo-go contract deploy -i contract.avm -c contract.yml -e http://localhost:20331 -w wallet.json -g 0.001 +``` + +Deployment works via an RPC server, an address of which is passed via `-e` +option and should be signed using a wallet from `-w` option. More details can +be found in `deploy` command help. + +#### Neo Express support + +It's possible to deploy contracts written in Go using [Neo +Express](https://github.com/neo-project/neo-express) which is a part of [Neo +Blockchain +Toolkit](https://github.com/neo-project/neo-blockchain-toolkit/). To do that +you need to generate a different metadata file using YAML written for +deployment with neo-go. It's done in the same step with compilation via +`--config` input parameter and `--abi` output parameter, combined with debug +support the command line will look like this: + +``` +$ ./bin/neo-go contract compile -i contract.go --config contract.yml -o contract.avm --debug contract.debug.json --abi contract.abi.json +``` + +This file can then be used by toolkit to deploy contract the same way +contracts in other languagues are deployed. + + +### Invoking +You can import your contract into the standalone VM and run it there (see [VM +documentation](vm.md) for more info), but that only works for simple contracts +that don't use blockchain a lot. For more real contracts you need to deploy +them first and then do test invocations and regular invocations with `contract +testinvokefunction` and `contract invokefunction` commands (or their variants, +see `contract` command help for more details. They all work via RPC, so it's a +mandatory parameter. + +Example call (contract `f84d6a337fbc3d3a201d41da99e86b479e7a2554` with method +`balanceOf` and method's parameter `AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y` using +given RPC server and wallet and paying 0.00001 GAS for this transaction): + +``` +$ ./bin/neo-go contract invokefunction -e http://localhost:20331 -w my_wallet.json -g 0.00001 f84d6a337fbc3d3a201d41da99e86b479e7a2554 balanceOf AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y +``` ## Smart contract examples -Some examples are provided in the [examples directory](examples). +Some examples are provided in the [examples directory](../examples). ### Check if the invoker of the contract is the owning address From ddbc9057c8be2dfb359d2f8c7635652c1e74df03 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 19 May 2020 01:01:35 +0300 Subject: [PATCH 35/35] docs: update RPC document, add notifications spec --- docs/notifications.md | 339 ++++++++++++++++++++++++++++++++++++++++++ docs/rpc.md | 35 ++++- 2 files changed, 366 insertions(+), 8 deletions(-) create mode 100644 docs/notifications.md diff --git a/docs/notifications.md b/docs/notifications.md new file mode 100644 index 000000000..0561a3282 --- /dev/null +++ b/docs/notifications.md @@ -0,0 +1,339 @@ +# Notification subsystem + +Original motivation, requirements and general solution strategy are described +in the issue #895. + +This extension allows a websocket client to subscribe to various events and +receive them as JSON-RPC notifications from the server. + +## Events +Currently supported events: + * new block added + Contents: block. + Filters: none. + * new transaction in the block + Contents: transaction. + Filters: type. + * notification generated during execution + Contents: container hash, contract script hash, stack item. + Filters: contract script hash. + * transaction executed + Contents: application execution result. + Filters: VM state. + +## Ordering and persistence guarantees + * new block is only announced after its processing is complete and the chain + is updated to the new height + * no disk-level persistence guarantees are given + * new in-block transaction is announced after block processing, but before + announcing the block itself + * transaction notifications are only announced for successful transactions + * all announcements are being done in the same order they happen on the chain + At first transaction execution is announced, then followed by notifications + generated during this execution, then followed by transaction announcement. + Transaction announcements are ordered the same way they're in the block. + * unsubscription may not cancel pending, but not yet sent events + +## Subscription management + +Errors are not described down below, but they can be returned as standard +JSON-RPC errors (most often caused by invalid parameters). + +### `subscribe` method + +Parameters: event stream name, stream-specific filter rules hash (can be +omitted if empty). + +Recognized stream names: + * `block_added` + No filter parameters defined. + * `transaction_added` + Filter: `type` as a string containing standard transaction types + (MinerTransaction, InvocationTransaction, etc) + * `notification_from_execution` + Filter: `contract` field containing string with hex-encoded Uint160 (LE + representation). + * `transaction_executed` + Filter: `state` field containing `HALT` or `FAULT` string for successful + and failed executions respectively. + +Response: returns subscription ID (string) as a result. This ID can be used to +cancel this subscription and has no meaning other than that. + +Example request (subscribe to notifications from contract +0x6293a440ed80a427038e175a507d3def1e04fb67 generated when executing +transactions): + +``` +{ + "jsonrpc": "2.0", + "method": "subscribe", + "params": ["notification_from_execution", {"contract": "6293a440ed80a427038e175a507d3def1e04fb67"}], + "id": 1 +} + +``` + +Example response: + +``` +{ + "jsonrpc": "2.0", + "id": 1, + "result": "55aaff00" +} +``` + +### `unsubscribe` method + +Parameters: subscription ID as a string. + +Response: boolean true. + +Example request (unsubscribe from "55aaff00"): + +``` +{ + "jsonrpc": "2.0", + "method": "unsubscribe", + "params": ["55aaff00"], + "id": 1 +} +``` + +Example response: + +``` +{ + "jsonrpc": "2.0", + "id": 1, + "result": true +} +``` + +## Events + +Events are sent as JSON-RPC notifications from the server with `method` field +being used for notification names. Notification names are identical to stream +names described for `subscribe` method with one important addition for +`event_missed` which can be sent for any subscription to signify that some +events were not delivered (usually when client isn't able to keep up with +event flow). + +Verbose responses for various structures like blocks and transactions are used +to simplify working with notifications on client side. Returned structures +mostly follow the one used by standard Neo RPC calls, but may have some minor +differences. + +### `block_added` notification +As a first parameter (`params` section) contains block converted to JSON +structure which is similar to verbose `getblock` response but with the +following differences: + * it doesn't have `size` field (you can calculate it client-side) + * it doesn't have `nextblockhash` field (it's supposed to be the latest one + anyway) + * it doesn't have `confirmations` field (see previous) + * transactions contained don't have `net_fee` and `sys_fee` fields + +No other parameters are sent. + +Example: +``` +{ + "jsonrpc" : "2.0", + "method" : "block_added", + "params" : [ + { + "previousblockhash" : "0x33f3e0e24542b2ec3b6420e6881c31f6460a39a4e733d88f7557cbcc3b5ed560", + "nextconsensus" : "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", + "index" : 205, + "nonce" : "0000000000000457", + "version" : 0, + "tx" : [ + { + "version" : 0, + "attributes" : [], + "txid" : "0xf9adfde059810f37b3d0686d67f6b29034e0c669537df7e59b40c14a0508b9ed", + "size" : 10, + "vin" : [], + "type" : "MinerTransaction", + "scripts" : [], + "vout" : [] + }, + { + "version" : 1, + "txid" : "0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7", + "attributes" : [], + "size" : 60, + "script" : "097465737476616c756507746573746b657952c103507574676f20ccfbd5f01d5b9633387428b8bab95a9e78c2", + "vin" : [], + "scripts" : [], + "type" : "InvocationTransaction", + "vout" : [] + } + ], + "time" : 1586154525, + "hash" : "0x48fba8aebf88278818a3dc0caecb230873d1d4ce1ea8bf473634317f94a609e5", + "script" : { + "invocation" : "4047a444a51218ac856f1cbc629f251c7c88187910534d6ba87847c86a9a73ed4951d203fd0a87f3e65657a7259269473896841f65c0a0c8efc79d270d917f4ff640435ee2f073c94a02f0276dfe4465037475e44e1c34c0decb87ec9c2f43edf688059fc4366a41c673d72ba772b4782c39e79f01cb981247353216d52d2df1651140527eb0dfd80a800fdd7ac8fbe68fc9366db2d71655d8ba235525a97a69a7181b1e069b82091be711c25e504a17c3c55eee6e76e6af13cb488fbe35d5c5d025c34041f39a02ebe9bb08be0e4aaa890f447dc9453209bbfb4705d8f2d869c2b55ee2d41dbec2ee476a059d77fb7c26400284328d05aece5f3168b48f1db1c6f7be0b", + "verification" : "532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae" + }, + "merkleroot" : "0x9d922c5cfd4c8cd1da7a6b2265061998dc438bd0dea7145192e2858155e6c57a" + } + ] +} +``` + +### `transaction_added` notification + +In the first parameter (`params` section) contains transaction converted to +JSON which is similar to verbose `getrawtransaction` response, but with the +following differences: + * `net_fee` and `sys_fee` fields are always missing + * block's metadata is missing (`blockhash`, `confirmations`, `blocktime`) + +No other parameters are sent. + +Example: +``` +{ + "params" : [ + { + "vin" : [], + "scripts" : [], + "attributes" : [], + "txid" : "0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7", + "size" : 60, + "vout" : [], + "type" : "InvocationTransaction", + "version" : 1, + "script" : "097465737476616c756507746573746b657952c103507574676f20ccfbd5f01d5b9633387428b8bab95a9e78c2" + } + ], + "method" : "transaction_added", + "jsonrpc" : "2.0" +} +``` + +### `notification_from_execution` notification + +Contains three parameters: container hash (hex-encoded LE Uint256 in a +string), contract script hash (hex-encoded LE Uint160 in a string) and stack +item (encoded the same way as `state` field contents for notifications from +`getapplicationlog` response). + +Example: + +``` +{ + "method" : "notification_from_execution", + "jsonrpc" : "2.0", + "params" : [ + { + "state" : { + "value" : [ + { + "value" : "636f6e74726163742063616c6c", + "type" : "ByteArray" + }, + { + "value" : "507574", + "type" : "ByteArray" + }, + { + "value" : [ + { + "type" : "ByteArray", + "value" : "746573746b6579" + }, + { + "value" : "7465737476616c7565", + "type" : "ByteArray" + } + ], + "type" : "Array" + } + ], + "type" : "Array" + }, + "contract" : "0xc2789e5ab9bab828743833965b1df0d5fbcc206f" + } + ] +} +``` + +### `transaction_executed` notification + +Contains the same result as from `getapplicationlog` method in the first +parameter and no other parameters. One difference from `getapplicationlog` is +that it always contains zero in the `contract` field. + +Example: +``` +{ + "method" : "transaction_executed", + "params" : [ + { + "executions" : [ + { + "vmstate" : "HALT", + "contract" : "0x0000000000000000000000000000000000000000", + "notifications" : [ + { + "state" : { + "type" : "Array", + "value" : [ + { + "type" : "ByteArray", + "value" : "636f6e74726163742063616c6c" + }, + { + "type" : "ByteArray", + "value" : "507574" + }, + { + "value" : [ + { + "value" : "746573746b6579", + "type" : "ByteArray" + }, + { + "type" : "ByteArray", + "value" : "7465737476616c7565" + } + ], + "type" : "Array" + } + ] + }, + "contract" : "0xc2789e5ab9bab828743833965b1df0d5fbcc206f" + } + ], + "gas_consumed" : "1.048", + "stack" : [ + { + "type" : "Integer", + "value" : "1" + } + ], + "trigger" : "Application" + } + ], + "txid" : "0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7" + } + ], + "jsonrpc" : "2.0" +} +``` + +### `event_missed` notification + +Never has any parameters. Example: + +``` +{ + "jsonrpc": "2.0", + "method": "event_missed", + "params": [] +} +``` diff --git a/docs/rpc.md b/docs/rpc.md index 9cae46a16..ba025c07d 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -65,6 +65,18 @@ which would yield the response: | `submitblock` | | `validateaddress` | +#### Implementation notices + +##### `invokefunction` and `invoke` + +neo-go's implementation of `invokefunction` and `invoke` does not return `tx` +field in the answer because that requires signing the transaction with some +key in the server which doesn't fit the model of our node-client interactions. +Lacking this signature the transaction is almost useless, so there is no point +in returning it. + +Both methods also don't currently support arrays in function parameters. + ### Unsupported methods Methods listed down below are not going to be supported for various reasons @@ -86,17 +98,24 @@ and we're not accepting issues related to them. | `sendmany` | Not applicable to neo-go, see `claimgas` comment | | `sendtoaddress` | Not applicable to neo-go, see `claimgas` comment | -#### Implementation notices +### Extensions -##### `invokefunction` and `invoke` +Some additional extensions are implemented as a part of this RPC server. -neo-go's implementation of `invokefunction` and `invoke` does not return `tx` -field in the answer because that requires signing the transaction with some -key in the server which doesn't fit the model of our node-client interactions. -Lacking this signature the transaction is almost useless, so there is no point -in returning it. +#### Websocket server -Both methods also don't currently support arrays in function parameters. +This server accepts websocket connections on `ws://$BASE_URL/ws` address. You +can use it to perform regular RPC calls over websockets (it's supposed to be a +little faster than going regular HTTP route) and you can also use it for +additional functionality provided only via websockets (like notifications). + +#### Notification subsystem + +Notification subsystem consists of two additional RPC methods (`subscribe` and +`unsubscribe` working only over websocket connection) that allow to subscribe +to various blockchain events (with simple event filtering) and receive them on +the client as JSON-RPC notifications. More details on that are written in the +[notifications specification](notifications.md). ## Reference