p2p、分布式,区块链笔记(IPFS): 论文Merkle-CRDTs : Merkle-DAGs meet CRDTs

papercode
Merkle-CRDTs : Merkle-DAGs meet CRDTs添加链接描述
  • 本文研究了实现冲突自由复制数据类型(CRDTs)的传输和持久化层的Merkle-DAGs,并提出了Merkle-CRDTs这一术语。本文概述了相关概念、属性、优势和局限性。研究表明,Merkle-DAGs可以充当逻辑时钟(logical clocks),使Merkle-CRDTs在具有弱消息层保证和大量副本的系统中简化收敛数据类型的设计和实现。Merkle-CRDTs可以利用分布式技术(如DHTs和PubSub算法)的可扩展性,利用内容寻址的安全性和去重特性。这类内容导向的系统可能包括对等内容交换、移动设备、物联网设备之间的同步应用,或运行在网页浏览器中的用户应用。

I. INTRODUCTION

  • 区块链技术的出现推广了点对点网络以及被称为Merkle DAGs的加密有向无环图的使用,以在加密货币等应用程序中实现全球分布式和最终一致的数据结构。在这些系统中,Merkle DAG是一种内容寻址的数据结构(content-addressed data structure),用于提供因果关系信息和对象的自我验证,这些信息可以在无信任的对等环境中轻松有效地共享。在对抗场景中,需要维护和应用某些规则来向区块链添加新块,这通常需要使用共识算法(consensus algorithms)。

  • 在分布式系统中获得最终一致性的另一种方法是使用无冲突复制数据类型(CRDT)[30],[33]。CRDT在非对抗性场景中很有用,在这种场景中,已知参与的副本行为正确。CRDT依赖于数据对象本身的一些属性,这些属性使其能够收敛到一个全局的、唯一的状态,而不需要达成共识。CRDT有两种主要形式:

    • 基于状态的CRDTs1(Also known as Convergent CRDTs or CvRDTs)——其中副本的状态形成一个连接半格(join-semilattice),并在其提供的保证下合并
    • 基于操作的CRDTs2(Also known as Commutative CRDTs or CmRDTs.)——其中交换操作由每个副本广播并应用于本地状态。此外,δ-CRDT是基于状态的CRDT的优化,以减少副本发送的有效载荷的大小。
  • Merkle DAG和CRDT都提供了有趣的特性:前者允许分布式系统利用内容寻址层进行数据的解析/可发现性和自我验证,而不管源位置如何;后者允许全球国家趋同,而不需要通常复杂而昂贵的共识机制。通过在Merkle DAG节点中嵌入CRDT对象,我们获得了两个世界的最佳属性,即我们获得了一个可以利用DAG作为逻辑时钟的收敛系统。此逻辑时钟由每个复制品提供和构建,无需协调。副本可以在没有交付保证的松散网络环境中无中断运行。正如我们将看到的,基于Merkle CRDT的系统完全不知道系统如何在副本之间宣布和发现数据,因此能够利用DHT和PubSub机制提供的不同方法,而不依赖于它们的特定版本。这与传统的共识算法形成鲜明对比,例如Raft,它与特定的消息传播协议相关联。

  • InterPlanetary文件系统提供了一个内容寻址的对等文件系统[14],它支持Merkle DAG与任意格式或有效载荷的无缝同步,使其成为PeerPad或OrbitDB等不同类型分布式应用程序的强大构建块,这两种应用程序都由CRDT和IPFS提供支持。IPFS不仅为处理内容寻址数据(如我们将使用的Merkle DAG)提供了合适的环境,还允许我们的CRDT应用程序与分布式系统的较低级别完全分离:网络传输、发现和数据传输设施都是模块化的,可以独立交换和调整。利用这种系统大大简化了CRDT层的设计和优化,使其能够很好地适应其要服务的用例。

  • 在本文中,将我们所说的Merkle CRDT形式化。目的是概述它们的性质、优点和局限性,以便为未来在该领域的研究和优化奠定基础。例如,Merkle CRDT允许在网络中的真实系统中构建完全分布式的键值存储,没有消息传递保证,并且完全灵活的副本集可以随时增长和收缩,而不会影响CRDT层。

  • 本文的其余部分组织如下:在第二节中,我们首先介绍相关的背景概念和现有技术。在第三节中,我们展示了系统模型的特点,并介绍了存储和同步Merkle CRDT所需的设施

文章贡献:

  • 本文的贡献如下:

    • 本文将Merkle Clocks定义为基于Merke DAG的逻辑时钟,以表示分布式系统中的因果关系信息。使用Merkle DAGs嵌入因果关系信息是加密货币和Git等源代码控制系统的核心,但它们很少被单独视为一种逻辑时钟。本文证明了Merkle时钟可以用来代替CRDT传统上使用的其他逻辑时钟,如版本向量(version vectors)和向量时钟(vector clocks)。本文证明,Merkle Clocks实际上可以被视为CRDT对象本身,可以同步和合并,本文可以正式证明不同副本之间的最终一致性。
    • 本文将Merkle CRDT定义为CRDT有效载荷的通用传输和持久层,它利用Merkle时钟的特性,使用DAG同步器和广播器通过设计提供每个对象的因果一致性(causal consistency)。这使得在消息传递层保证较弱且有大量副本的系统中可以使用简单的CRDT类型。
  • 我们在这篇论文中的目的是表明,副本之间的最终一致性可以在所有对等体都相等的情况下独立于底层传输机制来实现。这意味着Merkle CRDT可以在任何底层对等体发现和路由系统(例如DHT、PubSub)之上实现。我们认为,这是一个非常强大的概念,有可能导致新的最终一致性系统设计。它也与传统的共识算法(如Raft)有着根本的不同,在Raft中,在任何给定的时间都需要有一个从所有其他对等体收集最新状态的领导者对等体。因此,我们不提供对这些传统方法的评估,因为它们的要求和设计原则根本不同。

  • 在第四节中,我们介绍了Merkle Clocks,并在前几节的基础上,在第五节中定义了Merkle CRDT。我们讨论了不同的CRDT有效载荷(无论是基于操作、基于状态还是基于δ)如何从Merkle CRDT中受益。最后,我们在第六节中描述了Merkle CRDT的一些局限性和低效性,并介绍了克服它们的技术。

II. BACKGROUND & RELATED WORK

A. Eventual consistency 最终一致性

  • 分布式系统中的最终一致性(EC)是指系统副本之间的状态可能不同,但如果有足够的时间,也许在网络分区、停机和其他可能性得到解决之后,系统设计将确保状态在任何地方都是相同的。
  • 最终一致性定义的主要弱点是,它无法保证共享状态何时收敛,或者在此之前允许各个状态发散多少。强最终一致性(SEC)通过建立额外的安全保证来解决这些问题:如果两个副本收到了相同的更新,它们的状态将是相同的。
  • 共识算法,或者对本文更重要的是无冲突复制数据类型(CRDT),是在分布式系统中实现(强)最终一致性的方法。

B. Merkle DAGs

  • 有向无环图(DAG)是一种边有方向且不允许循环的图。例如,像A→B→C这样的链表是DAG的实例,其中A引用B,以此类推。我们说B是A的子节点或后代,节点A与B有链接。相反,A是B的父节点。我们称DAG中任何其他节点的非子节点为根节点。Merkle DAG是一个DAG,其中每个节点都有一个标识符,这是使用SHA256等加密哈希函数对节点内容(节点携带的任何不透明有效载荷及其子节点的标识符列表)进行哈希运算的结果。这带来了一些重要的考虑因素:

    • a) Merkle DAGs只能从叶子构建,也就是说,从没有子节点的节点构建。父母被添加在孩子后面,因为必须提前计算孩子的标识符才能将他们链接起来。
    • b) Merkle DAG中的每个节点都是(子)Merkle DAG本身的根,并且该子图包含在父DAG中
    • c) Merkle DAG节点是不可变的。节点中的任何更改都会改变其标识符,从而影响DAG中的所有升序,本质上创建了一个不同的DAG。
      .
  • 通过哈希值识别数据对象(如Merkle DAG节点)称为内容寻址。因此,我们将节点标识符命名为内容标识符或CID。

  • 例如,在前面的链表中,假设每个节点的有效载荷只是其后代的CID,则为:A = Hash(B) → B = Hash© → C = Hash(∅)。Hash函数的属性确保在创建Merkle DAG时不存在循环。

  • Merkle DAGs是自我验证和不可变的结构。节点的CID明确地链接到其有效载荷的内容及其所有后代的内容。因此,具有相同CID的两个节点明确表示完全相同的DAG。这将是有效同步Merkle CRDT的关键属性,而无需复制完整的DAG,如IPFS等系统所利用的。

  • Merkle DAGs被广泛使用。像Git[17]这样的源代码控制系统[13]使用它们来高效地存储存储库历史,从而能够消除对象的重复并检测分支之间的冲突。在像Dy-namo[19]这样的分布式数据库中,Merkle树用于有效比较和协调副本之间的状态。在哈希历史[22]中,内容寻址用于指代表示状态的Merkle树。

  • Merkle DAG也是区块链的基础块——它们可以被视为具有单个分支的Merkle DAG——以及它们最常见的应用:加密货币(例如[3]、[1]、[18])。比特币等加密货币[26]受益于链中编码的嵌入式因果关系信息:链中较深区块的交易总是发生在较早区块的交易之前。加密货币的主要问题之一是让所有参与的同行就链的尖端/头部/根部达成一致。除此之外,一些交易的非交换性质需要一个共识机制,该机制强制只有有效的块才能成为新的根。

  • 许多这些系统的一个共同点是,Merkle DAG隐式地嵌入了因果关系信息。DAG可以显示某个事务先于另一个事务,或者Git提交需要合并而不是快速转发。这将是我们在Merkle CRDT中使用的属性之一,本文对此进行了明确阐述,并与其他称为逻辑时钟的因果编码机制进行了对比。

C. Logical clocks

  • 因果收敛系统的设计涉及不同副本之间发散状态版本的协调,例如,当事件同时发生时。
  • 这要求我们能够识别两个事件是否实际上是同时发生的,以及两个状态是否由于并发更新或其他原因而实际上不同,例如一个副本收到了更多的更新。
  • 问题本质上是跟踪不同事件发生的顺序。例如,给定一个值在不同副本中多次写入寄存器,我们希望注册表中的最终值是最后一次写入的值。
  • 理想情况下,我们应该能够对系统中的所有事件进行排序,以便我们能够确定哪个是寄存器的实际最后一次更新。
  • 用时间戳标记事件可以给我们提供以下信息:如果所有事件都有时间戳,任何副本都可以建立它们发生的顺序,并使用该信息来决定最终状态应该是什么样子。然而,在分布式系统中,不可能可靠地使用时间戳[27],因为并非每个副本都能与全局时间完美同步。“挂钟”也很容易被模拟或欺骗,这在没有信任的对等系统中是有问题的。
  • 逻辑时钟是全球时间的替代品。它们提供了对分布式系统中不同参与者已知的事件之间的因果信息进行编码的方法。
  • 基本思想是,尽管我们可能不知道所有事件在全球范围内发生的顺序,但每个副本至少知道自己发出的事件的顺序。接收到该信息的任何其他副本都会知道,稍后由其自身发出的任何事件都发生在这些事件之后。本质上,这就是所谓的因果史。
  • 逻辑时钟是因果历史的表示[12],并提供事件之间的偏序。也就是说,给定两个事件a和b,逻辑时钟应该能够告诉我们a是否发生在b之前(a→b),反之亦然(b→a),或者a和b同时发生( a ∣ ∣ b a || b a∣∣b)。
  • 逻辑时钟的实际实现通常涉及附加到系统中每个事件的元数据。最常见的逻辑时钟形式之一是版本向量[28]:每个副本都维护并广播一个向量,跟踪所有副本的状态在哪个版本上。当副本对状态进行修改时,它会增加其版本。当一个副本合并来自不同副本的状态时,它会在本地版本和其他副本随事件提供的版本之间取最高值。因此,给定两个事件a、b,其版本向量为 V a V^a Va V b V^b Vb:a→b如果 V i a ≤ V i b V^a_i ≤ V^b_i ViaVib,对于向量中的每个位置i。如果a ¬ \neg ¬ →b和b ¬ \neg ¬→a,根据这个定义,a和b是并发的。
  • 正如我们所看到的,版本向量是紧凑的,因为它们不需要存储完整的因果历史,而只需要一个数字来指示每个副本的历史有多长。版本向量取决于副本的数量,因此它们可能需要进一步优化,以便在有许多副本或副本数量不稳定的情况下正常工作。
  • 本文证明了Merkle DAG可以作为逻辑时钟。正如我们将展示的那样,Merkle Clocks提供了一组不同的属性,但编码了关于事件的相同因果信息。

D. Conflict-Free Replicated Data Types (CRDTs)

  • CRDTs(可重复合并数据类型)是一种数据类型,通过对状态和/或修改操作的特定要求,确保在分布式系统中不同副本之间达到强一致性。此外,CRDTs 还具有单调性(monotonicity)的特征。应用于数据类型的单调性概念是指每次更新都是一种“膨胀”,即状态会不断增加(不是在大小上,而是在相对于先前状态的意义上)。这意味着状态之间总是存在一种顺序关系。单调性意味着,无论更新发生的顺序如何,都不需要对状态进行回滚。
  • CRDTs 主要有两种类型:基于状态的 CRDTs 和基于操作的 CRDTs。在基于状态的 CRDTs 中,系统中的所有状态——即不同副本和不同时间的状态——形成一个单调的联合半格(join-semilattice)。这意味着,对于任何两个状态 X 和 Y,它们都可以被“合并”(⊔),结果是一个新的状态,对应于 X 和 Y 的最小上界(LUB)。换句话说,每次对状态的修改都必须是“膨胀”,且两个状态 X 和 Y 的联合是包含 X 和 Y 的最小状态(LUB)。联合半格是一个部分有序集合,其 LUB 是能包含半格中所有状态的最小状态。这意味着⊔操作必须是幂等的(X ⊔ X = X)、交换的(X ⊔ Y = Y ⊔ X)和结合的((X ⊔ Y) ⊔ Z = X ⊔ (Y ⊔ Z))。
  • 在基于状态的 CRDT 中,副本会修改其状态或进行“膨胀”,然后将结果状态广播给其他副本。接收到状态后,其他副本会将其与本地状态合并。状态的属性确保,如果副本正确接收了来自其他副本的状态,并且反之亦然,那么这些副本最终会收敛到相同的状态。
  • 另一方面,基于操作的 CRDTs [30]不对状态本身施加任何属性,而是对修改状态的操作施加要求。这些操作必须是可交换的(即使对于同时发生的不同操作也是如此)。在基于操作的 CRDT 中,副本广播的是操作而不是状态。如果两个副本同时发生了两个操作,那么其他副本应用这些操作的顺序无关紧要:最终的状态将是相同的。
  • 因此,如果操作广播未能到达某个副本(例如由于网络故障),该副本将无法应用这些操作,导致状态无法收敛。因此,与基于状态的 CRDTs 不同,基于操作的 CRDTs 的最终一致性需要一个可靠的消息层来确保所有操作最终都被传递[11]。此外,如果操作不是幂等的,消息层还需确保每个操作仅传递一次。
  • 一些基于操作的 CRDTs 还可能要求因果传递:如果一个副本先发送了操作 a,再发送操作 b(a → b),那么 a 应该始终在 b 之前被传递到其他副本。
  • 这些属性和要求确保了状态和操作型 CRDTs 的每个对象之间的因果一致性:状态的更新将保持它们之间的因果关系。例如,在一个仅增长集合(Grow-Only Set, G-Set)中,当一个副本先添加元素 A,再添加元素 B 时,任何其他副本都不会有这样的集合:其中包含元素 B 但不包含元素 A。
  • 逻辑时钟,如前面所述,通常用于实现 CRDT 类型:它们有助于识别两个更新是否同时发生并需要合并。CRDTs 已在各种应用和分布式数据库中成功使用和优化,其中 Basho 的 Riak 是最著名的例子之一。Logical clocks, as seen in the previous section, are commonly used to implement CRDT types: they are useful to identify when two updates happen concurrently and need
    merging. CRDTs have been successfully used and optimized in
    different applications and distributed databases, Basho’s Riak
    [15], [16] being one of the most prominent examples.

E. Sync Protocols in Information-Centric Networks

  • 有多种类型的逻辑时钟与前面讨论的版本向量相似,但满足了不同的需求或解决了它们的一些缺点:向量时钟[21]、有界版本向量[8]、虚线版本向量[29]、树时钟[24]或间隔树时钟[9]就是其中的一些。
  • 最近在信息中心网络(ICN)领域的分布式数据集同步方面有大量工作[32],[31],[35],[7]。以信息为中心的架构提倡在网络层直接命名内容,并基于内容名称和(在某些情况下)最长前缀匹配进行后续路由和转发(由核心网络路由器)。
  • ChronoSync[35]正在利用命名数据网络架构[34]的功能来同步不同数据集之间的状态。ChronoSync是一种数据层机制,然而,它利用了NDN所构建的分层和灵活的命名方案。受Merkle树的启发,ChronoSync正在使用加密摘要和过滤器来同步对等体之间的数据集。ChronoSync利用了底层网络(NDN)的基于名称的特性,并为每个对等体分配了一个唯一的发布前缀。这种独特的前缀,加上序列号和网络层持久/长寿命的“兴趣包”,正在取代其他方法试图用时钟和CRDT做的大部分工作。虽然在网络层集成同步功能允许更原生的设计,但一些功能不可避免地会丢失。例如,ChronoSync无法处理同时(并发)的数据发布。RoundSync[32]通过将同步过程拆分为多个轮次,部分解决了这个问题。
  • VectorSync[31]是ChronoSync[35]的增强版本,它使用版本向量使对等体之间的同步更加高效。如前所述,版本向量在检测不一致性方面比简单的消息摘要更有效。然而,与该领域的其他提案类似,VectorSync需要实现“基于领导者的成员管理”,以处理更新数据集状态的活跃成员。
  • 在网络的网络层本地集成分布式数据集同步功能显然是一项先进的努力,但也有其自身的挑战。我们相信,Merkle CRDT带来的“传输无关”状态同步的优势可以应用并提高ChronoSync、RoundSync或VectorSync等协议的性能。另一方面,通过命名网络对象直接处理基于Merkle CRDT的状态同步,为Merkle CRDTs带来了标准ICN优势。因此,我们认为这两种不同的状态同步方法是互补的。

F. IPFS: The InterPlanetary File System

  • IPFS[14]是一个内容寻址的分布式文件系统。它使用分布式哈希表(DHT)来宣布和发现哪些副本(或对等体)提供特定的Merkle DAG节点。它实现了一个名为bitswap的节点交换协议,用于从任何提供者检索DAG节点。IPFS建立在libp2p之上[5],libp2p是一种用于P2P网络的模块化网络协议栈,它还提供了主要基于发布-订阅模型的高效广播机制[2]。
  • IPFS还使用IPLD,行星间链接数据格式[4],这是一个用任意节点格式描述Merkle DAG并支持多种类型CID的框架[6],这使得创建和同步自定义DAG节点变得非常容易。
  • 这些特性使IPFS成为实现Merkle CRDT的合适层,因为它提供了在潜在的超大网络中发现、路由和通告内容的必要机制。这并不是说其他传输机制不适合在其上构建Merkle CRDT。

III. SYSTEM MODEL & ASSUMPTIONS

  • 我们的Merkle CRDT方法旨在既简单又便于在具有大量副本且没有消息传递保证(即不可靠的传输)的对等分布式系统中使用CRDT。
  • 我们假设存在一个异步消息传递层,该层在单独的副本之间提供通信通道。此通道由两个设施管理,每个副本都会利用这两个设施:DAG同步器和广播器组件(定义如下)。
  • 我们假设消息可以被丢弃、重新排序、损坏或复制。没有必要事先知道参与系统的副本数量。副本可以随意加入和离开,而无需通知任何其他副本。可能存在网络分区,但一旦重新建立连接并且副本广播新事件,这些分区就会得到解决。(网络分区指的是由于网络故障导致系统中的副本之间无法通信的情况。当网络恢复正常后,其中一个副本向其他副本广播一个新的事件,整个系统的状态就会重新同步,网络分区的问题也就随之解决。)
  • 副本可能具有持久存储,具体取决于其自身的要求和数据类型。使用Merkle CRDT,只要至少有一个其他副本处于最新的系统状态,没有持久存储的新副本和崩溃的副本最终将能够重建系统的完整状态。

A. The DAG-Syncer component

  • DAG同步器是一个组件,它使副本能够根据其内容标识符(CID)从其他副本中获取远程Merkle DAG节点,并使其自己的节点可供其他副本使用。由于节点包含指向其直接子代的链接,给定根节点的CID,DAG同步器组件可用于通过跟踪每个节点中子节点的链接来获取完整的DAG。因此,我们可以按如下方式定义DAG同步器:

  • Definition 1. (DAG-Syncer). A DAG-Syncer is a component with two methods:

    • Get(CID) : Node
    • Put(Node)
  • 我们没有指定更多细节,例如宣布和检索节点的协议是什么样子的。理想情况下,DAG同步器层不应对系统模型施加任何额外的约束。我们的方法依赖于DAG同步器和Merkle DAG的特性来容忍上述所有网络突发事件。

B. The Broadcaster component

  • Broadcaster是一个组件,用于将任意数据从一个副本分发到所有其他副本(直接或通过中继)。理想情况下,有效载荷将到达系统中的每个副本,但这不是每条广播消息的要求:
  • Definition 2. (Broadcaster). A Broadcaster is a component with one method:
    • Broadcast(Data)

C. IPFS as a DAG-Syncer and Broadcaster component

  • 上述组件可以通过使用IPFS(如第二节所述)来实现。IPFS可以充当DAG同步器,而其libp2p层提供的PubSub机制之一可以执行Broadcaster组件的任务。
  • 这样的实现通常应该允许副本集具有极大的可扩展性。网络中的对等体不需要完全连接到其他所有人,该系统具有极高的模块化和可配置性,既适合小型设备,也适合大型存储服务器。设置和实现的选择将影响系统在不同环境和网络拓扑下的性能,但与Merkle CRDT对象和数据类型无关。

IV. MERKLE-CLOCKS

A. Overview

  • Merkle-Clock M \mathcal{M} M是一个 Merkle-DAG,其中每个节点代表一个事件。换句话说,对于系统中的某个事件,我们可以在这个 DAG 中找到一个节点来表示它,从而使我们能够将其与其他事件进行比较。

  • DAG是通过根据一些简单的规则合并其他DAG(其他副本中的DAG)构建的。新事件被添加为新的根节点(现有事件的父节点)。请注意,默克尔时钟在给定时间可能有几个根。

  • 例如,给定Mα和Mβ(α和β是这些DAGs中的单根CID,在这个例子中,我们假设,在不失一般性的情况下,我们从包含单个根而不是多个根的DAG开始)

    • 1)如果α=β,则不需要采取任何行动,因为它们是相同的DAG
    • 2) 否则,如果α∈Mβ,我们保持Mβ作为我们的新时钟,因为Mα中的历史已经是它的一部分。在这种情况下,我们说Mα<Mβ。
    • 3) 否则,如果β∈Mα,我们出于同样的原因保留Mα。在这种情况下,我们说Mβ<Mα。
    • 4) 否则,我们通过保持两个DAG不变来合并两个Clocks,从而有两个根节点,分别由α和β引用。请注意,Mα和Mβ可能完全不相交,这取决于它们是否共享一些更深的节点。如果我们想记录一个新事件,我们可以创建一个有两个子项α和β的新根γ。
  • 我们已经可以看到,通过确定一个 Merkle-Clock 是否包含在另一个 Merkle-Clock 中,我们引入了 Merkle-Clock 之间的顺序概念。类似地,在每个时钟中的节点之间也存在顺序概念,因为发生得较早的事件将始终是发生得较晚的事件的后代。此外,我们还引入了一种根据比较结果合并 Merkle-Clock 的方法。合并后的 Merkle-Clock 总是包含两个 Merkle-Clock 中的因果信息。这最终意味着,所有副本中的 Merkle-Clock 存储的因果信息在合并后将收敛到相同的 Merkle-Clock。

  • Merkle-Clocks 提供的因果顺序在构建具有相似规则的 Merkle-DAG 时被嵌入,并且通常被视为非常直观的内容。然而,明确如何定义 Merkle-Clock 之间的顺序并证明在同步和合并时保持因果信息是重要的。这是下一节的主题,并将是 Merkle-CRDTs 的一个重要属性。

B. Merkle-Clocks as a convergent, replicated data type

  • 本节正式定义了 Merkle-Clock 及其作为 Merkle-Clock DAG 的表示。我们将展示 Merkle-Clock DAG 可以被视为一个 Growing-Set (G-Set) CRDT,因此在多个副本中会收敛。

  • Let F \mathcal{F} F be the set of all system events:

  • Definition 3. (Merkle-Clock Node). A Merkle-Clock Node n α n_\alpha nα is a triple:
    n α ≡ ( α , e α , C α ) n_\alpha \equiv (α, e_α, C_α) nα(α,eα,Cα)
    这表示一个事件 e α ∈ F e_\alpha \in \mathcal{F} eαF,其中 α \alpha α是节点的 CID,而 C α C_\alpha Cα是节点 n α n_\alpha nα的直接后代的 CID 集合。

  • Definition 4. (Merkle-Clock DAG). A Merkle-Clock DAG is a pair:

< N , ≤ > <N, ≤> <N,≤>

  • 其中, N N N是一个不可变的 DAG 节点集合,并且在 N N N上定义了一个部分顺序 ≤ \leq ,具体定义如下:

  • n α , n β ∈ N n_α, n_β ∈ N nα,nβN : n α < n β n_α < n_β nα<nβ n α n_α nα is a descendant(后裔) of n β nβ nβ

  • 换句话说, n α < n β n_\alpha < n_\beta nα<nβ如果存在一条从 n β n_\beta nβ n α n_\alpha nα 的连接节点路径。

  • 为了保持这种关系,Merkle-Clock DAG 必须按照以下实施规则构建:
    IR. 系统中的每个新事件必须作为现有 Merkle-Clock DAG 的新根节点来表示。特别是,C 集合必须包含之前根节点的 CIDs。

  • Definition 5. (Merkle-Clock). A Merkle-Clock ( M \mathcal{M} M) is a function which given an event e α ∈ S e_α ∈ S eαS returns a node from the Merkle-Clock DAG N N N:
    M : F → N \mathcal{M} : \mathcal{F} → N M:FN
    备注:Merkle时钟满足强时钟条件[23]。我们看到每个节点都代表一个比其子节点晚的事件: ∀ ( β , e β , C β ) ∈ N : ∀ α ∈ C β : e α → e β ∀(β, eβ, Cβ) ∈ N : ∀α ∈ Cβ : eα → eβ (β,eβ,)N:α:eαeβ

  • 由于每个事件都是使用实现规则构建的(子)DAG 的根,我们可以立即看到较早的 Merkle-Clock 值是较晚值的后代。
    M(eα) < M(eβ) ⇔ eα → eβ

  • 我们现在可以将Merkle Clocks DAG的连接半格定义为一对:

< J , ⊆ J > <J, ⊆_J> <J,J>

  • 其中J是一组Merkle Clocks DAG,⊆J是该组的偏序,定义如下。给定M,N∈J:
    M ⊂ J N ⇔ ∀ m ∈ M , ∃ n ∈ N ∣ m < n ⇔ M ⊂ N M ⊂_J N ⇔ ∀m ∈ M, ∃n ∈ N | m < n ⇔ M ⊂ N MJNmM,nNm<nMN
  • 请注意,m<n意味着m是n的后代,因此必须属于同一个DAG,那么"J只是意味着m是n的子集。
  • 这允许我们将两个Merkle Clocks DAG的最小上界(⊔J)定义为集合的正则并集:
    M ⊔ J N = M ∪ N M ⊔_J N = M ∪ N MJN=MN
  • 不出所料,默克尔时钟(Merkle-Clock)表示实际上对应于基于状态的CRDT形式的仅增长集(G-Set)[33]。集合中的元素是不可变的,以加密方式链接,并表示系统中的事件。当DAG不相交时,生成的DAG将包括N和M的根。这相当于有几个没有因果关系的事件。通过按照实现规则创建新的唯一根,可以在DAG合并后选择性地包含有关DAG合并事件的因果关系信息。
  • 在下一节中,我们将看到Merkle DAG的属性如何允许以比常规基于状态的G-Set更有效的方式同步Merkle时钟。

C. The Merkle in the Clocks: properties of Merkle-Clocks

  • 到目前为止,我们已经定义了一种对每个副本的因果关系信息进行编码的方法,并确保两个副本可以合并它们的默克尔时钟。现在我们将看到Merkle DAG的属性如何允许使用拉取而不是推送方法,这种方法与内容寻址一起,可以实现副本之间的高效时钟同步,并克服网络分区或意外事件的影响。复制品之间默克尔时钟同步的步骤如下。

    • 1) 广播默克尔时钟只需要广播当前的根CID。整个时钟由其根的CID明确标识,其完整的DAG可以根据需要从其向下遍历。

    • 2) Merkle DAG的不可变特性允许每个其他副本执行快速比较,并仅提取/获取它还没有的节点。

    • 3)Merkle DAG节点通过其CID进行自我验证,因此不受损坏和篡改的影响。因此,它们可以从任何愿意提供它们的来源获取(拉取),无论是否可信。

    • 4)通过设计消除了相同节点的重复:每个事件只能有一个唯一的表示。

    • 在实践中,每个副本只需从其他副本中获取增量因果历史,而不需要在系统中的任何地方明确构建这些增量。没有以前历史记录的全新副本将自动获取完整的历史记录。

    • Merkle Clocks可以取代版本时钟和其他通常是CRDT一部分的逻辑时钟。这需要考虑一些因素:

      • 通过使用Merkle Clocks,我们可以将因果关系信息与副本数量解耦,这是版本时钟的常见限制。这使得在实现CRDT时可以减小消息的大小,最重要的是,解决了在副本随机加入和离开系统时保持时钟工作的问题。
      • 在底层,因果信息随着每个事件而增长,即使事件信息被合并到较小的对象中,副本也可能存储较大的历史记录。
      • 保留整个因果历史使新的副本能够开箱即用地从头开始同步事件,而无需明确地向新来者发送系统快照。然而,如果历史记录非常大,同步可能会很慢。
  • Merkle Clocks相对于传统版本时钟的一个显著优势是,它们还可以处理几种类型的网络异常:

    • 丢弃的消息可能会阻止其他副本了解新的根。但是,由于每个Merkle Clock DAG都被未来的DAG取代,每次下载都会获取DAG的所有缺失部分,因此网络分区和副本停机时间不会对整个系统产生影响,一旦问题得到解决,就会开始自动修复。
    • 丢弃的消息可能会阻止其他副本了解新的根。但是,由于每个Merkle Clock DAG都被未来的DAG取代,每次下载都会获取DAG的所有缺失部分,因此网络分区和副本停机时间不会对整个系统产生影响,一旦问题得到解决,就会开始自动修复。
    • 由于同样的原因,订单交付不成问题。将按顺序提取和处理丢失的DAG。
    • 复制品会忽略重复的消息,因为它们已经被合并到它们的默克尔时钟中。
    • 损坏的消息有两种形式:a)如果广播新根的消息损坏,那么它将是一个对应于不存在的DAG的哈希,DAG同步器无法获取,最终将被忽略;b) 如果DAG节点在下载时损坏,如果其CID与下载的内容不匹配,DAG同步器组件(或应用程序)可以丢弃它
  • 正如我们在上一节中所展示的,Merkle Clocks代表了事件的严格偏序。并非系统中的所有事件都可以进行比较和排序。例如,当有多个根时,默克尔时钟无法说出哪个事件首先发生。

  • 总顺序可能很有用[23],例如,可以通过将并发事件视为相等来获得。同样,通过按节点的CID或基于附加到时钟节点的附加信息的任何其他任意用户定义策略对并发事件进行排序,可以建立严格的总顺序。任何此类方法都符合数据层冲突解决的条件

V. MERKLE-CRDTS: MERKLE-CLOCKS WITH PAYLOAD

  • Definition 6. (Merkle-CRDT). A Merkle-CRDT is a Merkle Clock whose nodes carry an arbitrary CRDT payload.
  • Merkle CRDT保留了之前Merkle Clocks的所有特性。然而,为了使有效载荷收敛,它们本身需要是收敛数据类型(CRDT)。优点是Merkle Clocks已经嵌入了顺序和因果关系信息,否则这些信息需要嵌入CRDT对象中(通常以其他逻辑时钟的形式)。或者由可靠的消息传递层提供。
  • 因此,Merkle CRDT节点的实现是:
    ( α , P , C ) (α,P,C) (α,P,C)
  • 其中α是内容标识符,P是具有CRDT属性的不透明数据对象,C是子标识符集

A. Per-object causal consistency and gap detection(每个对象的因果一致性和差距检测)

  • Merkle CRDT的有向链接特性允许按事件顺序遍历系统的完整因果历史,提供了所有必要的属性,以确保每个对象的因果一致性和设计上的差距检测,而无需修改我们的系统模型。
  • 这意味着Merkle CRDT非常适合携带基于操作的CRDT,因为它们可以确保没有操作丢失或无序应用(回想一下,默克尔时钟提供了一个严格的事件偏序。在这种情况下,应用于对象的两个非并发操作将按时钟排序。)。
  • 为了促进在Merkle CRDT中处理CRDT有效载荷的任务,在下一节中,我们提出了一种通用且简单(非优化)的反熵算法,可用于获得任何CRDT嵌入式对象的每个对象的因果一致性。

B. General anti-entropy algorithm for Merkle-CRDTs

  • Definition 7. (General anti-entropy algorithm for Merkle CRDTs).
  • R A R^A RA R B R^B RB在两个副本之间使用分别具有 M α M_α Mα M θ M_θ Mθ的Merkle CRDT作为其当前Merkle CRDT DAG。
      1. R B R^B RB issues a new payload by creating a new DAG node (β,P,{θ}) and adding it as the new root to its Merkle CRDT, which becomes M β M_β Mβ.
      1. R B R^B RB broadcasts β to the rest of replicas in the system.
      1. R A R^A RAreceives the broadcast of β and retrieves the full M β M_β Mβ. It does this by starting from the root β and walking down the DAG using the DAG-Syncer component to fetch all the nodes that are not in Mα, while collecting their CIDs in a CID-Set D. Given the inherent properties of DAGs, for any CID already in Mα the whole sub-DAG can be skipped.
      1. If D is empty, no further action is required. RA must have already processed all the payloads in Mβ. This means that Mβ ⊆Mα.
      1. If D is not empty, we sort the CIDs in D using the order provided by the Merkle-Clock.
        如果我们的系统中不要求因果交付,我们可以跳过订购。D中的项目数量将取决于系统中的并发量以及两个Merkle CRDT被允许发散的时间,但在正常情况下应该很小。
      1. R A R^A RA processes the payloads associated with the nodes corresponding to the CIDs in D, from the lowest to the highest.
      1. If α ∈ D, then Mα ⊆ Mβ and Mβ becomes the new local Merkle-CRDT in R A R^A RA.
      1. else, M α ⊄ M β Mα \not\subset Mβ Mα and M β ⊄ M α Mβ \not\subset Mα Mα. R A R^A RA keeps both nodes as roots.

C. Operation-based Merkle-CDRTs

  • Definition 8. Operation-based Merkle-CRDTs are those in which nodes embed an operation-based CRDT payload.
  • 基于操作的Merkle CRDTs是Merkle CRDTs最自然的应用。操作很容易定义,因为它们只需要是可交换的,这样无论它们接收操作的顺序如何,每个副本中的结果状态都是相同的。然而,这也意味着,为了使状态收敛,必须接收每个操作。可靠的消息传递层[11]是融合的先决条件,但在具有大量副本的现实世界网络中,通常无法确保没有消息丢失。这给我们留下了复杂的解决方法,比如额外的因果有效载荷、缓冲和重试机制,这些机制必须伴随着CRDT的实现,将本应简单的CRDT实现变成更复杂的东西。
  • Merkle DAGs提供了消息传递层的所有属性,消息总是按顺序传递,经过验证,从不重复或丢弃。因此,Merkle CRDT能够在以前不易使用的环境中实现基于操作的CRDT。
  • 正如我们所看到的,由于嵌入了Merkle DAG,每个副本只需要DAG中缺失的部分,一旦知道根,就可以获取这些部分。这包括加入系统的新副本,这些副本将能够获取和应用所有操作。我们不需要了解完整的副本集,也不需要将高效广播的责任放在Broadcaster组件中。

D. State-based Merkle-CRDTs

  • Definition 9. State-based Merkle-CRDTs are those in which nodes embed a state-based CRDT payload.
  • 在每个Merkle CRDT节点中嵌入完整状态是违反直觉的,因为基于状态的CRDT已经提供了每个对象的因果一致性,并且可以通过设计来应对不可靠的消息层。
  • 此外,尽管最终状态将由Merkle CRDT节点中的所有状态合并而成,但DAG同步器组件仍需要存储这些状态,这在处理大型状态对象时是令人望而却步的。也就是说,Merkle CRDT消除了附加因果关系元数据并将其与副本数量分离的需要,这可能对与副本数量相比状态非常小的基于状态的CRDT感兴趣。
  • 一种更有趣的方法是δ-CRDT[10],它能够发送更小的部分(增量),而不是广播完整状态。这些对象被称为δ-突变,可以像任何完整状态一样在下游合并,而不需要改变联合操作的语义。因此,多个增量可以合并形成所谓的δ组,并提高广播有效载荷的效率。正如[10]中指出的那样,“完整状态可以被视为δ群的特殊(极端)”。
  • 然而,在δ-CRDT的一般形式中,当消息丢失并且基于状态的CRDT的每个对象的因果一致性属性丢失时,一致性会无限延迟。这些问题可以通过一种额外的反熵算法来解决,该算法对缺失的增量进行分组、排序、跟踪和重新发送,如[10]中所述,但在δ态Merkle CRDT的情况下,反熵算法和附加到原始对象的任何因果信息都是不必要的。本质上,这种方法使δ态Merkle CRDT更接近其基于操作的对应物。

VI. LIMITS AND OPTIMIZATIONS OF MERKLE-CRDTS

A. Limitations of Merkle-CRDTs

  • 到目前为止,我们一直专注于解释Merkle CRDT与传统CRDT方法相比所提供的不同品质,但我们也必须强调它们带来的内在和实际局限性。
  • a) 不断增长的DAG大小:Merkle CRDT最明显的后果是,虽然CRDT通常会合并、应用、合并和丢弃广播对象,但Merkle CRDTs会构建一个必须存储且不断增长的永久Merkle DAG。正如我们所见,这提供了许多有利的特性,但也带来了一些影响:
    • DAG 的大小可能会增长到不可接受的程度。增长的速度将取决于事件的数量和负载的大小。这与区块链随时间增长到大型类似。例如,在非批处理的实现中,每次插入到 CRDT 键值存储实现中都会导致一个新的 DAG 节点。因此,对于每个键,我们将消耗额外的空间,而在小对象的情况下,这可能会比原始对象本身的大小还要大。当实际状态可能要小得多时,尤其是当在数据库中反复更新单个键时,这种情况尤其成问题。在某些情况下,可能可以将状态表达为所有 Merkle-CRDT 操作结果的紧凑形式,但这就引出了下一个要点。
    • 如果副本仅存储 Merkle-DAG 并可以从中重建完整状态,那么启动包含非常大 Merkle-DAG 的副本可能会很慢,因为它们需要重新处理整个 DAG,即使本地已有可用数据。否则,将在生成的状态和 Merkle-DAG 中存储冗余信息。
    • 从头同步 Merkle-CRDT 是可能的,并且在新副本加入时对系统来说是自然的。然而,Merkle DAG 不仅会不断增长,还往往很深且很薄。新副本将从广播操作中获取根 CID,并需要从中解析整个 DAG。由于 DAG 的薄而深的特性,无法并行获取多个分支。冷同步可能比传输快照花费更长时间,从而使 Merkle-DAG 的这一嵌入特性价值有限。
    • 在某些场景下,非常大的 DAG 和慢速同步可能不是问题,可以视为一种可接受的权衡,但这也突出了探索“垃圾收集”和 DAG 压缩机制的必要性。
    • b) Merkle-Clock 排序:合并两个 Merkle-Clock 需要比较它们,以确定它们是否包含彼此并找出差异。如果 DAG 已经显著分歧(或发生很久之前),这可能是一个代价高昂的操作。
    • c) DAG-Syncer 延迟:副本依赖于 DAG Syncer 组件来从消息层获取和提供节点。如前所述,Merkle-CRDT 对用于同步消息的机制(例如 DHT 或 PubSub)是无关的,但除非仔细选择,否则这种机制可能会引入同步延迟。根据应用程序的要求,这种延迟可能是可以接受的,也可能不可接受。
    • 这些限制的实际影响取决于应用程序的需求。特别是在考虑采用 Merkle-CRDT 时,用户应考虑以下方面:i) 节点数量与状态大小,ii) 冷同步时间,iii) 更新传播延迟,iv) 预期的副本总数,v) 预期的副本集修改(加入和离开),vi) 预期的并发事件量。

B. Optimizing Merkle-CRDTs

  • a) 延迟的 DAG 节点:在副本频繁更新的情况下,我们可以在发出单个包含所有负载的节点之前,将多个负载进行分组。显然,这种方法会带来一些好处,但也有权衡:更新不会立即发送,因此传播会花费更长时间。

  • b) 快速 Merkle-DAG 包含性检查:将本地副本 DAG 与远程 DAG 合并需要检查一个 DAG 是否包含另一个。通过遍历第一个 DAG 寻找匹配第二个 DAG 根的节点 CID 来实现这一点是可能的,但效率低下。将本地 DAG 的 CIDs 存储在一个可以快速检查 CID 是否属于本地 DAG 的键值存储中,可以显著简化操作。当遍历远程 DAG 检查本地 DAG 的包含性时,可以检查任意节点的子节点的 CIDs 以确定它们是否属于本地 DAG,从而方便地修剪其分支。然而,这意味着实现必须了解并访问本地节点存储系统。当前定义的 DAG-Syncer 无法区分本地或远程可用的节点。布隆过滤器、缓存和一些数据结构也可以提高效率,但它们通常是所选存储后端的一部分。

  • 类似的效果可以通过在负载中嵌入版本向量来实现,只要应用程序可以容忍它们所施加的限制。比较负载之间的版本向量是一种包含性检查,无需执行 DAG 遍历。

  • c) 广播负载调整:我们的标准方法通过仅包含新根的 CID 来减小广播的大小。发布机制本身就很复杂,始终受益于更小的负载。

  • 然而,在某些系统中,将新的 Merkle-DAG 节点直接作为广播负载的一部分发送可能是有益的。离线副本或丢失的消息将在收到未来的更新时恢复并完成它们的 DAG,因此在这方面没有影响。广播负载(假设它们足够小)可能会减少系统中更改传播的延迟。

  • d) 减少 Merkle-DAG 节点大小:我们可以通过压缩和去除 CRDT 本身不需要的冗余信息来尽可能减少负载的大小。例如,与其签署 CRDT 负载以确保其来自可信副本,不如签署广播消息,从而将签名排除在 Merkle-DAG 之外。

  • 另一个选项是使负载(或其部分)CIDs 引用实际内容。如果负载很大,这将大幅减少 Merkle-DAG 的大小,并可能提高 DAG 获取的效率。这在某些负载相同且可以去重,或可以有选择地访问数据时尤为相关。

  • e) 节点中的额外指针:解决薄 DAG 问题的一种方法是,在发出新节点时定期引入对 DAG 更深部分的引用。这种方法基本上是在节点中添加额外的子节点。通过能够跳转到 DAG 的其他部分,它允许在获取缺失部分时实现更多的并行操作,从而加快遍历速度。额外链接的实际数量及其目的地将取决于应用程序的需求。

  • 上述建议在任何 Merkle-CRDT 实现中都应考虑,因为它们相较于之前描述的未优化版本提供了显著优势。哪些优化适合哪个实现很大程度上取决于应用程序的具体情况。我们将 DAG 压缩和垃圾收集的主题留待未来工作,尽管我们直观地认为,在确保每个副本都了解这些部分之前,不应尝试丢弃 Merkle-DAG 的部分内容。这反过来需要了解当前副本集或使用外部真实性源(即区块链),这是我们之前没有的系统约束。

  • 基于历史时钟的系统,Merkle-CRDTs的嵌入规模会不断增加。

VII. CONCLUSION

  • 在本文中,我们将 Merkle-DAG 视为具有自我验证和高效同步特性的因果编码结构。这使我们引入了 Merkle Clock 的概念,展示了它们可以被描述为一种基于状态的 CRDT,通过 Broadcaster 组件进行广播,并通过 DAG-Syncer 设施进行获取,从而在所有副本中实现收敛。
  • 然后,我们将 Merkle-CRDTs 介绍为带有 CRDT 负载的 Merkle-Clocks。我们展示了 Merkle-CRDTs 如何在几乎没有消息层保证和副本集没有约束的情况下工作,副本集可以是动态的和未知的,同时提供每个对象的因果一致性。Merkle-CRDTs 被广泛应用于 IPFS 生态系统中的数据库日志操作、分布式 P2P 数据库 OrbitDB、其无服务器应用 Orbit,以及分布式协作编辑和移动照片共享应用中。
  • Merkle-CRDTs 是传统区块链(需要共识才能收敛)和 CRDT(通过设计实现收敛)的结合,因此既继承了两者的积极方面,也承载了负面影响。通过这项工作,我们希望为进一步研究这个主题奠定了良好的基础。

附录

Merkle-CRDTs 是非常直观的,即使它们之前没有被正式化,并且依赖于 Merkle-DAGs 的广泛使用和已知特性。IPFS 生态系统中的几个项目已经使用了它们,所有这些项目都将基于操作的 CRDT 嵌入到 Merkle-DAGs 中:

  • ipfs-log 是我们所知的第一个实现了上述 Merkle-CRDT 的实例。它实现了一个基于操作的、仅追加的日志 CRDT(类似于仅增长集合)。

  • ipfs-hyperlog 是一个用于构建和复制 Merkle DAG 的工具。

  • Orbit DB 是一个分布式的对等数据库。它使用 ipfs-log 和其他 CRDTs 支持不同的数据模型,并用于构建 Orbit,一个分布式的无服务器聊天应用程序。

  • Tevere 是一个基于操作的 Merkle-CRDT 键值存储。

  • peer-crdtpeer-crdt-ipfs 提供了几个 CRDT 的通用操作 Merkle-CRDT 实现:计数器、集合、数组、寄存器和文本(以及可组合的 CRDT)。

  • versidag 是一个提议的链接日志,具有冲突解决功能,用于存储版本信息,类似于 ipfs-log。

  • PeerPad 是一个基于 peer-crdt 和 δ-CRDTs 的实时协作文本编辑器。

  • Textile.photos 是一个移动的去中心化数字照片钱包。Textile Threads(v1)允许一组用户共享照片而无需中央数据库,并基于 Merkle-CRDTs。

  • go-ds-crdt 是一个使用 δ-state Merkle-CRDTs 的 Go 实现的键值分布式数据存储。它被 IPFS Cluster 使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值