频繁的交易使的以太坊虚拟机变得越来越慢,交易费也越来越高。当下,大多数建立在以太坊上的应用都是通过更新链上合约的存储变量来实现,用户需要支付交易费并花一定时间等待区块确认。
当然,这也是低效的。为了使用应用,我们要求用户手动将数据库更新提交至世界上最安全的、分布式的、无需信任的。。。1999 年老式手机(译者注:意为当前的以太坊就像 1999 年的老式手机)。
值得庆幸的是,有一个更好的方法。
我们可以通过将一些工作转移到客户端而不是完全依赖以太坊编写足够安全的程序。我们将这类方法统称为“第二层”区块链技术。在 L4,我们已经写了很多帮助解决这个问题的不同方法。
大多数“以太坊是不可拓展”的观点不是因为底层区块链不合适。更准确的说,是因为开发人员难以使用像状态通道这样的“第二层”区块链技术。我们需要以太坊上有更好的开发者工具,从而使得用有效的方式编写应用程序变得更加简单。
Counterfatual 是一个正致力于解决该问题的开源项目。我们的目标是让开发者在以太坊上轻松地使用状态通道构建应用。你可以在我们的主页上了解更多项目相关信息,同时可以在 github上查看我们的代码。
当下,如果开发者想要使用以太坊编写他自己的分布式应用,他们可能需要实现以下内容:
在大多数情况下,在开发者设计分布式应用时,这是一种较好的抽象方式。我们已经看到成千上万的开发人员在过去一年中使用这种模式在以太坊上构建分布式应用了,因此最好不要彻底改变它。
从这个角度思考状态通道可以让你与应用开发者更加感同身受。大多数情况下,当你尝试设计状态通道应用程序时,你会遇到各种各样的障碍,例如:
最糟糕的是,状态通道协议完全没有既定标准,这使得框架以及公共库难以产生。
使状态通道应用程序更易于实现的最重要的第一步是要清晰地将状态通道解析逻辑与应用逻辑分离开来,以此标准化通用状态通道功能。应该尽可能简单地用状态通道兼容的格式编写应用程序,理想情况下,应该感觉你在编写常规智能合约代码。
其中一种实现方式就是将应用程序建模成状态机。force-move game framework 就是一个例子,通常来说,EVM 就是建立在状态机的基本思想上——根据交易执行更新区块状态。
那么常规智能合约和状态通道兼容智能合约之间的区别是什么呢?从本质上来说,可以归纳为一句话:我们需要一种与应用程序状态转换交互的标准方法,而不管公共 API 如何实现。更简单的说,我们需要一个入口函数实现状态转换。
假设我们想要写一个井字游戏程序。我们会写这样一个智能合约:它有 X 和 O 函数的公共 API ,可以检查 msg.sender 以验证玩家是否正确的进行游戏。
contract TicTacToe {
address player1;
address player2;
uint8[3][3] board;
uint8 nextTurn;
function placeX(uint8 x, uint8 y) public {
if (msg.sender == player1) { ... }
}
function placeO(uint8 x, uint8 y) public {
if (msg.sender == player2) { ... }
}
}
-用 Solidity 编写的井字游戏智能合约例子-
将该游戏建模成一个状态机,我们可以看到共有 5 种状态以及 2 种有效的状态转换类型。
-
井字游戏状态机的例子。如果轮到 X 进行游戏,X 可以进行一次操作赢得游戏、平局结束游戏或者落子切换操作权。
-
回到更新应用状态的标准接口的想法,我们想要创建一个函数以接受状态及一些先前状态(如, X_TURN)以及会导致状态迁移的操作(如, PLACE_X)。有趣的是,这与现有的一些常见 Web 框架的模式完全相同,比如 Redux。这种功能常常被称作“reducer”。让我们试一下用这种方法编写井字游戏吧:
contract TicTacToe {
enum ActionTypes { PLACE_X, PLACE_O }
enum StateTypes { X_TURN, O_TURN, X_WIN, O_WIN, DRAW }
struct Action {
ActionTypes actionType;
uint8 x;
uint8 y;
}
struct AppState {
StateTypes stateType;
address player1;
address player2;
uint8[3][3] board;
uint8 nextTurn;
}
function reduce(AppState state, Action action)
public
view
returns (AppState)
{
if (action.actionType == ActionTypes.PLACE_X) {
require(state.stateType == StateTypes.X_TURN);
return onPlaceX(state, action);
} else if (action.actionType == ActionTypes.PLACE_O) {
require(state.stateType == StateTypes.O_TURN);
return onPlaceO(state, action);
} else {
revert("Invalid action type");
}
}
function onPlaceX(AppState state, Action action) internal returns (AppState) { ... }
function onPlaceO(AppState state, Action action) internal returns (AppState) { ... }
}
-现在这个井字游戏使用一个 reducer 函数处理 PLACE_X 和 PLACE_O 操作而非使用多个函数——placeX 和 placeO。Reducer 函数将具体操作“分派”给辅助函数。-
有了这些,我们现在有了一种计算状态更新的方法了:可以通过一个公共接口——reduceinterface 对应用程序进行状态更新。这一点在保护状态通道免受各种攻击时非常有用。
但是状态通道合约到底需要做什么呢?
本质上说,状态通道对象需要 使用 应用逻辑来判断状态转移是否有效。核心状态功能可以在一个用于处理各种可能攻击的通用合约中实现,状态机的具体状态转换规则则需要视具体应用而定。
-状态通道可以将应用逻辑作为确定有效转换的方法,根据应用逻辑提供的信息处理授权逻辑以及解决逻辑。-
有两个需要处理的主要场景。如果我们使用 Alice 与 Bob 互相交换信息这一常见例子,这两个场景可以被描述为:
如果 Bob 提交过时的状态该怎么办?
合约必须能够实现超时阶段(timeout period),这样 Alice 就可以提交比 Bob 提交更新的状态。如果 Bob 提交的状态被证明是旧的,那么 Bob 就要受到惩罚。
如果 Bob 停止响应、该怎么办?
合约必须让 Alice 可以提交她从 Bob 那里收到的最新签署状态,以便将她的钱取出来(超时阶段后)。在某些情况下,Alice 可能可以推动应用状态机状态迁移,因此她必须要能够在这种情况下使用应用程序的 reducer。
为此,合约需要公开一个基础 API。请注意,这个基础 API 的主要作用是处理有争议的事件。状态通道用户可以通过调用一组函数来保证他们签名的离线状态更新有意义。
这个基础API包括:
这就是我们的论文中描述的许多特性非常有用的地方。我们将资产放在一个通用的多签名钱包中,并将其作为依据状态通道结果分配资产的主要合约。
-一个简单的使用状态通道的应用所需要的合约集(不使用 counterfactual 实例,而是仅仅使用常规的合约引用)-
这给我们带来了很多好处。最大的好处之一就是可以利用 counterfatual 实例技术,这一点也在论文中详细描述了。当我们将其添加到组合当中时,可以在链下维护状态通道合约以及应用逻辑合约。
-与上面的设置相同,但是这一次状态通道合约以及应用逻辑合约是 counterfatual 的——它们只需要在产生争议时被部署到链上。-
在我们完成这些设置之后,我们又获得了另一个强大的特性:安装和卸载应用都可以在链下进行。
当我们使用 counterfatual 时,安装和卸载应用都是免费和即时的。由于我们可以从多重签名钱包中无限调用有条件转移,因此,可以保证任意数量的应用免费且即时的安装与卸载。
在以后的帖子、演讲以及讨论中,我们将概述我们对于合约层之上的软件的愿景。要想在生产生活中应用状态通道,还需要整个生态系统进行大量的协调。我们当前正在开展并期待与其他人进一步合作的具体领域包括:
作为一个好的起点,我们建议你查看我们共享在 GitHub 上的合约代码,代码包含该技术的参考实现。
链接: https://medium.com/statechannels/state-channel-applications-1f170e7d542e