对开发者的挑战

Challenges for Cardano Developers | Cardano Explorer (cexplorer.io)

开发人员可能很难充分利用 UTxO 模型,因为他们必须考虑并行化。 卡尔达诺不允许在智能合约的链上部分维护单个全局应用程序状态。 每个 UTxO 可以代表应用程序的一部分状态,并且可以独立并行处理。 理论上,这可以实现高吞吐量和可扩展性,但应用程序必须处理与管理并发事务相关的复杂性。 开发者在卡尔达诺上面临哪些挑战?

当并行化很容易时
在扩展未花费交易输出(eUTxO)模型中,每个 UTxO 都可以独立并行处理。 UTxO 的支出不依赖于任何全球 Cardano 状态。 如果满足支出条件,则将从输入 UTxO(或多个输入 UTxO)通过交易创建一个或多个输出 UTxO。 输入 UTxO 必须完全消耗。 输出 UTxO(s) 必须具有与输入 UTxO(s) 相同(或更小)的值。

如果 Alice 向 Bob 发送 100 ADA,则 UTxO 的支出仅取决于 Alice 的见证人。 如果一百个其他用户发送类似的交易,则使用 100 个其他唯一输入 UTxO。 每个交易发送者都是输入 UTxO 的所有者。 交易和输入 UTxO 之间不存在依赖关系。

交易和输入 UTxO 之间不存在依赖关系,因为所有发送者都是相互独立的。 他们自己独立决定发送 100 ADA。 所有 100 笔交易都可以插入到同一个区块中,并且将被评估为有效。

一笔交易消耗一个或多个 UTxO 作为输入,并产生一个或多个新的 UTxO 作为输出。 这个简单的规则既适用于 Alice 和 Bob 之间的价值转移,也适用于应用程序的情况,稍后您将看到。

在图中,您可以看到 3 笔相同的交易。 如果有 100 笔交易,看起来是一样的。 不要对发件人始终是 Alice 而收件人是 Bob 这一事实感到困惑。 每次都是不同的爱丽丝和不同的鲍勃。 该图旨在证明交易发送者之间不需要同步的事实。 如果交易有效,它们就不会失败,并且都会进入区块链。

等我们解释完 DEX 的工作原理之后,你可能就会明白这张图的含义了。

卡尔达诺网络可以按任何顺序验证交易,因为交易彼此独立。 从网络吞吐量的角度来看,这是一个优点,因为共识不依赖于顺序处理。

如何创建并行应用程序?
理想情况下,应用程序开发人员应该创建这样的智能合约逻辑,其在并行化方面的行为与 100 个发送者 (Alice) 发送交易时类似。 然而,这几乎是不可能的。

让我们通过一个使用流动性池的 DEX 示例来阐明这一点。

流动性池由产生输出 UTxO 的多个交易填充。 这些 UTxO 代表流动性池中的代币。 随着每个新添加的区块到账本,流动性池中 UTxO 的组成可能会发生变化。

在图中,您可以看到一个包含一对代币 X 和 Y 的流动性池。在流动性池中,有多个带有代币 X 和 Y 的 UTxO。在下一个区块中,Alice、Bob 和 Carol 将新的带有代币 X 和 Y 的 UTxO 放入其中。 X 进入流动性池,Dave、Eve 和 Frank 用代币 Y 放入了一些 UTxO。由于没有发生交换,因此没有从池中移除 UTxO。

为了进行交换,DEX 必须消耗流动性池中的 UTxO(或更多 UTxO)作为每种代币类型的输入。 这些 UTxO 必须完全用完。 如果交换不需要 UTxO 中的所有代币,则剩余代币必须作为新的 UTxO 返回到流动性池。

就 DEX 而言,许多操作是同时并发进行的。 用户将代币(流动性提供者)发送到流动性池。 提供商是发送者,DEX 是接收者。 同时,其他用户可以发送交换请求。 他们是向 DEX 提供 X 代币并希望获得 Y 代币的发送者。 或相反亦然。

多个参与者可以在短时间内提交交换请求。 这些互换可能与单个流动性池相关。 因此,参与者之间存在着相互依赖。

澄清 DEX 到底是什么也是一个好主意。 Cardano 中的复杂智能合约(如 DEX)由两部分组成:在区块链上执行的链上逻辑和在服务器(或本地钱包)上执行的链下逻辑。

在图中,你可以看到一个由链上和链下逻辑组成的DEX。

虽然链上逻辑的执行在卡尔达诺网络中自然是去中心化的,但该团队负责 DEX 链下逻辑的去中心化。 DEX 的链下部分不是(不应该)仅由一个代理组成,而是由多个代理组成。

就 DEX 而言,这些代理称为批处理程序。 他们一个负责执行交换。 批处理者创建满足在流动性池中花费 UTxO 的条件的交易,并按照交换参与者要求的比例转移资产。

卡尔达诺不允许在智能合约的链上部分维护统一的全局应用程序状态。 不过,这在技术上是可能的。

如果开发人员要将 DApp 的整个状态存储在单个 UTXO 中,那么他们本质上会创建一个类似于以太坊基于帐户的模型中存在的全局状态。 这可能会限制 DApp 的并发性和吞吐量。 这种方法无法充分利用 EUTxO 模型的优势。

在 DEX 的链上部分,UTxO 和相关 Datum 代表应用程序状态。 因此,状态分布在 UTxO 之间。 如果 DEX 要具有统一的全局应用程序状态,则必须跨批处理程序进行链下维护。

在图中,您可以看到一个包含代币 X 和 Y 以及 3 个批处理程序的流动性池。 全局应用程序状态和批处理程序之间的状态同步以蓝色表示。 应用程序状态由链上数据(与 UTxO 相关的数据)和批处理程序(代理)维护的链下应用程序状态组成。 批处理程序相互通信以同步统一的全局应用程序状态。

这是必要的,因为批处理程序(代理)访问相同的资源,即流动性池。 他们需要使用流动性池中的输入 UTxO(以执行交换),并且可能会发生两个(或更多)代理想要使用相同的 UTxO。 可能会出现争用问题。

现在是时候记住文章中的第一张图片了。 当 100 个发送者提交一笔交易时,他们无法相互竞争相同的 UTxO,因为每个发送者都使用自己的 UTxO。 流动性池中的 UTxO 是共享资源,即由多个代理访问的资源。

一般来说,争用是指多个线程或进程(在我们的例子中为代理)尝试访问同一资源的情况,其中至少一个线程或进程的运行速度比其他线程或进程未运行时的速度慢。

在我们的例子中,存在这样的风险:两个代理将构建一笔交易,其中他们使用来自流动性池的相同输入 UTxO。 在这种情况下,卡尔达诺只会接受一笔交易。 第二个失败了。

在图中,您可以看到批处理程序 1 和批处理程序 3 正在尝试使用带有代币 X 的相同 UTxO。发生了争用。 批处理程序显然同步性很差,并且不知道彼此使用此特定 UTxO 的意图。 如果构建 2 笔交换交易,则一笔会成功,另一笔会失败。

DEX 的目标是使交换能够并发执行,即各个代理可以同时构建交易,并且在选择 UTxO 时不会发生争用。

为了防止交易失败,代理之间必须存在链下通信或某种其他形式的同步。 换句话说,代理必须保持 DEX 的全局状态一致。

各个代理必须以某种方式在池中保留 UTxO,以便其他代理不会使用相同的 UTxO。 或者,它可以以这样的方式工作:在每个下一个块(20 秒)内,所有交易都将由单个(随机选择的)代理构建。 虽然这种方法是分散的,但并发性较低。

从图中可以看到,批处理程序 1 和批处理程序 3 以独占方式选择了带有代币 X 和 Y 的 UTxO 进行交换,因此不存在争用。 交换 1 和 2 同时运行。 没有争用,因为所有批处理程序都相互同步全局状态。

请注意,代币 X 的市场价值恰好是代币 Y 市场价值的 2 倍,巧合的是,流动性池中有合适的 UTxO 进行配对。 交换 1 消耗 100 个 X 代币和 50 个 Y 代币。 交换 2 消耗 200 个 X 代币和 100 个 Y 代币。 如果池中没有具有 100 X 代币的 UTxO,那么第二个最合适的将是具有 114 X 代币的 UTxO。 这意味着 14 X 代币必须作为新的 UTxO 返回到流动性池。

其他挑战之一是适当选择用于互换的 UTxO,本文不会进一步讨论。 对于以太坊,这不是问题,因为基本上只更新账户余额。

可以用与使用流动性池完全不同的方式来思考这个问题。 可以连接各个交换候选者,而不是将 UTxO 放入一个池中。 然而,为了简单起见,我们在本文中只讨论流动性池。

在 Cardano 上设计一个可以并行处理 UTxO 同时保持去中心化的 DEX 需要解决并发问题。 这是开发人员面临的挑战之一。

在文章中,我们展示了一种可能的解决方案,即使用链下和链上组件。 链下组件可用于正确形成交易与链上代码交互。 通过链下通信确保正确性,从而实现应用程序全局状态的同步。

其他可能的方法之一是创建一种算法,为用户提供提交所需操作的独占访问权限。 该算法随后可以在尊重时间和公平性的情况下将所有动作合并在一起。

开发人员可以在应用程序的链上部分维护单个状态,也可以将其拆分到多个 UTxO 中。 拥有单一的链上状态很容易,因为它更容易保持一致性。 应用程序的所有部分都使用相同的数据。 将链上状态拆分到多个 UTxoS 可以增加并发性,但它会带来一些挑战。 管理多个 UTxO 会增加智能合约逻辑的复杂性。 有必要确保某种形式的同步来确保正确性(避免争用)。

问题的核心在于去中心化环境下实现并行化。

应用程序逻辑始终链接到 UTxO。 每个 UTxO 代表一个可以并行处理的独立状态。 正如我们在文章中所解释的,只有实现某种可靠的同步形式,这才有可能。

如果只有一个批处理程序或链下代理,它可以管理 DEX 的状态并准备交易,而不必担心并发问题。 这可能会带来更快的交易处理和更高的吞吐量。

然而,这种做法本质上会将 DEX 的链下部分中心化,这违背了去中心化的原则。 因此,挑战是实现链下去中心化,同时仍保持高性能并避免并发问题。

以太坊开发者也面临挑战
以太坊使用基于账户的模型,智能合约具有通过交易更新的全局状态。 全局状态是驻留在以太坊区块链上特定地址的代码(函数)和数据(状态)。

DEX 的全局状态可以代表订单簿的当前状态,包括所有未平仓的买入和卖出订单。 当提交新的交换交易时,它代表了此全局状态的潜在变化。 但是,只有当交易包含在区块中并由网络验证后,此更改才会被接受。

以太坊中的交易是按顺序处理的,一次处理一个。 这意味着以太坊世界中不存在并发,因此不存在并发问题。 当涉及到并发和并行性时,这种顺序处理使得在以太坊上设计 DEX 变得更简单,因为开发人员不必处理管理并发事务的复杂性。

然而,这种顺序验证无法利用并发性。 并行执行交易是不安全的,因为合约之间可能存在依赖关系。 如果一个合约依赖于另一个合约的结果,那么每个验证者必须以相同的顺序执行这些合约。

块内交易的顺序由验证器确定。 他们可以选择根据 GAS 价格、随机数和首次出现时间等因素对交易进行排序。 因此,虽然 DEX 可以创建一个包含许多交换的队列,但它无法确定这些交换将按什么顺序执行。

这种不确定性可能会导致交易因竞争条件而失败,即不同的交易竞争消耗相同的流动性。 为了解决这个问题,一些 DEX 实施了滑点容忍和交易截止日期等机制,以增加交易成功执行的可能性。

当多个用户尝试同时交换代币时,可能会出现竞争情况。 例如,假设两个用户都想将 ETH 兑换成 USDT,而流动性池中的 USDT 只够进行其中一项兑换。 两个用户几乎同时提交交换交易。 以太坊验证器将决定这些交易包含在区块中的顺序。

如果用户 A 的交易先被纳入,则他们的交换将会完成,并且池中将没有足够的 USDT 供用户 B 的交换使用。 当以太坊网络尝试处理用户 B 的交易时,它将失败,因为它无法完成交换。

结果取决于两个或多个操作(交换)的相对时间。 尽管以太坊按顺序处理交易,但当多个交易依赖于共享资源(例如 DEX 中的流动性池)并大约在同一时间提交时,竞争条件仍然可能发生。

请注意,即使流动性池仅由一个 DEX 管理,竞争条件也可能发生。 这是因为竞争条件不是由 DEX 本身引起的,而是由交易处理的性质引起的。

换句话说,以太坊上的应用程序可以选择处理交易的某种顺序,但这并不在它们自己的控制之下。就卡尔达诺而言,交易按照先到先得的原则进行处理。 然而,有必要指出的是,矿池不必遵循此规则,并且没有机制可以强制矿池从内存池中正确选择交易。

让我们简要比较一下这两个平台上的开发人员面临的挑战。

在 Cardano 上构建 DEX 或任何其他去中心化应用程序时,挑战之一是跨多个 UTXO 和代理管理和同步状态片段。 这允许更高的吞吐量和可扩展性,但也引入了与管理并发事务相关的额外复杂性。

应用程序可以在链下管理和更新其状态,然后定期将状态提交到区块链。 这种方法可以帮助减少区块链的负载并提高交易速度。 此外,链下同步还可以促进链上交易的并行执行。 DEX可以并行准备多个交易,然后将它们提交到区块链上执行。

快速的链下同步可能会带来链上的高可扩展性和并行性。 开发人员尝试通过同步来实现最大可能的并行化。

这与以太坊基于帐户的模型不同,在以太坊中,整个应用程序的状态存储在单个全局状态中。 全局状态可以保存 DEX 所需的所有数据并通过交易进行更改。 由于以太坊按顺序处理交易(调用),因此开发人员根本不必担心并发性。

然而,问题在于,某种形式的并行性表现为用户可能尝试在以太坊上的 DEX 中消耗相同流动性的行为。 他们基本上是在争夺单一资源,类似于可能想要消耗相同 UTxO 的批处理程序。

以太坊 DEX 开发人员无法完全阻止与多个用户尝试消耗相同流动性相关的竞争条件。 流动性池的状态(即有多少流动性可用)由区块链中包含的交易决定。 用户无法实时知道他们的交易是否会成功,因为以太坊按顺序处理交易,并且顺序由验证器决定。

开发人员可能会尝试创建某种机制来阻止用户提交很有可能失败的交易。 然而,这是一项非常艰巨的任务。

请注意,卡尔达诺的行为具有确定性,与以太坊不同。

结论
为了充分利用 UTxO 模型和并行化,有必要改进 Ouroboros 权益证明。 卡尔达诺网络必须能够一次性验证和预先批准大量交易。 如果每 20 秒将数十个交易插入到区块中,那么并行处理它们并不重要。 可扩展性仍然相对较低。 输入背书者将为 PoS 共识带来必要的改进,其中 UTxO 模型将更加闪耀。