13. 简易的网络服务
13.1 NODE_ID设置
按照惯例先把之前的文件复制并重新命名
/Users/qinjianquan/publicChain/Part70-Merkle-NODE_ID
获取节点ID
/Users/qinjianquan/publicChain/Part70-Merkle-NODE_ID/src/CLI.go
func (cli CLI) Run() {
isValidArgs()
//get node id
nodeID := os.Getenv("NODE_ID")
if nodeID == "" {
fmt.Println("NODE_ID env. var is not set")
os.Exit(1)
}
fmt.Printf("NODE_ID:%s\n",nodeID)
--
}
在终端编译并运行
export NODE_ID=3000
qinjianquan@MacBook-Pro-10 Part70-Merkle-NODE_ID % go build main.go
13.2 NODE_ID项目配置
按照惯例先把之前的文件复制并重新命名
/Users/qinjianquan/publicChain/Part71-Net-NODE_ID-Configuration
然后我们需要重新配置数据库和钱包文件
格式化数据库
/Users/qinjianquan/publicChain/Part71-Net-NODE_ID-Configuration/src/Blockchain_Iterator.go
const dbName = "blockChain_%s.db"
然后更改部分函数,另外也需要为涉及的函数增加一个参数nodeID string
func CreatBlockchainWithGenesisBlock(address string, nodeID string) *Blockchain {
//format db name
dbName := fmt.Sprintf(dbName, nodeID)
if DBExists(dbName) {
fmt.Println("Genesis block existed")
os.Exit(1)
}
--
}
func BlockChainObject(nodeID string) *Blockchain {
//format db name
dbName := fmt.Sprintf(dbName, nodeID)
if DBExists(dbName) {
fmt.Println("database didn't exist")
os.Exit(1)
}
--
}
格式化钱包
/Users/qinjianquan/publicChain/Part71-Net-NODE_ID-Configuration/src/Wallets.go
const walletFile = "wallets_%s.dat"
func NewWallets(nodeID string) (*Wallets, error) {
walletFile := fmt.Sprintf(walletFile, nodeID)
//to find the file,if there is no file,create a one
if _, err := os.Stat(walletFile); os.IsNotExist(err) {
//create a struct
wallets := &Wallets{}
//access its property and initialize it
wallets.WalletMap = make(map[string]*Wallet)
return wallets, err
}
--
}
当然这也需要更新与之相关的其他函数,根据提示补充即可
这样做的目的在于可以将节点的印记和数据库、钱包关联起来,以作区分
编译并设置NODE_ID
go build main.go
export NODE_ID=3000
//创建钱包
./main createWallet
NODE_ID:3000
Wallet address: 15wtkW1HNytLtdbmm448Le6WaftU8hq5Lv
//创建创世区块
./main createBlockChain -address 15wtkW1HNytLtdbmm448Le6WaftU8hq5Lv
NODE_ID:3000
is creating genesis block...
Block hash:00006001e75e2905628142518f09a0fd03ef078b1040518056d2e44e03b80ca1
查看数据库和钱包文件,其名称如预期的一样已被更改
ls
blockChain_3000.db main main.go src wallets_3000.dat
拷贝重命名数据库
cp blockChain_3000.db blockChain_3001.db
qinjianquan@MacBook-Pro-10 Part71-Net-NODE_ID-Configuration % ls
blockChain_3000.db blockChain_3001.db main main.go src wallets_3000.dat
用新的节点创建钱包
NODE_ID=3001
qinjianquan@MacBook-Pro-10 Part71-Net-NODE_ID-Configuration % ./bc createWallet
NODE_ID:3001
Wallet address: 1G6xGK8Zn1AZVAW6LcdDr2fwf7RqWXWhEs
1
./bc getAddressList
NODE_ID:3001
Address list:
1G6xGK8Zn1AZVAW6LcdDr2fwf7RqWXWhEs
13iAHiLFxYiqNSJHP2baiFyDumd9chEfAU
注意:此时还是在一条链上
13.3 区块链的验证逻辑
按照惯例先把之前的文件复制并重新命名
/Users/qinjianquan/publicChain/Part72-Net-Mine_Cli
现在我们在转账时增加验证环节
/Users/qinjianquan/publicChain/Part72-Net-Mine_Cli/src/CLI.go
func (cli CLI) Run() {
--
flagSendBlockVerify := transferBlockCmd.Bool("mine", false, "Whether to verify now")
--
if transferBlockCmd.Parsed() {
if *flagFrom == "" || *flagTo == "" || *flagAmount == "" {
printUsage()
os.Exit(1)
}
from := JSONToArray(*flagFrom)
to := JSONToArray(*flagTo)
//verify the validity of address before transaction occurs
for index, fromAddress := range from {
if IsValidForAddress([]byte(fromAddress)) == false || IsValidForAddress([]byte(to[index])) == false {
fmt.Println("Address is invalid")
printUsage()
os.Exit(1)
}
}
amount := JSONToArray(*flagAmount)
cli.send(from, to, amount, nodeID, *flagSendBlockVerify)
}
--
}
/Users/qinjianquan/publicChain/Part72-Net-Mine_Cli/src/CLI_transfer.go
func (cli CLI) send(from []string, to []string, amount []string, nodeID string, mineNow bool) {
blockchain := BlockChainObject(nodeID)
defer blockchain.DB.Close()
if mineNow {
//mine a new clock
blockchain.MineNewBlock(from, to, amount, nodeID)
//when finished the transaction, update the data
utxoSet := &UTXOSet{blockchain}
utxoSet.Update()
} else {
//send transaction to miner verify
fmt.Println("handled by miner node...")
}
}
重新编译并运行,在不设置是否要验证参数时,默认不验证
./bc transfer -from '["1JSZG4ggQYYxR3LidBz3wEe8b1HmmfkARm"]' -to '["1JRTE3YTLV7UTpYDK53H5ahEyeUXb7DMx4"]' -amount '["6"]'
NODE_ID:3000
handled by miner node...
若验证时,则会继续mine
./bc transfer -from '["1JSZG4ggQYYxR3LidBz3wEe8b1HmmfkARm"]' -to '["1JRTE3YTLV7UTpYDK53H5ahEyeUXb7DMx4"]' -amount '["6"]' -mine
NODE_ID:3000
Block hash:000057e1b9e955d844e73919ced9f4b2fa30d9088ba448acd9475902ef46f9c3
13.4 客户端服务器使用
本节我们创建一个客户端和服务器
新建文件
/Users/qinjianquan/publicChain/Part73-Net-TCP
新建两个文件
server
/Users/qinjianquan/publicChain/Part73-Net-TCP/server.go
func main() {
//server
fmt.Println("Server has started...")
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Panic(err)
}
defer ln.Close()
for {
//receive data from client
conn, err1 := ln.Accept()
if err != nil {
log.Panic(err1)
}
//read data from client
request, err2 := ioutil.ReadAll(conn)
if err2 != nil {
log.Panic(err2)
}
fmt.Printf("received a message:%v\n", request)
}
}
client
/Users/qinjianquan/publicChain/Part73-Net-TCP/client.go
func main() {
sendMessage()
}
func sendMessage() {
fmt.Println("a client sends message to the server...")
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
log.Panic(err)
}
defer conn.Close()
//attach message
_, err1 := io.Copy(conn, bytes.NewReader([]byte("version")))
if err1 != nil {
log.Panic(err1)
}
}
编译生成可以执行的二进制文件
client client.go server server.go
13.5 startnode-cli
复制72…重命名
/Users/qinjianquan/publicChain/Part74-Net-TCP
增加命令
func printUsage() {
--
fmt.Println("\tstartNode -miner ADDRESS -- start the node server,and specify the mining reward address")
}
func (cli CLI) Run() {
--
startNodeCmd := flag.NewFlagSet("startNode", flag.ExitOnError)
--
flagMiner := startNodeCmd.String("miner", "", "reward address")
...
}
启动节点
func (cli *CLI) StartNode(nodeID string, mineAddress string) {
if IsValidForAddress([]byte(mineAddress)) || mineAddress == "" {
//start server
fmt.Printf("start the server, localhost:%s", nodeID)
//StartServer(nodeID,mineAddress)
} else {
fmt.Println("reward address is invalid")
}
}
记得编译测试
13.6 项目集成客户端和服务器代码
惯例复制重命名
/Users/qinjianquan/publicChain/Part75-Net-Server
/main node address
var konwNodes = []string{"localhost:3000"}
var nodeAddress string
func StartServer(nodeID string, mineAddress string) {
//current node address
nodeAddress = fmt.Sprintf("localhost:%s", nodeID)
ln, err := net.Listen("tcp", nodeAddress)
if err != nil {
log.Panic(err)
}
defer ln.Close()
if nodeAddress != konwNodes[0] {
//konwNodes[0] is main node
SendMessage(konwNodes[0], nodeAddress)
}
for {
//receive data from client
conn, err1 := ln.Accept()
if err != nil {
log.Panic(err1)
}
//read data from client
request, err2 := ioutil.ReadAll(conn)
if err2 != nil {
log.Panic(err2)
}
fmt.Printf("received a message:%s\n", request)
}
}
func SendMessage(to string, from string) {
fmt.Println("a client sends message to the server...")
conn, err := net.Dial("tcp", to)
if err != nil {
log.Panic(err)
}
defer conn.Close()
//attach message
_, err1 := io.Copy(conn, bytes.NewReader([]byte(from)))
if err1 != nil {
log.Panic(err1)
}
}
启动主节点和钱包节点
注意:请在两个终端中操作
启动主节点:
cd /Users/qinjianquan/publicChain/Part75-Net-Server
qinjianquan@MacBook-Pro-10 Part75-Net-Server % go build -o bc main.go
qinjianquan@MacBook-Pro-10 Part75-Net-Server % export NODE_ID=3000
qinjianquan@MacBook-Pro-10 Part75-Net-Server % ./bc startNode
NODE_ID:3000
start the server, localhost:3000
启动钱包节点:
cd /Users/qinjianquan/publicChain/Part75-Net-Server
qinjianquan@MacBook-Pro-10 Part75-Net-Server % go build -o bc1 main.go
qinjianquan@MacBook-Pro-10 Part75-Net-Server % export NODE_ID=3001
qinjianquan@MacBook-Pro-10 Part75-Net-Server % ./bc1 startNode
NODE_ID:3001
start the server, localhost:3001
a client sends message to the server...
此时主节点会收到来自钱包节点的请求
received a message:localhost:3001
13.7 Version信息处理
/Users/qinjianquan/publicChain/Part76-Net-Version
更新Server文件,主要是将一些信息打包然后发给主节点
/Users/qinjianquan/publicChain/Part76-Net-Version/src/Server.go
type GetData struct {
AddFrom string
Type string
ID []byte
}
type Inv struct {
AddFrom string
Type string
Items [][]byte
}
type Tx struct {
AddFrom string
Transaction []byte
}
type Verzion struct {
Version int
BestHeight int //the block height of current node
AddrFrom string //the address of current node
}
//main node address
var konwNodes = []string{"localhost:3000"}
var nodeAddress string
func StartServer(nodeID string, mineAddress string) {
//current node address
nodeAddress = fmt.Sprintf("localhost:%s", nodeID)
ln, err := net.Listen(PROTOCOL, nodeAddress)
if err != nil {
log.Panic(err)
}
defer ln.Close()
bc := BlockChainObject(nodeID)
if nodeAddress != konwNodes[0] {
//konwNodes[0] is main node
SendVersion(konwNodes[0], bc)
}
for {
//receive data from client
conn, err1 := ln.Accept()
if err != nil {
log.Panic(err1)
}
//read data from client
request, err2 := ioutil.ReadAll(conn)
if err2 != nil {
log.Panic(err2)
}
fmt.Printf("received a message:%s\n", request[:COMMANDLENGTH])
}
}
func SendVersion(toAddress string, bc *Blockchain) {
//bestHeight := bc.GetBestHeight()
bestHeight := 1
payload := GobEncode(Verzion{NODE_VERSION, bestHeight, nodeAddress})
request := append(CommandTOBytes(VERSION), payload...)
SendData(toAddress, request)
}
func SendData(to string, data []byte) {
fmt.Println("a client sends message to the server...")
conn, err := net.Dial("tcp", to)
if err != nil {
log.Panic(err)
}
defer conn.Close()
//attach message
_, err1 := io.Copy(conn, bytes.NewReader(data))
if err1 != nil {
log.Panic(err1)
}
}
/Users/qinjianquan/publicChain/Part76-Net-Version/src/Constant.go
const PROTOCOL = "tcp"
const COMMANDLENGTH = 12
const NODE_VERSION = 1
const VERSION = "version"
/Users/qinjianquan/publicChain/Part76-Net-Version/src/Utils.go
func bytesToCommand(bytes []byte) string {
var command []byte
for _, b := range bytes {
if b != 0x0 {
command = append(command, b)
}
}
return fmt.Sprintf("%s", command)
}
func GobEncode(data interface{}) []byte {
var buff bytes.Buffer
enc := gob.NewEncoder(&buff)
err := enc.Encode(data)
if err != nil {
log.Panic(err)
}
return buff.Bytes()
}
在两个节点中分别运行3000节点和3001节点,3000节点能够查阅到version信息
/bc startNode
NODE_ID:3001
start the server, localhost:3001
a client sends message to the server...
./bc startNode
NODE_ID:3000
start the server, localhost:3000
received a message:version
13.8 获取节点区块链高度
func (blockchain *Blockchain)GetBestHeight() int64{
block := blockchain.Iterator().Next()
return block.Height
}