diff --git a/identitychain/identityblock.go b/identitychain/identityblock.go index 46be703fe..7a438979e 100644 --- a/identitychain/identityblock.go +++ b/identitychain/identityblock.go @@ -7,8 +7,8 @@ import ( "log" "time" + "github.com/simple-rules/harmony-benchmark/node" "github.com/simple-rules/harmony-benchmark/utils" - "github.com/simple-rules/harmony-benchmark/waitnode" ) // IdentityBlock has the information of one node @@ -16,7 +16,7 @@ type IdentityBlock struct { Timestamp int64 PrevBlockHash [32]byte NumIdentities int32 - Identities []*waitnode.WaitNode + Identities []*node.Node } // Serialize serializes the block @@ -31,7 +31,7 @@ func (b *IdentityBlock) Serialize() []byte { } //Get Identities -func (b *IdentityBlock) GetIdentities() []*waitnode.WaitNode { +func (b *IdentityBlock) GetIdentities() []*node.Node { return b.Identities } @@ -47,7 +47,7 @@ func DeserializeBlock(d []byte) *IdentityBlock { } // NewBlock creates and returns a new block. -func NewBlock(Identities []*waitnode.WaitNode, prevBlockHash [32]byte) *IdentityBlock { +func NewBlock(Identities []*node.Node, prevBlockHash [32]byte) *IdentityBlock { block := &IdentityBlock{Timestamp: time.Now().Unix(), PrevBlockHash: prevBlockHash, NumIdentities: int32(len(Identities)), Identities: Identities} return block } @@ -59,7 +59,7 @@ func (b *IdentityBlock) CalculateBlockHash() [32]byte { hashes = append(hashes, utils.ConvertFixedDataIntoByteArray(b.Timestamp)) hashes = append(hashes, b.PrevBlockHash[:]) for _, id := range b.Identities { - hashes = append(hashes, utils.ConvertFixedDataIntoByteArray(id.ID)) + hashes = append(hashes, utils.ConvertFixedDataIntoByteArray(id)) } hashes = append(hashes, utils.ConvertFixedDataIntoByteArray(b.NumIdentities)) blockHash = sha256.Sum256(bytes.Join(hashes, []byte{})) @@ -68,7 +68,7 @@ func (b *IdentityBlock) CalculateBlockHash() [32]byte { // NewGenesisBlock creates and returns genesis Block. func NewGenesisBlock() *IdentityBlock { - var Ids []*waitnode.WaitNode + var Ids []*node.Node block := &IdentityBlock{Timestamp: time.Now().Unix(), PrevBlockHash: [32]byte{}, NumIdentities: 1, Identities: Ids} return block } diff --git a/identitychain/identitychain.go b/identitychain/identitychain.go index ffd9e1372..c84cb412d 100644 --- a/identitychain/identitychain.go +++ b/identitychain/identitychain.go @@ -9,8 +9,8 @@ import ( "sync" "github.com/simple-rules/harmony-benchmark/log" + "github.com/simple-rules/harmony-benchmark/node" "github.com/simple-rules/harmony-benchmark/p2p" - "github.com/simple-rules/harmony-benchmark/waitnode" ) var mutex sync.Mutex @@ -19,20 +19,21 @@ var identityPerBlock = 100000 // IdentityChain (Blockchain) keeps Identities per epoch, currently centralized! type IdentityChain struct { Identities []*IdentityBlock - PendingIdentities []*waitnode.WaitNode + PendingIdentities []*node.Node log log.Logger Peer p2p.Peer - SelectedIdentitites []*waitnode.WaitNode + SelectedIdentitites []*node.Node EpochNum int - PeerToShardMap map[*waitnode.WaitNode]int - ShardLeaderMap map[int]*waitnode.WaitNode + PeerToShardMap map[*node.Node]int + ShardLeaderMap map[int]*node.Node PubKey string CurrentEpochStartTime int64 NumberOfShards int NumberOfNodesInShard int + PowMap map[p2p.Peer]string } -func seekRandomNumber(EpochNum int, SelectedIdentitites []*waitnode.WaitNode) int { +func seekRandomNumber(EpochNum int, SelectedIdentitites []*node.Node) int { // Broadcast message to all nodes and collect back their numbers, do consensus and get a leader. // Use leader to generate a random number. //all here mocked @@ -50,38 +51,46 @@ type GlobalBlockchainConfig struct { //Shard func (IDC *IdentityChain) Shard() { - num := seekRandomNumber(IDC.EpochNum, IDC.SelectedIdentitites) - IDC.CreateShardAssignment(num) + IDC.CreateShardAssignment() IDC.ElectLeaders() + IDC.BroadCastNewConfiguration() } // func (IDC *IdentityChain) ElectLeaders() { } +func (IDC *IdentityChain) BroadCastNewConfiguration() { + // allPeers := make([]p2p.Peer, len(IDC.SelectedIdentitites)) + // msgToSend := proto. + // p2p.BroadCastMessage(allPeers, msgToSend) + +} + //CreateShardAssignment -func (IDC *IdentityChain) CreateShardAssignment(num int) { +func (IDC *IdentityChain) CreateShardAssignment() { + num := seekRandomNumber(IDC.EpochNum, IDC.SelectedIdentitites) IDC.NumberOfShards = IDC.NumberOfShards + needNewShards() IDC.SelectedIdentitites = generateRandomPermutations(num, IDC.SelectedIdentitites) - IDC.PeerToShardMap = make(map[*waitnode.WaitNode]int) + IDC.PeerToShardMap = make(map[*node.Node]int) numberInOneShard := len(IDC.SelectedIdentitites) / IDC.NumberOfShards for peerNum := 1; peerNum <= len(IDC.SelectedIdentitites); peerNum++ { IDC.PeerToShardMap[IDC.SelectedIdentitites[peerNum]] = peerNum / numberInOneShard } } -func generateRandomPermutations(num int, SelectedIdentitites []*waitnode.WaitNode) []*waitnode.WaitNode { +func generateRandomPermutations(num int, SelectedIdentitites []*node.Node) []*node.Node { src := rand.NewSource(int64(num)) rnd := rand.New(src) perm := rnd.Perm(len(SelectedIdentitites)) - SelectedIdentititesCopy := make([]*waitnode.WaitNode, len(SelectedIdentitites)) + SelectedIdentititesCopy := make([]*node.Node, len(SelectedIdentitites)) for j, i := range perm { SelectedIdentititesCopy[j] = SelectedIdentitites[i] } return SelectedIdentititesCopy } -// SelectIds +// SelectIds as func (IDC *IdentityChain) SelectIds() { selectNumber := IDC.NumberOfNodesInShard - len(IDC.Identities) IB := IDC.GetLatestBlock() @@ -89,7 +98,7 @@ func (IDC *IdentityChain) SelectIds() { selectNumber = int(math.Min(float64(len(IDC.PendingIdentities)), float64(selectNumber))) pending := IDC.PendingIdentities[:selectNumber] IDC.SelectedIdentitites = append(currentIDS, pending...) - IDC.PendingIdentities = []*waitnode.WaitNode{} + IDC.PendingIdentities = []*node.Node{} } //Checks how many new shards we need. Currently we say 0. @@ -120,7 +129,7 @@ func (IDC *IdentityChain) UpdateIdentityChain() { prevBlock := IDC.GetLatestBlock() prevBlockHash := prevBlock.CalculateBlockHash() NewIdentities := IDC.PendingIdentities[:identityPerBlock] - IDC.PendingIdentities = []*waitnode.WaitNode{} + IDC.PendingIdentities = []*node.Node{} //All other blocks are dropped, we need to inform them that they are dropped? IDBlock := NewBlock(NewIdentities, prevBlockHash) IDC.Identities = append(IDC.Identities, IDBlock) @@ -162,5 +171,6 @@ func New(Peer p2p.Peer) *IdentityChain { IDC := IdentityChain{} IDC.Peer = Peer IDC.log = log.New() + IDC.PowMap = make(map[p2p.Peer]string) return &IDC } diff --git a/identitychain/identitychain_handler.go b/identitychain/identitychain_handler.go index 4ac1b6c3f..7b004fd2e 100644 --- a/identitychain/identitychain_handler.go +++ b/identitychain/identitychain_handler.go @@ -1,14 +1,19 @@ package identitychain import ( + "bytes" "fmt" + "math/rand" "net" "os" + "strconv" + "time" + "github.com/simple-rules/harmony-benchmark/node" "github.com/simple-rules/harmony-benchmark/p2p" + "github.com/simple-rules/harmony-benchmark/pow" "github.com/simple-rules/harmony-benchmark/proto" proto_identity "github.com/simple-rules/harmony-benchmark/proto/identity" - "github.com/simple-rules/harmony-benchmark/waitnode" ) //IdentityChainHandler handles registration of new Identities @@ -27,6 +32,8 @@ func (IDC *IdentityChain) IdentityChainHandler(conn net.Conn) { if msgCategory != proto.IDENTITY { IDC.log.Error("Identity Chain Recieved incorrect protocol message") os.Exit(1) + } else { + fmt.Println("Message category is correct") } msgType, err := proto.GetMessageType(content) if err != nil { @@ -43,20 +50,73 @@ func (IDC *IdentityChain) IdentityChainHandler(conn net.Conn) { actionType := proto_identity.IdentityMessageType(msgType) switch actionType { case proto_identity.IDENTITY: - IDC.registerIdentity(msgPayload) + idMsgType, err := proto_identity.GetIdentityMessageType(msgPayload) + if err != nil { + fmt.Println("Error finding the identity message type") + } + switch idMsgType { + case proto_identity.REGISTER: + IDC.registerIdentity(msgPayload) + case proto_identity.ANNOUNCE: + IDC.acceptNewConnection(msgPayload) + } + } } } func (IDC *IdentityChain) registerIdentity(msgPayload []byte) { - identityPayload, err := proto_identity.GetIdentityMessagePayload(msgPayload) + payload, err := proto_identity.GetIdentityMessagePayload(msgPayload) if err != nil { IDC.log.Error("identity payload not read") } else { fmt.Println("identity payload read") } - NewWaitNode := waitnode.DeserializeWaitNode(identityPayload) - IDC.PendingIdentities = append(IDC.PendingIdentities, NewWaitNode) - fmt.Println(len(IDC.PendingIdentities)) + fmt.Println("we are now registering identities") + //reconstruct the challenge and check whether its correct + offset := 0 + proof := payload[offset : offset+32] + offset = offset + 32 + Node := node.DeserializeWaitNode(payload[offset:]) + req := IDC.PowMap[Node.Self] + ok, err := pow.Check(req, string(proof), []byte("")) + fmt.Println(err) + if ok { + fmt.Println("Proof of work accepted") + IDC.PendingIdentities = append(IDC.PendingIdentities, Node) + fmt.Println(len(IDC.PendingIdentities)) //Fix why IDC does not have log working. + } else { + fmt.Println("identity proof of work not accepted") + } +} + +func (IDC *IdentityChain) acceptNewConnection(msgPayload []byte) { + + identityPayload, err := proto_identity.GetIdentityMessagePayload(msgPayload) + if err != nil { + fmt.Println("There was a error in reading the identity payload") + } else { + fmt.Println("accepted new connection") + } + fmt.Println("Sleeping for 2 secs ...") + time.Sleep(2 * time.Second) + Node := node.DeserializeWaitNode(identityPayload) + buffer := bytes.NewBuffer([]byte{}) + src := rand.NewSource(time.Now().UnixNano()) + rnd := rand.New(src) + challengeNonce := int((rnd.Int31())) + req := pow.NewRequest(5, []byte(strconv.Itoa(challengeNonce))) + IDC.PowMap[Node.Self] = req + fmt.Println(req) + buffer.Write([]byte(req)) + // 32 byte block hash + // buffer.Write(prevBlockHash) + // The message is missing previous BlockHash, this is because we don't actively maintain a identitychain + // This canbe included in the fulfill request. + // Message should be encrypted and then signed to follow PKE. + //IDC should accept node publickey, encrypt the nonce and blockhash + // Then sign the message by own private key and send the message back. + msgToSend := proto_identity.ConstructIdentityMessage(proto_identity.REGISTER, buffer.Bytes()) + p2p.SendMessage(Node.Self, msgToSend) } diff --git a/identitymanage/identitymanage.go b/identitymanage/identitymanage.go deleted file mode 100644 index aaef5dfa3..000000000 --- a/identitymanage/identitymanage.go +++ /dev/null @@ -1,43 +0,0 @@ -package identitymanage - -// import ( -// "bytes" -// "encoding/binary" -// "log" - -// "github.com/dedis/kyber" -// ) - -// // Consensus data containing all info related to one round of consensus process -// type Identity struct { -// priKey kyber.Scalar -// pubKey kyber.Point -// Log log.Logger -// } - -// // Construct the response message to send to leader (assumption the consensus data is already verified) -// func (identity *Identity) registerIdentity(msgType proto_consensus.MessageType, response kyber.Scalar) []byte { -// buffer := bytes.NewBuffer([]byte{}) - -// // 4 byte consensus id -// fourBytes := make([]byte, 4) -// binary.BigEndian.PutUint32(fourBytes, consensus.consensusId) -// buffer.Write(fourBytes) - -// // 32 byte block hash -// buffer.Write(consensus.blockHash[:32]) - -// // 2 byte validator id -// twoBytes := make([]byte, 2) -// binary.BigEndian.PutUint16(twoBytes, consensus.nodeId) -// buffer.Write(twoBytes) - -// // 32 byte of response -// response.MarshalTo(buffer) - -// // 64 byte of signature on previous data -// signature := consensus.signMessage(buffer.Bytes()) -// buffer.Write(signature) - -// return proto_identity.ConstructIdentityMessage(msgType, buffer.Bytes()) -// } diff --git a/node/node.go b/node/node.go index 1120c8fa2..35fc1f7a6 100644 --- a/node/node.go +++ b/node/node.go @@ -1,10 +1,15 @@ package node import ( + "bytes" + "encoding/gob" + "fmt" "net" "sync" "github.com/simple-rules/harmony-benchmark/crypto/pki" + "github.com/simple-rules/harmony-benchmark/pow" + "github.com/simple-rules/harmony-benchmark/proto/identity" "github.com/simple-rules/harmony-benchmark/blockchain" "github.com/simple-rules/harmony-benchmark/client" @@ -33,6 +38,9 @@ type Node struct { doneSyncing chan struct{} ClientPeer *p2p.Peer // The peer for the benchmark tx generator client, used for leaders to return proof-of-accept Client *client.Client // The presence of a client object means this node will also act as a client + IsWaiting bool + Self p2p.Peer + IDCPeer p2p.Peer } // Add new crossTx and proofs to the list of crossTx that needs to be sent back to client @@ -64,6 +72,7 @@ func (node *Node) getTransactionsForNewBlock(maxNumTxs int) ([]*blockchain.Trans // Start a server and process the request by a handler. func (node *Node) StartServer(port string) { + fmt.Println("Hello in server now") node.log.Debug("Starting server", "node", node, "port", port) node.listenOnPort(port) @@ -110,6 +119,103 @@ func (node *Node) countNumTransactionsInBlockchain() int { return count } +//ConnectIdentityChain connects to identity chain +func (node *Node) ConnectIdentityChain() { + IDCPeer := node.IDCPeer + p2p.SendMessage(IDCPeer, identity.ConstructIdentityMessage(identity.ANNOUNCE, node.SerializeWaitNode())) + return +} + +//NewWaitNode is a way to initiate a waiting no +func NewWaitNode(peer, IDCPeer p2p.Peer) Node { + node := Node{} + node.Self = peer + node.IDCPeer = IDCPeer + node.log = log.New() + return node +} + +//NewNodefromIDC +func NewNodefromIDC(node Node, consensus *consensus.Consensus, db *db.LDBDatabase) *Node { + + if consensus != nil { + // Consensus and associated channel to communicate blocks + node.Consensus = consensus + node.BlockChannel = make(chan blockchain.Block) + + // Genesis Block + // TODO(minh): Use or implement new function in blockchain package for this. + genesisBlock := &blockchain.Blockchain{} + genesisBlock.Blocks = make([]*blockchain.Block, 0) + // TODO(RJ): use miner's address as coinbase address + coinbaseTx := blockchain.NewCoinbaseTX(pki.GetAddressFromInt(1), "0", node.Consensus.ShardID) + genesisBlock.Blocks = append(genesisBlock.Blocks, blockchain.NewGenesisBlock(coinbaseTx, node.Consensus.ShardID)) + node.blockchain = genesisBlock + + // UTXO pool from Genesis block + //node.UtxoPool = blockchain.CreateUTXOPoolFromGenesisBlockChain(node.blockchain) + + // Initialize level db. + node.db = db + + } + // Logger + node.log = log.New() + + return &node +} + +func (node *Node) processPOWMessage(message []byte) { + payload, err := identity.GetIdentityMessagePayload(message) + if err != nil { + fmt.Println("Could not read payload") + } + IDCPeer := node.IDCPeer + // 4 byte challengeNonce id + req := string(payload) + fmt.Println(req) + proof, _ := pow.Fulfil(req, []byte("")) //"This could be blockhasdata" + buffer := bytes.NewBuffer([]byte{}) + proofBytes := make([]byte, 32) //proof seems to be 32 byte here + copy(proofBytes[:], proof) + buffer.Write(proofBytes) + buffer.Write(node.SerializeWaitNode()) + msgPayload := buffer.Bytes() + p2p.SendMessage(IDCPeer, identity.ConstructIdentityMessage(identity.REGISTER, msgPayload)) +} + +//https://stackoverflow.com/questions/12854125/how-do-i-dump-the-struct-into-the-byte-array-without-reflection/12854659#12854659 +//SerializeWaitNode serializes the node +func (node *Node) SerializeWaitNode() []byte { + //Needs to escape the serialization of unexported fields + result := new(bytes.Buffer) + encoder := gob.NewEncoder(result) + err := encoder.Encode(node.Self) + if err != nil { + fmt.Println("Could not serialize node") + fmt.Println("ERROR", err) + //node.log.Error("Could not serialize node") + } + err = encoder.Encode(node.IDCPeer) + return result.Bytes() +} + +// DeserializeWaitNode deserializes the node +func DeserializeWaitNode(d []byte) *Node { + var wn Node + r := bytes.NewBuffer(d) + decoder := gob.NewDecoder(r) + err := decoder.Decode(&wn.Self) + if err != nil { + log.Error("Could not de-serialize node") + } + err = decoder.Decode(&wn.IDCPeer) + if err != nil { + log.Error("Could not de-serialize node") + } + return &wn +} + // Create a new Node func New(consensus *consensus.Consensus, db *db.LDBDatabase) *Node { node := Node{} diff --git a/node/node_handler.go b/node/node_handler.go index 76433d8a2..faf2312d6 100644 --- a/node/node_handler.go +++ b/node/node_handler.go @@ -3,6 +3,7 @@ package node import ( "bytes" "encoding/gob" + "fmt" "net" "os" "strconv" @@ -13,6 +14,7 @@ import ( "github.com/simple-rules/harmony-benchmark/proto" "github.com/simple-rules/harmony-benchmark/proto/client" "github.com/simple-rules/harmony-benchmark/proto/consensus" + proto_identity "github.com/simple-rules/harmony-benchmark/proto/identity" proto_node "github.com/simple-rules/harmony-benchmark/proto/node" ) @@ -55,6 +57,19 @@ func (node *Node) NodeHandler(conn net.Conn) { } switch msgCategory { + case proto.IDENTITY: + actionType := proto_identity.IdentityMessageType(msgType) + switch actionType { + case proto_identity.IDENTITY: + messageType := proto_identity.MessageType(msgPayload[0]) + switch messageType { + case proto_identity.REGISTER: + fmt.Println("received a identity message") + node.processPOWMessage(msgPayload) + case proto_identity.ANNOUNCE: + node.log.Error("Announce message should be sent to IdentityChain") + } + } case proto.CONSENSUS: actionType := consensus.ConsensusMessageType(msgType) switch actionType { diff --git a/pow/LICENSE b/pow/LICENSE new file mode 100755 index 000000000..bd93e247d --- /dev/null +++ b/pow/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Bas Westerbaan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pow/README.md b/pow/README.md new file mode 100755 index 000000000..5f7c83c17 --- /dev/null +++ b/pow/README.md @@ -0,0 +1,57 @@ +go-pow +====== + +`go-pow` is a simple Go package to add (asymmetric) *Proof of Work* to your service. + +To create a Proof-of-Work request (with difficulty 5), use `pow.NewRequest`: + +```go +req := pow.NewRequest(5, someRandomNonce) +``` + +This returns a string like `sha2bday-5-c29tZSByYW5kb20gbm9uY2U`, +which can be passed on to the client. +The client fulfils the proof of work by running `pow.Fulfil`: + +```go +proof, _ := pow.Fulfil(req, []byte("some bound data")) +``` + +The client returns the proof (in this case `AAAAAAAAAAMAAAAAAAAADgAAAAAAAAAb`) +to the server, which can check it is indeed a valid proof of work, by running: + + +``` go +ok, _ := pow.Check(req, proof, []byte("some bound data")) +``` + +Notes +----- +1. There should be at least sufficient randomness in either the `nonce` passed to + `NewRequest` or the `data` passed to `Fulfil` and `Check`. + Thus it is fine to use the same bound `data` for every client, if every client + get a different `nonce` in its proof-of-work request. + It is also fine to use the same `nonce` in the proof-of-work request, + if every client is (by the encapsulating protocol) forced to use + different bound `data`. +2. The work to fulfil a request scales exponentially in the difficulty parameter. + The work to check it proof is correct remains constant: + + ``` + Check on Difficulty=5 500000 2544 ns/op + Check on Difficulty=10 500000 2561 ns/op + Check on Difficulty=15 500000 2549 ns/op + Check on Difficulty=20 500000 2525 ns/op + Fulfil on Difficulty=5 100000 15725 ns/op + Fulfil on Difficulty=10 30000 46808 ns/op + Fulfil on Difficulty=15 2000 955606 ns/op + Fulfil on Difficulty=20 200 6887722 ns/op + ``` + +To do +----- + + - Support for [equihash](https://www.cryptolux.org/index.php/Equihash) would be nice. + - Port to Python, Java, Javascript, ... + - Parallelize. + diff --git a/pow/api.go b/pow/api.go new file mode 100755 index 000000000..f4e5ba906 --- /dev/null +++ b/pow/api.go @@ -0,0 +1,131 @@ +// Create and fulfill proof of work requests. +package pow + +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" +) + +type Algorithm string + +const ( + Sha2BDay Algorithm = "sha2bday" +) + +// Represents a proof-of-work request. +type Request struct { + + // The requested algorithm + Alg Algorithm + + // The requested difficulty + Difficulty uint32 + + // Nonce to diversify the request + Nonce []byte +} + +// Represents a completed proof-of-work +type Proof struct { + buf []byte +} + +// Convenience function to create a new sha3bday proof-of-work request +// as a string +func NewRequest(difficulty uint32, nonce []byte) string { + req := Request{ + Difficulty: difficulty, + Nonce: nonce, + Alg: Sha2BDay, + } + s, _ := req.MarshalText() + return string(s) +} + +func (proof Proof) MarshalText() ([]byte, error) { + return []byte(base64.RawStdEncoding.EncodeToString(proof.buf)), nil +} + +func (proof *Proof) UnmarshalText(buf []byte) error { + var err error + proof.buf, err = base64.RawStdEncoding.DecodeString(string(buf)) + return err +} + +func (req Request) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("%s-%d-%s", + req.Alg, + req.Difficulty, + string(base64.RawStdEncoding.EncodeToString(req.Nonce)))), nil +} + +func (req *Request) UnmarshalText(buf []byte) error { + bits := strings.SplitN(string(buf), "-", 3) + if len(bits) != 3 { + return fmt.Errorf("There should be two dashes in a PoW request") + } + alg := Algorithm(bits[0]) + if alg != Sha2BDay { + return fmt.Errorf("%s: unsupported algorithm", bits[0]) + } + req.Alg = alg + diff, err := strconv.Atoi(bits[1]) + if err != nil { + return err + } + req.Difficulty = uint32(diff) + req.Nonce, err = base64.RawStdEncoding.DecodeString(bits[2]) + if err != nil { + return err + } + return nil +} + +// Convenience function to check whether a proof of work is fulfilled +func Check(request, proof string, data []byte) (bool, error) { + var req Request + var prf Proof + err := req.UnmarshalText([]byte(request)) + if err != nil { + return false, err + } + err = prf.UnmarshalText([]byte(proof)) + if err != nil { + return false, err + } + return prf.Check(req, data), nil +} + +// Fulfil the proof-of-work request. +func (req *Request) Fulfil(data []byte) Proof { + switch req.Alg { + case Sha2BDay: + return Proof{fulfilSha2BDay(req.Nonce, req.Difficulty, data)} + default: + panic("No such algorithm") + } +} + +// Convenience function to fulfil the proof of work request +func Fulfil(request string, data []byte) (string, error) { + var req Request + err := req.UnmarshalText([]byte(request)) + if err != nil { + return "", err + } + proof := req.Fulfil(data) + s, _ := proof.MarshalText() + return string(s), nil +} + +// Check whether the proof is ok +func (proof *Proof) Check(req Request, data []byte) bool { + switch req.Alg { + case Sha2BDay: + return checkSha2BDay(proof.buf, req.Nonce, data, req.Difficulty) + default: + panic("No such algorithm") + } +} diff --git a/pow/api_test.go b/pow/api_test.go new file mode 100755 index 000000000..2361288f7 --- /dev/null +++ b/pow/api_test.go @@ -0,0 +1,56 @@ +package pow + +import ( + "testing" +) + +func TestSha2BDay(t *testing.T) { + nonce := []byte{1, 2, 3, 4, 5} + data := []byte{2, 2, 3, 4, 5} + r := NewRequest(5, nonce) + proof, err := Fulfil(r, data) + if err != nil { + t.Fatalf("Fulfil: %v", err) + } + ok, err := Check(r, proof, data) + if err != nil { + t.Fatalf("Check: %v", err) + } + if !ok { + t.Fatalf("Proof of work should be ok") + } + ok, err = Check(r, proof, nonce) + if err != nil { + t.Fatalf("Check: %v", err) + } + if ok { + t.Fatalf("Proof of work should not be ok") + } +} + +func BenchmarkCheck5(b *testing.B) { benchmarkCheck(5, b) } +func BenchmarkCheck10(b *testing.B) { benchmarkCheck(10, b) } +func BenchmarkCheck15(b *testing.B) { benchmarkCheck(15, b) } +func BenchmarkCheck20(b *testing.B) { benchmarkCheck(20, b) } + +func benchmarkCheck(diff uint32, b *testing.B) { + req := NewRequest(diff, []byte{1, 2, 3, 4, 5}) + prf, _ := Fulfil(req, []byte{6, 7, 8, 9}) + b.ResetTimer() + for n := 0; n < b.N; n++ { + Check(req, prf, []byte{6, 7, 8, 9}) + } +} + +func BenchmarkFulfil5(b *testing.B) { benchmarkFulfil(5, b) } +func BenchmarkFulfil10(b *testing.B) { benchmarkFulfil(10, b) } +func BenchmarkFulfil15(b *testing.B) { benchmarkFulfil(15, b) } +func BenchmarkFulfil20(b *testing.B) { benchmarkFulfil(20, b) } + +func benchmarkFulfil(diff uint32, b *testing.B) { + req := NewRequest(diff, []byte{1, 2, 3, 4, 5}) + b.ResetTimer() + for n := 0; n < b.N; n++ { + Fulfil(req, []byte{6, 7, 8, 9}) + } +} diff --git a/pow/example_test.go b/pow/example_test.go new file mode 100755 index 000000000..cca46eb42 --- /dev/null +++ b/pow/example_test.go @@ -0,0 +1,25 @@ +package pow_test + +import ( + "fmt" // imported as pow + + "github.com/simple-rules/harmony-benchmark/pow" +) + +func Example() { + // Create a proof of work request with difficulty 5 + req := pow.NewRequest(5, []byte("some random nonce")) + fmt.Printf("req: %s\n", req) + + // Fulfil the proof of work + proof, _ := pow.Fulfil(req, []byte("some bound data")) + fmt.Printf("proof: %s\n", proof) + + // Check if the proof is correct + ok, _ := pow.Check(req, proof, []byte("some bound data")) + fmt.Printf("check: %v", ok) + + // Output: req: sha2bday-5-c29tZSByYW5kb20gbm9uY2U + // proof: AAAAAAAAAAMAAAAAAAAADgAAAAAAAAAb + // check: true +} diff --git a/pow/pow.go b/pow/pow.go deleted file mode 100644 index a8c31b3d5..000000000 --- a/pow/pow.go +++ /dev/null @@ -1,81 +0,0 @@ -package pow - -import ( - "bytes" - "crypto/sha256" - "encoding/binary" - "fmt" - "math" - "math/big" -) - -var ( - maxNonce = math.MaxUint32 -) - -const targetBits = 24 - -// ProofOfWork represents a proof-of-work -type ProofOfWork struct { - Challenge uint32 - target *big.Int - FinalNonce uint32 -} - -// NewProofOfWork builds and returns a ProofOfWork -func NewProofOfWork(c uint32) *ProofOfWork { - target := big.NewInt(1) - target.Lsh(target, uint(256-targetBits)) - - pow := &ProofOfWork{Challenge: c, target: target, FinalNonce: 0} - - return pow -} - -func (pow *ProofOfWork) prepareData(nonce uint32) []byte { - challenge := make([]byte, 4) - binary.LittleEndian.PutUint32(challenge, pow.Challenge) - nonceB := make([]byte, 4) - binary.LittleEndian.PutUint32(nonceB, nonce) - data := bytes.Join( - [][]byte{ - challenge, - nonceB, - }, - []byte{}, - ) - return data -} - -// Run performs a proof-of-work -func (pow *ProofOfWork) Run() int { - var hashInt big.Int - var hash [32]byte - nonce := 0 - for nonce < maxNonce { - data := pow.prepareData(uint32(nonce)) - - hash = sha256.Sum256(data) - fmt.Printf("\r%x", hash) - hashInt.SetBytes(hash[:]) - - if hashInt.Cmp(pow.target) == -1 { - pow.FinalNonce = uint32(nonce) - break - } else { - nonce++ - } - } - fmt.Print("\n\n") - return nonce -} - -// Validate validates block's PoW -func (pow *ProofOfWork) Validate(nonce uint32) bool { - var hashInt big.Int - data := pow.prepareData(nonce) - hash := sha256.Sum256(data) - hashInt.SetBytes(hash[:]) - isValid := hashInt.Cmp(pow.target) == -1 - return isValid -} diff --git a/pow/sha2bday.go b/pow/sha2bday.go new file mode 100755 index 000000000..4fdd03134 --- /dev/null +++ b/pow/sha2bday.go @@ -0,0 +1,78 @@ +package pow + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" +) + +func checkSha2BDay(proof []byte, nonce, data []byte, diff uint32) bool { + if len(proof) != 24 { + return false + } + prefix1 := proof[:8] + prefix2 := proof[8:16] + prefix3 := proof[16:] + if bytes.Equal(prefix1, prefix2) || bytes.Equal(prefix2, prefix3) || + bytes.Equal(prefix1, prefix3) { + return false + } + resBuf := make([]byte, 32) + h := sha256.New() + h.Write(prefix1) + h.Write(data) + h.Write(nonce) + h.Sum(resBuf[:0]) + res1 := binary.BigEndian.Uint64(resBuf) & ((1 << diff) - 1) + h.Reset() + h.Write(prefix2) + h.Write(data) + h.Write(nonce) + h.Sum(resBuf[:0]) + res2 := binary.BigEndian.Uint64(resBuf) & ((1 << diff) - 1) + h.Reset() + h.Write(prefix3) + h.Write(data) + h.Write(nonce) + h.Sum(resBuf[:0]) + res3 := binary.BigEndian.Uint64(resBuf) & ((1 << diff) - 1) + return res1 == res2 && res2 == res3 +} + +func fulfilSha2BDay(nonce []byte, diff uint32, data []byte) []byte { + // TODO make multithreaded if the difficulty is high enough. + // For light proof-of-work requests, the overhead of parallelizing is + // not worth it. + type Pair struct { + First, Second uint64 + } + var i uint64 = 1 + prefix := make([]byte, 8) + resBuf := make([]byte, 32) + lut := make(map[uint64]Pair) + h := sha256.New() + for { + binary.BigEndian.PutUint64(prefix, i) + h.Write(prefix) + h.Write(data) + h.Write(nonce) + h.Sum(resBuf[:0]) + res := binary.BigEndian.Uint64(resBuf) & ((1 << diff) - 1) + pair, ok := lut[res] + if ok { + if pair.Second != 0 { + ret := make([]byte, 24) + binary.BigEndian.PutUint64(ret, pair.First) + binary.BigEndian.PutUint64(ret[8:], pair.Second) + copy(ret[16:], prefix) + return ret + } + + lut[res] = Pair{First: pair.First, Second: i} + } else { + lut[res] = Pair{First: i} + } + h.Reset() + i++ + } +} diff --git a/proto/identity/identity.go b/proto/identity/identity.go index 5458f348c..a3ab381f9 100644 --- a/proto/identity/identity.go +++ b/proto/identity/identity.go @@ -21,15 +21,17 @@ type MessageType int const ( REGISTER MessageType = iota + ANNOUNCE ) // Returns string name for the MessageType enum func (msgType MessageType) String() string { names := [...]string{ "REGISTER", + "ANNOUNCE", } - if msgType < REGISTER || msgType > REGISTER { + if msgType < REGISTER || msgType > ANNOUNCE { return "Unknown" } return names[msgType] diff --git a/runwait/run_wait.go b/runwait/run_wait.go index b2a953adb..62431c679 100644 --- a/runwait/run_wait.go +++ b/runwait/run_wait.go @@ -1,21 +1,25 @@ package main -import "fmt" +import ( + "flag" + "fmt" -// import ( -// "flag" - -// "github.com/simple-rules/harmony-benchmark/p2p" -// "github.com/simple-rules/harmony-benchmark/waitnode" -// ) + "github.com/simple-rules/harmony-benchmark/node" + "github.com/simple-rules/harmony-benchmark/p2p" +) func main() { - fmt.Println("hello") - // ip := flag.String("ip", "127.0.0.0", "IP of the node") - // port := flag.String("port", "8080", "port of the node") - // flag.Parse() - // peer := p2p.Peer{Ip: *ip, Port: *port} - // idcpeer := p2p.Peer{Ip: "localhost", Port: "9000"} //Hardcoded here. - // node := waitnode.New(peer) - // node.ConnectIdentityChain(idcpeer) + ip := flag.String("ip", "localhost", "IP of the node") + port := flag.String("port", "8080", "port of the node") + flag.Parse() + peer := p2p.Peer{Ip: *ip, Port: *port} + idcpeer := p2p.Peer{Ip: "localhost", Port: "9000"} //Hardcoded here. + node := node.NewWaitNode(peer, idcpeer) + go func() { + node.ConnectIdentityChain() + }() + fmt.Println("control is back with me") + node.StartServer(*port) + fmt.Println("starting the server") + } diff --git a/waitnode/wait_node.go b/waitnode/wait_node.go deleted file mode 100644 index c66cbe2c9..000000000 --- a/waitnode/wait_node.go +++ /dev/null @@ -1,82 +0,0 @@ -package waitnode - -import ( - "bytes" - "crypto/sha256" - "encoding/gob" - "log" - - "github.com/simple-rules/harmony-benchmark/p2p" - "github.com/simple-rules/harmony-benchmark/utils" -) - -//WaitNode is for nodes waiting to join consensus -type WaitNode struct { - Peer p2p.Peer - ID uint16 - SeedPeers p2p.Peer -} - -// StartServer a server and process the request by a handler. -func (node *WaitNode) StartServer() { - log.Printf("Starting waitnode on server %s and port %s", node.Peer.Ip, node.Peer.Port) -} - -// //ConnectIdentityChain connects to identity chain -// func (node *WaitNode) ConnectIdentityChain(peer p2p.Peer) { -// pow := NewProofOfWork(10) -// nonce := pow.Run() -// if pow.FinalNonce != uint32(nonce) { -// fmt.Println("Something wrong with POW") -// } -// p2p.SendMessage(peer, identity.ConstructIdentityMessage(identity.REGISTER, node.SerializeWaitNode())) -// } - -//Constructs node-id by hashing the IP. -func calculateHash(num string) []byte { - var hashes [][]byte - hashes = append(hashes, utils.ConvertFixedDataIntoByteArray(num)) - hash := sha256.Sum256(bytes.Join(hashes, []byte{})) - return hash[:] -} - -// //SerializePOW serializes the node -// func SerializePOW(pow ProofOfWork) []byte { -// var result bytes.Buffer -// encoder := gob.NewEncoder(&pow) -// err := encoder.Encode(pow) -// if err != nil { -// log.Panic(err.Error()) -// } -// return pow.Bytes() -// } - -//SerializeWaitNode serializes the node -func (node *WaitNode) SerializeWaitNode() []byte { - var result bytes.Buffer - encoder := gob.NewEncoder(&result) - err := encoder.Encode(node) - if err != nil { - log.Panic(err.Error()) - } - return result.Bytes() -} - -// DeserializeWaitNode deserializes the node -func DeserializeWaitNode(d []byte) *WaitNode { - var wn WaitNode - decoder := gob.NewDecoder(bytes.NewReader(d)) - err := decoder.Decode(&wn) - if err != nil { - log.Panic(err) - } - return &wn -} - -// New Create a new Node -func New(Peer p2p.Peer) *WaitNode { - node := WaitNode{} - node.Peer = Peer - node.ID = utils.GetUniqueIdFromPeer(Peer) - return &node -} diff --git a/waitnode/wait_node_test.go b/waitnode/wait_node_test.go deleted file mode 100644 index b7f89ef7d..000000000 --- a/waitnode/wait_node_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package waitnode - -import ( - "testing" - - "github.com/simple-rules/harmony-benchmark/p2p" -) - -func TestNewNode(test *testing.T) { - p := p2p.Peer{Ip: "127.0.0.1", Port: "8080"} - wn := New(p) - b := wn.SerializeWaitNode() - wnd := DeserializeWaitNode(b) - if *wn != *wnd { - test.Error("Serialization is not working") - } - -}