介绍
应用程序开发人员需要能够在以太坊2平台上创建在不同执行环境中具有不同分片合约的程序。应用程序开发人员需要能够使用他们习惯于与以太坊1平台一起使用的同步原子可组合编程技术。这篇文章提出了一种可以做到这一点的技术。
例如在下图中,使用跨分片调用来获取oracle的值。如果返回的值低于一定数量,则使用“跨分片”调用来购买商品。
这种跨分片交易技术依赖于添加到执行环境(EE)中的以下功能:
· 添加到交易收据中的系统事件消息,可以通过信标链交叉链接从其他分片上的其他EE引用。在EE中,当作为事务目标的函数调用结束时,EE会生成系统事件消息。系统事件消息与合约代码可能产生的应用程序事件消息不同。合约代码不能产生伪造系统事件消息的事件。
· 实时参数检查:当合约代码调用进行跨分片函数调用时,请检查实际分片,EE,合约,函数和参数是否与预期被调用的那些相匹配。
· 合约可锁定性:部署合约时,需要将其指定为可以锁定(可锁定)或不能锁定(不可锁定)。执行交易细分时,任何具有状态更新的合约都必须被锁定。如果将合约部署为不可锁定,则无法将其锁定,并且事务将失败。
· 临时状态存储和合约锁定:当合约作为跨分片事务的一部分进行更新时,其更新状态存储在临时存储中,并且合约被锁定。如果提交了跨分片事务,则临时状态将替换合约状态并且合约将被解锁。如果忽略交跨分片事务,则临时状态将被丢弃,合约将被解锁。
· 新的事务类型:EE需要支持本建议后面描述的事务类型。
示例
理解这项技术的最好方法是通过一个例子。想象一下下面显示的调用图。下面写入(没有更新)的段事务是读取状态并返回值的函数调用。下面写入(更新)的段事务是写入状态并返回值的函数调用。
下图显示了执行此复杂的跨分片事务的事务。在该图中,区块由方形框表示,交易由圆角框表示。编号为N-1的分片区块的已签名状态根会馈入编号为N的信标链块,称为交叉链接。编号为N的信标链块中所有分片块的交叉链接都可用于编号为N的分片块。虚线表示从分片块将交叉链接提交到信标链区块中,以及它们可用于信标链块中的分片块。实线表示应用程序将事务提交到事务池中,然后将其包含在分片块中。
逐步处理:
1. 应用程序将开始事务提交给Shard 1。这将为跨共享事务保留跨共享事务Id,并指定组成跨共享事务的根事务和事务段的调用图。
2. 应用程序将叶事务段提交给Shard 3、5、6和7。事务执行可以更新状态并返回值的函数调用。执行事务的EEs会发出系统事件,其中包含有关事务调用和函数调用的信息,包括错误状态和返回结果。在本例中,假设只有Shard 7有任何状态更新。
3. 该应用程序等待产生一个信标链块,该信标链块包括包含提交事务的Shard块的交叉链路。
4. 应用程序将事务段提交给Shard 2以执行函数调用。该事务包括来自在Shard 3和Shard 5上执行的事务的系统事件信息,以及显示该信息与作为在Shard上执行的块的一部分的事务相关的Merkle证明。Merkle根可以与分片的信标区块中的交叉链接进行比较。在本例中,假设Shard 2上的事务不会导致状态更新。
5. 在将事务段提交给Shard 2的同时,应用程序将事务段提交给Shard 4以执行函数调用。事务包括在shard 6和shard 7上执行的事务的系统事件信息和Merkle证明。在本例中,假设此事务导致Shard 4上的状态更新。
6. 该应用程序等待产生一个信标链区块,该信标链区块包括包含提交事务的Shard块的交叉链路。
7. 应用程序提交根事务,以及Shard 2和4上事务段的系统事件信息和Merkle证明。发出系统事件信息,指示应提交整个跨分片事务。
8. 该应用程序等待产生一个信标链区块,该信标链区块包括包含提交事务的Shard块的交叉链路。
9. 该应用程序在Shard 4和7上提交信号交易以解锁Shard 4和7上的合约。
10. 应用程序在Shard 1上提交清理交易,以从未清唯一ID列表中删除交叉分片交易ID。
交易类型
跨分片事务包含多种事务类型。
初始事物
初始事物指示跨分片事务的开始。此事务用于保留每个shard和每个EE的“唯一”跨shard事务Id,并注册根事务和事务段的整体调用图。
交易字段包括:
· 跨分片交易ID.
· 超时区块编号。
· 根事务信息(分片,EE,函数调用和参数)。
· 对于从作为该交易结果执行的代码中调用的每个交易细分:
· 带有Shard、EE、Contract Address、Function Name、被调用函数的参数值的日志消息
· 返回结果
· 是否进行状态更新和交易是否导致合约锁定。
· 将指定行的Merkle证明合并到交叉链接。
· 请注意,事务列表必须按照期望调用函数的顺序进行。
事务处理:
· 如果使用跨分片事务Id,则失败。
· 检查超时区块号是否为合适的值。
· 注册跨分片事务Id.
· 发出包含以下内容的启动系统事件:
· Tx Origin/Msg Sender:签署交易的帐户。
· 跨分片事务Id
· 从根事务开始的交叉分片函数调用/事务段的分层调用图。对于每个调用,包括:
· Shard
· EE
· Contract address
· Data: 函数名字和参数
根交易(Root Transaction)
根事务包含函数调用,该函数调用是跨分片事务的整体调用图的入口。此事务类型指示代码应按照正常的事务段执行(请参见下文),并且应提交或忽略所有分片上的所有锁定合约。如果事务在超时块之前成功完成,则作为跨碎片事务一部分的所有事务段的结果,所有状态更新都应提交。如果任何事务段失败,或者如果在超时后提交事务,则应丢弃所有事务段更新。
交易字段包括:
· 跨分片事务Id
· 要调用的Shard、EE、Contract、函数和参数。
· 启动事务日志消息和Merkle证明(注:包括超时块号)
· 对于从该事务执行的代码中调用的每个事务段:
· 带有Shard、EE、Contract Address、Function Name、被调用函数的参数值的日志消息
· 返回结果
· 是否有状态更新,并且事务已导致锁定的协定。
· 对特定区块的交联进行Merkle证明。
· 注意,事务列表必须按照预期调用函数的顺序。
事务处理:
1. 验证“初始事务”和每个事务段的日志信息,检查Merkle证明,直到“分片交叉链接”。
2. 如果区块编号在超时之后,或者任何“事务段”日志指示错误,则发出指示错误的系统事件。
3. 检查事务是否已由启动事务日志指示的同一实体提交。
4. 验证“初始事务”事件中的调用图是否与传入的事务段调用匹配。
5. 使用事务段的缓存返回值执行代码。
6. 检查代码调用的事务段是否与开始事务日志中预期调用的事务段匹配。
7. 状态更新被放入临时存储,合约被锁定。
8. 当这个shard的入口点函数调用完成时,发出一个系统事件消息,该消息与为根事务描述的消息相同。
信令事务
这些事务用于解除由事务段锁定的合约的锁定。
此交易包括:
· 根事务日志信息和显示根事务已成功完成或已失败/超时的Merkle证明。
· 此分片日志和Merkle证明的事务段,显示哪些合约被锁定。
事务处理:
1. 如果根事务日志指示“提交”,则应用临时更新并解锁合约。
2. 如果根事务日志指示“忽略”,则放弃临时更新并解锁合约。
这些交易应该是免费的,或者甚至应该给予鼓励,以确保参与者在失败/锁定合约的实体停止参与时提交此交易。无效的信令交易应惩罚用户。惩罚不应太重,因为信标链重组可能导致无效的信令交易。
清除交易(Clean Transaction)
从未完成的唯一ID列表中删除唯一ID。
交易字段包括:
· 初始交易日志和证明(指示交叉分片交易的调用图),
· 根事务日志和证明
· 交易细分日志和Merkle证明(表明已锁定哪些合约),
· 信令事务日志和Merkle证明(表明调用了适当的信令事务)。
应激励有效清除交易,以确保参与者提交了此交易。但是无效的清除事务应该惩罚用户。惩罚不应是繁重的,因为信标链重组可能导致无效的清除交易。
其他交易
除上述交易类型外,EE需要支持的其他交易包括:
· 部署可锁定的合约。
· 部署不可锁定的合约。
· 在某些情况下,可以创建专门的事务类型来消除对“start”和“clean”事务的需求。例如如果有一个根事务和一个执行跨碎片读取并且不更新状态的事务段,则可能可以创建不需要启动和清除事务的专用根事务。目前正在进一步考虑。
跨分片处理
要进行跨碎片交易,应用程序将执行以下操作。请注意,用户的应用程序并没有做很多事情,大多数复杂性将由库包装程序(如Web3J)处理。
1. 使用动态程序分析(代码模拟)确定事务的调用图和参数值。
2. 提交开始交易。这可以与下一步并行进行,也可以在下一步之前的一个插槽中完成。
3. 提交叶事务段。“leaf”事务是指其函数调用不调用其他跨碎片函数的事务。
4. 等待一个区块以发布交叉链接。
5. 提交包含调用其他事务段的函数的事务段。对嵌套事务的每个“层”重复此操作。
6. 等待一个区块以发布交叉链接。
7. 提交根事务。
8. 等待一个区块以发布交叉链接。
9. 提交更新状态下执行事务段的所有分片上的所有信令事务。
实时参数检查
实时参数检查用于确保在执行合约代码时,传递给跨分片函数调用的参数的实际值与已签名的交易段中的参数值匹配。回想一下,在执行事务时,“事务段”的参数值在系统事件中发布,并且应用程序将此信息馈送到“事务段”或“根事务”中,以调用它们所代表的函数。这意味着当代码执行时,EE可以访问来自Transaction Segments的跨分片函数调用的预期参数值。
图显示了在另一个返回值的shard(sh2.ee2.conC.func())上调用函数的示例约定代码,然后根据state1的值,可以在另一个不返回值的shard(sh1.ee4.conD.func())上调用函数。
当应用程序创建用于调用funcC和funcD的事务段时,它需要模拟代码执行。例如如果_param1将为5,state1为2,state2为3,并且在参数为5的情况下funcC将返回10,则需要创建事务段,并将参数值5传递给funcB,将5传递给funcC ,并将13传递给funcD。请注意,如果state1为1,则事务段将仅针对对funcB和funcC的调用进行构造,因为funcD将永远不会被调用。
安全与活性(Safety & Liveness)
以下内容介绍了一组可能的失败方案,并描述了如何处理该方案。
根交易失败
如果根事务因任何原因失败,则会创建一个系统事件,该事件指示所有事务段的所有更新都应该被丢弃。基于此系统事件,可以使用信令事务来解锁所有EE中所有碎片上的所有合约。根事务可能失败,因为:
· 根事务中的代码可能会引发错误。
· 传递到根事务中的系统事件消息可能表示事务段之一发生错误。
· 调用交叉分片函数的参数与函数调用的事务段的系统事件消息中的参数值不匹配。
· 交叉事务处理块超时后提交根事务。
事务段失败
如果事务段由于任何原因失败,则会创建一个系统事件,指示它失败。此系统事件可以传递到根事务,以导致整个跨分片事务失败。事务段可能会失败,因为:
· 事务段中的代码可能引发错误。
· 传递到事务段的系统事件消息可能指示某个从属事务段发生错误。
· 使用跨分片函数调用的参数与函数调用的事务段的系统事件消息中的参数值不匹配。
· 在跨分片事务块超时后提交事务段。
无效的Merkle证明或无效的系统事件
该应用程序可能会提交无效的Merkle证明或无效的System Event消息,从而使System Event消息与Merkle Proof结合使用的哈希不会产生与Cross Link的状态根匹配的Merkle根。在这种情况下,相关事务将失败。
应用程序未提交事务段
应用程序可能会看到从属事务段失败,并决定不再提交任何其他事务段,根事务或信令事务。为了解决这个问题,另一个用户可以等待跨分片事务超时,并提交一个根事务以使整个跨分片事务失败(这是免费的),并提交信令事务(将奖励调用者)和清除事务(将奖励调用者)以解锁所有锁定的合约并移除跨分片事务Id。
应用程序不提交根事务
应用程序可以看到一个从属事务段失败,并决定不提交根事务或发送事务信号。为了解决这个问题,另一个用户可以等待跨分片事务超时,并提交一个根事务以使整个跨分片事务失败(这是免费的),并提交信令事务(将奖励调用者)和清除事务(将奖励调用者)以解锁所有锁定的合约并移除跨分片事务Id。
重放攻击(Replay Attacks)
根事务和事务段与用于签署“开始事务”的帐户相关联,但在交叉分片超时已过期的失败情况下除外。该帐户的交易将具有一个帐户随机数,这将阻止重放。尝试重放“根事务”和“事务段”的其他用户将失败,因为他们将无法使用签署了“初始事务”的私钥对事务进行签名。
拒绝服务(DOS)攻击
攻击者可能提交带有正确数据的清除交易,并带有一个错误的Merkle证明。攻击者可能会重复提交交易,以试图在以太坊客户端上进行大量处理。一个大的调用图将导致以太坊客户需要处理许多Merkle证明。通过对无效的清除交易进行处罚来阻止这种行为。此外,任何用户提交有效的清除交易都会获得少量奖励。
信标链分叉
如果信标链分叉,则需要根据修订的交叉链接重播所有分片上分片块的事务。一些重播的事务可能期望不再存在交叉链接,因此这些事务将失败而不是通过。在这种情况下,交叉分片事务将失败,并且任何状态更新的临时状态都将被丢弃。
终结性
一旦最后一次信令交易完成后的检查点,交叉分片交易将是最终的。这通常是一个时期中的第一个信标区块。一旦检查点是最终的,所有先前的区块都是最终的,并且隐含地所有先前的交叉链接将是最终的,因此所有分片块都是最终的。
酒店列车示例(包括ERC20)
旅馆和火车问题是一个场景的示例,其中涉及旅行社,该旅行社需要确保跨越三个分片的复杂多合约交易的原子性。旅行社需要确保他们既预订酒店房间又预订火车座位,或者既不预订酒店房间,也不预订火车座位,因此避免了成功预订酒店房间但火车预订失败的情况,反之亦然。此外只有在进行预订后,才需要通过ERC20代币付款。最后的要求是,交易必须以其他用户可以预订酒店房间和火车座位并同时付款的方式进行。
想象一下,其中涉及三个分片:旅行社在shard 1上运行,旅馆在shard 3上运行,火车在shard 4上运行。还有第二个在shard 2上运行的旅行社。这是下面的示意图。
酒店被表示为一个不可锁定的router contract和多个可锁定的酒店room contracts。酒店向旅行社发放ERC 20代币,用于支付酒店客房费用。ERC 20合约包括router contract和每个帐户的一个或多个付款槽合约。类似地,火车由不可锁定的router contract和多个可锁定的火车seat contracts表示。
为了预订房间,旅行社创建了交叉分片功能调用(事务段),用于预订酒店房间和火车座位并支付费用。酒店router contract的工作原理是找到一个合适的房间,该房间在请求的当天可用,但目前尚未预订,然后预订该房间。搜索房间时,代码将跳过当前锁定的room contracts。同样在支付房间费用时,ERC的router contract需要将钱从旅行社的账户转到酒店的账户。它通过为酒店找到一个没有上锁的付款槽合约来实现这一点。
如果能够以编程方式确定哪些合约已被锁定,从而避免合约被锁定,则可以编写酒店,火车和ERC20合约,以便两家旅行社可以同时执行预订而不会遇到锁定的合约。
其他注意事项
帐户随机数(Account Nonces):帐户随机数被视为处于可锁定状态。这样当一个帐户提交一笔交易,并且交易现值增加时,该帐户就不会被锁定。
ETH转移(Ether Transfer):目前该方案只关注函数调用。用这种技术可以在分片之间进行ETH转移。
为所有分片上的Gas付费:如果用户可以在一个碎片上使用Ether,并使用它为跨分片事务执行的所有分片上的Gas付费,那就太好了。这种技术目前还不能做到这一点。也许当ETH转移被解决时,这是可能的。
事务大小(Transaction size):此提案中的交易通常包含多个Merkle证明和其他数据。需要分析对交易规模的可能影响。
以太坊1.x:假设以太坊1.x在分片中的EE中,所有现有合约都可以标记为不可锁定。EE可以支持本文中描述的功能。可以添加其他EVM操作码以允许跨分片函数调用。
应用程序代码(Application code):我听到您说,“在应用程序中听起来很复杂。这种技术难道不应该简化应用程序开发吗?” 答案是,绝大多数复杂性将被吸收到Web3J之类的库中。合约代码和其他应用程序代码应简单明了。