• 以太坊之Truffle 2.0版本升级3.0版本的集成指南


    一、前言

    • Truffle 3.0 版本引入了大量的新特性,这些特性为我们带来了大量的重要革新性变化,让 network 的管理更简单,新的抽象的合约层,允许你从第三方引入各种依赖文件。
    • 伴随以太坊的开发工具逐步成熟,这样的革新非常有价值。这个升级同样适用于从 beta 3.0.0-9 升级到 3.0.1 的用户。

    二、配置(Configuration)

    • Truffle 2.0 中使用了一个不舒服的配置方式,不仅可以使用一个 default,匿名的网络设置(通过 rpc 配置项);同时也可以使用命名的网络设置,比如 ropsten 或者 live。由此带来的一个后果是,可能会无意中覆盖了网络配置,或者部署到错误的网络。在 Truffle 3.0 中,解决了这个问题,代价是对配置方式的调整。
    • 在 Truffle 2.0 版本中,一个命名网络的声明方式示例如下:
    module.exports = {
      rpc: {
        host: "localhost",
        port: 8545
      },
      networks: {
        staging: {
          host: "localhost",
          port: 8546,
          network_id: 1337
        },
        ropsten: {
          host: "158.253.8.12",
          port: 8545,
          network_id: 3
        }
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 在 Truffle 3.0 中,修改为下述的方式:
    module.exports = {
      networks: {
        development: {
          host: "localhost",
          port: 8545,
          network_id: "*"
        },
        staging: {
          host: "localhost",
          port: 8546,
          network_id: 1337
        },
        ropsten: {
          host: "158.253.8.12",
          port: 8545,
          network_id: 3
        }
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 变化点如下:
      • 默认网络配置项 rpc 被移除,取而代之的是在 networks 配置项下的一个专门的名为 development 的网络配置项,development 中会指定的 ip 和 port,和配置的网络类型 1,默认是任意 *;
      • 如果没有指定网络,在执行 migrate 等命令时默认使用名为 development 的网络;
      • 为了避免出现部署到错误网络的情况,也可以完全移除配置项 development。如果已经移除 development 的网络配置,但在 truffle migrate 等命令时不指定网络会有下述错误发生:
    $ truffle migrate
    Compiling ./contracts/ConvertLib.sol...
    Compiling ./contracts/MetaCoin.sol...
    Compiling ./contracts/Migrations.sol...
    Writing artifacts to ./build/contracts
    
    Error: No network specified. Cannot determine current network.
        at Object.detect (/usr/local/lib/node_modules/truffle/lib/environment.js:27:23)
        at /usr/local/lib/node_modules/truffle/lib/commands/migrate.js:33:19
        at /usr/local/lib/node_modules/truffle/lib/contracts.js:51:11
        at /usr/local/lib/node_modules/truffle/lib/contracts.js:83:9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 如果要指定网络,如 ropsten,使用命令:
    truffle migrate --network ropsten
    
    • 1
    • 另外每个网络配置项,都需要指定一个网络 ID[networkID],这可以算是一种安全机制来保证部署的安全性,来保证一定是部署到你想要部署的网络,development 这样的网络环境,可以使用*来表示任意,以方便调测。

    三、移植和测试依赖(Migrations and Test Dependencies)

    • 在没有引入包管理前,Truffle 可以假设所有智能合约都是需要移植和测试的。在有了包管理之后,由于依赖可以来自多个不同的地方,不需要再进行默认关联,如果需要关联,必须明确指定,以减少自动关联带来的潜在问题。
    • 来看一个migration(移植)的手动引入关联的例子:
      • 在 2.0 版本中的 ./migrations/2_deploy_contracts.js:
    module.exports = function(deployer) {
      deployer.deploy(ConvertLib);
      deployer.autolink();
      deployer.deploy(MetaCoin);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
      • 3.0 版本中的 ./migrations/2_deploy_contracts.js:
    var ConvertLib = artifacts.require("ConvertLib.sol");
    var MetaCoin = artifacts.require("MetaCoin.sol");
    
    module.exports = function(deployer) {
      deployer.deploy(ConvertLib);
      deployer.link(ConvertLib, MetaCoin);
      deployer.deploy(MetaCoin);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 改进之处如下:
      • 提供 artifacts.require() 方法,类似 Node.js 的 require() 语句,用于声明依赖;
      • 可以通过 artifacts.require() 来引入一个本地的 Solidity 文件,或者是包中依赖路径,这个方法将负责引入对应的文件;
      • Javascript 中的测试用例也需要通过 artifacts.require() 语句来引入依赖。
    • 自动关联为依赖问题的解决带来了一点点方便,但随着包管理的引入,不再可能自动判断在移植中所真正需要的所有的依赖,提供的替代方案是,通过明确指定的方式来关联库。

    四、合约

    ① 合约抽象:JSON 格式(不再使用 .sol.js!)

    • Truffle 之前将合约定义为 Javascript 的格式,以 .sol.js 为后缀的文件存储。存为这样的格式的考虑,主要是可以在任何地方容易的使用。但最终发现,我们考虑的并不完善,不仅 Javascript 中存在一些情况中难以直接使用,更在非 Javascript 环境完全不可用。为解决这样的局限性,Truffle 3.0 将所有合约编译后的结果存为 JSON 格式,以能随时随地的,跨环境使用。
    • 如果已经使用 Truffle 2.0 生成了许多的文件,我们提供了一个工具来进行转换。如果要升级 Truffle2.0 生成的 .sol.js,需要先安装 truffle-soljs-updater:
    $ npm install -g truffle-soljs-updater
    
    • 1
    • 安装好后,可以通过 sjsu 命令来使用这个工具,使用方法如下:
    $ cd ./build/contracts
    $ sjsu
    Converting ConvertLib.sol.js...
    Converting MetaCoin.sol.js...
    Converting Migrations.sol.js...
    Files converted successfully.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 具体使用方法是,进入到 .sol.js 文件所在目录,这里是 ./build/contracts,然后运行 sjsu 即可。默认情况下 sjsu 命令只会创建新格式的 .json 后缀结尾的文件,并不会删除原始文件,需要手动删除旧的 .sol.js 来保证 Truffle 3.0 能正常工作。需要注意的是,建议删除前将旧的 .sol.js 的文件备份到其它地方,最终确认生成的 .json 文件正确的情况下再删除。
    • 另外一种选择是使用强制模式,sjsu -f 通过加 -f 参数,这样 sjsu 会删除旧的 .sol.js 文件,并生成生的 .json 文件,但需要保证旧的文件已提前进行了妥善的保存,以避免造成不必要的损失。
    $ cd ./build/contracts
    //务必保证你已经备份了旧的`.sol.js`文件
    //`-f`参数会强制删除旧的`.sol.js`文件
    $ sjsu -f
    Converting ConvertLib.sol.js...
    Converting MetaCoin.sol.js...
    Converting Migrations.sol.js...
    Files converted successfully.
    Successfully deleted old .sol.js files.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ② 合约交互抽象层:.deployed() 现在提供 then()

    • 这个改变,会影响 Migrations(移植)、测试、应用代码。在 Truffle2.0 中,合约抽象层使用原生的方式来管理网络。上文中的 default 的方式,引发潜在的网络不正确的可能,导致可能的部署到错误的网络的问题。
    • 原抽象层提供了一个精心考虑,方便使用的语法,MyContract.deployed().myFunction(…),但将错误直接暴露给了开发者。在 Truffle 3.0 中,改变了这个语法,为 .deployed() 提供了类似 promise 的语法。同时,当前的合约抽象层可以与以太坊的包管理标准,EIP190eip190 进行无缝集成,但这意味着必须改变原有代码的语法。
    • v2.0 版本的语法:
    MyContract.setProvider(someWeb3Provider);
    MyContract.deployed().someFunction().then(function(tx) {
      
    });
    
    • 1
    • 2
    • 3
    • 4
    • v3.0 版本的语法:
    MyContract.setProvider(someWeb3Provider);
    MyContract.deployed().then(function(instance) {  
      return instance.someFunction();
    }).then(function(result) {
      
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 上述的语法上可额能有些啰嗦,但目的是保证合约抽象层连接到了正确的网络。再来看看新语法的例子:
    MyContract.setProvider(someWeb3Provider);
    
    var deployed;
    
    MyContract.deployed().then(function(instance) {
      deployed = instance;  
      return deployed.someFunction();
    }).then(function(result) {
      return deployed.anotherFunction();
    }).then(function(result) {
      
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 需要注意的是:
      • 如果合约方法 f() 不会改变区块链上的数据的,那么调用时要使用 instance.f.call();
      • 如果合约方法 f() 会改变区块链上的数据的,则需使用 instance.f() 来进行调用;
      • 如果想在一个 promise 链中调用多个方法,需要注意示例中的 instance 的作用域,可以在外部定义一个对象,查看 var deployed; 的定义。

    ③ 合约抽象层:交易结果对象

    • 一直以来有个麻烦的事,关于在 Web3 中监听事件,在大多数情况下,事件一般不是通过主动跟踪事件,而是我们进行了对应的操作,从而引发对应的事件产生。尽管主动监听事件的方式仍然支持,但为了让后一种情况实现起来更加简单,调整了 transaction 的返回结果。
    • 在 Truffle2.0 版本中,transaction 简单的返回了一个交易的哈希串,而在 Truffle3.0 中,将返回一个包含交易详情的结果对象。
    • v2.0:
    MyContract.deployed().someFunction().then(function(tx) {
      
    });
    
    • 1
    • 2
    • 3
    • v3.0:
    MyContract.deployed().then(function(instance) {
      deployed = instance;  
      return deployed.someFunction();
    }).then(function(result) {
      //
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 在 Truffle 3.0 中通过返回 transaction 的详情信息,可以更加方便的决定是否要触发某些相应的行为。如下所示,是一个假想的关注合约发布事件:
    var assert = require("assert");
    var PackageIndex = artifacts.require("PackageIndex.sol");
    
    contract("PackageIndex", function(accounts) {
    
      it("publishes a release correctly", function() {
        return PackageIndex.deployed().then(function(deployed) {
          return deployed.publish("v2.0.0");
        }).then(function(result) {
    
          var found_published_event = false;
    
          for (var i = 0; i < result.logs.length; i++) {
            var log = result.logs[i];
    
            if (log.event == "ReleasePublished") {
              found_published_event = true;
              break;
            }
          }
    
          assert(found_published_event, "Uh oh! We didn't find the published event!")
        });
      });
    
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 虽然这样,需要在所有返回结果中,去找我们感兴趣的事件,但也许是比 PackageIndex.ReleasePublished.watch(…) 更好的一种方式,因为可以在当前的代码中管理事件逻辑,而不是分散在代码各处。

    五、构建流:默认不再需要构建器

    • Truffle1.0 和 Truffle2.0 中,将 Web 应用与整个框架紧紧的捆绑在一起,所以 Truffle 打包提供了一个默认的构建流,以便可以快速的建立 dapp,并运行起来。虽然在有些情况下,这带来了极大的便利,但在其它的场景下却显得极为鸡肋。
    • 基于以太坊的应用持续的在增加,从开始的仅仅支持 Web 的 dapp 应用,发展为可以支持原生语言,在手机或电脑上独立运行。支持各种各样的用户场景,一直是 Truffle 的初衷,所以 Truffle3.0 决定移除默认的构建流。如果你想使用 Truffle 来集成你的应用,仍然可以编写自定义的构建流,因为 Truffle 打算专注做智能合约相关的最好用工具。因此关于构建,通过集成更好工具,如 webpack,browserify,Grunt,Metalsmith 来实现。
    • 尽管构建流默认被移除,但并不意味着没得选择,在 Truffle 中非常注重开发者的使用体验,也永远不会对大家弃之不顾,下面提供两种可选方式,后一种选择会深度绑定到最终选择的构建工具上,如 webpack。

    ① 在 Truffle3.0 中使用旧的构建器

    • 如果在 Truffle2.0 中使用了默认的构建器,而又想升级到 Truffle3.0,我们升级了默认构建器4,所以它完全可以与 Truffle3.0 兼容。但这将是最后一次升级默认的构建器,因为要使之兼容所有的场景,将是一件工程非常复杂的事,所以推荐最终选用后面提到的其它构建系统。
    • 默认构建器,并未集成在 Truffle3.0 中,要使用它,需要安装 truffle-default-builder,并做为一个依赖进行引入,在工程目录运行下面的命令:
    $ npm install truffle-default-builder --save
    
    • 1
    • 一旦安装,可以在 truffle.js 配置文件中,使用默认构建器,配置文件的前后变化:
      • v2.0:Truffle.js:
    module.exports = {
      build: {
        "index.html": "index.html",
        "app.js": [
          "javascripts/app.js"
        ],
        "app.css": [
          "stylesheets/app.css"
        ],
        "images/": "images/"
      },
      rpc: {
        host: "localhost",
        port: 8545
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
      • v3.0: truffle.js:
    var DefaultBuilder = require("truffle-default-builder");
    
    module.exports = {
      build: new DefaultBuilder({
        "index.html": "index.html",
        "app.js": [
          "javascripts/app.js"
        ],
        "app.css": [
          "stylesheets/app.css"
        ],
        "images/": "images/"
      }),
      networks: {
        development: {
          host: "localhost",
          port: 8545,
          network_id: "*" // Match any network id
        }
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 可以发现,除了需要引入作为依赖的默认构建器包,放入构建器需要的配置文件以外,其它是完全一致的。由于默认构建器已经使用了最新的合约抽象层 5,因此上面的所有版本升级需要进行的调整,都适用使用默认构建器。

    ② 使用自定义构建流程/构建工具

    • 自定义构建流程并没有想像中难以使用,真正的难点在于写一个构建流程能兼容各种各样的构建需求,这里推荐去看看适合自己项目的构建工具,比如之前提及的 webpack,browserify,Grunt,Metalsmith,最终哪个的特性能满足我们自己的需要,需要视项目及构建需求来定。无论想构建一个浏览器应用,命令行工具,JS 库,还是原生的手机应用,合约的初始化,部署合约的使用均遵循通用的流程。
    • 当配置自定义工具或应用时,应遵循下述的流程:
      • 编译合约文件,将生成的 .json 结果放到 ./build/contracts 目录下;
      • 通过 truffle-contract[truffle-contract],将编译的合约结果,转为合约抽象层,来方便使用;
      • 为合约抽象层设置 web3 provider,需要注意的是在 Metamask 和 Mist 中,环境内会自动提供,但在其它情况下,需要手动通过配置完成指定。
    • 如下所示,集成 NodeJS:
    // 1.引入编译好的合约文件结果 
    var json = require("./build/contracts/MyContract.json");
    
    // 2.将合约转为合约抽象层实例
    var contract = require("truffle-contract");
    var MyContract = contract(json);
    
    // 3.设置合约抽象层实例的 web3 provider
    MyContract.setProvider(new Web3.providers.HttpProvider("http://localhost:8545"));
    
    // 4.使用
    MyContract.deployed().then(function(deployed) {
      return deployed.someFunction();
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 所有的构建流程,均遵循上述的流程,核心关注点是保证自定义流程要加载所有的合约资源,并正确的设置合约抽象层。
  • 相关阅读:
    Java IO简介说明
    分布式.数据库架构
    java if判断语句
    c# winform 多线程
    pycare检测时间序列异常
    〖Python 数据库开发实战 - Python与Redis交互篇④〗- 利用 redis-py 实现集合与有序集合的常用指令操作
    openvpn部署
    竞赛 推荐系统设计与实现 协同过滤推荐算法
    MySql8.0 + Qt 对数据库操作 - 初窥篇1
    力扣83. 删除排序链表中的重复元素
  • 原文地址:https://blog.csdn.net/Forever_wj/article/details/125628456