先记录下，做个笔记，翻译来自谷歌，有能力看原文。。 https://docs.trust.one/ Trust 是基于 EOS 网络的以太坊虚拟机，为开发者提供了一个交钥匙解决方案，可以在完全兼容以太坊的链上运行他们的应用程序，同时享受 EOS 的高吞吐量、可扩展性、安全性和可靠性，以及低交易成本 他们的用户。 由于设计的高度兼容性，以太坊原生应用程序可以无缝移植到 Trust。 开发人员在这里使用他们的 Solidity 智能合约时可能会喜欢熟悉的以太坊工具。 特点： 确定性的Gas计算 指令集级别的EVM兼容性 完全的RPC兼容 技术实现 https://docs.trust.one/understanding-trust/architecture EVM 作为 EOS 网络中的智能合约实现。 在 EVM 网络中生成交易是通过调用 EOS 网络上的 EVM 合约来完成的，EVM 的状态可以从 EOS 上的信息中得出。 为了实现完全 RPC 兼容性的目标，我们利用功能齐全的以太坊节点（当前设计中的 Geth）来提供所有读取 API，而所有写入访问将被转发到一个小型服务，以将它们打包到对 EVM 合约的 EOS 调用中。 我们所做的是设置一个“翻译器”服务，该服务读取运行在 EOS 上的 EVM 智能合约的共识输出，将该信息翻译成相应的 ETH 格式块并将这些块提供给 Geth 节点。 然后我们可以公开以太坊客户端 Web3 JSON RPC API（如有必要，还可以公开其他 API）。 如果我们发现它们更适合这种情况，我们也可能会在不同的场景中使用以太坊节点的其他实现。 未来的改进 当前设计正在运行并提供预期的兼容性级别。 还有一些潜在的方法可以改进整个系统： 合并 Translator 服务和 Geth 节点，去除相对不可靠的 p2p eth/66 通道。 将所有内容合并到一个 EOSIO 插件中，以便于部署。 MetaMask测试 https://docs.trust.one/about-the-testnet/connect-metamask Network Name: Trust Network Testnet Preview Chain ID: 15555 New RPC URL*: https://api.testnet-dev.trust.one Currency Symbol: EVM Block Explorer URL (Optional): https://trustscan.one Faucet https://faucet.testnet-dev.trust.one/ https://www.odaily.news/post/5178155 https://www.odaily.news/post/5178155
自两年多前发布 EOSIO 2.0 以来，EOS 网络没有进行过重大升级。从那时起，block.one 产生了 EOSIO 2.1 和 EOSIO 2.2 的候选版本；然而，由于各种原因，Clarionos 团队和更广泛的 EOS 社区不希望所有代码都与最新版本捆绑在一起。 展望未来，Clarionos 团队会将 EOSIO 代码库分叉到一个新的代码库中，我们称之为 Mandel（Mandelbrot 的缩写）。在所有 EOSIO 驱动的区块链之间达成更广泛的共识之前，Mandel 名称是一个占位符。 Mandel 的第一个版本将是 3.0，并将从 EOSIO 2.0 派生，同时从 EOSIO 2.1 和 EOSIO 2.2 中挑选一些最有价值的增强功能。Mandel 3.0 还将引入两个新的硬分叉：合同支付、增强的可配置区块链参数。此外，它将从 EOSIO 2.1 中挑选可配置的 WASM 限制硬分叉。 虽然 EOS 区块生产者大部分仍使用 EOSIO 2.0，但一些 EOS 基础设施节点和其他下游软件已升级到 EOSIO 2.1。在迁移到 Mandel 3.0 之前要求这些节点“降级”到 EOSIO 2.0 可能会造成不必要的短期负担；因此，Clairionos 还将发布源自 EOSIO 2.1 的 Mandel 2.3 版本，该版本对 Mandel 3.0 启用的新硬分叉支持精选支持。EOSIO 2.1 节点应该能够无缝升级到 Mandel 2.3，同时与网络保持同步。 Clarionos 的目标是将尽可能多的 EOSIO 2.1 增强功能迁移到 Mandel 3.0，而不会延迟关键硬分叉的交付。 即将推出的硬分叉功能 1. 可配置的 WASM 限制 这种硬分叉允许区块生产者增加可以部署的智能合约的规模，这将允许部署更大、更强大的合约。出于安全目的，EOSIO 必须限制各种 wasm 参数，例如内存、函数数量等。一旦合约达到这些限制之一，开发人员就被迫将其代码划分为多个合约。早在 EOSVM 为 EOS 带来巨大的性能提升之前，就已经建立了最初的限制。我们现在认为增加这些限制是安全的。我们没有一次性增加，而是使它们可配置。这使网络有能力在未来扩展或在攻击者以某种方式利用额外容量时对其进行调整。 2. 合同支付 开发人员面临的最具挑战性的事情之一是使他们的应用程序易于使用。要求用户从网络租用 CPU、NET 和 RAM 资源以便与应用程序交互是一个主要的可用性障碍。在理想的世界中，智能合约将支付合约用户所需的所有资源。 当今存在的 EOS 要求每笔交易都必须由至少一个密钥签名，并且每个权限级别的阈值至少为 1。这限制了合约获取用户所需资源的潜力。 我们开发了一种不需要硬分叉的合同支付方法，但“黑客”涉及发布任何人都可以签名的“私人”密钥。当我们可以简单地允许相同的交易发生而没有任何密钥对其进行签名时，这会给网络带来不必要的负担。 在没有任何密钥的情况下可能发生的操作的一个示例是合约需要执行一些维护任务。合约愿意支付自己的维护费用，它不关心谁授权了交易。如果没有要执行的维护，那么合约将简单地拒绝交易并避免任何资源使用。维护任务的一个示例可能是当前由已弃用的延迟事务执行的许多任务。 通过合约支付，可以实现与比特币交易结构相同的交易。这消除了那些只想将 EOS 用作货币的人的帐户创建成本。它还将使实施 隐私令牌成为可能，而不会让您的隐私受到资源系统的损害。虽然这些事情将通过合同支付成为可能，但它们超出了当前路线图的范围。 有关合同付款的更多信息，请点击此处。 3. 增强的可配置区块链参数 这个硬分叉功能使添加/删除/配置未来的目标功能变得更加容易。不必为每个新功能或可配置参数添加新的本机内在函数，合约可以调用一个内在函数。这允许合约根据特征的存在或配置参数的值进行有条件的操作。此功能主要在 EOSIO 2.1 中实现，但经过审查，Clarionos 团队得出结论，需要进行一些小调整以确保更一致的操作。 系统合约升级 Clarionos 团队对 EOS 系统联系人有一个拉取请求，该请求将通过发布“私钥”来启用合同支付功能。这将允许应用程序在等待硬分叉生效的同时开发增强的用户体验。硬分叉后也需要利用该特性。 时间线 以下时间表是理想的，可能会随着发展的变化而变化。 2022 年 1 月 31 日— Mandel 3.0 的候选版本 2022 年 2 月— Mandel 3.0 测试网络启动和社区验证 2022 年 3 月 1 日— Mandel 3.0 最终版本 2022 年 3 月 2 日——网络部署 Contract Pays 系统合约 2022 年 4 月 1 日——Mandel 2.3 发布 2022 年 4 月 9 日——下一次伊甸园选举 2022 年 5 月 19 日——硬分叉激活。（2022年的黄金分割率） 这将标志着 EOS 独立于 block.one 的象征性完成，因为这将是 EOS 网络第一次运行不是由 block.one 开发或发布的软件版本。 资金 EOS 网络基金会已与 Clarionos 达成协议（待区块生产者批准），在 Mandel 3.0 候选版本交付后（2022 年 1 月 31 日）向 Clarionos 支付 200,000 EOS。然后，Clarionos 将支持社区修复在测试阶段发现的任何错误。 未来方向 该路线图是 EOS 独立的最短路径，也是振兴 EOS 多年计划的第一步。您将在下一次路线图更新中看到的一些项目包括：3 秒确定性、加速 EVM 支持的内在函数以及加速隐私应用程序的内在函数。 EOS 即将以多年未见的速度加快发展速度。 原文地址：https://medium.com/edenoneos/eos-mandel-to-takeover-eosio-in-2022-2e25bf5451f0
引言：EVM+工作组正在采取行动，在EOS上部署以太坊虚拟机 概述 EOS正在凝聚社区力量共同开发生态所需的关键项目，从而进一步推动EOS生态的复苏。我们拥有才华横溢的开发人员，能够做出重大贡献的社区领袖，以及责任心和行动力都非常强的EOS网络基金会。 EOS正在开辟自己的前进道路，并开启一个新的协作时代。 目前，由EOS网络基金会资助成立的六大核心工作组正在高速运行，每个工作组都在其领域发挥重要作用，其中API+、Core+、Wallet+和Audit+四个工作组蓝皮书已接近完成，每份蓝皮书都是重要的研究成果，将详细阐述发展目标，各自领域如何取得成功的可行策略等。 今天，我们将带领大家认识EVM+核心工作组，在正式介绍之前，我们先来了解一下六大核心工作组的概况。 六大核心工作组介绍 目前，EOS网络基金会赞助成立了六个核心工作组，他们分别是： API+：主要负责提供数据接口，帮助EOS生态以外的应用程序更好的集成EOS网络。 Core+：主要负责维护EOS系统使EOS更适合运行各种类别的应用程序。 Wallet+：主要负责配合软件将EOS集成到外部应用程序中。 Audit+：主要负责为EOS应用提供安全分析工具和合约审计的整体框架。 EVM+：主要负责实现与EVM的兼容以及探索EOS可行性的经济激励措施。 Recover+：主要负责建立危机处理框架，通过制定紧急联络、反应机制、DAO保险和白帽奖励等措施，帮助EOS项目在遭遇黑客攻击后以更合理有效的方式应对危机及追回丢失资产。 核心工作组将会与EOS生态中最有经验的人和组织合作，在落实EOS路线图方面发挥重要作用。这将是一个评估EOS生态现状、识别潜在问题并为未来发展制定路线图的绝佳机会。 每个工作组都收到了来自EOS网络基金会提供的20–25万美元的资助，另外EOS网络基金会还支出10万美元成立了一个监督小组，专门监督工作组的整体进展。 但出于社区呼声和生态需求的考虑，EVM+工作组与其他工作组的工作流程略有不同。在EOS Argentina的领导下，EVM+工作组将采取更敏捷的方式进行开发。 EVM+工作组不撰写蓝皮书，而是直接进行开发和产品交付，从而更快的在EOS智能合约层面实现EVM兼容。另外， EVM+工作组的初始预算资金为20万美元。 EVM+工作组介绍 EOS目前面临的最重要的问题之一是区块链的互操作性。 区块链领域最容易产生的问题是向内聚焦发展，而忽视了向外兼容取长补短，这样的问题在EOS表现的很明显。开发人员经常陷入一种思维逻辑：EOS拥有最好的技术，所以EOS是最好的地方，即使其他公链生态的人都还没有意识到EOS的潜力。 这样的想法会使EOS与更广阔的区块链世界隔绝开来，很容易坐井观天错过许多机会。许多开发者离开EOS，转投以太坊，原因就是虽然以太坊存在缺陷，但是却拥有更好的兼容性。 这也是我们成立EVM+工作组并加速这方面工作的重要原因，在EOS上部署以太坊虚拟机是一件非常重要的工作，我们需要推动EOS融入更广阔的区块链生态，不断的引进来、走出去，兼容并蓄快速发展。 从技术及业务角度来看，实现EVM兼容能够在释放EOS潜力方面发挥至关重要的作用。我们需要更多的以太坊开发者和用户加入EOS生态，而帮助EOS实现EVM兼容是吸引他们加入的最佳方式。 站在成功的肩膀上，吸引开发人才 EVM+工作组的领导团队是EOS创世区块生产者EOS Argentina，该团队也是Evodex和EOS Metamask钱包背后的开发人员。 为EVM+工作组提供协助的还有EOS生态的著名开发者Syed Jafri，他在2020年凭借EOSIO.EVM赢得了Block.one举办的EVM挑战赛， EOSIO.EVM使Solidity开发人员能够在EOSIO上快速启动并以高达100倍的速度和低于1000倍的成本运行应用程序。可以说，Syed Jafri在帮助EOS实现EVM兼容方面十分有经验。 EVM是EOS急需的创新。EOS公链在过去几年中面临的主要问题之一是资金匮乏。让我们和以太坊生态做一个对比： 以太坊速度更慢、效率更低而且Gas成本非常高，但以太坊和其他基于EVM的公链之间拥有更高的网络效应和代币流动性。另一方面，一部分以太坊上的开发人员能够通过创造基于Solidity编写的模块化、可重复利用、安全开源的智能合约获得高额回报，另一部分开发者可通过OpenZeppelin等组织的收录的代码库获得丰富的开发工具和教程。一个不可否认的事实是：EVM生态系统中的开发人员工具、代码库、可供dApp开发人员使用的开源代码一直在快速增长。 反观EOS生态，开发人员只能使用不太成熟的工具从头开始构建许多代码库，一个不恰当的比喻是：以太坊生态的开发者制造汽车就像是搭积木，把现成的车轮、方向盘、发动机等拼接在一起就好，而EOS生态的开发者想要制造汽车，需要从制造车轮开始。 EOS网络基金会赞助的其他工作组正在专注于改善EOS开发人员的体验，提供更多可用的本地工具，但都需要一些时间才能充分实现。 因此，我们和社区都认为，我们可以利用EVM已经存在的人才和资源，并通过EVM兼容将这些引入EOS，以此挖掘出EOS生态的潜力。 EOS发展已经进入极其重要的一年，EOS仍然面临两个巨大挑战： 一方面，EOS必须向潜在的开发者展示其巨大的潜力和价值； 另一方面，EOS必须消除开发者进入生态的障碍，尽可能降低门槛，吸引更多开发者加入。 即使项目方或开发人员相信EOS更好，但如果进入EOS生态的过程漫长、繁琐，项目方和开发人员也不会加入。因此帮助其他公链的开发人员以无障碍、低门槛的方式，将项目部署、迁移或克隆到EOS生态，是更行之有效的方式。 EVM+工作组：在EVM和EOSIO之间架起桥梁 EVM+的总体目标是从开发人员和用户的角度，使EOS成为一个兼容的EVM平台。为此，以下是工作组正在解决的关键问题： 智能合约级别的兼容与本地节点实施：EOS Argentina正在努力开发基于智能合约的解决方案，使 EVM 能够在 EOS VM WASM 运行时正常运行。 这将允许EOS网络在不进行协议升级的情况下升级EVM运行（无需分叉）。 ETH 预编译合约：这不仅有利于EOS上的EVM，同时也有利于智能合约开发人员。 EOS与其他链一样，交易执行时间有限（协议为150毫秒，BP为30毫秒）。 尽管EOS VM功能强大且速度很快，但仍有一些类型的操作在Web汇编中执行时，缺乏它们在本机代码中可以进行的优化。 为了加快速度，EVM+工作组正在寻求将ETH预编译实现为EOS内在函数（本地代码）。 完全兼容RPC：该领域的计划仍在开发中，但EVM+团队的目标是至少支持大多数RPC，以允许在ETH上使用通用工具/开发环境。这将是该计划最重要的工作之一。 确定性Gas消耗/计费：EOS链使用三种资源：CPU（主观度量/可变价格）、NET（客观度量/可变价格）和RAM（客观度量/可变价格）计费。这方面工作的挑战是为用户提供确定性的Gas消耗和有竞争力的Gas价格，同时防止Web3 API被滥用。 支持Metamask：这意味着EOS生态将支持Metamask钱包，以便EOS本地合约和使用Solidity编写的EVM合约进行交互。 EVM+工作组将按照上述方向快速推进工作，为EOS实现EVM兼容创建最佳解决方案，并计划在2022年夏天之前产生切实可行的解决方案。 目前的六个工作组正在构建一个框架，使开发人员和用户更容易参与EOS，促进EOS网络的增长，使EOS生态系统更具吸引力。 EOS的未来从未如此光明。 我们邀请您加入我们，共同创造区块链世界最伟大的公链回归故事。 请继续关注EOS网络基金会赞助的其他工作组的深入研究。 关于EOS网络基金会 EOS网络基金会是一个非营利性的组织，旨在倾听社区声音、传达社区意愿并扶持社区优质项目发展，成为EOS社区的信息共享桥梁，并为EOS生态提供资金、技术、运营、未来规划、生态构建等关键基础设施支持，进一步发挥EOS作为世界上速度最快的治理型区块链的全部潜力。 EOS网络基金会Discord EOS网络基金会中文Twitter EOS网络基金会中文Medium EOS网络基金会中文电报订阅号 EOS网络基金会中文电报群 https://medium.com/eos-network-foundation/evm-bringing-the-ethereum-virtual-machine-to-eos-f26b88466d99
关键要点 EOS 网络基金会通过招募网络架构师 Dan Larimer，在加强其独立于 Block.one 方面迈出了重要一步。 Larimer 和他的团队将分叉 EOSIO 代码库。 多年来，该项目的创始公司 Block.one 一直未能兑现承诺，社区普遍对此表示不满，但这些进展都是如此。 EOS 网络基金会已聘请 EOSIO 的创始开发者 Dan Larimer 帮助其接管 EOS 生态系统的方向。该合作伙伴关系是在 EOS 社区投票停止授予其创始支持者 Block.one 之后的。 EOS Network Foundation 的目标是更美好的未来 EOS 网络基金会的目标是复兴。 Dan Larimer 是 EOS 的架构师，之前是该项目的创始支持者 Block.one 的一部分，现在将帮助该基金会重振该项目的生态系统。为了进一步建立与 Block.one 的独立性，Larimer 和他的团队将分叉 EOSIO 代码库。 分叉将发生在两个主要升级中：Mandel 2.3 和 Mandel 3.0。EOS Network Foundation 获得了 200,000 EOS 来帮助 Larimer 和他的团队执行分叉。 Larimer 在新闻稿中表示，Mandel 代码分叉是“通往 EOS 独立的最短路径”。他还将分叉描述为“振兴 EOS 多年计划的第一步”。 今天宣布的事态发展是在 EOS 社区多年的困境之后发生的。EOS 在 2017 年声名鹊起，当时 Block.one 通过 ICO 筹集了 41 亿美元来资助该项目。它是加密社区中被称为“以太坊杀手”的几个区块链之一。然而，它未能兑现承诺。在筹集资金之后，Block.one 提出了一些在几年后仍未推出的举措。 Block.one 的失误导致 EOS 社区内长期存在分歧。该公司被指控阻碍了该项目的进展，EOS 网络基金会的首席执行官不断声称由于 Block.one 未能执行而遭受了损失。在两个阵营之间多年的紧张关系之后，EOS 区块生产者在 12 月投票停止了 6700 万个 EOS 代币的归属，这些代币计划在未来六到七年内为 Block.one 解锁。该拨款目前价值约 1.8 亿美元。 EOS 网络基金会希望加入 Larimer 将使该项目更接近于实现其最初的愿景。上周，它从 EOS 社区获得了 2100 万美元的资金，以实现其目标。能否卷土重来，还有待观察。 英文原文：https://cryptobriefing.com/eos-wants-fulfill-2017-promises-with-dan-larimer/
EOS的BFT-DPOS共识机制的进化过程及背后逻辑
不知道是阅读量到了质变，还是后面引用的这篇文章太好，我终于对BFT-DPOS有了更深刻的理解 POS: 出块不再由算力说了算，由节点持有的stake说了算，解决了POW算力资源被大量无用消耗，但由于无条件信任代表，节点作恶非常容易 , 比如nothing at stake攻击 DPOS： 动态产生一定数量的代表比如21个节点代表，只由这21个节点生成区块，降低了作恶的概率，同时用事后惩罚限制节点代表作恶行为，但始终不够安全(怎么惩罚，使用抵押物？抵押物需要多少？作恶导致的失败交易的损失都是可以量化的吗？如果不能量化抵押物就没有意义)，同时确认速度确实也慢(需要14个block） BFT+DPOS： 一个区块生产后通过BFT协议立马确认，解决了DPOS确认慢问题，需要超过1/3节点才能作恶，相比POW 51%作恶条件，这个1/3值相对来说还是偏低了点，但是这是一个折中，也只能这样了 BFT + DPOS + 小块： 该协议是为了加快出块速度，该改进的核心思想是让同一个节点产生6个小块。这个能提升性能的核心原因是6个小块的产生没有等待确认的环节。在BFT+DPOS算法中，一个节点要创建新块，必须等上一个块被确认(或者超时)，因为它不确定上一个节点是否作恶是否会被确认，所以它只能等。但是如果上一个块时也是该节点产生的，它自然不需要等上一块确认，因为它知道上一个块是真实的，不是作恶的块，最后肯定会被确认，所以安心生产下一块。 可见DPOS+BFT+小块机制中，区块生成和确认以pipeline的方式进行，是同时进行的，加快了出块速度。 何为作恶： 各种共识算法，节点基本都会进行数据检验功能，因而数据伪造作恶基本不可能。那真正的作恶是什么？ 主动作恶：节点主动生产两个区块，给一部分节点发送新区块A，另外一部分节点发送区块A1,这样系统就产生分叉了。 被动作恶： 块传播因为网络延时或者中断，会导致一部分节点缺失该块的信息，进而会产生分叉，然后后续区块生产者发现分叉后，如果遵循只选择一个分支生产区块就会让分叉收敛(能收敛最后就能达成共识)，但是如果继续保持并加重这个分叉，最后就会导致整个链变成一颗森林，最后节点就永远没法达成共识, 这是被动作恶。比如POS的noting at stake攻击，当系统出现分叉时，生产者基于自己的利益会在两个分叉区块上各自生成一个区块(这样不管哪个分叉获胜，自己都能获得收益)，这其实间接助长和加重了分叉 因而要阻止这些作恶行为，有三种方法: 1)机制保证，让生产者没有时间来生成2个区块(POW) 2)利益保证，生成两个区块最后肯定没用(BFT算法, 大家最后只会认可一个快)，生产者获取不了什么好处，自然没必要生产两个区块 3）事后惩罚，比如DPOS，这种方法其实没啥用，因为事后发现作恶节点后，系统没法回溯区块，只能惩罚作恶节点，但是这个作恶损失是不确定的(前面提到过)，起不到惩罚作用，且抵押物模式是一种限制，不利于生态发展 引用原文 区块链中最重要的便是共识算法，比特币使用的是POW（Proof of Work，工作量证明），以太币使用的POS（Proof of Stake，股权证明）而EOS使用的是BFT-DPOS。 什么是BFT-DPOS呢？即拜占庭容错式的委任权益证明。 要想明白BFT-DPOS的运行机制，首先就要先明白什么是DPOS。 由于POW在比特币的共识算法中极大地消耗了算法的资源。而且会有算法集中的问题，所以在2014年的时候Dan Larimer提出了一个相较于POW来说更加高效，轻便的共识机制即DPOS。该共识机制一边能让网络成本小型化，另一方面有回复语每个持股人一定的投票权。 这些超级节点呢能够：供相关计算资源和网络资源，保证节点的正常运行;当轮到某超级节点拥有出块权时，超级节点收集该时段内的所有交易，并对交易验证后打包成区块广播至其他超级节点，其他节点验证后把区块添加到自己的数据库中。这种共识机制采用随机的见证人出块顺序，出块速度为 3 秒，交易不可逆需要45秒。为什么需要 45 秒呢？因为 DPoS 下，见证人生产一个新区块，才表示他对之前的整条区块链进行了确认，表明这个见证人认可目前的整条链。而一个交易要达到不可逆状态，需要 三分之二以上的见证人确认，在 EOS 里就是 14 个见证人。DPoS共识算法也有极强的抗分叉能力，因为区块添加到一条区块链分叉的速率与拥有该共识的超级节点比例是相关的。当一个超级节点设法在两条分叉上同时生产区块时，EOS的持有者会在下一轮投票中将该超级节点删掉，并且EOS社区会给予相关恶意节点一定的惩罚。因此，在一般情况下，使用DPoS的EOS都是很难经历分叉的。 其次，我们还要明白BFT所代表的的意义。 拜占庭容错技术（Byzantine Fault Tolerance，BFT）是一类分布式计算领域的容错技术。拜占庭假设是对现实世界的模型化，由于硬件错误、网络拥塞或中断以及遭到恶意攻击等原因，计算机和网络可能出现不可预料的行为。拜占庭容错技术被设计用来处理这些异常行为，并满足所要解决的问题的规范要求。 拜占庭容错技术来源于拜占庭将军问题。拜占庭将军问题是Leslie Lamport在20世纪80年代提出的一个假象问题。拜占庭是东罗马帝国的首都，由于时拜占庭罗马帝国国土辽阔，每支军队的驻地分隔很远，将军们只能靠信使传递消息发生战争时，将军们必须制订统一的行动计划。然而，这些将军中有叛徒，叛徒希望通过影响统一行动计划的制定与传播，破坏忠诚的将军们一致的行动计划。因此，将军们必须有一个预定的方法协议，使所有忠诚的将军能够达成一致，而且少数几个叛徒不能使忠诚的将军做出错误的计划。也就是说，拜占庭将军问题的实质就是要寻找一个方法，使得将军们能在一个有叛徒的非信任环境中建立对战斗计划的共识。在分布式系统中，特别是在区块链网络环境中，也和拜占庭将军的环境类似，有运行正常的服务器（类似忠诚的拜占庭将军），有故障的服务器还有破坏者的服务器（类似叛变的拜占庭将军）。共识算法的核心是在正常的节点间形成对网络状态的共识。 简单形容就是：通过在一群数量有限的节点中，使用轮换或者其他算法来筛选出某个节点作为主节点。并且赋予该节点出块的权利。在主节点是将该时段的交易打包成区块后用自己的私钥对该区块签名，并将其广播到所有节点。当主节点收到至少三分之二的不同节点的签名区块后，则该区块完成了所有节点的验证成为不可逆区块串联到区块链中。 BFT与DPOS二者相结合就诞生了BFT—DPOS共识算法。 、为了挖掘 EOS 系统的性能，Daniel Larimer 在以上基础上又进行了修改。首先，他将出块速度由 3 秒 缩短至 0.5 秒，理论上这样可以极大提升系统性能，但带来了网络延迟问题：0.5 秒的确认时间会导致下一个出块者还没有收到上一个出块者的区块，就该生产下一个区块了，那么下一个出块者会忽略上一个区块，导致区块链分叉（相同区块高度有两个区块）。比如：中国见证人后面可能就是美国见证人，中美网络延迟有时高达 300ms，很有可能到时美国见证人没有收到中国见证人的区块时，就该出块了，那么中国见证人的区块就会被略过。 为解决这个问题，Daniel Larimer 将原先的随机出块顺序改为由见证人商议后确定的出块顺序，这样网络连接延迟较低的见证人之间就可以相邻出块。比如：日本的见证人后面是中国的见证人，再后面是俄罗斯的见证人，再后面是英国的见证人，再后面是美国的见证人。这样可以大大降低见证人之间的网络延迟。使得 0.5 秒的出块速度有了理论上的可能。 为了保证万无一失，不让任何一个见证人因为网络延迟的意外而被跳过，Daniel Larimer 让每个见证人连续生产 6 个区块，也就是每个见证人还是负责 3 秒的区块生产，但是由最初的只生产 1 个变成生产 6 个。最恶劣的情况下，6 个区块中，最后一个或两个有可能因为网络延迟或其他意外被下一个见证人略过，但 6 个区块中的前几个会有足够的时间传递给下一个见证人。 再来讨论 BFT-DPoS 的交易确认时间问题：每个区块生产后立即进行全网广播，区块生产者一边等待 0.5 秒生产下一个区块，同时会接收其他见证人对于上一个区块的确认结果。新区块的生产和旧区块确认的接收同时进行。大部分的情况下，交易会在 1 秒之内确认（不可逆）。这其中包括了 0.5 秒的区块生产，和要求其他见证人确认的时间 使用上述BFT-DPoS协议就可以使得EOS的出块间隔从原来的3秒降低到500毫秒，这也使得跨链通信的时延大大缩短，单位时间内可确认的交易数量大大提升。笔者相信如果这样的机制在EOSIO1.0的正式版本中成功实现，那无疑是区块链技术向支持百万级别用户的目标迈出的巨大一步。 引用原文来自:金色财经 转载自：https://www.cxyzjd.com/article/ITleaks/80359033
在审查EOS智能合约时，我注意到每个开发人员都有自己的编程风格，并且有许多不同的方法可以执行相同的操作，例如处理时间或将操作发送到其他合约。这篇文章是一些EOSIO库函数的思想转述，我觉得这些函数很优雅且有用。 1. eosio::same_payer 第一个只是一个常量表达式，可以在修改多索引表的条目时使用。使用eosio::same_payer时，将使用的新RAM（如果有）将分配给已为表条目支付的同一帐户。 用法： statstable.modify( st, eosio::same_payer, [&]( auto& s ) { s.supply += quantity; }); 它在[multi_index.hpp]中定义，只是空name(value:0)""_n 或者name(0)的常量表达式，一些开发人员仍然使用它来表示同一个付款人。 2. get_first_receiver，get_self() 在contracts.hpp中定义的两个getters get_self和get_first_receiver返回正在运行的操作的执行上下文的一部分。（在EOSIO.CDT1.6中实现了get_first_receiver以支持旧的get_code，现在不推荐使用。）get_self方法返回当前运行代码的合约，而get_first_receiver返回动作源自的帐户。除非涉及通过require_recipient的通知，否则这两个帐户是相同的。 例如，通过侦听eosio.token的传输操作的通知，get_self()返回你的合约部署到的帐户，而get_first_receiver()返回eosio.token帐户。这是因为该行为来自向你的合约帐户发送转移操作到eosio.token帐户的帐户。 用法： transfer") void cryptoship::transfer(name from, name to, const asset &quantity, string memo) { print(get_self()); // cryptoship print(get_first_receiver()); // eosio.token } 3.action_wrapper 许多用例需要将合约代码中的新操作发送到另一个合约。这是合约可以相互积极沟通的唯一方式。同样，有很多方法可以做到这一点，但最优雅的方法之一是使用eosio::action_wrappers。它为特定智能合约代码的特定操作创建操作模板，然后可用于调用此操作。 第一个参数是操作名称，第二个参数是操作的方法声明。 用法 eosio.token标头为eosio.token.hpp header file中的所有操作定义了动作包装器： action void create( name issuer, asset maximum_supply); action void issue( name to, asset quantity, string memo ); action void retire( asset quantity, string memo ); action void transfer( name from, name to, asset quantity, string memo ); // ... using create_action = eosio::action_wrapper<"create"_n, &token::create>; using issue_action = eosio::action_wrapper<"issue"_n, &token::issue>; using retire_action = eosio::action_wrapper<"retire"_n, &token::retire>; using transfer_action = eosio::action_wrapper<"transfer"_n, &token::transfer>; // ... 我们现在可以通过包含此头文件向任何eosio.token合约发送内联操作。 需要注意的是，只需要包含带有声明的头文件。意思是，即使对于具有未知实现细节的闭源合约，也可以轻松编写动作包装器。只需要写出声明，动作签名，可以从ABI获得。 使用eosio-cpp的-I标志包含头文件的附加包含目录。 包含头文件后，将发送内联传输操作，如下所示： #include <eosio_token/include/eosio_token.hpp> // can specify the contract to send the action to as first argument token::transfer_action payout("eosio.token"_n, {get_self(), "active"_n}); // transfer arguments are now passed as postional arguments payout.send(get_self(), to, quantity, memo); 对于使用to_action方法的延迟交易，同样适用： token::transfer_action payout("eosio.token"_n, {get_self(), "active"_n}); transaction t{}; t.actions.emplace_back(payout.to_action(get_self(), to, quantity, memo)); t.delay_sec = 10; t.send(0 /* sender id */, get_self(), false); 4. EOSIO时间类time_point，time_point_sec，microseconds EOSIO库在time.hpp header中定义了两个日期类，它们的精度不同。time_point_sec类是一个标准的UNIX时间戳，用于存储自1970年1月1日以来在uint32_t中的秒数，time_point具有更精确的精度，用于存储uint64_t中经过的microseconds（而不是毫秒数）。从两个类转换都很容易。 要使用时间算术，可以使用microseconds，它带有有用的助手，如seconds，minutes或hours。 用法： eosio::time_point tp = eosio::current_time_point(); eosio::time_point_sec tps = eosio::current_time_point(); eosio::microseconds micros = tp.time_since_epoch(); uint64_t count_micros = micros.count(); uint32_t count_seconds = tps.sec_since_epoch(); // no more 60*60*24*1e6 const auto MICROSECONDS_IN_DAY = hours(24); count_micros += MICROSECONDS_IN_DAY; // no more 60*60*24 count_seconds += hours(24).to_seconds(); eosio::time_point_sec lastGame = /* ... */; check((eosio::time_point_sec)(current_time_point() + minutes(1)) >= lastGame, "last game not finished"); 使用microseconds及其助手可以避免任何类型的常量，如const auto SECONDS_PER_DAY = 60*60*24，使代码更容易推理。 转载自：http://blog.hubwiz.com/2019/06/28/eos-programming-concepts/
前由 当前的区块链底层技术平台百花齐放，不同的业务、不同的技术底层的区块链之间缺乏统一的互联互通的机制，这极大限制了区块链技术和应用生态的健康发展。跨链的需求由此而来，本文通过分析几种主流的跨链方案探讨跨链技术的本质及相应的解决思路。 跨链的类型 跨链交互根据所跨越的区块链底层技术平台的不同可以分为同构链跨链和异构链跨链：同构链之间安全机制、共识算法、网络拓扑、区块生成验证逻辑都一致，它们之间的跨链交互相对简单。而异构链的跨链交互相对复杂，比如比特币采用PoW算法而联盟链Fabric采用传统确定性共识算法，其区块的组成形式和确定性保证机制均有很大不同，直接跨链交互机制不易设计。异构链之间的跨链交互一般需要第三方辅助服务辅助跨链交互。 主流跨链机制概述 截至目前，主流的区块链跨链技术方案按照其具体的实现方式主要分为三大类，分别是公证人机制、侧链/中继和哈希锁定： 公证人机制（Notary schemes）: 公证人也称见证人机制，公证人机制本质上是一种中介的方式。具体而言，假设区块链A和B本身是不能直接进行互操作的，那么他们可以引入一个共同信任的第三方作为中介，由这个共同信任的中介进行跨链消息的验证和转发。公证人机制的优点在于能够灵活地支持各种不同结构的区块链（前提是公证人能够访问相关方的链上信息），缺点在于存在中心化风险 2.哈希锁定（Hash-locking）: 哈希锁定技术主要是支持跨链中的原子资产交换，最早起源自比特币的闪电网络。其典型实现是哈希时间锁定合约HTLC(Hashed TimeLock Contract)。哈希锁定的原理是通过时间差和影藏哈希值来达到资产的原子交换。哈希锁定只能做到交换而不能做到资产或者信息的转移，因此其使用场景有限。 3.侧链/中继链（Sidechains / Relays）: 侧链是指完全拥有某链的功能的另一条区块链，侧链可以读取和验证主链上的信息。主链不知道侧链的存在，由侧链主动感知主链信息并进行相应的动作。而中继链则是侧链和公证人机制的结合体，中继链具有访问需要和验证进行互操作的链的关键信息并对两条链的跨链消息进行转移。从这个角度看中继链也是一种去中心的公证人机制。 下面就这几种跨链方式的典型实现方式进行详细分析： 典型跨链机制实现分析 公证人机制 最传统的公证人机制是基于中心化交易所得跨链资产交换，这种跨链的方式比较单一，只支持资产的交换，如下图演示了Alice通过交易所，用比特币和Bob交换ETH的过程。 Alice 通过交易所钱包将自己的比特币打入交易所地址; Alice 在交易所上挂上卖单1个BTC卖出20ETH价格； Bob需要将自己的ETH打入交易所的以太坊地址； Bob通过交易所挂出购买比特币的单子 20ETH买一个比特币； 交易所将Alice的卖单和Bob的卖单进行撮合； 交易所将Alice在交易所存储的1BTC 转移给Bob的比特币地址； 交易所将Bob在交易所存储的20ETH 转移给Alice的以太坊地址； 至此完成了Alice和Bob的BTC和ETH的交换（案例中省去了交易所的服务费）。通过该例子可以看出交易所的方式目前仅能够支持资产的交换，且资产交换的原子性、安全性完全由中心化的交易所保障存在较大的中心化风险。 除此之外还有一种著名的分布式账本技术Ripple，也是采用类似公证人的机制来解决全球金融机构之间的资产交换。Ripple的系统架构如上图所示，Ripple系统中交易通过网络中的验证者进行交易的验证，验证者验证的交易通过加密算法保护交易内容不能被验证着窥探从而保证交易的隐私性。 公证人机制的跨链技术实现简单，且能够比较灵活地支持不同类型的底层区块链体系。公证人机制的主要问题在于公证人机制的安全性保障完全由公证人系统保障。参与跨链的相关方需要对中间人给予较大的信任。 哈希锁定 哈希时间锁定（HTLC）最早出现在比特币的闪电网络，跨链资产交换支持一定数量的A链资产和一定数量的B链资产进行原子交换。哈希时间锁定巧妙地采用了哈希锁和时间锁，迫使资产的接收方在deadline内确定收款并产生一种收款证明给打款人，否则资产会归还给打款人。收款证明能够被付款人用来获取接收人区块链上的等量价值的数量资产或触发其他事件。 如下图所示，我们用一个例子来阐述如何使用哈希时间锁定进行跨链的原子资产交换，假设Alice和Bob有资产交换的需求，Alice想用1个BTC和Bob换20个ETH. 那么首先需要在两条链上设置哈希时间锁定合约，然后执行如下步骤： Alice 随机构建一个字符串s，并计算出其哈希 h = hash(s)； Alice 将h发送给Bob的合约； Alice锁定自己的1个BTC资产，并设置一个较长的锁定时间t1, 并设置了获取该BTC的一个条件：谁能够提供h的原始值s就可以得到该BTC; Bob观察到Alice 合约中锁定了一个BTC, 然后Bob锁定自己的20个ETH资产，并设置一个相对较短的锁定时间t2, t2 < t1, Bob也设置了同样获取条件（谁提供h的原始值s就可以获取20个ETH）； Alice将自己最初生成的字符串s 发送到Bob的合约里取得了20个ETH; Bob观察到步骤5中Alice的s值，将其发送给Alice的合约成功获取1个BTC; 至此Alice和Bob完成了资产的交换。 从上述的过程我们可以看出哈希时间锁定合约有一些约束条件 进行跨链资产交换的双方必须能够解析双方的合约内部数据，例如s，例如锁定资产的证明等； 哈希锁定的超时时间设置时需要保证存在时间差，这样在单方面作弊时另一方可以及时撤回自己的资产。 哈希锁定的思想运用在支付领域较多，例如闪电网络、雷电网络以及跨链资产转移协议Interledger等。但是哈希锁定目前看只适合偏资产或者关键数据的交换，甚至不支持转移因此其试用场景受限。 侧链/中继链 侧链 侧链是相对于主链而言的，最初的侧链提出是针对比特币做新特性的测试和研发。侧链相对主链而言能够验证和解析主链中的区块数据和账本数据。侧链实现的基础技术是双向锚定（Two-way Peg），通过双向锚定技术可以将数字资产在主链上进行锁定，同时将等价的资产在侧链中释放。相反当侧链中相关资产进行锁定时，主链上锚定的等价资产也可以被释放。 BTC-Relay是号称的史上第一个侧链，BTC-Relay是通过以太坊构建了一个比特币的侧链，运用以太坊的智能合约允许用户验证比特币的交易。这里我们仍然以Alice 1BTC和Bob的20ETH数字资产交换为例阐述相应原理： Bob将20ETH发送到BTCSwap的合约进行冻结；(该合约只要能够确认BTC网络上Bob接收到来自Alice 1BTC就自动将20ETH转给Alice) Alice 确认Bob冻结信息后，将1 BTC转给Bob比特币账户； BTC Relayer将比特币区块头推送到BTCSwap合约； Alice 接下来就可以调用relay tx; BTCSwap合约结合tx和BTC链的区块链进行SPV验证，验证通过则将20ETH转给Alice以太坊地址。 这种跨链的实现方式简单，但是BTC Relay需要额外的信任和维护成本，且智能合约内部的数据存储会有体积膨胀的问题。但是侧链的机制相对哈希锁定而言能够提供更多的跨链交互场景，侧链以及类SPV验证的思想适合所有跨链的场景。 中继链 中继链本质上算是公证人机制和侧链机制的融合和扩展，目前社区内最活跃的两个跨链项目Cosmos 和 Polkadot 采用的都是基于中继链的多链多层架构，其中Cosmos目前支持的是跨链资产交互而Polkadot则宣称提供任意类型的跨链交互，具体实现还有待观察。 Cosmos Cosmos网络是一个多链混合的区块链网格结构，如下图所示，该网络中主要包括两种角色： Hub: 用于处理跨链交互的中继链； Zone: Cosmos中的平行链， Cosmos中平行链需要具备两个前提条件： 1. 快速确定性（fast finality）, 这个特性由共识算法保障，也就是说Cosmos的跨链不直接支持PoW等概率确定模型的区块链； 2. 强监管性（Sovereignty)：每个平行链都具有一组验证者能够决定其出块。 为了支持平行链之间的跨链互操作，Cosmos提出了一种跨链交互协议IBC(Inter-Blockchain Communication protocol), 并利用tendermint共识算法的即时确定性实现多个异构链之间的价值和数据传输。 首先我们以Chain A 到Chain B 转账10 token为例说明使用IBC的跨链交互： 1. 互相跟踪，也就是说如果A要和B进行跨链交易，那么A和B链需要分别运行相当于对方区块链的轻节点服务，这样互相可以实时接收到对方的区块头信息（方便后续执行类SPV验证）； 2. A链上初始化IBC协议，冻结相关资产10 token, 并生成相应的证明发送给B区块链； 3. B链接收到相应的IBC消息，通过A链的区块头信息确定A确实进行相应的资产冻结，然后B链会生成等价值10 token的资产。 以上是使用IBC协议的两个平行链直接进行跨链的基本过程，如果区块链很多，那么这种方式的两两跨链复杂度会呈现组合级别增加。因此Cosmos网络又引入了一种Hub的中继链，所有的平行链都通过IBC连接到Hub，让Hub辅助跨链交易的验证和转移，目前Cosmos实现了一个官方的Hub称为Cosmos Hub（如前图所示）。 如下图所示是Cosmos 网络的详细架构图，Cosmos为方便平行链开发提供了基本服务CosmosSDK包括：共识、网络以及IBC协议等，这样基于Cosmos SDK开发的子链之间都能够方便地互相交互。此外对于非Cosmos SDK 开发的区块链需要使用Peg Zone进行桥接，如图中的Ethereum。 笔者认为Cosmos为跨链带来的最大贡献在于IBC协议的设计，IBC协议提供了一种通用的跨链协议标准。IBC的设计使得跨链交易可以在多个Hub之间进行安全路由和转发，类似目前互联网的TCP/IP 协议。但是遗憾的是目前的Cosmos设计也只能够支持资产的跨链，而且由于不同区块链的业务不同其共识速率的不一致也会影响跨链交易有效性的证明。 Polkadot Polkadot也是一种集成平行链和中继链的多层多链架构，Polkadot区块链的整体架构图如下图所示，主要包含三种角色链和四种参与方： 三种链角色： 中继链（Relay chain）: 中继链位于Polkadot的体系的核心地位，主要是为整个系统提供统一的共识和安全性保障； 平行链（Parachain）: 在Polkadot中平行链负责具体的业务场景，平行链自身不具备区块的共识，它们将共识的职责渡让给了中继链，所有平行链共享来自中继链的安全保障，中继链是Polkadot组成的一部分； 桥接链：桥接链指的是非Polkadot体系之外的区块链，如Bitcoin, Ethereum， 这些区块链有自身的共识算法，它们通过不同的Bridge与Polkadot连接在一起进行跨链交互。 四种参与方： 验证者(Validator): 验证者负责Polkadot的网络出块，会运行一个中继链的客户端，在每一轮区块产生中会对其提名的平行链出的块进行核验。当平行链的跨都被他们的子验证者集合确定好之后，验证者们会将所有平行链区块头组装到中继链的区块并进行共识。 核验人(Collator): 帮助验证者收集、验证和提交备选平行链区块，维护了一个平行链的全节点。 钓鱼人(Fisherman):钓鱼人主要靠检举非法交易或者区块以获取收益； 提名人(Nominator): 拥有stake的相关方，维护和负责验证者的安全性。 Polkadot的特性包括两个，一个是共享安全性，一个是不需信任的跨链交互。这里的不需信任的跨链交互其实是和第一个特点共享安全性密切相关的，而且Polkadot的不需信任的跨链交互也主要是只其内部的平行链之间。 在Polkadot中如果parachain A 需要发送一笔交易到parachain B的过程如下： A链将跨链交易放到自己的engress(每个平行链有一个消息输出队列engress 和一个消息输入队列ingress); A链的Collator收集A链的普通交易以及跨链交易并提交给A链的验证者集合； A链的验证者集合验证成功，将本次A链的区块头信息以及A链的engress内信息提交到中继链上； 中继链运行共识算法进行区块确认以及跨链交易路由，中继链上的验证者会将A链的相应交易从A链的engress queue中移动到B链的ingress queue中。 B链执行区块，将ingress queue中相应交易执行并修改自身账本。 以上便是Polkadot跨链交易的主要步骤，由于所有平行链的共识同步发生（中继链区块示意图如下），因此跨链交易不会有诸如双花等安全性问题。 Polkadot 的平行链之间的跨链交换的安全性保障主要来自共享安全性这个特点，共享安全性使得跨链交易和普通交易同步发生也就不存在其他跨链场景中的双花等跨链数据不一致问题。其次Polkadot中的引入的特殊状态验证方法方便中继链进行跨链等消息的有效性验证。 值得一提的是Polkadot项目目前还处在项目初期，对于parachain的设计、Collator的协作以及Validator的共识、工作效率等都未完善。这种共享安全性的方式是否也限制了平行链自身的性能都还有待考证。 关于跨链技术的几点思考 综合以上的一些主流跨链场景和方案的分析，从跨链的概念以及需求上看跨链的本质其实就是 如何将A链上的消息M安全可信地转移到B链并在B链上产生预期效果。那么一个成功的跨链交互到底需要解决哪些问题呢？笔者认为主要有以下四个问题： 消息M的真实性证明，也就是说M是否确实是存在A链上的，也确实是A链发给B链的； 消息M的路由，如何让跨链消息安全跨系统路由； 消息M的有效性证明，这里的有效性是指来自A链的消息M如何让B链认可其抵达B链时状态仍然有效，比如转移的资产是否是冻结的，没有双花的，如果是状态那么是否在此期间未发生改变等； 消息M的执行结果证明，这个是指A链需要确认跨链操作是否成功，以及成功操作的相应回执。 那么针对这些关键本质问题，如何去处理呢？笔者设想未来的区块链应该在底层平台的设计之初就需要遵循统一的跨链协议标准，就像现在的操作系统对TCP/IP协议的支持一样。需要进行通用跨链的区块链至少要支持一下功能： 提供跨链消息的输入和输出口径，例如Cosmos和Polkadot的跨链队列； 提供跨链消息的真实性证明，区块链需要提供类似SPV的证明手段； 消息的有效路由需要构建跨链消息的统一格式，定义好消息的来源和去处以及消息内容，如Cosmos的IBC协议； 消息的有效性证明，区块链可能需要设计新的类似UTXO的可验证存储结构，方便做类SPV类验证，否则目前的基于KV的数据存储方式做有效性证明几乎不可能； 跨链执行结果证明，和有效性证明类似，需要全新的数据结构和运行算法支持。 除此之外，跨链系统的设计还需要考虑系统稳定性、可扩展性以及易升级性、容错等等，总而言之，真正的可信互联网建设艰辛蛮长，诸君共勉！ 转载自：https://juejin.im/post/5c98ed76e51d4566b962628e
EOS区块结构、生产、打包、验证、存储等流程分析
Table of Content 总览 环境 预备知识 chain分析 基本数据结构 producer_plugin controller chainbase分析 基本数据结构 智能合约的持久化存储和database的交互 总览 本文主要致力于EOS区块结构、生产、打包、验证、存储等流程分析，当然其他部分源码也有涉及，但在此不做详细讨论。 环境 Mac OS High Sierra 10.13.6 EOS源码版本1.2.1 CLion CMake 预备知识 文档 C++11新特性学习 chain分析 blockchain基本数据结构 区块结构: block_header,定义在：libraries/chain/include/eosio/chain/block_header.hpp第7行 struct block_header { block_timestamp_type timestamp; //区块产生时间 account_name producer; //区块生产者 uint16_t confirmed = 1; //dpos确认数 block_id_type previous; //前一个区块的头的hash值 checksum256_type transaction_mroot; //区块包含的transactions的merkel树根 checksum256_type action_mroot; //区块包含的actions的merkel树根，这些actions实际包含在transactions中 uint32_t schedule_version = 0; optional<producer_schedule_type> new_producers; extension_type header_extension; } signed_block_header,定义在：libraries/chain/include/eosio/chain/block_header.hpp第45行 struct signed_block_header : public block_header { signature_type producer_signature; //生产者的签名 } signed_block,定义在：libraries/chain/include/eosio/chain/block.hpp 57行 struct signed_block : public signed_block_header { vector<transacton_receipt> transactions; //区块包含的transactions执行后得到的回执 extrension_type block_extensions; } transaction_receipt_header,定义在：libraries/chain/include/eosio/chain/block.hpp 12行 struct transaction_receipt_header { enum status_enum { executed = 0, //transaction成功执行，没有错误发生 soft_fail = 1, // hard_fail = 2, delayed = 3, expired = 4 }; fc::enum_type<uint8_t,status_enum> status; uint32_t cpu_usage_us; //总CPU使用时间，单位为微秒 fc::unsigned_int net_usage_words;//总网络使用量 } transaction_receipt,定义在：libraries/chain/include/eosio/chain/block.hpp 33行 struct transaction_receipt : public transaction_receipt_header { fc::static_variant<transaction_id_type,packed_transaction> trx; //已经执行过的transactions } transaction_header,定义在：libraries/chain/include/eosio/chain/transaction.hpp 30行 struct transaction_header { time_point_sec expiration; //过期时间 uint16_t ref_block_num = 0U; //用于TaPos验证 uint32_t ref_block_prefix = 0UL; //用于TaPos验证 fc::unsigned_int max_net_usage_words = 0UL; uint8_t max_cpu_usage_ms = 0; fc::unsigned_int delay_sec; } transaction,定义在：libraries/chain/include/eosio/chain/transaction.hpp 54行 struct transaction : public transaction_header { vector<action> context_free_actions; //上下文无关的actions vector<action> actions; extension_type transaction_extensions; } signed_transaction,定义在：libraries/chain/include/eosio/chain/transaction.hpp 78行 struct signed_transaction : public transaction { vector<signature> signatures; vector<bytes> context_free_data; //和context_free_action一一对应 } packed_transaction,定义在：libraries/chain/include/eosio/chain/transaction.hpp 98行 struct packed_transaction { enum compression_type { none = 0, zlib = 1 } vector<signature_type> signatures; fc::enum_type<uint8_t,compression_type> compression; bytes packed_context_free_data; bytes packed_trx; } deferred_transaction,定义在：libraries/chain/include/eosio/chain/transaction.hpp 157行 struct deferred_transaction : public signed_transaction { uint128_t sender_id; account_name sender; account_name payer; time_point_sec execute_after; } action，定义在：libraries/chain/include/eosio/chain/action.hpp 60行 struct action { account_name account; action_name name; vector<permission_level> authorization; bytes data; } pending_state,定义在：libraries/chain/controller.cpp 91行,这是区块生产过程和区块同步过程中一个非常关键的数据结构 struct pending_state { maybe_session _db_session; //数据库session，主要涉及undo,squash,push相关操作，使数据库undo_state处于正确状态 block_state_ptr _pending_block_state; vector<action_receipt> _actions; //transactions在执行过程中生成的action_receipt，会打包到区块中(finalize_block) controller::block_status _block_status; } block_header_state,定义在：libraries/chain/include/eosio/chain/block_header_state.hpp 11行 这个结构定义了验证transaction所需的头部信息，以及生成一个新的block所需的信息 struct block_header_state { block_id_type id;//最近的block_id uint32_t block_num = 0;//最近的block的高度/值 signed_block_header header; //最近的block header; uint32_t dpos_proposed_irreversible_blocknum = 0;//最新的被提出dpos不可逆的区块高度/值，需要dpos计算确认 uint32_t dpos_irreversible_blocknum = 0;//最新的dpos不可逆区块高度/值，这个是已经确认了的 uint32_t bft_irreversible_block = 0; //bft不可逆区块高度/值 uint32_t pending_schedule_lib_num; // digest_type pending_schedule_hash; producer_schedule_type pending_schedule; producer_schedule_type active_schedule; incremental_merkel block_root_merkle; flat_map<account_name,uint32_t> producer_to_last_produced; flat_map<account_name,uint32_t> procuer_to_last_implied_irb; public_key_type block_signing_key; //当前生产者的签名 vector<uint8_t> confirm_count; vector<header_confirmation> confirmations; } block_state,定义在：libraries/chain/include/eosio/chain/block_state.hpp 14行 struct block_state : public block_header_state { signed_block_ptr block; //前一个block指针 bool validated = false; bool in_current_chain = false; } 以上为EOS区块的关键数据结构，下面的分析都是围绕着以上的数据结构来进行的。数据结构之间的关系如下： producer_plugin producer_plugin实现了区块生产和区块同步的调用功能。 头文件定义在：plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp 实现文件定义在：plugins/producer_plugin/producer_plugin.cpp 开始插件系统会调用producer_plugin::set_program_options函数进行相关程序项的设置： 生成config.ini文件(如果该文件不存在的话) 读取配置 调用producer_plugin::plugin_initialize函数进行初始化工作： 初始化配置 设置信号函数 调用producer_plugin::plugin_start函数，主要完成的功能如下： 设置信号函数： my->_accepted_block_connection.emplace(chain.accepted_block.connect( [this]( const auto& bsp ){ my->on_block( bsp ); } )); my->_irreversible_block_connection.emplace(chain.irreversible_block.connect( [this]( const auto& bsp ){ my->on_irreversible_block( bsp->block ); } )); 获取最新的不可逆的区块号 进入生产区块的调度 producer_plugin_impl::schedule_production_loop producer_plugin_impl::schedule_production_loop: 取消前一次的_timer操作： chain::controller& chain = app().get_plugin<chain_plugin>().chain(); _timer.cancel(); std::weak_ptr<producer_plugin_impl> weak_this = shared_from_this(); 调用 result = start_block(bool &last_block),函数定义在plugins/producer_plugin/producer_plugin.cpp 882行: 在该函数中： 首先会取得chain::controller的引用chain，判断chain当前的数据库模式是否为db_read_mode::READ_ONLY,如果是则返回状态start_block_result::waiting;如果不是则将当前的_pending_block_mode设为pending_block_mode::producing： chain::controller& chain = app().get_plugin<chain_plugin>().chain(); if( chain.get_read_mode() == chain::db_read_mode::READ_ONLY ) return start_block_result::waiting; 计算当前节点是否为生产节点，获取当前被调度的生产者的watermark和signature: last_block = ((block_timestamp_type(block_time).slot % config::producer_repetitions) == config::producer_repetitions - 1); const auto& scheduled_producer = hbs->get_scheduled_producer(block_time); auto currrent_watermark_itr = _producer_watermarks.find(scheduled_producer.producer_name); auto signature_provider_itr = _signature_providers.find(scheduled_producer.block_signing_key); auto irreversible_block_age = get_irreversible_block_age(); 进行一系列的条件判断: 检查当前节点是否被允许生产、被调度的生产者是否在生产队列中等：if( !_production_enabled ) { _pending_block_mode = pending_block_mode::speculating; } else if( _producers.find(scheduled_producer.producer_name) == _producers.end()) { _pending_block_mode = pending_block_mode::speculating; } else if (signature_provider_itr == _signature_providers.end()) { elog("Not producing block because I don't have the private key for ${scheduled_key}", ("scheduled_key", scheduled_producer.block_signing_key)); _pending_block_mode = pending_block_mode::speculating; } else if ( _pause_production ) { elog("Not producing block because production is explicitly paused"); _pending_block_mode = pending_block_mode::speculating; } else if ( _max_irreversible_block_age_us.count() >= 0 && irreversible_block_age >= _max_irreversible_block_age_us ) { elog("Not producing block because the irreversible block is too old [age:${age}s, max:${max}s]", ("age", irreversible_block_age.count() / 1'000'000)( "max", _max_irreversible_block_age_us.count() / 1'000'000 )); _pending_block_mode = pending_block_mode::speculating; } 调用controller::abort_block 调用controller::start_block 这两个函数在后文详述。 在调用controller::start_block之后，在controller_impl中就会生成一个全新的pending包含了最新生成的区块头部信息 然后需要对新块进行transaction打包： 清理过期的transaction: // remove all persisted transactions that have now expired auto& persisted_by_id = _persistent_transactions.get<by_id>(); auto& persisted_by_expiry = _persistent_transactions.get<by_expiry>(); while(!persisted_by_expiry.empty() && persisted_by_expiry.begin()->expiry <= pbs->header.timestamp.to_time_point()) { persisted_by_expiry.erase(persisted_by_expiry.begin()); } 判断start_block返回值： result == failed start pending block 失败，稍后再试.启动定时器_timer，等待50ms再次进入schedule_production_loop if (result == start_block_result::failed) { elog("Failed to start a pending block, will try again later"); _timer.expires_from_now( boost::posix_time::microseconds( config::block_interval_us / 10 )); // we failed to start a block, so try again later? //启动定时器，待会儿再试 _timer.async_wait([weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) { auto self = weak_this.lock(); if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id) { self->schedule_production_loop(); } }); } result == waiting 调用producer_plugin_impl::schedule_delayed_production if (result == start_block_result::waiting){ //这里检查生产者队列是否为空和是否被允许生产 if (!_producers.empty() && !production_disabled_by_policy()) { fc_dlog(_log, "Waiting till another block is received and scheduling Speculative/Production Change"); schedule_delayed_production_loop(weak_this, calculate_pending_block_time()); } else { fc_dlog(_log, "Waiting till another block is received"); // nothing to do until more blocks arrive } } _pending_block_mode == producint && result == successed 启动定时器,在若干毫秒之后调用producer_plugin_impl::maybe_produce_block进行区块生产的完成工作，在这个时间段内当前节点收到的所有transaction都会被打进这个区块中。 if (_pending_block_mode == pending_block_mode::producing) { // we succeeded but block may be exhausted static const boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); if (result == start_block_result::succeeded) { // ship this block off no later than its deadline _timer.expires_at(epoch + boost::posix_time::microseconds(chain.pending_block_time().time_since_epoch().count() + (last_block ? _last_block_time_offset_us : _produce_time_offset_us))); fc_dlog(_log, "Scheduling Block Production on Normal Block #${num} for ${time}", ("num", chain.pending_block_state()->block_num)("time",chain.pending_block_time())); } else { auto expect_time = chain.pending_block_time() - fc::microseconds(config::block_interval_us); // ship this block off up to 1 block time earlier or immediately if (fc::time_point::now() >= expect_time) { _timer.expires_from_now( boost::posix_time::microseconds( 0 )); } else { _timer.expires_at(epoch + boost::posix_time::microseconds(expect_time.time_since_epoch().count())); } fc_dlog(_log, "Scheduling Block Production on Exhausted Block #${num} immediately", ("num", chain.pending_block_state()->block_num)); } _timer.async_wait([&chain,weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) { auto self = weak_this.lock(); if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id) { auto res = self->maybe_produce_block(); fc_dlog(_log, "Producing Block #${num} returned: ${res}", ("num", chain.pending_block_state()->block_num)("res", res) ); } }); } producer_plugin_impl::maybe_produce_block,这个函数会调用producer_plugin_impl::produce_block完成区块生产： //确保在异常退出候，scheudle_production_loop依然能够正常进行下去 auto reschedule = fc::make_scoped_exit([this]{ schedule_production_loop(); }); try { //完成区块的finalize_block,区块签名，更新fork_db produce_block(); return true; } catch ( const guard_exception& e ) { app().get_plugin().handle_guard_exception(e); return false; } catch ( boost::interprocess::bad_alloc& ) { raise(SIGUSR1); return false; } FC_LOG_AND_DROP(); fc_dlog(_log, "Aborting block due to produce_block error"); chain::controller& chain = app().get_plugin().chain(); chain.abort_block(); return false; 5. producer_plugin_impl::produce_block函数主要完成区块生产的主要工作包括： * finalize_block: 更新资源限制 设置action merkle树根 设置transaction merkle树根 ...在controller中有更详细说明 * sign_block 对block进行签名，防止被篡改 * commit_block 将新产生的区块加到数据库中，并将该区块广播出去。在controller有详细叙述 ```cpp EOS_ASSERT(_pending_block_mode == pending_block_mode::producing, producer_exception, "called produce_block while not actually producing"); chain::controller& chain = app().get_plugin<chain_plugin>().chain(); const auto& pbs = chain.pending_block_state(); const auto& hbs = chain.head_block_state(); EOS_ASSERT(pbs, missing_pending_block_state, "pending_block_state does not exist but it should, another plugin may have corrupted it"); auto signature_provider_itr = _signature_providers.find( pbs->block_signing_key ); EOS_ASSERT(signature_provider_itr != _signature_providers.end(), producer_priv_key_not_found, "Attempting to produce a block for which we don't have the private key"); //idump( (fc::time_point::now() - chain.pending_block_time()) ); //完成块 chain.finalize_block(); //对块进行签名 chain.sign_block( [&]( const digest_type& d ) { auto debug_logger = maybe_make_debug_time_logger(); return signature_provider_itr->second(d); } ); //提交块到数据库 chain.commit_block(); auto hbt = chain.head_block_time(); //idump((fc::time_point::now() - hbt)); block_state_ptr new_bs = chain.head_block_state(); _producer_watermarks[new_bs->header.producer] = chain.head_block_num(); ilog("Produced block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, confirmed: ${confs}]", ("p",new_bs->header.producer)("id",fc::variant(new_bs->id).as_string().substr(0,16)) ("n",new_bs->block_num)("t",new_bs->header.timestamp) ("count",new_bs->block->transactions.size())("lib",chain.last_irreversible_block_num())("confs", new_bs->header.confirmed)); 至此producer_plugin中区块的生产流程已经介绍完毕，更详细的分析会在controller中体现出来。 总体时序如下： ![image](diagram/producer_sequence.png) 区块同步流程： controller producer_plugin在区块生产的过程中扮演着调度的角色，而实际工作是放在controller中来完成的，下面将纤细分析controller在区块生成过程中所扮演的角色功能： 上文说到在producer_plugin_impl::start_block函数中会调用controller::abort_block和controller::start_block两个函数，这里需要展示一下controller相关数据结构,controller的功能主要是在controller_impl中实现的，这里只列举关键部分： struct controller { enum class block_status { irreversible = 0, //区块已经被应用,且不可逆 validated = 1, //区块已经被可信任的生产者签名，并已经应用但还不是不可逆状态 complete = 2, //区块已经被可信任的生产者签名，但是还没有被应用，状态为可逆 incomplete = 3 //区块正在生产过程 }； //信号量集合 signal<void(const signed_block_ptr&)> pre_accepted_block; signal<void(const block_state_ptr&)> accepted_block_header; signal<void(const block_state_ptr&)> accepted_block; signal<void(const block_state_ptr&)> irreversible_block; signal<void(const transaction_metadata_ptr&)> accepted_transaction; signal<void(const transaction_trace_ptr&)> applied_transaction; signal<void(const header_confirmation&)> accepted_confirmation; signal<void(const int&)> bad_alloc; private: std::unique_ptr<controller_impl> my; }; struct controller_impl { controller& self; chainbase::database db; // state db,主要是存储合约执行后的各种状态信息 chainbase::database reversible_blocks; //用来存储已经成功应用但是还是可逆状态 block_log blog; optional<pending_state> pending; //保存正在生成的block信息，该结构在上文已经列出 block_state_ptr head; //上一次block state信息，该结构在上文已经列出 fork_database fork_db; wasm_interface wasmif; resource_limits_manager resource_limits; authorization_manager authorization; ... /** * Transactions that were undone by pop_block or abort_block, transactions * are removed from this list if they are re-applied in other blocks. Producers * can query this list when scheduling new transactions into blocks. */ /**transaction的撤销由pop_block或abort_block来完成。如果有其他块重新应用了这些事物，则需要从该列表中将其删除。 * 当新transaction被调度成块是，用户可以查询列表。 * 从后面的分析中可以看到，abort_block并没有完成撤销工作 */ map<digest_type,transaction_metadata_ptr> unapplied_transactions; . . . } controller的初始化工作是由chain_plugin::plugin_initialize函数来完成的：检查白名单、黑名单、灰名单，数据库目录、检查点、及命令行参数的检查，主要功能定义在：plugins/chain_plugin/chain_plugin.cpp 314行。 在chain_plugin中还负责相关channel的初始化工作。 然后chain_plugin::plugin_start函数会将controller启动,定义在：plugins/chain_plugin/chain_plugin.cpp 633行： try { try { //controller启动 my->chain->startup(); } catch (const database_guard_exception& e) { log_guard_exception(e); // make sure to properly close the db my->chain.reset(); throw; } if(!my->readonly) { ilog("starting chain in read/write mode"); } ilog("Blockchain started; head block is #${num}, genesis timestamp is ${ts}", ("num", my->chain->head_block_num())("ts", (std::string)my->chain_config->genesis.initial_timestamp)); my->chain_config.reset(); } FC_CAPTURE_AND_RETHROW() 在controller::startup中会调用controller_impl::add_index: 这个函数主要为controller_impl::reversible_block和db添加索引： //为reversible block建立索引 reversible_blocks.add_index<reversible_block_index>(); db.add_index<account_index>(); db.add_index<account_sequence_index>(); db.add_index<table_id_multi_index>(); db.add_index<key_value_index>(); db.add_index<index64_index>(); db.add_index<index128_index>(); db.add_index<index256_index>(); db.add_index<index_double_index>(); db.add_index<index_long_double_index>(); db.add_index<global_property_multi_index>(); db.add_index<dynamic_global_property_multi_index>(); db.add_index<block_summary_multi_index>(); db.add_index<transaction_multi_index>(); db.add_index<generated_transaction_multi_index>(); authorization.add_indices(); resource_limits.add_indices(); 上述结构在后文有详细说明；然后进行fork_db的初始化工作，设置controller_impl::head,使其处于正确的状态为后续的区块生产做准备工作，到这里区块的初始化基本完成了，下面就到了区块生产的环节了。 从上文我们知道producer_plugin::start_block最后会调用controller::abort_block和start_block两个函数,这两个函数最终会调用controller_impl::abort_block和controller_impl::start_block两个函数： controller_impl::abort_block重置controller_impl::pending信息，使pending处于全新状态: if( pending ) { //这里只是将_pending_block_state中的transaction重新放到unapplied_transactions中，并没有做撤销工作 if ( read_mode == db_read_mode::SPECULATIVE ) { for( const auto& t : pending->_pending_block_state->trxs ) unapplied_transactions[t->signed_id] = t; } pending.reset(); } controller_impl::start_block函数接受三个参数：1.即将要产生的区块的时间戳when，2.区块确认数量confirm_block_count,3.区块当前的状态status: 判断controller_impl::pending是否为初始状态，否则抛出异常 EOS_ASSERT( !pending, block_validate_exception, "pending block already exists" ); 建立db session if (!self.skip_db_sessions(s)) { EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block", ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); pending.emplace(maybe_session(db)); } else { pending.emplace(maybe_session()); } 根据最近的controller_impl::head生成新的pending pending->_block_status = s; //这里会调用block_head::block_head(const block_header_state& prev, block_timestamp_type when) //然后调用block_state_head::generate_next根据传进来的时间戳when生成新的block_header_state(新块) //应为当前节点是正在出块的节点，所以在generate_next不需要对块进行完整性验证 //在同步块的时候则需要调用next函数，并做完整性验证后面详述 //generate_next代码定义在 libraries/chain/block_header_state.cpp 36行 pending->_pending_block_state = std::make_shared<block_state>( *head, when ); // promotes pending schedule (if any) to active pending->_pending_block_state->in_current_chain = true; 将出块action打进transaction并执行,然后清理过期的transactions更新生产者授权 try { auto onbtrx = std::make_shared<transaction_metadata>( get_on_block_transaction() ); onbtrx->implicit = true; auto reset_in_trx_requiring_checks = fc::make_scoped_exit([old_value=in_trx_requiring_checks,this](){ in_trx_requiring_checks = old_value; }); in_trx_requiring_checks = true; push_transaction( onbtrx, fc::time_point::maximum(), self.get_global_properties().configuration.min_transaction_cpu_usage, true ); } catch( const boost::interprocess::bad_alloc& e ) { elog( "on block transaction failed due to a bad allocation" ); throw; } catch( const fc::exception& e ) { wlog( "on block transaction failed, but shouldn't impact block generation, system contract needs update" ); edump((e.to_detail_string())); } catch( ... ) { } clear_expired_input_transactions(); update_producers_authority(); 至此controller_impl::start_block函数分析完毕，其主要功能就是根据当前head生成新块，并将出块action打进transaction中。 在controller_impl::start_block函数执行完毕候，控制权就交还给producer_plugin_impl::start_block了,在上文有对应的分析，producer_plugin_impl::start_block最终会把控制权交给producer_plugin_impl::schedule_production_loop，在这个函数中会启动一个定时器，在延迟一段时间之后会调用proudcer_plugin_impl::maybe_produce_block，这个函数会调用producer_plugin_impl::produce_block这在上文都有分析到，在producer_plugin_impl::produce_block中会调用: controller::finalize_block,controller::sign_block和controller::commit_block三个函数来完成区块生产，区块签名，区块上链过程，下面来一次分析这三个函数： controller::finalize_block 这个函数主要是完成资源更新包括生产该区块所使用的cpu资源，带宽资源；设置action merkle树根；设置transaction merkle树根，创建block summary信息: resource_limits.process_account_limit_updates(); const auto& chain_config = self.get_global_properties().configuration; uint32_t max_virtual_mult = 1000; uint64_t CPU_TARGET = EOS_PERCENT(chain_config.max_block_cpu_usage, chain_config.target_block_cpu_usage_pct); resource_limits.set_block_parameters( { CPU_TARGET, chain_config.max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, max_virtual_mult, {99, 100}, {1000, 999}}, {EOS_PERCENT(chain_config.max_block_net_usage, chain_config.target_block_net_usage_pct), chain_config.max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, max_virtual_mult, {99, 100}, {1000, 999}} ); resource_limits.process_block_usage(pending->_pending_block_state->block_num); //设置action merkle树根 set_action_merkle(); //设置transaction merkle树根 set_trx_merkle(); auto p = pending->_pending_block_state; p->id = p->header.id(); //根据block id生成 summary信息并放到数据库中 create_block_summary(p->id); controller::sign_block 根据当前生产者提供的私钥签名函数对当前区块进行签名，并对做一次签名验证。 auto p = pending->_pending_block_state; p->sign( signer_callback ); static_cast<signed_block_header&>(*p->block) = p->header; block_header_state::sign(上面p->sign)定义如下： auto d = sig_digest(); header.producer_signature = signer( d ); EOS_ASSERT( block_signing_key == fc::crypto::public_key( header.producer_signature, d ), wrong_signing_key, "block is signed with unexpected key" ); controller::commit_block 在详细分析这个函数之前需要先来分析一下fork_database这个类，它的结构如下： struct by_block_id; struct by_block_num; struct by_lib_block_num; struct by_prev; //建立一个基于block_state_ptr的多索引容器 //by_block_id以block id为索引 //by_block_num 以区块高度为索引 //by_lib_block_num以最近的区块不可逆高度为索引 //by_prev以前一个block id为索引 typedef multi_index_container< block_state_ptr, indexed_by< hashed_unique< tag<by_block_id>, member<block_header_state, block_id_type, &block_header_state::id>, std::hash<block_id_type>>, ordered_non_unique< tag<by_prev>, const_mem_fun<block_header_state, const block_id_type&, &block_header_state::prev> >, ordered_non_unique< tag<by_block_num>, composite_key< block_state, member<block_header_state,uint32_t,&block_header_state::block_num>, member<block_state,bool,&block_state::in_current_chain> >, composite_key_compare< std::less<uint32_t>, std::greater<bool> > >, ordered_non_unique< tag<by_lib_block_num>, composite_key< block_header_state, member<block_header_state,uint32_t,&block_header_state::dpos_irreversible_blocknum>, member<block_header_state,uint32_t,&block_header_state::bft_irreversible_blocknum>, member<block_header_state,uint32_t,&block_header_state::block_num> >, composite_key_compare< std::greater<uint32_t>, std::greater<uint32_t>, std::greater<uint32_t> > > > > fork_multi_index_type; struct fork_database_impl { fork_multi_index_type index; block_state_ptr head; //区块头 fc::path datadir; //存储路径 } class fork_database { public: //这里列举关键函数,详细定义参见 libraries/chain/include/eosio/chain/fork_database.hpp //根据区块id获取block_state信息 block_state_ptr get_block(const block_id_type &id) const; //根据区块高度从当前链中获取block_state信息 block_state_ptr get_block_in_current_chain_by_num(uint32_t num) const; //提供一个“有效的”区块状态，有可能以此建立分支 void set(block_state_ptr s); block_state_ptr add(signed_block_ptr b,bool trust = false); block_state_ptr add(block_state_ptr next_block); void remove(const block_id_type &id); void add(const header_confirmation &c); const block_state_ptr &head() const; //根据两个头block，获取两个分支（两个分支有共同的祖先，即两个头部的previous的值相同） pair<branch_type,branch_type> fetch_branch_from(const block_id_type &first,const block_id_type &second) const; //若该区块为invalid,将会从数据库中删除。若为valid，在发射irreversible信号后，所有比LIB大的block将会被修正 void set_validity(const block_state_ptr &h,bool valid); void mark_in_current_chain(const block_state_ptr &h,bool in_current_chain); void prune(const block_state_ptr&); signal<void(block_state_ptr)> irreversible; private: void set_bft_irreversible(block_id_type id); unique_ptr<for_database_impl> my; } 回到controller_impl::commit_block,该接受一个bool参数，该参数表示是否需要将controller_impl::pending->_pending_block_state加入fork_database,如果是则将pending->_pending_block_state->validated设为true,然后调用fork_database::add(block_state_ptr)将该块加入数据库，然后会根据当前的block_state进行数据库数据修正(后文fork_database部分有详细分析),然后检查是否正在重演该区块，如果否则将其加入可以缓存reversible_blocks,发射accept_block信号,该信号会调用net_plugin_impl::accept_block，函数，这些信号量的设置定义在plugins/net_plugin/net_plugin.cpp 3017行： ```cpp chain::controller&cc = my->chain_plug->chain(); { cc.accepted_block_header.connect( boost::bind(&net_plugin_impl::accepted_block_header, my.get(), _1)); cc.accepted_block.connect( boost::bind(&net_plugin_impl::accepted_block, my.get(), _1)); cc.irreversible_block.connect( boost::bind(&net_plugin_impl::irreversible_block, my.get(), _1)); cc.accepted_transaction.connect( boost::bind(&net_plugin_impl::accepted_transaction, my.get(), _1)); cc.applied_transaction.connect( boost::bind(&net_plugin_impl::applied_transaction, my.get(), _1)); cc.accepted_confirmation.connect( boost::bind(&net_plugin_impl::accepted_confirmation, my.get(), _1)); } commit_block关键代码如下： try { if (add_to_fork_db) { pending->_pending_block_state->validated = true; auto new_bsp = fork_db.add(pending->_pending_block_state); emit(self.accepted_block_header, pending->_pending_block_state); //更新head到最新生成的区块头 head = fork_db.head(); EOS_ASSERT(new_bsp == head, fork_database_exception, "committed block did not become the new head in fork database"); } if( !replaying ) { reversible_blocks.create<reversible_block_object>( [&]( auto& ubo ) { ubo.blocknum = pending->_pending_block_state->block_num; ubo.set_block( pending->_pending_block_state->block ); }); } emit( self.accepted_block, pending->_pending_block_state ); } catch (...) { // dont bother resetting pending, instead abort the block reset_pending_on_exit.cancel(); abort_block(); throw; } 至此controller_impl::commit_block工作完成。控制权回到producer_plugin_impl::produce_block,一次block生产调度就完成了，然后进入下一次调度。 fork_database分析： 结构如下： struct by_block_id; struct by_block_num; struct by_lib_block_num; struct by_prev; //建立一个基于block_state_ptr的多索引容器 //by_block_id以block id为索引 //by_block_num 以区块高度为索引，组合键<block_num,in_current_chain>,降序 //by_lib_block_num以最近的区块不可逆高度为索引，组合键<dpos_irreversible_blocknum,bft_irreversible_blocknum,block_num>，升序 //by_prev以前一个block id为索引 typedef multi_index_container< block_state_ptr, indexed_by< hashed_unique< tag<by_block_id>, member<block_header_state, block_id_type, &block_header_state::id>, std::hash<block_id_type>>, ordered_non_unique< tag<by_prev>, const_mem_fun<block_header_state, const block_id_type&, &block_header_state::prev> >, ordered_non_unique< tag<by_block_num>, composite_key< block_state, member<block_header_state,uint32_t,&block_header_state::block_num>, member<block_state,bool,&block_state::in_current_chain> >, composite_key_compare< std::less<uint32_t>, std::greater<bool> > >, ordered_non_unique< tag<by_lib_block_num>, composite_key< block_header_state, member<block_header_state,uint32_t,&block_header_state::dpos_irreversible_blocknum>, member<block_header_state,uint32_t,&block_header_state::bft_irreversible_blocknum>, member<block_header_state,uint32_t,&block_header_state::block_num> >, composite_key_compare< std::greater<uint32_t>, std::greater<uint32_t>, std::greater<uint32_t> > > > > fork_multi_index_type; struct fork_database_impl { fork_multi_index_type index; block_state_ptr head; //区块头 fc::path datadir; //存储路径 } class fork_database { public: //这里列举关键函数,详细定义参见 libraries/chain/include/eosio/chain/fork_database.hpp //根据区块id获取block_state信息 block_state_ptr get_block(const block_id_type &id) const; //根据区块高度从当前链中获取block_state信息 block_state_ptr get_block_in_current_chain_by_num(uint32_t num) const; //提供一个“有效的”区块状态，有可能以此建立分支 void set(block_state_ptr s); block_state_ptr add(signed_block_ptr b,bool trust = false); block_state_ptr add(block_state_ptr next_block); void remove(const block_id_type &id); void add(const header_confirmation &c); const block_state_ptr &head() const; //根据两个头block，获取两个分支（两个分支有共同的祖先，即两个头部的previous的值相同） pair<branch_type,branch_type> fetch_branch_from(const block_id_type &first,const block_id_type &second) const; //若该区块为invalid,将会从数据库中删除。若为valid，在发射irreversible信号后，所有比LIB大的block将会被修正 void set_validity(const block_state_ptr &h,bool valid); void mark_in_current_chain(const block_state_ptr &h,bool in_current_chain); void prune(const block_state_ptr&); signal<void(block_state_ptr)> irreversible; private: void set_bft_irreversible(block_id_type id); unique_ptr<for_database_impl> my; } 下面一次解释每个函数的实现： void fork_database::set(block_state_ptr s) //将s插入多索引容器中 auto result = my->index.insert( s ); EOS_ASSERT( s->id == s->header.id(), fork_database_exception, "block state id (${id}) is different from block state header id (${hid})", ("id", string(s->id))("hid", string(s->header.id())) ); //FC_ASSERT( s->block_num == s->header.block_num() ); EOS_ASSERT( result.second, fork_database_exception, "unable to insert block state, duplicate state detected" ); //更新head状态 if( !my->head ) { my->head = s; } else if( my->head->block_num < s->block_num ) { my->head = s; } transaction执行，涉及到的关键数据结构如下： struct action_receipt { account_name receiver; //执行该action的account digest_type act_digest; uint64_t global_sequence = 0; uint64_t recv_sequence = 0; flat_map<account_name,uint64_t> auth_sequence; fc::unsigned_int code_sequence; fc::unsigned_int abi_sequence; }; struct base_action_trace { action_receipt receipt; action act; fc::microseconds elapsed; uint64_t cpu_usage = 0; string console; uint64_t total_cpu_usage = 0; transaction_id_type trx_id; } struct action_trace : public base_action_trace { vector<action_trace> inline_traces; } struct transaction_trace { transaction_id_type id; fc::optional<transaction_receipt_header> receipt; fc::microseconds elapsed; uint64_t net_usage; bool scheduled = false; vector<action_trace> action_traces; transaction_trace_ptr failed_dtrx_trace; fc::optional<fc::exception> except; std::exception_ptr except_ptr; } 一个transaction是由一个或多个action组成的，这些action如果又一个失败了，那么该transaction也就失败了，已经执行过的action需要回滚。每个transaction必须在30ms内完成，如果一个包含了多个action且这些action执行时间总和超过30ms，则整个transaction失败。 chainbase分析 database基本数据结构 和数据库相关的数据结构均派生自 struct object,结构如下： template<typename T> class oid { public: oid( int64_t i = 0 ):_id{i}{} oid& operator++() { ++_id; return *this; } friend bool operator < ( const oid& a,const oid& b ) { return a._id < b._id; } friend bool operator > ( const oid& a,const oid& b ) { return a._id > b._id; } friend bool operator == ( const oid& a,const oid& b ) { return a._id == b._id; } friend bool operator != ( const oid& a,const oid& b ) { return a._id != b._id; } friend std::ostream& operator << ( std::ostream& s,const oid& id ) { s << boost::core::demangle( typeid( oid<T> ).name() ) << '(' << id._id << ')'; return s; } int64_t _id; }; template<uint16_t TypeNumber,typename Derived> struct object { typedef oid<Derived> id_type; static const uint16_t type_id = TypeNumber; //类型标识 }; 数据库的索引是通过元编程来实现的，每一种数据类型都有一个唯一id作为标识。程序在运行过程中要产生27个数据表： account_object: 保存账户信息，结构如下： class account_object : public chainbase::object<account_object_type,account_objct> { OBJECT_CTOR(account_object,(code)(abi)) id_type id; account_name name; //账户名称base32编码 uint8_t vm_type = 0; // vm_type uint8_t vm_version = 0; // vm_version bool privileged = false; // 是否优先 time_point last_code_update; //上次参与权限验证的时间 digest_type code_version; block_timestamp_type creation_date; //创建时间 shared_string code; shared_string abi; void set_abi( const eosio::chain::abi_def& a ) { abi.resize( fc::raw::pack_size( a ) ); fc::datastream<char*> ds( abi.data(), abi.size() ); fc::raw::pack( ds, a ); } eosio::chain::abi_def get_abi()const { eosio::chain::abi_def a; EOS_ASSERT( abi.size() != 0, abi_not_found_exception, "No ABI set on account ${n}", ("n",name) ); fc::datastream<const char*> ds( abi.data(), abi.size() ); fc::raw::unpack( ds, a ); return a; } }; 其中宏OBJECT_CTOR(account_object,(code)(abi))展开如下： account_object() = delete; public: template<typename Constructor, typename Allocator> account_object(Constructor&& c, chainbase::allocator<Allocator> a) : id(0) ,code(a) ,abi(a) { c(*this); } 该结构保存了账户的信息，对应的多索引容器为： struct by_name; using account_index = chainbase::shared_multi_index_container< account_object, indexed_by< ordered_unique<tag<by_id>, member<account_object, account_object::id_type, &account_object::id>>, ordered_unique<tag<by_name>, member<account_object, account_name, &account_object::name>> > >; 创建一个账户的函数调用在libraries/chain/eos_contract.cpp void apply_eosio_newaccount(apply_context& context)函数中. account_sequence_object 这个结构用来存储和账户相关的序列数据，具体结构如下： class account_sequence_object : public chainbase::object<account_sequence_object_type, account_sequence_object> { OBJECT_CTOR(account_sequence_object); id_type id; account_name name; uint64_t recv_sequence = 0; uint64_t auth_sequence = 0; uint64_t code_sequence = 0; uint64_t abi_sequence = 0; }; 对应的多索引容器如下： struct by_name; using account_sequence_index = chainbase::shared_multi_index_container< account_sequence_object, indexed_by< ordered_unique<tag<by_id>, member<account_sequence_object, account_sequence_object::id_type, &account_sequence_object::id>>, ordered_unique<tag<by_name>, member<account_sequence_object, account_name, &account_sequence_object::name>> > >; permission_object 用来存储授权相关信息，具体结构如下： class permission_object : public chainbase::object<permission_object_type, permission_object> { OBJECT_CTOR(permission_object, (auth) ) id_type id; permission_usage_object::id_type usage_id; id_type parent; ///< parent permission account_name owner; ///< the account this permission belongs to permission_name name; ///< human-readable name for the permission time_point last_updated; ///< the last time this authority was updated shared_authority auth; ///< authority required to execute this permission /** * @brief Checks if this permission is equivalent or greater than other * @tparam Index The permission_index * @return true if this permission is equivalent or greater than other, false otherwise * * Permissions are organized hierarchically such that a parent permission is strictly more powerful than its * children/grandchildren. This method checks whether this permission is of greater or equal power (capable of * satisfying) permission @ref other. */ template <typename Index> bool satisfies(const permission_object& other, const Index& permission_index) const { // If the owners are not the same, this permission cannot satisfy other if( owner != other.owner ) return false; // If this permission matches other, or is the immediate parent of other, then this permission satisfies other if( id == other.id || id == other.parent ) return true; // Walk up other's parent tree, seeing if we find this permission. If so, this permission satisfies other const permission_object* parent = &*permission_index.template get<by_id>().find(other.parent); while( parent ) { if( id == parent->parent ) return true; if( parent->parent._id == 0 ) return false; parent = &*permission_index.template get<by_id>().find(parent->parent); } // This permission is not a parent of other, and so does not satisfy other return false; } }; 对应的多索引容器为： struct by_parent; struct by_owner; struct by_name; using permission_index = chainbase::shared_multi_index_container< permission_object, indexed_by< ordered_unique<tag<by_id>, member<permission_object, permission_object::id_type, &permission_object::id>>, ordered_unique<tag<by_parent>, composite_key<permission_object, member<permission_object, permission_object::id_type, &permission_object::parent>, member<permission_object, permission_object::id_type, &permission_object::id> > >, ordered_unique<tag<by_owner>, composite_key<permission_object, member<permission_object, account_name, &permission_object::owner>, member<permission_object, permission_name, &permission_object::name> > >, ordered_unique<tag<by_name>, composite_key<permission_object, member<permission_object, permission_name, &permission_object::name>, member<permission_object, permission_object::id_type, &permission_object::id> > > > >; permission_usage_object 保存了授权的使用信息，具体结构如下： class permission_usage_object : public chainbase::object<permission_usage_object_type, permission_usage_object> { OBJECT_CTOR(permission_usage_object) id_type id; time_point last_used; ///< when this permission was last used }; 对应的多索引容器为： struct by_account_permission; using permission_usage_index = chainbase::shared_multi_index_container< permission_usage_object, indexed_by< ordered_unique<tag<by_id>, member<permission_usage_object, permission_usage_object::id_type, &permission_usage_object::id>> > >; permission_link_object 这个类记录了contract 和 action之间的permission_object的链接，以记录这些contract在执行的过程中所需要的权限 class permission_link_object : public chainbase::object<permission_link_object_type, permission_link_object> { OBJECT_CTOR(permission_link_object) id_type id; /// The account which is defining its permission requirements account_name account; /// The contract which account requires @ref required_permission to invoke account_name code; /// TODO: rename to scope /// The message type which account requires @ref required_permission to invoke /// May be empty; if so, it sets a default @ref required_permission for all messages to @ref code action_name message_type; /// The permission level which @ref account requires for the specified message types permission_name required_permission; }; 对应的索引如下： struct by_action_name; struct by_permission_name; using permission_link_index = chainbase::shared_multi_index_container< permission_link_object, indexed_by< ordered_unique<tag<by_id>, BOOST_MULTI_INDEX_MEMBER(permission_link_object, permission_link_object::id_type, id) >, ordered_unique<tag<by_action_name>, composite_key<permission_link_object, BOOST_MULTI_INDEX_MEMBER(permission_link_object, account_name, account), BOOST_MULTI_INDEX_MEMBER(permission_link_object, account_name, code), BOOST_MULTI_INDEX_MEMBER(permission_link_object, action_name, message_type) > >, ordered_unique<tag<by_permission_name>, composite_key<permission_link_object, BOOST_MULTI_INDEX_MEMBER(permission_link_object, account_name, account), BOOST_MULTI_INDEX_MEMBER(permission_link_object, permission_name, required_permission), BOOST_MULTI_INDEX_MEMBER(permission_link_object, account_name, code), BOOST_MULTI_INDEX_MEMBER(permission_link_object, action_name, message_type) > > > >; key_value_object 结构如下： struct key_value_object : public chainbase::object<key_value_object_type, key_value_object> { OBJECT_CTOR(key_value_object, (value)) typedef uint64_t key_type; static const int number_of_keys = 1; id_type id; table_id t_id; uint64_t primary_key; //主键 account_name payer = 0; shared_string value; //值 }; 对应的索引： using key_value_index = chainbase::shared_multi_index_container< key_value_object, indexed_by< ordered_unique<tag<by_id>, member<key_value_object, key_value_object::id_type, &key_value_object::id>>, ordered_unique<tag<by_scope_primary>, composite_key< key_value_object, member<key_value_object, table_id, &key_value_object::t_id>, member<key_value_object, uint64_t, &key_value_object::primary_key> >, composite_key_compare< std::less<table_id>, std::less<uint64_t> > > > >; index64_object 是基于多索引容器建立的一个二级索引，定义如下： typedef secondary_index<uint64_t,index64_object_type>::index_object index64_object; typedef secondary_index<uint64_t,index64_object_type>::index_index index64_index; index128_object 同上 index256_object 同上 index_double_object 同上 index_long_double_object 同上 global_property_object 存储了初始设定的值，用来调用块参数： class global_property_object : public chainbase::object<global_property_object_type, global_property_object> { OBJECT_CTOR(global_property_object, (proposed_schedule)) id_type id; optional<block_num_type> proposed_schedule_block_num; shared_producer_schedule_type proposed_schedule; chain_config configuration; }; 对应的索引： using dynamic_global_property_multi_index = chainbase::shared_multi_index_container< dynamic_global_property_object, indexed_by< ordered_unique<tag<by_id>, BOOST_MULTI_INDEX_MEMBER(dynamic_global_property_object, dynamic_global_property_object::id_type, id) > > >; dynamic_global_property_object 记录了区块链正常操作期间所计算的值，这些值反映了区块链的当前的全局的值： class dynamic_global_property_object : public chainbase::object<dynamic_global_property_object_type, dynamic_global_property_object> { OBJECT_CTOR(dynamic_global_property_object) id_type id; uint64_t global_action_sequence = 0; }; 对应的索引为： using global_property_multi_index = chainbase::shared_multi_index_container< global_property_object, indexed_by< ordered_unique<tag, BOOST_MULTI_INDEX_MEMBER(global_property_object, global_property_object::id_type, id) ; 14. block_summary_object block的一个简明信息，用于transaction的TaPos验证。结构如下： class block_summary_object : public chainbase::object<block_summary_object_type, block_summary_object> { OBJECT_CTOR(block_summary_object) id_type id; block_id_type block_id; }; ``` 对应的索引为： ``` struct by_block_id; using block_summary_multi_index = chainbase::shared_multi_index_container< block_summary_object, indexed_by< ordered_unique<tag<by_id>, BOOST_MULTI_INDEX_MEMBER(block_summary_object, block_summary_object::id_type, id)> // ordered_unique<tag<by_block_id>, BOOST_MULTI_INDEX_MEMBER(block_summary_object, block_id_type, block_id)> > >; ``` 在controller::finalize_block函数中，会产生一个该结构的记录： ``` set_action_merkle(); set_trx_merkle(); auto p = pending->_pending_block_state; p->id = p->header.id(); create_block_summary(p->id); //创建一个block_summary ``` transaction_object 记录了transaction的过期时间，在该过期时间内，如果该transaction还没得倒确认，则会删除： class transaction_object : public chainbase::object<transaction_object_type, transaction_object> { OBJECT_CTOR(transaction_object) id_type id; time_point_sec expiration; transaction_id_type trx_id; }; 对应的索引为： struct by_expiration; struct by_trx_id; using transaction_multi_index = chainbase::shared_multi_index_container< transaction_object, indexed_by< ordered_unique< tag<by_id>, BOOST_MULTI_INDEX_MEMBER(transaction_object, transaction_object::id_type, id)>, ordered_unique< tag<by_trx_id>, BOOST_MULTI_INDEX_MEMBER(transaction_object, transaction_id_type, trx_id)>, ordered_unique< tag<by_expiration>, composite_key< transaction_object, BOOST_MULTI_INDEX_MEMBER( transaction_object, time_point_sec, expiration ), BOOST_MULTI_INDEX_MEMBER( transaction_object, transaction_object::id_type, id) > > > >; 在transaction执行的时候，会对收到的transaction做一个初始化工作，transaction_context::init_for_input_trx会调用该函数产生一个transaction_object记录： published = control.pending_block_time(); is_input = true; if (!control.skip_trx_checks()) { control.validate_expiration(trx); control.validate_tapos(trx); control.validate_referenced_accounts(trx); } init( initial_net_usage); if (!skip_recording) record_transaction( id, trx.expiration ); /// checks for dupes generated_transaction_object 结构如下： class generated_transaction_object : public chainbase::object<generated_transaction_object_type, generated_transaction_object> { OBJECT_CTOR(generated_transaction_object, (packed_trx) ) id_type id; transaction_id_type trx_id; account_name sender; uint128_t sender_id = 0; /// ID given this transaction by the sender account_name payer; time_point delay_until; /// this generated transaction will not be applied until the specified time time_point expiration; /// this generated transaction will not be applied after this time time_point published; shared_string packed_trx; uint32_t set( const transaction& trx ) { auto trxsize = fc::raw::pack_size( trx ); packed_trx.resize( trxsize ); fc::datastream<char*> ds( packed_trx.data(), trxsize ); fc::raw::pack( ds, trx ); return trxsize; } }; 对应的索引： struct by_trx_id; struct by_expiration; struct by_delay; struct by_status; struct by_sender_id; using generated_transaction_multi_index = chainbase::shared_multi_index_container< generated_transaction_object, indexed_by< ordered_unique< tag, BOOST_MULTI_INDEX_MEMBER(generated_transaction_object, generated_transaction_object::id_type, id)>, ordered_unique< tag, BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, transaction_id_type, trx_id)>, ordered_unique< tag, composite_key< generated_transaction_object, BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, expiration), BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id) , ordered_unique< tag, composite_key< generated_transaction_object, BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, delay_until), BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id) , ordered_unique< tag, composite_key< generated_transaction_object, BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, account_name, sender), BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, uint128_t, sender_id) ; 17. producer_object 结构如下： class producer_object : public chainbase::object<producer_object_type, producer_object> { OBJECT_CTOR(producer_object) id_type id; account_name owner; uint64_t last_aslot = 0; public_key_type signing_key; int64_t total_missed = 0; uint32_t last_confirmed_block_num = 0; /// The blockchain configuration values this producer recommends chain_config configuration; }; 对应的索引： struct by_key; struct by_owner; using producer_multi_index = chainbase::shared_multi_index_container< producer_object, indexed_by< ordered_unique<tag, member<producer_object, producer_object::id_type, &producer_object::id>>, ordered_unique<tag, member<producer_object, account_name, &producer_object::owner>>, ordered_unique<tag, composite_key<producer_object, member<producer_object, public_key_type, &producer_object::signing_key>, member<producer_object, producer_object::id_type, &producer_object::id> ; 18. account_control_history_object 19. public_key_history_object 20. table_id_object 结构如下： class table_id_object : public chainbase::object<table_id_object_type, table_id_object> { OBJECT_CTOR(table_id_object) id_type id; account_name code; scope_name scope; table_name table; account_name payer; uint32_t count = 0; /// the number of elements in the table }; 对应的索引： struct by_code_scope_table; using table_id_multi_index = chainbase::shared_multi_index_container< table_id_object, indexed_by< ordered_unique<tag, member<table_id_object, table_id_object::id_type, &table_id_object::id> , ordered_unique<tag, composite_key< table_id_object, member<table_id_object, account_name, &table_id_object::code>, member<table_id_object, scope_name, &table_id_object::scope>, member<table_id_object, table_name, &table_id_object::table> ; 21. resource_limits_object 结构如下： struct resource_limits_object : public chainbase::object<resource_limits_object_type, resource_limits_object> { OBJECT_CTOR(resource_limits_object) id_type id; account_name owner; bool pending = false; int64_t net_weight = -1; int64_t cpu_weight = -1; int64_t ram_bytes = -1; }; 对应的索引: struct by_owner; struct by_dirty; using resource_limits_index = chainbase::shared_multi_index_container< resource_limits_object, indexed_by< ordered_unique<tag, member<resource_limits_object, resource_limits_object::id_type, &resource_limits_object::id>>, ordered_unique<tag, composite_key<resource_limits_object, BOOST_MULTI_INDEX_MEMBER(resource_limits_object, bool, pending), BOOST_MULTI_INDEX_MEMBER(resource_limits_object, account_name, owner) ; 22. resource_usage_object 结构如下： struct resource_usage_object : public chainbase::object<resource_usage_object_type, resource_usage_object> { OBJECT_CTOR(resource_usage_object) id_type id; account_name owner; usage_accumulator net_usage; usage_accumulator cpu_usage; uint64_t ram_usage = 0; }; 对应的索引： using resource_usage_index = chainbase::shared_multi_index_container< resource_usage_object, indexed_by< ordered_unique<tag, member<resource_usage_object, resource_usage_object::id_type, &resource_usage_object::id>>, ordered_unique<tag, member<resource_usage_object, account_name, &resource_usage_object::owner> > ; 23. resource_limits_config_object 结构如下： class resource_limits_config_object : public chainbase::object<resource_limits_config_object_type, resource_limits_config_object> { OBJECT_CTOR(resource_limits_config_object); id_type id; static_assert( config::block_interval_ms > 0, "config::block_interval_ms must be positive" ); static_assert( config::block_cpu_usage_average_window_ms >= config::block_interval_ms, "config::block_cpu_usage_average_window_ms cannot be less than config::block_interval_ms" ); static_assert( config::block_size_average_window_ms >= config::block_interval_ms, "config::block_size_average_window_ms cannot be less than config::block_interval_ms" ); elastic_limit_parameters cpu_limit_parameters = {EOS_PERCENT(config::default_max_block_cpu_usage, config::default_target_block_cpu_usage_pct), config::default_max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}}; elastic_limit_parameters net_limit_parameters = {EOS_PERCENT(config::default_max_block_net_usage, config::default_target_block_net_usage_pct), config::default_max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}}; uint32_t account_cpu_usage_average_window = config::account_cpu_usage_average_window_ms / config::block_interval_ms; uint32_t account_net_usage_average_window = config::account_net_usage_average_window_ms / config::block_interval_ms; }; 对应的索引： using resource_limits_config_index = chainbase::shared_multi_index_container< resource_limits_config_object, indexed_by< ordered_unique<tag, member<resource_limits_config_object, resource_limits_config_object::id_type, &resource_limits_config_object::id>> ; 24. resource_limits_state_object class resource_limits_state_object : public chainbase::object<resource_limits_state_object_type, resource_limits_state_object> { OBJECT_CTOR(resource_limits_state_object); id_type id; /** * Track the average netusage for blocks */ usage_accumulator average_block_net_usage; /** * Track the average cpu usage for blocks */ usage_accumulator average_block_cpu_usage; void update_virtual_net_limit( const resource_limits_config_object& cfg ); void update_virtual_cpu_limit( const resource_limits_config_object& cfg ); uint64_t pending_net_usage = 0ULL; uint64_t pending_cpu_usage = 0ULL; uint64_t total_net_weight = 0ULL; uint64_t total_cpu_weight = 0ULL; uint64_t total_ram_bytes = 0ULL; /** * The virtual number of bytes that would be consumed over blocksize_average_window_ms * if all blocks were at their maximum virtual size. This is virtual because the * real maximum block is less, this virtual number is only used for rate limiting users. * * It's lowest possible value is max_block_size * blocksize_average_window_ms / block_interval * It's highest possible value is 1000 times its lowest possible value * * This means that the most an account can consume during idle periods is 1000x the bandwidth * it is gauranteed under congestion. * * Increases when average_block_size < target_block_size, decreases when * average_block_size > target_block_size, with a cap at 1000x max_block_size * and a floor at max_block_size; **/ uint64_t virtual_net_limit = 0ULL; /** * Increases when average_bloc */ uint64_t virtual_cpu_limit = 0ULL; }; 对应的索引： using resource_limits_state_index = chainbase::shared_multi_index_container< resource_limits_state_object, indexed_by< ordered_unique<tag, member<resource_limits_state_object, resource_limits_state_object::id_type, &resource_limits_state_object::id>> ; 25. account_history_object 26. action_history_object 27. reversible_block_object 记录还没变成不可逆的区块，结构如下： class reversible_block_object : public chainbase::object<reversible_block_object_type, reversible_block_object> { OBJECT_CTOR(reversible_block_object,(packedblock) ) id_type id; uint32_t blocknum = 0; shared_string packedblock; void set_block( const signed_block_ptr& b ) { packedblock.resize( fc::raw::pack_size( *b ) ); fc::datastream<char*> ds( packedblock.data(), packedblock.size() ); fc::raw::pack( ds, *b ); } signed_block_ptr get_block()const { fc::datastream<const char*> ds( packedblock.data(), packedblock.size() ); auto result = std::make_shared<signed_block>(); fc::raw::unpack( ds, *result ); return result; } }; 对应的索引为： struct by_num; using reversible_block_index = chainbase::shared_multi_index_container< reversible_block_object, indexed_by< ordered_unique<tag, member<reversible_block_object, reversible_block_object::id_type, &reversible_block_object::id>>, ordered_unique<tag, member<reversible_block_object, uint32_t, &reversible_block_object::blocknum>> ; 以上数据表的初始化工作在controller_impl::add_indices()函数中： reversible_blocks.add_index<reversible_block_index>(); db.add_index<account_index>(); db.add_index<account_sequence_index>(); db.add_index<table_id_multi_index>(); db.add_index<key_value_index>(); db.add_index<index64_index>(); db.add_index<index128_index>(); db.add_index<index256_index>(); db.add_index<index_double_index>(); db.add_index<index_long_double_index>(); db.add_index<global_property_multi_index>(); db.add_index<dynamic_global_property_multi_index>(); db.add_index<block_summary_multi_index>(); db.add_index<transaction_multi_index>(); db.add_index<generated_transaction_multi_index>(); authorization.add_indices(); resource_limits.add_indices(); 在authorization_manager::add_indices(): _db.add_index<permission_index>(); _db.add_index<permission_usage_index>(); _db.add_index<permission_link_index>(); 在resource_limits_manager::add_indices(): _db.add_index<resource_limits_index>(); _db.add_index<resource_usage_index>(); _db.add_index<resource_limits_state_index>(); _db.add_index<resource_limits_config_index>(); 在transaction执行过程中涉及到的数据及流程如下： 调用controller_impl::push_transaction,在该函数中会生成一个transaction_context类型的变量trx_context, 然后对transaction进行初始化操作： transaction_context trx_context(self, trx->trx, trx->id); if ((bool)subjective_cpu_leeway && pending->_block_status == controller::block_status::incomplete) { trx_context.leeway = *subjective_cpu_leeway; } trx_context.deadline = deadline; trx_context.explicit_billed_cpu_time = explicit_billed_cpu_time; trx_context.billed_cpu_time_us = billed_cpu_time_us; trace = trx_context.trace; try { if( trx->implicit ) { trx_context.init_for_implicit_trx(); trx_context.can_subjectively_fail = false; } else { bool skip_recording = replay_head_time && (time_point(trx->trx.expiration) <= *replay_head_time); trx_context.init_for_input_trx( trx->packed_trx.get_unprunable_size(), trx->packed_trx.get_prunable_size(), trx->trx.signatures.size(), skip_recording); } if( trx_context.can_subjectively_fail && pending->_block_status == controller::block_status::incomplete ) { check_actor_list( trx_context.bill_to_accounts ); // Assumes bill_to_accounts is the set of actors authorizing the transaction } trx_context.delay = fc::seconds(trx->trx.delay_sec); 然后对transaction进行授权检查： if( !self.skip_auth_check() && !trx->implicit ) { authorization.check_authorization( trx->trx.actions, trx->recover_keys( chain_id ), {}, trx_context.delay, [](){} /*std::bind(&transaction_context::add_cpu_usage_and_check_time, &trx_context, std::placeholders::_1)*/, false ); } trx_context.exec(); trx_context.finalize(); / 在此需要用到上面的global_property_object数据表，然后调用transaction_context::exec()对action进行调用： EOS_ASSERT( is_initialized, transaction_exception, "must first initialize" ); if( apply_context_free ) { for( const auto& act : trx.context_free_actions ) { trace->action_traces.emplace_back(); dispatch_action( trace->action_traces.back(), act, true );//action调用 } } if( delay == fc::microseconds() ) { for( const auto& act : trx.actions ) { trace->action_traces.emplace_back(); dispatch_action( trace->action_traces.back(), act ); //action调用 } } else { schedule_transaction(); } 在transaction_context::dispatch_action中会产生一个类型为apply_context的变量 acontext,调用apply_context::exec()进行真正的action的执行 apply_context acontext( control, *this, a, recurse_depth ); acontext.context_free = context_free; acontext.receiver = receiver; try { acontext.exec(); } catch( ... ) { trace = move(acontext.trace); throw; } trace = move(acontext.trace); 在apply_context::exec()中会调用apply_context::exec_one() 调用vm借口进入合约层，进行action和数据的解析并执行。 vm会通过注册进入的借口来调用action执行，注册的借口为： REGISTER_INTRINSICS(transaction_api, (send_inline, void(int, int) ) (send_context_free_inline, void(int, int) ) (send_deferred, void(int, int64_t, int, int, int32_t) ) (cancel_deferred, int(int) ) ); transaction_api接口定义如下： class transaction_api : public context_aware_api { public: using context_aware_api::context_aware_api; void send_inline( array_ptr<char> data, size_t data_len ) { //TODO: Why is this limit even needed? And why is it not consistently checked on actions in input or deferred transactions EOS_ASSERT( data_len < context.control.get_global_properties().configuration.max_inline_action_size, inline_action_too_big, "inline action too big" ); action act; fc::raw::unpack<action>(data, data_len, act); context.execute_inline(std::move(act)); } void send_context_free_inline( array_ptr<char> data, size_t data_len ) { //TODO: Why is this limit even needed? And why is it not consistently checked on actions in input or deferred transactions EOS_ASSERT( data_len < context.control.get_global_properties().configuration.max_inline_action_size, inline_action_too_big, "inline action too big" ); action act; fc::raw::unpack<action>(data, data_len, act); context.execute_context_free_inline(std::move(act)); } void send_deferred( const uint128_t& sender_id, account_name payer, array_ptr<char> data, size_t data_len, uint32_t replace_existing) { try { transaction trx; fc::raw::unpack<transaction>(data, data_len, trx); context.schedule_deferred_transaction(sender_id, payer, std::move(trx), replace_existing); } FC_RETHROW_EXCEPTIONS(warn, "data as hex: ${data}", ("data", fc::to_hex(data, data_len))) } bool cancel_deferred( const unsigned __int128& val ) { fc::uint128_t sender_id(val>>64, uint64_t(val) ); return context.cancel_deferred_transaction( (unsigned __int128)sender_id ); } }; 以上为系统数据表和database交互的模式。 智能合约的持久化存储和database交互 说到智能合约的持久化存储离不开Multi-Index,这个Multi-index是EOS实现的类boost::multi_index_container的功能，定义在： contracts/eosiolib/multi_index.hpp文件中，采用的是hana元编程，我们写的智能合约中的数据就是存储在这个multi_index中的。 该类实现了数据的增删改查接口：emplace,erase,modify,get,find等接口，通过这些接口和database进行交互。 emplace中和database交互的关键代码： datastream<char*> ds( (char*)buffer, size ); ds << obj; auto pk = obj.primary_key(); //db_store_i64就是和database进行交互的接口 i.__primary_itr = db_store_i64( _scope, TableName, payer, pk, buffer, size ); if ( max_stack_buffer_size < size ) { free(buffer); } erase中和database交互的关键代码: eosio_assert( itr2 != _items_vector.rend(), "attempt to remove object that was not in multi_index" ); _items_vector.erase(--(itr2.base())); //和database进行交互 db_remove_i64( objitem.__primary_itr ); 其他接口和数据库交互请参看源码。 multi_index的使用： class book_manager : public eosio::contract { public: void create() void delete() void find() private: account_name _contract_name; struct book { uint64_t _id; std::string _name; EOSLIB_SERIALIZE(book,(_id)(_name)); } typedef eosio::multi_index<N(book),book> _table; } 大概类似于上面的代码，后面我会出一个详细智能合约开发的例子。EOSLIB_SERIALIZE宏用于序列化book接口，将其转为字节数组。后面我就可以基于_table对book进行管理了，增删改查也会与database进行交互，现在来看一下database提供的api接口，这些接口定义在 libraries/chain/wasm_interface.cpp中： class database_api : public context_aware_api { public: using context_aware_api::context_aware_api; int db_store_i64( uint64_t scope, uint64_t table, uint64_t payer, uint64_t id, array_ptr<const char> buffer, size_t buffer_size ) { return context.db_store_i64( scope, table, payer, id, buffer, buffer_size ); } void db_update_i64( int itr, uint64_t payer, array_ptr<const char> buffer, size_t buffer_size ) { context.db_update_i64( itr, payer, buffer, buffer_size ); } void db_remove_i64( int itr ) { context.db_remove_i64( itr ); } int db_get_i64( int itr, array_ptr<char> buffer, size_t buffer_size ) { return context.db_get_i64( itr, buffer, buffer_size ); } int db_next_i64( int itr, uint64_t& primary ) { return context.db_next_i64(itr, primary); } int db_previous_i64( int itr, uint64_t& primary ) { return context.db_previous_i64(itr, primary); } int db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { return context.db_find_i64( code, scope, table, id ); } int db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { return context.db_lowerbound_i64( code, scope, table, id ); } int db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { return context.db_upperbound_i64( code, scope, table, id ); } int db_end_i64( uint64_t code, uint64_t scope, uint64_t table ) { return context.db_end_i64( code, scope, table ); } DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx64, uint64_t) DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx128, uint128_t) DB_API_METHOD_WRAPPERS_ARRAY_SECONDARY(idx256, 2, uint128_t) DB_API_METHOD_WRAPPERS_FLOAT_SECONDARY(idx_double, float64_t) DB_API_METHOD_WRAPPERS_FLOAT_SECONDARY(idx_long_double, float128_t) } ; 由上可见database_api调用的是apply_context提供的接口，而appy_context中有database的引用，最终所有的操作都会反映到database中去 转载自：https://github.com/123youyouer/eosex/tree/master/block_chain
上篇笔记写到远程节点给本地节点发送notice消息，通知本地节点同步区块。本篇笔记继续学习本地节点如何从远程节点同步区块，这次重点写notice消息的发送与处理过程。 1、notice_message消息结构 //消息结构 定义部分 struct notice_message { notice_message () : known_trx(), known_blocks() {} ordered_txn_ids known_trx; ordered_blk_ids known_blocks; }; //选择查看定义，跳转到 using ordered_txn_ids = select_ids<transaction_id_type>; using ordered_blk_ids = select_ids<block_id_type>; //选择查看定义，跳转到 template<typename T> struct select_ids { select_ids () : mode(none),pending(0),ids() {} id_list_modes mode; uint32_t pending; vector<T> ids; //模板类 bool empty () const { return (mode == none || ids.empty()); } }; notice_message消息类型包括id_list_modes类型的变量，该类型为一个枚举类型，包括enum id_list_modes { none, catch_up, last_irr_catch_up, normal };，pending表示区块数目。 2、发送notice_message消息 远程节点给本地节点发送notice_message，通知本地节点同步到不可逆区块。 //远程节点给本地节点发送的通知消息内容 消息调用部分 if (lib_num > msg.head_num ) { fc_dlog(logger, "sync check state 2"); if (msg.generation > 1 || c->protocol_version > proto_base) { notice_message note; note.known_trx.pending = lib_num; note.known_trx.mode = last_irr_catch_up; //类型为不可逆区块类型 note.known_blocks.mode = last_irr_catch_up; note.known_blocks.pending = head; c->enqueue( note ); } c->syncing = true; return; } 远程节点发送的notice_message消息内容比较简单，包括不可逆区块数、最新区块数、同步方式（同步到不可逆区块或者同步到最新区块），然后放入到消息队列等待发送出去。 3、接收notice_message消息 同上篇笔记所写的一样，处理notice_message消息同样在一个handle_message的重载函数里进行，参数为当前通信的远程节点的connect对象指针和消息。 void net_plugin_impl::handle_message( connection_ptr c, const notice_message &msg) { // peer tells us about one or more blocks or txns. When done syncing, forward on // notices of previously unknown blocks or txns, // peer_ilog(c, "received notice_message"); c->connecting = false; request_message req; bool send_req = false; if (msg.known_trx.mode != none) { fc_dlog(logger,"this is a ${m} notice with ${n} blocks", ("m",modes_str(msg.known_trx.mode))("n",msg.known_trx.pending)); } switch (msg.known_trx.mode) { case none: break; case last_irr_catch_up: { c->last_handshake_recv.head_num = msg.known_trx.pending; req.req_trx.mode = none; break; } case catch_up : { if( msg.known_trx.pending > 0) { // plan to get all except what we already know about. req.req_trx.mode = catch_up; send_req = true; size_t known_sum = local_txns.size(); if( known_sum ) { for( const auto& t : local_txns.get<by_id>( ) ) { req.req_trx.ids.push_back( t.id ); } } } break; } case normal: { dispatcher->recv_notice (c, msg, false); } } if (msg.known_blocks.mode != none) { fc_dlog(logger,"this is a ${m} notice with ${n} blocks", ("m",modes_str(msg.known_blocks.mode))("n",msg.known_blocks.pending)); } switch (msg.known_blocks.mode) { case none : { if (msg.known_trx.mode != normal) { return; } break; } case last_irr_catch_up: //同步到最新区块和同步到不可逆区块 调用的函数相同 case catch_up: { sync_master->recv_notice(c,msg); //通过sync_master进行区块同步 break; } case normal : { dispatcher->recv_notice (c, msg, false); break; } default: { peer_elog(c, "bad notice_message : invalid known_blocks.mode ${m}",("m",static_cast<uint32_t>(msg.known_blocks.mode))); } } fc_dlog(logger, "send req = ${sr}", ("sr",send_req)); if( send_req) { c->enqueue(req); } } 由于远程节点发送的notice_message数据包内容为同步到不可逆区块，mode为last_irr_catch_up，所以代码中调用sync_master->recv_notice(c,msg)函数来进行区块同步。此函数同步区块分为两种情况 void sync_manager::recv_notice (connection_ptr c, const notice_message &msg) { fc_ilog (logger, "sync_manager got ${m} block notice",("m",modes_str(msg.known_blocks.mode))); if (msg.known_blocks.mode == catch_up) { if (msg.known_blocks.ids.size() == 0) { elog ("got a catch up with ids size = 0"); } else { //同步到最新区块 verify_catchup(c, msg.known_blocks.pending, msg.known_blocks.ids.back()); } } else { c->last_handshake_recv.last_irreversible_block_num = msg.known_trx.pending; reset_lib_num (c); //同步到不可逆区块 start_sync(c, msg.known_blocks.pending); } } 我们重点看同步到不可逆区块，函数为start_sync，参数为connection对象指针和消息中包含的最新区块数（不过我觉得应该是不可逆区块数）。 void sync_manager::start_sync( connection_ptr c, uint32_t target) { if( target > sync_known_lib_num) { sync_known_lib_num = target; } if (!sync_required()) { uint32_t bnum = chain_plug->chain().last_irreversible_block_num(); uint32_t hnum = chain_plug->chain().fork_db_head_block_num(); fc_dlog( logger, "We are already caught up, my irr = ${b}, head = ${h}, target = ${t}", ("b",bnum)("h",hnum)("t",target)); return; } if (state == in_sync) { set_state(lib_catchup); sync_next_expected_num = chain_plug->chain().last_irreversible_block_num() + 1; } fc_ilog(logger, "Catching up with chain, our last req is ${cc}, theirs is ${t} peer ${p}", ( "cc",sync_last_requested_num)("t",target)("p",c->peer_name())); request_next_chunk(c); } 在这里，成员变量sync_known_lib_num赋值为sync_known_lib_num和target的最大值。该成员变量表示已知的当前不可逆区块数，可见上一步同步到不可逆区块的调用应该为start_sync(c, msg.known_trx.pending);，在这个函数里，主要是检查区块同步信息，然后调用request_next_chunk(c)函数进行区块同步。 void sync_manager::request_next_chunk( connection_ptr conn ) { uint32_t head_block = chain_plug->chain().fork_db_head_block_num(); ······ //省略代码 if( sync_last_requested_num != sync_known_lib_num ) { uint32_t start = sync_next_expected_num; uint32_t end = start + sync_req_span - 1; // sync_req_span为配置文件中设置，每次同步多少个区块，默认是100个。 if( end > sync_known_lib_num ) end = sync_known_lib_num; if( end > 0 && end >= start ) { fc_ilog(logger, "requesting range ${s} to ${e}, from ${n}", ("n",source->peer_name())("s",start)("e",end)); source->request_sync_blocks(start, end);//同样是发送某种消息来同步区块 sync_last_requested_num = end; } } } 查看request_next_chunk函数的功能，我们发现内部调用request_sync_blocks函数来发送同步消息，具体实现需要进入分析。 void connection::request_sync_blocks (uint32_t start, uint32_t end) { sync_request_message srm = {start,end}; enqueue( net_message(srm)); sync_wait(); } request_sync_blocks函数内部实现很简单，构造了一个sync_request_message类型的消息，然后放入到消息队列中，消息的内容为起始区块和结束区块，每次请求100个（config.ini中默认设置，可以修改）。 此时这篇笔记讨论的消息类型先到这里，下一篇笔记讨论sync_request_message类型的消息，分析本地节点是如何从远程同步区块的。 转载自：https://github.com/RootkitKiller/EosLearn/blob/master/Eos%E4%BB%A3%E7%A0%81%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%94%EF%BC%89%E5%90%8C%E6%AD%A5%E5%8C%BA%E5%9D%97--p2p%E9%80%9A%E4%BF%A1--notice_message.md
上一篇整体上学习了net_plugin插件，但是具体的内容没有写。从这篇笔记开始，重点分析各节点之间是如何通过p2p插件同步区块的。首先，启动一个节点之后，当前节点向远程节点发送的第一个数据包就是handshake_message类型的（time时间戳类型除外），所以这篇笔记中分析handshake_message类型数据包的发送过程和接收过程。 运行环境：CLion编译器，并配置连接到主网节点。 1、handshake_message消息结构 struct handshake_message { uint16_t network_version = 0; ///< incremental value above a computed base chain_id_type chain_id; ///< used to identify chain fc::sha256 node_id; ///< used to identify peers and prevent self-connect chain::public_key_type key; ///< authentication key; may be a producer or peer key, or empty tstamp time; fc::sha256 token; ///< digest of time to prove we own the private key of the key above chain::signature_type sig; ///< signature for the digest string p2p_address; uint32_t last_irreversible_block_num = 0; block_id_type last_irreversible_block_id; uint32_t head_num = 0; block_id_type head_id; string os; string agent; int16_t generation; }; 握手包内容包括：网络版本、chain_id、node_id、p2p_address、节点名称等配置信息，以及链的状态（当前节点的不可逆区块数、不可逆区块id、最新区块id、最新区块数）。其中区块数是指区块编号（1、2、3......），区块id是指32位的hash值。 2、发送handshake_message消息 启动本地节点之后，会连接到配置文件中的p2p节点，并获取当前链信息、配置信息，然后构建handshake_message包并发送到其他p2p节点。 void connection::send_handshake( ) { handshake_initializer::populate(last_handshake_sent); //填充消息内容 last_handshake_sent.generation = ++sent_handshake_count; fc_dlog(logger, "Sending handshake generation ${g} to ${ep}", ("g",last_handshake_sent.generation)("ep", peer_name())); enqueue(last_handshake_sent); //构建完成握手包之后，放入队列中 } 发送的数据包内容如下： //大小 348个字节 连接之后发送的报文 0040 5c 01 00 00 00 b6 04 ac a3 76 f2 06 b8 fc 2Ñ\....¶.¬£vò.¸ü 0050 25 a6 ed 44 db dc 66 54 7c 36 c6 c3 3e 3a 11 9f %¦íDÛÜfT|6ÆÃ>:.. 0060 fb ea ef 94 36 42 f0 e9 06 da b2 ea 2d 82 ce 71 ûêï.6Bðé.Ú²ê-.Îq 0070 45 c4 74 df 2f 3f 5f d9 df 11 af 8c 70 62 06 7a EÄtß/?_Ùß.¯.pb.z 0080 5d de 3e 6b 87 10 22 18 0c 00 00 00 00 00 00 00 ]Þ>k.."......... 0090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00a0 00 00 00 00 00 00 00 00 00 00 00 aa 8b b8 81 00 ...........ª.¸.. 00b0 77 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 w............... 00c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0110 00 00 00 00 00 2d 6d 6f 6f 6e 69 6e 77 61 74 65 .....-mooninwate 0120 72 64 65 4d 61 63 42 6f 6f 6b 2d 50 72 6f 2e 6c rdeMacBook-Pro.l 0130 6f 63 61 6c 3a 39 38 37 36 20 2d 20 64 61 62 32 ocal:9876 - dab2 0140 65 61 32 00 00 00 00 00 00 00 00 00 00 00 00 00 ea2............. 0150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0160 00 00 00 00 00 00 00 01 00 00 00 00 00 00 01 40 ...............@ 0170 51 47 47 7a b2 f5 f5 1c da 42 7b 63 81 91 c6 6d QGGz²õõ.ÚB{c..Æm 0180 2c 59 aa 39 2d 5c 2c 98 07 6c b0 03 6f 73 78 10 ,Yª9-\,..l°.osx. 0190 22 45 4f 53 20 54 65 73 74 20 41 67 65 6e 74 22 "EOS Test Agent" 01a0 01 00 3、接收handshake_message消息 eos接收到其他节点发送过来的消息后，根据消息类型的不同，重载了不同的handle_message函数，其中handshake_message类型，主要功能包括两方面： （1）、验证接收到的handshake包内容中的链id、节点id、网络版本等信息。 （2）、对比接收到的消息中链的状态和自身链的状态对比，然后进行区块同步。 //重载后的函数， 处理handshake_message消息 void net_plugin_impl::handle_message( connection_ptr c, const handshake_message &msg) { peer_ilog(c, "received handshake_message"); if (!is_valid(msg)) { peer_elog( c, "bad handshake message"); c->enqueue( go_away_message( fatal_other )); return; } controller& cc = chain_plug->chain(); uint32_t lib_num = cc.last_irreversible_block_num( ); uint32_t peer_lib = msg.last_irreversible_block_num; if (msg.generation == 1) { if( c->peer_addr.empty() || c->last_handshake_recv.node_id == fc::sha256()) { fc_dlog(logger, "checking for duplicate" ); //遍历所有连接的节点，c代表当前连接中的节点 保证同一个p2p节点只存在一个连接 ········ } } if( msg.chain_id != chain_id) { elog( "Peer on a different chain. Closing connection"); c->enqueue( go_away_message(go_away_reason::wrong_chain) ); return; } ······ if( c->node_id != msg.node_id) { c->node_id = msg.node_id; } ········ c->last_handshake_recv = msg; c->_logger_variant.reset(); sync_master->recv_handshake(c,msg); //根据链的状态进行同步管理 } 主要功能在于recv_handshake函数中同步区块管理。 void sync_manager::recv_handshake (connection_ptr c, const handshake_message &msg) { controller& cc = chain_plug->chain(); uint32_t lib_num = cc.last_irreversible_block_num( ); uint32_t peer_lib = msg.last_irreversible_block_num; reset_lib_num(c); c->syncing = false; //-------------------------------- // sync need checks; (lib == last irreversible block) // // 0. my head block id == peer head id means we are all caugnt up block wise // 1. my head block num < peer lib - start sync locally // 2. my lib > peer head num - send an last_irr_catch_up notice if not the first generation // // 3 my head block num <= peer head block num - update sync state and send a catchup request // 4 my head block num > peer block num ssend a notice catchup if this is not the first generation // //----------------------------- uint32_t head = cc.fork_db_head_block_num( ); block_id_type head_id = cc.fork_db_head_block_id(); if (head_id == msg.head_id) { fc_dlog(logger, "sync check state 0"); // notify peer of our pending transactions notice_message note; note.known_blocks.mode = none; note.known_trx.mode = catch_up; note.known_trx.pending = my_impl->local_txns.size(); c->enqueue( note ); return; } if (head < peer_lib) { fc_dlog(logger, "sync check state 1"); // wait for receipt of a notice message before initiating sync if (c->protocol_version < proto_explicit_sync) { start_sync( c, peer_lib); } return; } if (lib_num > msg.head_num ) { fc_dlog(logger, "sync check state 2"); if (msg.generation > 1 || c->protocol_version > proto_base) { notice_message note; note.known_trx.pending = lib_num; note.known_trx.mode = last_irr_catch_up; note.known_blocks.mode = last_irr_catch_up; note.known_blocks.pending = head; c->enqueue( note ); } c->syncing = true; return; } if (head <= msg.head_num ) { fc_dlog(logger, "sync check state 3"); verify_catchup (c, msg.head_num, msg.head_id); return; } else { fc_dlog(logger, "sync check state 4"); if (msg.generation > 1 || c->protocol_version > proto_base) { notice_message note; note.known_trx.mode = none; note.known_blocks.mode = catch_up; note.known_blocks.pending = head; note.known_blocks.ids.push_back(head_id); c->enqueue( note ); } c->syncing = true; return; } elog ("sync check failed to resolve status"); } 这里的对比区块信息，主要分为五种情况： （1）、接收到的最新区块id与自身最新区块id相同。 （2）、自身最新区块数小于接收到的区块的不可逆数。（自己的最新区块高度比远程节点的不可逆区块高度还要低，需要同步。） （3）、自身不可逆区块数大于接收到的最新区块数。（与（2）相反，通知远程节点，需要同步。发送的是notice_message类型的消息，下一篇笔记中再写。） （4）、自身最新区块数小于接收到的最新区块数。（需要同步，发送的是request_message消息，后面再写这种消息类型。） （5）、自身最新区块数大于接收到的最新区块数。（与（4）相反，告通知远程节点，需要同步。发送的是notice_message类型的消息，下一篇笔记中在写。） 以上便是handshake_message消息的发送与接收过程。此篇笔记写到本地节点将自己的配置信息、链的状态告诉远程节点，远程节点接收到这些信息之后，发现自身链比本地节点的链更长，所以给本地节点发送消息同步区块。（消息类型为notice_message，下一篇分析本地节点接收到notice_message类型的消息之后，如何同步区块）。 转载自：https://github.com/RootkitKiller/EosLearn/blob/master/Eos%E4%BB%A3%E7%A0%81%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%9B%9B%EF%BC%89%E5%90%8C%E6%AD%A5%E5%8C%BA%E5%9D%97--p2p%E9%80%9A%E4%BF%A1--handshake_message.md