From 6adcba16e8d6de0db9751e85b2834c2ab9caecc3 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sat, 20 May 2017 14:24:09 -0700 Subject: [PATCH 1/5] Better testing for `blockchain` (#23) * Update Marhsal functions and rewrite Wallet to be easier to use' * update travis to use glide * Override install phase in travis Add tests * add another test for blockchain encoding and refactor hashing * Fix wallet sign * small change to marshal func * Add makefile * fix getting deps for travis * oops --- .travis.yml | 3 + Makefile | 18 ++++++ README.md | 16 ++---- blockchain/block.go | 61 +++++++++++++-------- blockchain/block_test.go | 19 +++++++ blockchain/blockchain.go | 35 +++++++++--- blockchain/blockchain_test.go | 19 +++++++ blockchain/hash.go | 28 ++++++++++ blockchain/test_utils.go | 90 ++++++++++++++++++++++++++++++ blockchain/transaction.go | 67 +++++++++++++---------- blockchain/wallet.go | 100 ++++++++++++++++++++++++---------- scripts/install_glide.sh | 8 +++ 12 files changed, 364 insertions(+), 100 deletions(-) create mode 100644 Makefile create mode 100644 blockchain/block_test.go create mode 100644 blockchain/blockchain_test.go create mode 100644 blockchain/hash.go create mode 100644 blockchain/test_utils.go create mode 100644 scripts/install_glide.sh diff --git a/.travis.yml b/.travis.yml index afe9f4e..d500570 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,8 @@ go: - 1.7 before_install: - go get github.com/mattn/goveralls +install: + - make install-glide + - make deps script: - $HOME/gopath/bin/goveralls -service=travis-ci diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..53978bf --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +PACKAGES = `go list ./... | grep -v vendor/` + +all: cumulus + +cumulus: + go build + +test: + go test $(PACKAGES) + +deps: + glide install + +clean: + rm cumulus + +install-glide: + sh scripts/install_glide.sh diff --git a/README.md b/README.md index 985c3c0..6763433 100644 --- a/README.md +++ b/README.md @@ -12,25 +12,21 @@ Install dependencies. We need to manually use version 0.11.1 of Glide temporaril Install Glide. ```sh -go get github.com/Masterminds/glide -cd $GOPATH/src/github.com/Masterminds/glide -git checkout tags/v0.11.1 -go install +make install-glide ``` -Verify you have the correct version installed. +Get dependencies. ```sh -glide --version +make deps ``` -Get dependencies. +Build. ```sh -cd $GOPATH/src/github.com/ubclaunchpad/cumulus -glide install +make ``` ## Testing ``` -go test ./... +make test ``` diff --git a/blockchain/block.go b/blockchain/block.go index 11eb130..7ebd69e 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -2,40 +2,55 @@ package blockchain // BlockHeader contains metadata about a block import ( - "crypto/sha256" "encoding/binary" "encoding/gob" + "fmt" "io" ) +const ( + // BlockSize is the maximum size of a block in bytes when marshaled (about 250K). + BlockSize = 1 << 18 + // BlockHeaderLen is the length in bytes of a block header. + BlockHeaderLen = 32/8 + HashLen + AddrLen +) + // BlockHeader contains metadata about a block type BlockHeader struct { - blockNumber uint32 - lastBlock Hash - miner Wallet + BlockNumber uint32 + LastBlock Hash + Miner Address } // Marshal converts a BlockHeader to a byte slice func (bh *BlockHeader) Marshal() []byte { - buf := []byte{} - binary.LittleEndian.PutUint32(buf, bh.blockNumber) - for _, b := range bh.lastBlock { - buf = append(buf, b) - } - buf = append(buf, bh.miner.Marshal()...) + buf := make([]byte, 4, BlockHeaderLen) + binary.LittleEndian.PutUint32(buf, bh.BlockNumber) + buf = append(buf, bh.LastBlock.Marshal()...) + buf = append(buf, bh.Miner.Marshal()...) return buf } // Block represents a block in the blockchain. Contains transactions and header metadata. type Block struct { BlockHeader - transactions []*Transaction + Transactions []*Transaction } -// Marshal converts a Block to a byte slice +// Len returns the length in bytes of the Block. +func (b *Block) Len() int { + l := BlockHeaderLen + for _, t := range b.Transactions { + l += t.Len() + } + return l +} + +// Marshal converts a Block to a byte slice. func (b *Block) Marshal() []byte { - buf := b.BlockHeader.Marshal() - for _, t := range b.transactions { + buf := make([]byte, 0, b.Len()) + buf = append(buf, b.BlockHeader.Marshal()...) + for _, t := range b.Transactions { buf = append(buf, t.Marshal()...) } return buf @@ -43,15 +58,15 @@ func (b *Block) Marshal() []byte { // Encode writes the marshalled block to the given io.Writer func (b *Block) Encode(w io.Writer) { - gob.NewEncoder(w).Encode(b) -} - -// Decode reads the marshalled block from the given io.Reader -func (b *Block) Decode(r io.Reader) { - gob.NewDecoder(r).Decode(b) + err := gob.NewEncoder(w).Encode(b) + if err != nil { + fmt.Println(err.Error()) + } } -// Hash computes and returns the SHA256 hash of the block -func (b *Block) Hash() Hash { - return sha256.Sum256(b.Marshal()) +// DecodeBlock reads the marshalled block from the given io.Reader and populates b +func DecodeBlock(r io.Reader) *Block { + var b Block + gob.NewDecoder(r).Decode(&b) + return &b } diff --git a/blockchain/block_test.go b/blockchain/block_test.go new file mode 100644 index 0000000..b420394 --- /dev/null +++ b/blockchain/block_test.go @@ -0,0 +1,19 @@ +package blockchain + +import ( + "bytes" + "testing" +) + +func TestEncodeDecodeBlock(t *testing.T) { + b1 := newBlock() + + buf := bytes.NewBuffer(make([]byte, 0, b1.Len())) + + b1.Encode(buf) + b2 := DecodeBlock(buf) + + if HashSum(b1) != HashSum(b2) { + t.Fail() + } +} diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 4ce12c7..8c69801 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -5,13 +5,28 @@ import ( "io" ) -// Hash represents a 256-bit hash of a block or transaction -type Hash [32]byte - // BlockChain represents a linked list of blocks type BlockChain struct { - blocks []*Block - head Hash + Blocks []*Block + Head Hash +} + +// Len returns the length of the BlockChain when marshalled +func (bc *BlockChain) Len() int { + l := 0 + for _, b := range bc.Blocks { + l += b.Len() + } + return l + HashLen +} + +// Marshal converts the BlockChain to a byte slice. +func (bc *BlockChain) Marshal() []byte { + buf := make([]byte, 0, bc.Len()) + for _, b := range bc.Blocks { + buf = append(buf, b.Marshal()...) + } + return append(buf, bc.Head.Marshal()...) } // Encode writes the marshalled blockchain to the given io.Writer @@ -19,9 +34,11 @@ func (bc *BlockChain) Encode(w io.Writer) { gob.NewEncoder(w).Encode(bc) } -// Decode reads the marshalled blockchain from the given io.Reader -func (bc *BlockChain) Decode(r io.Reader) { - gob.NewDecoder(r).Decode(bc) +// DecodeBlockChain reads the marshalled blockchain from the given io.Reader +func DecodeBlockChain(r io.Reader) *BlockChain { + var bc BlockChain + gob.NewDecoder(r).Decode(&bc) + return &bc } // ValidTransaction checks whether a transaction is valid, assuming the blockchain is valid. @@ -34,7 +51,7 @@ func (bc *BlockChain) ValidTransaction(t *Transaction) bool { // ValidBlock checks whether a block is valid func (bc *BlockChain) ValidBlock(b *Block) bool { - for _, t := range b.transactions { + for _, t := range b.Transactions { if !bc.ValidTransaction(t) { return false } diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go new file mode 100644 index 0000000..88faf07 --- /dev/null +++ b/blockchain/blockchain_test.go @@ -0,0 +1,19 @@ +package blockchain + +import ( + "bytes" + "testing" +) + +func TestEncodeDecodeBlockChain(t *testing.T) { + b1 := newBlockChain() + + buf := bytes.NewBuffer(make([]byte, 0, b1.Len())) + + b1.Encode(buf) + b2 := DecodeBlockChain(buf) + + if HashSum(b1) != HashSum(b2) { + t.Fail() + } +} diff --git a/blockchain/hash.go b/blockchain/hash.go new file mode 100644 index 0000000..9f6839c --- /dev/null +++ b/blockchain/hash.go @@ -0,0 +1,28 @@ +package blockchain + +import "crypto/sha256" + +// HashLen is the length in bytes of a hash. +const HashLen = 32 + +// Hash represents a 256-bit hash of a block or transaction +type Hash [HashLen]byte + +// Marshal converts a Hash to a slice. +func (h Hash) Marshal() []byte { + buf := make([]byte, HashLen) + for i, b := range h { + buf[i] = b + } + return buf +} + +// Marshaller is any type that can convert itself to a byte slice +type Marshaller interface { + Marshal() []byte +} + +// HashSum computes the SHA256 hash of a Marshaller. +func HashSum(m Marshaller) Hash { + return sha256.Sum256(m.Marshal()) +} diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go new file mode 100644 index 0000000..e5e249b --- /dev/null +++ b/blockchain/test_utils.go @@ -0,0 +1,90 @@ +package blockchain + +import ( + "crypto/ecdsa" + crand "crypto/rand" + "crypto/sha256" + mrand "math/rand" +) + +func newHash() Hash { + message := make([]byte, 256) + crand.Read(message) + return sha256.Sum256(message) +} + +func newWallet() Wallet { + priv, _ := ecdsa.GenerateKey(curve, crand.Reader) + return (*wallet)(priv) +} + +func newTxHashPointer() TxHashPointer { + return TxHashPointer{ + BlockNumber: mrand.Uint32(), + Hash: newHash(), + } +} + +func newTxOutput() TxOutput { + return TxOutput{ + Amount: uint64(mrand.Int63()), + Recipient: newWallet().Public(), + } +} + +func newTxBody() TxBody { + // Uniform distribution on [1, 4] + nOutputs := mrand.Intn(4) + 1 + body := TxBody{ + Sender: newWallet().Public(), + Input: newTxHashPointer(), + Outputs: make([]TxOutput, nOutputs), + } + for i := 0; i < nOutputs; i++ { + body.Outputs[i] = newTxOutput() + } + return body +} + +func newTransaction() *Transaction { + sender := newWallet() + tbody := newTxBody() + digest := HashSum(tbody) + sig, _ := sender.Sign(digest, crand.Reader) + return &Transaction{ + TxBody: tbody, + Sig: sig, + } +} + +func newBlockHeader() BlockHeader { + return BlockHeader{ + BlockNumber: mrand.Uint32(), + LastBlock: newHash(), + Miner: newWallet().Public(), + } +} + +func newBlock() *Block { + // Uniform distribution on [500, 999] + nTransactions := mrand.Intn(500) + 500 + b := Block{ + BlockHeader: newBlockHeader(), + Transactions: make([]*Transaction, nTransactions), + } + for i := 0; i < nTransactions; i++ { + b.Transactions[i] = newTransaction() + } + return &b +} + +func newBlockChain() *BlockChain { + // Uniform distribution on [10, 50] + nBlocks := mrand.Intn(40) + 10 + bc := BlockChain{Blocks: make([]*Block, nBlocks)} + for i := 0; i < nBlocks; i++ { + bc.Blocks[i] = newBlock() + } + bc.Head = HashSum(bc.Blocks[nBlocks-1]) + return &bc +} diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 74a406c..507e370 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -1,52 +1,60 @@ package blockchain -import ( - "crypto/sha256" - "encoding/binary" +import "encoding/binary" + +const ( + // TxHashPointerLen is the length in bytes of a hash pointer. + TxHashPointerLen = 32/8 + HashLen + // TxOutputLen is the length in bytes of a transaction output. + TxOutputLen = 64/8 + AddrLen ) // TxHashPointer is a reference to a transaction on the blockchain. type TxHashPointer struct { - blockNumber uint32 - hash Hash + BlockNumber uint32 + Hash Hash } // Marshal converts a TxHashPointer to a byte slice func (thp TxHashPointer) Marshal() []byte { - buf := []byte{} - binary.LittleEndian.PutUint32(buf, thp.blockNumber) - for _, b := range thp.hash { - buf = append(buf, b) - } + buf := make([]byte, 4, TxHashPointerLen) + binary.LittleEndian.PutUint32(buf, thp.BlockNumber) + buf = append(buf, thp.Hash.Marshal()...) return buf } // TxOutput defines an output to a transaction type TxOutput struct { - amount uint64 - recipient Wallet + Amount uint64 + Recipient Address } // Marshal converts a TxOutput to a byte slice func (to TxOutput) Marshal() []byte { - buf := []byte{} - binary.LittleEndian.PutUint64(buf, to.amount) - buf = append(buf, to.recipient.Marshal()...) + buf := make([]byte, 8, TxOutputLen) + binary.LittleEndian.PutUint64(buf, to.Amount) + buf = append(buf, to.Recipient.Marshal()...) return buf } // TxBody contains all relevant information about a transaction type TxBody struct { - sender Wallet - input TxHashPointer - outputs []TxOutput + Sender Address + Input TxHashPointer + Outputs []TxOutput +} + +// Len returns the length of a transaction body +func (tb TxBody) Len() int { + return AddrLen + TxHashPointerLen + len(tb.Outputs)*TxOutputLen } // Marshal converts a TxBody to a byte slice func (tb TxBody) Marshal() []byte { - buf := tb.sender.Marshal() - buf = append(buf, tb.input.Marshal()...) - for _, out := range tb.outputs { + buf := make([]byte, 0, tb.Len()) + buf = append(buf, tb.Sender.Marshal()...) + buf = append(buf, tb.Input.Marshal()...) + for _, out := range tb.Outputs { buf = append(buf, out.Marshal()...) } return buf @@ -55,17 +63,18 @@ func (tb TxBody) Marshal() []byte { // Transaction contains a TxBody and a signature verifying it type Transaction struct { TxBody - sig Signature + Sig Signature +} + +// Len returns the length in bytes of a transaction +func (t *Transaction) Len() int { + return t.TxBody.Len() + SigLen } // Marshal converts a Transaction to a byte slice func (t *Transaction) Marshal() []byte { - buf := t.TxBody.Marshal() - buf = append(buf, t.sig.Marshal()...) + buf := make([]byte, 0, t.Len()) + buf = append(buf, t.TxBody.Marshal()...) + buf = append(buf, t.Sig.Marshal()...) return buf } - -// Hash returns the SHA256 hash of a transaction -func (t *Transaction) Hash() Hash { - return sha256.Sum256(t.Marshal()) -} diff --git a/blockchain/wallet.go b/blockchain/wallet.go index b99bfca..c7ac756 100644 --- a/blockchain/wallet.go +++ b/blockchain/wallet.go @@ -3,49 +3,91 @@ package blockchain import ( "crypto/ecdsa" "crypto/elliptic" - "crypto/rand" - "fmt" + "io" "math/big" ) -// The curve we use for our ECC crypto. -var curve = elliptic.P256() +const ( + // CoordLen is the length in bytes of coordinates with our ECC curve. + CoordLen = 32 + // AddrLen is the length in bytes of addresses. + AddrLen = 2 * CoordLen + // SigLen is the length in bytes of signatures. + SigLen = AddrLen +) -// Wallet represents a Cumulus wallet address in the blockchain. -type Wallet ecdsa.PublicKey +var ( + // The curve we use for our ECC crypto. + curve = elliptic.P256() + // NilSig is a signature representing a failed Sign operation + NilSig = Signature{big.NewInt(0), big.NewInt(0)} + // NilAddr is an address representing no address + NilAddr = Address{} +) -// Signature represents a signature of a transaction. -type Signature struct { - X big.Int - Y big.Int +// Address represents a wallet that can be a recipient in a transaction. +type Address [AddrLen]byte + +// Marshal converts an Address to a byte slice. +func (a Address) Marshal() []byte { + buf := make([]byte, AddrLen) + for i, b := range a { + buf[i] = b + } + return buf } -// Marshal converts a signature to a byte slice -func (s *Signature) Marshal() []byte { - return append(s.X.Bytes(), s.Y.Bytes()...) +// Wallet represents a wallet that we have the ability to sign for. +type Wallet interface { + Public() Address + Sign(digest Hash, random io.Reader) (Signature, error) +} + +// Internal representation of a wallet. +type wallet ecdsa.PrivateKey + +// Key retreives the underlying private key from a wallet. +func (w *wallet) key() *ecdsa.PrivateKey { + return (*ecdsa.PrivateKey)(w) } -// New creates a new Wallet backed by a ECC key pair. Uses system entropy. -func newWallet() (*Wallet, error) { - k, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - return nil, err +// Public returns the public key as byte array, or address, of the wallet. +func (w *wallet) Public() Address { + addr := Address{} + x := w.PublicKey.X.Bytes() + y := w.PublicKey.Y.Bytes() + + if len(x) != CoordLen || len(y) != CoordLen { + // Invalid wallet + return NilAddr + } + + for i, b := range x { + addr[i] = b + } + for i, b := range y { + addr[CoordLen+i] = b } - pk := Wallet(k.PublicKey) - return &pk, nil + + return addr } -// String returns a human-readable string representation of a wallet -func (w *Wallet) String() string { - return fmt.Sprintf("%x-%x", w.X, w.Y) +// Sign returns a signature of the digest. +func (w *wallet) Sign(digest Hash, random io.Reader) (Signature, error) { + r, s, err := ecdsa.Sign(random, w.key(), digest.Marshal()) + return Signature{r, s}, err } -// Marshal converts the Wallet to a byte slice -func (w *Wallet) Marshal() []byte { - return elliptic.Marshal(curve, w.X, w.Y) +// Signature represents a signature of a transaction. +type Signature struct { + R *big.Int + S *big.Int } -// Equals checks whether two wallets are the same. -func (w *Wallet) Equals(other *Wallet) bool { - return w.X.Cmp(other.X) == 0 && w.Y.Cmp(other.Y) == 0 +// Marshal converts a signature to a byte slice. Should be 64 bytes long. +func (s *Signature) Marshal() []byte { + buf := make([]byte, 0, SigLen) + buf = append(buf, s.R.Bytes()...) + buf = append(buf, s.S.Bytes()...) + return buf } diff --git a/scripts/install_glide.sh b/scripts/install_glide.sh new file mode 100644 index 0000000..55abfae --- /dev/null +++ b/scripts/install_glide.sh @@ -0,0 +1,8 @@ +go get github.com/Masterminds/glide +cd $GOPATH/src/github.com/Masterminds/glide +git checkout tags/v0.11.1 +go install + +glide --version + +cd $GOPATH/src/github.com/ubclaunchpad/cumulus From dcaafba5e835f3870f050f79f12284def77e4f13 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sat, 20 May 2017 15:34:37 -0700 Subject: [PATCH 2/5] Update wallet (#37) * Update Marhsal functions and rewrite Wallet to be easier to use' * update travis to use glide * Override install phase in travis Add tests * add another test for blockchain encoding and refactor hashing * Fix wallet sign * small change to marshal func * Add makefile * fix getting deps for travis * oops * modify the way addresses work slightly --- blockchain/wallet.go | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/blockchain/wallet.go b/blockchain/wallet.go index c7ac756..3888e5e 100644 --- a/blockchain/wallet.go +++ b/blockchain/wallet.go @@ -26,17 +26,27 @@ var ( ) // Address represents a wallet that can be a recipient in a transaction. -type Address [AddrLen]byte +type Address struct { + X, Y *big.Int +} // Marshal converts an Address to a byte slice. func (a Address) Marshal() []byte { buf := make([]byte, AddrLen) - for i, b := range a { - buf[i] = b - } + buf = append(buf, a.X.Bytes()...) + buf = append(buf, a.Y.Bytes()...) return buf } +// Key returns the ECDSA public key representation of the address. +func (a Address) Key() *ecdsa.PublicKey { + return &ecdsa.PublicKey{ + Curve: curve, + X: a.X, + Y: a.X, + } +} + // Wallet represents a wallet that we have the ability to sign for. type Wallet interface { Public() Address @@ -53,23 +63,7 @@ func (w *wallet) key() *ecdsa.PrivateKey { // Public returns the public key as byte array, or address, of the wallet. func (w *wallet) Public() Address { - addr := Address{} - x := w.PublicKey.X.Bytes() - y := w.PublicKey.Y.Bytes() - - if len(x) != CoordLen || len(y) != CoordLen { - // Invalid wallet - return NilAddr - } - - for i, b := range x { - addr[i] = b - } - for i, b := range y { - addr[CoordLen+i] = b - } - - return addr + return Address{X: w.PublicKey.X, Y: w.PublicKey.Y} } // Sign returns a signature of the digest. From c5a326ac34b341429cd8e0a6f9aa73a84fd13715 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sun, 21 May 2017 16:55:49 -0700 Subject: [PATCH 3/5] Create message types and structs --- glide.lock | 2 +- glide.yaml | 2 +- main.go | 2 +- message/message.go | 111 +++++++++++++++++++++++---------------------- peer/peer.go | 2 +- subnet/subnet.go | 2 +- 6 files changed, 62 insertions(+), 59 deletions(-) diff --git a/glide.lock b/glide.lock index 06bc085..9522746 100644 --- a/glide.lock +++ b/glide.lock @@ -135,7 +135,7 @@ imports: version: b8f1996688ab586031517919b49b1967fca8d5d9 - name: github.com/satori/go.uuid version: 5bf94b69c6b68ee1b541973bb8e1144db23a194b -- name: github.com/sirupsen/logrus +- name: github.com/Sirupsen/logrus version: acfabf31db8f45a9174f54a0d48ea4d15627af4d - name: github.com/spaolacci/murmur3 version: 0d12bf811670bf6a1a63828dfbd003eded177fce diff --git a/glide.yaml b/glide.yaml index da8a2c7..6b0b13f 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,4 +10,4 @@ import: subpackages: - p2p/host/basic - package: github.com/multiformats/go-multiaddr -- package: github.com/sirupsen/logrus +- package: github.com/Sirupsen/logrus diff --git a/main.go b/main.go index cabbb40..44b7ecc 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "flag" "io/ioutil" - log "github.com/sirupsen/logrus" + log "github.com/Sirupsen/logrus" "github.com/ubclaunchpad/cumulus/peer" ) diff --git a/message/message.go b/message/message.go index 11227bc..685e880 100644 --- a/message/message.go +++ b/message/message.go @@ -1,73 +1,76 @@ package message import ( - "encoding/json" - "errors" - "strings" + "encoding/gob" + "io" +) + +type ( + // Type specifies the type of a message. + Type int + // ResourceType specifies the type of a resource in a message. + ResourceType int +) + +const ( + // MessageRequest messages ask a peer for a resource. + MessageRequest Type = iota + // MessageResponse messages repond to a request message with an error or a resource. + MessageResponse + // MessagePush messages proactively send a resource to a peer. + MessagePush ) -// Message types -// NOTE: because of the way iota works, changing the order in which the -// following constants appear will change their values, which may affect the -// ability of your peer to communicate with others. const ( - // Send the multiaddress of a peer to another peer - PeerInfo = iota - // Request addressess of peers in the remote peer's subnet - RequestPeerInfo = iota - // Send information about a block that was just hashed - NewBlock = iota - // Request chunk of the blockchain from peer - RequestChunk = iota - // Advertise that we have a chunk of the blockchain - AdvertiseChunk = iota - // Send information about a new transaction to another peer - Transaction = iota + // ResourcePeerInfo resources contain a list of peers. + ResourcePeerInfo ResourceType = iota + // ResourceBlock resources contain a block in the blockchain. + ResourceBlock + // ResourceTransaction resources contain a transaction to add to the blockchain. + ResourceTransaction ) -// Message is a container for information and its type that is -// sent between Cumulus peers. +// Message is a container for messages, containing a type and either a Request, +// Response, or Push in the payload. type Message struct { - msgType int - content []byte + Type Type + Payload interface{} } -// New returns a pointer to a message initialized with a byte array -// of content and a message type, or an error if the type is not one -// of those defined above. -func New(c []byte, t int) (*Message, error) { - switch t { - case PeerInfo: - case NewBlock: - case RequestChunk: - case AdvertiseChunk: - case Transaction: - break - default: - return nil, errors.New("Invalid message type") - } +// Request is a container for a request payload, containing a unique request ID, +// the resource type we are requesting, and a Params field specifying request +// parameters. PeerInfo requests should send all info of all peers. Block requests +// should specify block number in parameters. +type Request struct { + ID string + ResourceType ResourceType + Params map[string]interface{} +} - m := &Message{msgType: t, content: c} - return m, nil +// Response is a container for a response payload, containing the unique request +// ID of the request prompting it, an Error (if one occurred), and the requested +// resource (if no error occurred). +type Response struct { + ID string + Error error + Resource interface{} } -// Bytes returns JSON representation of message as a byte array, or error if -// message cannot be marshalled. -func (m *Message) Bytes() ([]byte, error) { - return json.Marshal(m) +// Push is a container for a push payload, containing a resource proactively sent +// to us by another peer. +type Push struct { + ResourceType ResourceType + Resource interface{} } -// FromString parses a message in the form of a string and returns a pointer -// to a new Message struct made from the contents of the string. Returns error -// if string is malformed. -func FromString(s string) (*Message, error) { - var msg Message - s = strings.TrimSpace(s) - err := json.Unmarshal([]byte(s), &msg) - return &msg, err +// Encode encodes and writes the Message into the given Writer. +func (m *Message) Encode(w io.Writer) error { + return gob.NewEncoder(w).Encode(m) } -// Type returns msgType for message -func (m *Message) Type() int { - return m.msgType +// Decode decodes a message from a Reader and returns it. +func Decode(r io.Reader) (*Message, error) { + var m Message + err := gob.NewDecoder(r).Decode(&m) + return &m, err } diff --git a/peer/peer.go b/peer/peer.go index 0189912..c03634e 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" + log "github.com/Sirupsen/logrus" crypto "github.com/libp2p/go-libp2p-crypto" host "github.com/libp2p/go-libp2p-host" net "github.com/libp2p/go-libp2p-net" @@ -14,7 +15,6 @@ import ( swarm "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" - log "github.com/sirupsen/logrus" msg "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) diff --git a/subnet/subnet.go b/subnet/subnet.go index e289e3e..30e5b0f 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -3,9 +3,9 @@ package subnet import ( "errors" + log "github.com/Sirupsen/logrus" net "github.com/libp2p/go-libp2p-net" ma "github.com/multiformats/go-multiaddr" - log "github.com/sirupsen/logrus" ) const ( From d3d17b4227c15a8a5b7dd6568b60d415ccb3bdc6 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sun, 21 May 2017 17:27:14 -0700 Subject: [PATCH 4/5] Update peer package to use new message structure --- message/message.go | 16 +++++++++---- peer/peer.go | 60 +++++++++++++++++----------------------------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/message/message.go b/message/message.go index 685e880..f2fd34a 100644 --- a/message/message.go +++ b/message/message.go @@ -37,6 +37,14 @@ type Message struct { Payload interface{} } +// New returns a new Message. +func New(t Type, payload interface{}) *Message { + return &Message{ + Type: t, + Payload: payload, + } +} + // Request is a container for a request payload, containing a unique request ID, // the resource type we are requesting, and a Params field specifying request // parameters. PeerInfo requests should send all info of all peers. Block requests @@ -63,13 +71,13 @@ type Push struct { Resource interface{} } -// Encode encodes and writes the Message into the given Writer. -func (m *Message) Encode(w io.Writer) error { +// Write encodes and writes the Message into the given Writer. +func (m *Message) Write(w io.Writer) error { return gob.NewEncoder(w).Encode(m) } -// Decode decodes a message from a Reader and returns it. -func Decode(r io.Reader) (*Message, error) { +// Read decodes a message from a Reader and returns it. +func Read(r io.Reader) (*Message, error) { var m Message err := gob.NewDecoder(r).Decode(&m) return &m, err diff --git a/peer/peer.go b/peer/peer.go index c03634e..b65aa99 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1,21 +1,21 @@ package peer import ( - "bufio" "context" "errors" "fmt" + "time" log "github.com/Sirupsen/logrus" - crypto "github.com/libp2p/go-libp2p-crypto" - host "github.com/libp2p/go-libp2p-host" - net "github.com/libp2p/go-libp2p-net" + "github.com/libp2p/go-libp2p-crypto" + "github.com/libp2p/go-libp2p-host" + "github.com/libp2p/go-libp2p-net" lpeer "github.com/libp2p/go-libp2p-peer" pstore "github.com/libp2p/go-libp2p-peerstore" - swarm "github.com/libp2p/go-libp2p-swarm" + "github.com/libp2p/go-libp2p-swarm" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" - msg "github.com/ubclaunchpad/cumulus/message" + "github.com/ubclaunchpad/cumulus/message" sn "github.com/ubclaunchpad/cumulus/subnet" ) @@ -27,6 +27,8 @@ const ( CumulusProtocol = "/cumulus/0.0.1" // DefaultIP is the IP address new hosts will use if none if provided DefaultIP = "127.0.0.1" + // Timeout is the time after which reads from a stream will timeout + Timeout = time.Second * 30 ) // Peer is a cumulus Peer composed of a host @@ -117,24 +119,20 @@ func (p *Peer) Receive(s net.Stream) { } defer p.subnet.RemovePeer(remoteMA) - buf := bufio.NewReader(s) - strMsg, err := buf.ReadString('\n') // TODO: set timeout here + err = s.SetDeadline(time.Now().Add(Timeout)) if err != nil { - log.Error(err) - return + log.WithError(err).Error("Failed to set read deadline on stream") } - - // Turn the string into a message we can deal with - message, err := msg.FromString(strMsg) + msg, err := message.Read(s) if err != nil { - log.Error(err) + log.WithError(err).Error("Error reading from the stream") return } - log.Debugf("Peer %s message:\n%s", p.ID(), strMsg) + log.Debugf("Peer %s message:\n%s", p.ID(), msg.Type) // Respond to message - p.handleMessage(*message, s) + p.handleMessage(msg, s) } // Connect adds the given multiaddress to p's Peerstore and opens a stream @@ -177,7 +175,7 @@ func (p *Peer) Connect(peerma string) (net.Stream, error) { } // Broadcast sends message to all peers this peer is currently connected to -func (p *Peer) Broadcast(m msg.Message) error { +func (p *Peer) Broadcast(m message.Message) error { return errors.New("Function not implemented") } @@ -218,25 +216,11 @@ func extractPeerInfo(peerma string) (lpeer.ID, ma.Multiaddr, error) { // advertisePeers writes messages into the given stream advertising the // multiaddress of each peer in this peer's subnet. func (p *Peer) advertisePeers(s net.Stream) { - mAddrs := p.subnet.Multiaddrs() log.Debug("Peers on this subnet: ") - for mAddr := range mAddrs { - mAddrString := string(mAddr) - log.Debug("\t", mAddrString) - message, err := msg.New([]byte(mAddrString), msg.PeerInfo) - if err != nil { - log.Error("Failed to create message") - return - } - msgBytes, err := message.Bytes() - if err != nil { - log.Error("Failed to marshal message") - return - } - _, err = s.Write(msgBytes) - if err != nil { - log.Errorf("Failed to send message to %s", string(mAddr)) - } + msg := message.New(message.MessageResponse, p.subnet.Multiaddrs()) + err := msg.Write(s) + if err != nil { + log.WithError(err).Error("Error writing PeerInfo message to stream") } } @@ -251,9 +235,9 @@ func makeMultiaddr(iAddr ma.Multiaddr, pid lpeer.ID) (ma.Multiaddr, error) { return mAddr, err } -func (p *Peer) handleMessage(m msg.Message, s net.Stream) { - switch m.Type() { - case msg.RequestPeerInfo: +func (p *Peer) handleMessage(m *message.Message, s net.Stream) { + switch m.Type { + case message.MessageRequest: p.advertisePeers(s) break default: From 3c898b286dd8aa204011cb9ea5d4dfe6a58ad878 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Sun, 21 May 2017 17:33:00 -0700 Subject: [PATCH 5/5] udpate import --- peer/peer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peer/peer_test.go b/peer/peer_test.go index 820eba9..4d0f4e3 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - log "github.com/sirupsen/logrus" + log "github.com/Sirupsen/logrus" ) func TestMain(t *testing.T) {