Created
December 5, 2017 09:06
-
-
Save forthxu/89ead70a08a5efd21a4a173f9726c01b to your computer and use it in GitHub Desktop.
200行区块链-go语言版本 原版 https://github.com/lhartikk/naivechain.git
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"crypto/sha256" | |
"encoding/json" | |
"flag" | |
"fmt" | |
"io/ioutil" | |
"net" | |
"net/http" | |
"strings" | |
"time" | |
"github.com/gin-gonic/gin" | |
log "github.com/laohanlinux/utils/gokitlog" | |
"github.com/laohanlinux/utils/netrpc" | |
pool "github.com/laohanlinux/utils/pool/rpc" | |
) | |
var ( | |
blockChain = []Block{GetGenesiseBlock()} | |
initialPeers = flag.String("peers", "localhost:6660,localhost:6661,localhost:6662", "") | |
httpPort = flag.String("http_port", "localhost:7000", "") | |
p2pPort = flag.String("p2p_port", "localhost:6660", "") | |
peersConnections = make(map[string]*pool.NetRPCRing) | |
s Service | |
) | |
func main() { | |
flag.Parse() | |
l, err := initP2PServer() | |
if err != nil { | |
log.Crit("err", err) | |
} | |
log.Debug("p2pServer listener address", *p2pPort) | |
defer l.Close() | |
peers := strings.Split(*initialPeers, ",") | |
conncetToPeers(peers) | |
initHttpServer() | |
time.Sleep(time.Hour * 10000) | |
} | |
type Block struct { | |
Index int64 | |
PreviousHash string | |
TimeStamp int64 | |
Data []byte | |
Hash string | |
} | |
func NewBlock(index int64, previousHash string, timeStamp int64, Data []byte, Hash string) Block { | |
return Block{ | |
Index: index, | |
PreviousHash: previousHash, | |
TimeStamp: timeStamp, | |
Data: Data, | |
Hash: Hash, | |
} | |
} | |
func CalculationHashForBlock(block Block) string { | |
return CalculationHash(block.Index, block.PreviousHash, block.TimeStamp, block.Data) | |
} | |
func CalculationHash(index int64, previousHash string, timeStamp int64, data []byte) string { | |
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%v%v%v%s", index, previousHash, timeStamp, data)))) | |
} | |
func GenerateNextBlock(blockData []byte) Block { | |
var ( | |
previousBlock = getLatestBlock() | |
nextIndex = previousBlock.Index + 1 | |
nextTimeStamp = time.Now().Unix() | |
nextHash = CalculationHash(nextIndex, previousBlock.Hash, nextTimeStamp, blockData) | |
) | |
return NewBlock(nextIndex, previousBlock.Hash, nextTimeStamp, blockData, nextHash) | |
} | |
func GetGenesiseBlock() Block { | |
return NewBlock(0, "0", 1465154705, []byte("my genesis block!!"), "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7") | |
} | |
func AddBlock(newBlock Block) { | |
if isValidNewBlock(newBlock, getLatestBlock()) { | |
blockChain = append(blockChain, newBlock) | |
} | |
} | |
func replaceChain(newBlocks []Block) { | |
if isValidBlock(newBlocks) && len(newBlocks) > len(blockChain) { | |
log.Warnf("received blockchain is valid, replacing current blockchain with received blockchain") | |
blockChain = newBlocks | |
// broadcast | |
for _, peerClient := range peersConnections { | |
go peerClient.Call(serviceAddBlockChain, &AddBlockChainArgs{NewBlockChain: blockChain}, &AddBlockChainReply{}) | |
} | |
} else { | |
log.Debugf("received blockchain invalid") | |
} | |
} | |
func isValidNewBlock(newBlock, previousBlock Block) bool { | |
if previousBlock.Index+1 != newBlock.Index { | |
log.Debug("msg", "invalid index") | |
return false | |
} | |
if previousBlock.Hash != newBlock.PreviousHash { | |
log.Debug("msg", "invalid previousHash") | |
return false | |
} | |
// check wether newBlock.Hash is right calculation by his content | |
if CalculationHashForBlock(newBlock) != newBlock.Hash { | |
log.Debugf("invalid hash: %v, %v", CalculationHashForBlock(newBlock), newBlock.Hash) | |
return false | |
} | |
return true | |
} | |
func isValidBlock(blockChainToValidate []Block) bool { | |
// check genesis block | |
if blockChainToValidate[0].Hash != GetGenesiseBlock().Hash { | |
return false | |
} | |
for i := 1; i < len(blockChainToValidate); i++ { | |
if !isValidNewBlock(blockChainToValidate[i], blockChainToValidate[i-1]) { | |
return false | |
} | |
} | |
return true | |
} | |
func getLatestBlock() Block { return blockChain[len(blockChain)-1] } | |
func initHttpServer() { | |
router := gin.Default() | |
router.GET("/blocks", func(c *gin.Context) { json.NewEncoder(c.Writer).Encode(blockChain) }) | |
router.POST("/mineBlock", func(c *gin.Context) { | |
var ( | |
buf []byte | |
err error | |
) | |
if buf, err = ioutil.ReadAll(c.Request.Body); err != nil { | |
log.Error("err", err) | |
c.Writer.WriteHeader(http.StatusInternalServerError) | |
return | |
} | |
AddBlock(GenerateNextBlock(buf)) | |
// broadcast to notify peers our has add new block | |
for _, peerClient := range peersConnections { | |
go func() { | |
var rpcArgs, rpcReply = AddBlockChainArgs{NewBlockChain: blockChain}, AddBlockChainReply{} | |
if err := peerClient.Call(serviceAddBlockChain, &rpcArgs, &rpcReply); err != nil { | |
log.Error("err", err) | |
} | |
}() | |
} | |
c.Writer.WriteHeader(http.StatusOK) | |
}) | |
router.GET("/peers", func(c *gin.Context) { | |
var peers []string | |
for peer, _ := range peersConnections { | |
peers = append(peers, peer) | |
} | |
if err := json.NewEncoder(c.Writer).Encode(peers); err != nil { | |
log.Error("err", err) | |
} | |
}) | |
router.POST("/addPeer", func(c *gin.Context) { | |
var peers []string | |
if err := json.NewDecoder(c.Request.Body).Decode(&peers); err != nil { | |
log.Error("err", err) | |
c.Writer.WriteHeader(http.StatusBadRequest) | |
return | |
} | |
conncetToPeers(peers) | |
}) | |
go http.ListenAndServe(*httpPort, router) | |
} | |
func initP2PServer() (net.Listener, error) { | |
l, err := net.Listen("tcp", *p2pPort) | |
if err != nil { | |
return nil, err | |
} | |
server := netrpc.NewServer() | |
server.Register(&s) | |
server.Register(&netrpc.HealthCheck{}) | |
go server.Accept(l) | |
return l, nil | |
} | |
func conncetToPeers(peers []string) { | |
var ( | |
rpcOpt pool.NetRPCRingOpt | |
) | |
for _, peer := range peers { | |
if _, ok := peersConnections[peer]; ok { | |
continue | |
} | |
rpcOpt.Addr, rpcOpt.NetWork, rpcOpt.PoolSize = peer, "tcp", 1 | |
if pools, err := pool.NewNetRPCRing([]pool.NetRPCRingOpt{rpcOpt}); err != nil { | |
log.Error("err", err) | |
} else { | |
peersConnections[peer] = pools | |
} | |
} | |
} | |
// AddBlockChain add new blockchain into cluster | |
// if the new block is new than local lasted block, agree it | |
func handleBlockChain(args AddBlockChainArgs) { | |
log.Debug("who", *p2pPort, "do", "handle blockchain") | |
var ( | |
receivedBlocks = args.NewBlockChain | |
latestBlockReceived = receivedBlocks[len(receivedBlocks)-1] | |
latestBlockHeld = getLatestBlock() | |
) | |
if latestBlockReceived.Index > latestBlockHeld.Index { | |
log.Warnf("blockchain possibly behind. We got: %v Peer got: %v", latestBlockHeld.Index, latestBlockReceived.Index) | |
if latestBlockHeld.Hash == latestBlockReceived.PreviousHash { | |
log.Warnf("We can append the received block to our block") | |
blockChain = append(blockChain, latestBlockReceived) | |
// broadcast to other | |
for _, peerClient := range peersConnections { | |
go func() { | |
var rpcArgs, rpcReply = AddBlockChainArgs{NewBlockChain: args.NewBlockChain}, AddBlockChainReply{} | |
if err := peerClient.Call(serviceAddBlockChain, &rpcArgs, &rpcReply); err != nil { | |
log.Error("err", err) | |
} | |
}() | |
} | |
} else if len(receivedBlocks) == 1 { | |
log.Warnf("we have must query the chain from our peer") | |
// broadcast | |
for _, peersClient := range peersConnections { | |
go func() { | |
var ( | |
rpcArgs QueryAllBlockChainsArgs | |
rpcreply QueryAllBlockChainsReply | |
err error | |
) | |
if err = peersClient.Call(serviceQueryAllBlockChains, &rpcArgs, &rpcreply); err != nil { | |
log.Error("err", err) | |
} else { | |
handleBlockChain(AddBlockChainArgs{NewBlockChain: rpcreply.NewBlockChains}) | |
} | |
}() | |
} | |
} else { | |
log.Debugf("Received blockchain is longer than current blockchain") | |
replaceChain(receivedBlocks) | |
} | |
} else { | |
log.Debug("received blockchain is not longer than received blockchain. Do nothing") | |
} | |
} | |
const ( | |
serviceName = "Service" | |
serviceAddBlockChain = serviceName + ".AddBlockChain" | |
serviceQueryAllBlockChains = serviceName + ".QueryAllBlockChains" | |
) | |
type AddBlockChainArgs struct{ NewBlockChain []Block } | |
type AddBlockChainReply struct{} | |
/////// | |
type QueryAllBlockChainsArgs struct{} | |
type QueryAllBlockChainsReply struct{ NewBlockChains []Block } | |
////// | |
type Service struct{} | |
func (s *Service) QueryAllMsg(_ context.Context, _ *QueryAllBlockChainsArgs, reply *QueryAllBlockChainsReply) error { | |
reply.NewBlockChains = blockChain | |
return nil | |
} | |
func (s *Service) AddBlockChain(_ context.Context, args *AddBlockChainArgs, reply *AddBlockChainReply) error { | |
handleBlockChain(*args) | |
return nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
go build -o blockchain blockchain.go | |
sudo pkill -9 blockchain | |
./blockchain -http_port="localhost:7000" -p2p_port="localhost:6660" -peers="localhost:6661" & | |
./blockchain -http_port="localhost:7001" -p2p_port="localhost:6661" -peers="localhost:6660" & | |
sleep 1 | |
curl -v -XPOST "http://localhost:7001/addPeer" -d '[localhost:6660]' | jq | |
sleep 2 | |
curl -v -XPOST "http://localhost:7000/addPeer" -d '[localhost:6661]' | jq | |
sleep 2 | |
curl -v -XPOST "http://localhost:7001/mineBlock" -d 'hello word' | jq | |
sleep 1 | |
curl -v -XPOST "http://localhost:7001/mineBlock" -d 'hello word' | jq | |
sleep 1 | |
curl -v -XPOST "http://localhost:7001/mineBlock" -d 'hello word' | jq | |
sleep 1 | |
curl -v -XPOST "http://localhost:7001/mineBlock" -d 'hello word' | jq | |
sleep 1 | |
curl -v -XPOST "http://localhost:7001/mineBlock" -d 'hello word' | jq | |
sleep 1 | |
curl -v -XPOST "http://localhost:7001/mineBlock" -d 'hello word' | jq | |
sleep 1 | |
curl -v -XPOST "http://localhost:7001/mineBlock" -d 'hello word' | jq | |
sleep 1 | |
curl -v "http://localhost:7001/blocks" | jq | |
sleep 1 | |
curl -v "http://localhost:7000/blocks" | jq | |
curl -v "http://localhost:7000/peers" | jq | |
curl -v "http://localhost:7001/peers" | jq |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment