307-搭建简单P2P网络






搭建简单P2P网络





背景
什么是点对点P2P?
在真实的P2P结构中,你不需要一个中心化的服务来维持区块链的状态.
举个例子,当你转账了一些比特币给你朋友,比特币区块链的状态需要被更新.
所以你朋友的余额会增加,你的余额会减少.

这里没有一个像银行一样的中心权威机构去维持区块链的状态.
取而代之的是,在比特币网络中的所有想要维持比特币备份的节点,
都会去更新他们的区块链备份,包含你的交易.
这样一来,只要网络中51%以上的节点同意区块链的状态,它就保持了它的准确性.




开始编写代码
编写P2P网络代码并不简单,有非常多的极端例子,
并且需要非常多的工程来让它保持扩展性和可靠性.
像任何一个好的工程一样,我们来先看一下需要哪些工具.

幸运的是,有一个出色的P2Pgo语言库叫做go-libp2p.
同时,它也是发明IPFS的那群人开发的.




警告
据我们所知,go-libp2p包有2个缺点.
1.安装非常痛苦.它使用了gx作为包管理工具,gx并不是很方便.
2.它依然在开发中,当我们使用的时候,可能会遇到一些数据冲突.它们有办法消除.

不要担心第一个缺点,我们会帮助你解决它.
第二个问题比较麻烦,但是并不影响我们的代码.
但是,如果你遇到了数据冲突,它们很有可能来自这个包里的底层代码.

现在可用的P2P包比较少,特别是go语言的包.
总的来说,go-libp2p还是不错的,也适合我们的目标.





安装
最好的办法是克隆整个包然后直接在里面编写代码.
你可以在环境外开发但是你需要知道如何使用gx.




go get -d github.com/libp2p/go-libp2p/...
打开目录
make 
make deps



这能帮助你获取到所有的包和依赖.
我们准备在examples子目录中进行开发.
所以我们可以在examples目录下建一个p2p文件夹
mkdir ./examples/p2p

然后我们打开p2p文件夹,创建一个main.go文件
我们所有的代码都在这个文件中编写.





// Block represents each 'item' in the blockchain
type Block struct {
	Index     int
	Timestamp string
	BPM       int
	Hash      string
	PrevHash  string
}

// Blockchain is a series of validated Blocks
var Blockchain []Block

var mutex = &sync.Mutex{}




Block是我们需要的交易信息.
我们使用BPM作为每个block的数据点.

Blockchain是我们的区块链状态,是一个Block切片.

我们声明了mutex用来控制和防止冲突的情况.











// make sure block is valid by checking index, and comparing the hash of the previous block
func isBlockValid(newBlock, oldBlock Block) bool {
	if oldBlock.Index+1 != newBlock.Index {
		return false
	}

	if oldBlock.Hash != newBlock.PrevHash {
		return false
	}

	if calculateHash(newBlock) != newBlock.Hash {
		return false
	}

	return true
}

// SHA256 hashing
func calculateHash(block Block) string {
	record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash
	h := sha256.New()
	h.Write([]byte(record))
	hashed := h.Sum(nil)
	return hex.EncodeToString(hashed)
}

// create a new block using previous block's hash
func generateBlock(oldBlock Block, BPM int) Block {

	var newBlock Block

	t := time.Now()

	newBlock.Index = oldBlock.Index + 1
	newBlock.Timestamp = t.String()
	newBlock.BPM = BPM
	newBlock.PrevHash = oldBlock.Hash
	newBlock.Hash = calculateHash(newBlock)

	return newBlock
}




isBlockValid是来检查区块链中的区块的hash是否一致.

calculateHash是用sha256来对数据进行hash运算.

generateBlock是用来创建区块来添加到区块链中.













P2P
Host-主机

首先我们需要编写代码来创建主机.
当一个节点运行我们的go程序,
它应该成为一个主机来让其他节点进行连接.




// makeBasicHost creates a LibP2P host with a random peer ID listening on the
// given multiaddress. It will use secio if secio is true.
func makeBasicHost(listenPort int, secio bool, randseed int64) (host.Host, error) {

	// If the seed is zero, use real cryptographic randomness. Otherwise, use a
	// deterministic randomness source to make generated keys stay the same
	// across multiple runs
	var r io.Reader
	if randseed == 0 {
		r = rand.Reader
	} else {
		r = mrand.New(mrand.NewSource(randseed))
	}

	// Generate a key pair for this host. We will use it
	// to obtain a valid host ID.
	priv, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
	if err != nil {
		return nil, err
	}

	opts := []libp2p.Option{
		libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", listenPort)),
		libp2p.Identity(priv),
	}

	if !secio {
		opts = append(opts, libp2p.NoEncryption())
	}

	basicHost, err := libp2p.New(context.Background(), opts...)
	if err != nil {
		return nil, err
	}

	// Build host multiaddress
	hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", basicHost.ID().Pretty()))

	// Now we can build a full multiaddress to reach this host
	// by encapsulating both addresses:
	addr := basicHost.Addrs()[0]
	fullAddr := addr.Encapsulate(hostAddr)
	log.Printf("I am %s\n", fullAddr)
	if secio {
		log.Printf("Now run \"go run main.go -l %d -d %s -secio\" on a different terminal\n", listenPort+1, fullAddr)
	} else {
		log.Printf("Now run \"go run main.go -l %d -d %s\" on a different terminal\n", listenPort+1, fullAddr)
	}

	return basicHost, nil
}




makeBasicHost方法需要3个参数,并返回host和error

listenPort是其他节点需要连接的端口

secio是boolean用来打开和关闭安全数据流.

randSeed是一个可选的命令行标志,让我们提供一个种子去创建一个随机地址.


方法的第一个if语句决定了seed是否被提供了以及是否创建了相应的key.
然后我们会创建公钥和私钥来让主机保持安全.
opts部分来构建让其他节点连接的地址.

!secio部分绕开了加密,但是我们会使用secio,所以这行代码现在不适用.

然后我们创建host,完成其他节点可以连接的地址.
log.Printf的部分是告诉新节点如何连接主机









 

发布了1063 篇原创文章 · 获赞 41 · 访问量 20万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览