From 66c8fc801223faf1edc537bb26e66c5cd47ab1c2 Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Thu, 1 Feb 2018 21:28:45 +0100 Subject: [PATCH] merge original into coz repo (#10) merged with the original repo. --- VERSION | 2 +- pkg/core/block.go | 53 +++++++++++++++++++++-- pkg/core/block_test.go | 6 +++ pkg/core/blockchain.go | 26 ++++++++++++ pkg/core/blockchain_storer.go | 44 +++++++++++++++++++ pkg/network/server.go | 79 +++++++++++++++++++++++++++++++++-- pkg/network/tcp.go | 22 +++++----- 7 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 pkg/core/block_test.go create mode 100644 pkg/core/blockchain.go create mode 100644 pkg/core/blockchain_storer.go diff --git a/VERSION b/VERSION index 6c6aa7cb0..341cf11fa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 \ No newline at end of file +0.2.0 \ No newline at end of file diff --git a/pkg/core/block.go b/pkg/core/block.go index 9b82c6960..552528c2d 100644 --- a/pkg/core/block.go +++ b/pkg/core/block.go @@ -1,20 +1,24 @@ package core import ( + "bytes" + "crypto/sha256" "encoding/binary" "io" . "github.com/CityOfZion/neo-go/pkg/util" ) -// Block represents one block in the chain. -type Block struct { +// BlockBase holds the base info of a block +type BlockBase struct { Version uint32 // hash of the previous block. PrevBlock Uint256 // Root hash of a transaction list. MerkleRoot Uint256 - // timestamp + // The time stamp of each block must be later than previous block's time stamp. + // Generally the difference of two block's time stamp is about 15 seconds and imprecision is allowed. + // The height of the block must be exactly equal to the height of the previous block plus 1. Timestamp uint32 // height of the block Height uint32 @@ -22,14 +26,40 @@ type Block struct { Nonce uint64 // contract addresss of the next miner NextMiner Uint160 - // seperator ? fixed to 1 + // fixed to 1 _sep uint8 // Script used to validate the block Script *Witness +} + +// BlockHead holds the head info of a block +type BlockHead struct { + BlockBase + // fixed to 0 + _sep1 uint8 +} + +// Block represents one block in the chain. +type Block struct { + BlockBase // transaction list Transactions []*Transaction } +// encodeHashableFields will only encode the fields used for hashing. +// see Hash() for more information about the fields. +func (b *Block) encodeHashableFields(w io.Writer) error { + err := binary.Write(w, binary.LittleEndian, &b.Version) + err = binary.Write(w, binary.LittleEndian, &b.PrevBlock) + err = binary.Write(w, binary.LittleEndian, &b.MerkleRoot) + err = binary.Write(w, binary.LittleEndian, &b.Timestamp) + err = binary.Write(w, binary.LittleEndian, &b.Height) + err = binary.Write(w, binary.LittleEndian, &b.Nonce) + err = binary.Write(w, binary.LittleEndian, &b.NextMiner) + + return err +} + // EncodeBinary encodes the block to the given writer. func (b *Block) EncodeBinary(w io.Writer) error { return nil @@ -66,5 +96,20 @@ func (b *Block) DecodeBinary(r io.Reader) error { return err } +// Hash return the hash of the block. +// When calculating the hash value of the block, instead of calculating the entire block, +// only first seven fields in the block head will be calculated, which are +// version, PrevBlock, MerkleRoot, timestamp, and height, the nonce, NextMiner. +// Since MerkleRoot already contains the hash value of all transactions, +// the modification of transaction will influence the hash value of the block. +func (b *Block) Hash() (hash Uint256, err error) { + buf := new(bytes.Buffer) + if err = b.encodeHashableFields(buf); err != nil { + return + } + hash = sha256.Sum256(buf.Bytes()) + return +} + // Size implements the payload interface. func (b *Block) Size() uint32 { return 0 } diff --git a/pkg/core/block_test.go b/pkg/core/block_test.go new file mode 100644 index 000000000..88d5e11df --- /dev/null +++ b/pkg/core/block_test.go @@ -0,0 +1,6 @@ +package core + +import "testing" + +func TestBlockEncodeDecode(t *testing.T) { +} diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go new file mode 100644 index 000000000..f8e84451e --- /dev/null +++ b/pkg/core/blockchain.go @@ -0,0 +1,26 @@ +package core + +// tuning parameters +const ( + secondsPerBlock = 15 +) + +var ( + genAmount = []int{8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} +) + +// Blockchain holds the chain. +type Blockchain struct { + // Any object that satisfies the BlockchainStorer interface. + BlockchainStorer + + // index of the latest block. + currentHeight uint32 +} + +// NewBlockchain returns a pointer to a Blockchain. +func NewBlockchain(store BlockchainStorer) *Blockchain { + return &Blockchain{ + BlockchainStorer: store, + } +} diff --git a/pkg/core/blockchain_storer.go b/pkg/core/blockchain_storer.go new file mode 100644 index 000000000..6b874c0a0 --- /dev/null +++ b/pkg/core/blockchain_storer.go @@ -0,0 +1,44 @@ +package core + +import ( + "sync" + + "github.com/CityOfZion/neo-go/pkg/util" +) + +// BlockchainStorer is anything that can persist and retrieve the blockchain. +type BlockchainStorer interface { + HasBlock(util.Uint256) bool + GetBlockByHeight(uint32) (*Block, error) + GetBlockByHash(util.Uint256) (*Block, error) +} + +// MemoryStore is an in memory implementation of a BlockChainStorer. +type MemoryStore struct { + mtx *sync.RWMutex + blocks map[util.Uint256]*Block +} + +// NewMemoryStore returns a pointer to a MemoryStore object. +func NewMemoryStore() *MemoryStore { + return &MemoryStore{ + mtx: &sync.RWMutex{}, + blocks: map[util.Uint256]*Block{}, + } +} + +// HasBlock implements the BlockchainStorer interface. +func (s *MemoryStore) HasBlock(hash util.Uint256) bool { + _, ok := s.blocks[hash] + return ok +} + +// GetBlockByHash returns a block by its hash. +func (s *MemoryStore) GetBlockByHash(hash util.Uint256) (*Block, error) { + return nil, nil +} + +// GetBlockByHeight returns a block by its height. +func (s *MemoryStore) GetBlockByHeight(i uint32) (*Block, error) { + return nil, nil +} diff --git a/pkg/network/server.go b/pkg/network/server.go index 0d568482c..5bc145d36 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -5,8 +5,10 @@ import ( "log" "net" "os" + "sync" "time" + "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/network/payload" "github.com/CityOfZion/neo-go/pkg/util" ) @@ -54,6 +56,46 @@ type Server struct { listener net.Listener // channel for safely responding the number of current connected peers. peerCountCh chan peerCount + // a list of hashes that + knownHashes protectedHashmap + // The blockchain. + bc *core.Blockchain +} + +type protectedHashmap struct { + *sync.RWMutex + hashes map[util.Uint256]bool +} + +func (m protectedHashmap) add(h util.Uint256) bool { + m.Lock() + defer m.Unlock() + + if _, ok := m.hashes[h]; !ok { + m.hashes[h] = true + return true + } + return false +} + +func (m protectedHashmap) remove(h util.Uint256) bool { + m.Lock() + defer m.Unlock() + + if _, ok := m.hashes[h]; ok { + delete(m.hashes, h) + return true + } + return false +} + +func (m protectedHashmap) has(h util.Uint256) bool { + m.RLock() + defer m.RUnlock() + + _, ok := m.hashes[h] + + return ok } // NewServer returns a pointer to a new server. @@ -76,6 +118,8 @@ func NewServer(net NetMode) *Server { net: net, quit: make(chan struct{}), peerCountCh: make(chan peerCount), + // knownHashes: protectedHashmap{}, + bc: core.NewBlockchain(core.NewMemoryStore()), } return s @@ -162,11 +206,11 @@ func (s *Server) handlePeerConnected(p Peer) { func (s *Server) handleVersionCmd(msg *Message, p Peer) *Message { version := msg.Payload.(*payload.Version) if s.id == version.Nonce { - p.disconnect() + // s.unregister <- p return nil } if p.addr().Port != version.Port { - p.disconnect() + // s.unregister <- p return nil } return newMessage(ModeDevNet, cmdVerack, nil) @@ -176,22 +220,46 @@ func (s *Server) handleGetaddrCmd(msg *Message, p Peer) *Message { return nil } +// The node can broadcast the object information it owns by this message. +// The message can be sent automatically or can be used to answer getbloks messages. func (s *Server) handleInvCmd(msg *Message, p Peer) *Message { inv := msg.Payload.(*payload.Inventory) if !inv.Type.Valid() { - p.disconnect() + s.unregister <- p return nil } if len(inv.Hashes) == 0 { - p.disconnect() + s.unregister <- p return nil } + // todo: only grab the hashes that we dont know. + payload := payload.NewInventory(inv.Type, inv.Hashes) resp := newMessage(s.net, cmdGetData, payload) return resp } +// handleBlockCmd processes the received block. +func (s *Server) handleBlockCmd(msg *Message, p Peer) { + block := msg.Payload.(*core.Block) + hash, err := block.Hash() + if err != nil { + // not quite sure what to do here. + // should we disconnect the client or just silently log and move on? + s.logger.Printf("failed to generate block hash: %s", err) + return + } + + fmt.Println(hash) + + if s.bc.HasBlock(hash) { + return + } +} + +// After receiving the getaddr message, the node returns an addr message as response +// and provides information about the known nodes on the network. func (s *Server) handleAddrCmd(msg *Message, p Peer) { addrList := msg.Payload.(*payload.AddressList) for _, addr := range addrList.Addrs { @@ -202,6 +270,9 @@ func (s *Server) handleAddrCmd(msg *Message, p Peer) { } } +func (s *Server) relayInventory(inv *payload.Inventory) { +} + // check if the addr is already connected to the server. func (s *Server) peerAlreadyConnected(addr net.Addr) bool { for peer := range s.peers { diff --git a/pkg/network/tcp.go b/pkg/network/tcp.go index 4c9b13b4d..dffb56b4d 100644 --- a/pkg/network/tcp.go +++ b/pkg/network/tcp.go @@ -3,6 +3,7 @@ package network import ( "bytes" "fmt" + "io" "net" "github.com/CityOfZion/neo-go/pkg/network/payload" @@ -64,6 +65,9 @@ func handleConnection(s *Server, conn net.Conn) { buf := make([]byte, 1024) for { _, err := conn.Read(buf) + if err == io.EOF { + break + } if err != nil { s.logger.Printf("conn read error: %s", err) break @@ -83,7 +87,7 @@ func handleConnection(s *Server, conn net.Conn) { func handleMessage(s *Server, p *TCPPeer) { // Disconnect the peer when we break out of the loop. defer func() { - p.disconnect() + s.unregister <- p }() for { @@ -97,14 +101,6 @@ func handleMessage(s *Server, p *TCPPeer) { resp := s.handleVersionCmd(msg, p) p.nonce = msg.Payload.(*payload.Version).Nonce p.send <- resp - - // after sending our version we want a "verack" and nothing else. - msg := <-p.receive - if msg.commandType() != cmdVerack { - break - } - // we can start the protocol now. - go s.sendLoop(p) case cmdAddr: s.handleAddrCmd(msg, p) case cmdGetAddr: @@ -113,11 +109,11 @@ func handleMessage(s *Server, p *TCPPeer) { resp := s.handleInvCmd(msg, p) p.send <- resp case cmdBlock: + s.handleBlockCmd(msg, p) case cmdConsensus: case cmdTX: case cmdVerack: - // disconnect the peer, verack should already be handled. - break + go s.sendLoop(p) case cmdGetHeaders: case cmdGetBlocks: case cmdGetData: @@ -174,9 +170,11 @@ func (p *TCPPeer) callGetaddr(msg *Message) { } // disconnect closes the send channel and the underlying connection. +// TODO: this needs some love. We will get send on closed channel. func (p *TCPPeer) disconnect() { - close(p.send) p.conn.Close() + close(p.send) + close(p.receive) } // writeLoop writes messages to the underlying TCP connection.