diff --git a/README.md b/README.md new file mode 100644 index 0000000..078f8fc --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# bc + +Own p2p network and own blockchain libraries written in Go, to develop own decentralized apps. diff --git a/memory.md b/memory.md new file mode 100644 index 0000000..f155507 --- /dev/null +++ b/memory.md @@ -0,0 +1,4 @@ +- p2plib/messages.go / MessageHandler +put in a way that the messages handlers can be created from the main code (not from the package) + +- subsitute the REST functions by the messages in the MessageHandler diff --git a/peer/.gitignore b/peer/.gitignore new file mode 100644 index 0000000..f9e8176 --- /dev/null +++ b/peer/.gitignore @@ -0,0 +1 @@ +*.data diff --git a/peer/README.md b/peer/README.md new file mode 100644 index 0000000..292c9f2 --- /dev/null +++ b/peer/README.md @@ -0,0 +1,73 @@ +# Peer + +To run as a normal peer: +``` +./peer +``` + +To run as a p2p server: +``` +./peer server +``` + +Needs the config.json file: +```json +{ + "ip": "127.0.0.1", + "port": "3001", + "serverip": "127.0.0.1", + "serverport": "3000" +} +``` + + +## Peer REST API +- GET / + - Returns the peer.ID (where peer.ID = hash(peer.IP + ":" + peer.Port)) + + +- GET /peers + - Returns the peer outcomingPeersList (the peers which the peer have connection) + +```json +{ + "PeerID": "VOnL-15rFsUiCnRoyGFksKvWKcwNBRz5iarRem0Ilvo=", + "peerslist": [ + { + "id": "VOnL-15rFsUiCnRoyGFksKvWKcwNBRz5iarRem0Ilvo=", + "ip": "127.0.0.1", + "port": "3000", + "role": "server", + "conn": null + }, + { + "id": "Lk9jEP1YcOAzl51yY61GdWADNe35_g5Teh12JeguHhA=", + "ip": "127.0.0.1", + "port": "3003", + "role": "client", + "conn": {} + }, + { + "id": "xj78wuyN2_thFBsXOUXnwij4L8vualxQ9GnVRK6RS4c=", + "ip": "127.0.0.1", + "port": "3005", + "role": "client", + "conn": {} + } + ], + "date": "0001-01-01T00:00:00Z" +} +``` + +- POST /register + - Adds the address (pubK of the user) to the blockchain + + +## TODO +- When a peer connects to the network, sends his last Block, and receives the new Blocks from this last Block --> DONE with REST petitions, maybe is better with tcp conn +- Delete the peer from the peers list when the connection is closed --> DONE +- REST: + - endpoint to get if the address is in the blockchain (to verify users) +- parameters Date or LastUpdate on the structs needs to be updated values +- implement rsa encryption between peers +- store blockchain in a .data file diff --git a/peer/blockchainlib/block.go b/peer/blockchainlib/block.go new file mode 100644 index 0000000..20c0d06 --- /dev/null +++ b/peer/blockchainlib/block.go @@ -0,0 +1,20 @@ +package blockchainlib + +import ( + "crypto/sha256" + "encoding/base64" + "encoding/json" + "log" +) + +func HashBlock(b Block) string { + blockJson, err := json.Marshal(b) + if err != nil { + log.Println(err) + } + blockString := string(blockJson) + + h := sha256.New() + h.Write([]byte(blockString)) + return base64.URLEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/peer/blockchainlib/blockchain.go b/peer/blockchainlib/blockchain.go new file mode 100644 index 0000000..21be2dd --- /dev/null +++ b/peer/blockchainlib/blockchain.go @@ -0,0 +1,155 @@ +package blockchainlib + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "time" + + "github.com/fatih/color" +) + +type Block struct { + Hash string `json:"hash"` + Height int64 `json:"height"` + Date time.Time `json:"date"` + PreviousHash string `json:"previoushash"` + NextHash string `json:"nexthash"` + Data []string `json:"data"` + Emitter string `json:"emitter"` //the ID of the peer that has emmited the block +} + +type Blockchain struct { + GenesisBlock string `json:"genesisblock"` + LastUpdate time.Time `json:"lastupdate"` + Blocks []Block `json:"blocks"` +} + +//var blockchain Blockchain + +func (bc *Blockchain) GetBlockByHash(hash string) (Block, error) { + for _, block := range bc.Blocks { + if block.Hash == hash { + return block, nil + } + } + var b Block + return b, errors.New("Block Hash not found") +} + +func (bc *Blockchain) CreateBlock(data string) Block { + var b Block + b.Height = int64(len(bc.Blocks)) + if len(bc.Blocks) == 0 { + b.Height = 0 + } else { + b.PreviousHash = bc.Blocks[len(bc.Blocks)-1].Hash + } + b.Date = time.Now() + b.Data = append(b.Data, data) + //b.Emitter = runningPeer.ID + + b.Hash = HashBlock(b) + return b +} + +func (bc *Blockchain) BlockExists(block Block) bool { + for _, b := range bc.Blocks { + if b.Hash == block.Hash { + return true + } + } + return false +} + +func (bc *Blockchain) AddBlock(block Block) error { + if bc.BlockExists(block) { + return errors.New("[Error adding Block]: Block already exists in the Blockchain") + } + if len(bc.Blocks) > 0 { + bc.Blocks[len(bc.Blocks)-1].NextHash = block.Hash + } else { + bc.GenesisBlock = block.Hash + } + bc.Blocks = append(bc.Blocks, block) + + bc.SaveToDisk() + + return nil +} + +func (bc *Blockchain) ReconstructBlockchainFromBlock(urlAPI string, h string) { + color.Yellow("reconstructing the blockchain from last block in memory") + var block Block + var err error + + block, err = bc.GetBlockByHash(h) + check(err) + + if h == "" { + //no genesis block yet + color.Green(urlAPI + "/blocks/genesis") + res, err := http.Get(urlAPI + "/blocks/genesis") + check(err) + body, err := ioutil.ReadAll(res.Body) + check(err) + err = json.Unmarshal(body, &block) + check(err) + color.Yellow("[New Block]: " + block.Hash) + err = bc.AddBlock(block) + check(err) + } else { + block.NextHash = h + } + + for block.NextHash != "" && block.Hash != "" { + res, err := http.Get(urlAPI + "/blocks/next/" + block.Hash) + check(err) + body, err := ioutil.ReadAll(res.Body) + check(err) + err = json.Unmarshal(body, &block) + check(err) + if block.Hash != "" { + color.Yellow("[New Block]: " + block.Hash) + err = bc.AddBlock(block) + check(err) + } + } + bc.Print() +} + +func (bc *Blockchain) Print() { + color.Green("Printing Blockchain stored in memory") + color.Green("Genesis Block: " + bc.GenesisBlock) + for _, b := range bc.Blocks { + color.Green("Block height:") + fmt.Println(b.Height) + color.Green("Hash: " + b.Hash) + color.Green("Date: " + b.Date.String()) + color.Green("---") + } +} + +func (bc *Blockchain) ReadFromDisk() error { + file, err := ioutil.ReadFile("blockchain.data") + if err != nil { + return err + } + content := string(file) + json.Unmarshal([]byte(content), &bc) + return nil +} + +func (bc *Blockchain) SaveToDisk() error { + bytesBlockchain, err := json.Marshal(bc) + if err != nil { + return err + } + err = ioutil.WriteFile("blockchain.data", bytesBlockchain, 0644) + if err != nil { + return err + } + return nil +} diff --git a/peer/blockchainlib/errors.go b/peer/blockchainlib/errors.go new file mode 100644 index 0000000..2c86a73 --- /dev/null +++ b/peer/blockchainlib/errors.go @@ -0,0 +1,15 @@ +package blockchainlib + +import ( + "log" + "runtime" +) + +func check(err error) { + if err != nil { + _, fn, line, _ := runtime.Caller(1) + log.Println(line) + log.Println(fn) + log.Println(err) + } +} diff --git a/peer/config.json b/peer/config.json new file mode 100755 index 0000000..299a8ce --- /dev/null +++ b/peer/config.json @@ -0,0 +1,6 @@ +{ + "ip": "127.0.0.1", + "serverip": "127.0.0.1", + "serverport": "3000", + "serverrestport": "3002" +} diff --git a/peer/errors.go b/peer/errors.go new file mode 100755 index 0000000..b3cf6b2 --- /dev/null +++ b/peer/errors.go @@ -0,0 +1,15 @@ +package main + +import ( + "log" + "runtime" +) + +func check(err error) { + if err != nil { + _, fn, line, _ := runtime.Caller(1) + log.Println(line) + log.Println(fn) + log.Println(err) + } +} diff --git a/peer/log.go b/peer/log.go new file mode 100755 index 0000000..e8f391a --- /dev/null +++ b/peer/log.go @@ -0,0 +1,24 @@ +package main + +import ( + "io" + "log" + "os" + "strings" + "time" +) + +func savelog() { + timeS := time.Now().String() + _ = os.Mkdir("logs", os.ModePerm) + //next 3 lines are to avoid windows filesystem errors + timeS = strings.Replace(timeS, " ", "_", -1) + timeS = strings.Replace(timeS, ".", "-", -1) + timeS = strings.Replace(timeS, ":", "-", -1) + logFile, err := os.OpenFile("logs/log-"+timeS+".log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if err != nil { + panic(err) + } + mw := io.MultiWriter(os.Stdout, logFile) + log.SetOutput(mw) +} diff --git a/peer/main.go b/peer/main.go new file mode 100644 index 0000000..9f462a0 --- /dev/null +++ b/peer/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "os" + "time" + + blockchainlib "./blockchainlib" + p2plib "./p2plib" + "github.com/fatih/color" +) + +var tp p2plib.ThisPeer +var blockchain blockchainlib.Blockchain + +func main() { + + if len(os.Args) < 3 { + color.Red("need to call:") + color.Red("./peer client 3001 3002") + os.Exit(3) + } + + color.Blue("Starting Peer") + //read configuration file + readConfig("config.json") + + //read the stored blockchain + err := blockchain.ReadFromDisk() + check(err) + blockchain.Print() + + tp = p2plib.InitializePeer(os.Args[1], "127.0.0.1", + os.Args[2], os.Args[3], config.ServerIP, config.ServerPort) + + if tp.RunningPeer.Role == "client" { + color.Red("http://" + config.IP + ":" + config.ServerRESTPort) + fmt.Println(blockchain.GenesisBlock) + blockchain.ReconstructBlockchainFromBlock("http://"+config.IP+":"+config.ServerRESTPort, blockchain.GenesisBlock) + } + color.Blue("initialized") + go runRestServer() + + fmt.Println(tp.Running) + for tp.Running { + time.Sleep(1000 * time.Millisecond) + } +} diff --git a/peer/p2plib/connections.go b/peer/p2plib/connections.go new file mode 100644 index 0000000..1a74cf1 --- /dev/null +++ b/peer/p2plib/connections.go @@ -0,0 +1,81 @@ +package p2plib + +import ( + "bufio" + "fmt" + "log" + "net" + + "github.com/fatih/color" +) + +func (tp *ThisPeer) AcceptPeers(peer Peer) { + fmt.Println("accepting peers at: " + peer.Port) + l, err := net.Listen("tcp", peer.IP+":"+peer.Port) + if err != nil { + log.Println("Error accepting peers. Listening port: " + peer.Port) + tp.Running = false + } + for tp.Running { + conn, err := l.Accept() + if err != nil { + log.Println("Error accepting peers. Error accepting connection") + tp.Running = false + } + var newPeer Peer + newPeer.IP = GetIPFromConn(conn) + newPeer.Port = GetPortFromConn(conn) + newPeer.Conn = conn + globalTP.PeersConnections.Incoming = AppendPeerIfNoExist(globalTP.PeersConnections.Incoming, newPeer) + go HandleConn(conn, newPeer) + } +} +func ConnectToPeer(peer Peer) { + color.Green("connecting to new peer") + log.Println("Connecting to new peer: " + peer.IP + ":" + peer.Port) + conn, err := net.Dial("tcp", peer.IP+":"+peer.Port) + if err != nil { + log.Println("Error connecting to: " + peer.IP + ":" + peer.Port) + return + } + peer.Conn = conn + globalTP.PeersConnections.Outcoming = AppendPeerIfNoExist(globalTP.PeersConnections.Outcoming, peer) + go HandleConn(conn, peer) +} +func HandleConn(conn net.Conn, connPeer Peer) { + connRunning := true + log.Println("handling conn: " + conn.RemoteAddr().String()) + //reply to the conn, send the peerList + var msg Msg + msg.Construct("PeersList", "here my outcomingPeersList") + msg.PeersList = globalTP.PeersConnections.Outcoming + msgB := msg.ToBytes() + _, err := conn.Write(msgB) + if err != nil { + log.Println(err) + } + + for connRunning { + /* + buffer := make([]byte, 1024) + bytesRead, err := conn.Read(buffer) + */ + newmsg, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + log.Println(err) + connRunning = false + } else { + var msg Msg + //msg = msg.createFromBytes([]byte(string(buffer[0:bytesRead]))) + msg = msg.CreateFromBytes([]byte(newmsg)) + MessageHandler(connPeer, msg) + } + } + //TODO add that if the peer closed is the p2p server, show a warning message at the peer + log.Println("Peer: " + conn.RemoteAddr().String() + " connection closed") + conn.Close() + //TODO delete the peer from the outcomingPeersList --> DONE + DeletePeerFromPeersList(connPeer, &globalTP.PeersConnections.Outcoming) + /*color.Yellow("peer deleted, current peerList:") + PrintPeersList()*/ +} diff --git a/peer/p2plib/errors.go b/peer/p2plib/errors.go new file mode 100644 index 0000000..d5d81a1 --- /dev/null +++ b/peer/p2plib/errors.go @@ -0,0 +1,15 @@ +package p2plib + +import ( + "log" + "runtime" +) + +func check(err error) { + if err != nil { + _, fn, line, _ := runtime.Caller(1) + log.Println(line) + log.Println(fn) + log.Println(err) + } +} diff --git a/peer/p2plib/init.go b/peer/p2plib/init.go new file mode 100644 index 0000000..7e66149 --- /dev/null +++ b/peer/p2plib/init.go @@ -0,0 +1,43 @@ +package p2plib + +import ( + "fmt" + "math/rand" + "time" +) + +func InitializePeer(role, ip, port, restport, serverip, serverport string) ThisPeer { + //initialize some vars + rand.Seed(time.Now().Unix()) + + var tp ThisPeer + tp.Running = true + tp.RunningPeer.Role = role + tp.RunningPeer.Port = port + tp.RunningPeer.RESTPort = restport + tp.RunningPeer.ID = HashPeer(tp.RunningPeer) + + tp.ID = tp.RunningPeer.ID + globalTP.PeersConnections.Outcoming.PeerID = tp.RunningPeer.ID + fmt.Println(tp.RunningPeer) + //outcomingPeersList.Peers = append(outcomingPeersList.Peers, peer.RunningPeer) + globalTP.PeersConnections.Outcoming = AppendPeerIfNoExist(globalTP.PeersConnections.Outcoming, tp.RunningPeer) + fmt.Println(globalTP.PeersConnections.Outcoming) + + if tp.RunningPeer.Role == "server" { + go tp.AcceptPeers(tp.RunningPeer) + } + if tp.RunningPeer.Role == "client" { + var serverPeer Peer + serverPeer.IP = serverip + serverPeer.Port = serverport + serverPeer.Role = "server" + serverPeer.ID = HashPeer(serverPeer) + go tp.AcceptPeers(tp.RunningPeer) + ConnectToPeer(serverPeer) + } + globalTP = tp + + return tp + +} diff --git a/peer/p2plib/messages.go b/peer/p2plib/messages.go new file mode 100644 index 0000000..3a8e507 --- /dev/null +++ b/peer/p2plib/messages.go @@ -0,0 +1,78 @@ +package p2plib + +import ( + "encoding/json" + "fmt" + "log" + "time" + + "github.com/fatih/color" +) + +type Msg struct { + Type string `json:"type"` + Date time.Time `json:"date"` + Content string `json:"content"` + PeersList PeersList `json:"peerslist"` + Data []byte `json:"data"` +} + +func MessageHandler(peer Peer, msg Msg) { + + log.Println("[New msg]") + log.Println(msg) + + switch msg.Type { + case "Hi": + color.Yellow(msg.Type) + color.Green(msg.Content) + break + case "PeersList": + color.Blue("newPeerslist") + fmt.Println(msg.PeersList) + color.Red("PeersList") + + UpdateNetworkPeersList(peer.Conn, msg.PeersList) + PropagatePeersList(peer) + PrintPeersList() + break + case "PeersList_Response": + //for the moment is not beeing used + color.Blue("newPeerslist, from PeersList_Response") + fmt.Println(msg.PeersList) + color.Red("PeersList_Response") + + UpdateNetworkPeersList(peer.Conn, msg.PeersList) + PropagatePeersList(peer) + PrintPeersList() + break + case "Block": + /*//TODO check if the block is signed by an autorized emitter + if !blockchain.blockExists(msg.Block) { + blockchain.addBlock(msg.Block) + propagateBlock(msg.Block) + }*/ + break + default: + log.Println("Msg.Type not supported") + break + } + +} +func (msg *Msg) Construct(msgtype string, msgcontent string) { + msg.Type = msgtype + msg.Content = msgcontent + msg.Date = time.Now() +} +func (msg Msg) ToBytes() []byte { + msgS, err := json.Marshal(msg) + check(err) + l := string(msgS) + "\n" + r := []byte(l) + return r +} +func (msg Msg) CreateFromBytes(bytes []byte) Msg { + err := json.Unmarshal(bytes, &msg) + check(err) + return msg +} diff --git a/peer/p2plib/peers.go b/peer/p2plib/peers.go new file mode 100644 index 0000000..a67e7c1 --- /dev/null +++ b/peer/p2plib/peers.go @@ -0,0 +1,164 @@ +package p2plib + +import ( + "fmt" + "net" + "time" + + "github.com/fatih/color" +) + +type Peer struct { + ID string `json:"id"` //in the future, this will be the peer hash + IP string `json:"ip"` + Port string `json:"port"` + RESTPort string `json:"restport"` + Role string `json:"role"` //client or server + Conn net.Conn `json:"conn"` +} + +type PeersList struct { + PeerID string + Peers []Peer `json:"peerslist"` + Date time.Time `json:"date"` +} + +type PeersConnections struct { + Incoming PeersList + Outcoming PeersList + Network PeersList //the peers that have been received in the lists from other peers +} + +type ThisPeer struct { + Running bool + ID string + RunningPeer Peer + PeersConnections PeersConnections +} + +var globalTP ThisPeer + +func PeerIsInPeersList(p Peer, pl []Peer) int { + r := -1 + for i, peer := range pl { + if peer.IP+":"+peer.Port == p.IP+":"+p.Port { + r = i + } + } + return r +} + +func DeletePeerFromPeersList(p Peer, pl *PeersList) { + i := PeerIsInPeersList(p, pl.Peers) + if i != -1 { + //delete peer from pl.Peers + pl.Peers = append(pl.Peers[:i], pl.Peers[i+1:]...) + } +} +func AppendPeerIfNoExist(pl PeersList, p Peer) PeersList { + i := PeerIsInPeersList(p, pl.Peers) + if i == -1 { + pl.Peers = append(pl.Peers, p) + } + return pl +} +func UpdateNetworkPeersList(conn net.Conn, newPeersList PeersList) { + for _, peer := range newPeersList.Peers { + if GetIPPortFromConn(conn) == peer.IP+":"+peer.Port { + peer.ID = newPeersList.PeerID + color.Yellow(peer.ID) + } + i := PeerIsInPeersList(peer, globalTP.PeersConnections.Network.Peers) + if i == -1 { + globalTP.PeersConnections.Network.Peers = append(globalTP.PeersConnections.Network.Peers, peer) + } else { + fmt.Println(globalTP.PeersConnections.Network.Peers[i]) + globalTP.PeersConnections.Network.Peers[i].ID = peer.ID + } + } +} +func SearchPeerAndUpdate(p Peer) { + for _, peer := range globalTP.PeersConnections.Outcoming.Peers { + color.Red(p.IP + ":" + p.Port) + color.Yellow(peer.IP + ":" + peer.Port) + if p.IP+":"+p.Port == peer.IP+":"+peer.Port { + peer.ID = p.ID + } + } +} + +//send the outcomingPeersList to all the peers except the peer p that has send the outcomingPeersList +func PropagatePeersList(p Peer) { + for _, peer := range globalTP.PeersConnections.Network.Peers { + if peer.Conn != nil { + if peer.ID != p.ID && p.ID != "" { + color.Yellow(peer.ID + " - " + p.ID) + var msg Msg + msg.Construct("PeersList", "here my outcomingPeersList") + msg.PeersList = globalTP.PeersConnections.Outcoming + msgB := msg.ToBytes() + _, err := peer.Conn.Write(msgB) + check(err) + } else { + /* + for the moment, this is not being called, due that in the IncomingPeersList, + there is no peer.ID, so in the comparation wih the peer that has send the + peersList, is comparing ID with "", so nevere enters this 'else' section + + maybe it's not needed. TODO check if it's needed the PeerList_Response + For the moment is working without it + */ + //to the peer that has sent the peerList, we send our PeersList + + var msg Msg + msg.Construct("PeersList_Response", "here my outcomingPeersList") + msg.PeersList = globalTP.PeersConnections.Outcoming + msgB := msg.ToBytes() + _, err := peer.Conn.Write(msgB) + check(err) + } + } else { + //connect to peer + if peer.ID != p.ID && peer.ID != globalTP.RunningPeer.ID { + if PeerIsInPeersList(peer, globalTP.PeersConnections.Outcoming.Peers) == -1 { + color.Red("no connection, connecting to peer: " + peer.Port) + ConnectToPeer(peer) + } + } + + } + } +} +func PrintPeersList() { + fmt.Println("") + color.Blue("runningPeer.ID: " + globalTP.RunningPeer.ID) + color.Green("OUTCOMING PEERSLIST:") + for _, peer := range globalTP.PeersConnections.Outcoming.Peers { + fmt.Println(peer) + } + color.Green("INCOMING PEERSLIST:") + for _, peer := range globalTP.PeersConnections.Incoming.Peers { + fmt.Println(peer) + } + + color.Green("NETWORK PEERSLIST:") + for _, peer := range globalTP.PeersConnections.Network.Peers { + fmt.Println(peer) + } + fmt.Println("") +} + +//send the block to all the peers of the outcomingPeersList +/*func PropagateBlock(b Block) { + //prepare the msg to send to all connected peers + var msg Msg + msg.construct("Block", "new block") + msg.Block = b + msgB := msg.toBytes() + for _, peer := range outcomingPeersList.Peers { + if peer.Conn != nil { + _, err := peer.Conn.Write(msgB) + check(err) + } + } +}*/ diff --git a/peer/p2plib/utils.go b/peer/p2plib/utils.go new file mode 100644 index 0000000..f6e3a4f --- /dev/null +++ b/peer/p2plib/utils.go @@ -0,0 +1,38 @@ +package p2plib + +import ( + "crypto/sha256" + "encoding/base64" + "math/rand" + "net" + "strings" +) + +func GetIPPortFromConn(conn net.Conn) string { + ip := GetIPFromConn(conn) + port := GetPortFromConn(conn) + return ip + ":" + port +} +func GetIPFromConn(conn net.Conn) string { + s := conn.RemoteAddr().String() + s = strings.Split(s, ":")[0] + s = strings.Trim(s, ":") + return s +} +func GetPortFromConn(conn net.Conn) string { + s := conn.RemoteAddr().String() + s = strings.Split(s, ":")[1] + s = strings.Trim(s, ":") + return s +} +func RandInt(min int, max int) int { + r := rand.Intn(max-min) + min + return r +} +func HashPeer(p Peer) string { + peerString := p.IP + ":" + p.Port + + h := sha256.New() + h.Write([]byte(peerString)) + return base64.URLEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/peer/peer b/peer/peer new file mode 100755 index 0000000..869c21e Binary files /dev/null and b/peer/peer differ diff --git a/peer/readConfig.go b/peer/readConfig.go new file mode 100755 index 0000000..a148c5b --- /dev/null +++ b/peer/readConfig.go @@ -0,0 +1,26 @@ +package main + +import ( + "encoding/json" + "io/ioutil" +) + +//Config reads the config +type Config struct { + IP string `json:"ip"` + Port string `json:"port"` + RestIP string `json:"restip"` + RESTPort string `json:"restport"` + ServerIP string `json:"serverip"` + ServerPort string `json:"serverport"` + ServerRESTPort string `json:"serverrestport"` +} + +var config Config + +func readConfig(path string) { + file, err := ioutil.ReadFile(path) + check(err) + content := string(file) + json.Unmarshal([]byte(content), &config) +} diff --git a/peer/restConfig.go b/peer/restConfig.go new file mode 100755 index 0000000..36a332e --- /dev/null +++ b/peer/restConfig.go @@ -0,0 +1,47 @@ +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gorilla/mux" +) + +type Route struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +func Logger(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + inner.ServeHTTP(w, r) + + log.Printf( + "%s\t%s\t%s\t%s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} +func NewRouter() *mux.Router { + router := mux.NewRouter().StrictSlash(true) + for _, route := range routes { + var handler http.Handler + handler = route.HandlerFunc + handler = Logger(handler, route.Name) + + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) + } + return router +} diff --git a/peer/restRoutes.go b/peer/restRoutes.go new file mode 100755 index 0000000..9bec3fe --- /dev/null +++ b/peer/restRoutes.go @@ -0,0 +1,141 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + + blockchainlib "./blockchainlib" + p2plib "./p2plib" + "github.com/fatih/color" + "github.com/gorilla/mux" +) + +type Routes []Route + +var routes = Routes{ + Route{ + "Index", + "GET", + "/", + Index, + }, + Route{ + "GetPeers", + "GET", + "/peers", + GetPeers, + }, + Route{ + "PostUser", + "POST", + "/register", + PostUser, + }, + Route{ + "GenesisBlock", + "GET", + "/blocks/genesis", + GenesisBlock, + }, + Route{ + "NextBlock", + "GET", + "/blocks/next/{blockhash}", + NextBlock, + }, + Route{ + "LastBlock", + "GET", + "/blocks/last", + LastBlock, + }, +} + +type Address struct { + Address string `json:"address"` //the pubK of the user, to perform logins +} + +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, tp.ID) +} +func GetPeers(w http.ResponseWriter, r *http.Request) { + jResp, err := json.Marshal(tp.PeersConnections.Outcoming) + check(err) + fmt.Fprintln(w, string(jResp)) +} +func PostUser(w http.ResponseWriter, r *http.Request) { + + decoder := json.NewDecoder(r.Body) + var address string + err := decoder.Decode(&address) + if err != nil { + panic(err) + } + defer r.Body.Close() + fmt.Println(address) + color.Blue(address) + + //TODO add the verification of the address, to decide if it's accepted to create a new Block + block := blockchain.CreateBlock(address) + blockchain.AddBlock(block) + + go PropagateBlock(block) + + jResp, err := json.Marshal(blockchain) + check(err) + fmt.Fprintln(w, string(jResp)) +} + +func PropagateBlock(b blockchainlib.Block) { + //prepare the msg to send to all connected peers + var msg p2plib.Msg + msg.Construct("Block", "new block") + bJson, err := json.Marshal(b) + check(err) + + msg.Data = []byte(bJson) + msgB := msg.ToBytes() + for _, peer := range tp.PeersConnections.Outcoming.Peers { + if peer.Conn != nil { + _, err := peer.Conn.Write(msgB) + check(err) + } + } +} + +func GenesisBlock(w http.ResponseWriter, r *http.Request) { + var genesis blockchainlib.Block + if len(blockchain.Blocks) >= 0 { + genesis = blockchain.Blocks[0] + } + + jResp, err := json.Marshal(genesis) + check(err) + fmt.Fprintln(w, string(jResp)) +} + +func NextBlock(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + blockhash := vars["blockhash"] + + currBlock, err := blockchain.GetBlockByHash(blockhash) + check(err) + nextBlock, err := blockchain.GetBlockByHash(currBlock.NextHash) + check(err) + + jResp, err := json.Marshal(nextBlock) + check(err) + fmt.Fprintln(w, string(jResp)) +} + +func LastBlock(w http.ResponseWriter, r *http.Request) { + var genesis blockchainlib.Block + if len(blockchain.Blocks) > 0 { + genesis = blockchain.Blocks[len(blockchain.Blocks)-1] + } + + jResp, err := json.Marshal(genesis) + check(err) + fmt.Fprintln(w, string(jResp)) +} diff --git a/peer/restServer.go b/peer/restServer.go new file mode 100644 index 0000000..9d7e66b --- /dev/null +++ b/peer/restServer.go @@ -0,0 +1,20 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gorilla/handlers" +) + +func runRestServer() { + //run API + log.Println("server running") + log.Print("port: ") + log.Println(config.RESTPort) + router := NewRouter() + headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Access-Control-Allow-Origin"}) + originsOk := handlers.AllowedOrigins([]string{"*"}) + methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"}) + log.Fatal(http.ListenAndServe(":"+config.RESTPort, handlers.CORS(originsOk, headersOk, methodsOk)(router))) +} diff --git a/peer/tests.sh b/peer/tests.sh new file mode 100644 index 0000000..081dd3d --- /dev/null +++ b/peer/tests.sh @@ -0,0 +1 @@ +curl -X POST http://127.0.0.1:3002/register -d '{\"address\": \"sampleaddress\"}' diff --git a/runTmuxTestPeers.sh b/runTmuxTestPeers.sh new file mode 100644 index 0000000..d7a5870 --- /dev/null +++ b/runTmuxTestPeers.sh @@ -0,0 +1,16 @@ +SESSION='peersTest' + +tmux new-session -d -s $SESSION +tmux split-window -d -t 0 -v +tmux split-window -d -t 1 -h +tmux split-window -d -t 0 -h + +tmux send-keys -t 0 'cd peer && go run *.go server 3001 3002' enter +sleep 2 +tmux send-keys -t 1 "curl -X POST http://127.0.0.1:3002/register -d '{\"address\": \"firstaddress\"}'" enter +sleep 1 +tmux send-keys -t 1 'cd peer && go run *.go client 3003 3004' enter +tmux send-keys -t 2 'cd peer && go run *.go client 3005 3006' enter +tmux send-keys -t 3 'cd peer && go run *.go client 3007 3008' enter + +tmux attach