NEO-Python

概述

neo-python 目前功能

  • 此项目的目标是成为原始 C# NEO 项目 的全功能移植
  • 运行基于 Python 的 P2P 节点
  • 提供可交互 CLI 用于配置节点和检测区块链
  • 编译、测试、部署以及运行以 python 编写的智能合约或任意 .avm 格式的合约
  • 符合 NEP2NEP5 标准的钱包功能
  • RPC 客户端
  • RPC 服务器
  • 通知服务器 (用于查看 NEP5 通证的转账)
  • Runtime.LogRuntime.Notify 事件监控

即将支持功能

  • 共识节点
  • 完整的智能合约调试和检查

入门指南

请按照 安装 章节的说明进行开始操作。

此项目的主要功能包含在 cli 应用程序的 np-prompt 命令中。详情请参见 使用命令行与 NEO 区块链交互

我们发布了一个 Youtube 视频 帮助你快速上手。在CityOfZion 的 Youtube 频道下还有其它视频可供参考。

相关项目

安装(Ubuntu/OSX)

需要安装 libleveldb 库。安装 Python 3.6Python 3.7 以免因为与当前维护者版本不同遇到问题。注意不支持 Python 3.5 及更低版本。

安装 neo-python 前需要安装平台相关项目。

平台相关操作

Ubuntu/Debian 16.10+

Ubuntu 从16.10 开始在官方存储库中支持 Python 3.6 。

首先,请使用以下命令确保 Ubuntu 是最新的:

sudo apt-get update && sudo apt-get upgrade
复制代码

你可以使用以下命令安装 Python 3.7 和所有系统依赖:

sudo apt-get install python3.7 python3.7-dev python3.7-venv python3-pip libleveldb-dev libssl-dev g++
复制代码

你也可以使用以下命令直接安装 Python 3.6 和所有系统依赖:

sudo apt-get install python3.6 python3.6-dev python3.6-venv python3-pip libleveldb-dev libssl-dev g++
复制代码

旧版 Ubuntu(如16.04)

对于较旧的 Ubuntu 版本,你需要使用一个像 Felix Krull 的 deadsnakes PPA 这样的外部存储库 (更多内容,阅读 here):

(本文档中第三方软件链接的使用由您自行决定并承担风险,且您同意对计算机系统的任何损坏或此类活动导致的数据丢失承担全部责任。)

apt-get install software-properties-common python-software-properties
add-apt-repository ppa:deadsnakes/ppa
apt-get update
apt-get install python3.6 python3.6-dev python3.6-venv python3-pip libleveldb-dev libssl-dev g++
复制代码

Centos/Redhat/Fedora

# Install Python 3.6:
yum install -y centos-release-scl
yum install -y rh-python36
scl enable rh-python36 bash

# Install dependencies:
yum install -y epel-release
yum install -y readline-devel leveldb-devel libffi-devel gcc-c++ redhat-rpm-config gcc python-devel openssl-devel
复制代码

Windows

在 Windows 操作系统上安装 neo-python 需要进行特别的操作,还可能遇到一些常见问题需要解决,相关内容请查看 Installation (Windows)

OSX

brew install leveldb
复制代码
关于 OSX 的常见问题

如果您遇到与此类似的问题:

from ._plyvel import (  # noqa
ImportError: dlopen(neo-python/venv/lib/python3.6/site-packages/plyvel/_plyvel.cpython-35m-darwin.so, 2): Symbol not found: __ZN7leveldb2DB4OpenERKNS_7Options
ERKSsPPS0_
Referenced from: neo-python/venv/lib/python3.6/site-packages/plyvel/_plyvel.cpython-35m-darwin.so
Expected in: flat namespace
复制代码

解决方案:更新到 plyvel 1.0.4: pip install -r requirements.txt

在 OSX 上安装 pycrypto 模块时可能会遇到问题:

src/_fastmath.c:36:11: fatal error: 'gmp.h' file not found
# include <gmp.h>
          ^~~~~~~
330 warnings and 1 error generated.
error: command 'clang' failed with exit status 1
复制代码

要解决此问题,可以使用 homebrew 安装 gmp 库,并使用以下命令行运行pip install:

brew install gmp
CFLAGS='-mmacosx-version-min=10.7 -stdlib=libc++' pip install --no-use-wheel pycrypto --no-cache-dir --global-option=build_ext --global-option="-I/usr/local/Cellar/gmp/6.1.2/include/" --global-option="-L/usr/local/lib"
复制代码

import scrypt / Reason: image not found

如果遇到如下错误:

import scrypt
File "/project_dir/venv/lib/python3.6/site-packages/scrypt.py", line 11, in
_scrypt = cdll.LoadLibrary(imp.find_module('_scrypt')[1])
File "/project_dir/venv/lib/python3.6/ctypes/init.py", line 429, in LoadLibrary
return self._dlltype(name)
File "/project_dir/venv/lib/python3.6/ctypes/init.py", line 351, in init
self._handle = _dlopen(self._name, mode)
OSError: dlopen(/project_dir/venv/lib/python3.6/site-packages/_scrypt.cpython-36m-darwin.so, 6): Library not loaded: /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib
Referenced from: /project_dir/venv/lib/python3.6/site-packages/_scrypt.cpython-36m-darwin.so
Reason: image not found
复制代码

可以尝试以下命令:

brew reinstall openssl
复制代码

从 PyPi 安装

在计算机上安装 neo-python 的最简单方法下载后使用 pip 从 PyPi 安装。建议您先创建一个虚拟环境,将此安装与系统目录隔离,然后像往常一样安装:

# create project dir
mkdir myproject
cd myproject

# create virtual environment and activate

python3.6 -m venv venv # this can also be python3 -m venv venv depending on your environment
source venv/bin/activate

(venv) pip install neo-python
复制代码

从 Git 安装

github.com/CityOfZion/… clone 存储库并导航到项目目录。创建一个 Python 3 虚拟环境并激活:

git clone https://github.com/CityOfZion/neo-python.git
cd neo-python

# if you want to use the development branch, switch now
git checkout development

# create virtual environment using Python 3.7 and activate or skip to the next step for Python 3.6
python3.7 -m venv venv
source venv/bin/activate

# create virtual environment using Python 3.6 and activate
python3.6 -m venv venv
source venv/bin/activate

# install the package in an editable form
(venv) pip install -e .
复制代码

从 Git 更新 neo-python

如果要使用 git pull 更新 neo-python ,还需要使用 pip install -r requirements.txt 更新依赖。

同步区块链

第一次使用 neo-python 时需要同步区块链。这将花费较长时间。此项目包含的 bootstrap.py 可以自动下载一个链目录。

要同步测试网,运行 np-bootstrap

要同步主网,运行 np-bootstrap -m 并等待同步完成 (3.3 GB 文件)。

在 Windows 系统中安装

以下安装说明针对带有 MSYS2 环境和 Visual Studio 2017 的 Windows 7 x64 系统,也适用于大多数 Windows 发行版。你也可以选择使用 Ubuntu 安装一个 Linux 子系统( 更多信息参见 这里)。

NOTE

从 Microsoft Store 安装的 Ubuntu 是 Ubuntu 16.04。你需要从这里下载 Ubuntu 18.04:www.microsoft.com/en-us/p/ubu…

构建 LevelDB

在 Windows 系统中安装 leveldb 最简单的办法是使用 VC++ 打包工具。如果你使用的是 windows x64 系统,还需要先设置环境变量:set VCPKG_DEFAULT_TRIPLET=x64-windows

git clone https://github.com/Microsoft/vcpkg
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg integrate install
.\vcpkg install leveldb
复制代码

安装 python 依赖项

安装 Anaconda package manager,并激活 python 虚拟环境。

conda create -n neo python=3.6.4
activate neo
复制代码

(可选)激活 Visual Studio 构建环境,如下所示:

"e:\Program Files\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
复制代码

构建 plyvel (1.0.4)

确认编译器可以访问 .lib 和 leveldb 头文件,然后将它们复制到 MSVC 构建工具目录下:

vcpkg\installed\x64-windows\include\ 复制到 e:\Program Files\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.12.25827\include

vcpkg\installed\x64-windows\lib\libleveldb.lib 复制到 e:\Program Files\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.12.25827\lib\x64\leveldb.lib

然后克隆存储库并进入所需版本,安装 cython,从 C++ 文件构建 python 扩展,最后安装 plyvel,如下所示:

git clone https://github.com/wbolster/plyvel
cd plyvel
git checkout e3887a5fae5d7b8414eac4c185c1e3b0cebbdba8
pip install cython
cython --cplus --fast-fail --annotate plyvel/_plyvel.pyx
python setup.py build_ext --inplace --force
python setup.py install
复制代码

构建 peewee (2.10.2)

git clone https://github.com/coleifer/peewee
cd peewee
git checkout 761f9144a0e17381147a81658019cffe14c118ca
python setup.py install
复制代码

构建 mmh3 (2.5.1)

git clone https://github.com/hajimes/mmh3
cd mmh3
git checkout a73b373858dedfdb6d362f5ca985ae1bb6bc2161
python setup.py install
复制代码

从 Anaconda 安装依赖项

某些依赖项无法正确地从 pip 安装,但可以从 Anaconda 安装。

conda install twisted==17.9.0
conda install pycrypto==2.6.1
复制代码

安装 neo-python

导航到 neo-python 目录并安装其它依赖项,使用以下命令:

pip install -r requirements.txt
复制代码

检查安装: python prompt.py

故障排除

以下列出了在安装过程中可能遇到的问题和解决方案。

LINK : fatal error LNK1181: cannot open input file ‘leveldb.lib’

确保编译器可以访问 .lib 与 leveldb 头文件。

error LNK2001: unresolved external symbol __imp_PathFileExistsW

找到库 shlwapi.lib,它可能在你的文件系统中。 将其合并到 leveldb.lib:lib.exe /OUT:newleveldb.lib leveldb.lib ShLwApi.Lib 并替换原始文件。

ImportError: No module named ‘winrandom’

导航到你的 python 发行版包目录,例如: e:\Programs\Anaconda3\envs\neo\Lib\site-packages

在 Crypto\Random\OSRNG\nt.py 中将 import winrandom 修改为 from . import winrandom

ImportError: No module named ‘win32api’

安装模块: pip install pypiwin32

NEO-Python SeedList

介绍

关于 SeedList

SeedList 是一个 URL 列表,是 NEO-Python 在启动时尝试连接的节点。在 NEO-Python 目录下 (/neo/data) 的 protcol.*.json 文件中可以找到 SeedList。有三个常用 protocol.*.json 文件:

protocol.mainnet.json
protocol.testnet.json
protocol.privnet.json
复制代码

本文指的是 protocol.mainnet.json ,但这些信息是通用的。

json
{
    "ProtocolConfiguration": {
    "Magic": ...,
    "AddressVersion": ...,
    "SecondsPerBlock": ...,
    "StandbyValidators": [
    ...
    ],
    "SeedList": [
      "seed1.neo.org:10333",
      "seed2.neo.org:10333",
      "seed3.neo.org:10333",
      "seed4.neo.org:10333",
      "seed5.neo.org:10333"
    ],
    "RPCList":[
    ...
    ],
    "SystemFee": {
    ...
    }
  }
}复制代码

在上面的文件中,NEO-Python 配置为通过PORT:10333 连接 seed1.neo.org, seed2.neo.org 等节点 。

潜在问题

假如以上列表中的每个节点故障,NEO-Python 会尝试连接相邻节点。但是这种方法存在很多未知因素,例如相邻节点出问题了,可能导致等待时间相当漫长。

更新 SeedList

通过使用我们确定的活跃节点更新 SeedList,可以避免上述潜在问题中提到的漫长等待风险。

更新 Windows WSL (Ubuntu) 上的 NEO-Python 节点列表

如果是在 Ubuntu 上运行 neo-python ,你应该使用了 venv。你需要修改 venv 目录里 lib/python3.6/site-packages/neo/data 路径下的 protocol.mainnet.json 文件。如果该路径不存在,说明你尚未在当前 venv 中使用该文件,那么你可以到 neo/data 路径下的 neo-python 文件夹中修改此文件。或者你也可以删除 venv 文件夹,修改父文件,新建一个 venv 文件夹(python -m venv venv)并激活 venv,然后使用 pip install e . 重新安装。

更新 SeedList

活跃节点

要寻找活跃节点,使用 NEO 网络状态监测网。要查看更详细的信息,查看该监测网 Github 上的 存储库

如上图所示,列表中显示了可用节点。最新节点显示在最上方。 图中

2
标识的地方表示节点是否响应。通常,绿色并显示 yes 的节点为活跃节点。

我们会根据以下端口的标准协议进行选择,比如我们会选择上图中的节点 1 而不选节点3,因为节点 1符合标准而节点 3 不符合。

Main NetTest Net
JSON-RPC via HTTPS1033120331
JSON-RPC via HTTP1033220332
P2P via TCP1033320333
P2P via WebSocket1033420334

以下是选出的活跃节点地址:

seed3.aphelion-neo.com
seed4.aphelion-neo.com
node2.ams2.bridgeprotocol.io
pyrpc1.nodeneo.ch
node2.nyc3.bridgeprotocol.io
复制代码

编辑 protocol 文件

为了告知 NEO-Python 新的节点列表,需要将前面几个地址粘贴到 protocol.mainnet.json,如下所示:

json
{
    "ProtocolConfiguration": {
    "Magic": ...,
    "AddressVersion": ...,
    "SecondsPerBlock": ...,
    "StandbyValidators": [
    ...
    ],
    "SeedList": [
      "seed1.neo.org:10333",
      "seed2.neo.org:10333",
      "seed3.neo.org:10333",
      "seed4.neo.org:10333",
      "seed5.neo.org:10333",
      "seed4.aphelion-neo.com:10333",
      "node2.sgp1.bridgeprotocol.io:10333",
      "seed2.aphelion-neo.com:10333",
      "seed3.aphelion-neo.com:10333",
      "node2.ams2.bridgeprotocol.io:10333",
      "pyrpc1.narrative.network:10333",
      "node2.nyc3.bridgeprotocol.io:10333",
      "pyrpc4.narrative.network:10333",
      "pyrpc2.narrative.network:10333",
      "pyrpc3.narrative.network:10333",
      "seed1.aphelion-neo.com:10333",
      "seed1.switcheo.network:10333",
      "seed2.switcheo.network:10333",
      "seed5.cityofzion.io:10333",
      "seed3.cityofzion.io:10333",
      "seed3.switcheo.network:10333",
      "seed1.o3node.org:10333",
      "seed3.travala.com:10333",
      "seed4.cityofzion.io:10333",
      "seed2.cityofzion.io:10333",
      "seed2.o3node.org:10333",
      "seed3.o3node.org:10333",
      "node1.sgp1.bridgeprotocol.io:10333",
      "seed2.travala.com:10333",
      "seed4.switcheo.network:10333",
      "seed1.spotcoin.com:10333",
      "node1.nyc3.bridgeprotocol.io:10333"
    ],
    "RPCList":[
    ...
    ],
    "SystemFee": {
    ...
    }
  }
}
复制代码

请注意,上例中的每个地址后加上了 :10333 以便告知 NEO-Python 使用 P2P 协议连接。

现在就可以像往常一样启动 neo-python 了。

JSON 和 REST API 服务器

建议你在启动任意 API 服务器之前先更新节点列表以保证最大连接数。有关更多API服务器信息,请查看 这里

基本用法

使用 neo-python 主要有两种方式:运行 np-prompt 和运行自定义代码的节点。

np-prompt

在测试网运行 np-prompt :

$ np-prompt
复制代码

显示 help 的所有可用参数:

$ np-prompt -h
usage: np-prompt [-h] [-m | -p [host] | --coznet | -c CONFIG]
                 [-t {dark,light}] [-v] [--datadir DATADIR] [--version]

optional arguments:
  -h, --help            show this help message and exit
  -m, --mainnet         Use MainNet instead of the default TestNet
  -p [host], --privnet [host]
                        Use a private net instead of the default TestNet,
                        optionally using a custom host (default: 127.0.0.1)
  --coznet              Use the CoZ network instead of the default TestNet
  -c CONFIG, --config CONFIG
                        Use a specific config file
  -t {dark,light}, --set-default-theme {dark,light}
                        Set the default theme to be loaded from the config
                        file. Default: 'dark'
  -v, --verbose         Show smart-contract events by default
  --datadir DATADIR     Absolute path to use for database directories
  --version             show program's version number and exit
复制代码

自定义代码节点

查看 /examples 目录下的示例:github.com/CityOfZion/…

另外可参考 “Settings and Logging” and “Interacting with Smart Contracts”.

API 服务器 (JSON / REST)

在主网启动 JSON 和 REST API 服务器:

$ np-api-server --mainnet --port-rpc 10332 --port-rest 80
复制代码

示例通知和 help 下的所有可用参数:

$ np-api-server --testnet --port-rpc 8080 --port-rest 8088
[I 180315 09:27:09 NotificationDB:44] Created Notification DB At /Users/thomassaunders/.neopython/Chains/Test_Notif
[I 180315 09:27:09 threading:864] [TestNet] Block 5644 / 53999
[I 180315 09:27:09 np-api-server:11] Starting json-rpc api server on http://0.0.0.0:8080
[I 180315 09:27:09 _observer:131] Site starting on 8080
[I 180315 09:27:09 _observer:131] Starting factory <twisted.web.server.Site object at 0x110619828>
[I 180315 09:27:09 np-api-server:11] Starting REST api server on http://0.0.0.0:8088

# view help
$ np-api-server -h
usage: np-api-server [-h]
                   (--mainnet | --testnet | --privnet | --coznet | --config CONFIG)
                   [--port-rpc PORT_RPC] [--port-rest PORT_REST]
                   [--logfile LOGFILE] [--syslog] [--syslog-local [0-7]]
                   [--disable-stderr] [--datadir DATADIR]
                   [--maxpeers MAXPEERS] [--wallet WALLET] [--host HOST]

  optional arguments:
  -h, --help            show this help message and exit
  --datadir DATADIR     Absolute path to use for database directories
  --maxpeers MAXPEERS   Max peers to use for P2P Joining
  --wallet WALLET       Open wallet. Will allow you to use methods that
                        require an open wallet
  --host HOST           Hostname ( for example 127.0.0.1)

  Network options:
  --mainnet             Use MainNet
  --testnet             Use TestNet
  --privnet             Use PrivNet
  --coznet              Use CozNet
  --config CONFIG       Use a specific config file

  Mode(s):
  --port-rpc PORT_RPC     port to use for the json-rpc api (eg. 10332)
  --port-rest PORT_REST   port to use for the rest api (eg. 80)

  Logging options:
  --logfile LOGFILE     Logfile
  --syslog              Log to syslog instead of to log file ('user' is the
                        default facility)
  --syslog-local [0-7]  Log to a local syslog facility instead of 'user'.
                        Value must be between 0 and 7 (e.g. 0 for 'local0').
  --disable-stderr      Disable stderr logger
复制代码

端口描述

要使外部程序能访问你的 API 服务器,需要打开防火墙端口。下表显示的端口可以设置为全部打开或按需打开。

Main NetTest Net
JSON-RPC via HTTPS1033120331
JSON-RPC via HTTP1033220332

使用 Windows WSL (Ubuntu) 运行 API 服务器

如果在 Windows WSL (Ubuntu) 上运行 neo-python, 除了打开路由器上的相应端口,还需要参考 这里 为你的 Windows 防火墙添加一个入站策略。

使用命令行与 NEO 区块链交互

Prompt 是用于运行和与 NEO 区块链交互的默认界面。

如下所示:

$ np-prompt
NEO cli. Type 'help' to get started

neo>
复制代码

钱包操作

下表列出了所有可用的钱包命令。

命令描述
create wallet <wallet_path>创建钱包文件
open wallet <wallet_path>打开钱包文件
wallet检查钱包
wallet <verbose> < rebuild> <rebuild block_height>重建钱包索引
wallet migrated迁移你的钱包
export wif <address>导出私钥
export nep2 <address>将地址导出为NEP2加密私钥
import wif <WIF>导入私钥
import nep2 <address>将地址导入为NEP2加密私钥
import watch_addr <address>导入 watch only 地址
import contract_addr <script_hash> <pubkey>导入智能合约地址
send <asset_ID> <address> <amount> [from_address]将资产发送到指定的地址
wallet delete_addr <address>删除地址

解释与示例

创建钱包
neo> create wallet path/to/walletfile
[Password 1]> **********
[Password 2]> **********
Wallet {
    "addresses": [
        "AayaivCAcYnM8q79JCrfpRGXrCEHJRN5bV"
    ],
    "claims": {
        "available": 0.0,
        "unavailable": 0.0
    },
    "tokens": [],
    "height": 0,
    "synced_balances": [],
    "path": "Wallets/blahblah.db3",
    "public_keys": [
        {
            "Address": "AayaivCAcYnM8q79JCrfpRGXrCEHJRN5bV",
            "Public Key": "027973267230b7cba0724589653e667ddea7aa8479c01a82bf8dd398cec93508ef"
        }
    ],
    "percent_synced": 0
}
neo>
复制代码
打开钱包
neo> open wallet path/to/walletfile
[Password]> ***********
Opened wallet at path/to/walletfile
neo>
复制代码
检查钱包
neo> wallet
Wallet {
    "addresses": [
        "AayaivCAcYnM8q79JCrfpRGXrCEHJRN5bV"
    ],
    "claims": {
        "available": 0.0,
        "unavailable": 0.0
    },
    "tokens": [],
    "height": 75500,
    "synced_balances": [],
    "path": "Wallets/blahblah.db3",
    "public_keys": [
        {
            "Address": "AayaivCAcYnM8q79JCrfpRGXrCEHJRN5bV",
            "Public Key": "027973267230b7cba0724589653e667ddea7aa8479c01a82bf8dd398cec93508ef"
        }
    ],
    "percent_synced": 9
}
复制代码
重建钱包索引

如果您的钱包出现异常,或者您已将新地址导入钱包,则可能需要重建钱包索引,从区块头同步钱包。也可以指定一个区块号开始重新同步,如下所示:

neo> wallet rebuild 700000
restarting at 700000
neo>
复制代码
迁移钱包

如果钱包数据模型发生更改,则可能需要迁移钱包,如下所示:

neo> wallet migrated
migrated wallet
neo>
复制代码
重新加密钱包

如果你打开钱包时收到如下信息,则必须重新加密以防止之前的钱包漏洞。

Could not open wallet: This wallet is currently vulnerable. Please execute the "reencrypt_wallet.py" script on this wallet before continuing
复制代码

要修复此问题,先使用命令 exit 退出 neo prompt,然后运行重新加密脚本:

python reencrypt_wallet.py path/to/mywallet.db3
复制代码

你需要输入密码并使用新名称path/to/new_mywallet.db3保存重新加密的钱包。

导入私钥

您可能需要导入一个WIF私钥来添加地址到您的钱包,如下所示:

neo> import wif KxP97gujib35PBEnTq78e5NmYVbeaosU4AdguDzZ4tyf6a7W32UM
Imported key KxP97gujib35PBEnTq78e5NmYVbeaosU4AdguDzZ4tyf6a7W32UM
Pubkey: 303263383231666338336465373331313039633435653034346136353863386631313337623730303461396232323237613335653262353566613061313630323731
neo>
复制代码
导出私钥

您可能需要从钱包导出 WIF 私钥用于另一个程序。指定要导出的 WIF 地址,如下所示:

neo> export wif AXjaFSP23Jkbe6Pk9pPGT6NBDs1HVdqaXK
[Wallet Password]> ***********
WIF key export: KxP97gujib35PBEnTq78e5NmYVbeaosU4AdguDzZ4tyf6a7W32UM
neo>
复制代码
导出 NEP2 密码保护的私钥

您可以将地址导出为 NEP2 加密私钥,如下所示:

neo> export nep2 AStZHy8E6StCqYQbzMqi4poH7YNDHQKxvt
[Wallet Password]> ***********
[Key Password 1]> ******************
[Key Password 2]> ******************
NEP2 key export: 6PYVPVe1fQznphjbUxXP9KZJqPMVnVwCx5s5pr5axRJ8uHkMtZg97eT5kL
neo>
复制代码
导入NEP2密码保护的私钥

您可以将地址导入为 NEP2 加密私钥,如下所示:

neo> import nep2 6PYVPVe1fQznphjbUxXP9KZJqPMVnVwCx5s5pr5axRJ8uHkMtZg97eT5kL
[Key Password]> ******************
Imported nep2 key: 6PYVPVe1fQznphjbUxXP9KZJqPMVnVwCx5s5pr5axRJ8uHkMtZg97eT5kL
Pubkey: 303236323431653765323662333862623731353462386164343934353862393766623163343739373434336463393231633563613537373466353131613262626663
复制代码
导入 watch only 地址

watch only 是一种您没有公钥但想观察的地址。watch only 地址可以像普通地址一样删除。

neo> import watch_addr AStZHy8E6StCqYQbzMqi4poH7YNDHQKxvt
neo>
复制代码
导入智能合约地址

您可能想要使用已部署的智能合约中的资金。根据合约编程,如果其允许您像使用自己的资金一样使用合约中的资金,则您可以通过指定合约的script_hash,以及您希望与合约关联的钱包中地址的公钥来导入合约地址。合约地址可以像钱包中的普通地址一样删除。

# import contract_addr {script_hash} {pubkey}
neo> import contract_addr 3c62006802d895974069a1d96398a04b4703f0f8 027973267230b7cba0724589653e667ddea7aa8479c01a82bf8dd398cec93508ef
Added contract addres AeU8kTJxynwkT3q9ao8aDFuaRJBkU3AfFG to wallet
neo>
复制代码
删除地址
neo> wallet delete_addr AStZHy8E6StCqYQbzMqi4poH7YNDHQKxvt
Deleted address AStZHy8E6StCqYQbzMqi4poH7YNDHQKxvt
neo>
复制代码

发送资产

从钱包发送

使用以下命令可以从你的钱包发送资产。使用此命令,发送的资产来自于你的某个地址或者多个地址。 change_address 是你钱包的中的某个地址。

# syntax send {asset_name} {address to} {amount} ( optional: --from-addr={from_addr})
neo> send gas AeU8kTJxynwkT3q9ao8aDFuaRJBkU3AfFG 11
[Password]> ***********
Relayed Tx: 468e294b11a9f65cc5e2c372124877472eebf121befb77ceed23a84862a606d3
neo>
复制代码

从指定地址发送

也可以指定一个特定的地址来发送资产,如从合约地址发送资产。

# syntax send {asset_name} {address to} {amount} ( optional: --from-addr={from_addr})
neo> send gas AeU8kTJxynwkT3q9ao8aDFuaRJBkU3AfFG 11 --from-addr=AXjaFSP23Jkbe6Pk9pPGT6NBDs1HVdqaXK
[Password]> ***********
Relayed Tx: a43dfb30af63bd0e5a510b05f02b3d40932af26d4564e040e3812ce78e76ce71
neo>
复制代码

NEP5 Tokens

导入 NEP5 代币

您可以使用钱包观察 NEP5 代币并与之交互,为此,您需要首先注册钱包以观察代币,如下所示:

neo> import token f8d448b227991cf07cb96a6f9c0322437f1599b9
added token {
    "name": "NEP5 Standard",
    "script_hash": "f8d448b227991cf07cb96a6f9c0322437f1599b9",
    "decimals": 8,
    "symbol": "NEP5",
    "contract address": "AYhE3Svuqdfh1RtzvE8hUhNR7HSpaSDFQg"
}
neo> wallet
Wallet {
    # truncated ...

    "percent_synced": 100,
    "addresses": [
        "AayaivCAcYnM8q79JCrfpRGXrCEHJRN5bV",
        {
            "balances": {
                "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b": "4051.0",
                "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7": "897.48372409"
            },
            "script_hash": "AXjaFSP23Jkbe6Pk9pPGT6NBDs1HVdqaXK",
            "votes": [],
            "version": 0,
            "is_watch_only": false,
            "tokens": [
                "[f8d448b227991cf07cb96a6f9c0322437f1599b9] NEP5 : 4519175.65580000"
            ],
            "frozen": false
        },
        {
    }
}
复制代码

在 prompt 中与智能合约交互

查看在 prompt 中与智能合约交互的完整说明: Smart Contracts within the Prompt

获取 NEO 测试网资产

本节介绍如何通过 NEO 官方申请表 获取测试网资产。

获取测试网资产需要两步:

  1. 在钱包中添加多方签名地址。
  2. 将资产转移到你的地址。

添加多方签名地址

你需要准备好以下信息:

  1. 你从 NEO 收到的电子邮件中提示的公钥
  2. 你自己钱包里的公钥。 打开钱包并在提示符下输入 wallet 获取所需信息。
neo> wallet
Wallet {
    ...
    "public_keys": [
        {
            "Address": "ANFLgwKG8Eni9gJmKfM7yFXEaWwoGkSUid",
            "Public Key": "037b8992e8384212f82e05c8836816c0f14dff9528397138731638b17d6357021e" <--- take this
        }
    ],
    ...
}
复制代码

然后使用如下命令创建多方签名地址:

neo> import multisig_addr
please specify multisig contract like such: 'import multisig {pubkey in wallet} {minimum # of signatures required} {signing pubkey 1} {signing pubkey 2}...'

neo> import multisig_addr 037b8992e8384212f82e05c8836816c0f14dff9528397138731638b17d6357021e 1 037b8992e8384212f82e05c8836816c0f14dff9528397138731638b17d6357021e 02883118351f8f47107c83ab634dc7e4
ffe29d274e7d3dcf70159c8935ff769beb
[I 180310 16:49:19 UserWallet:191] contract does not exist yet
Added multi-sig contract address ALXEKioZntX73QawcnfcHUDvTVm8qXjAxf to wallet
复制代码

再次检查你的钱包,应该能查看到余额(特别是查看 synced_balances )。如果没有看到新增余额,请运行wallet rebuild 并等待它完全同步后再次尝试。

neo> wallet
Wallet {
    "path": "test",
    "addresses": [
        {
            "address": "ANFLgwKG8Eni9gJmKfM7yFXEaWwoGkSUid",
            "script_hash": "47028f2a3d33466f29fba10e65c90fd8f3d01e1f",
            "tokens": null
        },
        {
            "version": 0,
            "script_hash": "ALXEKioZntX73QawcnfcHUDvTVm8qXjAxf",
            "frozen": false,
            "votes": [],
            "balances": {
                "0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b": "50.0",
                "0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7": "50.0"
            },
            "is_watch_only": false
        }
    ],
    ...
    "synced_balances": [
        "[NEO]: 50.0 ",
        "[NEOGas]: 50.0 "
    ],
    "public_keys": [
        {
            "Address": "ANFLgwKG8Eni9gJmKfM7yFXEaWwoGkSUid",
            "Public Key": "037b8992e8384212f82e05c8836816c0f14dff9528397138731638b17d6357021e"
        },
        {
            "Address": "ALXEKioZntX73QawcnfcHUDvTVm8qXjAxf",
            "Public Key": "037b8992e8384212f82e05c8836816c0f14dff9528397138731638b17d6357021e"
        }
    ],
    ...
}
复制代码

将资产转账到你自己的地址

现在就可以安装以下操作将资产转移到你自己的地址:

neo> send NEO ANFLgwKG8Eni9gJmKfM7yFXEaWwoGkSUid 5 --from-addr=ALXEKioZntX73QawcnfcHUDvTVm8qXjAxf
[Password]> **********
[I 180310 17:02:42 Transaction:611] Verifying transaction: b'c32b0e3d9adbef6720abfad5106dcd2dacb17b31d4f9d32cbcf8ed6e7f566ef3'
Relayed Tx: c32b0e3d9adbef6720abfad5106dcd2dacb17b31d4f9d32cbcf8ed6e7f566ef3
复制代码

注意参数 --from-addr 指定的是提取资产的多方签名合约地址。

设置和日志

neo-python提供一个设置模块,可以用来配置以下事情:

  • 网络:主网、测试网、私网或自定义配置
  • 日志:

    • 智能合约事件日志
    • 日志文件(可切换)
    • Loglevel

要更改设置,导入设置示例:

from neo.Settings import settings
复制代码

设置网络

您可以使用以下设置方法配置网络:

settings.setup_mainnet()
settings.setup_testnet()
settings.setup_privnet()
settings.setup(config_file)
复制代码

neo-python 默认使用测试网。

数据保存路径

默认情况下, neo-python 将链数据保存在 ~/.neopython/Chains。如果想指定 Chains 目录放置的路径,可以将 --datadir 标志传递给以下任意命令:np-prompt, np-api-server, 或 np-bootstrap。也可以使用 settings 模块手动设置:

settings.set_data_dir('your/path')
复制代码

日志

neo-python 使用以下默认方式:

  • 来自所有智能合约的事件都使用loglevel INFO进行记录
  • loglevel 设置为 INFO
  • 记录到 logfile 被取消激活 (prompt.py 记录到 prompt.log)

智能合约事件

要禁用所有智能合约事件的记录,参照以下示例:

settings.set_log_smart_contract_events(False)
复制代码

更改日志级别

要更改日志级别,如也显示DEBUG日志或只显示错误日志,参照以下示例:

import logging

# Show everything, including debug logs:
settings.set_loglevel(logging.DEBUG)

# Only show errors:
settings.set_loglevel(logging.ERROR)
复制代码

在 prompt 中更改

要在 prompt 中更改 log 级别,使用以下命令:

neo> config sc-events on
neo> config sc-events off
复制代码

配置日志文件

要启动日志文件的记录功能,参照以下示例:

# Just a single logfile, with no limits or rotation:
settings.set_logfile(your_logfile_path)

# To enable rotation with a maximum of 10MB per file and 3 rotations:
settings.set_logfile(your_logfile_path, 1e7, 3)
复制代码

记录自定义代码

neo-python 使用 logzero 进行记录。要在现有的 neo 日志记录配置中使用 logger,只需从logzero 导入 logger,如下所示:

from logzero import logger

# These log messages are sent to the console
logger.debug("hello")
logger.info("info")
logger.warn("warn")
logger.error("error")

# This is how you'd log an exception
try:
    raise Exception("this is a demo exception")
except Exception as e:
    logger.exception(e)复制代码

neo.Core.TX.Transaction

本文将详细介绍 neo.Core.TX.Transaction 模块。

描述:

交易基本类

用法

从 neo.Core.Transaction 导入交易

class neo.Core.TX.Transaction.ContractTransaction(*args, **kwargs) [source]

class neo.Core.TX.Transaction.Transaction(inputs=[], outputs=[], attributes=[], scripts=[])[source]

Deserialize(reader) [source]

反序列化完整的对象。

参数reader (

neo.IO.BinaryReader

) –

static DeserializeFrom(reader) [source]

反序列化完整的对象。

参数: reader (

neo.IO.BinaryReader

) –

返回值

返回类型Transaction

static DeserializeFromBufer (buffer, offset=0) [source]

从指定的缓冲区反序列化对象实例。

参数

buffer (

bytes
,
bytearray
,
BytesIO

) – (可选)用于创建数据流的数据。

offset – 不使用

返回值

返回类型Transaction

DeserializeUnsigned(reader) [source]

反序列化对象。

参数reader (

neo.IO.BinaryReader

) –

抛出Exception – 如果交易类型错误。

DeserializeUnsignedWithoutType(

reader

) [source]

不读取交易类型数据的反序列化对象。

参数reader (

neo.IO.BinaryReader

) –

GetHashData() [source]

获取用于散列的数据。

返回值

返回类型:bytes

GetMessage() [source]

获取用于散列的数据。

返回值

返回类型:bytes

GetScriptHashesForVerifying() [source]

获取用于验证交易的脚本散列。

抛出Exception – 如果交易中没有有效资产。

返回值:UInt160 类型脚本散列。

返回类型:list

GetTransactionResults() [source]

获取交易的执行结果。

返回值:如果交易没有引用 list: TransactionResult 对象。

返回类型:无

Hash

获取交易的散列值。

返回值

返回类型:UInt256

NetworkFee() [source]

获取网络手续费

返回值

返回类型:Fixed8

References

获取所有引用

返回值:Key (UInt256): input PrevHash Value (TransactionOutput): object.

返回类型:dict

ResetHashData() [source]

复位本地保存的散列数据

ResetReferences() [source]

复位本地保存的引用

Scripts

获取脚本

返回值

返回类型:list

Serialize(

writer

) [source]

序列化对象

参数writer (

neo.IO.BinaryWriter

) –

SerializeUnsigned(

writer

) [source]

序列化对象

参数writer (

neo.IO.BinaryWriter

) –

Size() [source]

获取对象的总大小(以字节为单位)。

返回值:大小

返回类型:int

SystemFee() [source]

获取系统手续费。

返回值:目前为 0。

返回类型:Fixed8

ToArray() [source]

获取 self 的字节数据。

返回值

返回类型:byte

ToJson() [source]

将对象成员转换为可以解析为JSON的字典编码。

返回值

返回类型:dict

Verify(

mempool

) [source]

验证交易。

参数mempool

返回值:验证通过返回 True,否则返回 False。

返回类型:bool

getAllInputs() [source]

获取输入

返回值

返回类型:list

withdraw_hold

= None

交易的 docstring

class neo.Core.TX.Transaction.TransactionInput(prevHash=None, prevIndex=None) [source]

TransactionInput 的 docstring

Deserialize(

reader

) [source]

反序列化全对象

参数reader (

neo.IO.BinaryReader

) –

Serialize(

writer

) [source]

序列化对象

参数writer (

neo.IO.BinaryWriter

) –

ToJson() [source]

将对象成员转换为可以解析为JSON的字典编码。

返回值

返回类型:dict

ToString() [source]

获取对象的字符串表示形式。

返回:PrevHash:PrevIndexReturn type:str

class neo.Core.TX.Transaction.TransactionOutput (AssetId=None, Value=None, script_hash=None)[source]

Address

获取交易的公共地址。

返回值:代表地址的 base58 编码字符串。

返回类型:str

AddressBytes

获取交易的公共地址。

返回值:base58 编码的字符串。

返回类型:bytes

AssetId

= None

docstring for TransactionOutput

Deserialize(

reader

) [source]

反序列化完整对象

参数reader (

neo.IO.BinaryReader

) –

Serialize(

writer

) [source]

序列化对象

参数writer (

neo.IO.BinaryWriter

) –

ToJson(

index

) [source]

将对象成员转换为可以解析为JSON的字典编码。 :param index: 交易输出的索引 :type index: int

返回值

返回类型:dict

与智能合约交互

neo-python 的一个常见用例是与智能合约进行交互。典型的智能合约事件包括 Runtime.Notify, Runtime.Log, 执行成功或失败,以及 Storage.GET/PUT/DELETE

事件类型

以下列出了当前 NEO-Python 可以处理的智能合约事件类型:

RUNTIME_NOTIFY = "SmartContract.Runtime.Notify"
RUNTIME_LOG = "SmartContract.Runtime.Log"

EXECUTION = "SmartContract.Execution.*"
EXECUTION_INVOKE = "SmartContract.Execution.Invoke"
EXECUTION_SUCCESS = "SmartContract.Execution.Success"
EXECUTION_FAIL = "SmartContract.Execution.Fail"

VERIFICATION = "SmartContract.Verification.*"
VERIFICATION_SUCCESS = "SmartContract.Verification.Success"
VERIFICATION_FAIL = "SmartContract.Verification.Fail"

STORAGE = "SmartContract.Storage.*"
STORAGE_GET = "SmartContract.Storage.Get"
STORAGE_PUT = "SmartContract.Storage.Put"
STORAGE_DELETE = "SmartContract.Storage.Delete"

CONTRACT = "SmartContract.Contract.*"
CONTRACT_CREATED = "SmartContract.Contract.Create"
CONTRACT_MIGRATED = "SmartContract.Contract.Migrate"
CONTRACT_DESTROY = "SmartContract.Contract.Destroy"
复制代码

当这些事件发生在接收区块时,由neo.EventHub调度一个 SmartContractEvent实例。

SmartContractEvent

事件处理程序总是收到参数 neo.EventHub.SmartContractEvent 的一个实例,其包含有关当前事件的所有信息。SmartContractEvent 具有以下属性:

属性数据类型描述
event_typestrneo.EventHub.SmartContractEvent中的一个事件类型
contract_hashUInt160合约散列值
tx_hashUInt256交易散列值
block_numberint收到此事件的区块号
event_payloadobject[]对象列表,取决于智能合约发出的数据类型(例如,使用Runtime.Notify)。
execution_successbool方法调用是否成功
test_modebool这个事件是否由本地TestInvoke调度,而不是从区块链接收

neo.contrib.smartcontract.SmartContract

开发人员可以使用neo.contrib.smartcontract.SmartContract 轻松订阅这些事件。以下是一个使用散列6537b4bd100e514119e3a7ab49d520d20ef2c2a4监听 Runtime.Notify智能合约事件的示例:

from neo.contrib.smartcontract import SmartContract

smart_contract = SmartContract("6537b4bd100e514119e3a7ab49d520d20ef2c2a4")

@smart_contract.on_notify
def sc_notify(event):
    print("SmartContract Runtime.Notify event:", event)

    # Make sure that the event payload list has at least one element.
    if not len(event.event_payload):
        return

    # The event payload list has at least one element. As developer of the smart contract
    # you should know what data-type is in the bytes, and how to decode it. In this example,
    # it's just a string, so we decode it with utf-8:
    print("- payload part 1:", event.event_payload[0].decode("utf-8"))
复制代码

目前可用的装饰器如下:

装饰器智能合约事件
@on_any所有事件
@on_notifyRuntime.Notify
@on_logRuntime.Log
@on_storage存储 PUT, GET 和 DELETE
@on_execution方法调用,成功或失败

以下示例显示如何监听所有事件并区分代码中的事件类型:

from neo.contrib.smartcontract import SmartContract
from neo.EventHub import SmartContractEvent

smart_contract = SmartContract("6537b4bd100e514119e3a7ab49d520d20ef2c2a4")

@smart_contract.on_all
def handle_sc_event(event):
    print("SmartContract Runtime.Notify event:", event)

    # Check if it is a Runtime.Notify event
    if event.event_type == SmartContractEvent.RUNTIME_NOTIFY:
        # Exit if an empty payload list
        if not len(event.event_payload):
            return

        # Decode the first payload item and print it
        print("- payload part 1:", event.event_payload[0].decode("utf-8"))
复制代码

Prompt 中的智能合约交互

neo-python 最令人欣喜的功能之一是能够在 NEO 平台上快速构建、测试、导入以及调用智能合约。本节将提供在 Prompt 里操作智能合约的基本指南。

创建合约

首先,你需要在 prompt 里创建一个智能合约。该方法使用 neo-boa 编译器编译智能合约并将其保存为 .avm 格式。

在 prompt 里创建或导入智能合约时,最好使用相对路径 (相对于 neo-python 安装目录),尽管绝对路径可能也有效。

以下是一个示例: sample1.py

def Main():
  print("Hello World")
  return True
neo> build docs/source/example/sample1.py
Saved output to docs/source/example/sample1.avm
复制代码

以上命令只是简单地编译文件,之后你就可以在 Prompt 或者 NEO-GUI 中导入已编译的 .avm 文件。

创建并测试合约

创建并测试命令更加常用,它可以在编译文件后执行和检测结果,只是其语法要复杂些。

查看 ContractParameterType 列表:ContractParameterTypes

该命令语法为:

build path/to/file.py test {input_params} {return_type} {needs_storage} {needs_dynamic_invoke} param1 param2 etc..where {input_params} and {return_type}

  • {input_params} :输入一个或一系列 ContractParameterType,例如 0710 表示智能合约接收一个字符串和列表。
  • {return_type}:输入一个 ContractParameterType, 例如 02 表示智能合约返回一个整数。
  • {needs_storage} :输入布尔值 TrueFalse 指示智能合约是否使用 Storage.Get/Put/Delete 互操作 API。
  • {needs_dynamic_invoke} :输入布尔值TrueFalse ,用于指示智能合约是否调用另一个运行时才知道地址的合约,通常为 False
  • params1 params2 etc... :输入你用来测试的参数。

因此要创建并测试 sample1.py,命令格式为 build docs/source/example/sample1.py test '' 01 False False,其中 '' 表示不接收参数,01 表示返回一个布尔值。在 Prompt 中输入如下:

neo> build docs/source/example/sample1.py test '' 01 False false
Saved output to docs/source/example/sample1.avm
please open a wallet to test built contract
neo>
复制代码

现在我们可以打开钱包来测试之前创建的合约了。注意,打开钱包后,你可以使用向上箭头键选择之前输入过的命令。

neo> open wallet Wallets/awesome
[password]> ***********
Opened wallet at Wallets/awesome
neo> build docs/source/example/sample1.py test '' 01 False false
Saved output to docs/source/example/sample1.avm
[I 180302 22:22:58 Invoke:482] Used 0.016 Gas

-----------------------------------------------------------
Calling docs/source/example/sample1.py with arguments []
Test deploy invoke successful
Used total of 11 operations
Result [{'type': 'Boolean', 'value': True}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

到此我们就完成了第一个智能合约的创建和测试。如果想要查看该智能合约作为整数时的结果,可以更改 return_type ,你会得到如下所示的输出:

neo> build docs/source/example/sample1.py test '' 02 False False
Saved output to docs/source/example/sample1.avm
[I 180302 22:25:09 Invoke:482] Used 0.016 Gas

-----------------------------------------------------------
Calling docs/source/example/sample1.py with arguments []
Test deploy invoke successful
Used total of 11 operations
Result [{'type': 'Integer', 'value': 1}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

在上例中你会发现,尽管合约中包含了 print 命令,却并没有打印输出任何字符。要解决这个问题,让我们打开智能合约事件并再次运行一遍。

neo>
neo> config sc-events on
Smart contract event logging is now enabled
neo> build docs/source/example/sample1.py test '' 01 False False
Saved output to docs/source/example/sample1.avm
[I 180302 22:56:19 EventHub:71] [test_mode][SmartContract.Contract.Create] [09a129673c61917593cb4b57dce066688f539d15] ['{\n    "version": 0,\n    "code": {\n        "hash": "0x09a129673c61917593cb4b57dce066688f539d15",\n        "script": "54c56b0b48656c6c6f20576f726c64680f4e656f2e52756e74696d652e4c6f67516c7566",\n        "parameters": "",\n        "returntype": 1\n    },\n    "name": "test",\n    "code_version": "test",\n    "author": "test",\n    "email": "test",\n    "description": "test",\n    "properties": {\n        "storage": false,\n        "dynamic_invoke": false\n    }\n}']
[I 180302 22:56:19 EventHub:71] [test_mode][SmartContract.Runtime.Log] [09a129673c61917593cb4b57dce066688f539d15] [b'Hello World']
[I 180302 22:56:19 EventHub:71] [test_mode][SmartContract.Execution.Success] [09a129673c61917593cb4b57dce066688f539d15] [1]
[I 180302 22:56:20 Invoke:482] Used 0.016 Gas

-----------------------------------------------------------
Calling docs/source/example/sample1.py with arguments []
Test deploy invoke successful
Used total of 11 operations
Result [{'type': 'Boolean', 'value': True}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

当我们在 prompt 里使用 config sc-events on命令打开 SmartContractEvent 日志功能后,再次运行相同的命令会发现这次输出结果中多出了三行。

  • SmartContract.Contract.Create 是在 VM 中创建了你的智能合约事件的事件
  • SmartContract.Runtime.Log 是输出 Hello World 的事件
  • SmartContract.Execution.Success 表示智能合约成功执行完成

下面让我们尝试一个复杂点的合约,sample2.py:

def Main(operation, a, b):

    if operation == 'add':
        return a + b

    elif operation == 'sub':
        return a - b

    elif operation == 'mul':
        return a * b

    elif operation == 'div':
        return a / b

    else:
        return -1
复制代码

创建并指定几个参数运行该合约:

neo> build docs/source/example/sample2.py test 070202 02 False False
Saved output to docs/source/example/sample2.avm
[E 180302 22:30:01 ExecutionEngine:825] COULD NOT EXECUTE OP: Invalid list operation b'z' ROLL
[E 180302 22:30:01 ExecutionEngine:826] Invalid list operation
Traceback (most recent call last):
  File "/Users/thomassaunders/Workshop/neo-python/neo/VM/ExecutionEngine.py", line 823, in StepInto
    self.ExecuteOp(op, self.CurrentContext)
  File "/Users/thomassaunders/Workshop/neo-python/neo/VM/ExecutionEngine.py", line 276, in ExecuteOp
    estack.PushT(estack.Remove(n))
  File "/Users/thomassaunders/Workshop/neo-python/neo/VM/RandomAccessStack.py", line 57, in Remove
    raise Exception("Invalid list operation")
Exception: Invalid list operation
[I 180302 22:30:01 InteropService:93] Trying to get big integer Array: ['None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None']
复制代码

出现以上结果的原因是,我们测试的合约要求提供更多的参数。如果你在创建并测试合约时遇到相似的错误信息,很有可能是相同的原因。让我们输入一些参数再尝试一次:

neo> build docs/source/example/sample2.py test 070202 02 False False add 1 2
Saved output to docs/source/example/sample2.avm
[I 180302 22:32:06 Invoke:482] Used 0.033 Gas

-----------------------------------------------------------
Calling docs/source/example/sample2.py with arguments ['add', '1', '2']
Test deploy invoke successful
Used total of 39 operations
Result [{'type': 'Integer', 'value': 3}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
neo> build docs/source/example/sample2.py test 070202 02 False False mul -1 20000
Saved output to docs/source/example/sample2.avm
[I 180302 22:33:36 Invoke:482] Used 0.041 Gas

-----------------------------------------------------------
Calling docs/source/example/sample2.py with arguments ['mul', '-1', '20000']
Test deploy invoke successful
Used total of 53 operations
Result [{'type': 'Integer', 'value': -20000}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

这次好多了。接下来让我们做些更有用的尝试,我们将做一个简单的地址余额跟踪器。

from boa.interop.Neo.Storage import Get,Put,Delete,GetContext

def Main(operation, addr, value):


    if not is_valid_addr(addr):
        return False

    ctx = GetContext()

    if operation == 'add':
        balance = Get(ctx, addr)
        new_balance = balance + value
        Put(ctx, addr, new_balance)
        return new_balance

    elif operation == 'remove':
        balance = Get(ctx, addr)
        Put(ctx, addr, balance - value)
        return balance - value

    elif operation == 'balance':
        return Get(ctx, addr)

    return False

def is_valid_addr(addr):

    if len(addr) == 20:
        return True
    return False
复制代码

我们将使用 add 进行一个测试,给钱包里的一个地址添加一些值。你会注意到,当你在钱包里输入任意地址时会自动完成输入,这可能会产生误导。当通过 prompt 将地址发送到 SC 时,它会自动转换为 ByteArray 以便使用。 因此方法签名看起来像 070502 或 StringByteArrayInteger

我们使用 True 来表示使用智能合约的 Storage API。

neo> build docs/source/example/sample3.py test 070502 02 True False add AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy 3
Saved output to docs/source/example/sample3.avm
[I 180302 23:04:33 Invoke:482] Used 1.174 Gas

-----------------------------------------------------------
Calling docs/source/example/sample3.py with arguments ['add', 'AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy', '3']
Test deploy invoke successful
Used total of 106 operations
Result [{'type': 'Integer', 'value': 3}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

再次调用,将看到我们的测试调用保留了存储库中的值。

neo> build docs/source/example/sample3.py test 070502 02 True False add AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy 3
Saved output to docs/source/example/sample3.avm
[I 180302 23:04:33 Invoke:482] Used 1.174 Gas

-----------------------------------------------------------
Calling docs/source/example/sample3.py with arguments ['add', 'AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy', '3']
Test deploy invoke successful
Used total of 106 operations
Result [{'type': 'Integer', 'value': 6}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

现在除去一些值:

neo> build docs/source/example/sample3.py test 070502 02 True False remove AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy 2
Saved output to docs/source/example/sample3.avm
[I 180302 23:09:21 Invoke:482] Used 1.176 Gas

-----------------------------------------------------------
Calling docs/source/example/sample3.py with arguments ['remove', 'AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy', '2']
Test deploy invoke successful
Used total of 109 operations
Result [{'type': 'Integer', 'value': 4}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

你也可以为地址传入一个 ByteArray 对象,并测试是否 is_valid_addr 在任何事情发生前返回 False,这会被解析为 0:

neo> build docs/source/example/sample3.py test 070502 02 True False add bytearray(b'\x00\x01\x02\x03') 4
Saved output to docs/source/example/sample3.avm
[I 180302 23:12:43 Invoke:482] Used 0.041 Gas

-----------------------------------------------------------
Calling docs/source/example/sample3.py with arguments ['add', "bytearray(b'\\x00\\x01\\x02\\x03')", '4']
Test deploy invoke successful
Used total of 52 operations
Result [{'type': 'Integer', 'value': 0}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

请注意,以可读格式 (

AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy

) 发送地址与发送地址的脚本哈希是一样的。我们将通过获取余额进行尝试。由于智能合约期望第三个参数,我在最后添加了一个额外的 0 作为最后一个参数:

neo> build docs/source/example/sample3.py test 070502 02 True False balance bytearray(b'\x03\x19\xe0)\xb9%\x85w\x90\xe4\x17\x85\xbe\x9c\xce\xc6\xca\xb1\x98\x96') 0
Saved output to docs/source/example/sample3.avm
[I 180302 23:16:23 Invoke:482] Used 0.162 Gas

-----------------------------------------------------------
Calling docs/source/example/sample3.py with arguments ['balance', "bytearray(b'\\x03\\x19\\xe0)\\xb9%\\x85w\\x90\\xe4\\x17\\x85\\xbe\\x9c\\xce\\xc6\\xca\\xb1\\x98\\x96')", '0']
Test deploy invoke successful
Used total of 87 operations
Result [{'type': 'Integer', 'value': 4}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

导入智能合约

导入智能合约有点类似 build .. test 命令,但你不需要发送任何参数。命令格式为:

import contract path/to/sample2.avm {input_params} {return_type} {needs_storage} {needs_dynamic_invoke}

运行该命令后,如果一切正常系统会提示你添加合约相关的元数据。完成后你就可以选择在网络中实际部署将该合约。请注意部署合约将花费 GAS。

neo>
neo> import contract docs/source/example/sample2.avm 070202 02 False False
Please fill out the following contract details:
[Contract Name] > Sample Calculator
[Contract Version] > .01
[Contract Author] > Thomas Saunders
[Contract Email] > tom@cityofzion.io
[Contract Description] > A test calculator contract
Creating smart contract....
               Name: A test calculator contract
            Version: .01
             Author: tom@cityofzion.io
              Email: tom@cityofzion.io
        Description: A test calculator contract
      Needs Storage: False
Needs Dynamic Invoke: False
{
  "hash": "0x86d58778c8d29e03182f38369f0d97782d303cc0",
  "script": "5ec56b6a00527ac46a51527ac46a52527ac46a00c3036164649c640d006a51c36a52c3936c7566616a00c3037375629c640d006a51c36a52c3946c7566616a00c3036d756c9c640d006a51c36a52c3956c7566616a00c3036469769c640d006a51c36a52c3966c7566614f6c7566006c7566",
  "parameters": "070202",
  "returntype": "02"
}
Used 100.0 Gas

-------------------------------------------------------------------------------------------------------------------------------------
Test deploy invoke successful
Total operations executed: 11
Results:
[<neo.Core.State.ContractState.ContractState object at 0x11435d2e8>]
Deploy Invoke TX GAS cost: 90.0
Deploy Invoke TX Fee: 0.0
-------------------------------------------------------------------------------------------------------------------------------------

Enter your password to continue and deploy this contract
[password]>
复制代码

从这里开始,如果你确定要花费 GAS 来部署合约,输入密码:

Enter your password to continue and deploy this contract
[password]> ***********
[I 180302 23:46:23 Transaction:611] Verifying transaction: b'f8ad261d28bf4bc5544e47f9bc3fff85f85ee674f14162dac81dd56bf73cf0a3'
Relayed Tx: f8ad261d28bf4bc5544e47f9bc3fff85f85ee674f14162dac81dd56bf73cf0a3
neo>
复制代码

现在你已将合约部署到网络。 如果一切顺利,将很快部署。 要确定何时部署完成,在区块链中搜索 txid 或合约哈希。

neo> tx f8ad261d28bf4bc5544e47f9bc3fff85f85ee674f14162dac81dd56bf73cf0a3
{
  "txid": "0xf8ad261d28bf4bc5544e47f9bc3fff85f85ee674f14162dac81dd56bf73cf0a3",
  "type": "InvocationTransaction",
  "version": 1,
  "attributes": [],
  [ MORE Output Omitted ]

neo> contract 0x86d58778c8d29e03182f38369f0d97782d303cc0
{
    "version": 0,
    "code": {
        "hash": "0x86d58778c8d29e03182f38369f0d97782d303cc0",
        "script": "5ec56b6a00527ac46a51527ac46a52527ac46a00c3036164649c640d006a51c36a52c3936c7566616a00c3037375629c640d006a51c36a52c3946c7566616a00c3036d756c9c640d006a51c36a52c3956c7566616a00c3036469769c640d006a51c36a52c3966c7566614f6c7566006c7566",
        "parameters": "070202",
        "returntype": 2
    },
    "name": "A test calculator contract",
    "code_version": ".01",
    "author": "tom@cityofzion.io",
    "email": "tom@cityofzion.io",
    "description": "A test calculator contract",
    "properties": {
        "storage": false,
        "dynamic_invoke": false
    }
}

neo>
复制代码

现在你已经在网络上部署了合同,可以使用真正的 InvocationTransactions 与它进行交互。

测试调用合约

一旦部署了合约,你就无法再像使用 build .. test 命令那样进行交互,更改和构建,但最好是使用 testinvoke 来确定链上的工作。

现在我们已经部署了

Calculator Contract

,只要知道它的脚本哈希,就可以使用 testinvoke 命令与它进行交互。命令格式是 testinvoke {contract_hash} param1 param2 ..

neo> testinvoke 0x86d58778c8d29e03182f38369f0d97782d303cc0 add 1 2
Used 0.033 Gas

-------------------------------------------------------------------------------------------------------------------------------------
Test invoke successful
Total operations: 39
Results ['Integer: 3 ']
Invoke TX GAS cost: 0.0
Invoke TX fee: 0.0001
-------------------------------------------------------------------------------------------------------------------------------------

Enter your password to continue and invoke on the network

[password]>
复制代码

这个调用只在本地完成,只有在你输入密码后才能运行。如果你不希望在网络中调用,只需输入错误密码即可取消。这里我们取消调用,然后设置 config sc-events on 以确切地查看测试调用时发生的情况,然后将其发送到网络:

Enter your password to continue and invoke on the network

[password]> **
Incorrect password
neo>
neo> config sc-events on
Smart contract event logging is now enabled
neo>
neo> testinvoke 0x86d58778c8d29e03182f38369f0d97782d303cc0 add 1 2
[I 180303 07:38:58 EventHub:71] [test_mode][SmartContract.Execution.Success] [86d58778c8d29e03182f38369f0d97782d303cc0] [3]
Used 0.033 Gas

-------------------------------------------------------------------------------------------------------------------------------------
Test invoke successful
Total operations: 39
Results ['Integer: 3 ']
Invoke TX GAS cost: 0.0
Invoke TX fee: 0.0001
-------------------------------------------------------------------------------------------------------------------------------------

Enter your password to continue and invoke on the network

[password]> ***********
[I 180303 07:39:04 Transaction:611] Verifying transaction: b'e0f4251a83f7081fb6fd94ce884d12b0bb597c1c1b3f1a89f07db68e114f4fa2'
[I 180303 07:39:04 EventHub:89] [SmartContract.Verification.Success][433121] [4c896601a99d58e22c32dcadd24974ca24c10587] [tx e0f4251a83f7081fb6fd94ce884d12b0bb597c1c1b3f1a89f07db68e114f4fa2] [True]
Relayed Tx: e0f4251a83f7081fb6fd94ce884d12b0bb597c1c1b3f1a89f07db68e114f4fa2
neo>
neo> [I 180303 07:39:31 EventHub:89] [SmartContract.Execution.Success][433122] [86d58778c8d29e03182f38369f0d97782d303cc0] [tx e0f4251a83f7081fb6fd94ce884d12b0bb597c1c1b3f1a89f07db68e114f4fa2] [3]
neo>
复制代码

在这里请注意以下几点:

  1. 当使用 sc-events on 测试调用时,将看到
    SmartContract.Execution.Success
    事件,并且将看到该事件显示已在 test_mode 完成测试。
  2. 现在你将看到 SmartContract.Verification.Success 事件。 这说明 TX 已正确签名并将通过验证,因为它被传递到其他节点并最终将达成一致。
  3. 将 InvocationTransaction 发送到网络后,你将获得一个TX ID,可以用来查找调用。
  4. 在网络处理完 TX 之后,本地 VM 会运行你的调用,这次不在 test_mode 中,你将再次看到SmartContract.Execution.Success 事件。

Application vs. Verification

现在,你已经完成了合约的构建、测试和测试调用,你可能会问,这个验证步骤是什么,如何与之程序化的交互?让我们从之前一个跟踪余额的例子开始,限制仅有一个所有者地址可以对其执行操作。以下是智能合约代码:

from boa.interop.Neo.Runtime import GetTrigger,CheckWitness
from boa.interop.Neo.Storage import Get,Put,Delete,GetContext
from boa.interop.Neo.TriggerType import Application, Verification

OWNER = b'\x03\x19\xe0)\xb9%\x85w\x90\xe4\x17\x85\xbe\x9c\xce\xc6\xca\xb1\x98\x96'

def Main(operation, addr, value):

    print("Running Sample v4")
    trigger = GetTrigger()

    # This determines that the SC is runnning in Verification mode
    # This determines whether the TX will be relayed to the rest of the network
    # The `Verification` portion of SC is *read-only*, so calls to `Storage.Put` will fail.
    # You can, however, use `Storage.Get`
    if trigger == Verification():

        print("Running Verification!")

        # This routine is: if the invoker ( or the Address that signed the contract ) is not OWNER,
        # Then we return False, and the TX will not be relayed to the network
        # Otherwise, we know the owner address signed the TX and return True
        is_owner = CheckWitness(OWNER)

        if is_owner:
            print("Is Owner!")
            return True

        print("Not Owner")

        return False

    elif trigger == Application():

        print("Running Application!")

          if not is_valid_addr(addr):
              print("Not Valid Address")
              return False

          ctx = GetContext()

          if operation == 'add':
              balance = Get(ctx, addr)
              new_balance = balance + value
              Put(ctx, addr, new_balance)
              return new_balance

          elif operation == 'remove':
              balance = Get(ctx, addr)
              Put(ctx, addr, balance - value)
              return balance - value

          elif operation == 'balance':
              return Get(ctx, addr)

          return False

    return False


def is_valid_addr(addr):

  if len(addr) == 20:
      return True
  return False
复制代码

OWNER 就是我们之前用过的ByteArray, 是我们使用的钱包里的地址。上例将使用boa.interop.Neo.Runtime.CheckWitness 方法来验证签名 InvocationTransaction 的钱包是否与 OWNER 的一样。首先,构建合约并测试其是否正常执行。我们将打开 sc-events以便准确地看到执行过程。

neo>
neo> build docs/source/neo/example/sample4.py test 070202 02 True False add AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy 7
Saved output to docs/source/neo/example/sample4.avm
[I 180303 08:25:12 EventHub:71] [test_mode][SmartContract.Contract.Create] [562d6c29209dc96432c6868621fe489cedd05222] ['{\n    "version": 0,\n    "code": {\n        "hash": "0x562d6c29209dc96432c6868621fe489cedd05222",\n        "script": "0122c56b6a00527ac46a51527ac46a52527ac4140319e029b925857790e41785be9ccec6cab198966a53527ac41152756e6e696e672053616d706c65207634680f4e656f2e52756e74696d652e4c6f6768164e656f2e52756e74696d652e47657454726967676572616a54527ac46a54c301009c6492001552756e6e696e6720566572696669636174696f6e21680f4e656f2e52756e74696d652e4c6f676a53c368184e656f2e52756e74696d652e436865636b5769746e657373616a55527ac46a55c3642200094973204f776e657221680f4e656f2e52756e74696d652e4c6f67516c756661094e6f74204f776e6572680f4e656f2e52756e74696d652e4c6f67006c7566616a54c301109c6454011452756e6e696e67204170706c69636174696f6e21680f4e656f2e52756e74696d652e4c6f676a51c3652d01632a00114e6f742056616c69642041646472657373680f4e656f2e52756e74696d652e4c6f67006c75666168164e656f2e53746f726167652e476574436f6e74657874616a56527ac46a00c3036164649c6450006a56c36a51c37c680f4e656f2e53746f726167652e476574616a57527ac46a57c36a52c3936a58527ac46a56c36a51c36a58c35272680f4e656f2e53746f726167652e507574616a58c36c7566616a00c30672656d6f76659c644c006a56c36a51c37c680f4e656f2e53746f726167652e476574616a57527ac46a56c36a51c36a57c36a52c3945272680f4e656f2e53746f726167652e507574616a57c36a52c3946c7566616a00c30762616c616e63659c641f006a56c36a51c37c680f4e656f2e53746f726167652e476574616c756661006c756656c56b6a00527ac46a00c3c001149c640700516c756661006c7566",\n        "parameters": "070202",\n        "returntype": 2\n    },\n    "name": "test",\n    "code_version": "test",\n    "author": "test",\n    "email": "test",\n    "description": "test",\n    "properties": {\n        "storage": true,\n        "dynamic_invoke": false\n    }\n}']
[I 180303 08:25:12 EventHub:71] [test_mode][SmartContract.Runtime.Log] [562d6c29209dc96432c6868621fe489cedd05222] [b'Running Sample v4']
[I 180303 08:25:12 EventHub:71] [test_mode][SmartContract.Runtime.Log] [562d6c29209dc96432c6868621fe489cedd05222] [b'Running Application!']
[I 180303 08:25:12 EventHub:71] [test_mode][SmartContract.Storage.Get] [562d6c29209dc96432c6868621fe489cedd05222] ['AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy -> 0']
[I 180303 08:25:12 EventHub:71] [test_mode][SmartContract.Storage.Put] [562d6c29209dc96432c6868621fe489cedd05222] ['AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy -> 7']
[I 180303 08:25:12 EventHub:71] [test_mode][SmartContract.Execution.Success] [562d6c29209dc96432c6868621fe489cedd05222] [7]
[I 180303 08:25:12 Invoke:482] Used 1.191 Gas

-----------------------------------------------------------
Calling docs/source/neo/example/sample4.py with arguments ['add', 'AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy', '7']
Test deploy invoke successful
Used total of 136 operations
Result [{'type': 'Integer', 'value': 7}]
Invoke TX gas cost: 0.0001
-------------------------------------------------------------

neo>
复制代码

到这里执行过程跟之前差不多。我们添加了一些新的 print 语句,通过这些语句能看到智能合约的 Verification部分从未执行。另外,合约中的 SmartContract.Storage.\* 事件对调试非常有用。目前,为了与智能合约的Verification 阶段交互,你需要部署并使用 testinvoke。假设你已经构建并导入合约,将得到如下结果:

neo> contract 2e80ee491a0a54c9bbb0f791672050f9ab367767
{
    "version": 0,
    "code": {
        "hash": "0x2e80ee491a0a54c9bbb0f791672050f9ab367767",
        "script": "0123c56b6a00527ac46a51527ac46a52527ac4140319e029b925857790e41785be9ccec6cab198966a53527ac41152756e6e696e672053616d706c65207634680f4e656f2e52756e74696d652e4c6f6768164e656f2e52756e74696d652e47657454726967676572616a54527ac46a54c3680f4e656f2e52756e74696d652e4c6f676a54c301009c6492001552756e6e696e6720566572696669636174696f6e21680f4e656f2e52756e74696d652e4c6f676a53c368184e656f2e52756e74696d652e436865636b5769746e657373616a55527ac46a55c3642200094973204f776e657221680f4e656f2e52756e74696d652e4c6f67516c756661094e6f74204f776e6572680f4e656f2e52756e74696d652e4c6f67006c7566616a54c301109c6454011452756e6e696e67204170706c69636174696f6e21680f4e656f2e52756e74696d652e4c6f676a51c3652d01632a00114e6f742056616c69642041646472657373680f4e656f2e52756e74696d652e4c6f67006c75666168164e656f2e53746f726167652e476574436f6e74657874616a56527ac46a00c3036164649c6450006a56c36a51c37c680f4e656f2e53746f726167652e476574616a57527ac46a57c36a52c3936a58527ac46a56c36a51c36a58c35272680f4e656f2e53746f726167652e507574616a58c36c7566616a00c30672656d6f76659c644c006a56c36a51c37c680f4e656f2e53746f726167652e476574616a57527ac46a56c36a51c36a57c36a52c3945272680f4e656f2e53746f726167652e507574616a57c36a52c3946c7566616a00c30762616c616e63659c641f006a56c36a51c37c680f4e656f2e53746f726167652e476574616c756661006c756656c56b6a00527ac46a00c3c001149c640700516c756661006c7566",
        "parameters": "070202",
        "returntype": 2
    },
    "name": "test",
    "code_version": "test",
    "author": "test",
    "email": "test",
    "description": "test",
    "properties": {
        "storage": true,
        "dynamic_invoke": false
    }
}

neo>
复制代码

再次测试调用一遍。

neo> testinvoke 0x2e80ee491a0a54c9bbb0f791672050f9ab367767 add AMUUgxnLhGxNSATinNp8gKmndqM1BxDZHR 42
[I 180303 09:08:14 EventHub:71] [test_mode][SmartContract.Runtime.Log] [2e80ee491a0a54c9bbb0f791672050f9ab367767] [b'Running Sample v4']
[I 180303 09:08:14 EventHub:71] [test_mode][SmartContract.Runtime.Log] [2e80ee491a0a54c9bbb0f791672050f9ab367767] [b'\x10']
[I 180303 09:08:14 EventHub:71] [test_mode][SmartContract.Runtime.Log] [2e80ee491a0a54c9bbb0f791672050f9ab367767] [b'Running Application!']
[I 180303 09:08:14 EventHub:71] [test_mode][SmartContract.Storage.Get] [2e80ee491a0a54c9bbb0f791672050f9ab367767] ['AMUUgxnLhGxNSATinNp8gKmndqM1BxDZHR -> 0']
[I 180303 09:08:14 EventHub:71] [test_mode][SmartContract.Storage.Put] [2e80ee491a0a54c9bbb0f791672050f9ab367767] ['AMUUgxnLhGxNSATinNp8gKmndqM1BxDZHR -> 42']
[I 180303 09:08:14 EventHub:71] [test_mode][SmartContract.Execution.Success] [2e80ee491a0a54c9bbb0f791672050f9ab367767] [42]
Used 1.194 Gas

-------------------------------------------------------------------------------------------------------------------------------------
Test invoke successful
Total operations: 140
Results ['Integer: 42 ']
Invoke TX GAS cost: 0.0
Invoke TX fee: 0.0001
-------------------------------------------------------------------------------------------------------------------------------------

Enter your password to continue and invoke on the network

[password]>
复制代码

这里还是没有看到任何验证事件。一旦我们输入密码将其转发到网络,将看到如下结果:

[password]> ***********
[I 180303 08:36:52 Transaction:611] Verifying transaction: b'0fd755e847a5ce54f9894c0c0bbf9303730ac28d8aeacdaddb2f912a2a3fcd40'
[I 180303 08:35:38 EventHub:71] [test_mode][SmartContract.Runtime.Log] [562d6c29209dc96432c6868621fe489cedd05222] [b'Running Sample v4']
[I 180303 08:35:38 EventHub:71] [test_mode][SmartContract.Runtime.Log] [562d6c29209dc96432c6868621fe489cedd05222] [b'Running Verification!']
[I 180303 08:35:38 EventHub:71] [test_mode][SmartContract.Runtime.Log] [562d6c29209dc96432c6868621fe489cedd05222] [b'Is Owner!']
[I 180303 08:36:52 EventHub:89] [SmartContract.Verification.Success][433331] [f64d628af19f53a6b8226a44c93182eff6fcb222] [tx 0fd755e847a5ce54f9894c0c0bbf9303730ac28d8aeacdaddb2f912a2a3fcd40] [True]
Relayed Tx: 0fd755e847a5ce54f9894c0c0bbf9303730ac28d8aeacdaddb2f912a2a3fcd40
neo>
neo>
neo>
neo> [I 180303 08:37:29 EventHub:89] [SmartContract.Runtime.Log][433333] [562d6c29209dc96432c6868621fe489cedd05222] [tx 0fd755e847a5ce54f9894c0c0bbf9303730ac28d8aeacdaddb2f912a2a3fcd40] [b'Running Sample v4']
[I 180303 08:37:29 EventHub:89] [SmartContract.Runtime.Log][433333] [562d6c29209dc96432c6868621fe489cedd05222] [tx 0fd755e847a5ce54f9894c0c0bbf9303730ac28d8aeacdaddb2f912a2a3fcd40] [b'Running Application!']
[I 180303 08:37:29 EventHub:89] [SmartContract.Storage.Get][433333] [562d6c29209dc96432c6868621fe489cedd05222] [tx 0fd755e847a5ce54f9894c0c0bbf9303730ac28d8aeacdaddb2f912a2a3fcd40] ['AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy -> 0']
[I 180303 08:37:29 EventHub:89] [SmartContract.Storage.Put][433333] [562d6c29209dc96432c6868621fe489cedd05222] [tx 0fd755e847a5ce54f9894c0c0bbf9303730ac28d8aeacdaddb2f912a2a3fcd40] ['AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy -> 17']
[I 180303 08:37:29 EventHub:89] [SmartContract.Execution.Success][433333] [562d6c29209dc96432c6868621fe489cedd05222] [tx 0fd755e847a5ce54f9894c0c0bbf9303730ac28d8aeacdaddb2f912a2a3fcd40] [17]
复制代码

和预期的一样。现在我们将打开不同的钱包并尝试调用相同的东西。

NEO-Python 数据类型

在使用 NEO-python 或 NEO 区块链时,你需要熟悉一些数据类型,这可以帮助你识别在系统的各部分以不同格式出现的这些数据类型,以及正确使用它们。本章将简要描述每种数据类型及其一般用法。

请注意,这些数据类型是在 neocore 项目中实现的,但在 neo-python 中用的很多。

KeyPair / 地址

NEO 中的地址实际上是一对公钥/私钥。当创建一个钱包时,会根据钱包密码创建一个32位的私钥,这个私钥只能你自己知道和保存。此私钥与公钥配对,用于标识网络上的地址。只有签名交易时才需要用到私钥。

如果在 prompt 中打开一个钱包,运行 wallet 命令,在输出的信息中可以查看到钱包的公钥,例如:

"public_keys": [
  {
      "Address": "AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy",
      "Public Key": "036d4de3e05057df18b82718d635795cb67d9c19001e998d76c77b86081be5f160"
  }
],
复制代码

上例中 Public Key 以压缩格式表示 ECDSA 曲线上的 x 和 y 坐标,特别是 *SECP256R1 曲线。我们通过一系列步骤推导出 Address

  • 创建一个以 21 开头 ac 结尾的公钥 UInt160ScriptHash

    >>> from neocore.Cryptography.Crypto import Crypto
    >>> pubkey_hex = '036d4de3e05057df18b82718d635795cb67d9c19001e998d76c77b86081be5f160'
    >>> pubkey_hex_for_addr = '21' + pubkey_hex + 'ac'
    >>> pubkey_hex_for_addr
    '21036d4de3e05057df18b82718d635795cb67d9c19001e998d76c77b86081be5f160ac'
    >>> script_hash = Crypto.ToScriptHash(pubkey_hex_for_addr, unhex=True)
    >>> script_hash
    <neocore.UInt160.UInt160 object at 0x10d33e908>
    >>> script_hash.Data
    bytearray(b'\x03\x19\xe0)\xb9%\x85w\x90\xe4\x17\x85\xbe\x9c\xce\xc6\xca\xb1\x98\x96')
    复制代码
  • 接着,从以下脚本散列创建一个地址:

    >>> addr = Crypto.ToAddress(script_hash)
    >>> addr
    'AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy'
    >>>
    复制代码

如果对 KeyPairUInt160、或 Crypto 包的实现细节感兴趣,可以查看 neocore repository

UInt256

UInt256 表示 32位 的散列。它通常是一个 Transaction 对象或 Block的散列。 它一般显示为 64 字符的字符串或带有 0x 十六进制说明符的 66 个字符的字符串。以下是一个与 UInt256 交互的例子。

>>>
>>> from neocore.UInt256 import UInt256
>>>
>>> hash = "0x99e2be05956027b884cbf11cddbf9d2e5a8fb97ab18d5cde44d5ae2d4c980d18"
>>>
>>> uint = UInt256.ParseString(hash)
>>> uint
<neocore.UInt256.UInt256 object at 0x10cb9b240>
>>> uint.ToString()
'99e2be05956027b884cbf11cddbf9d2e5a8fb97ab18d5cde44d5ae2d4c980d18'
>>> uint.To0xString()
'0x99e2be05956027b884cbf11cddbf9d2e5a8fb97ab18d5cde44d5ae2d4c980d18'
>>> uint.Data
bytearray(b"\x18\r\x98L-\xae\xd5D\xde\\\x8d\xb1z\xb9\x8fZ.\x9d\xbf\xdd\x1c\xf1\xcb\x84\xb8\'`\x95\x05\xbe\xe2\x99")
>>>
>>> uint.ToBytes()
b'99e2be05956027b884cbf11cddbf9d2e5a8fb97ab18d5cde44d5ae2d4c980d18'
>>>
>>> data = uint.Data
>>> data
bytearray(b"\x18\r\x98L-\xae\xd5D\xde\\\x8d\xb1z\xb9\x8fZ.\x9d\xbf\xdd\x1c\xf1\xcb\x84\xb8\'`\x95\x05\xbe\xe2\x99")
>>>
>>> copy = UInt256(data=data)
>>>
>>> copy.To0xString()
'0x99e2be05956027b884cbf11cddbf9d2e5a8fb97ab18d5cde44d5ae2d4c980d18'
>>>
复制代码

需要注意的是,我们通常看到的是字符串,或者 UInt256 的 0x 字符串版本。

UInt160

UInt160 表示 20 位的散列,也可称为一个 ScriptHash。它用于显示 NEO 中的 Address 对象是普通地址还是合约地址。以下是一个与 UInt160交互的例子。

>>>
>>> data = bytearray(b'\x03\x19\xe0)\xb9%\x85w\x90\xe4\x17\x85\xbe\x9c\xce\xc6\xca\xb1\x98\x96')
>>>
>>> from neocore.UInt160 import UInt160
>>>
>>> new_sh = UInt160(data=data)
>>> new_sh
<neocore.UInt160.UInt160 object at 0x10d3460b8>
>>> new_sh.Data
bytearray(b'\x03\x19\xe0)\xb9%\x85w\x90\xe4\x17\x85\xbe\x9c\xce\xc6\xca\xb1\x98\x96')
>>>
>>> new_sh.To0xString()
'0x9698b1cac6ce9cbe8517e490778525b929e01903'
>>>
>>> sh_again = UInt160.ParseString( new_sh.To0xString() )
>>> sh_again.Data
bytearray(b'\x03\x19\xe0)\xb9%\x85w\x90\xe4\x17\x85\xbe\x9c\xce\xc6\xca\xb1\x98\x96')
>>>
>>> Crypto.ToAddress( sh_again)
'AG4GfwjnvydAZodm4xEDivguCtjCFzLcJy'
>>>
复制代码

Fixed8

Fixed8 用于表示整数格式的 8 位小数。以下是一个使用 Fixed8的基本示例。

>>> from neocore.Fixed8 import Fixed8
>>>
>>> three = Fixed8.FromDecimal(3)
>>> three.value
300000000
>>> three.ToInt()
3
>>> three.ToString()
'3.0'
>>>
>>>
>>> point5 = Fixed8(50000000)
>>> point5.ToString()
'0.5'
>>>
>>> point5 + three
<neocore.Fixed8.Fixed8 object at 0x10cd48ba8>
>>> threepoint5 = point5 + three
>>> threepoint5.value
350000000
>>>
>>> threepoint5.ToString()
'3.5'
>>>
>>>
>>> threepoint5 * 2
Traceback (most recent call last):
File "<input>", line 1, in <module>
  threepoint5 * 2
File "/Users/thomassaunders/Workshop/neo-python/venv/lib/python3.6/site-packages/neocore/Fixed8.py", line 85, in __mul__
  return Fixed8(self.value * other.value)
AttributeError: 'int' object has no attribute 'value'
>>>
>>>
复制代码

以下是一些总结:

  • 如果你想创建一个 Fixed8 并且你有一个小数,最简单的做法是使用 Fixed8.FromDecimal 方法。
  • 假设每个操作数是一个 Fixed8,你可以对 Fixed8 对象进行数学运算。
  • 在一个 Fixed8 和另一种类型的数字之间进行数学运算会引发错误。
  • 你可以通过访问 value 属性来访问 Fixed8 对象的完整值。

BigInteger

BigInteger 用于存储和对任意大小的整数进行运算,包括负数和正数。这对将数字序列化为字节和返回非常有用。以下是 BigInteger 的一些示例用法。

>>> from neocore.BigInteger import BigInteger
>>>
>>> bi = BigInteger(10000)
>>>
>>> bi.ToByteArray()
b"\x10'"
>>>
>>> bi2 = BigInteger.FromBytes( bi.ToByteArray() )
>>> bi2
10000
>>>
>>> bi3 = BigInteger(-3)
>>>
>>> bi4 = bi2 * bi3
>>> bi4
-30000
>>>
>>> bi4 += 100000
>>> bi4
70000
>>> bi4.ToByteArray()
b'p\x11\x01'
>>>
复制代码

BigInteger 实现中需要注意的一点是它与 Fixed8 不同,因此你可以在 BigInteger 和普通整数之间进行数学运算。

ContractParameterTypes

以下是创建和调用智能合约中用到的 ContractParameterTypes。

描述

neo.Wallets 中的合约参数

用法:

从 neo.SmartContract.ContractParameterType 导入 ContractParameterType

class neo.SmartContract.ContractParameterType.ContractParameterType

Contract Parameter Types are used to denote different types of objects used in the VM

Signature

00

Boolean

01

Integer

02

Hash160

03

Hash256

04

ByteArray

05

PublicKey

06

String

07

Array

10

InteropInterface

F0

Void

FF

neo.SmartContract.ContractParameterType.``ToName (param_type)

根据其值 param_type 获取 ContractParameterType 的名称。

测试

您可以使用此命令开始测试:

make test
复制代码

使用此命令运行样式检查:

make lint
复制代码

使用此命令运行 neo-python 测试

python -m unittest discover neo
复制代码

使用此命令运行 neo-boa 项目的测试:

python -m unittest discover boa_test
复制代码

如果要添加测试或更改功能,可以只运行一个测试:

python -m unittest neo/test_settings.py复制代码

测试装置指南

以下指南与所有依赖于 BlockChainFixtureTestCaseWalletFixtureTestCaseunit-tests 相关。这种测试依赖于 neo-python-privnet-unittest 映像生成的装置。该映像位于这里

  • 直接依赖 BlockChainFixtureTestCase 的测试是使用 BC 钱包(neo-test-bc.walletneo-test2-bc.wallet)里的地址之间的交易生成的。有时在少数测试中也使用 neo-test-coz.wallet 里的默认地址。COZ 钱包很特别,它是原始的 COZ 私网钱包,拥有 100000000 NEO。
  • 直接依赖 WalletFixtureTestCase 的测试是使用 W 钱包 (neo-test1-w.wallet, neo-test2-w.wallet, neo-test3-w.wallet) 里的地址之间的交易生成的。

    下图可以更好的说明测试 BC 和 W:

向私网测试装置新增测试时请遵循以下方针,以便最小化单元测试间的依赖:

  • 从便于维护的角度来说,尽量不要在测试中直接使用 neo-test-coz.wallet,因为该钱包是其它测试钱包的 NEO 和 GAS的来源,会经常被更改。如果测试中使用了neo-test-coz.wallet,你将随时需要进行更新。
  • 尽量使用 privnet fixture 中现成的交易创建新测试。
  • 仅在必要时才向 privnet fixture 添加新交易,因为这会有破坏现有测试的风险。
  • 如果想要隔离你的测试,请创建你自己的通证。
  • 只有在绝对必要的情况下,才能创造新的钱包。

上述最后三条要求更新测试装置,操作如下:

  1. 拉取最新 neo-python-privnet-unittest image:

    docker pull cityofzion/neo-python-privnet-unittest:v0.0.xx
    复制代码
  2. 运行 image:

    docker run --rm -d --name neo-privnet-unittest -p 20333-20336:20333-20336/tcp -p 30333-30336:30333-30336/tcp dautt/neo-privnet-unittest:v0.0.xx``
    复制代码
  3. 清除当前 unittest 链:

    rm -rf ~/.neopython/Chains/unittest
    rm -rf ~/.neopython/Chains/unittest_notif
    复制代码
  4. 激活你的虚拟环境:

    source venv/bin/activate
    复制代码
  5. 启动 NEO 节点:

    python prompt.py -u
    复制代码
  6. 使用以下钱包生成交易:

    neo-test-coz.wallet     (pwd = coz)
    neo-test1-bc.wallet     (pwd = 1234567890)
    neo-test2-bc.wallet     (pwd = 1234567890)
    neo-test1-w.wallet      (pwd = 1234567890)
    neo-test2-w.wallet      (pwd = 1234567890)
    neo-test3-w.wallet      (pwd = 1234567890)
    复制代码

    (要添加直接依赖 BlockchainFixtureTestCase 的新测试,请使用 -bc.wallet 类型的钱包。要添加直接依赖 WalletFixtureTestCase 的新测试,请使用 -w.wallet 类型的钱包)

  7. 如果你需要创建一个新智能合约,考虑使用这里现有的合约:

    fixtures/UnitTest-SM.zip
    复制代码

    (如果以上 zip 包中没有新合约的源代码,请添加。)

  8. 如果你已经在 neo-python-privnet-unittest 映像上部署了一个新合约,请在指定合约名称时使用 test 为前缀,这样可以使用以下命令查找出所有部署在映像上的合约:

    contract search test
    复制代码
  9. 当你对新的单元测试满意后,保存测试,然后重启 docker 映像并重新部署你的测试。然后通过增加版本号 (xx+1) 创建一个新的映像:

    docker commit  neo-privnet-unittest dautt/neo-privnet-unittest:v0.0.xx+1
    复制代码

    这样做的原因是我们需要使映像尽可能小。你的映像文件可能在不经意间积累了几天或几周的新块,例如,在分阶段执行新测试时,这会不必要地增加映像的大小。我们的测试装置在构建系统中被重置和提取20多次,所以任何尺寸的增加都会增加延迟 20倍或更多。

  10. 通过增加数字后缀创建测试装置 (x+1):

    notif_fixtures_vx+1.tar.gz
    fixtures_vx+1.tar.gz
    复制代码
  11. 在如下文件的静态类变量中更新装置名称:

    neo.Utils.BlockchainFixtureTestCase.py
    neo.api.REST.test_rest_api.py
    复制代码
  12. 创建一个新的PR,链接到新的映像和新建的测试装置。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值