This commit is contained in:
anthdm 2018-02-01 21:30:06 +01:00
commit 4050dbeeb8
28 changed files with 653 additions and 126 deletions

18
.gitignore vendored Normal file
View file

@ -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/

23
Makefile Normal file
View file

@ -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)

109
README.md
View file

@ -5,10 +5,10 @@
> >
</p> </p>
<h1 align="center">NEO-GO</h1> <h1 align="center">neo-go</h1>
<p align="center"> <p align="center">
Node and SDK for the <b>NEO</b> blockchain written in the <b>Go</b> language. <b>Go</b> Node and SDK for the <a href="https://neo.org">NEO</a> blockchain.
</p> </p>
<p align="center"> <p align="center">
@ -18,67 +18,112 @@
</p> </p>
# Overview # Overview
> This project is currently in alpha and under active development.
### Long term project goals > This project is currently in **alpha** and under active development.
Full port of the original C# [NEO project](https://github.com/neo-project). A complete toolkit for the NEO blockchain.
- 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 - RPC client
- build, compile and deploy smart contracts with the Go vm - CLI tool
- Smart contract compiler
### Current state ## 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.
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) The project will exist out of the following packages:
2. core (started)
3. api (JSON-RPC server) (started)
4. vm (open)
5. smartcontract (open)
# Getting started | Package | State | Developer |
### Server |---------------|---------|--------------------------------------|
| 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: 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: 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: 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 ## VM
To be implemented..
### smart contracts ```
To be implemented.. TODO
```
## Smart Contracts
```
TODO
```
# Contributing # 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 # Contact
- [@anthdm](https://github.com/anthdm) on Github - [@anthdm](https://github.com/anthdm) on Github
- [@anthdm](https://twitter.com/anthdm) on Twitter - [@anthdm](https://twitter.com/anthdm) on Twitter
- Send me an email anthony@cityofzion.io - Send me an email anthony@cityofzion.io
# License # License
- Open-source [MIT](https://github.com/anthdm/neo-go/blob/master/LICENCE.md) - Open-source [MIT](https://github.com/anthdm/neo-go/blob/master/LICENCE.md)

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.2.0

86
circle.yml Normal file
View file

@ -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

View file

@ -4,7 +4,7 @@ import (
"flag" "flag"
"strings" "strings"
"github.com/anthdm/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/network"
) )
var ( var (

4
glide.lock generated Normal file
View file

@ -0,0 +1,4 @@
hash: b1152abdd9a1fa1e70773cddcf54247d3fe3332602604f9f2233165ced02eeaf
updated: 2018-02-01T18:34:22.684905Z
imports: []
testImports: []

2
glide.yaml Normal file
View file

@ -0,0 +1,2 @@
package: github.com/CityOfZion/neo-go
import: []

View file

@ -1,20 +1,24 @@
package core package core
import ( import (
"bytes"
"crypto/sha256"
"encoding/binary" "encoding/binary"
"io" "io"
. "github.com/anthdm/neo-go/pkg/util" . "github.com/CityOfZion/neo-go/pkg/util"
) )
// Block represents one block in the chain. // BlockBase holds the base info of a block
type Block struct { type BlockBase struct {
Version uint32 Version uint32
// hash of the previous block. // hash of the previous block.
PrevBlock Uint256 PrevBlock Uint256
// Root hash of a transaction list. // Root hash of a transaction list.
MerkleRoot Uint256 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 Timestamp uint32
// height of the block // height of the block
Height uint32 Height uint32
@ -22,14 +26,40 @@ type Block struct {
Nonce uint64 Nonce uint64
// contract addresss of the next miner // contract addresss of the next miner
NextMiner Uint160 NextMiner Uint160
// seperator ? fixed to 1 // fixed to 1
_sep uint8 _sep uint8
// Script used to validate the block // Script used to validate the block
Script *Witness 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 // transaction list
Transactions []*Transaction 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. // EncodeBinary encodes the block to the given writer.
func (b *Block) EncodeBinary(w io.Writer) error { func (b *Block) EncodeBinary(w io.Writer) error {
return nil return nil
@ -66,5 +96,20 @@ func (b *Block) DecodeBinary(r io.Reader) error {
return err 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. // Size implements the payload interface.
func (b *Block) Size() uint32 { return 0 } func (b *Block) Size() uint32 { return 0 }

6
pkg/core/block_test.go Normal file
View file

@ -0,0 +1,6 @@
package core
import "testing"
func TestBlockEncodeDecode(t *testing.T) {
}

26
pkg/core/blockchain.go Normal file
View file

@ -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,
}
}

View file

@ -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
}

View file

@ -5,31 +5,9 @@ import (
"io" "io"
) )
// TransactionType is the type of a transaction. // Transaction is a process recorded in the NEO system.
type TransactionType uint8 type Transaction struct {
Type TransactionType
// 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 ""
}
} }
// All processes in NEO system are recorded in transactions. // All processes in NEO system are recorded in transactions.
@ -45,18 +23,13 @@ const (
AgencyTX = 0xb0 AgencyTX = 0xb0
) )
// Transaction is a process recorded in the NEO system.
type Transaction struct {
Type TransactionType
}
// DecodeBinary implements the payload interface. // 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) err := binary.Read(r, binary.LittleEndian, &t.Type)
return err return err
} }
// EncodeBinary implements the payload interface. // EncodeBinary implements the payload interface.
func (t *Transaction) EncodeBinary(w io.Writer) error { func (t Transaction) EncodeBinary(w io.Writer) error {
return nil return nil
} }

View file

@ -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 ""
}
}

View file

@ -8,8 +8,8 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/anthdm/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/anthdm/neo-go/pkg/network/payload" "github.com/CityOfZion/neo-go/pkg/network/payload"
) )
const ( const (

View file

@ -5,7 +5,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/anthdm/neo-go/pkg/network/payload" "github.com/CityOfZion/neo-go/pkg/network/payload"
) )
func TestMessageEncodeDecode(t *testing.T) { func TestMessageEncodeDecode(t *testing.T) {

View file

@ -4,7 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"github.com/anthdm/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
// AddrWithTime payload // AddrWithTime payload

View file

@ -6,7 +6,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/anthdm/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
func TestEncodeDecodeAddr(t *testing.T) { func TestEncodeDecodeAddr(t *testing.T) {

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -4,7 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"io" "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. // The node can broadcast the object information it owns by this message.

View file

@ -6,7 +6,7 @@ import (
"reflect" "reflect"
"testing" "testing"
. "github.com/anthdm/neo-go/pkg/util" . "github.com/CityOfZion/neo-go/pkg/util"
) )
func TestInventoryEncodeDecode(t *testing.T) { func TestInventoryEncodeDecode(t *testing.T) {

View file

@ -1,7 +1,7 @@
package network package network
import ( 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 // Peer is the local representation of a remote node. It's an interface that may
@ -9,7 +9,6 @@ import (
type Peer interface { type Peer interface {
id() uint32 id() uint32
addr() util.Endpoint addr() util.Endpoint
verack() bool
disconnect() disconnect()
callVersion(*Message) callVersion(*Message)
callGetaddr(*Message) callGetaddr(*Message)
@ -20,7 +19,6 @@ type Peer interface {
type LocalPeer struct { type LocalPeer struct {
s *Server s *Server
nonce uint32 nonce uint32
isVerack bool
endpoint util.Endpoint endpoint util.Endpoint
} }
@ -39,6 +37,5 @@ func (p *LocalPeer) callGetaddr(msg *Message) {
} }
func (p *LocalPeer) id() uint32 { return p.nonce } 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) addr() util.Endpoint { return p.endpoint }
func (p *LocalPeer) disconnect() {} func (p *LocalPeer) disconnect() {}

View file

@ -5,10 +5,12 @@ import (
"log" "log"
"net" "net"
"os" "os"
"sync"
"time" "time"
"github.com/anthdm/neo-go/pkg/network/payload" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/anthdm/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/network/payload"
"github.com/CityOfZion/neo-go/pkg/util"
) )
const ( const (
@ -54,6 +56,46 @@ type Server struct {
listener net.Listener listener net.Listener
// channel for safely responding the number of current connected peers. // channel for safely responding the number of current connected peers.
peerCountCh chan peerCount 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. // NewServer returns a pointer to a new server.
@ -76,6 +118,8 @@ func NewServer(net NetMode) *Server {
net: net, net: net,
quit: make(chan struct{}), quit: make(chan struct{}),
peerCountCh: make(chan peerCount), peerCountCh: make(chan peerCount),
// knownHashes: protectedHashmap{},
bc: core.NewBlockchain(core.NewMemoryStore()),
} }
return s return s
@ -162,11 +206,11 @@ func (s *Server) handlePeerConnected(p Peer) {
func (s *Server) handleVersionCmd(msg *Message, p Peer) *Message { func (s *Server) handleVersionCmd(msg *Message, p Peer) *Message {
version := msg.Payload.(*payload.Version) version := msg.Payload.(*payload.Version)
if s.id == version.Nonce { if s.id == version.Nonce {
p.disconnect() // s.unregister <- p
return nil return nil
} }
if p.addr().Port != version.Port { if p.addr().Port != version.Port {
p.disconnect() // s.unregister <- p
return nil return nil
} }
return newMessage(ModeDevNet, cmdVerack, nil) return newMessage(ModeDevNet, cmdVerack, nil)
@ -176,22 +220,46 @@ func (s *Server) handleGetaddrCmd(msg *Message, p Peer) *Message {
return nil 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 { func (s *Server) handleInvCmd(msg *Message, p Peer) *Message {
inv := msg.Payload.(*payload.Inventory) inv := msg.Payload.(*payload.Inventory)
if !inv.Type.Valid() { if !inv.Type.Valid() {
p.disconnect() s.unregister <- p
return nil return nil
} }
if len(inv.Hashes) == 0 { if len(inv.Hashes) == 0 {
p.disconnect() s.unregister <- p
return nil return nil
} }
// todo: only grab the hashes that we dont know.
payload := payload.NewInventory(inv.Type, inv.Hashes) payload := payload.NewInventory(inv.Type, inv.Hashes)
resp := newMessage(s.net, cmdGetData, payload) resp := newMessage(s.net, cmdGetData, payload)
return resp 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) { func (s *Server) handleAddrCmd(msg *Message, p Peer) {
addrList := msg.Payload.(*payload.AddressList) addrList := msg.Payload.(*payload.AddressList)
for _, addr := range addrList.Addrs { 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. // check if the addr is already connected to the server.
func (s *Server) peerAlreadyConnected(addr net.Addr) bool { func (s *Server) peerAlreadyConnected(addr net.Addr) bool {
for peer := range s.peers { for peer := range s.peers {

View file

@ -3,7 +3,7 @@ package network
import ( import (
"testing" "testing"
"github.com/anthdm/neo-go/pkg/network/payload" "github.com/CityOfZion/neo-go/pkg/network/payload"
) )
func TestHandleVersion(t *testing.T) { func TestHandleVersion(t *testing.T) {

View file

@ -3,10 +3,11 @@ package network
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"net" "net"
"github.com/anthdm/neo-go/pkg/network/payload" "github.com/CityOfZion/neo-go/pkg/network/payload"
"github.com/anthdm/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
func listenTCP(s *Server, port int) error { func listenTCP(s *Server, port int) error {
@ -22,6 +23,7 @@ func listenTCP(s *Server, port int) error {
if err != nil { if err != nil {
return err return err
} }
go handleConnection(s, conn) go handleConnection(s, conn)
} }
} }
@ -54,13 +56,18 @@ func handleConnection(s *Server, conn net.Conn) {
s.unregister <- peer 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() 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. // Read from the connection and decode it into a Message ready for processing.
buf := make([]byte, 1024) buf := make([]byte, 1024)
for { for {
_, err := conn.Read(buf) _, err := conn.Read(buf)
if err == io.EOF {
break
}
if err != nil { if err != nil {
s.logger.Printf("conn read error: %s", err) s.logger.Printf("conn read error: %s", err)
break break
@ -71,12 +78,20 @@ func handleConnection(s *Server, conn net.Conn) {
s.logger.Printf("decode error %s", err) s.logger.Printf("decode error %s", err)
break break
} }
handleMessage(msg, s, peer)
peer.receive <- msg
} }
} }
// handleMessage hands the message received from a TCP connection over to the server. // handleMessage hands the message received from a TCP connection over to the server.
func handleMessage(msg *Message, s *Server, p *TCPPeer) { func handleMessage(s *Server, p *TCPPeer) {
// Disconnect the peer when we break out of the loop.
defer func() {
s.unregister <- p
}()
for {
msg := <-p.receive
command := msg.commandType() command := msg.commandType()
s.logger.Printf("IN :: %d :: %s :: %v", p.id(), command, msg) s.logger.Printf("IN :: %d :: %s :: %v", p.id(), command, msg)
@ -84,7 +99,6 @@ func handleMessage(msg *Message, s *Server, p *TCPPeer) {
switch command { switch command {
case cmdVersion: case cmdVersion:
resp := s.handleVersionCmd(msg, p) resp := s.handleVersionCmd(msg, p)
p.isVerack = true
p.nonce = msg.Payload.(*payload.Version).Nonce p.nonce = msg.Payload.(*payload.Version).Nonce
p.send <- resp p.send <- resp
case cmdAddr: case cmdAddr:
@ -95,6 +109,7 @@ func handleMessage(msg *Message, s *Server, p *TCPPeer) {
resp := s.handleInvCmd(msg, p) resp := s.handleInvCmd(msg, p)
p.send <- resp p.send <- resp
case cmdBlock: case cmdBlock:
s.handleBlockCmd(msg, p)
case cmdConsensus: case cmdConsensus:
case cmdTX: case cmdTX:
case cmdVerack: case cmdVerack:
@ -103,7 +118,7 @@ func handleMessage(msg *Message, s *Server, p *TCPPeer) {
case cmdGetBlocks: case cmdGetBlocks:
case cmdGetData: case cmdGetData:
case cmdHeaders: case cmdHeaders:
default: }
} }
} }
@ -118,8 +133,8 @@ type TCPPeer struct {
endpoint util.Endpoint endpoint util.Endpoint
// channel to coordinate messages writen back to the connection. // channel to coordinate messages writen back to the connection.
send chan *Message send chan *Message
// whether this peers version was acknowledged. // channel to receive from underlying connection.
isVerack bool receive chan *Message
} }
// NewTCPPeer returns a pointer to a TCP Peer. // NewTCPPeer returns a pointer to a TCP Peer.
@ -129,6 +144,7 @@ func NewTCPPeer(conn net.Conn, s *Server) *TCPPeer {
return &TCPPeer{ return &TCPPeer{
conn: conn, conn: conn,
send: make(chan *Message), send: make(chan *Message),
receive: make(chan *Message),
endpoint: e, endpoint: e,
s: s, s: s,
} }
@ -148,20 +164,17 @@ func (p *TCPPeer) addr() util.Endpoint {
return p.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. // callGetaddr will send the "getaddr" command to the remote.
func (p *TCPPeer) callGetaddr(msg *Message) { func (p *TCPPeer) callGetaddr(msg *Message) {
p.send <- msg p.send <- msg
} }
// disconnect closes the send channel and the underlying connection. // 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() { func (p *TCPPeer) disconnect() {
close(p.send)
p.conn.Close() p.conn.Close()
close(p.send)
close(p.receive)
} }
// writeLoop writes messages to the underlying TCP connection. // writeLoop writes messages to the underlying TCP connection.