搭建区块链网络
本文最后更新于:2023年6月19日 晚上
从零开始
【1】https://github.com/omnigeeker/golang_blockchain_demo/tree/master/blockchain
这是第一个,当然,我还找到好几个不错的,都 star 了。
下载之后,需要放置到 GOPATH 的 src 目录下面,然后开始跑。
注意,定义端口的文件叫.env
,需要放在这里,当时报错报的我都懵了。
然后,运行起来是这样的。
原文中说,可以发送一个 post 请求用来创建新的块,我试了一下,postman 不能用插件了,postwoman 无法调试本地地址?然后 curl 命令也是可以的,参考https://www.jianshu.com/p/9bd018253bed,然后另外一个插件
也不错。
完美。
当我们访问http://localhost:8000/,可以看到:
搭建 block-DAG
网上这部分资料很少,只找到一个https://github.com/soteria-dag/soterd/tree/master/blockdag,还不确定可不可以利用,加油。
然后还有一个 python 版的https://github.com/AvivYaish/PHANTOM/tree/master/phantom,里面貌似实现了一个小型的 DAG 区块链,但是感觉写的代码好少,不确定能不能用。
根据 soteria-dag 搭建
本模块除了搭建步骤记录,还包括,数据结构记录,类方法记录,调用关系记录,以及 DAG 前置知识。
为什么用 BLOCK-DAG
BlockDAG 提供了比特币设计的主要功能,包括(1)分散化;您可以在没有中间人的情况下将货币转移给其他人。(2)某种程度的匿名性;货币与你的真实身份没有直接联系。(3)无信任共识;区块链实施的规则允许您对陌生人所做的区块验证工作有一定程度的信任,并使更改区块和交易变得困难。
另外,还提供了:
- 提高了事务吞吐量;
- 允许使用工作证明系统,提高区块开采的可访问性和公平性。
- 加密事务负载
- 可扩展性
- 轻巧
- 量子安全(使用三元或平衡三元计算,而不是经典计算机执行的标准二进制计算
DAG Vs Bitchain
- 单链技术存在的几个问题
效率问题:传统区块链技术基于区块,比特币的效率较低,每十分钟出一个块,6 个出块确认需要一个小时,整个网络同时只能有一条单链,基于 PoW 共识机制出块无法并发执行。
确定性问题:比特币和以太坊存在 51%算力攻击问题,基于 PoW 共识的最大问题就是没有一个确定的不可更改的最终状态;如果某群体控制 51%算力,并发起攻击,比特币体系会崩溃。
中心化问题:基于区块的 PoW 共识中,矿工一方面可以形成集中化的矿场集团,一方面,获得打包交易权的矿工拥有巨大权力,可以选择那些交易进入区块,哪些交易不被处理,这样的风险目前已经是事实存在。
能耗问题:由于传统区块链基于 PoW 算力工作量证明,达成共识机制,比特币的挖矿能耗已经和阿根廷整个国家的耗电量持平。
2.DAG 区块链的优势和价值
a.传统区块链需要矿工完成工作量证明(PoW)来执行每一笔交易,而 DAG 区块链能摆脱这样的限制。
b.在 DAG 系统中,交易指令能极快的扩散通知至全网,大部分双重支付的攻击尝试将会被系统捕捉到并立即拒绝执行。
二者区块结构示意图对比:
其中parent sub-header
包括:
- Version - A value that could be used to trigger different block evaluation rules
- Parents count - The number of parents this block has
- Parents - A list of parents
BLOCK-DAG 整体结构
区块链系统架构分别由数据层、网络层、共识层、激励层、合约层和应用层组成。
此处需要一个 DAG 区块链的架构图,类似于传统区块链的架构图–共识层,激励层什么的
- 创世区块:高度为 0 的 genesis 块(它没有父块,同一高度/代上没有其他块)
- 一个或多个区块连接到 genesis 区块(高度 1)
- 连接到这些块的一个或多个块
拓扑结构如下:
由 Yonatan Sompolinsky 提出,新产生的区块指向所有已知的分叉末端区块,即一个区块有多个父亲,此时区块链就从一条链变为多条分叉链共同组成的的结构。
这就形成了一个远离创世纪区块的区块结构,每个世代/高度都有一组区块远离创世纪区块。区块之间的连接不像区块链那样严格;您可以有多个父级,并且父级的高度不同(在一个上限内)。
没有任何块与之连接的块集称为 DAG 的“tips”。当挖掘新块时,这些块用作父块,当节点彼此同步时,这些块也被引用。
1 |
|
并且使用“链视图”来存储链状态,
1 |
|
类方法
BlockDAG
Methods:
calcNextBlockVersion(prevNodes []*blockNode) (int32, error) |
---|
calcNextParentVersion(prevNodes []*blockNode) (int32, error) |
CalcNextBlockVersion() (int32, error) |
CalcNextParentVersion() (int32, error) |
warnUnknownRuleActivations(nodes []*blockNode) error |
warnUnknownVersions(nodes []*blockNode) error |
calcEasiestDifficulty(bits uint32, duration time.Duration) uint32 |
findPrevTestNetDifficulty(startNode *blockNode) uint32 |
calcNextRequiredDifficulty(lastNode *blockNode, newBlockTime time.Time) (uint32, error) |
CalcNextRequiredDifficulty(timestamp time.Time) (uint32, error) |
TargetDifficulty(blockHeight int32) (*big.Int, error) |
FetchSpendJournal(targetBlock *soterutil.Block) ([]SpentTxOut, error) |
createChainState() error |
initChainState() error |
BlocksByHeight(blockHeight int32) ([]*soterutil.Block, error) |
BlockByHash(hash *chainhash.Hash) (*soterutil.Block, error) |
FetchUtxoView(tx *soterutil.Tx) (*UtxoViewpoint, error) |
FetchUtxoEntry(outpoint wire.OutPoint) (*UtxoEntry, error) |
visitOrphan(hash _chainhash.Hash, order _[]chainhash.Hash, tempMark *map[chainhash.Hash]int, done *map[chainhash.Hash]int) error |
HaveBlock(hash *chainhash.Hash) (bool, error) |
IsKnownOrphan(hash *chainhash.Hash) bool |
GetOrphanBlocks() []*soterutil.Block |
GetOrphanChildren(parent *chainhash.Hash) []*soterutil.Block |
GetOrphanLocator(hashes []chainhash.Hash) BlockLocator |
GetOrphanOrder() ([]chainhash.Hash, error) |
GetOrphanRoot(hash *chainhash.Hash) []chainhash.Hash |
GetOrphans() []*orphanBlock |
removeOrphanBlock(orphan *orphanBlock) |
addOrphanBlock(block *soterutil.Block) |
CalcSequenceLock(tx *soterutil.Tx, utxoView UtxoViewpoint, mempool bool) (\SequenceLock, error) |
calcSequenceLock(nodes []*blockNode, tx *soterutil.Tx, utxoView *UtxoViewpoint, mempool bool) (*SequenceLock, error) |
getAllInputTxos(block *soterutil.Block, view *UtxoViewpoint) ([]SpentTxOut, error) |
connectBlock(node *blockNode, block soterutil.Block, view \UtxoViewpoint, stxos []SpentTxOut) error |
connectBestChain(node *blockNode, block *soterutil.Block, flags BehaviorFlags) (bool, error) |
isCurrent() bool |
IsCurrent() bool |
BestSnapshot() *BestState |
DAGSnapshot() *DAGState |
DAGColoring() []*chainhash.Hash |
DAGOrdering() []*chainhash.Hash |
HeaderByHash(hash *chainhash.Hash) (wire.BlockHeader, error) |
MainChainHasBlock(hash *chainhash.Hash) bool |
BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator |
BlockLocatorFromHeight(height int32) BlockLocator |
LatestBlockLocator() (BlockLocator, error) |
BlockHeightByHash(hash *chainhash.Hash) (int32, error) |
BlockHashesByHeight(blockHeight int32) ([]chainhash.Hash, error) |
HeightRange(startHeight int32, endHeight int32) ([]chainhash.Hash, error) |
HeightToHashRange(startHeight int32, endHash *chainhash.Hash, maxResults int) ([]chainhash.Hash, error) |
IntervalBlockHashes(endHash *chainhash.Hash, interval int) ([]chainhash.Hash, error) |
locateInventory(locator BlockLocator, hashStop *chainhash.Hash, maxEntries uint32) []*blockNode |
locateBlocks(locator BlockLocator, hashStop *chainhash.Hash, maxHashes uint32) []chainhash.Hash |
LocateBlocks(locator BlockLocator, hashStop *chainhash.Hash, maxHashes uint32) []chainhash.Hash |
locateHeaders(locator BlockLocator, hashStop *chainhash.Hash, maxHeaders uint32) []wire.BlockHeader |
LocateHeaders(locator BlockLocator, hashStop *chainhash.Hash) []wire.BlockHeader |
GetBlockDiff(subtips []*chainhash.Hash) []*chainhash.Hash |
Subscribe(callback NotificationCallback) |
sendNotification(typ NotificationType, data interface{}) |
checkBlockHeaderContext(header *wire.BlockHeader, prevNodes []*blockNode, flags BehaviorFlags) error |
checkBlockContext(block *soterutil.Block, prevNodes []*blockNode, flags BehaviorFlags) error |
checkBIP0030(node *blockNode, block soterutil.Block, view \UtxoViewpoint) error |
checkConnectBlock(node *blockNode, block *soterutil.Block, view _UtxoViewpoint, stxos _[]SpentTxOut) error |
CheckConnectBlockTemplate(block *soterutil.Block) error |
RenderDot() ([]byte, error) |
RenderSvg() ([]byte, error) |
TstSetCoinbaseMaturity(maturity uint16) |
thresholdStates(nodes []*blockNode, checker thresholdConditionChecker, cache *thresholdStateCache) ([]ThresholdState, error) |
ThresholdStates(deploymentID uint32) ([]ThresholdState, error) |
IsDeploymentActive(deploymentID uint32) (bool, error) |
deploymentStates(prevNodes []*blockNode, deploymentID uint32) ([]ThresholdState, error) |
initThresholdCaches() error |
blockExists(hash *chainhash.Hash) (bool, error) |
processOrphans(flags BehaviorFlags) (bool, error) |
ProcessBlock(block *soterutil.Block, flags BehaviorFlags) (bool, bool, error) |
maybeAcceptBlock(block *soterutil.Block, flags BehaviorFlags) (bool, error) |
dagview
genesis() *blockNode |
---|
Genesis() *blockNode |
tips() []*blockNode |
Tips() []*blockNode |
addTip(node *blockNode) |
AddTip(node *blockNode) |
checkAndAddNode(height int32, node *blockNode) bool |
removeTip(node *blockNode) |
RemoveTip(node *blockNode) |
containsAtHeight(height int32, node *blockNode) bool |
height() int32 |
Height() int32 |
nodesByHeight(height int32) []*blockNode |
NodesByHeight(height int32) []*blockNode |
virtualHash() *chainhash.Hash |
count() int |
Equals(other *dagView) bool |
contains(node *blockNode) bool |
Contains(node *blockNode) bool |
next(node *blockNode) *blockNode |
Next(node *blockNode) *blockNode |
blockLocator(node *blockNode) BlockLocator |
BlockLocator(node *blockNode) BlockLocator |
SPECTRE 共识协议
核心思想:丢弃主链概念,所有产生的区块共同构成账本,不丢弃任何一个区块,同时维护一个无冲突交易集合。
只要是产生的区块就不会被丢弃,所有的区块都是有效的,所有区块共同组成账本,这样进一步提高了区块链的处理交易能力,该设计的关键在于设计算法来保证区块链不会被恶意攻击成功。
如何产生区块
SPECTRE 协议中,当产生区块时,要从之前所有分叉的末端区块(入度为零,又称 tip)中挑选大于等于 2 个父块 hash 作为引用。
1 |
|
当有新区块产生时,节点要立刻将新区块(包含基于哪些区块产生这一信息)发送给与自己相连接的节点。
1 |
|
该协议下,挖矿节点只负责迅速挖区块(能够达到 1 秒一个区块),而对分叉中可能包含的冲突交易在挖矿阶段并不做任何处理,将记录交易速度最大化,让 DAG 这种区块链有着恐怖的处理交易能力。
冲突解决
SPECTRE 的思路是设计一个计算投票的算法,让诚实区块会投票给诚实的区块,后边的诚实区块会给前边的堆叠算力,从而让恶意攻击失败,其安全算力也是51%**。
**算法是:
对指定的块 z,考虑其对其他的块 x,y 的投票过程。
(1) 如果 z 的先行块是 x 不是 y,那么它将投票给 x。
(2) 如果 z 的先行块既是 x 也是 y,投票将取决于与 z 的可达块相同的虚拟块进行递归投票的结果。
(3) 如果 z 的先行块既不是 x 也不是 y,投票将取决于以 z 为先行块的大多数块的投票。
(4) 如果 z 是一个可以到达整个 DAG 的虚拟块,它将遵从 DAG 的大多数块来决定投票结果。
(5) 如果 z=x 或者 z=y,只要 x 与 y 之间互相不可达,它将投票给自己。
拿双花举例,下图中,X 和 Y 区块中包含着两条冲突交易会导致双花,此时 DAG 中的区块会对 X 和 Y 进行投票,决定哪一个交易有效。
投票规则如下,投 X 的标蓝,投 Y 的标红,X<Y 代表 X 先于 Y:
- X 后边的,只能到 X 的投 X,图中 6、7、8 投 X,同理 9、10、11 投 Y。
- X 后边的,能到 X 也能到 Y,根据上一次(图中虚线圈内)的投票结果投,12 投 X。
- 不在 X、Y 后边的,根据自己后边所有的区块投哪个票多的投,1-5 区块投 X
- X、Y 投自己
根据投票结果,X 中的那条交易信息有效,Y 中对应的那条交易信息无效。
可信交易集合
SPECTRE 可信交易集就相当于超过当前 6 个区块的比特币链里组成的交易集合。
区块链从数字加密货币的角度来说,就是一个账本,从账本上的交易信息中得出每个账户所拥有的货币,所以,得出确定的、不可能更改的交易信息就至关重要,SPECTRE 可信交易集产生过程如下:
- 遍历区块,依次提取交易信息,无冲突交易加入无冲突交易集。
- 将导致账户余额不足的冲突交易加入冲突交易集。对应时空数据即相同时间的用户
- 根据上边的投票算法,依次对冲突交易进行投票,产生冲突区块顺序集,决定哪一条交易有效。
- 将投票有效的交易加入无冲突交易集。
- 将无冲突交易集中超过一定时间的交易组建可信交易集,即该交易池交易信息基本不可能被篡改。
SPECTRE 并不会对所有区块进行排序,所有区块没有一个完整的线形顺序,有的只是决定冲突信息先后的区块顺序对。
比特币中的高度代表的就是线形顺序,高度低的区块中交易信息先于高度高的区块里的信息,高度高的区块就不能包含和高度低的区块冲突的交易,而SPECTRE 有大量的分叉,区块高度不能代表线形顺序,前边的区块交易信息不一定先于后边的分叉区块交易信息,交易信息的有效性要由投票算法来决定。
区块投票算法很快,再加上它将所有分叉区块都包含进来,也就没有了比特币所面临的分叉风险(等待 6 个区块),交易确认时间可以达到 10 秒。(我认为,一次投票的时间肯定低于 10s(即使它遍历了所有块),因此,只要投票成功,交易就被确认,因此效率非常高!)
DAG 区块链通信机制
P2P 对等网络
见书 19 页
似乎应该放到前置知识?
其他图详见:https://github.com/Blockchain-CN/blockchain
网络结构及特性
从底层网络的角度来看,本作品的核心是基于 Gossip 协议的交易传播。这种机制意味着只要数据具有足够的权重,就能快速传播到整个网络中去。作品中的时空数据发布分五步完成:
- 金字塔编码:通过多级金字塔编码,将时空数据划分进入多个层次,提高时空数据的细粒度;
- 加入 Bloom 过滤器:将编码后的数据放入 Bloom 过滤器,并使用同态加密将 Bloom 数组进行加密处理;
- 签名:采用自身私钥对加密结果进行签名;
- Tip 选择:采用马尔科夫链蒙特卡洛(MCMC)算法随机挑选 50 个未确认的交易并将它们打包进入区块;
- 工作量证明(Proof of Work):只是用于限制出块速率,防止网络堵塞。
见 p28
注意:比特币中,如果我想转账一笔 BTC,我会发出一个交易,并广播给周围的节点,周围节点验证后,继续广播,最后被矿工收到,打包进入区块,并把区块广播出来,同样的传遍整个网络,交易生效。
而且通过比特币钱包,可以做到每次交易都更换地址,即生成不同的公钥私钥对,然后都指向同一个账户,这样可以避免被追踪。
掩码认证消息
由于本作品的数据发布是通过没有手续费的交易,因此可以在分布式账本中自由的发送消息,该消息可以占据交易中的字段,而普通交易中该字段是被发送者的签名所占据。默认情况下,这些消息对任何人都是可见的。而本项目采用掩码认证消息(MAM)加密消息,提供基于哈希的签名认证和完整性验证。
基本概念
MAM 是一个附加的数据传输协议,在 DAG 上增加了发送和接受加密数据流的功能,且对设备的大小和成本没有限制。鉴于这些特性,MAM 满足了行业重要的需求,及完整性和私密性。用户可以在任意时间发布
见 p29
写本子能用到的图
见:https://ledger.jd.com/architecturedesign.html
存储层
【1】trillian/google
https://www.jianshu.com/p/61e432d121f9
https://blog.csdn.net/mutourend/article/details/113365448
https://my.oschina.net/u/3843525/blog/3175914
项目地址:https://github.com/google/trillian
应用示例:https://github.com/google/trillian-examples
【2】https://github.com/vldmkr/merkle-patricia-trie 这个接口非常干净简洁,竟然没人点 star
这里有两篇解释性文档。提到了这些问题(未给出解决):
区块链中,交易被如何打包进区块
- 在打包开始之前,这些交易记录是以什么方式存在于网络?
- 打包是否会把所有交易记录打包进区块?怎么可能保证所有交易都不被遗漏?
- 如何防止矿工伪造交易?将伪造的交易打包进区块?
- 怎么从区块链里面查一个交易?
- 怎么获取 merkle 验证路径?
- 怎么确保网络上这个返回的验证路径不是伪造的?
激励层
为了能够把挖矿过程讲清楚,我们只拿比特币遵循的 PoW 来进行演示。
SHA256(SHA256(version + prevHash + merkleRoot + time + currentDifficulty + nonce )) < TARGET
矿机执行上面公式,只要满足上面这个公式(执行结果为真),就算挖到矿。现在对这个公式进行解释。
- 矿机会做一个 double sha256 的运算,运算的参数其实全部是块头里面的信息,但是因为这个时候区块还没有生成,所以这些信息是暂时保存的,如果抢到记账权,就把这些信息记录进去,因此,当你觉得一个区块的 hash 是否是作弊算出来的时候,用区块头里面的信息自己去算一遍,看看是不是能得到相同的 hash 就可以了,从一点上讲,挖矿也是不能作弊的,必须老老实实不停算
- version 是当前运行矿机的客户端软件版本,每次版本升级,可能对一些参数会有影响,比如区块大小从 1M 扩容到 2M,但是对于挖矿算法而言是不变的
- prevHash 是前一个区块的 hash 值
- merkleRoot 是当前矿机内存里暂存的交易的 merkle 算法得到的根 hash,merkle 会在下文讲
- time 是当前时间戳
- currentDifficulty 是当前难度,这个当前难度是由一个公式算出来的,这个公式是
currentDifficulty = diff_1_target/TARGET
这个公式里面的 diff_1_target 可以认为是一个常量,在比特币客户端里面是不变的,值为 0x1d00ffff。当然,其实它也有可能变,但怎么变都差不多这个值,我们还是把它当作常量。而 TARGET 我们在下面会讲到。 - nonce 是一个正整数,nonce 的值就算矿机要找的值。当矿机开始执行 double sha256 算法时,nonce 为 0,如果执行完一次,无法满足上面那个公式,那么 nonce 就自加 1,再执行一遍算法,如果不满足公式,继续加 1 再执行,就是这样一直加上去,直到找到一个 nonce 满足上面的公式,就算挖到矿。所以,这个 nonce 每一次挖矿都可能不一样,它是完全随机出现的,到底是多少完全看运气。但不管怎样,你看每个区块里面但 nonce 值,就知道矿机做了多少次运算,也就知道挖到矿有多难了。
- TARGET 是用以对比的目标值,它是一个特定值,比特币的发明者希望 10 分钟产生一个区块,所以最初设计的 TARGET 就是为了让 currentDifficulty 能够到一个合适的值,保证 10 分钟一个块。但是实际情况并不可能保证 10 分钟一定出一个块,如果算力下降,时间就会加长,这时就应该调整一下难度,使出块时间尽可能恢复在 10 分钟左右。所以 2016 个区块(2 周)TARGET 就会调整一次,而如果真实的情况是产生 2016 个区块的时间超过 2 周,那么 TARGET 就会适当增加,从而使 currentDifficulty 减小,下面 2016 个区块的难度就会降低一些。相反,则提高难度。这个调整算法本文就不展开了。所以 TARGET 是一个 2016 次不变,但总体而言一直在变的值,它的目标就是让产生一个区块的时间大概在 10 分钟左右。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!