diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ec197f14d --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +# Added by CoZ developers +vendor/ +bin/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..26ffa0846 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +BRANCH = "master" +VERSION = $(shell cat ./VERSION) + +build: + @go build -o ./bin/neo-go ./cli/main.go + +check-version: + git fetch && (! git rev-list ${VERSION}) + +deps: + @glide install + +push-tag: + git checkout ${BRANCH} + git pull origin ${BRANCH} + git tag ${VERSION} + git push origin ${BRANCH} --tags + +test: + @go test $(shell glide nv) -cover + +vet: + @go vet $(shell glide nv) \ No newline at end of file diff --git a/README.md b/README.md index 87e863e74..0da105a96 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ >

-

NEO-GO

+

neo-go

- Node and SDK for the NEO blockchain written in the Go language. + Go Node and SDK for the NEO blockchain.

@@ -18,67 +18,112 @@

# Overview -> This project is currently in alpha and under active development. -### Long term project goals -Full port of the original C# [NEO project](https://github.com/neo-project). A complete toolkit for the NEO blockchain. +> This project is currently in **alpha** and under active development. -- Full server (consensus and RPC) nodes. +## Project Goals + +Full port of the original C# [NEO project](https://github.com/neo-project). +A complete toolkit for the NEO blockchain, including: + +- Full consensus node +- Full RPC node - RPC client -- build, compile and deploy smart contracts with the Go vm +- CLI tool +- Smart contract compiler -### Current state -This project is still under heavy development. Still working on internal API's and project layout. This should not take longer than 2 weeks. +## Current State -The project will exist out of the following topics/packages: +This project is still under heavy development. Still working on internal API's and project layout. T +his should not take longer than 2 weeks. -1. network (started) -2. core (started) -3. api (JSON-RPC server) (started) -4. vm (open) -5. smartcontract (open) +The project will exist out of the following packages: -# Getting started -### Server +| Package | State | Developer | +|---------------|---------|--------------------------------------| +| api | started | [@anthdm](https://github.com/anthdm) | +| core | started | [@anthdm](https://github.com/anthdm) | +| network | started | [@anthdm](https://github.com/anthdm) | +| smartcontract | started | [@revett](https://github.com/revett) | +| vm | started | [@revett](https://github.com/revett) | -Install the neoserver cli `go install ./cmd/neoserver` +# Getting Started -Currently, there is a minimal subset of the NEO protocol implemented. To start experimenting make sure you a have a private net running on your machine. If you dont, take a look at [docker-privnet-with-gas](https://hub.docker.com/r/metachris/neo-privnet-with-gas/). +## Server + +Install dependencies, this requires [Glide](https://github.com/Masterminds/glide#install): + +``` +make deps +``` + +Build the **neo-go** CLI: + +``` +make build +``` + +Currently, there is a minimal subset of the NEO protocol implemented. +To start experimenting make sure you a have a private net running on your machine. +If you dont, take a look at [docker-privnet-with-gas](https://hub.docker.com/r/metachris/neo-privnet-with-gas/). Start the server: -`neoserver -seed 127.0.0.1:20333` +``` +./bin/neo-go -seed 127.0.0.1:20333 +``` You can add multiple seeds if you want: -`neoserver -seed 127.0.0.1:20333,127.0.01:20334` +``` +./bin/neo-go -seed 127.0.0.1:20333,127.0.01:20334 +``` -By default the server will currently run on port 3000, for testing purposes. You can change that by setting the tcp flag: +By default the server will currently run on port 3000, for testing purposes. +You can change that by setting the tcp flag: -`neoserver -seed 127.0.0.1:20333 -tcp 1337` +``` +./bin/neo-go -seed 127.0.0.1:20333 -tcp 1337 +``` + +## RPC -### RPC If you want your node to also serve JSON-RPC, you can do that by setting the following flag: -`neoserver -rpc 4000` +``` +./bin/neo-go -rpc 4000 +``` -In this case server will accept and respond JSON-RPC on port 4000. Keep in mind that currently there is only a small subset of the JSON-RPC implemented. Feel free to make a PR with more functionality. +In this case server will accept and respond JSON-RPC on port 4000. +Keep in mind that currently there is only a small subset of the JSON-RPC implemented. +Feel free to make a PR with more functionality. -### vm -To be implemented.. +## VM -### smart contracts -To be implemented.. +``` +TODO +``` + +## Smart Contracts + +``` +TODO +``` # Contributing -Feel free to contribute to this project after reading the [contributing guidelines](https://github.com/anthdm/neo-go/blob/master/CONTRIBUTING.md). -Before starting to work on a certain topic, create an new issue first, describing the feauture/topic you are going to implement. +Feel free to contribute to this project after reading the +[contributing guidelines](https://github.com/anthdm/neo-go/blob/master/CONTRIBUTING.md). + +Before starting to work on a certain topic, create an new issue first, +describing the feauture/topic you are going to implement. # Contact + - [@anthdm](https://github.com/anthdm) on Github - [@anthdm](https://twitter.com/anthdm) on Twitter - Send me an email anthony@cityofzion.io # License + - Open-source [MIT](https://github.com/anthdm/neo-go/blob/master/LICENCE.md) diff --git a/VERSION b/VERSION new file mode 100644 index 000000000..341cf11fa --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.2.0 \ No newline at end of file diff --git a/circle.yml b/circle.yml new file mode 100644 index 000000000..86ce09569 --- /dev/null +++ b/circle.yml @@ -0,0 +1,86 @@ +version: 2 +jobs: + install_deps: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ .Revision }} + - run: BUILD=false /scripts/build.sh + - save_cache: + key: dependency-cache-{{ .Revision }} + paths: + - vendor + test: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ .Revision }} + - run: make test + vet: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ .Revision }} + - run: make vet + check_version: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + steps: + - checkout + - run: make check-version + build_cli: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + steps: + - checkout + - run: make build + +workflows: + version: 2 + workflow: + jobs: + - install_deps: + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + branches: + ignore: master + - test: + requires: + - install_deps + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + branches: + ignore: master + - vet: + requires: + - install_deps + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + branches: + ignore: master + - check_version: + filters: + branches: + ignore: master + - build_cli: + requires: + - install_deps + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + branches: + ignore: master \ No newline at end of file diff --git a/cmd/neoserver/main.go b/cli/main.go similarity index 94% rename from cmd/neoserver/main.go rename to cli/main.go index fff926922..c8b13fd1a 100644 --- a/cmd/neoserver/main.go +++ b/cli/main.go @@ -4,7 +4,7 @@ import ( "flag" "strings" - "github.com/anthdm/neo-go/pkg/network" + "github.com/CityOfZion/neo-go/pkg/network" ) var ( diff --git a/glide.lock b/glide.lock new file mode 100644 index 000000000..59f70a101 --- /dev/null +++ b/glide.lock @@ -0,0 +1,4 @@ +hash: b1152abdd9a1fa1e70773cddcf54247d3fe3332602604f9f2233165ced02eeaf +updated: 2018-02-01T18:34:22.684905Z +imports: [] +testImports: [] diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 000000000..3b61baf8d --- /dev/null +++ b/glide.yaml @@ -0,0 +1,2 @@ +package: github.com/CityOfZion/neo-go +import: [] diff --git a/pkg/core/block.go b/pkg/core/block.go index 5e8eece3c..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/anthdm/neo-go/pkg/util" + . "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/core/transaction.go b/pkg/core/transaction.go index da582461f..bc432bca1 100644 --- a/pkg/core/transaction.go +++ b/pkg/core/transaction.go @@ -5,31 +5,9 @@ import ( "io" ) -// TransactionType is the type of a transaction. -type TransactionType uint8 - -// String implements the stringer interface. -func (t TransactionType) String() string { - switch t { - case MinerTX: - return "miner transaction" - case IssueTX: - return "issue transaction" - case ClaimTX: - return "claim transaction" - case EnrollmentTX: - return "enrollment transaction" - case VotingTX: - return "voting transaction" - case RegisterTX: - return "register transaction" - case ContractTX: - return "contract transaction" - case AgencyTX: - return "agency transaction" - default: - return "" - } +// Transaction is a process recorded in the NEO system. +type Transaction struct { + Type TransactionType } // All processes in NEO system are recorded in transactions. @@ -45,18 +23,13 @@ const ( AgencyTX = 0xb0 ) -// Transaction is a process recorded in the NEO system. -type Transaction struct { - Type TransactionType -} - // DecodeBinary implements the payload interface. -func (t *Transaction) DecodeBinary(r io.Reader) error { +func (t Transaction) DecodeBinary(r io.Reader) error { err := binary.Read(r, binary.LittleEndian, &t.Type) return err } // EncodeBinary implements the payload interface. -func (t *Transaction) EncodeBinary(w io.Writer) error { +func (t Transaction) EncodeBinary(w io.Writer) error { return nil } diff --git a/pkg/core/transaction_type.go b/pkg/core/transaction_type.go new file mode 100644 index 000000000..3aafe0166 --- /dev/null +++ b/pkg/core/transaction_type.go @@ -0,0 +1,28 @@ +package core + +// TransactionType is the type of a transaction. +type TransactionType uint8 + +// String implements the stringer interface. +func (t TransactionType) String() string { + switch t { + case MinerTX: + return "miner transaction" + case IssueTX: + return "issue transaction" + case ClaimTX: + return "claim transaction" + case EnrollmentTX: + return "enrollment transaction" + case VotingTX: + return "voting transaction" + case RegisterTX: + return "register transaction" + case ContractTX: + return "contract transaction" + case AgencyTX: + return "agency transaction" + default: + return "" + } +} diff --git a/pkg/network/message.go b/pkg/network/message.go index 070cec320..3d9dc96a1 100644 --- a/pkg/network/message.go +++ b/pkg/network/message.go @@ -8,8 +8,8 @@ import ( "fmt" "io" - "github.com/anthdm/neo-go/pkg/core" - "github.com/anthdm/neo-go/pkg/network/payload" + "github.com/CityOfZion/neo-go/pkg/core" + "github.com/CityOfZion/neo-go/pkg/network/payload" ) const ( diff --git a/pkg/network/message_test.go b/pkg/network/message_test.go index 4b4e5608f..6b1c5b596 100644 --- a/pkg/network/message_test.go +++ b/pkg/network/message_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/anthdm/neo-go/pkg/network/payload" + "github.com/CityOfZion/neo-go/pkg/network/payload" ) func TestMessageEncodeDecode(t *testing.T) { diff --git a/pkg/network/payload/addr.go b/pkg/network/payload/addr.go index b5376524a..26f2de4af 100644 --- a/pkg/network/payload/addr.go +++ b/pkg/network/payload/addr.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "io" - "github.com/anthdm/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/util" ) // AddrWithTime payload diff --git a/pkg/network/payload/addr_test.go b/pkg/network/payload/addr_test.go index c021b72df..8f2da310d 100644 --- a/pkg/network/payload/addr_test.go +++ b/pkg/network/payload/addr_test.go @@ -6,7 +6,7 @@ import ( "reflect" "testing" - "github.com/anthdm/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/util" ) func TestEncodeDecodeAddr(t *testing.T) { diff --git a/pkg/network/payload/getblocks.go b/pkg/network/payload/getblocks.go new file mode 100644 index 000000000..f7a84a1c2 --- /dev/null +++ b/pkg/network/payload/getblocks.go @@ -0,0 +1,54 @@ +package payload + +import ( + "encoding/binary" + "io" + + . "github.com/CityOfZion/neo-go/pkg/util" +) + +// HashStartStop contains fields and methods to be shared with the +// "GetBlocks" and "GetHeaders" payload. +type HashStartStop struct { + // hash of latest block that node requests + HashStart []Uint256 + // hash of last block that node requests + HashStop Uint256 +} + +// DecodeBinary implements the payload interface. +func (p *HashStartStop) DecodeBinary(r io.Reader) error { + var lenStart uint8 + + err := binary.Read(r, binary.LittleEndian, &lenStart) + p.HashStart = make([]Uint256, lenStart) + err = binary.Read(r, binary.LittleEndian, &p.HashStart) + err = binary.Read(r, binary.LittleEndian, &p.HashStop) + + return err +} + +// EncodeBinary implements the payload interface. +func (p *HashStartStop) EncodeBinary(w io.Writer) error { + err := binary.Write(w, binary.LittleEndian, uint8(len(p.HashStart))) + err = binary.Write(w, binary.LittleEndian, p.HashStart) + err = binary.Write(w, binary.LittleEndian, p.HashStop) + + return err +} + +// Size implements the payload interface. +func (p *HashStartStop) Size() uint32 { return 0 } + +// GetBlocks payload +type GetBlocks struct { + HashStartStop +} + +// NewGetBlocks return a pointer to a GetBlocks object. +func NewGetBlocks(start []Uint256, stop Uint256) *GetBlocks { + p := &GetBlocks{} + p.HashStart = start + p.HashStop = stop + return p +} diff --git a/pkg/network/payload/getblocks_test.go b/pkg/network/payload/getblocks_test.go new file mode 100644 index 000000000..5c62edd96 --- /dev/null +++ b/pkg/network/payload/getblocks_test.go @@ -0,0 +1,37 @@ +package payload + +import ( + "bytes" + "crypto/sha256" + "reflect" + "testing" + + . "github.com/CityOfZion/neo-go/pkg/util" +) + +func TestGetBlocksEncodeDecode(t *testing.T) { + start := []Uint256{ + sha256.Sum256([]byte("a")), + sha256.Sum256([]byte("b")), + } + stop := sha256.Sum256([]byte("c")) + + p := NewGetBlocks(start, stop) + buf := new(bytes.Buffer) + if err := p.EncodeBinary(buf); err != nil { + t.Fatal(err) + } + + if have, want := buf.Len(), 1+64+32; have != want { + t.Fatalf("expecting a length of %d got %d", want, have) + } + + pDecode := &GetBlocks{} + if err := pDecode.DecodeBinary(buf); err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(p, pDecode) { + t.Fatalf("expecting both getblocks payloads to be equal %v and %v", p, pDecode) + } +} diff --git a/pkg/network/payload/getheaders.go b/pkg/network/payload/getheaders.go new file mode 100644 index 000000000..ce71a626c --- /dev/null +++ b/pkg/network/payload/getheaders.go @@ -0,0 +1,17 @@ +package payload + +import "github.com/CityOfZion/neo-go/pkg/util" + +// GetHeaders payload is the same as the "GetBlocks" payload. +type GetHeaders struct { + HashStartStop +} + +// NewGetHeaders return a pointer to a GetHeaders object. +func NewGetHeaders(start []util.Uint256, stop util.Uint256) *GetHeaders { + p := &GetHeaders{} + p.HashStart = start + p.HashStop = stop + + return p +} diff --git a/pkg/network/payload/getheaders_test.go b/pkg/network/payload/getheaders_test.go new file mode 100644 index 000000000..87a3e6672 --- /dev/null +++ b/pkg/network/payload/getheaders_test.go @@ -0,0 +1,37 @@ +package payload + +import ( + "bytes" + "crypto/sha256" + "reflect" + "testing" + + "github.com/CityOfZion/neo-go/pkg/util" +) + +func TestGetHeadersEncodeDecode(t *testing.T) { + start := []util.Uint256{ + sha256.Sum256([]byte("a")), + sha256.Sum256([]byte("b")), + } + stop := sha256.Sum256([]byte("c")) + + p := NewGetHeaders(start, stop) + buf := new(bytes.Buffer) + if err := p.EncodeBinary(buf); err != nil { + t.Fatal(err) + } + + if have, want := buf.Len(), 1+64+32; have != want { + t.Fatalf("expecting a length of %d got %d", want, have) + } + + pDecode := &GetHeaders{} + if err := pDecode.DecodeBinary(buf); err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(p, pDecode) { + t.Fatalf("expecting both getheaders payloads to be equal %v and %v", p, pDecode) + } +} diff --git a/pkg/network/payload/inventory.go b/pkg/network/payload/inventory.go index 306995016..46310bfe5 100644 --- a/pkg/network/payload/inventory.go +++ b/pkg/network/payload/inventory.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "io" - . "github.com/anthdm/neo-go/pkg/util" + . "github.com/CityOfZion/neo-go/pkg/util" ) // The node can broadcast the object information it owns by this message. diff --git a/pkg/network/payload/inventory_test.go b/pkg/network/payload/inventory_test.go index 774e33017..4cfba1ef5 100644 --- a/pkg/network/payload/inventory_test.go +++ b/pkg/network/payload/inventory_test.go @@ -6,7 +6,7 @@ import ( "reflect" "testing" - . "github.com/anthdm/neo-go/pkg/util" + . "github.com/CityOfZion/neo-go/pkg/util" ) func TestInventoryEncodeDecode(t *testing.T) { diff --git a/pkg/network/peer.go b/pkg/network/peer.go index 025f4b7d8..c83b7b24c 100644 --- a/pkg/network/peer.go +++ b/pkg/network/peer.go @@ -1,7 +1,7 @@ package network import ( - "github.com/anthdm/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/util" ) // Peer is the local representation of a remote node. It's an interface that may @@ -9,7 +9,6 @@ import ( type Peer interface { id() uint32 addr() util.Endpoint - verack() bool disconnect() callVersion(*Message) callGetaddr(*Message) @@ -20,7 +19,6 @@ type Peer interface { type LocalPeer struct { s *Server nonce uint32 - isVerack bool endpoint util.Endpoint } @@ -39,6 +37,5 @@ func (p *LocalPeer) callGetaddr(msg *Message) { } func (p *LocalPeer) id() uint32 { return p.nonce } -func (p *LocalPeer) verack() bool { return p.isVerack } func (p *LocalPeer) addr() util.Endpoint { return p.endpoint } func (p *LocalPeer) disconnect() {} diff --git a/pkg/network/server.go b/pkg/network/server.go index c18c11695..5bc145d36 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -5,10 +5,12 @@ import ( "log" "net" "os" + "sync" "time" - "github.com/anthdm/neo-go/pkg/network/payload" - "github.com/anthdm/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/core" + "github.com/CityOfZion/neo-go/pkg/network/payload" + "github.com/CityOfZion/neo-go/pkg/util" ) const ( @@ -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/server_test.go b/pkg/network/server_test.go index f0ba721f7..115c2b74c 100644 --- a/pkg/network/server_test.go +++ b/pkg/network/server_test.go @@ -3,7 +3,7 @@ package network import ( "testing" - "github.com/anthdm/neo-go/pkg/network/payload" + "github.com/CityOfZion/neo-go/pkg/network/payload" ) func TestHandleVersion(t *testing.T) { diff --git a/pkg/network/tcp.go b/pkg/network/tcp.go index a1280a6b0..dffb56b4d 100644 --- a/pkg/network/tcp.go +++ b/pkg/network/tcp.go @@ -3,10 +3,11 @@ package network import ( "bytes" "fmt" + "io" "net" - "github.com/anthdm/neo-go/pkg/network/payload" - "github.com/anthdm/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/network/payload" + "github.com/CityOfZion/neo-go/pkg/util" ) func listenTCP(s *Server, port int) error { @@ -22,6 +23,7 @@ func listenTCP(s *Server, port int) error { if err != nil { return err } + go handleConnection(s, conn) } } @@ -54,13 +56,18 @@ func handleConnection(s *Server, conn net.Conn) { s.unregister <- peer }() - // Start a goroutine that will handle all writes to the registered peer. + // Start a goroutine that will handle all outgoing messages. go peer.writeLoop() + // Start a goroutine that will handle all incomming messages. + go handleMessage(s, peer) // Read from the connection and decode it into a Message ready for processing. 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 @@ -71,39 +78,47 @@ func handleConnection(s *Server, conn net.Conn) { s.logger.Printf("decode error %s", err) break } - handleMessage(msg, s, peer) + + peer.receive <- msg } } // handleMessage hands the message received from a TCP connection over to the server. -func handleMessage(msg *Message, s *Server, p *TCPPeer) { - command := msg.commandType() +func handleMessage(s *Server, p *TCPPeer) { + // Disconnect the peer when we break out of the loop. + defer func() { + s.unregister <- p + }() - s.logger.Printf("IN :: %d :: %s :: %v", p.id(), command, msg) + for { + msg := <-p.receive + command := msg.commandType() - switch command { - case cmdVersion: - resp := s.handleVersionCmd(msg, p) - p.isVerack = true - p.nonce = msg.Payload.(*payload.Version).Nonce - p.send <- resp - case cmdAddr: - s.handleAddrCmd(msg, p) - case cmdGetAddr: - s.handleGetaddrCmd(msg, p) - case cmdInv: - resp := s.handleInvCmd(msg, p) - p.send <- resp - case cmdBlock: - case cmdConsensus: - case cmdTX: - case cmdVerack: - go s.sendLoop(p) - case cmdGetHeaders: - case cmdGetBlocks: - case cmdGetData: - case cmdHeaders: - default: + s.logger.Printf("IN :: %d :: %s :: %v", p.id(), command, msg) + + switch command { + case cmdVersion: + resp := s.handleVersionCmd(msg, p) + p.nonce = msg.Payload.(*payload.Version).Nonce + p.send <- resp + case cmdAddr: + s.handleAddrCmd(msg, p) + case cmdGetAddr: + s.handleGetaddrCmd(msg, p) + case cmdInv: + resp := s.handleInvCmd(msg, p) + p.send <- resp + case cmdBlock: + s.handleBlockCmd(msg, p) + case cmdConsensus: + case cmdTX: + case cmdVerack: + go s.sendLoop(p) + case cmdGetHeaders: + case cmdGetBlocks: + case cmdGetData: + case cmdHeaders: + } } } @@ -118,8 +133,8 @@ type TCPPeer struct { endpoint util.Endpoint // channel to coordinate messages writen back to the connection. send chan *Message - // whether this peers version was acknowledged. - isVerack bool + // channel to receive from underlying connection. + receive chan *Message } // NewTCPPeer returns a pointer to a TCP Peer. @@ -129,6 +144,7 @@ func NewTCPPeer(conn net.Conn, s *Server) *TCPPeer { return &TCPPeer{ conn: conn, send: make(chan *Message), + receive: make(chan *Message), endpoint: e, s: s, } @@ -148,20 +164,17 @@ func (p *TCPPeer) addr() util.Endpoint { return p.endpoint } -// verack implements the peer interface -func (p *TCPPeer) verack() bool { - return p.isVerack -} - // callGetaddr will send the "getaddr" command to the remote. func (p *TCPPeer) callGetaddr(msg *Message) { p.send <- msg } // 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.