DAG区块链和传统区块链搭建异同

本文最后更新于:2023年6月19日 晚上

区块数据类型区别

dag 的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// blockNode represents a block within the DAG
type blockNode struct {
// NOTE: Additions, deletions, or modifications to the order of the
// definitions in this struct should not be changed without considering
// how it affects alignment on 64-bit platforms. The current order is
// specifically crafted to result in minimal padding. There will be
// hundreds of thousands of these in memory, so a few extra bytes of
// padding adds up.

// parents are the parent block for this node.
parents []*blockNode

// parent metadata like version and extra data
parentMetadata []*parentInfo

parentVersion int32

// hash is the double sha 256 of the block.
hash chainhash.Hash

// workSum is the total amount of work in the chain up to and including
// this node.
workSum *big.Int

// height is parentsMaxHeight + 1
height int32

// Some fields from block headers to
// reconstruct headers from memory. These must be treated as
// immutable and are intentionally ordered to avoid padding on 64-bit
// platforms.
version int32
bits uint32
nonce uint32
timestamp int64
merkleRoot chainhash.Hash

}

type parentInfo struct {
hash chainhash.Hash
data [32]byte
}

block 的:

1
2
3
4
5
6
7
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
}
  • Index 是这个块在整个链中的位置
  • Timestamp 显而易见就是块生成时的时间戳
  • Hash 是这个块通过 SHA256 算法生成的散列值
  • PrevHash 代表前一个块的 SHA256 散列值
  • BPM 每分钟心跳数,也就是心率。–不太清楚干什么用的

接着,我们再定义一个结构表示整个链,最简单的表示形式就是一个 Block 的数组:
var ``Blockchain`` []``Block
可以看出来,在 go 里面,是先写变量名,再写类型(比如*,[]),然后是最终类型。

通信系统

比特币网络

首先,比特币网络中的节点主要有四大功能:钱包、挖矿、区块链数据库、网络路由。每个节点都会具备路由功能,但其他功能不一定都具备,不同类型的节点可能只包含部分功能,一般只有比特币核心(bitcoin core)**节点才会包含所有四大功能。
所有节点都会参与校验和广播交易及区块信息,且会发现和维持与其他节点的连接。有些节点会包含完整的区块链数据库,包括所有交易数据,这种节点也称为
全节点(Full Node)。另外一些节点只存储了区块链数据库的一部分,一般只存储区块头而不存储交易数据,它们会通过“简化交易验证(SPV)”的方式完成交易校验,这样的节点也称为  **SPV 节点轻节点(Lightweight Node)**。钱包一般是 PC 或手机客户端的功能,用户通过钱包查看自己的账户金额、管理钱包地址和私钥、发起交易等。除了比特币核心钱包是全节点之外,大部分钱包都是轻节点。挖矿节点则通过解决工作量证明(PoW)算法问题,与其他挖矿节点相互竞争创建新区块。有些挖矿节点同时也是全节点,即也存储了完整的区块链数据库,这种节点一般都是独立矿工(Solo Miner)**。
我们知道,矿工创建新区块后,是需要广播给全网所有节点的,当全网都接受了该区块,给矿工的挖矿奖励才算是有效的,这之后才好开始下一个区块 Hash 的计算。所以矿工必须最大限度缩短新区块的广播和下一个区块 Hash 计算之间的时间。

初试牛刀

我猜你一定对传统的 web 服务及开发非常熟悉,所以这部分你肯定一看就会。 借助 Gorilla/mux 包,我们先写一个函数来初始化我们的 web 服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func run() error {
mux := makeMuxRouter()
httpAddr := os.Getenv("ADDR")
log.Println("Listening on ", os.Getenv("ADDR"))
s := &http.Server{
Addr: ":" + httpAddr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
if err := s.ListenAndServe(); err != nil {
return err
}
return nil
}

其中的端口号是通过前面提到的 .env 来获得,再添加一些基本的配置参数,这个 web 服务就已经可以 listen and serve 了! 接下来我们再来定义不同 endpoint 以及对应的 handler。例如,对“/”的 GET 请求我们可以查看整个链,“/”的 POST 请求可以创建块。

1
2
3
4
5
6
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
return muxRouter
}

GET 请求的 handler:

1
2
3
4
5
6
7
8
func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
bytes, err := json.MarshalIndent(Blockchain, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.WriteString(w, string(bytes))
}

为了简化,我们直接以 JSON 格式返回整个链,你可以在浏览器中访问 localhost:8080 或者 127.0.0.1:8080 来查看(这里的 8080 就是你在 .env 中定义的端口号 ADDR)。
POST 请求的 handler 稍微有些复杂,我们先来定义一下 POST 请求的 payload:

1
2
3
type Message struct {
BPM int
}

再看看 handler 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
var m Message
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&m); err != nil {
respondWithJSON(w, r, http.StatusBadRequest, r.Body)
return
}
defer r.Body.Close()
newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
if err != nil {
respondWithJSON(w, r, http.StatusInternalServerError, m)
return
}
if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
newBlockchain := append(Blockchain, newBlock)
replaceChain(newBlockchain)
spew.Dump(Blockchain)
}
respondWithJSON(w, r, http.StatusCreated, newBlock)
}

我们的 POST 请求体中可以使用上面定义的 payload,比如:
{``"BPM"``:``75``}
还记得前面我们写的 generateBlock 这个函数吗?它接受一个“前一个块”参数,和一个 BPM 值。POST handler 接受请求后就能获得请求体中的 BPM 值,接着借助生成块的函数以及校验块的函数就能生成一个新的块了!
除此之外,你也可以:

  • 使用 spew.Dump 这个函数可以以非常美观和方便阅读的方式将 struct、slice 等数据打印在控制台里,方便我们调试。
  • 测试 POST 请求时,可以使用 POSTMAN 这个 chrome 插件,相比 curl 它更直观和方便。

POST 请求处理完之后,无论创建块成功与否,我们需要返回客户端一个响应:

1
2
3
4
5
6
7
8
9
10
func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
response, err := json.MarshalIndent(payload, "", " ")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("HTTP 500: Internal Server Error"))
return
}
w.WriteHeader(code)
w.Write(response)
}

快要大功告成了

接下来,我们把这些关于区块链的函数,web 服务的函数“组装”起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
go func() {
t := time.Now()
genesisBlock := Block{0, t.String(), 0, "", ""}
spew.Dump(genesisBlock)
Blockchain = append(Blockchain, genesisBlock)
}()
log.Fatal(run())
}

这里的 genesisBlock (创世块)是 main 函数中最重要的部分,通过它来初始化区块链,毕竟第一个块的 PrevHash 是空的。

遇到问题

https://github.com/omnigeeker/golang_blockchain_demo/tree/master/networking
在参考这个步骤的时候,发现始终无法监听到 8000 端口的信息,晚上苦苦思索一个小时没有任何解决思路,今天早上又看了一下,发现我没有改文件????我还是用第一个 demo 文件来模拟多节点,笑死。
赶紧创建文件,复制粘贴。
bingo!
image.png
这里设置的每 30 秒广播一次区块信息,所以如果一直监听可能会遇到这样的情况:
image.png
注意:这里的 nc 命令和文章中提到的不一样,文章中简单的nc localhost 8000当然可以用,但是这样更规范,它相当于模拟了一个客户端,开放 5000 端口和主机地址的 8000 端口进行通信。
用它,甚至可以模拟一个简易的聊天室!
nc 的本质是在两台机器之间建立连接,之后就可以基于这个连接做很多事情,数据传输是其中一个最为基本的。我们下面就使用 nc 来建立一个 C/S 的聊天室。
模拟 Server 端:

1
2
3
# -v :输出详细信息
# -l :指定监听地址和端口
nc -v -l 127.0.0.1 6000

模拟 Client 端:

1
2
# -p : 指定源端口
nc -v -p 5000 localhost 6000

之后,Client 和 Server 端就可以相互聊天了。
Client:

1
2
3
4
5
# nc -v -p 5000 localhost 6000
nc: connect to localhost port 6000 (tcp) failed: Connection refused
Connection to localhost 6000 port [tcp/x11] succeeded!
Hi, server
Hi, client

Server:

1
2
3
4
5
# nc -v -l 127.0.0.1 6000
Listening on [127.0.0.1] (family 0, port 6000)
Connection from [127.0.0.1] port 6000 [tcp/x11] accepted (family 2, sport 5000)
Hi, server
Hi, client

nc 具体操作可以参考:
【1】https://www.cnblogs.com/bakari/p/10898604.html
【2】https://www.huaweicloud.com/articles/7f323edef71be76eb5705275718ecfef.html
【3】https://www.cnblogs.com/zhaijiahui/p/9028402.html
【4】https://phpor.net/blog/post/225

p2p 多节点网络

https://github.com/corgi-kx/blockchain_golang/blob/master/network/server.go
我认为需要先在单节点完成 dag 架构的 pow 之后再尝试多节点通讯,所以这里暂时搁置。

DAG

SPECTRE 共识

账户系统与交易签名

前置知识:
【1】签名与校验
【2】比特币交易中的签名
【3】比特币交易中的签名与验证

国密 SM2

流程:https://blog.csdn.net/samsho2/article/details/80770862
详细原理:
【1】SM2 算法与 KDF 密钥导出函数
【2】SM2 的签名和验证过程
以上均要写在本子里
“github.com/paul-lee-attorney/gm/sm2” 这个库将所有代码都注释了,非常好,可以直接用
https://pkg.go.dev/github.com/paul-lee-attorney/gm/sm2 这是它的用法
image.png
这个函数可以用在本子里,提升逼格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main

import (
"crypto/rand"
"encoding/hex"
"fmt"
"log"
_ "time"
//"github.com/rongzer/gm/sm2"
"github.com/paul-lee-attorney/gm/sm2"
)

func main() {

//fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
//fmt.Println(time.Now().Unix())
//bf := bbloom.New(float64(1<<12), float64(0.01))
//bf.Add([]byte("butter"))
////Json := bf.JSONMarshal()
//var set = bf.ShowBitset()
////var value = 0
////for _,v := range set{
//// value += int(v)
////}
// fmt.Println(len(set))
//priv, err := sm2.GenerateKey(rand.Reader) // 生成密钥对
//if err != nil {
// log.Fatal(err)
//}
//msg := []byte("Tongji Fintech Research Institute")
//pub := &priv.PublicKey
//sign,err := priv.Sign(rand.Reader, msg, nil) //sm2签名
//if err != nil {
// log.Fatal(err)
//}
//fmt.Println(*pub)
//isok := pub.Verify(msg, sign) //sm2验签
//fmt.Printf("Verified: %v\n", isok)
msg := []byte("test message 123012301230")
// 创建公私钥
priKey, _ := sm2.GenerateKey(rand.Reader)
//
// 签名
sign, err := sm2.Sign(priKey,nil, msg)
if err != nil {
panic(err)
}
fmt.Printf("sign:%s\n", hex.EncodeToString(sign))
fmt.Println("prikey:\n",priKey.GetRawBytes())

fmt.Printf("prikey:%s\n",hex.EncodeToString(priKey.GetRawBytes()))
fmt.Printf("pubkey:%s\n",hex.EncodeToString(priKey.PublicKey.GetRawBytes()))

src := hex.EncodeToString(priKey.GetRawBytes())
n, err := hex.DecodeString(src)
if err != nil {
log.Fatal(err)
}

fmt.Println(n)
// 验签
var res bool
res,err = sm2.Verify(&priKey.PublicKey,nil,msg, sign)
//if err != nil{
// panic(err)
//}
fmt.Println(res)

return
}

image.png

维护可信交易用到的技术栈

hashmap–用于快速查找
具体原理:https://zhuanlan.zhihu.com/p/27108356
测试性能:https://github.com/phf/go-hashmap 那个测试的样式可以嫖一下

测压 tps

【1】https://hyperledger.github.io/caliper/v0.3.2/architecture-ch/
【2】https://blog.csdn.net/qq_44316726/article/details/108112300?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242
【3】https://zhuanlan.zhihu.com/p/133873895


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!