尽管所选区块链会计模型对用户体验和可扩展性潜力具有重大影响,但其重要性常常被低估。在本文中,我们将深入研究卡尔达诺和比特币所采用的 UTxO 模型,以及以太坊和许多其他 EVM 兼容区块链所采用的基于账户的模型的复杂性。这篇文章充满了说明性图像。仔细阅读后,您将理解为什么以太坊中的交易验证是不确定性的并且经常容易失败,而卡尔达诺则确定性地验证交易。我们还将阐明以太坊和卡尔达诺的全局状态之间的区别。
区块链会计
会计是现代金融格局的基石。每个金融机构都必须保留个人账户余额和现金流量的记录。这个过程必须完全可靠、安全、可审计,最重要的是值得信赖。
每个账本都应该能够准确地表明任何特定时刻资产的所有权。换句话说,用户 X 在时间 Z 拥有一定数量的 Y 资产的所有权。由于个人之间的财务交互不断发生,账本中的条目永远在变化。该系统,无论是集中式还是分散式,都必须准确验证每笔交易并将其正确记录在分类账中。
从历史上看,纸质账本是常态。然而,随着计算机的出现,会计经历了数字化转型。区块链技术的出现,使得分布式记账成为可能。
区块链网络可以被视为一个金融系统。它们可以被视为分布式账本,即不受任何中央机构控制的会计系统。用户可以以点对点的方式相互进行金融交易。然而,中介机构或验证交易的实体不是中央机构,而是由志愿者组成的分布式网络。一组志愿者负责向分类账添加新的财务条目。
中本聪在比特币中实现了 UTxO 模型。 Vitalik Buterin 选择了基于账户的以太坊模型。 IOG 团队选择为 Cardano 使用扩展的 UTxO 模型。每个变体都有自己的优点和缺点,我们将在后续分析中深入探讨。
状态是验证交易的先决条件
从技术上讲,区块链是一个状态机。它会记住以前的事件,这称为系统状态或全局状态。这些事件主要以交易为代表,是用户之间的交互。状态可以在预定义的条件下改变。每个协议都实现了一组规则和逻辑,允许从状态 S-0 转换到状态 S-1。区块链网络会定期添加新块,其中会发生状态转换。
块是从现有状态过渡到新状态的基本单位。它是由交易组成的。区块生产者的任务是按照协议的规则制作一个新区块,这意味着必须满足区块接受的所有条件。要使区块被视为有效,它必须仅包含有效交易。
交易的验证(即两方或多方之间的价值转移)需要一些上下文或状态来进行验证。除其他事项外,系统必须验证发送者的帐户是否拥有足够的资金(例如有效签名)。它还必须防止用户多次花费相同的资金,从而解决臭名昭著的双重支出问题。孤立地验证交易是不可行的。验证器根据账本的当前状态验证交易。
负责构建新块的节点和验证新创建的块的节点都需要了解上下文(即账本的当前状态)才能进行验证。验证的上下文是区块链中最新的有效块。
出块节点在特定轮次中构造新块,使用代表当前状态的最后一个块作为验证的输入。随后,它会将所有要插入到新块中的交易进行验证。然后它将其他所需的数据附加到该块并将其扩散到网络。
在图中,节点正在尝试构建一个新块,以允许从状态 S+3 转换到下一个状态 S+4。
生成新块的节点和所有验证节点都必须共享相同的验证上下文才能转换到新状态。如果各个节点使用不同的上下文,则新提出的块只能被网络中的部分节点接受。有效的块只会通过与块的生产者使用相同上下文的那些节点的验证。
随着每个新添加的区块,甚至每个额外的经过验证的交易,都会发生巨大的变化。交易的发送和网络的处理(即将交易包含在区块中)之间可能存在相当长的时间间隔,在此期间全局状态可能会发生变化。当区块生产者节点将交易合并到新的候选区块中时,用户在分派交易时所拥有的上下文可能会也可能不会改变。
如图所示,用户在添加区块 N+2 后不久发送了 TX 10 交易。此后,添加了块 N+3,导致全局状态发生变化。区块生产者节点正在构建区块 N+4,并打算将交易 TX 10 纳入其中。区块 N+3 的添加改变了全局状态,这也可能会修改与交易 TX 10 的验证直接相关的上下文。
用户在添加块 N+2 后拥有有效的上下文。然而,区块生产者在添加区块 N+3 后具有有效的上下文。所以语境不一样。
UTxO 和基于账户的模型之间的关键区别在于验证器验证交易所需的上下文以及验证块内交易的方法。
UTxO 基础知识
UTxO是Unspent Transaction Output的缩写,指的是先前交易的输出,可以在未来的交易中使用。
UTxO模型不识别账户和余额等概念。 UTxO 的功能更像纸币,能够持有任何名义价值。例如,UTxO 可以是 6.9 ADA、47 ADA 或 459.7 ADA。
Cardano 钱包帮助用户管理 UTxO,就像管理账户余额一样。上述三个 UTxO 在 Alice 的钱包中将显示为 513.6 ADA。
那么,当 Alice 希望向 Bob 发送 50 ADA 时会发生什么?
爱丽丝在卡尔达诺钱包中发起交易。钱包必须将一组 UTxO 纳入交易中,这足以支付总共 50 ADA 加上 0.17 ADA 费用。
钱包可以使用值为 459.7 ADA 的单个输入 UTxO 或值为 6.9 ADA 和 47 ADA 的两个输入 UTxO 进行交易。钱包选择了后者,总共使用 53.9 ADA 作为交易输入。
通常,创建两个 UTxO 作为交易的输出:1)发送给接收者的 UTxO,2)返回给发送者的 UTxO。
请注意,交易输入的 ADA 数量比 Alice 打算发送给 Bob 的数量(包括费用)要大。大多数交易都是这种情况,并且它与 UTxO 模型的工作原理一致。
通过交易,Alice 花费了 50.17 ADA,通过新创建的 UTxO,3.73 ADA 返回到她的账户。 UTxO 的可用值与发送者想要花费的值完全相同的情况很少发生。
该交易将产生 2 个输出 UTxO:Bob 50 ADA,Alice 3.73 ADA。
在下图中,您可以观察所描述的交易。两个输入 UTxO 将被完全用完,并且将从它们生成新的输出 UTxO。输出 UTxO 的价值将因交易费用而减少。 Bob 正好收到 50 ADA。发送交易后,Alice 的钱包中将有 2 个 UTxO。
卡尔达诺 UTxO 模型
现在您知道从用户角度来看 UTxO 是如何工作的。但这对于交易验证和全局状态(上下文)意味着什么?
UTxO是独立且不可变的对象,并且可以确保独占访问。一旦 UTxO 作为交易的输出被创建,它就保持不变,直到它被用于新交易。当它被花费时,它会被完全消耗,并创建一个新的 UTxO 作为新交易的输出。
验证器验证交易所需的上下文是验证时所有现有 UTxO 的集合。这组 UTxO 代表区块链的活动账本状态(全局状态)。
当一笔交易被提交时,它引用一个或多个 UTxO 作为输入,这些 UTxO 是打算被花费的。验证者的工作是检查:
所有输入 UTxO 确实是当前全局状态的一部分,即它们未花费并存在于账本中。
输入 UTxO 的总价值大于或等于输出 UTxO 的总价值,确保价值守恒(任何差额都将成为交易费用)。
该交易由输入 UTxO 的所有者正确签名,确保只有合法所有者才能花费 UTxO。
区块链的当前状态可以用所有未花费的 UTxO 的集合来表示,区块链的历史可以看作是所有 UTxO(包括已花费的和未花费的)的图。该图提供了区块链上发生的所有交易的完整、可审计的历史记录。
在下图中,您可以看到交易如何消耗 UTxO。输出 UTxO(绿色)变为输入 UTxO(红色)。所有未花费的 UTxO 代表当前全局状态或活动 UTxO 集(蓝色)。所有蓝色的 UTxO 都可以成为交易输入,即被花费。绿色 UTxO 已被花费,因此不能再次花费。
要验证新交易,无需了解区块链的整个历史记录,而只需了解未花费的 UTxO(蓝色 UTxO)的活动集。然而,获取一组活跃的 UTxO 通常需要处理整个区块链历史记录。
请注意,UTxO 的活动集还包括过去创建的那些 UTxO。
图中每个状态N,都产生了一个区块。在新的状态 N+5 中,包含来自状态 N+2、N+3 和 N+4 的 UTxO 的上下文将用于交易验证。在状态 N+5 的新区块中,将有交易 14 尝试花费交易 10 中的 UTxO,交易 15 尝试花费交易 11 中的 UTxO。
在状态 N+5 中,创建一个新块。因此,新的 UTxO 也会被创建,并且它们将被插入到 UTxO 的活动集中。
每个UTxO都可以独立并行处理,因此交易验证彼此独立。现在我们来解释为什么卡尔达诺中的交易是确定性验证的。
在 UTxO 模型中,交易的验证取决于其输入和输出,从而消除了对共享全局状态的需要。这似乎与前文有些矛盾,我在前文中指出,UTxO 的活动集构成了一个全局状态。
整个 UTxO 集对于交易验证来说并不是必需的,而只需要其中的一小部分,特别是输入 UTxO。 UTxO集中的剩余UTxO是不相关的。
交易的有效性仅取决于所消耗的 UTxO 是否有效且未花费,以及输入的总值是否与输出的总值一致。
交易输入是独立的不可变实体,因此验证是确定性的。高度确定地预测验证结果(包括输出 UTxO)是可行的。
可以在提交时验证交易,因为可以肯定的是,当同一交易被网络验证时,即当区块生产者将交易合并到区块中时,输入和输出将保持不变。一个新的候选块。两种情况下的验证结果可能是相同的。因此,如果交易通过本地验证,它也将通过网络验证。
该图描绘了一个验证器,该验证器构建了一个新块 N+4,其中包含事务 TX 9 和 TX 10。验证的输入是节点从其内存池中获取的事务以及每个节点维护的一组活动 UTxO。
新交易 TX 9 和 TX 10 消耗旧的未使用输出 UTxO(由蓝色框表示)以形成输入 UTxO(由红色框表示)。区块内的交易顺序并不重要,因为交易是自治的并且不会相互影响。在块 N+2 生成后不久调度的事务 TX 10 可以利用来自该块内的事务的 UTxO。新 N+4 块的创建导致从输入 UTxO 创建新的 UTxO。随着 N+4 区块添加到区块链中,网络过渡到新的全局状态。
如图所示,新区块 N+4 已被附加到区块链中,导致全局状态发生变化。从活跃的 UTxO 集中,区块 N+4 中包含的交易中使用的 UTxO 将被消除(绿色)。新创建的 UTxO(紫色)已添加到集合中。该块通过网络传播,因此 UTxO 的活动集将在网络中接受新块的所有节点上更新。
确定性交易验证
让我们回顾一下这个例子,Alice 的钱包里有 3 个 UTxO,每个 UTxO 的价值分别为 6.9 ADA、47 ADA 和 459.7 ADA。
Alice 打算向三个人(Bob、Carol 和 Dave)各发送 5 个 ADA,每次都会启动一个新交易。虽然 Alice 可以向三个接收者发送单个交易,但我们在本例中特意选择三个单独的交易来说明确定性的概念。所有交易都快速连续发送,并且都可以在后续块中处理。
如图所示,每笔交易都使用 Alice 钱包中的一个 UTxO(用红框表示),从而确保每笔交易都有独立的输入。每笔交易都会产生两个输出 UTxO,用蓝色框表示。一个价值 5 ADA 的 UTxO 被分配给接收者,第二个 UTxO(包含剩余的 ADA)被返回到 Alice 的钱包。输出 UTxO 在交易提交时确定。为了简单起见,本例中省略了交易费用。
成功的本地验证后,交易将在网络上传播。所有交易均已处理在下一个街区。在下图中,您可以观察 Alice 和所有接收者钱包中的 UTxO。
请注意,输入 UTxO 已被消耗(由绿色框表示)。生成了 6 个新的 UTxO。三个 UTxO 返回给 Alice。三名接收者每人收到一份 UTxO。
观察每笔交易中都保留了一个值。输入 UTxO 的总和等于所有输出 UTxO 的总和。这适用于所有交易。
交易验证的顺序并不重要。即使只有一个交易进入下一个区块并且其余两个交易包含在后续区块中(出于任何给定原因),验证结果也将保持不变。全局状态可以在 Bob 的交易验证与 Carol 和 Dave 的交易之间经历任何更改(跨多个块),而不会影响交易的验证。
由于输入 UTxO 彼此独立,交易的验证彼此独立。因此,交易验证是确定性的。尽管全局状态可以在事务提交和网络处理之间发生变化,但它不会影响结果。结果(有效或无效)可以仅根据交易本身及其输入 UTxO 来确定。
Cardano 的全局状态是独立且不可变的 UTxO 的汇编。鉴于这些 UTxO 的独立性,并行使用它们是可行的。这意味着可以同时验证多个交易,这可以显着提高网络的吞吐量和效率。
在图中,您可以看到每个交易如何专门消耗 UTxO 集合中的 UTxO。如果所有钱包和 DApp 都设计良好,就不会发生两笔交易消耗相同 UTxO 的情况。消耗的 UTxO 将从活动集中删除,并插入新的 UTxO。
图中未显示消耗 UTxO 并向集合添加新的 UTxO。该图仅说明交易如何引用输入UTxO
另一个优点是适用于标准交易的原则也扩展到验证器脚本的执行。构建通过本地验证(包括脚本执行)的交易是可行的。鉴于所有验证输入(包括 Datum 和 Redeemer)将保持一致,此类交易也很可能通过网络验证。换句话说,即使是脚本验证器的执行也不依赖于外部全局状态,而仅依赖于交易输入。唯一的区别是,在脚本交易的情况下,有更多的输入。
在图中,您可以看到卡尔达诺如何通过添加另一个块来并行处理多个交易。添加块会更改全局状态。从 UTxO 的活动集中(从全局状态)移除的输入 UTxO 的消耗以红色表示。新输出 UTxO 的活动 UTxO 集的扩展(全局状态的扩展)以绿色表示。
决定论以 UTxO 访问的排他性为条件。
对于标准交易,UTxO 的所有者是发起交易的人。钱包作为代理,确保每个 UTxO 仅包含在交易中一次。
在我们的示例中,Alice 拥有她想要花费的所有 UTxO。没有其他人能够使用有效签名建立交易。
如果钱包允许使用相同的 UTxO 作为输入提交两笔交易,则只有其中一笔交易有机会成功。然而,一个设计良好的钱包不会允许用户犯这样的错误。
对于去中心化应用程序 (DApp),代理可以是钱包以外的实体(例如批处理程序)。该实体负责在构建交易时提供对 UTxO 的独占访问。这确保每个 UTxO 只使用一次,从而保持系统的确定性和安全性。
在下图中,您可以看到带有代币 X 和 Y 的流动性池。批处理程序(在本例中是 DEX 的链下部分)正在尝试进行 2 次交换。具体来说,批处理程序 1 和批处理程序 3。批处理程序必须在链下相互通信,以确保对流动性池中 UTxO 的独占访问权(图中,链下通信以蓝色表示)。
如果批处理程序没有进行链下通信,他们可以尝试使用相同的 UTxO 作为交易输入。你可以在图中看到这种情况。两个不知道彼此意图的批处理程序可以各自提交一笔交易,其输入是来自流动性池的相同 UTxO。两笔交易都会通过本地验证,但只有其中一笔交易有机会通过网络验证。
如果一个代理或多个彼此独立的代理使用相同的输入 UTxO 提交有效交易,则卡尔达诺交易可能会失败。这种情况是因由设计不良的钱包或 Dapp 造成的。
总之,我们可以说卡尔达诺交易可以被认为是无状态的。
基于以太坊账户的模型
基于以太坊账户的模型很容易描述,因为其行为与常规银行账户类似。以太坊中的每个账户都有一个与之关联的状态,其中包括其当前的以太币 (ETH) 余额。该余额直接存储在账户状态中,并且可以通过交易进行更新。
在下图中,您可以看到状态之间的转换。注意用户帐户余额的变化。为简单起见,只有一个余额代表 ETH。
以太坊的全局状态可以被视为网络上所有账户及其当前不同资产余额的数据库。每次添加新块时,系统的状态都会根据该块中包含的所有事务进行更新。
该全局状态本地存储在以太坊网络中的每个节点上。
创建交易时,它会指定发送者帐户、接收者帐户、要传输的以太币数量,以及可选的一些数据和 GAS。如果接收方是合约账户,则交易会触发合约代码的执行。
在图中,您可以看到一个将全局状态 N 更改为状态 N+1 的事务。该交易改变了 Alice 和 Bob 账户中的余额。
处理交易时,以太坊网络会检查发送者的帐户是否有足够的余额来支付转账和天然气费用。如果检查通过,网络会从发送者的余额中扣除适当的金额,并将其添加到接收者的余额中(如果接收者是合约账户,则添加到合约的余额中)。
以太坊的全局状态会随着每个处理的交易而更新,并且此更新的状态存储在添加到以太坊区块链的每个新块中。
当处理交易时,EVM 根据当前状态和交易计算新状态。然后,这个新状态将成为下一个事务的当前状态。这样,以太坊区块链的状态就随着每笔交易而不断演变。
如图所示,最近添加的块是 N+3,使得全局状态为 N+3。网络尝试转换到新的状态 N+4,从而构建新的 N+4 块。值得注意的是,区块 N+4 中交易 TX 1 的验证(由绿色框表示)取决于全局状态。 TX 1 是区块生产者节点尝试包含在新区块 N+4 中的第一笔交易。
全局状态由所有账户和智能合约的当前状态表示。
一旦交易 TX 1 被合并到区块 N+4 中,后续交易就可以被验证,这将取决于全局状态和之前的交易。
从图中可以看出,交易 TX 2 的验证不仅取决于区块 N+3 中有效的全局状态,还取决于交易 TX 1。
以太坊区块生产者节点按顺序处理交易。添加到新建块的每个附加事务都会更改节点的当前全局状态。当区块扩散到网络时,验证者节点必须进行相同的操作,即按照区块生产者节点将交易插入区块的顺序准确地顺序验证交易。
以太坊交易失败的原因之一是交易验证时全局状态的不可预测性。当交易正在构建时,不可能预测其结果。交易验证期间的全局状态可能与交易构建期间的状态不同。在交易提交和验证之间,交易参与者的账户余额可能会发生变化。账户余额的这些变化可能会阻止交易按照提交者最初的预期执行。
该图说明了在区块 N+2(状态+2)周围发生的交易 TX 10 的提交与交易 TX 9 验证之后的后续交易验证之间的时期。在此期间,全局发生了许多变化。状态发生。事务 TX 10 的验证取决于全局状态和事务 TX 9 引入的更改。事务 TX 9 或任何先前的更改可能会导致事务 TX 10 失败。
交易输入是账户余额,这是共享资源。这意味着用户提交交易时的账户余额可能与交易验证时的账户余额不同。交易输入不像 UTxO 模型那样具有确定性。执行本地验证来显示交易稍后是否通过网络验证是没有意义的。
解释差异的最好方法是回到以 Alice 为例,她想向 3 个收件人发送 5 个 ETH。以以太坊为例,所有 3 个提交的交易都有相同的输入,即 Alice 账户的余额。
交易验证按顺序进行,每次从 Alice 的余额中扣除 5 ETH 并记入其中一位接收者的账户。
添加包含所有交易的新块后,用户帐户将如图所示。
这张图片不准确。稍后我们将展示状态之间的转换到底是如何发生的。
以太坊确实确定性地验证交易,即给定相同的输入(交易数据)和相同的状态(以太坊区块链的当前全局状态),EVM 将始终产生相同的输出。然而,交易验证的结果是不确定的。验证结果可能与用户的期望不同。
在交易提交期间,用户无法预测交易验证时的全局状态。因此,不可能高度确定地估计验证结果。这只能是假设。
如果多个用户可以访问相同的余额,则很容易发生从同一余额中扣除一定金额的多笔交易。假设金额为 1200 ETH。三个用户独立提交了一笔扣除金额500 ETH的交易。
在提交交易时,上下文可能是可以从余额中扣除给定金额的。尚未将任何一笔交易添加到全局状态(被网络接受)。然而,一旦 2 笔交易得到验证,第三笔交易就会失败。两笔交易总共减去了1000 ETH。余额只有 200 ETH。所以不可能再次提取500 ETH。 2 笔交易成功,1 笔失败。
图中可以看到,Bob 和 Carol 想要从申请账户中获取(扣除)500 ETH 的交易只有 2 笔成功。戴夫运气不好,他的交易失败了。
即使这张图也不完全准确,因为它没有捕捉到状态转换期间余额的逐渐变化。下一张图片将澄清这一点。
现在让我们解释一下将交易插入区块时顺序的重要性。即使戴夫先于鲍勃和卡罗尔提交了交易,他也可能不走运。这取决于区块生产者节点将交易插入新区块的顺序。
类似地,如果戴夫和卡罗尔的交易先后被插入到区块中,那么鲍勃的交易就会失败。
当 Bob、Carol 和 Dave 提交交易时,他们无法确保网络成功验证交易,因为不可能访问专门用于他们交易的部分余额。这与 UTxO 模型不同,因为它允许交易独占访问输入 UTxO。
在图中,您可以看到 4 个状态,代表块准备中的状态变化。白交易在内存池中。 2 笔绿色交易是从状态 N+1 和 N+2 的 DApp 余额中减去 500 ETH 的交易。红色是在状态 N+3 下失败的事务。您可以看到 DApp 余额(逐渐减去该值)在每个新状态下如何变化。一开始这个值是1200,然后是700,最后只有200。
请注意,在状态 N+2 中,所有交易都引用相同的 DApp 余额,但其值与状态 N+1 和 N 中不同。
以太坊全局状态可以认为是所有账户和合约账户的状态。账户的状态包括其以太币余额,对于合约账户,还包括智能合约的当前状态,包括合约中定义的任何变量。
以太坊基于账户的模型要求交易按顺序处理。这是因为每笔交易都可能影响网络上任何帐户的状态,并且交易的结果可能取决于其处理的顺序。因此,为了确保一致性并防止双重支出,每次验证都必须“锁定”全局状态,这意味着一次只能处理一笔交易。
需要全局状态的锁定来确保系统的完整性。如果没有这种锁定机制,两个交易可能会同时尝试花费相同的资金,从而导致全局状态不一致,并可能导致双重支出。以太坊通过顺序处理交易并在每次交易后更新全局状态,确保每笔交易都以一致的状态处理,并且交易的顺序不会影响系统的最终状态。
该图展示了以太坊的全球状态。每笔交易都会修改两个余额。红色箭头表示从余额中提取价值,而绿色箭头表示向余额添加价值。值得注意的是,一些按顺序处理的交易会调整相同的余额。例如,TX 1 和 TX 2 都从同一余额中扣除价值,而 TX 3 和 TX 4 则为同一余额增加价值。
在图中,您可以看到以太坊如何按顺序处理交易以及全局状态的余额在每个状态中如何变化。红色余额表示从余额中减去一个值,而绿色余额表示向余额中添加一个值。两个余额在处理的交易中同时(自动)变化。
以太坊交易可以被描述为有状态的。
结论
如果没有上下文或全局状态,则只能验证交易的某些属性,例如来自私钥所有者的加密签名。花费资源的能力需要上下文。网络中的所有节点必须维护一致的全局状态,该状态在节点构造新块时以及网络中其他节点的后续验证期间使用。
有时有人说卡尔达诺缺乏共享的全局状态。就以太坊而言,全球状态的重要性是一个相当恰当的类比。然而,验证交易的上下文对于卡尔达诺也至关重要。就卡尔达诺而言,全局状态由一组唯一且独立的对象组成,这些对象只能在单个事务中使用一次。输入 UTxO 的消耗导致新 UTxO 的生成。就以太坊而言,余额是持久实体,可以在任何给定时间通过交易重复修改。两个以太坊交易不能同时修改同一余额。因此,事务的顺序处理是必要的。就卡尔达诺而言,并行性是可行的,因为交易是相互独立的。
确定性对于 ZK 密码学是有利的。例如,在 Cardano 生态系统中,可以创建不需要排序器的 ZK Rollup。