neo-thinsdk-cs 之 thinWallet 接入私链
序
2017年底刚开始接触区块链,目前在被 NEO 折磨。
一开始被官方文档和 NEO-GUI 搞得体无完肤(尤其是传说中的 F12),也找了各种调试工具用来搞 NeoContract,然并卵。
直到有一天发现了 NewEconoLab ,在它的 github 上有一个项目 neo-thinsdk-cs ,用 C# 实现了一个轻钱包 —— thinWallet 。
这个轻钱包只做了一件事情,就是做交易,它把整个交易的流程都体现出来了,给开发者展示了明确的可视化交易拼接流程(包括Attribute、Inputs、Outputs、Script、Witness等,我曾经用 VS F11 一行一行的调试跟踪过它的源代码,所有的这些交易必须的数据都组装好,放到一个数据结构里,非常清晰)。
最重要的是分解了 UTXO 和 使得 Witness 变得可处理,以达成 NEO-GUI 达不到的操作。
此外,目前的 thinWallet ,默认接入到 Testnet,还不支持直接接入 Mainnet ,也不支持接入到 NEO 私有链 ,我们都知道 Testnet 上的 Gas 十分珍贵,需要去申请才能有,而我们部署合约,动不动就消耗 990 Gas 。而在自己的私有链上 Gas 就是大白菜了,随便乱玩都可以。
所以为了方便部署合约调试,今天的任务是将 thinWallet 接入到私有链中。
1、环境准备
- 基础要求:四台 Debian 9(其他发行版随你喜欢,单击此处查看支持的机器,建议创建一台虚拟机配好环境然后直接克隆,别忘了强制刷新 MAC 地址,要不然 IP 冲突) ,一台 Windows 10 ,.net core sdk,visual studio 2017 community,.net framework 4.7 SDK,NEO-CLI,搭私链跑共识,建钱包,拿到一亿的 NEO 币和至少 1000 个 Gas (相当于你的区块高度超过 125,这至少需要耗费31分钟),这些东西文档里都有,此处不再赘述。
- 克隆项目:NeoBlock-Mongo-Storage,NEO_Block_API,neo-thinsdk-cs,三个项目都是直接用 master 分支就可以了。
这里解释一下克隆下来的三个项目是什么样的关系,三个都是 .net 项目
- neo-thinsdk-cs 是一整个解决方案,用 vs 2017 打开,里面有一个 WPF 项目是 thinWallet ,把它设置为启动项目,直接 CTRL+F5 就可以运行了,当然默认是接入 Testnet 。项目里面有一个《NEO智能合约开发(一)不可能完成的任务.docx》,好好看看。运行结果如下:
这里我们按第二个按钮 ThinWallet Test 就好了,进去是这样的界面:
- 我们可以看到右上角在调用一个 API for UTXO,这是一个 webapi ,由 NEO_Block_API 提供,大家可以看到这地方默认就是 Testnet ,区块高度也是 Testnet 的高度,切换网络那个按钮还没写功能。总共五个大面板都很好理解,第一个登录方式,第二个 api 信息,第三个账户信息(你的 utxo 将会出现在这里),第四个交易三要素,第五个决定是什么类型的交易。
- NEO_Block_API 里面用到了 mongodb ,所以我们的第三个项目 NeoBlock-Mongo-Storage 作为一个调度任务跑着,用来实时收集链上的交易信息存入 mongodb 中,供 NEO_Block_API 使用。
- 顺便说明一下, vs 2017 很人性化,你直接 CTRL+F5 或者生成解决方案 ,会帮你还原依赖库的,看见黄色感叹号不要慌。如果你无法打开项目或者无法还原或者出现任何异常,相信我,一定是你的打开方式不对,或者你改了里面的东西改错了,或者环境有问题,三个项目是绝对没有错误的,我验证了好几遍了,请回头好好检查自己的环境等基础设施吧。
配置 NEO_Block_API
到这里,基础环境全部搭建完毕,thinWallet 也可以在 Testnet 上正常使用了,那么接下来就需要开始接入私链了。首先我们要把 NEO_Block_API 的源代码增改一下,让它支持接入私链。
我们先看一下源代码长什么样,还是 vs 2017 打开:
我已经生成过解决方案了,所以依赖库都没问题了,结构非常简单。Controllers 是控制器,就是一些 web 接口在这里实现,lib 就是要用到的工具,RPC 定义了一些数据结构,再加一个 Startup.cs 和 Program.cs 文件,典型的 .net core web 项目。
我们可以看到里边有一个 mongodbsettings.json 文件带了个黄色警告标志,说明这个文件丢失了,因为在 .gitignore 里边把它写进去了,所以 git 就把它忽略了,问题不大,我们删掉它重新建立一个一样名字的文件就好了,我们可以找一下在哪里用到了这个文件,你可以 CTRL+SHIFT+F 全局查找,最终发现在 mongoHelper.cs 里面有这么一段:
public mongoHelper() {
var config = new ConfigurationBuilder()
.AddInMemoryCollection() //将配置文件的数据加载到内存中
.SetBasePath(System.IO.Directory.GetCurrentDirectory()) //指定配置文件所在的目录
.AddJsonFile("mongodbsettings.json", optional: true, reloadOnChange: true) //指定加载的配置文件
.Build(); //编译成对象
mongodbConnStr_testnet = config["mongodbConnStr_testnet"];
mongodbDatabase_testnet = config["mongodbDatabase_testnet"];
neoCliJsonRPCUrl_testnet = config["neoCliJsonRPCUrl_testnet"];
mongodbConnStr_mainnet = config["mongodbConnStr_mainnet"];
mongodbDatabase_mainnet = config["mongodbDatabase_mainnet"];
neoCliJsonRPCUrl_mainnet = config["neoCliJsonRPCUrl_mainnet"];
mongodbConnStr_NeonOnline = config["mongodbConnStr_NeonOnline"];
mongodbDatabase_NeonOnline = config["mongodbDatabase_NeonOnline"];
}
那么事情就简单了,我们看到这里面一共有 8 个字段要用到,给它就是了,我们在新建的 mongodbsettings.json 文件写上这些内容:
{
"mongodbConnStr_testnet": "mongodb://127.0.0.1:27017",
"mongodbDatabase_testnet": "testnet",
"neoCliJsonRPCUrl_testnet": "http://47.96.168.8:20332",
"mongodbConnStr_mainnet": "mongodb://127.0.0.1:27017",
"mongodbDatabase_mainnet": "mainnet",
"neoCliJsonRPCUrl_mainnet": "",
"mongodbConnStr_privatenet": "mongodb://127.0.0.1:27017",
"mongodbDatabase_privatenet": "privatenet",
"neoCliJsonRPCUrl_privatenet": "http://192.168.1.135:20332",
"mongodbConnStr_NeonOnline": "mongodb://127.0.0.1:27017",
"mongodbDatabase_NeonOnline": "neononline"
}
mainnet 一时半会儿肯定用不着,我懒得写了,testnet 还是保留原样,加一个 privatenet 。注意 neoCliJsonRPCUrl_privatenet 这个字段的值是 http://192.168.1.135:20332 ,192.168.1.135 这台机器是四台共识机中的一台,开了 RPC ,就是:dotnet neo-cli.dll /rpc
。我们可以用 postman 来测试一下,随便找个接口,路由是: http://192.168.1.135:20332
就用 getbestblockhash 吧:
先看一下我跑的四个共识节点,我的共识跑了挺久了,现在高度接近 7000 :
接下来我们看一下 rpc 接口调用结果:
如果你无法得到类似的结果,那还是得去多看看文档,跑跑共识。现在我们得到了正确结果,说明私链的 RPC 完全没有问题。此刻我们得到了第一个需要的接口就是刚才打开的 thinWallet 右上角 RPC Node 所需要的接口地址:http://192.168.1.135:20332
。
mongodb 你得装一下(随你 linux 还是 windows,推荐 linux),为了演示方便,我装在了 windows 里面。装完之后就是 C:\Program Files\MongoDB\Server\3.6\bin 里面有一堆东西。其中 mongod.exe 这个是服务端,因为国际惯例有个 d 。别急着双击它,你得先建立两个文件夹:C:\data\db,不然双击就闪退了,其实你可以用 cmd 来打开,可以看到异常信息。现在你可以双击它了,结果就是一个控制台,打印了一堆日志,你可以用 mongodb 自带的可视化客户端连接一下。这边注意,我们不需要事先创建数据库。
下面开始创建 webapi 接口,也就是新建一个控制器,在项目的 Controllers 文件夹下,新建一个类,取名:PrivatenetController.cs ,然后把随便 TestnetController.cs 或者 MainnetController.cs 里面的内容全部复制粘贴到我们的 PrivatenetController.cs 中,改一下两个地方,变成 privatenet :
[Route("api/[controller]")]
public class PrivatenetController : Controller
{
Api api = new Api("privatenet");
接着 mongoHelper.cs 里面改一下,把私链的东西加进去:
public string mongodbConnStr_testnet = string.Empty;
public string mongodbDatabase_testnet = string.Empty;
public string neoCliJsonRPCUrl_testnet = string.Empty;
public string mongodbConnStr_mainnet = string.Empty;
public string mongodbDatabase_mainnet = string.Empty;
public string neoCliJsonRPCUrl_mainnet = string.Empty;
public string mongodbConnStr_privatenet = string.Empty;
public string mongodbDatabase_privatenet = string.Empty;
public string neoCliJsonRPCUrl_privatenet = string.Empty;
public string mongodbConnStr_NeonOnline = string.Empty;
public string mongodbDatabase_NeonOnline = string.Empty;
public mongoHelper() {
var config = new ConfigurationBuilder()
.AddInMemoryCollection() //将配置文件的数据加载到内存中
.SetBasePath(System.IO.Directory.GetCurrentDirectory()) //指定配置文件所在的目录
.AddJsonFile("mongodbsettings.json", optional: true, reloadOnChange: true) //指定加载的配置文件
.Build(); //编译成对象
mongodbConnStr_testnet = config["mongodbConnStr_testnet"];
mongodbDatabase_testnet = config["mongodbDatabase_testnet"];
neoCliJsonRPCUrl_testnet = config["neoCliJsonRPCUrl_testnet"];
mongodbConnStr_mainnet = config["mongodbConnStr_mainnet"];
mongodbDatabase_mainnet = config["mongodbDatabase_mainnet"];
neoCliJsonRPCUrl_mainnet = config["neoCliJsonRPCUrl_mainnet"];
mongodbConnStr_privatenet = config["mongodbConnStr_privatenet"];
mongodbDatabase_privatenet = config["mongodbDatabase_privatenet"];
neoCliJsonRPCUrl_privatenet = config["neoCliJsonRPCUrl_privatenet"];
mongodbConnStr_NeonOnline = config["mongodbConnStr_NeonOnline"];
mongodbDatabase_NeonOnline = config["mongodbDatabase_NeonOnline"];
}
最后,Api.cs 里面加私链的东西:
public Api(string node) {
netnode = node;
switch (netnode) {
case "testnet":
mongodbConnStr = mh.mongodbConnStr_testnet;
mongodbDatabase = mh.mongodbDatabase_testnet;
neoCliJsonRPCUrl = mh.neoCliJsonRPCUrl_testnet;
break;
case "mainnet":
mongodbConnStr = mh.mongodbConnStr_mainnet;
mongodbDatabase = mh.mongodbDatabase_mainnet;
neoCliJsonRPCUrl = mh.neoCliJsonRPCUrl_mainnet;
break;
case "privatenet":
mongodbConnStr = mh.mongodbConnStr_privatenet;
mongodbDatabase = mh.mongodbDatabase_privatenet;
neoCliJsonRPCUrl = mh.neoCliJsonRPCUrl_privatenet;
break;
}
}
打完收工我们来试一下,CTRL+F5,我们可以看到内置的 IIS Express 被启动了,可以访问这个地址:
http://localhost:59908/api/privatenet
显示的结果是:
{"jsonrpc":"2.0","id":0,"error":{"code":-100,"message":"Parameter Error","data":"Value cannot be null.\r\nParameter name: s"}}
那说明 webapi 本身已经没问题了,我们看到用的是 localhost ,如果想局域网都能用的话我们可以改一下 .vs\config\applicationhost.config 里面的配置再运行,或者我们用典型的 .net core 方法(我采用这种),就是在 Program.cs 里面加一点料:.UseUrls("http://*:59908")
,详细如下,这是允许局域网 ip 访问的第一步:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseUrls("http://*:59908")
.Build();
第二步我们改一下启动方式,先重新生成解决方案,然后把 appsettings.json
和 mongodbsettings.json
两个文件复制到 bin\Debug\netcoreapp2.0
里面去,关掉刚才启动的 IIS Express,然后我们以管理员身份打开 cmd ,cd 到你的 bin\Debug\netcoreapp2.0
文件夹里面,然后 dotnet NEO_Block_API.dll
就好了,结果如下:
你可以去局域网的其他机器上用 postman 访问一下,没啥问题,用 getblockcount
接口吧,路由是这个: http://192.168.1.151:59908/api/privatenet
,文档在这:单击此处 。
我在这台 windows 10 上操作的,IP 是 192.168.1.151
,所以我们得到了第二个想要的接口地址,就是 API for UTXO 的 api 地址:http://192.168.1.151:59908/api/privatenet
。
thinWallet 接入私链
现在我们来观察一下 thinWallet 的代码结构:
很显然,上面三个项目都是通用库,第四个是用户端,而且我们只需要改一个地方就可以了,双击打开 Window_thinwallet.xaml ,WPF 的界面文件,打开是这样的:
简直不能忍,不仅丑爆了,还影响阅读和写代码,我们做一下调整,下面图里面中间,左右两个红圈圈出来的按钮,先按左边那个,上下窗口对调,然后按右边那个,把下窗口缩进去,这样就是纯代码界面了,又好看又能高效阅读。
接下来我们要把它接入私链,其实就改两个接口地址,就在这个界面代码里面,thinWallet 对这两个地址目前是写死在界面上的,我们在界面代码里面查找一下 CTRL+F ,输入 47.96
就能找到了,一共两个地方,分别对应的填进去:
打完收工我们来访问一下,CTRL+F5:
我们发现一个很诡异的数字,下面那个直接访问 neo rpc 的已经完全正常了,但是上面那个访问 webapi 的居然是这么个诡异的数字,那么碰到问题,我们首先得想到的事情只有一件,就是 F5,首先我们先打开 Window_thinwallet.xaml 这个文件的后台文件,就是 Window_thinwallet.xaml.cs
,我们观察一下里面的代码,发现有一个异步更新界面的地方:
再来看一下这个 api_getHeight
是怎么实现的,F12 过去:
真相在这里,请求了 webapi 的一个 getblockcount
的接口,那么现在来看一下 NEO_Block_API 项目对 getblockcount
是怎么实现的:
这时候就发现了,这里居然在调用 mongodb 的数据,我们回想一下,似乎没有任何操作 mongodb 的地方,也就是说没有任何数据,那么我们可以猜到,getblockcount
接口返回了一个 0 ,而且在我们的 Window_thinwallet.xaml.cs
文件异步更新界面的地方,最后 var height = ulong.Parse(json[0].AsDict()["blockcount"].ToString()) - 1;
来了这么一句,很明确了,无符号长整型,减了个 1 ,导致变成了那个数字,现在来 F5 证明一下,断点打在 api_getHeight
这个方法的最后一句 return 的代码上:
其中 result 是请求结果,就是 0 ,已经说明了一切。那还差什么呢,想想我们好像漏了一个项目,没错还有最后一个调度任务项目没配置好。
NeoBlock-Mongo-Storage 开启对私链的任务调度
老样子观察代码:
很简单,一个入口,两个数据接口,一个工具,没了。
我们可以发现在 Program.cs
里面有这么一段:
var config = new ConfigurationBuilder()
.AddInMemoryCollection() //将配置文件的数据加载到内存中
.SetBasePath(System.IO.Directory.GetCurrentDirectory()) //指定配置文件所在的目录
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) //指定加载的配置文件
.Build(); //编译成对象
mongodbConnStr = config["mongodbConnStr"];
mongodbDatabase = config["mongodbDatabase"];
NeoCliJsonRPCUrl = config["NeoCliJsonRPCUrl"];
sleepTime = int.Parse(config["sleepTime"]);
if (int.Parse(config["utxoIsSleep"]) == 1) {
utxoIsSleep = true;
}
很简单了,加个文件,appsettings.json
:
{
"mongodbConnStr": "mongodb://127.0.0.1:27017",
"mongodbDatabase": "privatenet",
"NeoCliJsonRPCUrl": "http://192.168.1.135:20332",
"sleepTime": "0",
"utxoIsSleep": "0"
}
同样道理,重新生成解决方案,把这个 json 文件复制到 bin\Debug\netcoreapp2.0 里面去,然后管理员身份打开 cmd ,然后 cd 进去,然后 dotnet NeoBlockMongoStorage.dll
,看下结果:
这个狂暴的气息,然后我们开一下 thinWallet,看下结果:
这个高度正在快速地向最高高度同步过去,同步完成之后,我们就完成了 thinWallet 接入私链的所有步骤,你的 thinWallet 就可以在私链中随便玩了。
同步完成之后,我们最好把 NeoBlock-Mongo-Storage 项目的 appsettings.json 改一下,把 sleepTime
从 0 改为 2000,utxoIsSleep
从 0 改为 1,因为调度是个无限循环,如果不让它睡一会,就会一直浪费你的 cpu ,同步完成了,没那么多数据要写了,让它休息休息,这样造成的效果是,上面那个高度永远比下面那个高度更新慢半拍。单位是毫秒。自己随便调节。
{
"mongodbConnStr": "mongodb://127.0.0.1:27017",
"mongodbDatabase": "privatenet",
"NeoCliJsonRPCUrl": "http://192.168.1.135:20332",
"sleepTime": "2000",
"utxoIsSleep": "1"
}