9.1 交易数据的存储
目标
- 了解账本数据组成结构
- 了解区块本身及事务数据的组成结构
- 理解账本数据的存储过程
任务实现
9.1.1 区块链账本数据
分类账本中保存着所有交易变化的记录,具有有序和防篡改的特点。每一次交易链码需要将数据变化记录在分布式账本中,需要记录的数据称为状态, 以键值对( K-V )的形式进行存储。
Hyperledger Fabric 账本由两个不同但相关部分组成:
- 世界状态(World State)
- 区块链(Blockchain)
世界状态:保存世界状态的实际上是一个NoSQL数据库,以方便对状态的存储及检索;以键值对的方式保存一组分类帐状态的最新值。可以使应用程序无须遍历整个事务日志而快速获取当前账本的最新值。其 value 可以是一个简单的值,也可以由一组键值对组成的复杂数据组成。如下图所示:
从上图中可以看到,对于每一个世界状态都具有一个版本号,起始版本号的值为0。每次对状态进行更改时,状态的版本号都会递增。对状态进行更新时也会检查,确保它与创建事务时的版本匹配。
区块链:是一个记录交易日志的文件系统,它是由哈希值链接的 N 个区块构造而成;每个区块包含一系列的多个有序的交易。区块头中包含了本区块所记录交易的哈希值,以及前一个区块头的哈希值。通过这种方式,分类账本中的所有交易都被有序的并以加密的形式链接一起。换言之,在分布式网络中,如果不破坏哈希链的话,根本无法篡改账本数据。
在上图中,我们可以看到区块 B2 具有 区块数据 D2,其包含其所有事务:T5,T6,T7。最重要的是,区块 B2 的区块头(H2)中其包含 D2 中所有事务的加密散列以及来自前一区块(B1)的等效散列。通过这种链接方式,使得区块之间彼此有着不可分割的联系。
下面我们详细分析区块及交易所包含的详细结构。
区块:每一个区块都由三部分组成
如上图所示,区块 B2 的区块头(H2)由区块编号号(2),当前块数据(D2)的哈希(CH2)和来自上一个区块(块号1)的哈希(PH1)的副本组成。
区块头(Block Header):区块头包含三个字段,在创建区块时写入。
- 区块编号(Block number):从0开始的整数,对于追加到区块链的每个新的区块都会在前一个值的基础之上递增1。
- 当前区块哈希值(Current Block Hash):当前块中包含的所有事务的哈希值。
- 上一个区块哈希(Previous Block Hash):区块链中上一个区块的哈希副本。
区块数据(Block Data)
在创建块时写入,包含按顺序排列的一系列交易。
区块元数据(Block Metadata)
此部分包含写入区块的时间,以及相应的证书,公钥和签名。随后,Block Committer 还为每个交易添加了一个有效/无效的指示符(也称之为位掩码)。
现在我们了解了区块中的结构,那么,区块数据中的交易结构又是什么样的,下面我们进一步来了解交易/事务的详细结构。
交易:区块中的区块数据(Block Data)包含了一系列的交易的详细结构,该交易记录了世界状态的变化。
如上图所示:区块 B1 的区块数据(D1)中的事务(T4)包括事务头(H4),事务签名(S4),事务提案(P4),事务响应(R4)和背书列表(E4)。
事务头(Header)
获取有关事务的一些基本元数据如:链码相关的名称及其版本。
事务签名(Signature)
该部分包含使用客户端应用程序私钥而创建的加密签名。用于检查事务内容是否被篡改。
事务提案(Proposal)
包含要调用的链码的函数名称、调用函数所需的输入参数,链码根据提交的事务提案对分类帐进行更新。
事务响应(Response)
调用链码模拟执行后获取到世界状态的前后值,作为读写集(RW-set)返回给客户端。
背书列表(Endorsements)
交易中只包含一个交易响应,但有多个来自所需组织的背书签名,以满足所需的背书策略。
9.1.2 数据存储
区块链是以文件的形式进行存储的,各区块文件默认以 blockfile_ 为文件前缀,后面以六位数字命名,起始数字默认为 000000,如有新文件则每次递增1。区块链文件默认存储目录: /var/hyperledger/production/ledgersData/chains 中,该目录中包括两个子目录: 保存区块链文件的chains 目录(以通道文件目录区分各 Ledger,各个 Peer 节点对于它所属的每个通道,都会保存一份该通道的账本副本)与使用 levelDB 实现保存索引信息的 index 目录。目录结构如下:
root@a37b2b8a2858:/var/hyperledger/production/ledgersData/chains# ll
chains|----mychannel|----|----blockfile_000000
index|----000001.log|----CURRENT|----LOCK|----LOG|----MANIFEST-000000
Orderer 节点本身保存一份账本,但不包括状态数据库及历史索引数据,这些都是由 Peer 节点进行维护:
- 状态数据库(State Database):存储了交易日志中所有 key 的最新值(World State),默认数据库使用 LevelDB。链码调用基于当前的状态数据执行交易。
- 历史数据库(History Database):以 LevelDB 数据库作为数据存储载体,存储区块中有效交易相关的 key,而不存储 value(数据库不允许 value 为空,所以实际上 value 都为 []byte{})。
idStore:存储 Peer 加入的所有的 ledgerId(或称之为 chainid/channelId)。且保证账本编号在全局中的唯一性。默认存储目录为: /var/hyperledger/production/ledgersData/ledgerProvider
读写集
模拟交易和读写集
在模拟执行交易后,背书节点(Endorser)会生成读写集(Read-Write Set),读集(Read Set)中包含了交易在模拟执行期间读取的唯一 key 与对应已提交的值及其提交 version 的列表,写集(Write Set)中包含一个唯一键列表以及交易写入的新值。如果交易执行的是删除操作,则在写集(Write Set)中为该 key 设置一个删除标记。如果在一个交易中对同一个 key 多次进行更改,则仅保留最后更改的值(即最新值)。另外,如果交易读取指定 key 的值,只会返回已提交的状态值,而不能读取到同一交易中修改但未提交的值。
如上所述,key 的 version 只被包含在读集(Read Set)中;写集(Write Set)只包含 key 列表及其最新值。
version 为指定 key 生成一个非重复标识符,可以有各种方案来实现,如使用单调递增的数字来表示。在当前实现中,使用的是基于区块链高度的方式来表示,就是用交易的 height 作为该交易所修改的 key 的 version,交易的 height 由一个 Version 结构体表示(见下面Version struct),其中 TxNum 表示这个 tx 在区块中的编号。该方案相较于递增序号有很多优点,主要是可以很好地利用到诸如 statedb、模拟交易和交易验证这些模块中。
type TxReadWriteSet struct {DataModel TxReadWriteSet_DataModel NsRwset []*NsReadWriteSet
}type NsReadWriteSet struct {Namespace string Rwset []byte CollectionHashedRwset []*CollectionHashedReadWriteSet
}type RangeQueryInfo struct {StartKey string EndKey string ItrExhausted bool ReadsInfo isRangeQueryInfo_ReadsInfo
}......type KVRead struct {Key string Version *Version
}type KVWrite struct {Key string IsDelete bool Value []byte
}type Version struct {BlockNum uint64 TxNum uint64
}
下面是一个通过模拟假设事务准备的示例读写集的示例。为了方便,我们使用递增数字序号来表示版本号:
<TxReadWriteSet><NsReadWriteSet name="chaincode1"><read-set><read key="K1", version="1" /><read key="K2", version="1" /></read-set><write-set><write key="K1", value="V1" /><write key="K3", value="V2" /><write key="K4", isDelete="true" /></write-set></NsReadWriteSet>
<TxReadWriteSet>
另外,如果事务在模拟期间执行的是范围查询,则范围查询及其结果将添加到读写集中,使用query-info
来表示。
交易验证和更新世界状态
commiter 节点使用读写集的读集部分来进行交易的有效性的检查,写集部分更新受影响的 key 的版本号和值。
在验证阶段,使用读集中的每个 key 的版本号与状态数据库中的世界状态(world state)进行比较,如果匹配,则认为此交易有效。如果读写集还包含一个或多个查询信息(query-info),则执行额外的验证。该验证确保在此批量查询的结果范围内没有 key 被新增、删除或更改。换句话说,如果在进行验证期间重新执行任何的范围查询(事务在模拟过程中执行),应该产生与交易在模拟执行时得到的结果相同。此验证确保交易在提交时如果出现幻读则会被认为无效。注意,幻读保护只实现了Chaincode调用的 GetStateByRange
方法,其他批量查询方法(如:GetQueryResult
)则会有幻读风险,因此应该只在不需要提交给排序的只读事务中使用,除非应用程序能够保证模拟阶段和验证阶段结果集的稳定性。
如果交易通过了有效性检查,则 commiter 节点使用写集来更新世界状态。在更新阶段,对于写集中存在的每个 key,世界状态中对应的的 value 与版本号都会被更新。
模拟和验证示例
为了帮助理解读写集,我们来看一个模拟示例。假设在 worldState 中由元组(k,ver,val)
表示,其中 key 为 k,var是 k 的新最 version, val是 k 的 value。
有五个交易,分别是 T1、T2、T3、T4、T5
,这五个交易的模拟过程是针对相同的 worldSate 快照,下面的代码片段显示了每个交易执行读写的顺序。
World state: (k1,1,v1), (k2,1,v2), (k3,1,v3), (k4,1,v4), (k5,1,v5)
T1 -> Write(k1, v1'), Write(k2, v2')
T2 -> Read(k1), Write(k3, v3')
T3 -> Write(k2, v2'')
T4 -> Write(k2, v2'''), read(k2)
T5 -> Write(k6, v6'), read(k5)
现在假设交易的顺序为 T1~T5:
T1
验证成功,因为它没有 read 操作。之后在 worldState 中的k1
和k2
会被更新成(k1,2,v1'), (k2,2,v2')
T2
验证失败,因为它读取的k1
在之前的交易T1
中被修改了T3
验证成功,因为它没有 read 操作。之后在 worldState 中的k2
会被更新成(k2,3,v2'')
T4
验证失败,因为它读取的k2
在之前的交易T1
中被修改了T5
验证成功,因为它读取的k5
没有在之前的任何交易中修改
FAQ
我听说分类账本中有一个 blockdb,这个 blockdb 是什么?
从字面上来理解,可以解释为区块数据库,也就是存放区块的数据库,但实际是区块是一个文件系统,保存在 /var/hyperledger/production/ledgersData/chains 目录中。
9.1 交易数据的存储相关推荐
- Tablestore + Blink实战:交易数据的实时统计
背景 交易数据的实时统计是电商网站一个核心功能,可以帮助用户实时统计网站的整体销售情况,快速验证"新销售策略"的效果.我们今天介绍一个基于表格存储(Tablestore)实现交易数 ...
- 通过tushare获取贵州茅台和中国平安历史交易数据并使用plotly进行可视化分析
通过tushare获取贵州茅台和中国平安历史交易数据并使用plotly进行可视化分析 贵州茅台:赤水河永流淌 贵州茅台酒股份有限公司总部位于中国贵州省遵义市茅台镇,其主导产品贵州茅台酒历史悠久.源远流 ...
- 亲历惊心48小时抢救35亿交易数据
亲历惊心48小时抢救35亿交易数据 内容导航: 噩梦开始,35亿交易记录不翼而飞 四处支援,各路神仙爱莫能助 紧急预案,又出节外生枝 柳暗花明,35亿交易数据失而复得 后记:噩梦方醒不忘经验教训 IT ...
- MySQL——复杂的多表查询——以超市交易数据为例
复杂的多表查询--以超市交易数据为例 之前的内容基本上都是基于单表进行的查询操作,但是在实际工作中,数据往往分散在多个表中,这个时候我们就需要用到多表查询的知识了. 通常来说,多表查询主要有两类:一类 ...
- 阿里云Lindorm联合智臾科技发布,金融高频交易数据量化分析与处理方案
简介:面向银行.保险.券商和私募的高频数据高性能一站式解决方案. 金融市场L1/L2的报价和交易数据是量化交易研究非常重要的数据,随着数字业务快速演进,具有时序特征的交易数据激增,对底层数据库和量化分 ...
- 天弘基金交易数据清算从8小时缩至1.5小时 解决余额宝算力难题
天弘基金作为国内总规模最大的公募基金,阿里云MaxCompute为我们构建了企业级一站式大数据解决方案.MaxCompute对于海量数据的存储.运维.计算能力强大且安全稳定,MaxCompute服务将 ...
- Ethereum 以太坊 交易数据 构建原理
当我们需要将某些数据写入区块链,这个写入的过程叫交易.从区块链中取数据叫调用Call.交易有别与传统数据库数据的写入,以太坊区块链需要先将写入的数据编码成16进制的字节码,区块链块中存储的基础类型如: ...
- python获取股指_用Python读取csv文件中的沪深300指数历史交易数据
保存路径:D:\python\用Python读取csv文件中的沪深300指数历史交易数据 程序名称:readcsvhs300.py: 数据名称:沪深300指数历史交易数据.csv: 开发环境:Win7 ...
- 【全网详解】从0到1搭建双十一实时交易数据展示平台——Spark+Kafka构建实时分析系统
目录 万事具备之巧借东风 预备知识 环境搭建 Spark安装 Kafka安装 Kafka核心知识介绍 Kafka开启及测试服务 Python依赖库 PyCharm安装 搭建总结 八仙过海之各显神通 数 ...
- 非结构化数据的存储与查询
当今信息化时代充斥着大量的数据.海量数据存储是一个必然的趋势.然而数据如何的存储和查询,尤其是当今非结构化数据的快速增长,对其数据的存储,处理,查询.使得如今的 关系数据库存储带来了巨大的挑战.分布存 ...
最新文章
- servlet必知细节(二)--servlet执行过程
- 图论 ---- CF1495D .BFS Trees(图论最短路生成树+枚举计数+树的层次性)
- 十分钟了解分布式计算:GraphLab
- opengl加载显示3D模型MDL类型文件
- 可以在没有main()的情况下编写C程序吗?
- C/C++学习之路_六: 指针
- 平面设计中的网格系统pdf_深入浅出,带你认识网格系统与版式设计
- 什么是Python脚本?
- elementui树状菜单tree_Vue+Element UI 树形控件整合下拉功能菜单(tree + dropdown +input)...
- Spark面试:Spark on yarn 运行流程
- 我悲惨的人生,该死的UPX壳,谁能救救我
- [经典面试题][百度]数轴上从左到右有n各点a[0], a[1], ……,a[n -1],给定一根长度为L的绳子,求绳子最多能覆盖其中的几个点。...
- Eclipse中启动tomcat: java.lang.OutOfMemoryError: PermGen space的解决方法
- 消息称微软Windows暂停接受华为新订单;2019 Q1 亚太区公有云IaaS、PaaS服务收入排名现已揭晓……...
- 运行不同版本libc
- 菜肴百度百科html,酸汤鱼
- Docker 安装 Elasticsearch 7.12.1
- 解决U-net上采样过程后,结合下采样信息时特征图大小不匹配问题
- Linux常用过滤命令之grep命令的使用
- Win8.1/Win8/Win7桌面图标无法拖动怎么办