mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 13:47:19 +00:00
Initial commit
This commit is contained in:
commit
cda7a31e4e
147 changed files with 10366 additions and 0 deletions
1
Dockerfile
Normal file
1
Dockerfile
Normal file
|
@ -0,0 +1 @@
|
|||
FROM vidsyhq/go-base:latest
|
69
Gopkg.toml
Normal file
69
Gopkg.toml
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/urfave/cli"
|
||||
version = "1.20.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/anthdm/rfc6979"
|
||||
|
||||
[[constraint]]
|
||||
name = "golang.org/x/text"
|
||||
version = "0.3.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.2.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
version = "1.0.5"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-yaml/yaml"
|
||||
version = "2.1.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-redis/redis"
|
||||
version = "6.10.2"
|
57
Makefile
Normal file
57
Makefile
Normal file
|
@ -0,0 +1,57 @@
|
|||
BRANCH = "master"
|
||||
BUILD_TIME = "$(shell date -u +\"%Y-%m-%dT%H:%M:%SZ\")"
|
||||
VERSION = $(shell cat ./VERSION)
|
||||
REPONAME = "neo-go"
|
||||
NETMODE ?= "privnet"
|
||||
|
||||
build:
|
||||
@echo "=> Building darwin binary"
|
||||
@go build -i -ldflags "-X github.com/CityOfZion/neo-go/config.Version=${VERSION}-dev -X github.com/CityOfZion/neo-go/config.BuildTime=${BUILD_TIME}" -o ./bin/neo-go ./cli/main.go
|
||||
|
||||
build-image:
|
||||
docker build -t cityofzion/neo-go --build-arg VERSION=${VERSION} .
|
||||
|
||||
build-linux:
|
||||
@echo "=> Building linux binary"
|
||||
@GOOS=linux go build -i -ldflags "-X github.com/CityOfZion/neo-go/config.Version=${VERSION}-dev -X github.com/CityOfZion/neo-go/config.BuildTime=${BUILD_TIME}" -o ./bin/neo-go ./cli/main.go
|
||||
|
||||
check-version:
|
||||
git fetch && (! git rev-list ${VERSION})
|
||||
|
||||
clean-cluster:
|
||||
@echo "=> Removing all containers and chain storage"
|
||||
@rm -rf chains/privnet-docker-one chains/privnet-docker-two chains/privnet-docker-three chains/privnet-docker-four
|
||||
@docker-compose stop
|
||||
@docker-compose rm -f
|
||||
|
||||
deps:
|
||||
@dep ensure
|
||||
|
||||
push-tag:
|
||||
git checkout ${BRANCH}
|
||||
git pull origin ${BRANCH}
|
||||
git tag ${VERSION}
|
||||
git push origin ${VERSION}
|
||||
|
||||
push-to-registry:
|
||||
@docker login -e ${DOCKER_EMAIL} -u ${DOCKER_USER} -p ${DOCKER_PASS}
|
||||
@docker tag CityOfZion/${REPONAME}:latest CityOfZion/${REPONAME}:${CIRCLE_TAG}
|
||||
@docker push CityOfZion/${REPONAME}:${CIRCLE_TAG}
|
||||
@docker push CityOfZion/${REPONAME}
|
||||
|
||||
run: build
|
||||
./bin/neo-go node -config-path ./config -${NETMODE}
|
||||
|
||||
run-cluster: build-linux
|
||||
@echo "=> Starting docker-compose cluster"
|
||||
@echo "=> Building container image"
|
||||
@docker-compose build
|
||||
@docker-compose up -d
|
||||
@echo "=> Tailing logs, exiting this prompt will not stop the cluster"
|
||||
@docker-compose logs -f
|
||||
|
||||
test:
|
||||
@go test ./... -cover
|
||||
|
||||
vet:
|
||||
@go vet ./...
|
1
VERSION
Normal file
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
|||
0.44.8
|
152
circle.yml
Normal file
152
circle.yml
Normal file
|
@ -0,0 +1,152 @@
|
|||
version: 2
|
||||
jobs:
|
||||
install_deps:
|
||||
working_directory: /go/src/github.com/CityOfZion/neo-go
|
||||
docker:
|
||||
- image: vidsyhq/go-builder:latest
|
||||
environment:
|
||||
BUILD: false
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
|
||||
- run: /scripts/build.sh
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
|
||||
paths:
|
||||
- vendor
|
||||
- /go/pkg
|
||||
build_image:
|
||||
working_directory: /go/src/github.com/CityOfZion/neo-go
|
||||
docker:
|
||||
- image: alpine
|
||||
steps:
|
||||
- run: apk update && apk add git make curl tar
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dependency-cache-{{ .Revision }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Install Docker client
|
||||
command: |
|
||||
set -x
|
||||
VER="17.03.0-ce"
|
||||
curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
|
||||
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
|
||||
mv /tmp/docker/* /usr/bin
|
||||
- run: make build-image
|
||||
test:
|
||||
working_directory: /go/src/github.com/CityOfZion/neo-go
|
||||
docker:
|
||||
- image: vidsyhq/go-builder:latest
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
|
||||
- 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-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
|
||||
- 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
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
|
||||
- run: make build
|
||||
- save_cache:
|
||||
key: dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
|
||||
paths:
|
||||
- bin/neo-go
|
||||
deploy:
|
||||
working_directory: /go/src/github.com/CityOfZion/neo-go
|
||||
docker:
|
||||
- image: alpine
|
||||
steps:
|
||||
- run: apk update && apk add git make curl tar
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
|
||||
- restore_cache:
|
||||
key: dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Install Docker client
|
||||
command: |
|
||||
set -x
|
||||
VER="17.03.0-ce"
|
||||
curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
|
||||
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
|
||||
mv /tmp/docker/* /usr/bin
|
||||
- run: make build-image
|
||||
- deploy:
|
||||
name: deploy
|
||||
command: make push-to-registry
|
||||
workflows:
|
||||
version: 2
|
||||
workflow:
|
||||
jobs:
|
||||
- install_deps:
|
||||
filters:
|
||||
tags:
|
||||
only: /[0-9]+\.[0-9]+\.[0-9]+/
|
||||
- test:
|
||||
requires:
|
||||
- install_deps
|
||||
filters:
|
||||
tags:
|
||||
only: /[0-9]+\.[0-9]+\.[0-9]+/
|
||||
- vet:
|
||||
requires:
|
||||
- install_deps
|
||||
filters:
|
||||
tags:
|
||||
only: /[0-9]+\.[0-9]+\.[0-9]+/
|
||||
- check_version:
|
||||
filters:
|
||||
branches:
|
||||
ignore: master
|
||||
- build_cli:
|
||||
requires:
|
||||
- install_deps
|
||||
filters:
|
||||
tags:
|
||||
only: /[0-9]+\.[0-9]+\.[0-9]+/
|
||||
- build_image:
|
||||
requires:
|
||||
- install_deps
|
||||
- build_cli
|
||||
filters:
|
||||
tags:
|
||||
only: /[0-9]+\.[0-9]+\.[0-9]+/
|
||||
- deploy:
|
||||
requires:
|
||||
- build_image
|
||||
- test
|
||||
- vet
|
||||
- check_version
|
||||
filters:
|
||||
tags:
|
||||
only:
|
||||
- /[0-9]+\.[0-9]+\.[0-9]+/
|
||||
branches:
|
||||
ignore: /.*/
|
5
cli/main.go
Normal file
5
cli/main.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
28
docs/contribution.md
Normal file
28
docs/contribution.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Contributions
|
||||
|
||||
|
||||
This document will state guidelines for submitting code.
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
In order to contribute to Golang, it is essential that you know Golang or language similar to Golang. It is also required that you are familiar with NEO infrastructure.
|
||||
|
||||
Reading:
|
||||
|
||||
http://docs.neo.org/en-us/network/network-protocol.html
|
||||
|
||||
https://tour.golang.org/welcome/1
|
||||
|
||||
https://golang.org/doc/effective_go.html#introduction
|
||||
|
||||
## 1. Open an Issue Prior
|
||||
|
||||
Please open an issue prior to working on the feature, stating the feature you would like to work on.
|
||||
|
||||
## 2. Unit test
|
||||
|
||||
Unit tests are important, when submitting a PR please ensure that you have at least 70% test coverage.
|
||||
|
||||
## 3. Avoid CGo
|
||||
|
||||
Due to the complications that come with using CGo, if using it, please provide justification before using it. If using CGo, can provide significant speed boosts and or a large part of the code can be processed in CGo, then we should be able to come to consensus on using it. Generally, it is better to write everything in Golang, so that the code can be maintained by other Gophers.
|
17
docs/conventions.md
Normal file
17
docs/conventions.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Conventions
|
||||
|
||||
This document will list conventions that this repo should follow. These are guidelines and if you believe that one should not be followed, then please state why in your PR. If you believe that a piece of code does not follow one of the conventions listed, then please open an issue before making any changes.
|
||||
|
||||
When submitting a new convention, please open an issue for discussion, if possible please highlight parts in the code where this convention could help the code readiblity or simplicity.
|
||||
|
||||
|
||||
## Avoid Named return paramters
|
||||
|
||||
|
||||
func example(test int) (num int) {
|
||||
a = test + 1
|
||||
num = a * test
|
||||
return
|
||||
}
|
||||
|
||||
In the above function we have used a named return paramter, which allows you to include a simple return statement without the variables you are returning. This practice can cause confusion when functions become large or the logic becomes complex, so these should be avoided.
|
12
pkg/Readme.md
Normal file
12
pkg/Readme.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# ReadMe
|
||||
|
||||
Currently this package is in Development.
|
||||
|
||||
|
||||
## References
|
||||
|
||||
btcd https://github.com/btcsuite/btcd
|
||||
|
||||
geth https://github.com/ethereum/go-ethereum
|
||||
|
||||
aeternity https://github.com/aeternity/elixir-node
|
285
pkg/addrmgr/addrmgr.go
Normal file
285
pkg/addrmgr/addrmgr.go
Normal file
|
@ -0,0 +1,285 @@
|
|||
package addrmgr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/peer"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTries = 5 // Try to connect five times, if we cannot then we say it is bad
|
||||
// We will keep bad addresses, so that we do not attempt to connect to them in the future.
|
||||
// nodes at the moment seem to send a large percentage of bad nodes.
|
||||
|
||||
maxFailures = 12 // This will be incremented when we connect to a node and for whatever reason, they keep disconnecting.
|
||||
// The number is high because there could be a period of time, where a node is behaving unrepsonsively and
|
||||
// we do not want to immmediately put it inside of the bad bucket.
|
||||
|
||||
// This is the maximum amount of addresses that the addrmgr will hold
|
||||
maxAllowedAddrs = 2000
|
||||
)
|
||||
|
||||
type addrStats struct {
|
||||
tries uint8
|
||||
failures uint8
|
||||
permanent bool // permanent = inbound, temp = outbound
|
||||
lastTried time.Time
|
||||
lastSuccess time.Time
|
||||
}
|
||||
|
||||
type Addrmgr struct {
|
||||
addrmtx sync.RWMutex
|
||||
goodAddrs map[*payload.Net_addr]addrStats
|
||||
newAddrs map[*payload.Net_addr]struct{}
|
||||
badAddrs map[*payload.Net_addr]addrStats
|
||||
knownList map[string]*payload.Net_addr // this contains all of the addresses in badAddrs, newAddrs and goodAddrs
|
||||
}
|
||||
|
||||
func New() *Addrmgr {
|
||||
|
||||
return &Addrmgr{
|
||||
sync.RWMutex{},
|
||||
make(map[*payload.Net_addr]addrStats, 100),
|
||||
make(map[*payload.Net_addr]struct{}, 100),
|
||||
make(map[*payload.Net_addr]addrStats, 100),
|
||||
make(map[string]*payload.Net_addr, 100),
|
||||
}
|
||||
}
|
||||
|
||||
//AddAddrs will add new add new addresses into the newaddr list
|
||||
// This is safe for concurrent access.
|
||||
func (a *Addrmgr) AddAddrs(newAddrs []*payload.Net_addr) {
|
||||
|
||||
newAddrs = removeDuplicates(newAddrs)
|
||||
|
||||
var nas []*payload.Net_addr
|
||||
for _, addr := range newAddrs {
|
||||
a.addrmtx.Lock()
|
||||
|
||||
if _, ok := a.knownList[addr.IPPort()]; !ok { // filter unique addresses
|
||||
nas = append(nas, addr)
|
||||
}
|
||||
a.addrmtx.Unlock()
|
||||
}
|
||||
|
||||
for _, addr := range nas {
|
||||
a.addrmtx.Lock()
|
||||
a.newAddrs[addr] = struct{}{}
|
||||
a.knownList[addr.IPPort()] = addr
|
||||
a.addrmtx.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Good returns the good addresses.
|
||||
// A good address is:
|
||||
// - Known
|
||||
// - has been successfully connected to in the last week
|
||||
// - Note: that while users are launching full nodes from their laptops, the one week marker will need to be modified.
|
||||
func (a *Addrmgr) Good() []payload.Net_addr {
|
||||
|
||||
var goodAddrs []payload.Net_addr
|
||||
|
||||
var oneWeekAgo = time.Now().Add(((time.Hour * 24) * 7) * -1)
|
||||
|
||||
a.addrmtx.RLock()
|
||||
// TODO: sort addresses, permanent ones go first
|
||||
for addr, stat := range a.goodAddrs {
|
||||
if stat.lastTried.Before(oneWeekAgo) {
|
||||
continue
|
||||
}
|
||||
goodAddrs = append(goodAddrs, *addr)
|
||||
|
||||
}
|
||||
a.addrmtx.RUnlock()
|
||||
|
||||
return goodAddrs
|
||||
}
|
||||
|
||||
// Unconnected Addrs are addresses in the newAddr List
|
||||
func (a *Addrmgr) Unconnected() []payload.Net_addr {
|
||||
|
||||
var unconnAddrs []payload.Net_addr
|
||||
|
||||
a.addrmtx.RLock()
|
||||
for addr := range a.newAddrs {
|
||||
unconnAddrs = append(unconnAddrs, *addr)
|
||||
}
|
||||
a.addrmtx.RUnlock()
|
||||
return unconnAddrs
|
||||
}
|
||||
|
||||
// Bad Addrs are addresses in the badAddr List
|
||||
// They are put there if we have tried to connect
|
||||
// to them in the past and it failed.
|
||||
func (a *Addrmgr) Bad() []payload.Net_addr {
|
||||
|
||||
var badAddrs []payload.Net_addr
|
||||
|
||||
a.addrmtx.RLock()
|
||||
for addr := range a.badAddrs {
|
||||
badAddrs = append(badAddrs, *addr)
|
||||
}
|
||||
a.addrmtx.RUnlock()
|
||||
return badAddrs
|
||||
}
|
||||
|
||||
// FetchMoreAddresses will return true if the numOfKnownAddrs are less than 100
|
||||
// This number is kept low because at the moment, there are not a lot of Good Addresses
|
||||
// Tests have shown that at most there are < 100
|
||||
func (a *Addrmgr) FetchMoreAddresses() bool {
|
||||
|
||||
var numOfKnownAddrs int
|
||||
|
||||
a.addrmtx.RLock()
|
||||
numOfKnownAddrs = len(a.knownList)
|
||||
a.addrmtx.RUnlock()
|
||||
|
||||
return numOfKnownAddrs < maxAllowedAddrs
|
||||
}
|
||||
|
||||
// ConnectionComplete will be called by the server when we have done the handshake
|
||||
// and Not when we have successfully Connected with net.Conn
|
||||
// It is to tell the AddrMgr that we have connected to a peer
|
||||
// This peer should already be known to the AddrMgr because
|
||||
// We send it to the Connmgr
|
||||
func (a *Addrmgr) ConnectionComplete(addressport string, inbound bool) {
|
||||
a.addrmtx.Lock()
|
||||
defer a.addrmtx.Unlock()
|
||||
|
||||
// if addrmgr does not know this, then we just return
|
||||
if _, ok := a.knownList[addressport]; !ok {
|
||||
fmt.Println("Connected to an unknown address:port ", addressport)
|
||||
return
|
||||
}
|
||||
|
||||
na := a.knownList[addressport]
|
||||
|
||||
// move it from newAddrs List to GoodAddr List
|
||||
stats := a.goodAddrs[na]
|
||||
stats.lastSuccess = time.Now()
|
||||
stats.lastTried = time.Now()
|
||||
stats.permanent = inbound
|
||||
stats.tries++
|
||||
a.goodAddrs[na] = stats
|
||||
|
||||
// remove it from new and bad, if it was there
|
||||
delete(a.newAddrs, na)
|
||||
delete(a.badAddrs, na)
|
||||
|
||||
// Unfortunately, Will have a lot of bad nodes because of people connecting to nodes
|
||||
// from their laptop. TODO Sort function in good will mitigate.
|
||||
|
||||
}
|
||||
|
||||
// Failed will be called by ConnMgr
|
||||
// It is used to tell the AddrMgr that they had tried connecting an address an have failed
|
||||
// This is concurrent safe
|
||||
func (a *Addrmgr) Failed(addressport string) {
|
||||
a.addrmtx.Lock()
|
||||
defer a.addrmtx.Unlock()
|
||||
|
||||
// if addrmgr does not know this, then we just return
|
||||
if _, ok := a.knownList[addressport]; !ok {
|
||||
fmt.Println("Connected to an unknown address:port ", addressport)
|
||||
return
|
||||
}
|
||||
|
||||
na := a.knownList[addressport]
|
||||
|
||||
// HMM: logic here could be simpler if we make it one list instead
|
||||
|
||||
if stats, ok := a.badAddrs[na]; ok {
|
||||
stats.lastTried = time.Now()
|
||||
stats.failures++
|
||||
stats.tries++
|
||||
if float32(stats.failures)/float32(stats.tries) > 0.8 && stats.tries > 5 {
|
||||
delete(a.badAddrs, na)
|
||||
return
|
||||
}
|
||||
a.badAddrs[na] = stats
|
||||
return
|
||||
}
|
||||
|
||||
if stats, ok := a.goodAddrs[na]; ok {
|
||||
fmt.Println("We have a good addr", na.IPPort())
|
||||
stats.lastTried = time.Now()
|
||||
stats.failures++
|
||||
stats.tries++
|
||||
if float32(stats.failures)/float32(stats.tries) > 0.5 && stats.tries > 10 {
|
||||
delete(a.goodAddrs, na)
|
||||
a.badAddrs[na] = stats
|
||||
return
|
||||
}
|
||||
a.goodAddrs[na] = stats
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := a.newAddrs[na]; ok {
|
||||
delete(a.newAddrs, na)
|
||||
a.badAddrs[na] = addrStats{}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//OnAddr is the responder for the Config file
|
||||
// when a OnAddr is received by a peer
|
||||
func (a *Addrmgr) OnAddr(p *peer.Peer, msg *payload.AddrMessage) {
|
||||
a.AddAddrs(msg.AddrList)
|
||||
}
|
||||
|
||||
// OnGetAddr Is called when a peer sends a request for the addressList
|
||||
// We will give them the best addresses we have from good.
|
||||
func (a *Addrmgr) OnGetAddr(p *peer.Peer, msg *payload.GetAddrMessage) {
|
||||
a.addrmtx.RLock()
|
||||
defer a.addrmtx.RUnlock()
|
||||
// Push most recent peers to peer
|
||||
addrMsg, err := payload.NewAddrMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, add := range a.Good() {
|
||||
addrMsg.AddNetAddr(&add)
|
||||
}
|
||||
|
||||
p.Write(addrMsg)
|
||||
}
|
||||
|
||||
// NewAddr will return an address for the external caller to
|
||||
// connect to. In our case, it will be the connection manager.
|
||||
func (a *Addrmgr) NewAddr() (string, error) {
|
||||
// For now it just returns a random value from unconnected
|
||||
// TODO: When an address is tried, the address manager is notified.
|
||||
// When asked for a new address, this should be taken into account
|
||||
// when choosing a new one, also the number of retries.
|
||||
unconnected := a.Unconnected()
|
||||
if len(unconnected) == 0 {
|
||||
return "", errors.New("No Addresses to give")
|
||||
}
|
||||
rand := rand.Intn(len(unconnected))
|
||||
return unconnected[rand].IPPort(), nil
|
||||
}
|
||||
|
||||
// https://www.dotnetperls.com/duplicates-go
|
||||
func removeDuplicates(elements []*payload.Net_addr) []*payload.Net_addr {
|
||||
|
||||
encountered := map[string]bool{}
|
||||
result := []*payload.Net_addr{}
|
||||
|
||||
for _, element := range elements {
|
||||
if encountered[element.IPPort()] == true {
|
||||
// Do not add duplicate.
|
||||
} else {
|
||||
// Record this element as an encountered element.
|
||||
encountered[element.IPPort()] = true
|
||||
// Append to result slice.
|
||||
result = append(result, element)
|
||||
}
|
||||
}
|
||||
// Return the new slice.
|
||||
return result
|
||||
}
|
167
pkg/addrmgr/addrmgr_test.go
Normal file
167
pkg/addrmgr/addrmgr_test.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
package addrmgr_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/addrmgr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewAddrs(t *testing.T) {
|
||||
|
||||
addrmgr := addrmgr.New()
|
||||
assert.NotEqual(t, nil, addrmgr)
|
||||
}
|
||||
func TestAddAddrs(t *testing.T) {
|
||||
|
||||
addrmgr := addrmgr.New()
|
||||
|
||||
ip := [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
|
||||
|
||||
ip = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // same
|
||||
addr2, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
|
||||
|
||||
ip = [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
|
||||
addr3, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
|
||||
|
||||
addrs := []*payload.Net_addr{addr, addr2, addr, addr3}
|
||||
|
||||
addrmgr.AddAddrs(addrs)
|
||||
|
||||
assert.Equal(t, 2, len(addrmgr.Unconnected()))
|
||||
assert.Equal(t, 0, len(addrmgr.Good()))
|
||||
assert.Equal(t, 0, len(addrmgr.Bad()))
|
||||
}
|
||||
|
||||
func TestFetchMoreAddress(t *testing.T) {
|
||||
|
||||
addrmgr := addrmgr.New()
|
||||
|
||||
addrs := []*payload.Net_addr{}
|
||||
|
||||
ip := make([]byte, 16)
|
||||
|
||||
for i := 0; i <= 2000; i++ { // Add more than maxAllowedAddrs
|
||||
rand.Read(ip)
|
||||
|
||||
var nip [16]byte
|
||||
copy(nip[:], ip[:16])
|
||||
|
||||
addr, _ := payload.NewNetAddr(0, nip, 1033, protocol.NodePeerService)
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
addrmgr.AddAddrs(addrs)
|
||||
|
||||
assert.Equal(t, false, addrmgr.FetchMoreAddresses())
|
||||
|
||||
}
|
||||
func TestConnComplete(t *testing.T) {
|
||||
|
||||
addrmgr := addrmgr.New()
|
||||
|
||||
ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
|
||||
addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
|
||||
|
||||
ip2 := [16]byte{2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
|
||||
addr2, _ := payload.NewNetAddr(0, ip2, 1033, protocol.NodePeerService)
|
||||
|
||||
ip3 := [16]byte{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
|
||||
addr3, _ := payload.NewNetAddr(0, ip3, 1033, protocol.NodePeerService)
|
||||
|
||||
addrs := []*payload.Net_addr{addr, addr2, addr3}
|
||||
|
||||
addrmgr.AddAddrs(addrs)
|
||||
|
||||
assert.Equal(t, len(addrs), len(addrmgr.Unconnected()))
|
||||
|
||||
// a successful connection
|
||||
addrmgr.ConnectionComplete(addr.IPPort(), true)
|
||||
addrmgr.ConnectionComplete(addr.IPPort(), true) // should have no change
|
||||
|
||||
assert.Equal(t, len(addrs)-1, len(addrmgr.Unconnected()))
|
||||
assert.Equal(t, 1, len(addrmgr.Good()))
|
||||
|
||||
// another successful connection
|
||||
addrmgr.ConnectionComplete(addr2.IPPort(), true)
|
||||
|
||||
assert.Equal(t, len(addrs)-2, len(addrmgr.Unconnected()))
|
||||
assert.Equal(t, 2, len(addrmgr.Good()))
|
||||
|
||||
}
|
||||
func TestAttempted(t *testing.T) {
|
||||
|
||||
addrmgr := addrmgr.New()
|
||||
|
||||
ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
|
||||
addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
|
||||
|
||||
addrs := []*payload.Net_addr{addr}
|
||||
|
||||
addrmgr.AddAddrs(addrs)
|
||||
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
|
||||
assert.Equal(t, 1, len(addrmgr.Bad())) // newAddrs was attmepted and failed. Move to Bad
|
||||
|
||||
}
|
||||
func TestAttemptedMoveFromGoodToBad(t *testing.T) {
|
||||
|
||||
addrmgr := addrmgr.New()
|
||||
|
||||
ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
|
||||
addr, _ := payload.NewNetAddr(0, ip, 1043, protocol.NodePeerService)
|
||||
|
||||
addrs := []*payload.Net_addr{addr}
|
||||
|
||||
addrmgr.AddAddrs(addrs)
|
||||
|
||||
addrmgr.ConnectionComplete(addr.IPPort(), true)
|
||||
addrmgr.ConnectionComplete(addr.IPPort(), true)
|
||||
addrmgr.ConnectionComplete(addr.IPPort(), true)
|
||||
|
||||
assert.Equal(t, 1, len(addrmgr.Good()))
|
||||
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
addrmgr.Failed(addr.IPPort())
|
||||
// over threshhold, and will be classed as a badAddr L251
|
||||
|
||||
assert.Equal(t, 0, len(addrmgr.Good()))
|
||||
assert.Equal(t, 1, len(addrmgr.Bad()))
|
||||
}
|
||||
|
||||
func TestGetAddress(t *testing.T) {
|
||||
|
||||
addrmgr := addrmgr.New()
|
||||
|
||||
ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
|
||||
addr, _ := payload.NewNetAddr(0, ip, 10333, protocol.NodePeerService)
|
||||
ip2 := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
|
||||
addr2, _ := payload.NewNetAddr(0, ip2, 10334, protocol.NodePeerService)
|
||||
ip3 := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
|
||||
addr3, _ := payload.NewNetAddr(0, ip3, 10335, protocol.NodePeerService)
|
||||
|
||||
addrs := []*payload.Net_addr{addr, addr2, addr3}
|
||||
|
||||
addrmgr.AddAddrs(addrs)
|
||||
|
||||
fetchAddr, err := addrmgr.NewAddr()
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
ipports := []string{addr.IPPort(), addr2.IPPort(), addr3.IPPort()}
|
||||
|
||||
assert.Contains(t, ipports, fetchAddr)
|
||||
|
||||
}
|
0
pkg/addrmgr/peers.json
Normal file
0
pkg/addrmgr/peers.json
Normal file
23
pkg/addrmgr/readme.md
Normal file
23
pkg/addrmgr/readme.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Package - Address Manager
|
||||
|
||||
This package can be used as a standalone to manage addresses on the NEO network. Although you can use it for other chains, the config parameters have been modified for the state of NEO as of now.
|
||||
|
||||
## Responsibility
|
||||
|
||||
To manage the data that the node knows about addresses; data on good addresses, retry rates, failures, and lastSuccessful connection will be managed by the address manager. Also, If a service wants to fetch good address then it will be asked for from the address manager.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- On GetAddr it will give a list of good addresses to connect to
|
||||
|
||||
- On Addr it will receive addresses and remove any duplicates.
|
||||
|
||||
- General Management of Addresses
|
||||
|
||||
- Periodically saves the peers and metadata about peer into a .json file for retrieval (Not implemented yet)
|
||||
|
||||
|
||||
## Note
|
||||
|
||||
The Address manager will not deal with making connections to nodes. Please check the tests for the use cases for this package.
|
187
pkg/blockchain/blockchain.go
Normal file
187
pkg/blockchain/blockchain.go
Normal file
|
@ -0,0 +1,187 @@
|
|||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/chainparams"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/database"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBlockValidation = errors.New("Block failed sanity check")
|
||||
ErrBlockVerification = errors.New("Block failed to be consistent with the current blockchain")
|
||||
)
|
||||
|
||||
// Blockchain holds the state of the chain
|
||||
type Chain struct {
|
||||
db *database.LDB
|
||||
net protocol.Magic
|
||||
}
|
||||
|
||||
func New(db *database.LDB, net protocol.Magic) *Chain {
|
||||
|
||||
marker := []byte("HasBeenInitialisedAlready")
|
||||
|
||||
_, err := db.Get(marker)
|
||||
|
||||
if err != nil {
|
||||
// This is a new db
|
||||
fmt.Println("New Database initialisation")
|
||||
db.Put(marker, []byte{})
|
||||
|
||||
// We add the genesis block into the db
|
||||
// along with the indexes for it
|
||||
if net == protocol.MainNet {
|
||||
|
||||
genesisBlock, err := hex.DecodeString(chainparams.GenesisBlock)
|
||||
if err != nil {
|
||||
fmt.Println("Could not add genesis header into db")
|
||||
db.Delete(marker)
|
||||
return nil
|
||||
}
|
||||
r := bytes.NewReader(genesisBlock)
|
||||
b := payload.Block{}
|
||||
err = b.Decode(r)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("could not Decode genesis block")
|
||||
db.Delete(marker)
|
||||
return nil
|
||||
}
|
||||
err = db.AddHeader(&b.BlockBase)
|
||||
if err != nil {
|
||||
fmt.Println("Could not add genesis header")
|
||||
db.Delete(marker)
|
||||
return nil
|
||||
}
|
||||
err = db.AddTransactions(b.Hash, b.Txs)
|
||||
if err != nil {
|
||||
fmt.Println("Could not add Genesis Transactions")
|
||||
db.Delete(marker)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if net == protocol.TestNet {
|
||||
fmt.Println("TODO: Setup the genesisBlock for TestNet")
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
return &Chain{
|
||||
db,
|
||||
net,
|
||||
}
|
||||
}
|
||||
func (c *Chain) AddBlock(msg *payload.BlockMessage) error {
|
||||
if !validateBlock(msg) {
|
||||
return ErrBlockValidation
|
||||
}
|
||||
|
||||
if !c.verifyBlock(msg) {
|
||||
return ErrBlockVerification
|
||||
}
|
||||
|
||||
fmt.Println("Block Hash is ", msg.Hash.String())
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err := msg.Encode(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.db.Put(msg.Hash.Bytes(), buf.Bytes())
|
||||
|
||||
}
|
||||
|
||||
// validateBlock will check the transactions,
|
||||
// merkleroot is good, signature is good,every that does not require state
|
||||
// This may be moved to the syncmanager. This function should not be done in a seperate go-routine
|
||||
// We are intentionally blocking here because if the block is invalid, we will
|
||||
// disconnect from the peer.
|
||||
// We could have this return an error instead; where the error could even
|
||||
// say where the validation failed, for the logs.
|
||||
func validateBlock(msg *payload.BlockMessage) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Chain) verifyBlock(msg *payload.BlockMessage) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// This will add a header into the db,
|
||||
// indexing it also, this method will not
|
||||
// run any checks, like if it links with a header
|
||||
// previously in the db
|
||||
// func (c *Chain) addHeaderNoCheck(header *payload.BlockBase) error {
|
||||
|
||||
// }
|
||||
|
||||
//addHeaders is not safe for concurrent access
|
||||
func (c *Chain) ValidateHeaders(msg *payload.HeadersMessage) error {
|
||||
|
||||
table := database.NewTable(c.db, database.HEADER)
|
||||
|
||||
latestHash, err := table.Get(database.LATESTHEADER)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := latestHash
|
||||
val, err := table.Get(key)
|
||||
|
||||
lastHeader := &payload.BlockBase{}
|
||||
err = lastHeader.Decode(bytes.NewReader(val))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO?:Maybe we should sort these headers using the Index
|
||||
// We should not get them in mixed order, but doing it would not be expensive
|
||||
// If they are already in order
|
||||
|
||||
// Do checks on headers
|
||||
for _, currentHeader := range msg.Headers {
|
||||
|
||||
if lastHeader == nil {
|
||||
// This should not happen as genesis header is added if new
|
||||
// database, however we check nonetheless
|
||||
return errors.New("Previous Header is nil")
|
||||
}
|
||||
|
||||
// Check current hash links with previous
|
||||
if currentHeader.PrevHash != lastHeader.Hash {
|
||||
return errors.New("Last Header hash != current header Prev hash")
|
||||
}
|
||||
|
||||
// Check current Index is one more than the previous Index
|
||||
if currentHeader.Index != lastHeader.Index+1 {
|
||||
return errors.New("Last Header Index != current header Index")
|
||||
}
|
||||
|
||||
// Check current timestamp is more than the previous header's timestamp
|
||||
if lastHeader.Timestamp > currentHeader.Timestamp {
|
||||
return errors.New("Timestamp of Previous Header is more than Timestamp of current Header")
|
||||
}
|
||||
|
||||
// NONONO:Do not check if current is more than 15 secs in future
|
||||
// some blocks had delay from forks in past.
|
||||
|
||||
// NOTE: These are the only non-contextual checks we can do without the blockchain state
|
||||
lastHeader = currentHeader
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Chain) AddHeaders(msg *payload.HeadersMessage) error {
|
||||
for _, header := range msg.Headers {
|
||||
if err := c.db.AddHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
7
pkg/chainparams/asset.go
Normal file
7
pkg/chainparams/asset.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package chainparams
|
||||
|
||||
// GAS is the scripthash native neo asset: GAS
|
||||
var GAS = "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
||||
|
||||
// NEO is the scripthash native neo asset: NEO
|
||||
var NEO = "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
|
51
pkg/chainparams/config.go
Normal file
51
pkg/chainparams/config.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package chainparams
|
||||
|
||||
// For now we will just use this to store the
|
||||
// peers, to test peer data
|
||||
// Once complete, it will store the genesis params for testnet and mainnet
|
||||
|
||||
var mainnetSeedList = []string{
|
||||
"seed1.neo.org:10333", // NOP
|
||||
"seed2.neo.org:10333", // YEP
|
||||
"seed3.neo.org:10333", // YEP
|
||||
"seed4.neo.org:10333", // NOP
|
||||
"seed5.neo.org:10333", // YEP
|
||||
"13.59.52.94:10333", // NOP
|
||||
"18.220.214.143:10333", // NOP
|
||||
"13.58.198.112:10333", // NOP
|
||||
"13.59.14.206:10333", // NOP
|
||||
"18.216.9.7:10333", // NOP
|
||||
}
|
||||
|
||||
//MainnetSeedList is a string slice containing the initial seeds from protocol.mainnet
|
||||
// That are replying
|
||||
var MainnetSeedList = []string{
|
||||
"seed2.neo.org:10333",
|
||||
"seed3.neo.org:10333",
|
||||
"seed5.neo.org:10333",
|
||||
"http://seed1.ngd.network:10333",
|
||||
"http://seed2.ngd.network:10333",
|
||||
"http://seed3.ngd.network:10333",
|
||||
"http://seed4.ngd.network:10333",
|
||||
"http://seed5.ngd.network:10333",
|
||||
"http://seed6.ngd.network:10333",
|
||||
"http://seed7.ngd.network:10333",
|
||||
"http://seed8.ngd.network:10333",
|
||||
"http://seed9.ngd.network:10333",
|
||||
"http://seed10.ngd.network:10333",
|
||||
}
|
||||
|
||||
var testNetSeedList = []string{
|
||||
|
||||
"18.218.97.227:20333",
|
||||
"18.219.30.120:20333",
|
||||
"18.219.13.91:20333",
|
||||
"13.59.116.121:20333",
|
||||
"18.218.255.178:20333",
|
||||
}
|
||||
|
||||
// GenesisHash for mainnet
|
||||
var GenesisHash = "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf"
|
||||
|
||||
// GenesisBlock for mainnet
|
||||
var GenesisBlock = "000000000000000000000000000000000000000000000000000000000000000000000000f41bc036e39b0d6b0579c851c6fde83af802fa4e57bec0bc3365eae3abf43f8065fc8857000000001dac2b7c0000000059e75d652b5d3827bf04c165bbe9ef95cca4bf55010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff28623005fa99d93303775fe50ca119c327759313eccfa1c01000151"
|
25
pkg/connmgr/config.go
Normal file
25
pkg/connmgr/config.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package connmgr
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// Config contains all methods which will be set by the caller to setup the connection manager.
|
||||
type Config struct {
|
||||
// GetAddress will return a single address for the connection manager to connect to
|
||||
GetAddress func() (string, error)
|
||||
|
||||
// OnConnection is called by the connection manager when
|
||||
// we successfully connect to a peer
|
||||
// The caller should ideally inform the address manager that we have connected to this address in this function
|
||||
OnConnection func(conn net.Conn, addr string)
|
||||
|
||||
// OnAccept will take a established connection
|
||||
OnAccept func(net.Conn)
|
||||
|
||||
// Port is the port in the format "10333"
|
||||
Port string
|
||||
|
||||
// DialTimeout is the amount of time, before we can disconnect a pending dialed connection
|
||||
DialTimeout int
|
||||
}
|
272
pkg/connmgr/connmgr.go
Normal file
272
pkg/connmgr/connmgr.go
Normal file
|
@ -0,0 +1,272 @@
|
|||
package connmgr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util/ip"
|
||||
)
|
||||
|
||||
var (
|
||||
// maxOutboundConn is the maximum number of active peers
|
||||
// that the connection manager will try to have
|
||||
maxOutboundConn = 10
|
||||
|
||||
// maxRetries is the maximum amount of successive retries that
|
||||
// we can have before we stop dialing that peer
|
||||
maxRetries = uint8(5)
|
||||
)
|
||||
|
||||
// Connmgr manages pending/active/failed cnnections
|
||||
type Connmgr struct {
|
||||
config Config
|
||||
PendingList map[string]*Request
|
||||
ConnectedList map[string]*Request
|
||||
actionch chan func()
|
||||
}
|
||||
|
||||
//New creates a new connection manager
|
||||
func New(cfg Config) *Connmgr {
|
||||
cnnmgr := &Connmgr{
|
||||
cfg,
|
||||
make(map[string]*Request),
|
||||
make(map[string]*Request),
|
||||
make(chan func(), 300),
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
||||
ip := iputils.GetLocalIP()
|
||||
addrPort := ip.String() + ":" + cfg.Port
|
||||
|
||||
listener, err := net.Listen("tcp", addrPort)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error connecting to outbound ", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
listener.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
|
||||
conn, err := listener.Accept()
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// TODO(kev): in the OnAccept the connection address will be added to AddrMgr
|
||||
go cfg.OnAccept(conn)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return cnnmgr
|
||||
}
|
||||
|
||||
// NewRequest will make a new connection
|
||||
// Gets the address from address func in config
|
||||
// Then dials it and assigns it to pending
|
||||
func (c *Connmgr) NewRequest() {
|
||||
|
||||
// Fetch address
|
||||
addr, err := c.config.GetAddress()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting address", err)
|
||||
}
|
||||
|
||||
// empty request item
|
||||
r := &Request{}
|
||||
|
||||
r.Addr = addr
|
||||
fmt.Println("Connecting")
|
||||
c.Connect(r)
|
||||
|
||||
}
|
||||
|
||||
func (c *Connmgr) Connect(r *Request) error {
|
||||
|
||||
r.Retries++
|
||||
|
||||
conn, err := c.Dial(r.Addr)
|
||||
if err != nil {
|
||||
c.failed(r)
|
||||
return err
|
||||
}
|
||||
|
||||
r.Conn = conn
|
||||
r.Inbound = true
|
||||
|
||||
// r.Permanent is set by the caller. default is false
|
||||
// The permanent connections will be the ones that are hardcoded, e.g seed3.ngd.network
|
||||
|
||||
return c.connected(r)
|
||||
}
|
||||
|
||||
func (cm *Connmgr) Disconnect(addr string) {
|
||||
|
||||
// fetch from connected list
|
||||
r, ok := cm.ConnectedList[addr]
|
||||
|
||||
if !ok {
|
||||
// If not in connected, check pending
|
||||
r, ok = cm.PendingList[addr]
|
||||
}
|
||||
|
||||
cm.disconnected(r)
|
||||
|
||||
}
|
||||
|
||||
// Dial is used to dial up connections given the addres and ip in the form address:port
|
||||
func (c *Connmgr) Dial(addr string) (net.Conn, error) {
|
||||
dialTimeout := 1 * time.Second
|
||||
conn, err := net.DialTimeout("tcp", addr, dialTimeout)
|
||||
if err != nil {
|
||||
if !isConnected() {
|
||||
return nil, errors.New("Fatal Error: You do not seem to be connected to the internet")
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
func (cm *Connmgr) failed(r *Request) {
|
||||
|
||||
cm.actionch <- func() {
|
||||
// priority to check if it is permanent or inbound
|
||||
// if so then these peers are valuable in NEO and so we will just retry another time
|
||||
if r.Inbound || r.Permanent {
|
||||
|
||||
multiplier := time.Duration(r.Retries * 10)
|
||||
time.AfterFunc(multiplier*time.Second,
|
||||
func() {
|
||||
cm.Connect(r)
|
||||
},
|
||||
)
|
||||
// if not then we should check if this request has had maxRetries
|
||||
// if it has then get a new address
|
||||
// if not then call Connect on it again
|
||||
} else if r.Retries > maxRetries {
|
||||
if cm.config.GetAddress != nil {
|
||||
go cm.NewRequest()
|
||||
}
|
||||
fmt.Println("This peer has been tried the maximum amount of times and a source of new address has not been specified.")
|
||||
} else {
|
||||
go cm.Connect(r)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Disconnected is called when a peer disconnects.
|
||||
// we take the addr from peer, which is also it's key in the map
|
||||
// and we use it to remove it from the connectedList
|
||||
func (c *Connmgr) disconnected(r *Request) error {
|
||||
|
||||
errChan := make(chan error, 0)
|
||||
|
||||
c.actionch <- func() {
|
||||
|
||||
var err error
|
||||
|
||||
if r == nil {
|
||||
err = errors.New("Request object is nil")
|
||||
}
|
||||
|
||||
r2 := *r // dereference it, so that r.Addr is not lost on delete
|
||||
|
||||
// if for some reason the underlying connection is not closed, close it
|
||||
r.Conn.Close()
|
||||
r.Conn = nil
|
||||
// if for some reason it is in pending list, remove it
|
||||
delete(c.PendingList, r.Addr)
|
||||
delete(c.ConnectedList, r.Addr)
|
||||
c.failed(&r2)
|
||||
errChan <- err
|
||||
}
|
||||
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
//Connected is called when the connection manager
|
||||
// makes a successful connection.
|
||||
func (c *Connmgr) connected(r *Request) error {
|
||||
|
||||
errorChan := make(chan error, 0)
|
||||
|
||||
c.actionch <- func() {
|
||||
|
||||
var err error
|
||||
|
||||
// This should not be the case, since we connected
|
||||
// Keeping it here to be safe
|
||||
if r == nil {
|
||||
err = errors.New("Request object as nil inside of the connected function")
|
||||
}
|
||||
|
||||
// reset retries to 0
|
||||
r.Retries = 0
|
||||
|
||||
// add to connectedList
|
||||
c.ConnectedList[r.Addr] = r
|
||||
|
||||
// remove from pending if it was there
|
||||
delete(c.PendingList, r.Addr)
|
||||
|
||||
if c.config.OnConnection != nil {
|
||||
c.config.OnConnection(r.Conn, r.Addr)
|
||||
}
|
||||
|
||||
fmt.Println("Error connected", err)
|
||||
|
||||
errorChan <- err
|
||||
}
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
// Pending is synchronous, we do not want to continue with logic
|
||||
// until we are certain it has been added to the pendingList
|
||||
func (c *Connmgr) pending(r *Request) error {
|
||||
|
||||
errChan := make(chan error, 0)
|
||||
|
||||
c.actionch <- func() {
|
||||
|
||||
var err error
|
||||
|
||||
if r == nil {
|
||||
err = errors.New("Error : Request object is nil")
|
||||
}
|
||||
|
||||
c.PendingList[r.Addr] = r
|
||||
errChan <- err
|
||||
}
|
||||
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
func (c *Connmgr) Run() {
|
||||
go c.loop()
|
||||
}
|
||||
|
||||
func (c *Connmgr) loop() {
|
||||
for {
|
||||
select {
|
||||
case f := <-c.actionch:
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/50056144/check-for-internet-connection-from-application
|
||||
func isConnected() (ok bool) {
|
||||
_, err := http.Get("http://clients3.google.com/generate_204")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
106
pkg/connmgr/connmgr_test.go
Normal file
106
pkg/connmgr/connmgr_test.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
package connmgr_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/connmgr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDial(t *testing.T) {
|
||||
cfg := connmgr.Config{
|
||||
GetAddress: nil,
|
||||
OnConnection: nil,
|
||||
OnAccept: nil,
|
||||
Port: "",
|
||||
DialTimeout: 0,
|
||||
}
|
||||
|
||||
cm := connmgr.New(cfg)
|
||||
cm.Run()
|
||||
|
||||
ipport := "google.com:80" // google unlikely to go offline, a better approach to test Dialing is welcome.
|
||||
|
||||
conn, err := cm.Dial(ipport)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.NotEqual(t, nil, conn)
|
||||
}
|
||||
func TestConnect(t *testing.T) {
|
||||
cfg := connmgr.Config{
|
||||
GetAddress: nil,
|
||||
OnConnection: nil,
|
||||
OnAccept: nil,
|
||||
Port: "",
|
||||
DialTimeout: 0,
|
||||
}
|
||||
|
||||
cm := connmgr.New(cfg)
|
||||
cm.Run()
|
||||
|
||||
ipport := "google.com:80"
|
||||
|
||||
r := connmgr.Request{Addr: ipport}
|
||||
|
||||
cm.Connect(&r)
|
||||
|
||||
assert.Equal(t, 1, len(cm.ConnectedList))
|
||||
|
||||
}
|
||||
func TestNewRequest(t *testing.T) {
|
||||
|
||||
address := "google.com:80"
|
||||
|
||||
var getAddr = func() (string, error) {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
cfg := connmgr.Config{
|
||||
GetAddress: getAddr,
|
||||
OnConnection: nil,
|
||||
OnAccept: nil,
|
||||
Port: "",
|
||||
DialTimeout: 0,
|
||||
}
|
||||
|
||||
cm := connmgr.New(cfg)
|
||||
|
||||
cm.Run()
|
||||
|
||||
cm.NewRequest()
|
||||
|
||||
if _, ok := cm.ConnectedList[address]; ok {
|
||||
assert.Equal(t, true, ok)
|
||||
assert.Equal(t, 1, len(cm.ConnectedList))
|
||||
return
|
||||
}
|
||||
|
||||
assert.Fail(t, "Could not find the address in the connected lists")
|
||||
|
||||
}
|
||||
func TestDisconnect(t *testing.T) {
|
||||
|
||||
address := "google.com:80"
|
||||
|
||||
var getAddr = func() (string, error) {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
cfg := connmgr.Config{
|
||||
GetAddress: getAddr,
|
||||
OnConnection: nil,
|
||||
OnAccept: nil,
|
||||
Port: "",
|
||||
DialTimeout: 0,
|
||||
}
|
||||
|
||||
cm := connmgr.New(cfg)
|
||||
|
||||
cm.Run()
|
||||
|
||||
cm.NewRequest()
|
||||
|
||||
cm.Disconnect(address)
|
||||
|
||||
assert.Equal(t, 0, len(cm.ConnectedList))
|
||||
|
||||
}
|
26
pkg/connmgr/readme.md
Normal file
26
pkg/connmgr/readme.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Package - Connection Manager
|
||||
|
||||
## Responsibility
|
||||
|
||||
- Manages the active, failed and pending connections for the node.
|
||||
|
||||
## Features
|
||||
|
||||
- Takes an address, dials it and packages it into a request to manage.
|
||||
|
||||
- Retry failed connections.
|
||||
|
||||
- Uses one function as a source for it's addresses. It does not manage addresses.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The following methods are exposed from the Connection manager:
|
||||
|
||||
- NewRequest() : This will fetch a new address and connect to it.
|
||||
|
||||
- Connect(r *Request) : This takes a Request object and connects to it. It follow the same logic as NewRequest() however instead of getting the address from the datasource given upon initialisation, you directly feed the address you want to connect to.
|
||||
|
||||
- Disconnect(addrport string) : Given an address:port, this will disconnect it, close the connection and remove it from the connected and pending list, if it was there.
|
||||
|
||||
- Dial(addrport string) (net.Conn, error) : Given an address:port, this will connect to it and return a pointer to a connection plus a nil error if successful, or nil with an error.
|
13
pkg/connmgr/request.go
Normal file
13
pkg/connmgr/request.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package connmgr
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Conn net.Conn
|
||||
Addr string
|
||||
Permanent bool
|
||||
Inbound bool
|
||||
Retries uint8 // should not be trying more than 255 tries
|
||||
}
|
93
pkg/crypto/aes/aes256.go
Executable file
93
pkg/crypto/aes/aes256.go
Executable file
|
@ -0,0 +1,93 @@
|
|||
package aes
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
// AESEncrypt encrypts the key with the given source.
|
||||
func Encrypt(src, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ecb := newECBEncrypter(block)
|
||||
out := make([]byte, len(src))
|
||||
ecb.CryptBlocks(out, src)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AESDecrypt decrypts the encrypted source with the given key.
|
||||
func Decrypt(crypted, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockMode := newECBDecrypter(block)
|
||||
out := make([]byte, len(crypted))
|
||||
blockMode.CryptBlocks(out, crypted)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type ecb struct {
|
||||
b cipher.Block
|
||||
blockSize int
|
||||
}
|
||||
|
||||
func newECB(b cipher.Block) *ecb {
|
||||
return &ecb{
|
||||
b: b,
|
||||
blockSize: b.BlockSize(),
|
||||
}
|
||||
}
|
||||
|
||||
type ecbEncrypter ecb
|
||||
|
||||
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbEncrypter)(newECB(b))
|
||||
}
|
||||
|
||||
func (ecb *ecbEncrypter) BlockSize() int {
|
||||
return ecb.blockSize
|
||||
}
|
||||
|
||||
func (ecb *ecbEncrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%ecb.blockSize != 0 {
|
||||
panic("crypto/cipher: input not full blocks")
|
||||
}
|
||||
if len(dst) < len(src) {
|
||||
panic("crypto/cipher: output smaller than input")
|
||||
}
|
||||
for len(src) > 0 {
|
||||
ecb.b.Encrypt(dst, src[:ecb.blockSize])
|
||||
src = src[ecb.blockSize:]
|
||||
dst = dst[ecb.blockSize:]
|
||||
}
|
||||
}
|
||||
|
||||
type ecbDecrypter ecb
|
||||
|
||||
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbDecrypter)(newECB(b))
|
||||
}
|
||||
|
||||
func (ecb ecbDecrypter) BlockSize() int {
|
||||
return ecb.blockSize
|
||||
}
|
||||
|
||||
func (ecb *ecbDecrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%ecb.blockSize != 0 {
|
||||
panic("crypto/cipher: input not full blocks")
|
||||
}
|
||||
if len(dst) < len(src) {
|
||||
panic("crypto/cipher: output smaller than input")
|
||||
}
|
||||
for len(src) > 0 {
|
||||
ecb.b.Decrypt(dst, src[:ecb.blockSize])
|
||||
src = src[ecb.blockSize:]
|
||||
dst = dst[ecb.blockSize:]
|
||||
}
|
||||
}
|
78
pkg/crypto/base58/base58.go
Executable file
78
pkg/crypto/base58/base58.go
Executable file
|
@ -0,0 +1,78 @@
|
|||
package base58
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const prefix rune = '1'
|
||||
|
||||
var decodeMap = map[rune]int64{
|
||||
'1': 0, '2': 1, '3': 2, '4': 3, '5': 4,
|
||||
'6': 5, '7': 6, '8': 7, '9': 8, 'A': 9,
|
||||
'B': 10, 'C': 11, 'D': 12, 'E': 13, 'F': 14,
|
||||
'G': 15, 'H': 16, 'J': 17, 'K': 18, 'L': 19,
|
||||
'M': 20, 'N': 21, 'P': 22, 'Q': 23, 'R': 24,
|
||||
'S': 25, 'T': 26, 'U': 27, 'V': 28, 'W': 29,
|
||||
'X': 30, 'Y': 31, 'Z': 32, 'a': 33, 'b': 34,
|
||||
'c': 35, 'd': 36, 'e': 37, 'f': 38, 'g': 39,
|
||||
'h': 40, 'i': 41, 'j': 42, 'k': 43, 'm': 44,
|
||||
'n': 45, 'o': 46, 'p': 47, 'q': 48, 'r': 49,
|
||||
's': 50, 't': 51, 'u': 52, 'v': 53, 'w': 54,
|
||||
'x': 55, 'y': 56, 'z': 57,
|
||||
}
|
||||
|
||||
// Base58Decode decodes the base58 encoded string.
|
||||
func Decode(s string) ([]byte, error) {
|
||||
var (
|
||||
startIndex = 0
|
||||
zero = 0
|
||||
)
|
||||
for i, c := range s {
|
||||
if c == prefix {
|
||||
zero++
|
||||
} else {
|
||||
startIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
n = big.NewInt(0)
|
||||
div = big.NewInt(58)
|
||||
)
|
||||
for _, c := range s[startIndex:] {
|
||||
charIndex, ok := decodeMap[c]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"invalid character '%c' when decoding this base58 string: '%s'", c, s,
|
||||
)
|
||||
}
|
||||
n.Add(n.Mul(n, div), big.NewInt(charIndex))
|
||||
}
|
||||
|
||||
out := n.Bytes()
|
||||
buf := make([]byte, (zero + len(out)))
|
||||
copy(buf[zero:], out[:])
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Base58Encode encodes a byte slice to be a base58 encoded string.
|
||||
func Encode(bytes []byte) string {
|
||||
var (
|
||||
lookupTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
x = new(big.Int).SetBytes(bytes)
|
||||
r = new(big.Int)
|
||||
m = big.NewInt(58)
|
||||
zero = big.NewInt(0)
|
||||
encoded string
|
||||
)
|
||||
|
||||
for x.Cmp(zero) > 0 {
|
||||
x.QuoRem(x, m, r)
|
||||
encoded = string(lookupTable[r.Int64()]) + encoded
|
||||
}
|
||||
|
||||
return encoded
|
||||
}
|
32
pkg/crypto/base58/base58_test.go
Executable file
32
pkg/crypto/base58/base58_test.go
Executable file
|
@ -0,0 +1,32 @@
|
|||
package base58
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
input := "1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX"
|
||||
|
||||
data, err := Decode(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2"
|
||||
actual := hex.EncodeToString(data)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
func TestEncode(t *testing.T) {
|
||||
input := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2"
|
||||
|
||||
inputBytes, _ := hex.DecodeString(input)
|
||||
|
||||
data := Encode(inputBytes)
|
||||
|
||||
expected := "F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" // Removed the 1 as it is not checkEncoding
|
||||
actual := data
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
18
pkg/crypto/elliptic/Readme.md
Executable file
18
pkg/crypto/elliptic/Readme.md
Executable file
|
@ -0,0 +1,18 @@
|
|||
## Package - Elliptic
|
||||
|
||||
### Why
|
||||
|
||||
The curve and arithmetic functions have been modularised, so that curves can be swapped in and out, without effecting the functionality.
|
||||
|
||||
The modular arithmetic used is not specialised for a specific curve.
|
||||
|
||||
In order to use this package, you must declare an ellipticcurve struct and then set the curve.
|
||||
|
||||
Example:
|
||||
|
||||
`
|
||||
|
||||
curve = NewEllipticCurve(Secp256k1)
|
||||
|
||||
`
|
||||
If no curve is set, the default curve is the r1 curve used for NEO. The tests are done using the k1 curve, so in the elliptic_test.go file, the curve is changed accordingly.
|
58
pkg/crypto/elliptic/curves.go
Executable file
58
pkg/crypto/elliptic/curves.go
Executable file
|
@ -0,0 +1,58 @@
|
|||
package elliptic
|
||||
|
||||
/*
|
||||
This file was originally made by vsergeev.
|
||||
|
||||
Modifications have been made under the MIT license.
|
||||
License: MIT
|
||||
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var curve Curve
|
||||
|
||||
type curveType string
|
||||
|
||||
const (
|
||||
Secp256r1 curveType = "Secp256r1"
|
||||
Secp256k1 curveType = "Secp256k1"
|
||||
)
|
||||
|
||||
func (ChosenCurve *Curve) SetCurveSecp256r1() {
|
||||
ChosenCurve.P, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16) //Q
|
||||
ChosenCurve.A, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16)
|
||||
ChosenCurve.B, _ = new(big.Int).SetString("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16)
|
||||
ChosenCurve.G.X, _ = new(big.Int).SetString("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16)
|
||||
ChosenCurve.G.Y, _ = new(big.Int).SetString("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)
|
||||
ChosenCurve.N, _ = new(big.Int).SetString("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16)
|
||||
ChosenCurve.H, _ = new(big.Int).SetString("01", 16)
|
||||
ChosenCurve.Name = "Secp256r1"
|
||||
}
|
||||
|
||||
func (ChosenCurve *Curve) SetCurveSecp256k1() {
|
||||
ChosenCurve.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16)
|
||||
ChosenCurve.A, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000000", 16)
|
||||
ChosenCurve.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16)
|
||||
ChosenCurve.G.X, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16)
|
||||
ChosenCurve.G.Y, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
|
||||
ChosenCurve.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
|
||||
ChosenCurve.H, _ = new(big.Int).SetString("01", 16)
|
||||
ChosenCurve.Name = "Secp256k1"
|
||||
}
|
||||
|
||||
func NewEllipticCurve(ct curveType) Curve {
|
||||
var curve Curve
|
||||
switch ct {
|
||||
case Secp256k1:
|
||||
curve.SetCurveSecp256k1()
|
||||
case Secp256r1:
|
||||
curve.SetCurveSecp256r1()
|
||||
default:
|
||||
curve.SetCurveSecp256r1()
|
||||
}
|
||||
return curve
|
||||
}
|
317
pkg/crypto/elliptic/elliptic.go
Executable file
317
pkg/crypto/elliptic/elliptic.go
Executable file
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
This file has been modified under the MIT license.
|
||||
Original: https://github.com/vsergeev/btckeygenie
|
||||
*/
|
||||
|
||||
package elliptic
|
||||
|
||||
import (
|
||||
nativeelliptic "crypto/elliptic"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Point represents a point on an EllipticCurve.
|
||||
type Point struct {
|
||||
X *big.Int
|
||||
Y *big.Int
|
||||
}
|
||||
|
||||
/* y**2 = x**3 + a*x + b % p */
|
||||
// Curve represents the parameters of a short Weierstrass equation elliptic curve.
|
||||
type Curve struct {
|
||||
A *big.Int
|
||||
B *big.Int
|
||||
P *big.Int
|
||||
G Point
|
||||
N *big.Int
|
||||
H *big.Int
|
||||
Name string
|
||||
}
|
||||
|
||||
// dump dumps the bytes of a point for debugging.
|
||||
func (p *Point) dump() {
|
||||
fmt.Print(p.format())
|
||||
}
|
||||
|
||||
// format formats the bytes of a point for debugging.
|
||||
func (p *Point) format() string {
|
||||
if p.X == nil && p.Y == nil {
|
||||
return "(inf,inf)"
|
||||
}
|
||||
return fmt.Sprintf("(%s,%s)", hex.EncodeToString(p.X.Bytes()), hex.EncodeToString(p.Y.Bytes()))
|
||||
}
|
||||
|
||||
func (ec Curve) Params() *nativeelliptic.CurveParams {
|
||||
return &nativeelliptic.CurveParams{
|
||||
P: ec.P,
|
||||
N: ec.N,
|
||||
B: ec.B,
|
||||
Gx: ec.G.X,
|
||||
Gy: ec.G.Y,
|
||||
BitSize: 256,
|
||||
Name: ec.Name,
|
||||
}
|
||||
}
|
||||
|
||||
/*** Modular Arithmetic ***/
|
||||
|
||||
/* NOTE: Returning a new z each time below is very space inefficient, but the
|
||||
* alternate accumulator based design makes the point arithmetic functions look
|
||||
* absolutely hideous. I may still change this in the future. */
|
||||
|
||||
// addMod computes z = (x + y) % p.
|
||||
func addMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
|
||||
z = new(big.Int).Add(x, y)
|
||||
z.Mod(z, p)
|
||||
return z
|
||||
}
|
||||
|
||||
// subMod computes z = (x - y) % p.
|
||||
func subMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
|
||||
z = new(big.Int).Sub(x, y)
|
||||
z.Mod(z, p)
|
||||
return z
|
||||
}
|
||||
|
||||
// mulMod computes z = (x * y) % p.
|
||||
func mulMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
|
||||
n := new(big.Int).Set(x)
|
||||
z = big.NewInt(0)
|
||||
|
||||
for i := 0; i < y.BitLen(); i++ {
|
||||
if y.Bit(i) == 1 {
|
||||
z = addMod(z, n, p)
|
||||
}
|
||||
n = addMod(n, n, p)
|
||||
}
|
||||
|
||||
return z
|
||||
}
|
||||
|
||||
// invMod computes z = (1/x) % p.
|
||||
func invMod(x *big.Int, p *big.Int) (z *big.Int) {
|
||||
z = new(big.Int).ModInverse(x, p)
|
||||
return z
|
||||
}
|
||||
|
||||
// expMod computes z = (x^e) % p.
|
||||
func expMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
|
||||
z = new(big.Int).Exp(x, y, p)
|
||||
return z
|
||||
}
|
||||
|
||||
// sqrtMod computes z = sqrt(x) % p.
|
||||
func sqrtMod(x *big.Int, p *big.Int) (z *big.Int) {
|
||||
/* assert that p % 4 == 3 */
|
||||
if new(big.Int).Mod(p, big.NewInt(4)).Cmp(big.NewInt(3)) != 0 {
|
||||
panic("p is not equal to 3 mod 4!")
|
||||
}
|
||||
|
||||
/* z = sqrt(x) % p = x^((p+1)/4) % p */
|
||||
|
||||
/* e = (p+1)/4 */
|
||||
e := new(big.Int).Add(p, big.NewInt(1))
|
||||
e = e.Rsh(e, 2)
|
||||
|
||||
z = expMod(x, e, p)
|
||||
return z
|
||||
}
|
||||
|
||||
/*** Point Arithmetic on Curve ***/
|
||||
|
||||
// IsInfinity checks if point P is infinity on EllipticCurve ec.
|
||||
func (ec *Curve) IsInfinity(P Point) bool {
|
||||
/* We use (nil,nil) to represent O, the point at infinity. */
|
||||
|
||||
if P.X == nil && P.Y == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsOnCurve checks if point P is on EllipticCurve ec.
|
||||
func (ec Curve) IsOnCurve(P1, P2 *big.Int) bool {
|
||||
P := Point{P1, P2}
|
||||
if ec.IsInfinity(P) {
|
||||
return false
|
||||
}
|
||||
|
||||
/* y**2 = x**3 + a*x + b % p */
|
||||
lhs := mulMod(P.Y, P.Y, ec.P)
|
||||
rhs := addMod(
|
||||
addMod(
|
||||
expMod(P.X, big.NewInt(3), ec.P),
|
||||
mulMod(ec.A, P.X, ec.P), ec.P),
|
||||
ec.B, ec.P)
|
||||
|
||||
if lhs.Cmp(rhs) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Add computes R = P + Q on EllipticCurve ec.
|
||||
func (ec Curve) Add(P1, P2, Q1, Q2 *big.Int) (R1 *big.Int, R2 *big.Int) {
|
||||
/* See rules 1-5 on SEC1 pg.7 http://www.secg.org/collateral/sec1_final.pdf */
|
||||
P := Point{P1, P2}
|
||||
Q := Point{Q1, Q2}
|
||||
R := Point{}
|
||||
if ec.IsInfinity(P) && ec.IsInfinity(Q) {
|
||||
/* Rule #1 Identity */
|
||||
/* R = O + O = O */
|
||||
|
||||
R.X = nil
|
||||
R.Y = nil
|
||||
|
||||
} else if ec.IsInfinity(P) {
|
||||
/* Rule #2 Identity */
|
||||
/* R = O + Q = Q */
|
||||
|
||||
R.X = new(big.Int).Set(Q.X)
|
||||
R.Y = new(big.Int).Set(Q.Y)
|
||||
|
||||
} else if ec.IsInfinity(Q) {
|
||||
/* Rule #2 Identity */
|
||||
/* R = P + O = P */
|
||||
|
||||
R.X = new(big.Int).Set(P.X)
|
||||
R.Y = new(big.Int).Set(P.Y)
|
||||
|
||||
} else if P.X.Cmp(Q.X) == 0 && addMod(P.Y, Q.Y, ec.P).Sign() == 0 {
|
||||
/* Rule #3 Identity */
|
||||
/* R = (x,y) + (x,-y) = O */
|
||||
|
||||
R.X = nil
|
||||
R.Y = nil
|
||||
|
||||
} else if P.X.Cmp(Q.X) == 0 && P.Y.Cmp(Q.Y) == 0 && P.Y.Sign() != 0 {
|
||||
/* Rule #5 Point doubling */
|
||||
/* R = P + P */
|
||||
|
||||
/* Lambda = (3*P.X*P.X + a) / (2*P.Y) */
|
||||
num := addMod(
|
||||
mulMod(big.NewInt(3),
|
||||
mulMod(P.X, P.X, ec.P), ec.P),
|
||||
ec.A, ec.P)
|
||||
den := invMod(mulMod(big.NewInt(2), P.Y, ec.P), ec.P)
|
||||
lambda := mulMod(num, den, ec.P)
|
||||
|
||||
/* R.X = lambda*lambda - 2*P.X */
|
||||
R.X = subMod(
|
||||
mulMod(lambda, lambda, ec.P),
|
||||
mulMod(big.NewInt(2), P.X, ec.P),
|
||||
ec.P)
|
||||
/* R.Y = lambda*(P.X - R.X) - P.Y */
|
||||
R.Y = subMod(
|
||||
mulMod(lambda, subMod(P.X, R.X, ec.P), ec.P),
|
||||
P.Y, ec.P)
|
||||
|
||||
} else if P.X.Cmp(Q.X) != 0 {
|
||||
/* Rule #4 Point addition */
|
||||
/* R = P + Q */
|
||||
|
||||
/* Lambda = (Q.Y - P.Y) / (Q.X - P.X) */
|
||||
num := subMod(Q.Y, P.Y, ec.P)
|
||||
den := invMod(subMod(Q.X, P.X, ec.P), ec.P)
|
||||
lambda := mulMod(num, den, ec.P)
|
||||
|
||||
/* R.X = lambda*lambda - P.X - Q.X */
|
||||
R.X = subMod(
|
||||
subMod(
|
||||
mulMod(lambda, lambda, ec.P),
|
||||
P.X, ec.P),
|
||||
Q.X, ec.P)
|
||||
|
||||
/* R.Y = lambda*(P.X - R.X) - P.Y */
|
||||
R.Y = subMod(
|
||||
mulMod(lambda,
|
||||
subMod(P.X, R.X, ec.P), ec.P),
|
||||
P.Y, ec.P)
|
||||
} else {
|
||||
panic(fmt.Sprintf("Unsupported point addition: %v + %v", P.format(), Q.format()))
|
||||
}
|
||||
|
||||
return R.X, R.Y
|
||||
}
|
||||
|
||||
// ScalarMult computes Q = k * P on EllipticCurve ec.
|
||||
func (ec Curve) ScalarMult(P1, P2 *big.Int, l []byte) (Q1, Q2 *big.Int) {
|
||||
/* Note: this function is not constant time, due to the branching nature of
|
||||
* the underlying point Add() function. */
|
||||
|
||||
/* Montgomery Ladder Point Multiplication
|
||||
*
|
||||
* Implementation based on pseudocode here:
|
||||
* See https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder */
|
||||
|
||||
P := Point{P1, P2}
|
||||
k := big.Int{}
|
||||
k.SetBytes(l)
|
||||
|
||||
var R0 Point
|
||||
var R1 Point
|
||||
|
||||
R0.X = nil
|
||||
R0.Y = nil
|
||||
R1.X = new(big.Int).Set(P.X)
|
||||
R1.Y = new(big.Int).Set(P.Y)
|
||||
|
||||
for i := ec.N.BitLen() - 1; i >= 0; i-- {
|
||||
if k.Bit(i) == 0 {
|
||||
R1.X, R1.Y = ec.Add(R0.X, R0.Y, R1.X, R1.Y)
|
||||
R0.X, R0.Y = ec.Add(R0.X, R0.Y, R0.X, R0.Y)
|
||||
} else {
|
||||
R0.X, R0.Y = ec.Add(R0.X, R0.Y, R1.X, R1.Y)
|
||||
R1.X, R1.Y = ec.Add(R1.X, R1.Y, R1.X, R1.Y)
|
||||
}
|
||||
}
|
||||
|
||||
return R0.X, R0.Y
|
||||
}
|
||||
|
||||
// ScalarBaseMult computes Q = k * G on EllipticCurve ec.
|
||||
func (ec Curve) ScalarBaseMult(k []byte) (Q1, Q2 *big.Int) {
|
||||
|
||||
return ec.ScalarMult(ec.G.X, ec.G.Y, k)
|
||||
}
|
||||
|
||||
// Decompress decompresses coordinate x and ylsb (y's least significant bit) into a Point P on EllipticCurve ec.
|
||||
func (ec *Curve) Decompress(x *big.Int, ylsb uint) (P Point, err error) {
|
||||
/* y**2 = x**3 + a*x + b % p */
|
||||
rhs := addMod(
|
||||
addMod(
|
||||
expMod(x, big.NewInt(3), ec.P),
|
||||
mulMod(ec.A, x, ec.P),
|
||||
ec.P),
|
||||
ec.B, ec.P)
|
||||
|
||||
/* y = sqrt(rhs) % p */
|
||||
y := sqrtMod(rhs, ec.P)
|
||||
|
||||
/* Use -y if opposite lsb is required */
|
||||
if y.Bit(0) != (ylsb & 0x1) {
|
||||
y = subMod(big.NewInt(0), y, ec.P)
|
||||
}
|
||||
|
||||
P.X = x
|
||||
P.Y = y
|
||||
|
||||
if !ec.IsOnCurve(P.X, P.Y) {
|
||||
return P, errors.New("Compressed (x, ylsb) not on curve.")
|
||||
}
|
||||
|
||||
return P, nil
|
||||
}
|
||||
|
||||
func (ec Curve) Double(x1, y1 *big.Int) (x, y *big.Int) {
|
||||
x = &big.Int{}
|
||||
x.SetBytes([]byte{0x00})
|
||||
y = &big.Int{}
|
||||
y.SetBytes([]byte{0x00})
|
||||
return x, y
|
||||
}
|
231
pkg/crypto/elliptic/elliptic_test.go
Executable file
231
pkg/crypto/elliptic/elliptic_test.go
Executable file
|
@ -0,0 +1,231 @@
|
|||
/* btckeygenie v1.0.0
|
||||
* https://github.com/vsergeev/btckeygenie
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
package elliptic
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
curve = NewEllipticCurve(Secp256k1)
|
||||
}
|
||||
|
||||
func hex2int(hexstring string) (v *big.Int) {
|
||||
v, _ = new(big.Int).SetString(hexstring, 16)
|
||||
return v
|
||||
}
|
||||
|
||||
func TestOnCurve(t *testing.T) {
|
||||
if !curve.IsOnCurve(curve.G.X, curve.G.Y) {
|
||||
t.Fatal("failure G on curve")
|
||||
}
|
||||
|
||||
t.Log("G on curve")
|
||||
}
|
||||
|
||||
func TestInfinity(t *testing.T) {
|
||||
O := Point{nil, nil}
|
||||
|
||||
/* O not on curve */
|
||||
if curve.IsOnCurve(O.X, O.Y) {
|
||||
t.Fatal("failure O on curve")
|
||||
}
|
||||
|
||||
/* O is infinity */
|
||||
if !curve.IsInfinity(O) {
|
||||
t.Fatal("failure O not infinity on curve")
|
||||
}
|
||||
|
||||
t.Log("O is not on curve and is infinity")
|
||||
}
|
||||
|
||||
func TestPointAdd(t *testing.T) {
|
||||
X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
|
||||
Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6"
|
||||
|
||||
P := Point{hex2int(X), hex2int(Y)}
|
||||
O := Point{nil, nil}
|
||||
|
||||
/* R = O + O = O */
|
||||
{
|
||||
R1, R2 := curve.Add(O.X, O.Y, O.X, O.Y)
|
||||
R := Point{R1, R2}
|
||||
if !curve.IsInfinity(R) {
|
||||
t.Fatal("failure O + O = O")
|
||||
}
|
||||
t.Log("success O + O = O")
|
||||
}
|
||||
|
||||
/* R = P + O = P */
|
||||
{
|
||||
R1, R2 := curve.Add(P.X, P.Y, O.X, O.Y)
|
||||
R := Point{R1, R2}
|
||||
if R.X.Cmp(P.X) != 0 || R.Y.Cmp(P.Y) != 0 {
|
||||
t.Fatal("failure P + O = P")
|
||||
}
|
||||
t.Log("success P + O = P")
|
||||
}
|
||||
|
||||
/* R = O + Q = Q */
|
||||
{
|
||||
R1, R2 := curve.Add(O.X, O.Y, P.X, P.Y)
|
||||
R := Point{R1, R2}
|
||||
if R.X.Cmp(P.X) != 0 || R.Y.Cmp(P.Y) != 0 {
|
||||
t.Fatal("failure O + Q = Q")
|
||||
}
|
||||
t.Log("success O + Q = Q")
|
||||
}
|
||||
|
||||
/* R = (x,y) + (x,-y) = O */
|
||||
{
|
||||
Q := Point{P.X, subMod(big.NewInt(0), P.Y, curve.P)}
|
||||
|
||||
R1, R2 := curve.Add(P.X, P.Y, Q.X, Q.Y)
|
||||
R := Point{R1, R2}
|
||||
if !curve.IsInfinity(R) {
|
||||
t.Fatal("failure (x,y) + (x,-y) = O")
|
||||
}
|
||||
t.Log("success (x,y) + (x,-y) = O")
|
||||
}
|
||||
|
||||
/* R = P + P */
|
||||
{
|
||||
PP := Point{hex2int("5dbcd5dfea550eb4fd3b5333f533f086bb5267c776e2a1a9d8e84c16a6743d82"), hex2int("8dde3986b6cbe395da64b6e95fb81f8af73f6e0cf1100555005bb4ba2a6a4a07")}
|
||||
|
||||
R1, R2 := curve.Add(P.X, P.Y, P.X, P.Y)
|
||||
R := Point{R1, R2}
|
||||
if R.X.Cmp(PP.X) != 0 || R.Y.Cmp(PP.Y) != 0 {
|
||||
t.Fatal("failure P + P")
|
||||
}
|
||||
t.Log("success P + P")
|
||||
}
|
||||
|
||||
Q := Point{hex2int("a83b8de893467d3a88d959c0eb4032d9ce3bf80f175d4d9e75892a3ebb8ab7e5"), hex2int("370f723328c24b7a97fe34063ba68f253fb08f8645d7c8b9a4ff98e3c29e7f0d")}
|
||||
PQ := Point{hex2int("fe7d540002e4355eb0ec36c217b4735495de7bd8634055ded3683b0e9da70ef1"), hex2int("fc033c1d74cb34e087a3495e505c0fc0e9e3e8297994878d89d882254ce8a9ef")}
|
||||
|
||||
/* R = P + Q */
|
||||
{
|
||||
R1, R2 := curve.Add(P.X, P.Y, Q.X, Q.Y)
|
||||
R := Point{R1, R2}
|
||||
if R.X.Cmp(PQ.X) != 0 || R.Y.Cmp(PQ.Y) != 0 {
|
||||
t.Fatal("failure P + Q")
|
||||
}
|
||||
t.Log("success P + Q")
|
||||
}
|
||||
|
||||
/* R = Q + P */
|
||||
{
|
||||
R1, R2 := curve.Add(Q.X, Q.Y, P.X, P.Y)
|
||||
R := Point{R1, R2}
|
||||
if R.X.Cmp(PQ.X) != 0 || R.Y.Cmp(PQ.Y) != 0 {
|
||||
t.Fatal("failure Q + P")
|
||||
}
|
||||
t.Log("success Q + P")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointScalarMult(t *testing.T) {
|
||||
X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
|
||||
Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6"
|
||||
P := Point{hex2int(X), hex2int(Y)}
|
||||
|
||||
/* Q = k*P */
|
||||
{
|
||||
T := Point{hex2int("87d592bfdd24adb52147fea343db93e10d0585bc66d91e365c359973c0dc7067"), hex2int("a374e206cb7c8cd1074bdf9bf6ddea135f983aaa6475c9ab3bb4c38a0046541b")}
|
||||
input, _ := hex.DecodeString("14eb373700c3836404acd0820d9fa8dfa098d26177ca6e18b1c7f70c6af8fc18")
|
||||
|
||||
Q1, Q2 := curve.ScalarMult(P.X, P.Y, input)
|
||||
Q := Point{Q1, Q2}
|
||||
if Q.X.Cmp(T.X) != 0 || Q.Y.Cmp(T.Y) != 0 {
|
||||
t.Fatal("failure k*P")
|
||||
}
|
||||
t.Log("success k*P")
|
||||
}
|
||||
|
||||
/* Q = n*G = O */
|
||||
{
|
||||
Q1, Q2 := curve.ScalarMult(curve.G.X, curve.G.Y, curve.N.Bytes())
|
||||
Q := Point{Q1, Q2}
|
||||
if !curve.IsInfinity(Q) {
|
||||
t.Fatal("failure n*G = O")
|
||||
}
|
||||
t.Log("success n*G = O")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointScalarBaseMult(t *testing.T) {
|
||||
/* Sample Private Key */
|
||||
D := "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725"
|
||||
/* Sample Corresponding Public Key */
|
||||
X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
|
||||
Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6"
|
||||
|
||||
P := Point{hex2int(X), hex2int(Y)}
|
||||
|
||||
/* Q = d*G = P */
|
||||
|
||||
Q1, Q2 := curve.ScalarBaseMult(hex2int(D).Bytes())
|
||||
Q := Point{Q1, Q2}
|
||||
if P.X.Cmp(Q.X) != 0 || P.Y.Cmp(Q.Y) != 0 {
|
||||
t.Fatal("failure Q = d*G")
|
||||
}
|
||||
t.Log("success Q = d*G")
|
||||
|
||||
/* Q on curve */
|
||||
if !curve.IsOnCurve(Q.X, Q.Y) {
|
||||
t.Fatal("failure Q on curve")
|
||||
}
|
||||
t.Log("success Q on curve")
|
||||
|
||||
/* R = 0*G = O */
|
||||
R1, R2 := curve.ScalarBaseMult(big.NewInt(0).Bytes())
|
||||
R := Point{R1, R2}
|
||||
if !curve.IsInfinity(R) {
|
||||
t.Fatal("failure 0*G = O")
|
||||
}
|
||||
t.Log("success 0*G = O")
|
||||
}
|
||||
|
||||
func TestPointDecompress(t *testing.T) {
|
||||
/* Valid points */
|
||||
var validDecompressVectors = []Point{
|
||||
{hex2int("50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"), hex2int("2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6")},
|
||||
{hex2int("a83b8de893467d3a88d959c0eb4032d9ce3bf80f175d4d9e75892a3ebb8ab7e5"), hex2int("370f723328c24b7a97fe34063ba68f253fb08f8645d7c8b9a4ff98e3c29e7f0d")},
|
||||
{hex2int("f680556678e25084a82fa39e1b1dfd0944f7e69fddaa4e03ce934bd6b291dca0"), hex2int("52c10b721d34447e173721fb0151c68de1106badb089fb661523b8302a9097f5")},
|
||||
{hex2int("241febb8e23cbd77d664a18f66ad6240aaec6ecdc813b088d5b901b2e285131f"), hex2int("513378d9ff94f8d3d6c420bd13981df8cd50fd0fbd0cb5afabb3e66f2750026d")},
|
||||
}
|
||||
|
||||
for i := 0; i < len(validDecompressVectors); i++ {
|
||||
P, err := curve.Decompress(validDecompressVectors[i].X, validDecompressVectors[i].Y.Bit(0))
|
||||
if err != nil {
|
||||
t.Fatalf("failure decompress P, got error %v on index %d", err, i)
|
||||
}
|
||||
if P.X.Cmp(validDecompressVectors[i].X) != 0 || P.Y.Cmp(validDecompressVectors[i].Y) != 0 {
|
||||
t.Fatalf("failure decompress P, got mismatch on index %d", i)
|
||||
}
|
||||
}
|
||||
t.Log("success Decompress() on valid vectors")
|
||||
|
||||
/* Invalid points */
|
||||
var invalidDecompressVectors = []struct {
|
||||
X *big.Int
|
||||
YLsb uint
|
||||
}{
|
||||
{hex2int("c8e337cee51ae9af3c0ef923705a0cb1b76f7e8463b3d3060a1c8d795f9630fd"), 0},
|
||||
{hex2int("c8e337cee51ae9af3c0ef923705a0cb1b76f7e8463b3d3060a1c8d795f9630fd"), 1},
|
||||
}
|
||||
|
||||
for i := 0; i < len(invalidDecompressVectors); i++ {
|
||||
_, err := curve.Decompress(invalidDecompressVectors[i].X, invalidDecompressVectors[i].YLsb)
|
||||
if err == nil {
|
||||
t.Fatalf("failure decompress invalid P, got decompressed point on index %d", i)
|
||||
}
|
||||
}
|
||||
t.Log("success Decompress() on invalid vectors")
|
||||
}
|
77
pkg/crypto/hash/hash.go
Executable file
77
pkg/crypto/hash/hash.go
Executable file
|
@ -0,0 +1,77 @@
|
|||
package hash
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
)
|
||||
|
||||
func Sha256(data []byte) (util.Uint256, error) {
|
||||
var hash util.Uint256
|
||||
hasher := sha256.New()
|
||||
hasher.Reset()
|
||||
_, err := hasher.Write(data)
|
||||
|
||||
hash, err = util.Uint256DecodeBytes(hasher.Sum(nil))
|
||||
if err != nil {
|
||||
return hash, err
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func DoubleSha256(data []byte) (util.Uint256, error) {
|
||||
var hash util.Uint256
|
||||
|
||||
h1, err := Sha256(data)
|
||||
if err != nil {
|
||||
return hash, err
|
||||
}
|
||||
|
||||
hash, err = Sha256(h1.Bytes())
|
||||
if err != nil {
|
||||
return hash, err
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func RipeMD160(data []byte) (util.Uint160, error) {
|
||||
var hash util.Uint160
|
||||
hasher := ripemd160.New()
|
||||
hasher.Reset()
|
||||
_, err := io.WriteString(hasher, string(data))
|
||||
|
||||
hash, err = util.Uint160DecodeBytes(hasher.Sum(nil))
|
||||
if err != nil {
|
||||
return hash, err
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func Hash160(data []byte) (util.Uint160, error) {
|
||||
var hash util.Uint160
|
||||
h1, err := Sha256(data)
|
||||
|
||||
h2, err := RipeMD160(h1.Bytes())
|
||||
|
||||
hash, err = util.Uint160DecodeBytes(h2.Bytes())
|
||||
|
||||
if err != nil {
|
||||
return hash, err
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func Checksum(data []byte) ([]byte, error) {
|
||||
hash, err := Sum(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hash[:4], nil
|
||||
}
|
||||
|
||||
func Sum(b []byte) (util.Uint256, error) {
|
||||
hash, err := DoubleSha256((b))
|
||||
return hash, err
|
||||
}
|
62
pkg/crypto/hash/hash_test.go
Executable file
62
pkg/crypto/hash/hash_test.go
Executable file
|
@ -0,0 +1,62 @@
|
|||
package hash
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSha256(t *testing.T) {
|
||||
input := []byte("hello")
|
||||
data, err := Sha256(input)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
|
||||
actual := hex.EncodeToString(data.Bytes()) // MARK: In the DecodeBytes function, there is a bytes reverse, not sure why?
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestHashDoubleSha256(t *testing.T) {
|
||||
input := []byte("hello")
|
||||
data, err := DoubleSha256(input)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
firstSha, _ := Sha256(input)
|
||||
doubleSha, _ := Sha256(firstSha.Bytes())
|
||||
expected := hex.EncodeToString(doubleSha.Bytes())
|
||||
|
||||
actual := hex.EncodeToString(data.Bytes())
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestHashRipeMD160(t *testing.T) {
|
||||
input := []byte("hello")
|
||||
data, err := RipeMD160(input)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "108f07b8382412612c048d07d13f814118445acd"
|
||||
actual := hex.EncodeToString(data.Bytes())
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestHash160(t *testing.T) {
|
||||
input := "02cccafb41b220cab63fd77108d2d1ebcffa32be26da29a04dca4996afce5f75db"
|
||||
publicKeyBytes, _ := hex.DecodeString(input)
|
||||
data, err := Hash160(publicKeyBytes)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "c8e2b685cc70ec96743b55beb9449782f8f775d8"
|
||||
actual := hex.EncodeToString(data.Bytes())
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
117
pkg/crypto/privatekey/privatekey.go
Executable file
117
pkg/crypto/privatekey/privatekey.go
Executable file
|
@ -0,0 +1,117 @@
|
|||
package privatekey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/publickey"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/base58"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/rfc6979"
|
||||
)
|
||||
|
||||
// PrivateKey represents a NEO private key.
|
||||
type PrivateKey struct {
|
||||
b []byte
|
||||
}
|
||||
|
||||
func NewPrivateKey() (*PrivateKey, error) {
|
||||
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
|
||||
b := make([]byte, curve.N.BitLen()/8+8)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := new(big.Int).SetBytes(b)
|
||||
d.Mod(d, new(big.Int).Sub(curve.N, big.NewInt(1)))
|
||||
d.Add(d, big.NewInt(1))
|
||||
|
||||
p := &PrivateKey{b: d.Bytes()}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func NewPrivateKeyFromHex(str string) (*PrivateKey, error) {
|
||||
b, err := hex.DecodeString(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPrivateKeyFromBytes(b)
|
||||
}
|
||||
|
||||
// NewPrivateKeyFromBytes returns a NEO PrivateKey from the given byte slice.
|
||||
func NewPrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
|
||||
if len(b) != 32 {
|
||||
return nil, fmt.Errorf(
|
||||
"invalid byte length: expected %d bytes got %d", 32, len(b),
|
||||
)
|
||||
}
|
||||
return &PrivateKey{b}, nil
|
||||
}
|
||||
|
||||
func (p *PrivateKey) PublicKey() (*publickey.PublicKey, error) {
|
||||
var (
|
||||
c = elliptic.NewEllipticCurve(elliptic.Secp256r1)
|
||||
q = new(big.Int).SetBytes(p.b)
|
||||
)
|
||||
|
||||
p1, p2 := c.ScalarBaseMult(q.Bytes())
|
||||
point := elliptic.Point{
|
||||
X: p1,
|
||||
Y: p2,
|
||||
}
|
||||
if !c.IsOnCurve(p1, p2) {
|
||||
return nil, errors.New("failed to derive public key using elliptic curve")
|
||||
}
|
||||
|
||||
return &publickey.PublicKey{
|
||||
Curve: c,
|
||||
Point: point,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func WIFEncode(key []byte) (s string) {
|
||||
if len(key) != 32 {
|
||||
return "invalid private key length"
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteByte(0x80)
|
||||
buf.Write(key)
|
||||
|
||||
buf.WriteByte(0x01)
|
||||
|
||||
checksum, _ := hash.Checksum(buf.Bytes())
|
||||
|
||||
buf.Write(checksum)
|
||||
|
||||
WIF := base58.Encode(buf.Bytes())
|
||||
return WIF
|
||||
}
|
||||
|
||||
func (p *PrivateKey) Sign(data []byte) ([]byte, error) {
|
||||
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
|
||||
key := p.b
|
||||
digest, _ := hash.Sha256(data)
|
||||
|
||||
r, s, err := rfc6979.SignECDSA(curve, key, digest[:], sha256.New)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
curveOrderByteSize := curve.P.BitLen() / 8
|
||||
rBytes, sBytes := r.Bytes(), s.Bytes()
|
||||
signature := make([]byte, curveOrderByteSize*2)
|
||||
copy(signature[curveOrderByteSize-len(rBytes):], rBytes)
|
||||
copy(signature[curveOrderByteSize*2-len(sBytes):], sBytes)
|
||||
|
||||
return signature, nil
|
||||
}
|
48
pkg/crypto/privatekey/privatekey_test.go
Executable file
48
pkg/crypto/privatekey/privatekey_test.go
Executable file
|
@ -0,0 +1,48 @@
|
|||
package privatekey
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPrivateKeyToPublicKey(t *testing.T) {
|
||||
input := "495d528227c7dcc234c690af1222e67cde916dac1652cad97e0263825a8268a6"
|
||||
|
||||
privateKey, err := NewPrivateKeyFromHex(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pubKey, _ := privateKey.PublicKey()
|
||||
pubKeyBytes := pubKey.Bytes()
|
||||
actual := hex.EncodeToString(pubKeyBytes)
|
||||
expected := "03cd4c4ee9c8e1fae9d12ecf7c96cb3a057b550393f9e82182c4dae1139871682e"
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
func TestWIFEncode(t *testing.T) {
|
||||
input := "29bbf53185a973d2e3803cb92908fd08117486d1f2e7bab73ed0d00255511637"
|
||||
inputBytes, _ := hex.DecodeString(input)
|
||||
|
||||
actual := WIFEncode(inputBytes)
|
||||
expected := "KxcqV28rGDcpVR3fYg7R9vricLpyZ8oZhopyFLAWuRv7Y8TE9WhW"
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestSigning(t *testing.T) {
|
||||
// These were taken from the rfcPage:https://tools.ietf.org/html/rfc6979#page-33
|
||||
// public key: U = xG
|
||||
//Ux = 60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6
|
||||
//Uy = 7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299
|
||||
PrivateKey, _ := NewPrivateKeyFromHex("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721")
|
||||
|
||||
data, err := PrivateKey.Sign([]byte("sample"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r := "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716"
|
||||
s := "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8"
|
||||
assert.Equal(t, strings.ToLower(r+s), hex.EncodeToString(data))
|
||||
}
|
20
pkg/crypto/publickey/TestHelper/helper.go
Executable file
20
pkg/crypto/publickey/TestHelper/helper.go
Executable file
|
@ -0,0 +1,20 @@
|
|||
package pubkeytesthelper
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/privatekey"
|
||||
)
|
||||
|
||||
func SignDataWithRandomPrivateKey(data []byte) (bool, error) {
|
||||
|
||||
hashedData, _ := hash.Sha256(data)
|
||||
|
||||
privKey, _ := privatekey.NewPrivateKey()
|
||||
signedData, err := privKey.Sign(data)
|
||||
pubKey, _ := privKey.PublicKey()
|
||||
result := pubKey.Verify(signedData, hashedData.Bytes())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
34
pkg/crypto/publickey/TestHelper/helper_test.go
Executable file
34
pkg/crypto/publickey/TestHelper/helper_test.go
Executable file
|
@ -0,0 +1,34 @@
|
|||
package pubkeytesthelper
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/privatekey"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPubKeyVerify(t *testing.T) {
|
||||
actual, err := SignDataWithRandomPrivateKey([]byte("sample"))
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := true
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestWrongPubKey(t *testing.T) {
|
||||
privKey, _ := privatekey.NewPrivateKey()
|
||||
sample := []byte("sample")
|
||||
hashedData, _ := hash.Sha256(sample)
|
||||
signedData, _ := privKey.Sign(sample)
|
||||
|
||||
secondPrivKey, _ := privatekey.NewPrivateKey()
|
||||
wrongPubKey, _ := secondPrivKey.PublicKey()
|
||||
|
||||
actual := wrongPubKey.Verify(signedData, hashedData.Bytes())
|
||||
expcted := false
|
||||
assert.Equal(t, expcted, actual)
|
||||
}
|
160
pkg/crypto/publickey/publickey.go
Executable file
160
pkg/crypto/publickey/publickey.go
Executable file
|
@ -0,0 +1,160 @@
|
|||
package publickey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/base58"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
)
|
||||
|
||||
// PublicKeys is a list of public keys.
|
||||
type PublicKeys []*PublicKey
|
||||
|
||||
func (keys PublicKeys) Len() int { return len(keys) }
|
||||
func (keys PublicKeys) Swap(i, j int) { keys[i], keys[j] = keys[j], keys[i] }
|
||||
func (keys PublicKeys) Less(i, j int) bool {
|
||||
|
||||
if keys[i].X.Cmp(keys[j].X) == -1 {
|
||||
return true
|
||||
}
|
||||
if keys[i].X.Cmp(keys[j].X) == 1 {
|
||||
return false
|
||||
}
|
||||
if keys[i].X.Cmp(keys[j].X) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return keys[i].Y.Cmp(keys[j].Y) == -1
|
||||
}
|
||||
|
||||
// PublicKey represents a public key and provides a high level
|
||||
// API around the ECPoint.
|
||||
type PublicKey struct {
|
||||
Curve elliptic.Curve
|
||||
elliptic.Point
|
||||
}
|
||||
|
||||
// NewPublicKeyFromString return a public key created from the
|
||||
// given hex string.
|
||||
func NewPublicKeyFromString(s string) (*PublicKey, error) {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
|
||||
|
||||
pubKey := &PublicKey{curve, elliptic.Point{}}
|
||||
|
||||
if err := pubKey.DecodeBinary(bytes.NewReader(b)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// Bytes returns the byte array representation of the public key.
|
||||
func (p *PublicKey) Bytes() []byte {
|
||||
if p.Curve.IsInfinity(p.Point) {
|
||||
return []byte{0x00}
|
||||
}
|
||||
|
||||
var (
|
||||
x = p.X.Bytes()
|
||||
paddedX = append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...)
|
||||
prefix = byte(0x03)
|
||||
)
|
||||
|
||||
if p.Y.Bit(0) == 0 {
|
||||
prefix = byte(0x02)
|
||||
}
|
||||
|
||||
return append([]byte{prefix}, paddedX...)
|
||||
}
|
||||
func (p *PublicKey) ToAddress() string {
|
||||
|
||||
publicKeyBytes := p.Bytes()
|
||||
|
||||
publicKeyBytes = append([]byte{0x21}, publicKeyBytes...) // 0x21 = length of pubKey
|
||||
publicKeyBytes = append(publicKeyBytes, 0xAC) // 0xAC = CheckSig
|
||||
|
||||
hash160PubKey, _ := hash.Hash160(publicKeyBytes)
|
||||
|
||||
versionHash160PubKey := append([]byte{0x17}, hash160PubKey.Bytes()...)
|
||||
|
||||
checksum, _ := hash.Checksum(versionHash160PubKey)
|
||||
|
||||
checkVersionHash160 := append(versionHash160PubKey, checksum...)
|
||||
|
||||
address := base58.Encode(checkVersionHash160)
|
||||
|
||||
return address
|
||||
}
|
||||
|
||||
// DecodeBinary decodes a PublicKey from the given io.Reader.
|
||||
func (p *PublicKey) DecodeBinary(r io.Reader) error {
|
||||
|
||||
var prefix uint8
|
||||
if err := binary.Read(r, binary.LittleEndian, &prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Infinity
|
||||
if prefix == 0x00 {
|
||||
p.Point = elliptic.Point{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compressed public keys.
|
||||
if prefix == 0x02 || prefix == 0x03 {
|
||||
|
||||
b := make([]byte, 32)
|
||||
if err := binary.Read(r, binary.LittleEndian, b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
p.Point, err = p.Curve.Decompress(new(big.Int).SetBytes(b), uint(prefix&0x1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else if prefix == 0x04 {
|
||||
buf := make([]byte, 65)
|
||||
if err := binary.Read(r, binary.LittleEndian, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
p.X = new(big.Int).SetBytes(buf[1:33])
|
||||
p.Y = new(big.Int).SetBytes(buf[33:65])
|
||||
} else {
|
||||
return fmt.Errorf("invalid prefix %d", prefix)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBinary encodes a PublicKey to the given io.Writer.
|
||||
func (p *PublicKey) EncodeBinary(w io.Writer) error {
|
||||
return binary.Write(w, binary.LittleEndian, p.Bytes())
|
||||
}
|
||||
|
||||
func (p *PublicKey) Verify(signature []byte, hash []byte) bool {
|
||||
|
||||
publicKey := &ecdsa.PublicKey{}
|
||||
publicKey.Curve = p.Curve
|
||||
publicKey.X = p.X
|
||||
publicKey.Y = p.Y
|
||||
if p.X == nil || p.Y == nil {
|
||||
return false
|
||||
}
|
||||
rBytes := new(big.Int).SetBytes(signature[0:32])
|
||||
sBytes := new(big.Int).SetBytes(signature[32:64])
|
||||
return ecdsa.Verify(publicKey, hash, rBytes, sBytes)
|
||||
}
|
81
pkg/crypto/publickey/publickey_test.go
Executable file
81
pkg/crypto/publickey/publickey_test.go
Executable file
|
@ -0,0 +1,81 @@
|
|||
package publickey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecodeFromString(t *testing.T) {
|
||||
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
|
||||
pubKey, err := NewPublicKeyFromString(str)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, str, hex.EncodeToString(pubKey.Bytes()))
|
||||
}
|
||||
|
||||
func TestEncodeDecodeInfinity(t *testing.T) {
|
||||
|
||||
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
|
||||
|
||||
key := &PublicKey{curve, elliptic.Point{}}
|
||||
buf := new(bytes.Buffer)
|
||||
assert.Nil(t, key.EncodeBinary(buf))
|
||||
assert.Equal(t, 1, buf.Len())
|
||||
|
||||
keyDecode := &PublicKey{}
|
||||
assert.Nil(t, keyDecode.DecodeBinary(buf))
|
||||
assert.Equal(t, []byte{0x00}, keyDecode.Bytes())
|
||||
}
|
||||
|
||||
func TestEncodeDecodePublicKey(t *testing.T) {
|
||||
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
p := &PublicKey{curve, randomECPoint()}
|
||||
buf := new(bytes.Buffer)
|
||||
assert.Nil(t, p.EncodeBinary(buf))
|
||||
|
||||
pDecode := &PublicKey{curve, elliptic.Point{}}
|
||||
assert.Nil(t, pDecode.DecodeBinary(buf))
|
||||
assert.Equal(t, p.X, pDecode.X)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPubkeyToAddress(t *testing.T) {
|
||||
|
||||
pubKey, err := NewPublicKeyFromString("031ee4e73a17d8f76dc02532e2620bcb12425b33c0c9f9694cc2caa8226b68cad4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actual := pubKey.ToAddress()
|
||||
expected := "AUpGsNCHzSimeMRVPQfhwrVdiUp8Q2N2Qx"
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func randomECPoint() elliptic.Point {
|
||||
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
|
||||
b := make([]byte, curve.N.BitLen()/8+8)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return elliptic.Point{}
|
||||
}
|
||||
|
||||
d := new(big.Int).SetBytes(b)
|
||||
d.Mod(d, new(big.Int).Sub(curve.N, big.NewInt(1)))
|
||||
d.Add(d, big.NewInt(1))
|
||||
|
||||
q := new(big.Int).SetBytes(d.Bytes())
|
||||
P1, P2 := curve.ScalarBaseMult(q.Bytes())
|
||||
return elliptic.Point{
|
||||
X: P1,
|
||||
Y: P2,
|
||||
}
|
||||
}
|
21
pkg/crypto/rfc6979/LICENSE
Executable file
21
pkg/crypto/rfc6979/LICENSE
Executable file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Coda Hale
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
45
pkg/crypto/rfc6979/dsa.go
Executable file
45
pkg/crypto/rfc6979/dsa.go
Executable file
|
@ -0,0 +1,45 @@
|
|||
package rfc6979
|
||||
|
||||
import (
|
||||
"crypto/dsa"
|
||||
"hash"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// SignDSA signs an arbitrary length hash (which should be the result of hashing
|
||||
// a larger message) using the private key, priv. It returns the signature as a
|
||||
// pair of integers.
|
||||
//
|
||||
// Note that FIPS 186-3 section 4.6 specifies that the hash should be truncated
|
||||
// to the byte-length of the subgroup. This function does not perform that
|
||||
// truncation itself.
|
||||
func SignDSA(priv *dsa.PrivateKey, hash []byte, alg func() hash.Hash) (r, s *big.Int, err error) {
|
||||
n := priv.Q.BitLen()
|
||||
if n&7 != 0 {
|
||||
err = dsa.ErrInvalidPublicKey
|
||||
return
|
||||
}
|
||||
n >>= 3
|
||||
|
||||
generateSecret(priv.Q, priv.X, alg, hash, func(k *big.Int) bool {
|
||||
inv := new(big.Int).ModInverse(k, priv.Q)
|
||||
r = new(big.Int).Exp(priv.G, k, priv.P)
|
||||
r.Mod(r, priv.Q)
|
||||
|
||||
if r.Sign() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
z := new(big.Int).SetBytes(hash)
|
||||
|
||||
s = new(big.Int).Mul(priv.X, r)
|
||||
s.Add(s, z)
|
||||
s.Mod(s, priv.Q)
|
||||
s.Mul(s, inv)
|
||||
s.Mod(s, priv.Q)
|
||||
|
||||
return s.Sign() != 0
|
||||
})
|
||||
|
||||
return
|
||||
}
|
270
pkg/crypto/rfc6979/dsa_test.go
Executable file
270
pkg/crypto/rfc6979/dsa_test.go
Executable file
|
@ -0,0 +1,270 @@
|
|||
package rfc6979_test
|
||||
|
||||
import (
|
||||
"crypto/dsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/o3labs/neo-utils/neoutils/rfc6979"
|
||||
)
|
||||
|
||||
type dsaFixture struct {
|
||||
name string
|
||||
key *dsaKey
|
||||
alg func() hash.Hash
|
||||
message string
|
||||
r, s string
|
||||
}
|
||||
|
||||
type dsaKey struct {
|
||||
key *dsa.PrivateKey
|
||||
subgroup int
|
||||
}
|
||||
|
||||
var dsa1024 = &dsaKey{
|
||||
key: &dsa.PrivateKey{
|
||||
PublicKey: dsa.PublicKey{
|
||||
Parameters: dsa.Parameters{
|
||||
P: dsaLoadInt("86F5CA03DCFEB225063FF830A0C769B9DD9D6153AD91D7CE27F787C43278B447E6533B86B18BED6E8A48B784A14C252C5BE0DBF60B86D6385BD2F12FB763ED8873ABFD3F5BA2E0A8C0A59082EAC056935E529DAF7C610467899C77ADEDFC846C881870B7B19B2B58F9BE0521A17002E3BDD6B86685EE90B3D9A1B02B782B1779"),
|
||||
Q: dsaLoadInt("996F967F6C8E388D9E28D01E205FBA957A5698B1"),
|
||||
G: dsaLoadInt("07B0F92546150B62514BB771E2A0C0CE387F03BDA6C56B505209FF25FD3C133D89BBCD97E904E09114D9A7DEFDEADFC9078EA544D2E401AEECC40BB9FBBF78FD87995A10A1C27CB7789B594BA7EFB5C4326A9FE59A070E136DB77175464ADCA417BE5DCE2F40D10A46A3A3943F26AB7FD9C0398FF8C76EE0A56826A8A88F1DBD"),
|
||||
},
|
||||
Y: dsaLoadInt("5DF5E01DED31D0297E274E1691C192FE5868FEF9E19A84776454B100CF16F65392195A38B90523E2542EE61871C0440CB87C322FC4B4D2EC5E1E7EC766E1BE8D4CE935437DC11C3C8FD426338933EBFE739CB3465F4D3668C5E473508253B1E682F65CBDC4FAE93C2EA212390E54905A86E2223170B44EAA7DA5DD9FFCFB7F3B"),
|
||||
},
|
||||
X: dsaLoadInt("411602CB19A6CCC34494D79D98EF1E7ED5AF25F7"),
|
||||
},
|
||||
subgroup: 160,
|
||||
}
|
||||
|
||||
var dsa2048 = &dsaKey{
|
||||
key: &dsa.PrivateKey{
|
||||
PublicKey: dsa.PublicKey{
|
||||
Parameters: dsa.Parameters{
|
||||
P: dsaLoadInt("9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B"),
|
||||
Q: dsaLoadInt("F2C3119374CE76C9356990B465374A17F23F9ED35089BD969F61C6DDE9998C1F"),
|
||||
G: dsaLoadInt("5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7"),
|
||||
},
|
||||
Y: dsaLoadInt("667098C654426C78D7F8201EAC6C203EF030D43605032C2F1FA937E5237DBD949F34A0A2564FE126DC8B715C5141802CE0979C8246463C40E6B6BDAA2513FA611728716C2E4FD53BC95B89E69949D96512E873B9C8F8DFD499CC312882561ADECB31F658E934C0C197F2C4D96B05CBAD67381E7B768891E4DA3843D24D94CDFB5126E9B8BF21E8358EE0E0A30EF13FD6A664C0DCE3731F7FB49A4845A4FD8254687972A2D382599C9BAC4E0ED7998193078913032558134976410B89D2C171D123AC35FD977219597AA7D15C1A9A428E59194F75C721EBCBCFAE44696A499AFA74E04299F132026601638CB87AB79190D4A0986315DA8EEC6561C938996BEADF"),
|
||||
},
|
||||
X: dsaLoadInt("69C7548C21D0DFEA6B9A51C9EAD4E27C33D3B3F180316E5BCAB92C933F0E4DBC"),
|
||||
},
|
||||
subgroup: 256,
|
||||
}
|
||||
|
||||
var dsaFixtures = []dsaFixture{
|
||||
// DSA, 1024 Bits
|
||||
// https://tools.ietf.org/html/rfc6979#appendix-A.2.1
|
||||
dsaFixture{
|
||||
name: "1024/SHA-1 #1",
|
||||
key: dsa1024,
|
||||
alg: sha1.New,
|
||||
message: "sample",
|
||||
r: "2E1A0C2562B2912CAAF89186FB0F42001585DA55",
|
||||
s: "29EFB6B0AFF2D7A68EB70CA313022253B9A88DF5",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "1024/SHA-224 #1",
|
||||
key: dsa1024,
|
||||
alg: sha256.New224,
|
||||
message: "sample",
|
||||
r: "4BC3B686AEA70145856814A6F1BB53346F02101E",
|
||||
s: "410697B92295D994D21EDD2F4ADA85566F6F94C1",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "1024/SHA-256 #1",
|
||||
key: dsa1024,
|
||||
alg: sha256.New,
|
||||
message: "sample",
|
||||
r: "81F2F5850BE5BC123C43F71A3033E9384611C545",
|
||||
s: "4CDD914B65EB6C66A8AAAD27299BEE6B035F5E89",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "1024/SHA-384 #1",
|
||||
key: dsa1024,
|
||||
alg: sha512.New384,
|
||||
message: "sample",
|
||||
r: "07F2108557EE0E3921BC1774F1CA9B410B4CE65A",
|
||||
s: "54DF70456C86FAC10FAB47C1949AB83F2C6F7595",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "1024/SHA-512 #1",
|
||||
key: dsa1024,
|
||||
alg: sha512.New,
|
||||
message: "sample",
|
||||
r: "16C3491F9B8C3FBBDD5E7A7B667057F0D8EE8E1B",
|
||||
s: "02C36A127A7B89EDBB72E4FFBC71DABC7D4FC69C",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "1024/SHA-1 #2",
|
||||
key: dsa1024,
|
||||
alg: sha1.New,
|
||||
message: "test",
|
||||
r: "42AB2052FD43E123F0607F115052A67DCD9C5C77",
|
||||
s: "183916B0230D45B9931491D4C6B0BD2FB4AAF088",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "1024/SHA-224 #2",
|
||||
key: dsa1024,
|
||||
alg: sha256.New224,
|
||||
message: "test",
|
||||
r: "6868E9964E36C1689F6037F91F28D5F2C30610F2",
|
||||
s: "49CEC3ACDC83018C5BD2674ECAAD35B8CD22940F",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "1024/SHA-256 #2",
|
||||
key: dsa1024,
|
||||
alg: sha256.New,
|
||||
message: "test",
|
||||
r: "22518C127299B0F6FDC9872B282B9E70D0790812",
|
||||
s: "6837EC18F150D55DE95B5E29BE7AF5D01E4FE160",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "1024/SHA-384 #2",
|
||||
key: dsa1024,
|
||||
alg: sha512.New384,
|
||||
message: "test",
|
||||
r: "854CF929B58D73C3CBFDC421E8D5430CD6DB5E66",
|
||||
s: "91D0E0F53E22F898D158380676A871A157CDA622",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "1024/SHA-512 #2",
|
||||
key: dsa1024,
|
||||
alg: sha512.New,
|
||||
message: "test",
|
||||
r: "8EA47E475BA8AC6F2D821DA3BD212D11A3DEB9A0",
|
||||
s: "7C670C7AD72B6C050C109E1790008097125433E8",
|
||||
},
|
||||
|
||||
// DSA, 2048 Bits
|
||||
// https://tools.ietf.org/html/rfc6979#appendix-A.2.2
|
||||
dsaFixture{
|
||||
name: "2048/SHA-1 #1",
|
||||
key: dsa2048,
|
||||
alg: sha1.New,
|
||||
message: "sample",
|
||||
r: "3A1B2DBD7489D6ED7E608FD036C83AF396E290DBD602408E8677DAABD6E7445A",
|
||||
s: "D26FCBA19FA3E3058FFC02CA1596CDBB6E0D20CB37B06054F7E36DED0CDBBCCF",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "2048/SHA-224 #1",
|
||||
key: dsa2048,
|
||||
alg: sha256.New224,
|
||||
message: "sample",
|
||||
r: "DC9F4DEADA8D8FF588E98FED0AB690FFCE858DC8C79376450EB6B76C24537E2C",
|
||||
s: "A65A9C3BC7BABE286B195D5DA68616DA8D47FA0097F36DD19F517327DC848CEC",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "2048/SHA-256 #1",
|
||||
key: dsa2048,
|
||||
alg: sha256.New,
|
||||
message: "sample",
|
||||
r: "EACE8BDBBE353C432A795D9EC556C6D021F7A03F42C36E9BC87E4AC7932CC809",
|
||||
s: "7081E175455F9247B812B74583E9E94F9EA79BD640DC962533B0680793A38D53",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "2048/SHA-384 #1",
|
||||
key: dsa2048,
|
||||
alg: sha512.New384,
|
||||
message: "sample",
|
||||
r: "B2DA945E91858834FD9BF616EBAC151EDBC4B45D27D0DD4A7F6A22739F45C00B",
|
||||
s: "19048B63D9FD6BCA1D9BAE3664E1BCB97F7276C306130969F63F38FA8319021B",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "2048/SHA-512 #1",
|
||||
key: dsa2048,
|
||||
alg: sha512.New,
|
||||
message: "sample",
|
||||
r: "2016ED092DC5FB669B8EFB3D1F31A91EECB199879BE0CF78F02BA062CB4C942E",
|
||||
s: "D0C76F84B5F091E141572A639A4FB8C230807EEA7D55C8A154A224400AFF2351",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "2048/SHA-1 #2",
|
||||
key: dsa2048,
|
||||
alg: sha1.New,
|
||||
message: "test",
|
||||
r: "C18270A93CFC6063F57A4DFA86024F700D980E4CF4E2CB65A504397273D98EA0",
|
||||
s: "414F22E5F31A8B6D33295C7539C1C1BA3A6160D7D68D50AC0D3A5BEAC2884FAA",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "2048/SHA-224 #2",
|
||||
key: dsa2048,
|
||||
alg: sha256.New224,
|
||||
message: "test",
|
||||
r: "272ABA31572F6CC55E30BF616B7A265312018DD325BE031BE0CC82AA17870EA3",
|
||||
s: "E9CC286A52CCE201586722D36D1E917EB96A4EBDB47932F9576AC645B3A60806",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "2048/SHA-256 #2",
|
||||
key: dsa2048,
|
||||
alg: sha256.New,
|
||||
message: "test",
|
||||
r: "8190012A1969F9957D56FCCAAD223186F423398D58EF5B3CEFD5A4146A4476F0",
|
||||
s: "7452A53F7075D417B4B013B278D1BB8BBD21863F5E7B1CEE679CF2188E1AB19E",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "2048/SHA-384 #2",
|
||||
key: dsa2048,
|
||||
alg: sha512.New384,
|
||||
message: "test",
|
||||
r: "239E66DDBE8F8C230A3D071D601B6FFBDFB5901F94D444C6AF56F732BEB954BE",
|
||||
s: "6BD737513D5E72FE85D1C750E0F73921FE299B945AAD1C802F15C26A43D34961",
|
||||
},
|
||||
dsaFixture{
|
||||
name: "2048/SHA-512 #2",
|
||||
key: dsa2048,
|
||||
alg: sha512.New,
|
||||
message: "test",
|
||||
r: "89EC4BB1400ECCFF8E7D9AA515CD1DE7803F2DAFF09693EE7FD1353E90A68307",
|
||||
s: "C9F0BDABCC0D880BB137A994CC7F3980CE91CC10FAF529FC46565B15CEA854E1",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDSASignatures(t *testing.T) {
|
||||
for _, f := range dsaFixtures {
|
||||
testDsaFixture(&f, t)
|
||||
}
|
||||
}
|
||||
|
||||
func testDsaFixture(f *dsaFixture, t *testing.T) {
|
||||
t.Logf("Testing %s", f.name)
|
||||
|
||||
h := f.alg()
|
||||
h.Write([]byte(f.message))
|
||||
digest := h.Sum(nil)
|
||||
|
||||
g := f.key.subgroup / 8
|
||||
if len(digest) > g {
|
||||
digest = digest[0:g]
|
||||
}
|
||||
|
||||
r, s, err := rfc6979.SignDSA(f.key.key, digest, f.alg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
expectedR := dsaLoadInt(f.r)
|
||||
expectedS := dsaLoadInt(f.s)
|
||||
|
||||
if r.Cmp(expectedR) != 0 {
|
||||
t.Errorf("%s: Expected R of %X, got %X", f.name, expectedR, r)
|
||||
}
|
||||
|
||||
if s.Cmp(expectedS) != 0 {
|
||||
t.Errorf("%s: Expected S of %X, got %X", f.name, expectedS, s)
|
||||
}
|
||||
}
|
||||
|
||||
func dsaLoadInt(s string) *big.Int {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return new(big.Int).SetBytes(b)
|
||||
}
|
59
pkg/crypto/rfc6979/ecdsa.go
Executable file
59
pkg/crypto/rfc6979/ecdsa.go
Executable file
|
@ -0,0 +1,59 @@
|
|||
package rfc6979
|
||||
|
||||
import (
|
||||
"hash"
|
||||
"math/big"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
|
||||
)
|
||||
|
||||
// SignECDSA signs an arbitrary length hash (which should be the result of
|
||||
// hashing a larger message) using the private key, priv. It returns the
|
||||
// signature as a pair of integers.
|
||||
//
|
||||
// Note that FIPS 186-3 section 4.6 specifies that the hash should be truncated
|
||||
// to the byte-length of the subgroup. This function does not perform that
|
||||
// truncation itself.
|
||||
func SignECDSA(curve elliptic.Curve, priv []byte, hash []byte, alg func() hash.Hash) (r, s *big.Int, err error) {
|
||||
c := curve
|
||||
N := c.N
|
||||
D := new(big.Int)
|
||||
D.SetBytes(priv)
|
||||
generateSecret(N, D, alg, hash, func(k *big.Int) bool {
|
||||
|
||||
inv := new(big.Int).ModInverse(k, N)
|
||||
|
||||
r, _ = curve.ScalarBaseMult(k.Bytes())
|
||||
r.Mod(r, N)
|
||||
|
||||
if r.Sign() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
e := hashToInt(hash, c)
|
||||
s = new(big.Int).Mul(D, r)
|
||||
s.Add(s, e)
|
||||
s.Mul(s, inv)
|
||||
s.Mod(s, N)
|
||||
|
||||
return s.Sign() != 0
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// copied from crypto/ecdsa
|
||||
func hashToInt(hash []byte, c elliptic.Curve) *big.Int {
|
||||
orderBits := c.N.BitLen()
|
||||
orderBytes := (orderBits + 7) / 8
|
||||
if len(hash) > orderBytes {
|
||||
hash = hash[:orderBytes]
|
||||
}
|
||||
|
||||
ret := new(big.Int).SetBytes(hash)
|
||||
excess := len(hash)*8 - orderBits
|
||||
if excess > 0 {
|
||||
ret.Rsh(ret, uint(excess))
|
||||
}
|
||||
return ret
|
||||
}
|
447
pkg/crypto/rfc6979/ecdsa_test.go
Executable file
447
pkg/crypto/rfc6979/ecdsa_test.go
Executable file
|
@ -0,0 +1,447 @@
|
|||
package rfc6979_test
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/o3labs/neo-utils/neoutils/rfc6979"
|
||||
)
|
||||
|
||||
type ecdsaFixture struct {
|
||||
name string
|
||||
key *ecdsaKey
|
||||
alg func() hash.Hash
|
||||
message string
|
||||
r, s string
|
||||
}
|
||||
|
||||
type ecdsaKey struct {
|
||||
key *ecdsa.PrivateKey
|
||||
subgroup int
|
||||
}
|
||||
|
||||
var p224 = &ecdsaKey{
|
||||
key: &ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: elliptic.P224(),
|
||||
X: ecdsaLoadInt("00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C"),
|
||||
Y: ecdsaLoadInt("EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A"),
|
||||
},
|
||||
D: ecdsaLoadInt("F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1"),
|
||||
},
|
||||
subgroup: 224,
|
||||
}
|
||||
|
||||
var p256 = &ecdsaKey{
|
||||
key: &ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: elliptic.P256(),
|
||||
X: ecdsaLoadInt("60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6"),
|
||||
Y: ecdsaLoadInt("7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299"),
|
||||
},
|
||||
D: ecdsaLoadInt("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721"),
|
||||
},
|
||||
subgroup: 256,
|
||||
}
|
||||
|
||||
var p384 = &ecdsaKey{
|
||||
key: &ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: elliptic.P384(),
|
||||
X: ecdsaLoadInt("EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13"),
|
||||
Y: ecdsaLoadInt("8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720"),
|
||||
},
|
||||
D: ecdsaLoadInt("6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5"),
|
||||
},
|
||||
subgroup: 384,
|
||||
}
|
||||
|
||||
var p521 = &ecdsaKey{
|
||||
key: &ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: elliptic.P521(),
|
||||
X: ecdsaLoadInt("1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4"),
|
||||
Y: ecdsaLoadInt("0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5"),
|
||||
},
|
||||
D: ecdsaLoadInt("0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538"),
|
||||
},
|
||||
subgroup: 521,
|
||||
}
|
||||
|
||||
var fixtures = []ecdsaFixture{
|
||||
// ECDSA, 224 Bits (Prime Field)
|
||||
// https://tools.ietf.org/html/rfc6979#appendix-A.2.4
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-1 #1",
|
||||
key: p224,
|
||||
alg: sha1.New,
|
||||
message: "sample",
|
||||
r: "22226F9D40A96E19C4A301CE5B74B115303C0F3A4FD30FC257FB57AC",
|
||||
s: "66D1CDD83E3AF75605DD6E2FEFF196D30AA7ED7A2EDF7AF475403D69",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-224 #1",
|
||||
key: p224,
|
||||
alg: sha256.New224,
|
||||
message: "sample",
|
||||
r: "1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E",
|
||||
s: "A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-256 #1",
|
||||
key: p224,
|
||||
alg: sha256.New,
|
||||
message: "sample",
|
||||
r: "61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA",
|
||||
s: "BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-384 #1",
|
||||
key: p224,
|
||||
alg: sha512.New384,
|
||||
message: "sample",
|
||||
r: "0B115E5E36F0F9EC81F1325A5952878D745E19D7BB3EABFABA77E953",
|
||||
s: "830F34CCDFE826CCFDC81EB4129772E20E122348A2BBD889A1B1AF1D",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-512 #1",
|
||||
key: p224,
|
||||
alg: sha512.New,
|
||||
message: "sample",
|
||||
r: "074BD1D979D5F32BF958DDC61E4FB4872ADCAFEB2256497CDAC30397",
|
||||
s: "A4CECA196C3D5A1FF31027B33185DC8EE43F288B21AB342E5D8EB084",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-1 #2",
|
||||
key: p224,
|
||||
alg: sha1.New,
|
||||
message: "test",
|
||||
r: "DEAA646EC2AF2EA8AD53ED66B2E2DDAA49A12EFD8356561451F3E21C",
|
||||
s: "95987796F6CF2062AB8135271DE56AE55366C045F6D9593F53787BD2",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-224 #2",
|
||||
key: p224,
|
||||
alg: sha256.New224,
|
||||
message: "test",
|
||||
r: "C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019",
|
||||
s: "902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F4",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-256 #2",
|
||||
key: p224,
|
||||
alg: sha256.New,
|
||||
message: "test",
|
||||
r: "AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6",
|
||||
s: "178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFD",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-384 #2",
|
||||
key: p224,
|
||||
alg: sha512.New384,
|
||||
message: "test",
|
||||
r: "389B92682E399B26518A95506B52C03BC9379A9DADF3391A21FB0EA4",
|
||||
s: "414A718ED3249FF6DBC5B50C27F71F01F070944DA22AB1F78F559AAB",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P224/SHA-512 #2",
|
||||
key: p224,
|
||||
alg: sha512.New,
|
||||
message: "test",
|
||||
r: "049F050477C5ADD858CAC56208394B5A55BAEBBE887FDF765047C17C",
|
||||
s: "077EB13E7005929CEFA3CD0403C7CDCC077ADF4E44F3C41B2F60ECFF",
|
||||
},
|
||||
// ECDSA, 256 Bits (Prime Field)
|
||||
// https://tools.ietf.org/html/rfc6979#appendix-A.2.5
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-1 #1",
|
||||
key: p256,
|
||||
alg: sha1.New,
|
||||
message: "sample",
|
||||
r: "61340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D32",
|
||||
s: "6D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EB",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-224 #1",
|
||||
key: p256,
|
||||
alg: sha256.New224,
|
||||
message: "sample",
|
||||
r: "53B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F",
|
||||
s: "B9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74C",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-256 #1",
|
||||
key: p256,
|
||||
alg: sha256.New,
|
||||
message: "sample",
|
||||
r: "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716",
|
||||
s: "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-384 #1",
|
||||
key: p256,
|
||||
alg: sha512.New384,
|
||||
message: "sample",
|
||||
r: "0EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF7719",
|
||||
s: "4861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940954",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-512 #1",
|
||||
key: p256,
|
||||
alg: sha512.New,
|
||||
message: "sample",
|
||||
r: "8496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F00",
|
||||
s: "2362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FE",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-1 #2",
|
||||
key: p256,
|
||||
alg: sha1.New,
|
||||
message: "test",
|
||||
r: "0CBCC86FD6ABD1D99E703E1EC50069EE5C0B4BA4B9AC60E409E8EC5910D81A89",
|
||||
s: "01B9D7B73DFAA60D5651EC4591A0136F87653E0FD780C3B1BC872FFDEAE479B1",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-224 #2",
|
||||
key: p256,
|
||||
alg: sha256.New224,
|
||||
message: "test",
|
||||
r: "C37EDB6F0AE79D47C3C27E962FA269BB4F441770357E114EE511F662EC34A692",
|
||||
s: "C820053A05791E521FCAAD6042D40AEA1D6B1A540138558F47D0719800E18F2D",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-256 #2",
|
||||
key: p256,
|
||||
alg: sha256.New,
|
||||
message: "test",
|
||||
r: "F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367",
|
||||
s: "019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-384 #2",
|
||||
key: p256,
|
||||
alg: sha512.New384,
|
||||
message: "test",
|
||||
r: "83910E8B48BB0C74244EBDF7F07A1C5413D61472BD941EF3920E623FBCCEBEB6",
|
||||
s: "8DDBEC54CF8CD5874883841D712142A56A8D0F218F5003CB0296B6B509619F2C",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P256/SHA-512 #2",
|
||||
key: p256,
|
||||
alg: sha512.New,
|
||||
message: "test",
|
||||
r: "461D93F31B6540894788FD206C07CFA0CC35F46FA3C91816FFF1040AD1581A04",
|
||||
s: "39AF9F15DE0DB8D97E72719C74820D304CE5226E32DEDAE67519E840D1194E55",
|
||||
},
|
||||
// ECDSA, 384 Bits (Prime Field)
|
||||
// https://tools.ietf.org/html/rfc6979#appendix-A.2.6
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-1 #1",
|
||||
key: p384,
|
||||
alg: sha1.New,
|
||||
message: "sample",
|
||||
r: "EC748D839243D6FBEF4FC5C4859A7DFFD7F3ABDDF72014540C16D73309834FA37B9BA002899F6FDA3A4A9386790D4EB2",
|
||||
s: "A3BCFA947BEEF4732BF247AC17F71676CB31A847B9FF0CBC9C9ED4C1A5B3FACF26F49CA031D4857570CCB5CA4424A443",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-224 #1",
|
||||
key: p384,
|
||||
alg: sha256.New224,
|
||||
message: "sample",
|
||||
r: "42356E76B55A6D9B4631C865445DBE54E056D3B3431766D0509244793C3F9366450F76EE3DE43F5A125333A6BE060122",
|
||||
s: "9DA0C81787064021E78DF658F2FBB0B042BF304665DB721F077A4298B095E4834C082C03D83028EFBF93A3C23940CA8D",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-256 #1",
|
||||
key: p384,
|
||||
alg: sha256.New,
|
||||
message: "sample",
|
||||
r: "21B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD",
|
||||
s: "F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D45DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-384 #1",
|
||||
key: p384,
|
||||
alg: sha512.New384,
|
||||
message: "sample",
|
||||
r: "94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE46",
|
||||
s: "99EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-512 #1",
|
||||
key: p384,
|
||||
alg: sha512.New,
|
||||
message: "sample",
|
||||
r: "ED0959D5880AB2D869AE7F6C2915C6D60F96507F9CB3E047C0046861DA4A799CFE30F35CC900056D7C99CD7882433709",
|
||||
s: "512C8CCEEE3890A84058CE1E22DBC2198F42323CE8ACA9135329F03C068E5112DC7CC3EF3446DEFCEB01A45C2667FDD5",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-1 #2",
|
||||
key: p384,
|
||||
alg: sha1.New,
|
||||
message: "test",
|
||||
r: "4BC35D3A50EF4E30576F58CD96CE6BF638025EE624004A1F7789A8B8E43D0678ACD9D29876DAF46638645F7F404B11C7",
|
||||
s: "D5A6326C494ED3FF614703878961C0FDE7B2C278F9A65FD8C4B7186201A2991695BA1C84541327E966FA7B50F7382282",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-224 #2",
|
||||
key: p384,
|
||||
alg: sha256.New224,
|
||||
message: "test",
|
||||
r: "E8C9D0B6EA72A0E7837FEA1D14A1A9557F29FAA45D3E7EE888FC5BF954B5E62464A9A817C47FF78B8C11066B24080E72",
|
||||
s: "07041D4A7A0379AC7232FF72E6F77B6DDB8F09B16CCE0EC3286B2BD43FA8C6141C53EA5ABEF0D8231077A04540A96B66",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-256 #2",
|
||||
key: p384,
|
||||
alg: sha256.New,
|
||||
message: "test",
|
||||
r: "6D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B",
|
||||
s: "2D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-384 #2",
|
||||
key: p384,
|
||||
alg: sha512.New384,
|
||||
message: "test",
|
||||
r: "8203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB",
|
||||
s: "DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A5",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P384/SHA-512 #2",
|
||||
key: p384,
|
||||
alg: sha512.New,
|
||||
message: "test",
|
||||
r: "A0D5D090C9980FAF3C2CE57B7AE951D31977DD11C775D314AF55F76C676447D06FB6495CD21B4B6E340FC236584FB277",
|
||||
s: "976984E59B4C77B0E8E4460DCA3D9F20E07B9BB1F63BEEFAF576F6B2E8B224634A2092CD3792E0159AD9CEE37659C736",
|
||||
},
|
||||
// ECDSA, 521 Bits (Prime Field)
|
||||
// https://tools.ietf.org/html/rfc6979#appendix-A.2.7
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-1 #1",
|
||||
key: p521,
|
||||
alg: sha1.New,
|
||||
message: "sample",
|
||||
r: "0343B6EC45728975EA5CBA6659BBB6062A5FF89EEA58BE3C80B619F322C87910FE092F7D45BB0F8EEE01ED3F20BABEC079D202AE677B243AB40B5431D497C55D75D",
|
||||
s: "0E7B0E675A9B24413D448B8CC119D2BF7B2D2DF032741C096634D6D65D0DBE3D5694625FB9E8104D3B842C1B0E2D0B98BEA19341E8676AEF66AE4EBA3D5475D5D16",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-224 #1",
|
||||
key: p521,
|
||||
alg: sha256.New224,
|
||||
message: "sample",
|
||||
r: "1776331CFCDF927D666E032E00CF776187BC9FDD8E69D0DABB4109FFE1B5E2A30715F4CC923A4A5E94D2503E9ACFED92857B7F31D7152E0F8C00C15FF3D87E2ED2E",
|
||||
s: "050CB5265417FE2320BBB5A122B8E1A32BD699089851128E360E620A30C7E17BA41A666AF126CE100E5799B153B60528D5300D08489CA9178FB610A2006C254B41F",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-256 #1",
|
||||
key: p521,
|
||||
alg: sha256.New,
|
||||
message: "sample",
|
||||
r: "1511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A7",
|
||||
s: "04A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-384 #1",
|
||||
key: p521,
|
||||
alg: sha512.New384,
|
||||
message: "sample",
|
||||
r: "1EA842A0E17D2DE4F92C15315C63DDF72685C18195C2BB95E572B9C5136CA4B4B576AD712A52BE9730627D16054BA40CC0B8D3FF035B12AE75168397F5D50C67451",
|
||||
s: "1F21A3CEE066E1961025FB048BD5FE2B7924D0CD797BABE0A83B66F1E35EEAF5FDE143FA85DC394A7DEE766523393784484BDF3E00114A1C857CDE1AA203DB65D61",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-512 #1",
|
||||
key: p521,
|
||||
alg: sha512.New,
|
||||
message: "sample",
|
||||
r: "0C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA",
|
||||
s: "0617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-1 #2",
|
||||
key: p521,
|
||||
alg: sha1.New,
|
||||
message: "test",
|
||||
r: "13BAD9F29ABE20DE37EBEB823C252CA0F63361284015A3BF430A46AAA80B87B0693F0694BD88AFE4E661FC33B094CD3B7963BED5A727ED8BD6A3A202ABE009D0367",
|
||||
s: "1E9BB81FF7944CA409AD138DBBEE228E1AFCC0C890FC78EC8604639CB0DBDC90F717A99EAD9D272855D00162EE9527567DD6A92CBD629805C0445282BBC916797FF",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-224 #2",
|
||||
key: p521,
|
||||
alg: sha256.New224,
|
||||
message: "test",
|
||||
r: "1C7ED902E123E6815546065A2C4AF977B22AA8EADDB68B2C1110E7EA44D42086BFE4A34B67DDC0E17E96536E358219B23A706C6A6E16BA77B65E1C595D43CAE17FB",
|
||||
s: "177336676304FCB343CE028B38E7B4FBA76C1C1B277DA18CAD2A8478B2A9A9F5BEC0F3BA04F35DB3E4263569EC6AADE8C92746E4C82F8299AE1B8F1739F8FD519A4",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-256 #2",
|
||||
key: p521,
|
||||
alg: sha256.New,
|
||||
message: "test",
|
||||
r: "00E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8",
|
||||
s: "0CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9FDE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-384 #2",
|
||||
key: p521,
|
||||
alg: sha512.New384,
|
||||
message: "test",
|
||||
r: "14BEE21A18B6D8B3C93FAB08D43E739707953244FDBE924FA926D76669E7AC8C89DF62ED8975C2D8397A65A49DCC09F6B0AC62272741924D479354D74FF6075578C",
|
||||
s: "133330865C067A0EAF72362A65E2D7BC4E461E8C8995C3B6226A21BD1AA78F0ED94FE536A0DCA35534F0CD1510C41525D163FE9D74D134881E35141ED5E8E95B979",
|
||||
},
|
||||
ecdsaFixture{
|
||||
name: "P521/SHA-512 #2",
|
||||
key: p521,
|
||||
alg: sha512.New,
|
||||
message: "test",
|
||||
r: "13E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10CDB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D",
|
||||
s: "1FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE3",
|
||||
},
|
||||
}
|
||||
|
||||
func TestECDSA(t *testing.T) {
|
||||
for _, f := range fixtures {
|
||||
testEcsaFixture(&f, t)
|
||||
}
|
||||
}
|
||||
|
||||
func ecdsaLoadInt(s string) (n *big.Int) {
|
||||
n, _ = new(big.Int).SetString(s, 16)
|
||||
return
|
||||
}
|
||||
|
||||
func testEcsaFixture(f *ecdsaFixture, t *testing.T) {
|
||||
t.Logf("Testing %s", f.name)
|
||||
|
||||
h := f.alg()
|
||||
h.Write([]byte(f.message))
|
||||
digest := h.Sum(nil)
|
||||
|
||||
g := f.key.subgroup / 8
|
||||
if len(digest) > g {
|
||||
digest = digest[0:g]
|
||||
}
|
||||
|
||||
r, s, err := rfc6979.SignECDSA(f.key.key, digest, f.alg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
expectedR := ecdsaLoadInt(f.r)
|
||||
expectedS := ecdsaLoadInt(f.s)
|
||||
|
||||
if r.Cmp(expectedR) != 0 {
|
||||
t.Errorf("%s: Expected R of %X, got %X", f.name, expectedR, r)
|
||||
}
|
||||
|
||||
if s.Cmp(expectedS) != 0 {
|
||||
t.Errorf("%s: Expected S of %X, got %X", f.name, expectedS, s)
|
||||
}
|
||||
}
|
76
pkg/crypto/rfc6979/example_test.go
Executable file
76
pkg/crypto/rfc6979/example_test.go
Executable file
|
@ -0,0 +1,76 @@
|
|||
package rfc6979
|
||||
|
||||
import (
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
|
||||
)
|
||||
|
||||
// Generates a 521-bit ECDSA key, uses SHA-512 to sign a message, then verifies
|
||||
// it.
|
||||
func ExampleSignECDSA() {
|
||||
// Generate a key pair.
|
||||
// You need a high-quality PRNG for this.
|
||||
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
|
||||
k, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Hash a message.
|
||||
alg := sha512.New()
|
||||
alg.Write([]byte("I am a potato."))
|
||||
hash := alg.Sum(nil)
|
||||
|
||||
// Sign the message. You don't need a PRNG for this.
|
||||
|
||||
r, s, err := SignECDSA(curve, k.D.Bytes(), hash, sha512.New)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ecdsa.Verify(&k.PublicKey, hash, r, s) {
|
||||
fmt.Println("Invalid signature!")
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a 1024-bit DSA key, uses SHA-1 to sign a message, then verifies it.
|
||||
func ExampleSignDSA() {
|
||||
// Here I'm generating some DSA params, but you should really pre-generate
|
||||
// these and re-use them, since this takes a long time and isn't necessary.
|
||||
k := new(dsa.PrivateKey)
|
||||
dsa.GenerateParameters(&k.Parameters, rand.Reader, dsa.L1024N160)
|
||||
|
||||
// Generate a key pair.
|
||||
// You need a high-quality PRNG for this.
|
||||
err := dsa.GenerateKey(k, rand.Reader)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Hash a message.
|
||||
alg := sha1.New()
|
||||
alg.Write([]byte("I am a potato."))
|
||||
hash := alg.Sum(nil)
|
||||
|
||||
// Sign the message. You don't need a PRNG for this.
|
||||
r, s, err := SignDSA(k, hash, sha1.New)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !dsa.Verify(&k.PublicKey, hash, r, s) {
|
||||
fmt.Println("Invalid signature!")
|
||||
}
|
||||
|
||||
}
|
119
pkg/crypto/rfc6979/rfc6979.go
Executable file
119
pkg/crypto/rfc6979/rfc6979.go
Executable file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
Package rfc6979 is an implementation of RFC 6979's deterministic DSA.
|
||||
|
||||
Such signatures are compatible with standard Digital Signature Algorithm
|
||||
(DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA) digital
|
||||
signatures and can be processed with unmodified verifiers, which need not be
|
||||
aware of the procedure described therein. Deterministic signatures retain
|
||||
the cryptographic security features associated with digital signatures but
|
||||
can be more easily implemented in various environments, since they do not
|
||||
need access to a source of high-quality randomness.
|
||||
|
||||
(https://tools.ietf.org/html/rfc6979)
|
||||
|
||||
Provides functions similar to crypto/dsa and crypto/ecdsa.
|
||||
*/
|
||||
package rfc6979
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"hash"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// mac returns an HMAC of the given key and message.
|
||||
func mac(alg func() hash.Hash, k, m, buf []byte) []byte {
|
||||
h := hmac.New(alg, k)
|
||||
h.Write(m)
|
||||
return h.Sum(buf[:0])
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc6979#section-2.3.2
|
||||
func bits2int(in []byte, qlen int) *big.Int {
|
||||
vlen := len(in) * 8
|
||||
v := new(big.Int).SetBytes(in)
|
||||
if vlen > qlen {
|
||||
v = new(big.Int).Rsh(v, uint(vlen-qlen))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc6979#section-2.3.3
|
||||
func int2octets(v *big.Int, rolen int) []byte {
|
||||
out := v.Bytes()
|
||||
|
||||
// pad with zeros if it's too short
|
||||
if len(out) < rolen {
|
||||
out2 := make([]byte, rolen)
|
||||
copy(out2[rolen-len(out):], out)
|
||||
return out2
|
||||
}
|
||||
|
||||
// drop most significant bytes if it's too long
|
||||
if len(out) > rolen {
|
||||
out2 := make([]byte, rolen)
|
||||
copy(out2, out[len(out)-rolen:])
|
||||
return out2
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc6979#section-2.3.4
|
||||
func bits2octets(in []byte, q *big.Int, qlen, rolen int) []byte {
|
||||
z1 := bits2int(in, qlen)
|
||||
z2 := new(big.Int).Sub(z1, q)
|
||||
if z2.Sign() < 0 {
|
||||
return int2octets(z1, rolen)
|
||||
}
|
||||
return int2octets(z2, rolen)
|
||||
}
|
||||
|
||||
var one = big.NewInt(1)
|
||||
|
||||
// https://tools.ietf.org/html/rfc6979#section-3.2
|
||||
func generateSecret(q, x *big.Int, alg func() hash.Hash, hash []byte, test func(*big.Int) bool) {
|
||||
qlen := q.BitLen()
|
||||
holen := alg().Size()
|
||||
rolen := (qlen + 7) >> 3
|
||||
bx := append(int2octets(x, rolen), bits2octets(hash, q, qlen, rolen)...)
|
||||
|
||||
// Step B
|
||||
v := bytes.Repeat([]byte{0x01}, holen)
|
||||
|
||||
// Step C
|
||||
k := bytes.Repeat([]byte{0x00}, holen)
|
||||
|
||||
// Step D
|
||||
k = mac(alg, k, append(append(v, 0x00), bx...), k)
|
||||
|
||||
// Step E
|
||||
v = mac(alg, k, v, v)
|
||||
|
||||
// Step F
|
||||
k = mac(alg, k, append(append(v, 0x01), bx...), k)
|
||||
|
||||
// Step G
|
||||
v = mac(alg, k, v, v)
|
||||
|
||||
// Step H
|
||||
for {
|
||||
// Step H1
|
||||
var t []byte
|
||||
|
||||
// Step H2
|
||||
for len(t) < qlen/8 {
|
||||
v = mac(alg, k, v, v)
|
||||
t = append(t, v...)
|
||||
}
|
||||
|
||||
// Step H3
|
||||
secret := bits2int(t, qlen)
|
||||
if secret.Cmp(one) >= 0 && secret.Cmp(q) < 0 && test(secret) {
|
||||
return
|
||||
}
|
||||
k = mac(alg, k, append(v, 0x00), k)
|
||||
v = mac(alg, k, v, v)
|
||||
}
|
||||
}
|
28
pkg/crypto/rfc6979/rfc6979_test.go
Executable file
28
pkg/crypto/rfc6979/rfc6979_test.go
Executable file
|
@ -0,0 +1,28 @@
|
|||
package rfc6979
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// https://tools.ietf.org/html/rfc6979#appendix-A.1
|
||||
func TestGenerateSecret(t *testing.T) {
|
||||
q, _ := new(big.Int).SetString("4000000000000000000020108A2E0CC0D99F8A5EF", 16)
|
||||
|
||||
x, _ := new(big.Int).SetString("09A4D6792295A7F730FC3F2B49CBC0F62E862272F", 16)
|
||||
|
||||
hash, _ := hex.DecodeString("AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF")
|
||||
|
||||
expected, _ := new(big.Int).SetString("23AF4074C90A02B3FE61D286D5C87F425E6BDD81B", 16)
|
||||
var actual *big.Int
|
||||
generateSecret(q, x, sha256.New, hash, func(k *big.Int) bool {
|
||||
actual = k
|
||||
return true
|
||||
})
|
||||
|
||||
if actual.Cmp(expected) != 0 {
|
||||
t.Errorf("Expected %x, got %x", expected, actual)
|
||||
}
|
||||
}
|
143
pkg/database/leveldb.go
Normal file
143
pkg/database/leveldb.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
)
|
||||
|
||||
type LDB struct {
|
||||
db *leveldb.DB
|
||||
path string
|
||||
}
|
||||
|
||||
type Database interface {
|
||||
Has(key []byte) (bool, error)
|
||||
Put(key []byte, value []byte) error
|
||||
Get(key []byte) ([]byte, error)
|
||||
Delete(key []byte) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
var (
|
||||
// TX, HEADER AND UTXO are the prefixes for the db
|
||||
TX = []byte("TX")
|
||||
HEADER = []byte("HEADER")
|
||||
LATESTHEADER = []byte("LH")
|
||||
UTXO = []byte("UTXO")
|
||||
)
|
||||
|
||||
func New(path string) *LDB {
|
||||
db, err := leveldb.OpenFile(path, nil)
|
||||
|
||||
if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
|
||||
db, err = leveldb.RecoverFile(path, nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &LDB{
|
||||
db,
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LDB) Has(key []byte) (bool, error) {
|
||||
return l.db.Has(key, nil)
|
||||
}
|
||||
|
||||
func (l *LDB) Put(key []byte, value []byte) error {
|
||||
return l.db.Put(key, value, nil)
|
||||
}
|
||||
func (l *LDB) Get(key []byte) ([]byte, error) {
|
||||
return l.db.Get(key, nil)
|
||||
}
|
||||
func (l *LDB) Delete(key []byte) error {
|
||||
return l.db.Delete(key, nil)
|
||||
}
|
||||
func (l *LDB) Close() error {
|
||||
return l.db.Close()
|
||||
}
|
||||
|
||||
func (l *LDB) AddHeader(header *payload.BlockBase) error {
|
||||
|
||||
table := NewTable(l, HEADER)
|
||||
|
||||
byt, err := header.Bytes()
|
||||
if err != nil {
|
||||
fmt.Println("Could not Get bytes from decoded BlockBase")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("Adding Header, This should be batched!!!!")
|
||||
|
||||
// This is the main mapping
|
||||
//Key: HEADER+BLOCKHASH Value: contents of blockhash
|
||||
key := header.Hash.Bytes()
|
||||
err = table.Put(key, byt)
|
||||
if err != nil {
|
||||
fmt.Println("Error trying to add the original mapping into the DB for Header. Mapping is [Header]+[Hash]")
|
||||
return err
|
||||
}
|
||||
|
||||
// This is the secondary mapping
|
||||
// Key: HEADER + BLOCKHEIGHT Value: blockhash
|
||||
|
||||
bh := uint32ToBytes(header.Index)
|
||||
key = []byte(bh)
|
||||
err = table.Put(key, header.Hash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// This is the third mapping
|
||||
// WARNING: This assumes that headers are adding in order.
|
||||
return table.Put(LATESTHEADER, header.Hash.Bytes())
|
||||
}
|
||||
|
||||
func (l *LDB) AddTransactions(blockhash util.Uint256, txs []transaction.Transactioner) error {
|
||||
|
||||
// SHOULD BE DONE IN BATCH!!!!
|
||||
for i, tx := range txs {
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Println(tx.ID())
|
||||
tx.Encode(buf)
|
||||
txByt := buf.Bytes()
|
||||
txhash, err := tx.ID()
|
||||
if err != nil {
|
||||
fmt.Println("Error adding transaction with bytes", txByt)
|
||||
return err
|
||||
}
|
||||
// This is the original mapping
|
||||
// Key: [TX] + TXHASH
|
||||
key := append(TX, txhash.Bytes()...)
|
||||
l.Put(key, txByt)
|
||||
|
||||
// This is the index
|
||||
// Key: [TX] + BLOCKHASH + I <- i is the incrementer from the for loop
|
||||
//Value : TXHASH
|
||||
key = append(TX, blockhash.Bytes()...)
|
||||
key = append(key, uint32ToBytes(uint32(i))...)
|
||||
|
||||
err = l.Put(key, txhash.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println("Error could not add tx index into db")
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BigEndian
|
||||
func uint32ToBytes(h uint32) []byte {
|
||||
a := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(a, h)
|
||||
return a
|
||||
}
|
80
pkg/database/leveldb_test.go
Normal file
80
pkg/database/leveldb_test.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package database_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/database"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
)
|
||||
|
||||
const path = "temp"
|
||||
|
||||
func cleanup(db *database.LDB) {
|
||||
db.Close()
|
||||
os.RemoveAll(path)
|
||||
}
|
||||
func TestDBCreate(t *testing.T) {
|
||||
db := database.New(path)
|
||||
assert.NotEqual(t, nil, db)
|
||||
cleanup(db)
|
||||
}
|
||||
func TestPutGet(t *testing.T) {
|
||||
db := database.New(path)
|
||||
|
||||
key := []byte("Hello")
|
||||
value := []byte("World")
|
||||
|
||||
err := db.Put(key, value)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
res, err := db.Get(key)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, value, res)
|
||||
cleanup(db)
|
||||
}
|
||||
func TestPutDelete(t *testing.T) {
|
||||
|
||||
db := database.New(path)
|
||||
|
||||
key := []byte("Hello")
|
||||
value := []byte("World")
|
||||
|
||||
err := db.Put(key, value)
|
||||
|
||||
err = db.Delete(key)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
res, err := db.Get(key)
|
||||
|
||||
assert.Equal(t, errors.ErrNotFound, err)
|
||||
assert.Equal(t, res, []byte{})
|
||||
cleanup(db)
|
||||
}
|
||||
|
||||
func TestHas(t *testing.T) {
|
||||
db := database.New("temp")
|
||||
|
||||
res, err := db.Has([]byte("NotExist"))
|
||||
assert.Equal(t, res, false)
|
||||
assert.Equal(t, err, nil)
|
||||
|
||||
key := []byte("Hello")
|
||||
value := []byte("World")
|
||||
|
||||
err = db.Put(key, value)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
res, err = db.Has(key)
|
||||
assert.Equal(t, res, true)
|
||||
assert.Equal(t, err, nil)
|
||||
cleanup(db)
|
||||
|
||||
}
|
||||
func TestDBClose(t *testing.T) {
|
||||
db := database.New("temp")
|
||||
err := db.Close()
|
||||
assert.Equal(t, nil, err)
|
||||
cleanup(db)
|
||||
}
|
36
pkg/database/table.go
Normal file
36
pkg/database/table.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package database
|
||||
|
||||
//Table is an abstract datastructure built on
|
||||
// top of a db
|
||||
type Table struct {
|
||||
prefix []byte
|
||||
db Database
|
||||
}
|
||||
|
||||
func NewTable(db Database, prefix []byte) *Table {
|
||||
return &Table{
|
||||
prefix,
|
||||
db,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) Has(key []byte) (bool, error) {
|
||||
key = append(t.prefix, key...)
|
||||
return t.db.Has(key)
|
||||
}
|
||||
|
||||
func (t *Table) Put(key []byte, value []byte) error {
|
||||
key = append(t.prefix, key...)
|
||||
return t.db.Put(key, value)
|
||||
}
|
||||
func (t *Table) Get(key []byte) ([]byte, error) {
|
||||
key = append(t.prefix, key...)
|
||||
return t.db.Get(key)
|
||||
}
|
||||
func (t *Table) Delete(key []byte) error {
|
||||
key = append(t.prefix, key...)
|
||||
return t.db.Delete(key)
|
||||
}
|
||||
func (t *Table) Close() error {
|
||||
return nil
|
||||
}
|
31
pkg/mempool/config.go
Normal file
31
pkg/mempool/config.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package mempool
|
||||
|
||||
import "time"
|
||||
|
||||
type Config struct {
|
||||
|
||||
// This is the maximum amount
|
||||
// of transactions that we will allow in the mempool
|
||||
MaxNumOfTX uint64
|
||||
|
||||
// FreeTX defines the maximum amount of free txs that can be in the mempool at one time
|
||||
// Default is 20
|
||||
FreeTX uint32
|
||||
|
||||
// MinTXFee is a number in Fixed8 format. If set at 1GAS, minTXFee would equal 1e8
|
||||
// The mineTXFee is used to set the floor, it defaults to zero meaning we will allow all transactions
|
||||
// with a fee of 0 or more
|
||||
MinTXFee uint64
|
||||
|
||||
// MaxTXSize is the maximum number of bytes a tx can have to be entered into the pool
|
||||
MaxTXSize uint64
|
||||
|
||||
// TXTTL is the duration to which we should keep an item in the mempool before removing it
|
||||
// HMM: Should this be amount of blocks instead? For when blocks take time a long time
|
||||
// to process?
|
||||
TXTTL time.Duration
|
||||
|
||||
// SigLimit is the maximum amount of signatures
|
||||
// that we will allow a tx to have, default will be 20
|
||||
SigLimit uint8
|
||||
}
|
138
pkg/mempool/mempool.go
Normal file
138
pkg/mempool/mempool.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
package mempool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMemPoolFull = errors.New("mempool is currently full")
|
||||
ErrMempoolEmpty = errors.New("There are no TXs in the mempool")
|
||||
ErrTXTooBig = errors.New("TX has exceed the maximum threshold")
|
||||
ErrTXTooManyWitnesses = errors.New("Too many witness scripts")
|
||||
ErrFeeTooLow = errors.New("Fee for transaction too low")
|
||||
ErrDuplicateTX = errors.New("TX Already in pool")
|
||||
)
|
||||
|
||||
type Mempool struct {
|
||||
mtx sync.RWMutex
|
||||
pool map[util.Uint256]*TX
|
||||
|
||||
cfg Config
|
||||
}
|
||||
|
||||
func New(cfg Config) *Mempool {
|
||||
mem := &Mempool{
|
||||
sync.RWMutex{},
|
||||
make(map[util.Uint256]*TX, 200),
|
||||
cfg,
|
||||
}
|
||||
|
||||
return mem
|
||||
}
|
||||
func (m *Mempool) AddTransaction(trans transaction.Transactioner) error {
|
||||
|
||||
hash, err := trans.ID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if tx already in pool
|
||||
if m.Exists(hash) {
|
||||
return ErrDuplicateTX
|
||||
}
|
||||
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
if m.cfg.MaxNumOfTX == uint64(len(m.pool)) {
|
||||
return ErrMemPoolFull
|
||||
}
|
||||
|
||||
// TODO:Check for double spend from blockchain itself
|
||||
|
||||
// create tx descriptor
|
||||
tx := Descriptor(trans)
|
||||
|
||||
// check TX size
|
||||
if tx.Size > m.cfg.MaxTXSize {
|
||||
return ErrTXTooBig
|
||||
}
|
||||
|
||||
// check witness length
|
||||
if tx.NumWitness > m.cfg.SigLimit {
|
||||
return ErrTXTooManyWitnesses
|
||||
}
|
||||
|
||||
// TODO: check witness data is good -- Add method to take the Witness and tx return true or false.(blockchain)
|
||||
|
||||
//check fee is over minimum cnfigured
|
||||
if tx.Fee < m.cfg.MinTXFee {
|
||||
return ErrFeeTooLow
|
||||
}
|
||||
|
||||
// Add into pool
|
||||
m.pool[hash] = tx
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveTransaction will remove a transaction from the nodes mempool
|
||||
func (m *Mempool) RemoveTransaction(hash util.Uint256) error {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
if len(m.pool) == 0 {
|
||||
return ErrMempoolEmpty
|
||||
}
|
||||
// deletes regardless of whether key is there or not. So do not check for existence before delete.
|
||||
// Use Exists() for this.
|
||||
delete(m.pool, hash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Size returns the size of the mempool
|
||||
func (m *Mempool) Size() uint64 {
|
||||
m.mtx.RLock()
|
||||
len := uint64(len(m.pool))
|
||||
m.mtx.RUnlock()
|
||||
|
||||
return len
|
||||
}
|
||||
|
||||
// ReturnAllTransactions will return all transactions in the
|
||||
// mempool, will be mostly used by the RPC server
|
||||
func (m *Mempool) ReturnAllTransactions() ([]transaction.Transactioner, error) {
|
||||
transactions := make([]transaction.Transactioner, 0)
|
||||
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
if len(m.pool) == 0 {
|
||||
return nil, ErrMempoolEmpty
|
||||
}
|
||||
|
||||
for _, t := range m.pool {
|
||||
|
||||
if t.ParentTX == nil {
|
||||
fmt.Println(t, "NILNIL")
|
||||
}
|
||||
transactions = append(transactions, *t.ParentTX)
|
||||
fmt.Println(transactions)
|
||||
}
|
||||
|
||||
return transactions, nil
|
||||
|
||||
}
|
||||
|
||||
// Exists check whether the transaction exists in the mempool
|
||||
func (m *Mempool) Exists(hash util.Uint256) bool {
|
||||
m.mtx.RLock()
|
||||
_, ok := m.pool[hash]
|
||||
m.mtx.RUnlock()
|
||||
|
||||
return ok
|
||||
}
|
211
pkg/mempool/mempool_test.go
Normal file
211
pkg/mempool/mempool_test.go
Normal file
|
@ -0,0 +1,211 @@
|
|||
package mempool_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/mempool"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMempoolExists(t *testing.T) {
|
||||
cfg := mempool.Config{
|
||||
MaxNumOfTX: 100,
|
||||
FreeTX: 20,
|
||||
MinTXFee: 0,
|
||||
MaxTXSize: 10000,
|
||||
TXTTL: 10 * time.Minute,
|
||||
SigLimit: 20,
|
||||
}
|
||||
mem := mempool.New(cfg)
|
||||
|
||||
trans := transaction.NewContract(0)
|
||||
|
||||
assert.Equal(t, false, mem.Exists(trans.Hash))
|
||||
|
||||
err := mem.AddTransaction(trans)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
assert.Equal(t, true, mem.Exists(trans.Hash))
|
||||
}
|
||||
func TestMempoolFullPool(t *testing.T) {
|
||||
|
||||
maxTx := uint64(100)
|
||||
cfg := mempool.Config{
|
||||
MaxNumOfTX: maxTx,
|
||||
FreeTX: 20,
|
||||
MinTXFee: 0,
|
||||
MaxTXSize: 10000,
|
||||
TXTTL: 10 * time.Minute,
|
||||
SigLimit: 20,
|
||||
}
|
||||
mem := mempool.New(cfg)
|
||||
|
||||
for i := uint64(1); i <= maxTx; i++ {
|
||||
trans := transaction.NewContract(0)
|
||||
attr := &transaction.Attribute{
|
||||
Usage: transaction.Remark,
|
||||
Data: []byte{byte(i)},
|
||||
}
|
||||
trans.AddAttribute(attr)
|
||||
err := mem.AddTransaction(trans)
|
||||
assert.Equal(t, nil, err)
|
||||
}
|
||||
trans := transaction.NewContract(0)
|
||||
err := mem.AddTransaction(trans)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
assert.Equal(t, mempool.ErrMemPoolFull, err)
|
||||
}
|
||||
func TestMempoolLargeTX(t *testing.T) {
|
||||
|
||||
maxTxSize := uint64(100)
|
||||
cfg := mempool.Config{
|
||||
MaxNumOfTX: 100,
|
||||
FreeTX: 20,
|
||||
MinTXFee: 0,
|
||||
MaxTXSize: maxTxSize,
|
||||
TXTTL: 10 * time.Minute,
|
||||
SigLimit: 20,
|
||||
}
|
||||
mem := mempool.New(cfg)
|
||||
|
||||
trans := transaction.NewContract(0)
|
||||
for i := uint64(1); i <= 100; i++ { // 100 attributes will be over 100 bytes
|
||||
attr := &transaction.Attribute{
|
||||
Usage: transaction.Remark,
|
||||
Data: []byte{byte(i)},
|
||||
}
|
||||
trans.AddAttribute(attr)
|
||||
}
|
||||
|
||||
err := mem.AddTransaction(trans)
|
||||
assert.NotEqual(t, nil, err)
|
||||
assert.Equal(t, mempool.ErrTXTooBig, err)
|
||||
}
|
||||
func TestMempoolTooManyWitness(t *testing.T) {
|
||||
|
||||
maxWitness := uint8(3)
|
||||
cfg := mempool.Config{
|
||||
MaxNumOfTX: 100,
|
||||
FreeTX: 20,
|
||||
MinTXFee: 0,
|
||||
MaxTXSize: 10000,
|
||||
TXTTL: 10 * time.Minute,
|
||||
SigLimit: maxWitness,
|
||||
}
|
||||
mem := mempool.New(cfg)
|
||||
|
||||
trans := transaction.NewContract(0)
|
||||
for i := uint8(1); i <= maxWitness; i++ { // 100 attributes will be over 100 bytes
|
||||
wit := &transaction.Witness{
|
||||
InvocationScript: []byte{byte(i)},
|
||||
VerificationScript: []byte{byte(i)},
|
||||
}
|
||||
trans.AddWitness(wit)
|
||||
}
|
||||
|
||||
trans.AddWitness(&transaction.Witness{
|
||||
InvocationScript: []byte{},
|
||||
VerificationScript: []byte{},
|
||||
})
|
||||
|
||||
err := mem.AddTransaction(trans)
|
||||
assert.NotEqual(t, nil, err)
|
||||
assert.Equal(t, mempool.ErrTXTooManyWitnesses, err)
|
||||
}
|
||||
func TestMempoolDuplicate(t *testing.T) {
|
||||
|
||||
cfg := mempool.Config{
|
||||
MaxNumOfTX: 100,
|
||||
FreeTX: 20,
|
||||
MinTXFee: 0,
|
||||
MaxTXSize: 10000,
|
||||
TXTTL: 10 * time.Minute,
|
||||
SigLimit: 1,
|
||||
}
|
||||
mem := mempool.New(cfg)
|
||||
|
||||
trans := transaction.NewContract(0)
|
||||
|
||||
err := mem.AddTransaction(trans)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
err = mem.AddTransaction(trans)
|
||||
assert.NotEqual(t, nil, err)
|
||||
assert.Equal(t, mempool.ErrDuplicateTX, err)
|
||||
}
|
||||
func TestMempoolReturnAll(t *testing.T) {
|
||||
|
||||
cfg := mempool.Config{
|
||||
MaxNumOfTX: 100,
|
||||
FreeTX: 20,
|
||||
MinTXFee: 0,
|
||||
MaxTXSize: 10000,
|
||||
TXTTL: 10 * time.Minute,
|
||||
SigLimit: 1,
|
||||
}
|
||||
mem := mempool.New(cfg)
|
||||
|
||||
numTx := uint64(10)
|
||||
|
||||
for i := uint64(1); i <= numTx; i++ {
|
||||
trans := transaction.NewContract(0)
|
||||
attr := &transaction.Attribute{
|
||||
Usage: transaction.Remark,
|
||||
Data: []byte{byte(i)},
|
||||
}
|
||||
trans.AddAttribute(attr)
|
||||
err := mem.AddTransaction(trans)
|
||||
assert.Equal(t, nil, err)
|
||||
}
|
||||
|
||||
AllTrans, err := mem.ReturnAllTransactions()
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
assert.Equal(t, numTx, uint64(len(AllTrans)))
|
||||
|
||||
}
|
||||
func TestMempoolRemove(t *testing.T) {
|
||||
|
||||
cfg := mempool.Config{
|
||||
MaxNumOfTX: 100,
|
||||
FreeTX: 20,
|
||||
MinTXFee: 0,
|
||||
MaxTXSize: 10000,
|
||||
TXTTL: 3 * time.Minute,
|
||||
SigLimit: 1,
|
||||
}
|
||||
mem := mempool.New(cfg)
|
||||
|
||||
// Remove a transaction when mempool is empty
|
||||
trans := transaction.NewContract(0)
|
||||
hash, _ := trans.ID()
|
||||
err := mem.RemoveTransaction(hash)
|
||||
assert.Equal(t, mempool.ErrMempoolEmpty, err)
|
||||
|
||||
// Add tx1 into mempool
|
||||
err = mem.AddTransaction(trans)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
diffTrans := transaction.NewContract(0) // TX2
|
||||
|
||||
diffTrans.AddAttribute(
|
||||
&transaction.Attribute{
|
||||
Usage: transaction.Remark,
|
||||
Data: []byte{},
|
||||
})
|
||||
|
||||
diffHash, _ := diffTrans.ID()
|
||||
|
||||
// Try removing TX2, when only TX1 is in mempool
|
||||
err = mem.RemoveTransaction(diffHash)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, uint64(1), mem.Size())
|
||||
// Remove hash that is in mempool
|
||||
err = mem.RemoveTransaction(hash)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, uint64(0), mem.Size())
|
||||
|
||||
}
|
45
pkg/mempool/tx.go
Normal file
45
pkg/mempool/tx.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package mempool
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
|
||||
)
|
||||
|
||||
// TX is a wrapper struct around a normal tx
|
||||
// which includes extra information about the TX
|
||||
// <aligned>
|
||||
type TX struct {
|
||||
ParentTX *transaction.Transactioner
|
||||
Added time.Time
|
||||
Fee uint64
|
||||
Size uint64
|
||||
NumWitness uint8
|
||||
Free bool
|
||||
}
|
||||
|
||||
// Descriptor takes a transaction and puts it into a new TX struct along with metadata
|
||||
func Descriptor(trans transaction.Transactioner) *TX {
|
||||
|
||||
var desc TX
|
||||
desc.ParentTX = &trans
|
||||
desc.Fee = getFee(trans.TXOs(), trans.UTXOs())
|
||||
desc.Free = desc.Fee != 0
|
||||
desc.Added = time.Now()
|
||||
desc.Size = uint64(len(trans.Bytes()))
|
||||
|
||||
numWit := len(trans.Witness())
|
||||
if numWit > 255 || numWit < 0 { // < 0 should not happen
|
||||
numWit = 255
|
||||
}
|
||||
desc.NumWitness = uint8(numWit)
|
||||
|
||||
return &desc
|
||||
}
|
||||
|
||||
// TODO: need blockchain package complete for fee calculation
|
||||
// HMM:Could also put the function in the config
|
||||
func getFee(in []*transaction.Input, out []*transaction.Output) uint64 {
|
||||
// query utxo set for inputs, then subtract from out to get fee
|
||||
return 0
|
||||
}
|
48
pkg/peer/config.go
Normal file
48
pkg/peer/config.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package peer
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
)
|
||||
|
||||
// LocalConfig specifies the properties that should be available for each remote peer
|
||||
|
||||
type LocalConfig struct {
|
||||
Net protocol.Magic
|
||||
UserAgent string
|
||||
Services protocol.ServiceFlag
|
||||
Nonce uint32
|
||||
ProtocolVer protocol.Version
|
||||
Relay bool
|
||||
Port uint16
|
||||
// pointer to config will keep the startheight updated for each version
|
||||
//Message we plan to send
|
||||
StartHeight func() uint32
|
||||
OnHeader func(*Peer, *payload.HeadersMessage)
|
||||
OnGetHeaders func(msg *payload.GetHeadersMessage) // returns HeaderMessage
|
||||
OnAddr func(*Peer, *payload.AddrMessage)
|
||||
OnGetAddr func(*Peer, *payload.GetAddrMessage)
|
||||
OnInv func(*Peer, *payload.InvMessage)
|
||||
OnGetData func(msg *payload.GetDataMessage)
|
||||
OnBlock func(*Peer, *payload.BlockMessage)
|
||||
OnGetBlocks func(msg *payload.GetBlocksMessage)
|
||||
}
|
||||
|
||||
// func DefaultConfig() LocalConfig {
|
||||
// return LocalConfig{
|
||||
// Net: protocol.MainNet,
|
||||
// UserAgent: "NEO-GO-Default",
|
||||
// Services: protocol.NodePeerService,
|
||||
// Nonce: 1200,
|
||||
// ProtocolVer: 0,
|
||||
// Relay: false,
|
||||
// Port: 10332,
|
||||
// // pointer to config will keep the startheight updated for each version
|
||||
// //Message we plan to send
|
||||
// StartHeight: DefaultHeight,
|
||||
// }
|
||||
// }
|
||||
|
||||
// func DefaultHeight() uint32 {
|
||||
// return 10
|
||||
// }
|
433
pkg/peer/peer.go
Normal file
433
pkg/peer/peer.go
Normal file
|
@ -0,0 +1,433 @@
|
|||
// This impl uses channels to simulate the queue handler with the actor model.
|
||||
// A suitable number k ,should be set for channel size, because if #numOfMsg > k,
|
||||
// we lose determinism. k chosen should be large enough that when filled, it shall indicate that
|
||||
// the peer has stopped responding, since we do not have a pingMSG, we will need another way to shut down
|
||||
// peers
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/peer/stall"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
const (
|
||||
maxOutboundConnections = 100
|
||||
protocolVer = protocol.DefaultVersion
|
||||
handshakeTimeout = 30 * time.Second
|
||||
idleTimeout = 5 * time.Minute // If no message received after idleTimeout, then peer disconnects
|
||||
|
||||
// nodes will have `responseTime` seconds to reply with a response
|
||||
responseTime = 120 * time.Second
|
||||
|
||||
// the stall detector will check every `tickerInterval` to see if messages
|
||||
// are overdue. Should be less than `responseTime`
|
||||
tickerInterval = 30 * time.Second
|
||||
|
||||
// The input buffer size is the amount of mesages that
|
||||
// can be buffered into the channel to receive at once before
|
||||
// blocking, and before determinism is broken
|
||||
inputBufferSize = 100
|
||||
|
||||
// The output buffer size is the amount of messages that
|
||||
// can be buffered into the channel to send at once before
|
||||
// blocking, and before determinism is broken.
|
||||
outputBufferSize = 100
|
||||
|
||||
// pingInterval = 20 * time.Second //Not implemented in neo clients
|
||||
)
|
||||
|
||||
var (
|
||||
errHandShakeTimeout = errors.New("Handshake timed out, peers have " + string(handshakeTimeout) + " Seconds to Complete the handshake")
|
||||
)
|
||||
|
||||
type Peer struct {
|
||||
config LocalConfig
|
||||
conn net.Conn
|
||||
|
||||
// atomic vals
|
||||
disconnected int32
|
||||
|
||||
//unchangeable state: concurrent safe
|
||||
addr string
|
||||
protoVer protocol.Version
|
||||
port uint16
|
||||
inbound bool
|
||||
userAgent string
|
||||
services protocol.ServiceFlag
|
||||
createdAt time.Time
|
||||
relay bool
|
||||
|
||||
statemutex sync.Mutex
|
||||
verackReceived bool
|
||||
versionKnown bool
|
||||
|
||||
*stall.Detector
|
||||
|
||||
inch chan func() // will handle all incoming connections from peer
|
||||
outch chan func() // will handle all outcoming connections from peer
|
||||
quitch chan struct{}
|
||||
}
|
||||
|
||||
func NewPeer(con net.Conn, inbound bool, cfg LocalConfig) *Peer {
|
||||
p := Peer{}
|
||||
p.inch = make(chan func(), inputBufferSize)
|
||||
p.outch = make(chan func(), outputBufferSize)
|
||||
p.quitch = make(chan struct{}, 1)
|
||||
p.inbound = inbound
|
||||
p.config = cfg
|
||||
p.conn = con
|
||||
p.createdAt = time.Now()
|
||||
p.addr = p.conn.RemoteAddr().String()
|
||||
|
||||
p.Detector = stall.NewDetector(responseTime, tickerInterval)
|
||||
|
||||
// TODO: set the unchangeable states
|
||||
return &p
|
||||
}
|
||||
|
||||
// Write to a peer
|
||||
func (p *Peer) Write(msg wire.Messager) error {
|
||||
return wire.WriteMessage(p.conn, p.config.Net, msg)
|
||||
}
|
||||
|
||||
// Read to a peer
|
||||
func (p *Peer) Read() (wire.Messager, error) {
|
||||
return wire.ReadMessage(p.conn, p.config.Net)
|
||||
}
|
||||
|
||||
// Disconnects from a peer
|
||||
func (p *Peer) Disconnect() {
|
||||
|
||||
// return if already disconnected
|
||||
if atomic.LoadInt32(&p.disconnected) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddInt32(&p.disconnected, 1)
|
||||
|
||||
p.Detector.Quit()
|
||||
close(p.quitch)
|
||||
p.conn.Close()
|
||||
|
||||
fmt.Println("Disconnected Peer with address", p.RemoteAddr().String())
|
||||
|
||||
}
|
||||
|
||||
// Exposed API functions below
|
||||
func (p *Peer) Port() uint16 {
|
||||
return p.port
|
||||
}
|
||||
func (p *Peer) CreatedAt() time.Time {
|
||||
return p.createdAt
|
||||
}
|
||||
func (p *Peer) CanRelay() bool {
|
||||
return p.relay
|
||||
}
|
||||
func (p *Peer) LocalAddr() net.Addr {
|
||||
return p.conn.LocalAddr()
|
||||
}
|
||||
func (p *Peer) RemoteAddr() net.Addr {
|
||||
return p.conn.RemoteAddr()
|
||||
}
|
||||
func (p *Peer) Services() protocol.ServiceFlag {
|
||||
return p.config.Services
|
||||
}
|
||||
func (p *Peer) Inbound() bool {
|
||||
return p.inbound
|
||||
}
|
||||
func (p *Peer) UserAgent() string {
|
||||
return p.config.UserAgent
|
||||
}
|
||||
func (p *Peer) IsVerackReceived() bool {
|
||||
return p.verackReceived
|
||||
}
|
||||
func (p *Peer) NotifyDisconnect() bool {
|
||||
fmt.Println("Peer has not disconnected yet")
|
||||
<-p.quitch
|
||||
fmt.Println("Peer has just disconnected")
|
||||
return true
|
||||
}
|
||||
|
||||
//End of Exposed API functions//
|
||||
|
||||
// Ping not impl. in neo yet, adding it now
|
||||
// will cause this client to disconnect from all other implementations
|
||||
func (p *Peer) PingLoop() { /*not implemented in other neo clients*/ }
|
||||
|
||||
// Run is used to start communicating with the peer
|
||||
// completes the handshake and starts observing
|
||||
// for messages coming in
|
||||
func (p *Peer) Run() error {
|
||||
|
||||
err := p.Handshake()
|
||||
|
||||
go p.StartProtocol()
|
||||
go p.ReadLoop()
|
||||
go p.WriteLoop()
|
||||
|
||||
//go p.PingLoop() // since it is not implemented. It will disconnect all other impls.
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// run as a go-routine, will act as our queue for messages
|
||||
// should be ran after handshake
|
||||
func (p *Peer) StartProtocol() {
|
||||
loop:
|
||||
for atomic.LoadInt32(&p.disconnected) == 0 {
|
||||
select {
|
||||
case f := <-p.inch:
|
||||
f()
|
||||
case <-p.quitch:
|
||||
break loop
|
||||
case <-p.Detector.Quitch:
|
||||
fmt.Println("Peer stalled, disconnecting")
|
||||
break loop
|
||||
}
|
||||
}
|
||||
p.Disconnect()
|
||||
}
|
||||
|
||||
// Should only be called after handshake is complete
|
||||
// on a seperate go-routine.
|
||||
// ReadLoop Will block on the read until a message is
|
||||
// read
|
||||
|
||||
func (p *Peer) ReadLoop() {
|
||||
|
||||
idleTimer := time.AfterFunc(idleTimeout, func() {
|
||||
fmt.Println("Timing out peer")
|
||||
p.Disconnect()
|
||||
})
|
||||
|
||||
loop:
|
||||
for atomic.LoadInt32(&p.disconnected) == 0 {
|
||||
|
||||
idleTimer.Reset(idleTimeout) // reset timer on each loop
|
||||
|
||||
readmsg, err := p.Read()
|
||||
|
||||
// Message read; stop Timer
|
||||
idleTimer.Stop()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Err on read", err) // This will also happen if Peer is disconnected
|
||||
break loop
|
||||
}
|
||||
|
||||
// Remove message as pending from the stall detector
|
||||
p.Detector.RemoveMessage(readmsg.Command())
|
||||
|
||||
switch msg := readmsg.(type) {
|
||||
|
||||
case *payload.VersionMessage:
|
||||
fmt.Println("Already received a Version, disconnecting. " + p.RemoteAddr().String())
|
||||
break loop // We have already done the handshake, break loop and disconnect
|
||||
case *payload.VerackMessage:
|
||||
if p.verackReceived {
|
||||
fmt.Println("Already received a Verack, disconnecting. " + p.RemoteAddr().String())
|
||||
break loop
|
||||
}
|
||||
p.statemutex.Lock() // This should not happen, however if it does, then we should set it.
|
||||
p.verackReceived = true
|
||||
p.statemutex.Unlock()
|
||||
case *payload.AddrMessage:
|
||||
p.OnAddr(msg)
|
||||
case *payload.GetAddrMessage:
|
||||
p.OnGetAddr(msg)
|
||||
case *payload.GetBlocksMessage:
|
||||
p.OnGetBlocks(msg)
|
||||
case *payload.BlockMessage:
|
||||
p.OnBlocks(msg)
|
||||
case *payload.HeadersMessage:
|
||||
p.OnHeaders(msg)
|
||||
case *payload.GetHeadersMessage:
|
||||
p.OnGetHeaders(msg)
|
||||
case *payload.InvMessage:
|
||||
p.OnInv(msg)
|
||||
case *payload.GetDataMessage:
|
||||
p.OnGetData(msg)
|
||||
case *payload.TXMessage:
|
||||
p.OnTX(msg)
|
||||
default:
|
||||
fmt.Println("Cannot recognise message", msg.Command()) //Do not disconnect peer, just Log Message
|
||||
}
|
||||
}
|
||||
|
||||
idleTimer.Stop()
|
||||
p.Disconnect()
|
||||
}
|
||||
|
||||
// WriteLoop will Queue all messages to be written to
|
||||
// the peer.
|
||||
func (p *Peer) WriteLoop() {
|
||||
for atomic.LoadInt32(&p.disconnected) == 0 {
|
||||
select {
|
||||
case f := <-p.outch:
|
||||
f()
|
||||
case <-p.Detector.Quitch: // if the detector quits, disconnect peer
|
||||
p.Disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Peer) OnGetData(msg *payload.GetDataMessage) {
|
||||
|
||||
p.inch <- func() {
|
||||
// fmt.Println(msg.Hashes)
|
||||
fmt.Println("That was an getdata Message please pass func down through config", msg.Command())
|
||||
}
|
||||
}
|
||||
func (p *Peer) OnTX(msg *payload.TXMessage) {
|
||||
|
||||
p.inch <- func() {
|
||||
// fmt.Println(msg.Hashes)
|
||||
getdata, err := payload.NewGetDataMessage(payload.InvTypeTx)
|
||||
if err != nil {
|
||||
fmt.Println("Eor", err)
|
||||
}
|
||||
id, err := msg.Tx.ID()
|
||||
getdata.AddHash(id)
|
||||
p.Write(getdata)
|
||||
fmt.Println("That was an tx Message please pass func down through config", msg.Command())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Peer) OnInv(msg *payload.InvMessage) {
|
||||
|
||||
p.inch <- func() {
|
||||
if p.config.OnInv != nil {
|
||||
p.config.OnInv(p, msg)
|
||||
}
|
||||
fmt.Println("That was an inv Message please pass func down through config", msg.Command())
|
||||
}
|
||||
}
|
||||
|
||||
// OnGetHeaders Listener, outside of the anonymous func will be extra functionality
|
||||
// like timing
|
||||
func (p *Peer) OnGetHeaders(msg *payload.GetHeadersMessage) {
|
||||
p.inch <- func() {
|
||||
if p.config.OnGetHeaders != nil {
|
||||
p.config.OnGetHeaders(msg)
|
||||
}
|
||||
fmt.Println("That was a getheaders message, please pass func down through config", msg.Command())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// OnAddr Listener
|
||||
func (p *Peer) OnAddr(msg *payload.AddrMessage) {
|
||||
p.inch <- func() {
|
||||
if p.config.OnAddr != nil {
|
||||
p.config.OnAddr(p, msg)
|
||||
}
|
||||
fmt.Println("That was a addr message, please pass func down through config", msg.Command())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// OnGetAddr Listener
|
||||
func (p *Peer) OnGetAddr(msg *payload.GetAddrMessage) {
|
||||
p.inch <- func() {
|
||||
if p.config.OnGetAddr != nil {
|
||||
p.config.OnGetAddr(p, msg)
|
||||
}
|
||||
fmt.Println("That was a getaddr message, please pass func down through config", msg.Command())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// OnGetBlocks Listener
|
||||
func (p *Peer) OnGetBlocks(msg *payload.GetBlocksMessage) {
|
||||
p.inch <- func() {
|
||||
if p.config.OnGetBlocks != nil {
|
||||
p.config.OnGetBlocks(msg)
|
||||
}
|
||||
fmt.Println("That was a getblocks message, please pass func down through config", msg.Command())
|
||||
}
|
||||
}
|
||||
|
||||
// OnBlocks Listener
|
||||
func (p *Peer) OnBlocks(msg *payload.BlockMessage) {
|
||||
p.inch <- func() {
|
||||
if p.config.OnBlock != nil {
|
||||
p.config.OnBlock(p, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnVersion Listener will be called
|
||||
// during the handshake, any error checking should be done here for the versionMessage.
|
||||
// This should only ever be called during the handshake. Any other place and the peer will disconnect.
|
||||
func (p *Peer) OnVersion(msg *payload.VersionMessage) error {
|
||||
if msg.Nonce == p.config.Nonce {
|
||||
p.conn.Close()
|
||||
return errors.New("Self connection, disconnecting Peer")
|
||||
}
|
||||
p.versionKnown = true
|
||||
p.port = msg.Port
|
||||
p.services = msg.Services
|
||||
p.userAgent = string(msg.UserAgent)
|
||||
p.createdAt = time.Now()
|
||||
p.relay = msg.Relay
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnHeaders Listener
|
||||
func (p *Peer) OnHeaders(msg *payload.HeadersMessage) {
|
||||
fmt.Println("We have received the headers")
|
||||
p.inch <- func() {
|
||||
if p.config.OnHeader != nil {
|
||||
p.config.OnHeader(p, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RequestHeaders will write a getheaders to peer
|
||||
func (p *Peer) RequestHeaders(hash util.Uint256) error {
|
||||
fmt.Println("Sending header request")
|
||||
c := make(chan error, 0)
|
||||
p.outch <- func() {
|
||||
p.Detector.AddMessage(command.GetHeaders)
|
||||
getHeaders, err := payload.NewGetHeadersMessage([]util.Uint256{hash}, util.Uint256{})
|
||||
err = p.Write(getHeaders)
|
||||
c <- err
|
||||
}
|
||||
|
||||
return <-c
|
||||
|
||||
}
|
||||
|
||||
// RequestBlocks will ask a peer for a block
|
||||
func (p *Peer) RequestBlocks(hashes []util.Uint256) error {
|
||||
fmt.Println("Requesting block from peer")
|
||||
c := make(chan error, 0)
|
||||
|
||||
p.outch <- func() {
|
||||
p.Detector.AddMessage(command.GetData)
|
||||
getdata, err := payload.NewGetDataMessage(payload.InvTypeBlock)
|
||||
err = getdata.AddHashes(hashes)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
err = p.Write(getdata)
|
||||
c <- err
|
||||
}
|
||||
|
||||
return <-c
|
||||
|
||||
}
|
209
pkg/peer/peer_test.go
Normal file
209
pkg/peer/peer_test.go
Normal file
|
@ -0,0 +1,209 @@
|
|||
package peer_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/peer"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func returnConfig() peer.LocalConfig {
|
||||
|
||||
DefaultHeight := func() uint32 {
|
||||
return 10
|
||||
}
|
||||
|
||||
OnAddr := func(p *peer.Peer, msg *payload.AddrMessage) {}
|
||||
OnHeader := func(p *peer.Peer, msg *payload.HeadersMessage) {}
|
||||
OnGetHeaders := func(msg *payload.GetHeadersMessage) {}
|
||||
OnInv := func(p *peer.Peer, msg *payload.InvMessage) {}
|
||||
OnGetData := func(msg *payload.GetDataMessage) {}
|
||||
OnBlock := func(p *peer.Peer, msg *payload.BlockMessage) {}
|
||||
OnGetBlocks := func(msg *payload.GetBlocksMessage) {}
|
||||
|
||||
return peer.LocalConfig{
|
||||
Net: protocol.MainNet,
|
||||
UserAgent: "NEO-GO-Default",
|
||||
Services: protocol.NodePeerService,
|
||||
Nonce: 1200,
|
||||
ProtocolVer: 0,
|
||||
Relay: false,
|
||||
Port: 10332,
|
||||
// pointer to config will keep the startheight updated for each version
|
||||
//Message we plan to send
|
||||
StartHeight: DefaultHeight,
|
||||
OnHeader: OnHeader,
|
||||
OnAddr: OnAddr,
|
||||
OnGetHeaders: OnGetHeaders,
|
||||
OnInv: OnInv,
|
||||
OnGetData: OnGetData,
|
||||
OnBlock: OnBlock,
|
||||
OnGetBlocks: OnGetBlocks,
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshake(t *testing.T) {
|
||||
address := ":20338"
|
||||
go func() {
|
||||
|
||||
conn, err := net.DialTimeout("tcp", address, 2*time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p := peer.NewPeer(conn, true, returnConfig())
|
||||
err = p.Run()
|
||||
verack, err := payload.NewVerackMessage()
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
if err := p.Write(verack); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, true, p.IsVerackReceived())
|
||||
|
||||
}()
|
||||
|
||||
listener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
listener.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("82.2.97.142"), Port: 20338}
|
||||
nonce := uint32(100)
|
||||
messageVer, err := payload.NewVersionMessage(tcpAddrMe, 2595770, false, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := wire.WriteMessage(conn, protocol.MainNet, messageVer); err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
readmsg, err := wire.ReadMessage(conn, protocol.MainNet)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
version, ok := readmsg.(*payload.VersionMessage)
|
||||
if !ok {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.NotEqual(t, nil, version)
|
||||
|
||||
messageVrck, err := payload.NewVerackMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.NotEqual(t, nil, messageVrck)
|
||||
|
||||
if err := wire.WriteMessage(conn, protocol.MainNet, messageVrck); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
readmsg, err = wire.ReadMessage(conn, protocol.MainNet)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.NotEqual(t, nil, readmsg)
|
||||
|
||||
verk, ok := readmsg.(*payload.VerackMessage)
|
||||
if !ok {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.NotEqual(t, nil, verk)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestConfigurations(t *testing.T) {
|
||||
_, conn := net.Pipe()
|
||||
|
||||
inbound := true
|
||||
|
||||
config := returnConfig()
|
||||
|
||||
p := peer.NewPeer(conn, inbound, config)
|
||||
|
||||
// test inbound
|
||||
assert.Equal(t, inbound, p.Inbound())
|
||||
|
||||
// handshake not done, should be false
|
||||
assert.Equal(t, false, p.IsVerackReceived())
|
||||
|
||||
assert.Equal(t, config.Services, p.Services())
|
||||
|
||||
assert.Equal(t, config.UserAgent, p.UserAgent())
|
||||
|
||||
assert.Equal(t, config.Relay, p.CanRelay())
|
||||
|
||||
assert.WithinDuration(t, time.Now(), p.CreatedAt(), 1*time.Second)
|
||||
|
||||
}
|
||||
|
||||
func TestHandshakeCancelled(t *testing.T) {
|
||||
// These are the conditions which should invalidate the handshake.
|
||||
// Make sure peer is disconnected.
|
||||
}
|
||||
|
||||
func TestPeerDisconnect(t *testing.T) {
|
||||
// Make sure everything is shutdown
|
||||
// Make sure timer is shutdown in stall detector too. Should maybe put this part of test into stall detector.
|
||||
|
||||
_, conn := net.Pipe()
|
||||
inbound := true
|
||||
config := returnConfig()
|
||||
p := peer.NewPeer(conn, inbound, config)
|
||||
fmt.Println("Calling disconnect")
|
||||
p.Disconnect()
|
||||
fmt.Println("Disconnect finished calling")
|
||||
verack, _ := payload.NewVerackMessage()
|
||||
|
||||
fmt.Println(" We good here")
|
||||
|
||||
err := p.Write(verack)
|
||||
|
||||
assert.NotEqual(t, err, nil)
|
||||
|
||||
// Check if Stall detector is still running
|
||||
_, ok := <-p.Detector.Quitch
|
||||
assert.Equal(t, ok, false)
|
||||
|
||||
}
|
||||
|
||||
func TestNotifyDisconnect(t *testing.T) {
|
||||
|
||||
_, conn := net.Pipe()
|
||||
inbound := true
|
||||
config := returnConfig()
|
||||
p := peer.NewPeer(conn, inbound, config)
|
||||
|
||||
p.Disconnect()
|
||||
p.NotifyDisconnect()
|
||||
// TestNotify uses default test timeout as the passing condition
|
||||
// Failure condition can be seen when you comment out p.Disconnect()
|
||||
}
|
131
pkg/peer/peerhandshake.go
Normal file
131
pkg/peer/peerhandshake.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util/ip"
|
||||
)
|
||||
|
||||
func (p *Peer) Handshake() error {
|
||||
|
||||
handshakeErr := make(chan error, 1)
|
||||
go func() {
|
||||
if p.inbound {
|
||||
handshakeErr <- p.inboundHandShake()
|
||||
} else {
|
||||
handshakeErr <- p.outboundHandShake()
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-handshakeErr:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-time.After(handshakeTimeout):
|
||||
return errHandShakeTimeout
|
||||
}
|
||||
|
||||
// This is purely here for Logs
|
||||
if p.inbound {
|
||||
fmt.Println("inbound handshake with", p.RemoteAddr().String(), "successful")
|
||||
} else {
|
||||
|
||||
fmt.Println("outbound handshake with", p.RemoteAddr().String(), "successful")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If this peer has an inbound conn (conn that is going into another peer)
|
||||
// then he has dialed and so, we must read the version message
|
||||
func (p *Peer) inboundHandShake() error {
|
||||
var err error
|
||||
if err := p.writeLocalVersionMSG(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readRemoteVersionMSG(); err != nil {
|
||||
return err
|
||||
}
|
||||
verack, err := payload.NewVerackMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.Write(verack)
|
||||
return p.readVerack()
|
||||
}
|
||||
func (p *Peer) outboundHandShake() error {
|
||||
var err error
|
||||
err = p.readRemoteVersionMSG()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.writeLocalVersionMSG()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.readVerack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verack, err := payload.NewVerackMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.Write(verack)
|
||||
}
|
||||
func (p *Peer) writeLocalVersionMSG() error {
|
||||
|
||||
nonce := p.config.Nonce
|
||||
relay := p.config.Relay
|
||||
port := int(p.config.Port)
|
||||
ua := p.config.UserAgent
|
||||
sh := p.config.StartHeight()
|
||||
services := p.config.Services
|
||||
proto := p.config.ProtocolVer
|
||||
ip := iputils.GetLocalIP()
|
||||
tcpAddrMe := &net.TCPAddr{IP: ip, Port: port}
|
||||
|
||||
messageVer, err := payload.NewVersionMessage(tcpAddrMe, sh, relay, proto, ua, nonce, services)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.Write(messageVer)
|
||||
}
|
||||
|
||||
func (p *Peer) readRemoteVersionMSG() error {
|
||||
readmsg, err := wire.ReadMessage(p.conn, p.config.Net)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version, ok := readmsg.(*payload.VersionMessage)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
return p.OnVersion(version)
|
||||
}
|
||||
|
||||
func (p *Peer) readVerack() error {
|
||||
readmsg, err := wire.ReadMessage(p.conn, p.config.Net)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, ok := readmsg.(*payload.VerackMessage)
|
||||
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
// should only be accessed on one go-routine
|
||||
p.verackReceived = true
|
||||
|
||||
return nil
|
||||
}
|
67
pkg/peer/readme.md
Normal file
67
pkg/peer/readme.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Package - Peer
|
||||
|
||||
|
||||
|
||||
## Responsibility
|
||||
|
||||
Once a connection has been made. The connection will represent a established peer to the localNode. Since a connection and the `Wire` is a golang primitive, that we cannot do much with. The peer package will encapsulate both, while adding extra functionality.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- The handshake protocol is automatically executed and handled by the peer package. If a Version/Verack is received twice, the peer will be disconnected.
|
||||
|
||||
- IdleTimeouts: If a Message is not received from the peer within a set period of time, the peer will be disconnected.
|
||||
|
||||
- StallTimeouts: For Example, If a GetHeaders, is sent to the Peer and a Headers Response is not received within a certain period of time, then the peer is disconnected.
|
||||
|
||||
- Concurrency Model: The concurrency model used is similar to Actor model, with a few changes. Messages can be sent to a peer asynchronously or synchronously. An example of an synchornous message send is the `RequestHeaders` method, where the channel blocks until an error value is received. The `OnHeaders` message is however asynchronously called. Furthermore, all methods passed through the config, are wrapped inside of an additional `Peers` method, this is to lay the ground work to capturing statistics regarding a specific command. These are also used so that we can pass behaviour to be executed down the channel.
|
||||
|
||||
- Configuration: Each Peer will have a config struct passed to it, with information about the Local Peer and functions that will encapsulate the behaviour of what the peer should do, given a request. This way, the peer is not dependent on any other package.
|
||||
|
||||
## Usage
|
||||
|
||||
conn, err := net.Dial("tcp", "seed2.neo.org:10333")
|
||||
if err != nil {
|
||||
fmt.Println("Error dialing connection", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
config := peer.LocalConfig{
|
||||
Net: protocol.MainNet,
|
||||
UserAgent: "NEO-G",
|
||||
Services: protocol.NodePeerService,
|
||||
Nonce: 1200,
|
||||
ProtocolVer: 0,
|
||||
Relay: false,
|
||||
Port: 10332,
|
||||
StartHeight: LocalHeight,
|
||||
OnHeader: OnHeader,
|
||||
}
|
||||
|
||||
p := peer.NewPeer(conn, false, config)
|
||||
err = p.Run()
|
||||
|
||||
hash, err := util.Uint256DecodeString(chainparams.GenesisHash)
|
||||
// hash2, err := util.Uint256DecodeString("ff8fe95efc5d1cc3a22b17503aecaf289cef68f94b79ddad6f613569ca2342d8")
|
||||
err = p.RequestHeaders(hash)
|
||||
|
||||
func OnHeader(peer *peer.Peer, msg *payload.HeadersMessage) {
|
||||
// This function is passed to peer
|
||||
// and the peer will execute it on receiving a header
|
||||
}
|
||||
|
||||
func LocalHeight() uint32 {
|
||||
// This will be a function from the object that handles the block heights
|
||||
return 10
|
||||
}
|
||||
|
||||
|
||||
### Notes
|
||||
|
||||
|
||||
Should we follow the actor model for Peers? Each Peer will have a ID, which we can take as the PID or if
|
||||
we launch a go-routine for each peer, then we can use that as an implicit PID.
|
||||
|
||||
Peer information should be stored into a database, if no db exists, we should get it from an initial peers file.
|
||||
We can use this to periodically store information about a peer.
|
174
pkg/peer/stall/stall.go
Normal file
174
pkg/peer/stall/stall.go
Normal file
|
@ -0,0 +1,174 @@
|
|||
package stall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
)
|
||||
|
||||
// stall detector will keep track of all pendingMessages
|
||||
// If any message takes too long to reply
|
||||
// the detector will disconnect the peer
|
||||
|
||||
type Detector struct {
|
||||
responseTime time.Duration
|
||||
tickInterval time.Duration
|
||||
|
||||
lock sync.Mutex
|
||||
responses map[command.Type]time.Time
|
||||
|
||||
// The detector is embedded into a peer and the peer watches this quit chan
|
||||
// If this chan is closed, the peer disconnects
|
||||
Quitch chan struct{}
|
||||
|
||||
// atomic vals
|
||||
disconnected int32
|
||||
}
|
||||
|
||||
// rT is the responseTime and signals how long
|
||||
// a peer has to reply back to a sent message
|
||||
// tickerInterval is how often the detector wil check for stalled messages
|
||||
func NewDetector(rTime time.Duration, tickerInterval time.Duration) *Detector {
|
||||
d := &Detector{
|
||||
responseTime: rTime,
|
||||
tickInterval: tickerInterval,
|
||||
lock: sync.Mutex{},
|
||||
responses: map[command.Type]time.Time{},
|
||||
Quitch: make(chan struct{}),
|
||||
}
|
||||
go d.loop()
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Detector) loop() {
|
||||
ticker := time.NewTicker(d.tickInterval)
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
now := time.Now()
|
||||
for _, deadline := range d.responses {
|
||||
if now.After(deadline) {
|
||||
fmt.Println("Deadline passed")
|
||||
ticker.Stop()
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
d.Quit()
|
||||
d.DeleteAll()
|
||||
ticker.Stop()
|
||||
}
|
||||
|
||||
// Quit is a concurrent safe way to call the Quit channel
|
||||
// Without blocking
|
||||
func (d *Detector) Quit() {
|
||||
// return if already disconnected
|
||||
if atomic.LoadInt32(&d.disconnected) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddInt32(&d.disconnected, 1)
|
||||
close(d.Quitch)
|
||||
}
|
||||
|
||||
// Call this function when we send a message to a peer
|
||||
// The command passed through is the command that we sent
|
||||
// and not the command we expect to receive
|
||||
func (d *Detector) AddMessage(cmd command.Type) {
|
||||
cmds := d.addMessage(cmd)
|
||||
d.lock.Lock()
|
||||
for _, cmd := range cmds {
|
||||
d.responses[cmd] = time.Now().Add(d.responseTime)
|
||||
}
|
||||
d.lock.Unlock()
|
||||
}
|
||||
|
||||
// Call this function when we receive a message from
|
||||
// peer. This will remove the pendingresponse message from the map.
|
||||
// The command passed through is the command we received
|
||||
func (d *Detector) RemoveMessage(cmd command.Type) {
|
||||
cmds := d.addMessage(cmd)
|
||||
d.lock.Lock()
|
||||
for _, cmd := range cmds {
|
||||
delete(d.responses, cmd)
|
||||
}
|
||||
d.lock.Unlock()
|
||||
}
|
||||
|
||||
// DeleteAll empties the map of all contents and
|
||||
// is called when the detector is being shut down
|
||||
func (d *Detector) DeleteAll() {
|
||||
d.lock.Lock()
|
||||
d.responses = make(map[command.Type]time.Time)
|
||||
d.lock.Unlock()
|
||||
}
|
||||
|
||||
// GetMessages Will return a map of all of the pendingResponses
|
||||
// and their deadlines
|
||||
func (d *Detector) GetMessages() map[command.Type]time.Time {
|
||||
var resp map[command.Type]time.Time
|
||||
d.lock.Lock()
|
||||
resp = d.responses
|
||||
d.lock.Unlock()
|
||||
return resp
|
||||
}
|
||||
|
||||
// when a message is added, we will add a deadline for
|
||||
// expected response
|
||||
func (d *Detector) addMessage(cmd command.Type) []command.Type {
|
||||
|
||||
cmds := []command.Type{}
|
||||
|
||||
switch cmd {
|
||||
case command.GetHeaders:
|
||||
// We now will expect a Headers Message
|
||||
cmds = append(cmds, command.Headers)
|
||||
case command.GetAddr:
|
||||
// We now will expect a Headers Message
|
||||
cmds = append(cmds, command.Addr)
|
||||
|
||||
case command.GetData:
|
||||
// We will now expect a block/tx message
|
||||
// We can optimise this by including the exact inventory type, however it is not needed
|
||||
cmds = append(cmds, command.Block)
|
||||
cmds = append(cmds, command.TX)
|
||||
case command.GetBlocks:
|
||||
// we will now expect a inv message
|
||||
cmds = append(cmds, command.Inv)
|
||||
case command.Version:
|
||||
// We will now expect a verack
|
||||
cmds = append(cmds, command.Verack)
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
||||
// if receive a message, we will delete it from pending
|
||||
func (d *Detector) removeMessage(cmd command.Type) []command.Type {
|
||||
|
||||
cmds := []command.Type{}
|
||||
|
||||
switch cmd {
|
||||
case command.Block:
|
||||
// We will now expect a block/tx message
|
||||
cmds = append(cmds, command.Block)
|
||||
cmds = append(cmds, command.TX)
|
||||
case command.TX:
|
||||
// We will now expect a block/tx message
|
||||
cmds = append(cmds, command.Block)
|
||||
cmds = append(cmds, command.TX)
|
||||
case command.GetBlocks:
|
||||
// we will now expect a inv message
|
||||
cmds = append(cmds, command.Inv)
|
||||
default:
|
||||
// We will now expect a verack
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
return cmds
|
||||
}
|
75
pkg/peer/stall/stall_test.go
Normal file
75
pkg/peer/stall/stall_test.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package stall
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
)
|
||||
|
||||
func TestAddRemoveMessage(t *testing.T) {
|
||||
|
||||
responseTime := 2 * time.Second
|
||||
tickerInterval := 1 * time.Second
|
||||
|
||||
d := NewDetector(responseTime, tickerInterval)
|
||||
d.AddMessage(command.GetAddr)
|
||||
mp := d.GetMessages()
|
||||
|
||||
assert.Equal(t, 1, len(mp))
|
||||
assert.IsType(t, time.Time{}, mp[command.GetAddr])
|
||||
|
||||
d.RemoveMessage(command.GetAddr)
|
||||
mp = d.GetMessages()
|
||||
|
||||
assert.Equal(t, 0, len(mp))
|
||||
assert.Empty(t, mp[command.GetAddr])
|
||||
}
|
||||
|
||||
type mockPeer struct {
|
||||
online bool
|
||||
detector *Detector
|
||||
}
|
||||
|
||||
func (mp *mockPeer) loop() {
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-mp.detector.Quitch:
|
||||
|
||||
break loop
|
||||
}
|
||||
}
|
||||
// cleanup
|
||||
mp.online = false
|
||||
}
|
||||
func TestDeadlineWorks(t *testing.T) {
|
||||
|
||||
responseTime := 2 * time.Second
|
||||
tickerInterval := 1 * time.Second
|
||||
|
||||
d := NewDetector(responseTime, tickerInterval)
|
||||
mp := mockPeer{online: true, detector: d}
|
||||
go mp.loop()
|
||||
|
||||
d.AddMessage(command.GetAddr)
|
||||
time.Sleep(responseTime + 1*time.Second)
|
||||
|
||||
k := make(map[command.Type]time.Time)
|
||||
assert.Equal(t, k, d.responses)
|
||||
assert.Equal(t, false, mp.online)
|
||||
|
||||
}
|
||||
func TestDeadlineShouldNotBeEmpty(t *testing.T) {
|
||||
responseTime := 10 * time.Second
|
||||
tickerInterval := 1 * time.Second
|
||||
|
||||
d := NewDetector(responseTime, tickerInterval)
|
||||
d.AddMessage(command.GetAddr)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
k := make(map[command.Type]time.Time)
|
||||
assert.NotEqual(t, k, d.responses)
|
||||
}
|
67
pkg/peermanager/peermgr.go
Normal file
67
pkg/peermanager/peermgr.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package peermanager
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/peer"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
// NOTE: This package may be removed in the future
|
||||
// and so full functionality is not yet implemented, see Issue #33 for more details.
|
||||
|
||||
//PeerMgr will act as a convenience Mgr
|
||||
// It will be notified of added Peers
|
||||
// It will take care of sending messages to the right peers. In this way, it acts as a load balancer
|
||||
// If we send a getdata to one peer, it will be smart and send it to another peer who is not as busy
|
||||
// Using subscription model, we can have the syncmanager/other modules notify the peermgr when they have received data
|
||||
type PeerMgr struct {
|
||||
peers []*peer.Peer
|
||||
}
|
||||
|
||||
// New will create a new peer manager
|
||||
// As of now it just returns a peerMgr struct and so
|
||||
// the New method is redundant. A config file will be passed as a parameter,
|
||||
// if it is decided that we will use this.
|
||||
func New() *PeerMgr {
|
||||
return &PeerMgr{}
|
||||
}
|
||||
|
||||
// Disconnect will close the connection on a peer and
|
||||
// remove it from the list
|
||||
// TODO: remove from list once disconnected
|
||||
func (pm *PeerMgr) Disconnect(p *peer.Peer) {
|
||||
p.Disconnect()
|
||||
// Once disconnected, we remove it from the list
|
||||
// and look for more peers to connect to
|
||||
}
|
||||
|
||||
// RequestHeaders will request the headers from the most available peer
|
||||
// As of now, it requests from the first peer in the list, TODO(Kev)
|
||||
func (pm *PeerMgr) RequestHeaders(hash util.Uint256) (*peer.Peer, error) {
|
||||
|
||||
if len(pm.peers) == 0 {
|
||||
return nil, errors.New("Peer manager currently has no peers")
|
||||
}
|
||||
|
||||
return pm.peers[0], pm.peers[0].RequestHeaders(hash)
|
||||
}
|
||||
|
||||
// RequestBlocks will request blocks from the most available peer
|
||||
// As of now, it requests from the first peer in the list, TODO(Kev)
|
||||
func (pm *PeerMgr) RequestBlocks(hash []util.Uint256) (*peer.Peer, error) {
|
||||
|
||||
if len(pm.peers) == 0 {
|
||||
return nil, errors.New("Peer manager currently has no peers")
|
||||
}
|
||||
|
||||
return pm.peers[0], pm.peers[0].RequestBlocks(hash)
|
||||
}
|
||||
|
||||
// AddPeer will add a new peer for the PeerManager to use
|
||||
func (pm *PeerMgr) AddPeer(p *peer.Peer) error {
|
||||
pm.peers = append(pm.peers, p)
|
||||
fmt.Println("Adding peers into the peermanager")
|
||||
return nil
|
||||
}
|
14
pkg/pubsub/event.go
Normal file
14
pkg/pubsub/event.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package pubsub
|
||||
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
NewBlock EventType = iota // When blockchain connects a new block, it will emit an NewBlock Event
|
||||
BadBlock // When blockchain declines a block, it will emit a new block event
|
||||
BadHeader // When blockchain rejects a Header, it will emit this event
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
Type EventType // E.g. event.NewBlock
|
||||
data []byte // Raw information
|
||||
}
|
20
pkg/pubsub/pub.go
Normal file
20
pkg/pubsub/pub.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package pubsub
|
||||
|
||||
type Publisher struct {
|
||||
subs []Subscriber
|
||||
}
|
||||
|
||||
// Send iterates over each subscriber and checks
|
||||
// if they are interested in the Event
|
||||
// By looking at their topics, if they are then
|
||||
// the event is emitted to them
|
||||
func (p *Publisher) Send(e Event) error {
|
||||
for _, sub := range p.subs {
|
||||
for _, topic := range sub.Topics() {
|
||||
if e.Type == topic {
|
||||
sub.Emit(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
6
pkg/pubsub/sub.go
Normal file
6
pkg/pubsub/sub.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package pubsub
|
||||
|
||||
type Subscriber interface {
|
||||
Topics() []EventType
|
||||
Emit(Event)
|
||||
}
|
11
pkg/syncmanager/config.go
Normal file
11
pkg/syncmanager/config.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package syncmanager
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/blockchain"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Chain *blockchain.Chain
|
||||
BestHash util.Uint256
|
||||
}
|
152
pkg/syncmanager/syncman.go
Normal file
152
pkg/syncmanager/syncman.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
// The syncmanager will use a modified verison of the initial block download in bitcoin
|
||||
// Seen here: https://en.bitcoinwiki.org/wiki/Bitcoin_Core_0.11_(ch_5):_Initial_Block_Download
|
||||
// MovingWindow is a desired featured from the original codebase
|
||||
|
||||
package syncmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/peermanager"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/blockchain"
|
||||
"github.com/CityOfZion/neo-go/pkg/peer"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
var (
|
||||
// This is the maximum amount of inflight objects that we would like to have
|
||||
// Number taken from original codebase
|
||||
maxBlockRequest = 1024
|
||||
|
||||
// This is the maximum amount of blocks that we will ask for from a single peer
|
||||
// Number taken from original codebase
|
||||
maxBlockRequestPerPeer = 16
|
||||
)
|
||||
|
||||
type Syncmanager struct {
|
||||
pmgr *peermanager.PeerMgr
|
||||
Mode int // 1 = headersFirst, 2 = Blocks, 3 = Maintain
|
||||
chain *blockchain.Chain
|
||||
headers []util.Uint256
|
||||
inflightBlockReqs map[util.Uint256]*peer.Peer // when we send a req for block, we will put hash in here, along with peer who we requested it from
|
||||
}
|
||||
|
||||
// New will setup the syncmanager with the required
|
||||
// parameters
|
||||
func New(cfg Config) *Syncmanager {
|
||||
return &Syncmanager{
|
||||
peermanager.New(),
|
||||
1,
|
||||
cfg.Chain,
|
||||
[]util.Uint256{},
|
||||
make(map[util.Uint256]*peer.Peer, 2000),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Syncmanager) AddPeer(peer *peer.Peer) error {
|
||||
return s.pmgr.AddPeer(peer)
|
||||
}
|
||||
|
||||
func (s *Syncmanager) OnHeaders(p *peer.Peer, msg *payload.HeadersMessage) {
|
||||
fmt.Println("Sync manager On Headers called")
|
||||
// On receipt of Headers
|
||||
// check what mode we are in
|
||||
// HeadersMode, we check if there is 2k. If so call again. If not then change mode into BlocksOnly
|
||||
if s.Mode == 1 {
|
||||
err := s.HeadersFirstMode(p, msg)
|
||||
if err != nil {
|
||||
fmt.Println("Error re blocks", err)
|
||||
return // We should custom name error so, that we can do something on WrongHash Error, Peer disconnect error
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Syncmanager) HeadersFirstMode(p *peer.Peer, msg *payload.HeadersMessage) error {
|
||||
|
||||
fmt.Println("Headers first mode")
|
||||
|
||||
// Validate Headers
|
||||
err := s.chain.ValidateHeaders(msg)
|
||||
|
||||
if err != nil {
|
||||
// Re-request headers from a different peer
|
||||
s.pmgr.Disconnect(p)
|
||||
fmt.Println("Error Validating headers", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Add Headers into db
|
||||
err = s.chain.AddHeaders(msg)
|
||||
if err != nil {
|
||||
// Try addding them into the db again?
|
||||
// Since this is simply a db insert, any problems here means trouble
|
||||
//TODO(KEV) : Should we Switch off system or warn the user that the system is corrupted?
|
||||
fmt.Println("Error Adding headers", err)
|
||||
|
||||
//TODO: Batching is not yet implemented,
|
||||
// So here we would need to remove headers which have been added
|
||||
// from the slice
|
||||
return err
|
||||
}
|
||||
|
||||
// Add header hashes into slice
|
||||
// Requets first batch of blocks here
|
||||
var hashes []util.Uint256
|
||||
for _, header := range msg.Headers {
|
||||
hashes = append(hashes, header.Hash)
|
||||
}
|
||||
s.headers = append(s.headers, hashes...)
|
||||
|
||||
if len(msg.Headers) == 2*1e3 { // should be less than 2000, leave it as this for tests
|
||||
fmt.Println("Switching to BlocksOnly Mode")
|
||||
s.Mode = 2 // switch to BlocksOnly. XXX: because HeadersFirst is not in parallel, no race condition here.
|
||||
return s.RequestMoreBlocks()
|
||||
}
|
||||
lastHeader := msg.Headers[len(msg.Headers)-1]
|
||||
_, err = s.pmgr.RequestHeaders(lastHeader.Hash)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Syncmanager) RequestMoreBlocks() error {
|
||||
|
||||
var blockReq []util.Uint256
|
||||
|
||||
var reqAmount int
|
||||
|
||||
if len(s.headers) >= maxBlockRequestPerPeer {
|
||||
reqAmount = maxBlockRequestPerPeer
|
||||
blockReq = s.headers[:reqAmount]
|
||||
} else {
|
||||
reqAmount = len(s.headers)
|
||||
blockReq = s.headers[:reqAmount]
|
||||
}
|
||||
peer, err := s.pmgr.RequestBlocks(blockReq)
|
||||
if err != nil { // This could happen if the peermanager has no valid peers to connect to. We should wait a bit and re-request
|
||||
return err // alternatively we could make RequestBlocks blocking, then make sure it is not triggered when a block is received
|
||||
}
|
||||
|
||||
//XXX: Possible race condition, between us requesting the block and adding it to
|
||||
// the inflight block map? Give that node a medal.
|
||||
|
||||
for _, hash := range s.headers {
|
||||
s.inflightBlockReqs[hash] = peer
|
||||
}
|
||||
s.headers = s.headers[reqAmount:]
|
||||
// NONONO: Here we do not pass all of the hashes to peermanager because
|
||||
// it is not the peermanagers responsibility to mange inflight blocks
|
||||
return err
|
||||
}
|
||||
|
||||
// OnBlock receives a block from a peer, then passes it to the blockchain to process.
|
||||
// For now we will only use this simple setup, to allow us to test the other parts of the system.
|
||||
// See Issue #24
|
||||
func (s *Syncmanager) OnBlock(p *peer.Peer, msg *payload.BlockMessage) {
|
||||
err := s.chain.AddBlock(msg)
|
||||
if err != nil {
|
||||
// Put headers back in front of queue to fetch block for.
|
||||
fmt.Println("Block had an error", err)
|
||||
}
|
||||
}
|
62
pkg/wire/Readme.md
Normal file
62
pkg/wire/Readme.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Package - Wire
|
||||
|
||||
|
||||
The neo wire package will implement the network protocol displayed here: http://docs.neo.org/en-us/network/network-protocol.html
|
||||
|
||||
This package will act as a standalone package.
|
||||
|
||||
# Responsibility
|
||||
|
||||
This package will solely be responsible for Encoding and decoding a Message.
|
||||
It will return a Messager interface, which means that the caller of the package will need to type assert it to the appropriate type.
|
||||
|
||||
# Usage
|
||||
|
||||
## Write Message
|
||||
|
||||
expectedIP := "127.0.0.1"
|
||||
expectedPort := 8333
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
|
||||
message, err := NewVersionMessage(tcpAddrMe, 0, true, defaultVersion)
|
||||
|
||||
conn := new(bytes.Buffer)
|
||||
if err := WriteMessage(con, Production, message); err != nil {
|
||||
// handle Error
|
||||
}
|
||||
|
||||
## Read Message
|
||||
|
||||
readmsg, err := ReadMessage(conn, Production)
|
||||
|
||||
if err != nil {
|
||||
// Log error
|
||||
}
|
||||
version := readmsg.(*VersionMessage)
|
||||
|
||||
## RoadMap
|
||||
|
||||
These below commands are left to implement.
|
||||
|
||||
[ x ] CMDVersion (Tests added)
|
||||
[ x ] CMDVerack (Tests Added)
|
||||
[ x ] CMDGetAddr(Tests Added)
|
||||
[ x ] CMDAddr (Tests Added)
|
||||
[ x ] CMDGetHeaders (Tests Added)
|
||||
[ x ] CMDHeaders (Tests Added)
|
||||
[ x ] CMDGetBlocks (Tests Added)
|
||||
[ x ] CMDInv (Tests Added)
|
||||
[ x ] CMDGetData (Tests Added)
|
||||
[ x ] CMDBlock (Tests Added)
|
||||
[ x ] CMDTX // Each tx implments the messager interface
|
||||
[ ] CMDConsensus
|
||||
|
||||
## Notes
|
||||
|
||||
Please not that this package will do sanity checks on the fields, however it will not verify if any of the items are valid for the current state of the system. Please see `Responbilities`.
|
||||
|
||||
The difference between Encode/Decode and EncodePayload/DecodePayload, is the parameter type.
|
||||
In most cases, Encode/Decode is just a convenience method.
|
||||
# Contributors
|
||||
|
||||
When modifying this package, please ensure that it does not depend on any other package and that it conforms to the Single Responsibility Principle. If you see somewhere in the current implementation that does not do this, then please tell me.
|
||||
|
44
pkg/wire/base.go
Normal file
44
pkg/wire/base.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package wire
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
// Base is everything in the message except the payload
|
||||
|
||||
type Base struct {
|
||||
Magic uint32
|
||||
CMD command.Type
|
||||
PayloadLength uint32
|
||||
Checksum uint32
|
||||
}
|
||||
|
||||
// Note, That there is no EncodeBase
|
||||
// As the header is implicitly inferred from
|
||||
// the message on Encode To send
|
||||
func (h *Base) DecodeBase(r io.Reader) (io.Reader, error) {
|
||||
br := &util.BinReader{R: r}
|
||||
|
||||
br.Read(&h.Magic)
|
||||
|
||||
var cmd [12]byte
|
||||
br.Read(&cmd)
|
||||
h.CMD = command.Type(cmdByteArrayToString(cmd))
|
||||
|
||||
br.Read(&h.PayloadLength)
|
||||
br.Read(&h.Checksum)
|
||||
return br.R, br.Err
|
||||
}
|
||||
|
||||
func cmdByteArrayToString(cmd [command.Size]byte) string {
|
||||
buf := []byte{}
|
||||
for i := 0; i < command.Size; i++ {
|
||||
if cmd[i] != 0 {
|
||||
buf = append(buf, cmd[i])
|
||||
}
|
||||
}
|
||||
return string(buf)
|
||||
}
|
30
pkg/wire/command/command.go
Normal file
30
pkg/wire/command/command.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package command
|
||||
|
||||
// Size of command field in bytes
|
||||
const (
|
||||
Size = 12
|
||||
)
|
||||
|
||||
// CommandType represents the type of a message command.
|
||||
type Type string
|
||||
|
||||
// Valid protocol commands used to send between nodes.
|
||||
// use this to get
|
||||
const (
|
||||
Version Type = "version"
|
||||
Mempool Type = "mempool"
|
||||
Ping Type = "ping"
|
||||
Pong Type = "pong"
|
||||
Verack Type = "verack"
|
||||
GetAddr Type = "getaddr"
|
||||
Addr Type = "addr"
|
||||
GetHeaders Type = "getheaders"
|
||||
Headers Type = "headers"
|
||||
GetBlocks Type = "getblocks"
|
||||
Inv Type = "inv"
|
||||
GetData Type = "getdata"
|
||||
Block Type = "block"
|
||||
TX Type = "tx"
|
||||
Consensus Type = "consensus"
|
||||
Unknown Type = "unknown"
|
||||
)
|
148
pkg/wire/message.go
Normal file
148
pkg/wire/message.go
Normal file
|
@ -0,0 +1,148 @@
|
|||
package wire
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
|
||||
checksum "github.com/CityOfZion/neo-go/pkg/wire/util/Checksum"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
type Messager interface {
|
||||
EncodePayload(w io.Writer) error
|
||||
DecodePayload(r io.Reader) error
|
||||
Command() command.Type
|
||||
}
|
||||
|
||||
const (
|
||||
// Magic + cmd + length + checksum
|
||||
minMsgSize = 4 + 12 + 4 + 4
|
||||
)
|
||||
|
||||
var (
|
||||
errChecksumMismatch = errors.New("checksum mismatch")
|
||||
)
|
||||
|
||||
func WriteMessage(w io.Writer, magic protocol.Magic, message Messager) error {
|
||||
bw := &util.BinWriter{W: w}
|
||||
bw.Write(magic)
|
||||
bw.Write(cmdToByteArray(message.Command()))
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := message.EncodePayload(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payloadLen := util.BufferLength(buf)
|
||||
checksum := checksum.FromBytes(buf.Bytes())
|
||||
|
||||
bw.Write(payloadLen)
|
||||
bw.Write(checksum)
|
||||
|
||||
bw.WriteBigEnd(buf.Bytes())
|
||||
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
func ReadMessage(r io.Reader, magic protocol.Magic) (Messager, error) {
|
||||
|
||||
byt := make([]byte, minMsgSize)
|
||||
|
||||
if _, err := io.ReadFull(r, byt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(byt)
|
||||
|
||||
var header Base
|
||||
_, err := header.DecodeBase(reader)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.New("Error decoding into the header base")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
_, err = io.CopyN(buf, r, int64(header.PayloadLength))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Compare the checksum of the payload.
|
||||
if !checksum.Compare(header.Checksum, buf.Bytes()) {
|
||||
return nil, errChecksumMismatch
|
||||
}
|
||||
switch header.CMD {
|
||||
case command.Version:
|
||||
v := &payload.VersionMessage{}
|
||||
err := v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.Verack:
|
||||
v, err := payload.NewVerackMessage()
|
||||
err = v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.Inv:
|
||||
v, err := payload.NewInvMessage(0)
|
||||
err = v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.GetAddr:
|
||||
v, err := payload.NewGetAddrMessage()
|
||||
err = v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.Addr:
|
||||
v, err := payload.NewAddrMessage()
|
||||
err = v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.Block:
|
||||
v, err := payload.NewBlockMessage()
|
||||
err = v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.GetBlocks:
|
||||
v, err := payload.NewGetBlocksMessage([]util.Uint256{}, util.Uint256{})
|
||||
err = v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.GetData:
|
||||
v, err := payload.NewGetDataMessage(payload.InvTypeTx)
|
||||
err = v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.GetHeaders:
|
||||
v, err := payload.NewGetHeadersMessage([]util.Uint256{}, util.Uint256{})
|
||||
err = v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.Headers:
|
||||
v, err := payload.NewHeadersMessage()
|
||||
err = v.DecodePayload(buf)
|
||||
return v, err
|
||||
case command.TX:
|
||||
reader := bufio.NewReader(buf)
|
||||
tx, err := transaction.FromBytes(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload.NewTXMessage(tx)
|
||||
}
|
||||
return nil, errors.New("Unknown Message found")
|
||||
|
||||
}
|
||||
|
||||
func cmdToByteArray(cmd command.Type) [command.Size]byte {
|
||||
cmdLen := len(cmd)
|
||||
if cmdLen > command.Size {
|
||||
panic("exceeded command max length of size 12")
|
||||
}
|
||||
|
||||
// The command can have max 12 bytes, rest is filled with 0.
|
||||
b := [command.Size]byte{}
|
||||
for i := 0; i < cmdLen; i++ {
|
||||
b[i] = cmd[i]
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
55
pkg/wire/message_test.go
Normal file
55
pkg/wire/message_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package wire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// This is quite hard to test because the message uses time.Now()
|
||||
// TODO: Test each field expect time.Now(), just make sure it is a uint32
|
||||
func TestWriteMessageLen(t *testing.T) {
|
||||
|
||||
expectedIP := "127.0.0.1"
|
||||
expectedPort := 8333
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
|
||||
|
||||
message, err := payload.NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, 100, protocol.NodePeerService)
|
||||
if err != nil {
|
||||
assert.Fail(t, err.Error())
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := WriteMessage(buf, protocol.MainNet, message); err != nil {
|
||||
assert.Fail(t, err.Error())
|
||||
}
|
||||
assert.Equal(t, 60, len(buf.Bytes()))
|
||||
}
|
||||
func TestReadMessage(t *testing.T) {
|
||||
|
||||
expectedIP := "127.0.0.1"
|
||||
expectedPort := 8333
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
|
||||
|
||||
message, err := payload.NewVersionMessage(tcpAddrMe, 23, true, protocol.DefaultVersion, protocol.UserAgent, 100, protocol.NodePeerService)
|
||||
if err != nil {
|
||||
assert.Fail(t, err.Error())
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := WriteMessage(buf, protocol.MainNet, message); err != nil {
|
||||
assert.Fail(t, err.Error())
|
||||
}
|
||||
|
||||
readmsg, err := ReadMessage(buf, protocol.MainNet)
|
||||
|
||||
if err != nil {
|
||||
assert.Fail(t, err.Error())
|
||||
}
|
||||
version := readmsg.(*payload.VersionMessage)
|
||||
assert.Equal(t, 23, int(version.StartHeight))
|
||||
// If MessageReading was unsuccessfull it will return a nil object
|
||||
}
|
59
pkg/wire/payload/block.go
Normal file
59
pkg/wire/payload/block.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
type Block struct {
|
||||
BlockBase
|
||||
Txs []transaction.Transactioner
|
||||
}
|
||||
|
||||
func (b *Block) Decode(r io.Reader) error {
|
||||
br := &util.BinReader{R: r}
|
||||
b.DecodePayload(br)
|
||||
return br.Err
|
||||
}
|
||||
func (b *Block) Encode(w io.Writer) error {
|
||||
bw := &util.BinWriter{W: w}
|
||||
b.EncodePayload(bw)
|
||||
return bw.Err
|
||||
}
|
||||
func (b *Block) EncodePayload(bw *util.BinWriter) {
|
||||
b.BlockBase.EncodePayload(bw)
|
||||
bw.VarUint(uint64(len(b.Txs)))
|
||||
for _, tx := range b.Txs {
|
||||
tx.Encode(bw.W)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Block) DecodePayload(br *util.BinReader) error {
|
||||
|
||||
b.BlockBase.DecodePayload(br)
|
||||
lenTXs := br.VarUint()
|
||||
|
||||
b.Txs = make([]transaction.Transactioner, lenTXs)
|
||||
|
||||
reader := bufio.NewReader(br.R)
|
||||
for i := 0; i < int(lenTXs); i++ {
|
||||
|
||||
tx, err := transaction.FromBytes(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Txs[i] = tx
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Block) Bytes() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := b.Encode(buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
80
pkg/wire/payload/block_test.go
Normal file
80
pkg/wire/payload/block_test.go
Normal file
File diff suppressed because one or more lines are too long
124
pkg/wire/payload/blockbase.go
Normal file
124
pkg/wire/payload/blockbase.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPadding = errors.New("There is a padding mismatch")
|
||||
)
|
||||
|
||||
type BlockBase struct {
|
||||
// Version of the block.
|
||||
Version uint32 `json:"version"`
|
||||
|
||||
// hash of the previous block.
|
||||
PrevHash util.Uint256 `json:"previousblockhash"`
|
||||
|
||||
// Root hash of a transaction list.
|
||||
MerkleRoot util.Uint256 `json:"merkleroot"`
|
||||
|
||||
// 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 `json:"time"`
|
||||
|
||||
// index/height of the block
|
||||
Index uint32 `json:"height"`
|
||||
|
||||
// Random number also called nonce
|
||||
ConsensusData uint64 `json:"nonce"`
|
||||
|
||||
// Contract addresss of the next miner
|
||||
NextConsensus util.Uint160 `json:"next_consensus"`
|
||||
|
||||
// Padding that is fixed to 1
|
||||
_ uint8
|
||||
|
||||
// Script used to validate the block
|
||||
Witness transaction.Witness `json:"script"`
|
||||
|
||||
// hash of this block, created when binary encoded.
|
||||
Hash util.Uint256
|
||||
}
|
||||
|
||||
func (b *BlockBase) EncodePayload(bw *util.BinWriter) error {
|
||||
|
||||
b.encodeHashableFields(bw)
|
||||
|
||||
bw.Write(uint8(1))
|
||||
b.Witness.Encode(bw)
|
||||
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
func (b *BlockBase) Decode(r io.Reader) error {
|
||||
br := &util.BinReader{R: r}
|
||||
b.DecodePayload(br)
|
||||
return br.Err
|
||||
}
|
||||
func (b *BlockBase) Encode(w io.Writer) error {
|
||||
bw := &util.BinWriter{W: w}
|
||||
b.EncodePayload(bw)
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
func (b *BlockBase) encodeHashableFields(bw *util.BinWriter) {
|
||||
bw.Write(b.Version)
|
||||
bw.Write(b.PrevHash)
|
||||
bw.Write(b.MerkleRoot)
|
||||
bw.Write(b.Timestamp)
|
||||
bw.Write(b.Index)
|
||||
bw.Write(b.ConsensusData)
|
||||
bw.Write(b.NextConsensus)
|
||||
}
|
||||
|
||||
func (b *BlockBase) DecodePayload(br *util.BinReader) error {
|
||||
|
||||
b.decodeHashableFields(br)
|
||||
|
||||
var padding uint8
|
||||
br.Read(&padding)
|
||||
if padding != 1 {
|
||||
return ErrPadding
|
||||
}
|
||||
|
||||
b.Witness = transaction.Witness{}
|
||||
b.Witness.Decode(br)
|
||||
|
||||
err := b.createHash()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return br.Err
|
||||
}
|
||||
|
||||
func (b *BlockBase) decodeHashableFields(br *util.BinReader) {
|
||||
br.Read(&b.Version)
|
||||
br.Read(&b.PrevHash)
|
||||
br.Read(&b.MerkleRoot)
|
||||
br.Read(&b.Timestamp)
|
||||
br.Read(&b.Index)
|
||||
br.Read(&b.ConsensusData)
|
||||
br.Read(&b.NextConsensus)
|
||||
}
|
||||
|
||||
func (b *BlockBase) createHash() error {
|
||||
|
||||
hash, err := util.CalculateHash(b.encodeHashableFields)
|
||||
b.Hash = hash
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *BlockBase) Bytes() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := b.Encode(buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
8
pkg/wire/payload/blockbase_test.go
Normal file
8
pkg/wire/payload/blockbase_test.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package payload
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test(t *testing.T) {
|
||||
//tests for this have been included in the mheaders_test file
|
||||
|
||||
}
|
61
pkg/wire/payload/maddr.go
Normal file
61
pkg/wire/payload/maddr.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
type AddrMessage struct {
|
||||
AddrList []*Net_addr
|
||||
}
|
||||
|
||||
func NewAddrMessage() (*AddrMessage, error) {
|
||||
addrMess := &AddrMessage{
|
||||
nil,
|
||||
}
|
||||
return addrMess, nil
|
||||
}
|
||||
|
||||
func (a *AddrMessage) AddNetAddr(n *Net_addr) error {
|
||||
a.AddrList = append(a.AddrList, n)
|
||||
// TODO:check if max reached, if so return err. What is max?
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (a *AddrMessage) DecodePayload(r io.Reader) error {
|
||||
|
||||
br := &util.BinReader{R: r}
|
||||
listLen := br.VarUint()
|
||||
|
||||
a.AddrList = make([]*Net_addr, listLen)
|
||||
for i := 0; i < int(listLen); i++ {
|
||||
a.AddrList[i] = &Net_addr{}
|
||||
a.AddrList[i].DecodePayload(br)
|
||||
if br.Err != nil {
|
||||
return br.Err
|
||||
}
|
||||
}
|
||||
return br.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *AddrMessage) EncodePayload(w io.Writer) error {
|
||||
bw := &util.BinWriter{W: w}
|
||||
|
||||
listLen := uint64(len(v.AddrList))
|
||||
bw.VarUint(listLen)
|
||||
|
||||
for _, addr := range v.AddrList {
|
||||
addr.EncodePayload(bw)
|
||||
}
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *AddrMessage) Command() command.Type {
|
||||
return command.Addr
|
||||
}
|
40
pkg/wire/payload/maddr_test.go
Normal file
40
pkg/wire/payload/maddr_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util/Checksum"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAddrMessageEncodeDecode(t *testing.T) {
|
||||
|
||||
ip := []byte(net.ParseIP("127.0.0.1").To16())
|
||||
|
||||
var ipByte [16]byte
|
||||
copy(ipByte[:], ip)
|
||||
|
||||
netaddr, err := NewNetAddr(uint32(time.Now().Unix()), ipByte, 8080, protocol.NodePeerService)
|
||||
addrmsg, err := NewAddrMessage()
|
||||
addrmsg.AddNetAddr(netaddr)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err = addrmsg.EncodePayload(buf)
|
||||
expected := checksum.FromBuf(buf)
|
||||
|
||||
addrmsgDec, err := NewAddrMessage()
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
err = addrmsgDec.DecodePayload(r)
|
||||
|
||||
buf = new(bytes.Buffer)
|
||||
err = addrmsgDec.EncodePayload(buf)
|
||||
have := checksum.FromBuf(buf)
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, expected, have)
|
||||
}
|
36
pkg/wire/payload/mblock.go
Normal file
36
pkg/wire/payload/mblock.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
)
|
||||
|
||||
type BlockMessage struct {
|
||||
Block
|
||||
}
|
||||
|
||||
func NewBlockMessage() (*BlockMessage, error) {
|
||||
return &BlockMessage{}, nil
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (b *BlockMessage) DecodePayload(r io.Reader) error {
|
||||
br := &util.BinReader{R: r}
|
||||
b.Block.DecodePayload(br)
|
||||
return br.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (b *BlockMessage) EncodePayload(w io.Writer) error {
|
||||
bw := &util.BinWriter{W: w}
|
||||
b.Block.EncodePayload(bw)
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *BlockMessage) Command() command.Type {
|
||||
return command.Block
|
||||
}
|
29
pkg/wire/payload/mgetaddr.go
Normal file
29
pkg/wire/payload/mgetaddr.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
)
|
||||
|
||||
// No payload
|
||||
type GetAddrMessage struct{}
|
||||
|
||||
func NewGetAddrMessage() (*GetAddrMessage, error) {
|
||||
return &GetAddrMessage{}, nil
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (v *GetAddrMessage) DecodePayload(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *GetAddrMessage) EncodePayload(w io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *GetAddrMessage) Command() command.Type {
|
||||
return command.GetAddr
|
||||
}
|
24
pkg/wire/payload/mgetaddr_test.go
Normal file
24
pkg/wire/payload/mgetaddr_test.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util/Checksum"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewGetAddr(t *testing.T) {
|
||||
|
||||
getAddrMessage, err := NewGetAddrMessage()
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
assert.Equal(t, command.GetAddr, getAddrMessage.Command())
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
assert.Equal(t, int(3806393949), int(checksum.FromBuf(buf)))
|
||||
assert.Equal(t, int(0), len(buf.Bytes()))
|
||||
}
|
22
pkg/wire/payload/mgetblocks.go
Normal file
22
pkg/wire/payload/mgetblocks.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
type GetBlocksMessage struct {
|
||||
*GetHeadersMessage
|
||||
}
|
||||
|
||||
func NewGetBlocksMessage(start []util.Uint256, stop util.Uint256) (*GetBlocksMessage, error) {
|
||||
GetHeaders, err := newAbstractGetHeaders(start, stop, command.GetBlocks)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GetBlocksMessage{
|
||||
GetHeaders,
|
||||
}, nil
|
||||
|
||||
}
|
27
pkg/wire/payload/mgetblocks_test.go
Normal file
27
pkg/wire/payload/mgetblocks_test.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetBlocksCommandType(t *testing.T) {
|
||||
var (
|
||||
start = []util.Uint256{
|
||||
sha256.Sum256([]byte("a")),
|
||||
sha256.Sum256([]byte("b")),
|
||||
sha256.Sum256([]byte("c")),
|
||||
sha256.Sum256([]byte("d")),
|
||||
}
|
||||
stop = sha256.Sum256([]byte("e"))
|
||||
)
|
||||
|
||||
getBlocks, err := NewGetBlocksMessage(start, stop)
|
||||
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, command.GetBlocks, getBlocks.Command())
|
||||
}
|
16
pkg/wire/payload/mgetdata.go
Normal file
16
pkg/wire/payload/mgetdata.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
)
|
||||
|
||||
type GetDataMessage struct {
|
||||
*InvMessage
|
||||
}
|
||||
|
||||
func NewGetDataMessage(typ InvType) (*GetDataMessage, error) {
|
||||
getData, err := newAbstractInv(typ, command.GetData)
|
||||
return &GetDataMessage{
|
||||
getData,
|
||||
}, err
|
||||
}
|
19
pkg/wire/payload/mgetdata_test.go
Normal file
19
pkg/wire/payload/mgetdata_test.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetDataCommandType(t *testing.T) {
|
||||
getData, err := NewGetDataMessage(InvTypeBlock)
|
||||
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, command.GetData, getData.Command())
|
||||
}
|
||||
|
||||
func TestOtherFunctions(t *testing.T) {
|
||||
// Other capabilities are tested in the inherited struct
|
||||
}
|
59
pkg/wire/payload/mgetheaders.go
Normal file
59
pkg/wire/payload/mgetheaders.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
type GetHeadersMessage struct {
|
||||
cmd command.Type
|
||||
hashStart []util.Uint256
|
||||
hashStop util.Uint256
|
||||
}
|
||||
|
||||
// Start contains the list of all headers you want to fetch
|
||||
// End contains the list of the highest header hash you would like to fetch
|
||||
func NewGetHeadersMessage(start []util.Uint256, stop util.Uint256) (*GetHeadersMessage, error) {
|
||||
getHeaders := &GetHeadersMessage{command.GetHeaders, start, stop}
|
||||
|
||||
return getHeaders, nil
|
||||
|
||||
}
|
||||
|
||||
func newAbstractGetHeaders(start []util.Uint256, stop util.Uint256, cmd command.Type) (*GetHeadersMessage, error) {
|
||||
getHeaders, err := NewGetHeadersMessage(start, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getHeaders.cmd = cmd
|
||||
return getHeaders, nil
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (v *GetHeadersMessage) DecodePayload(r io.Reader) error {
|
||||
|
||||
br := util.BinReader{R: r}
|
||||
lenStart := br.VarUint()
|
||||
v.hashStart = make([]util.Uint256, lenStart)
|
||||
br.Read(&v.hashStart)
|
||||
br.Read(&v.hashStop)
|
||||
|
||||
return br.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *GetHeadersMessage) EncodePayload(w io.Writer) error {
|
||||
bw := &util.BinWriter{W: w}
|
||||
bw.VarUint(uint64(len(v.hashStart)))
|
||||
bw.Write(v.hashStart)
|
||||
bw.Write(v.hashStop)
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *GetHeadersMessage) Command() command.Type {
|
||||
return v.cmd
|
||||
}
|
47
pkg/wire/payload/mgetheaders_test.go
Normal file
47
pkg/wire/payload/mgetheaders_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util/Checksum"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Test taken from neo-go v1
|
||||
func TestGetHeadersEncodeDecode(t *testing.T) {
|
||||
|
||||
var (
|
||||
start = []util.Uint256{
|
||||
sha256.Sum256([]byte("a")),
|
||||
sha256.Sum256([]byte("b")),
|
||||
sha256.Sum256([]byte("c")),
|
||||
sha256.Sum256([]byte("d")),
|
||||
}
|
||||
stop = sha256.Sum256([]byte("e"))
|
||||
)
|
||||
msgGetHeaders, err := NewGetHeadersMessage(start, stop)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
err = msgGetHeaders.EncodePayload(buf)
|
||||
assert.Equal(t, nil, err)
|
||||
expected := checksum.FromBuf(buf)
|
||||
|
||||
msgGetHeadersDec, err := NewGetHeadersMessage([]util.Uint256{}, util.Uint256{})
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
err = msgGetHeadersDec.DecodePayload(r)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
buf = new(bytes.Buffer)
|
||||
err = msgGetHeadersDec.EncodePayload(buf)
|
||||
have := checksum.FromBuf(buf)
|
||||
|
||||
assert.Equal(t, expected, have)
|
||||
}
|
78
pkg/wire/payload/mheaders.go
Normal file
78
pkg/wire/payload/mheaders.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
type HeadersMessage struct {
|
||||
Headers []*BlockBase
|
||||
|
||||
// Padding that is fixed to 0
|
||||
_ uint8
|
||||
}
|
||||
|
||||
// Users can at most request 2k header
|
||||
const (
|
||||
maxHeadersAllowed = 2000
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMaxHeaders = errors.New("Maximum amount of headers allowed is 2000")
|
||||
)
|
||||
|
||||
func NewHeadersMessage() (*HeadersMessage, error) {
|
||||
|
||||
headers := &HeadersMessage{nil, 0}
|
||||
return headers, nil
|
||||
}
|
||||
|
||||
func (h *HeadersMessage) AddHeader(head *BlockBase) error {
|
||||
if len(h.Headers)+1 > maxHeadersAllowed {
|
||||
return ErrMaxHeaders
|
||||
}
|
||||
h.Headers = append(h.Headers, head)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (v *HeadersMessage) DecodePayload(r io.Reader) error {
|
||||
|
||||
br := &util.BinReader{R: r}
|
||||
|
||||
lenHeaders := br.VarUint()
|
||||
v.Headers = make([]*BlockBase, lenHeaders)
|
||||
|
||||
for i := 0; i < int(lenHeaders); i++ {
|
||||
header := &BlockBase{}
|
||||
header.DecodePayload(br)
|
||||
var padding uint8
|
||||
br.Read(&padding)
|
||||
if padding != 0 {
|
||||
return ErrPadding
|
||||
}
|
||||
v.Headers[i] = header
|
||||
}
|
||||
|
||||
return br.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *HeadersMessage) EncodePayload(w io.Writer) error {
|
||||
bw := &util.BinWriter{W: w}
|
||||
bw.VarUint(uint64(len(v.Headers)))
|
||||
for _, header := range v.Headers {
|
||||
header.EncodePayload(bw)
|
||||
bw.Write(uint8(0))
|
||||
}
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *HeadersMessage) Command() command.Type {
|
||||
return command.Headers
|
||||
}
|
82
pkg/wire/payload/mheaders_test.go
Normal file
82
pkg/wire/payload/mheaders_test.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util/address"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewHeaderMessage(t *testing.T) {
|
||||
msgHeaders, err := NewHeadersMessage()
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 0, len(msgHeaders.Headers))
|
||||
|
||||
}
|
||||
|
||||
func TestAddAndEncodeHeaders(t *testing.T) {
|
||||
|
||||
// uses block10 from mainnet
|
||||
|
||||
msgHeaders, _ := NewHeadersMessage()
|
||||
|
||||
prevH, _ := util.Uint256DecodeString("005fb74a6de169ce5daf59a114405e5b27238b2489690e3b2a60c14ddfc3b326")
|
||||
merkleRoot, _ := util.Uint256DecodeString("ca6d58bcb837472c2f77877e68495b83fd5b714dfe0c8230a525f4511a3239f4")
|
||||
invocationScript, _ := hex.DecodeString("4036fdd23248880c1c311bcd97df04fe6d740dc1bf340c26915f0466e31e81c039012eca7a760270389e04b58b99820fe49cf8c24c9afc65d696b4d3f406a1e6b5405172a9b461e68dd399c8716de11d31f7dd2ec3be327c636b024562db6ac5df1cffdbee74c994736fd49803234d2baffbc0054f28ba5ec76494a467b4106955bb4084af7746d269241628c667003e9d39288b190ad5cef218ada625cbba8be411bb153828d8d3634e8f586638e2448425bc5b671be69800392ccbdebc945a5099c7406f6a11824105ecad345e525957053e77fbc0119d6b3fa7f854527e816cfce0d95dac66888e07e8990c95103d8e46124aac16f152e088520d7ec8325e3a2456f840e5b77ef0e3c410b347ccaf8a87516d10b88d436563c80712153273993afc320ec49b638225f58de464a1345e62a564b398939f96f6f4b7cf21b583609f85495a")
|
||||
verificationScript, _ := hex.DecodeString("552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae")
|
||||
nextCon, _ := util.Uint160DecodeString(address.ToScriptHash("APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR"))
|
||||
|
||||
msgHeaders.AddHeader(&BlockBase{
|
||||
Version: 0,
|
||||
Index: 10,
|
||||
PrevHash: prevH.Reverse(),
|
||||
MerkleRoot: merkleRoot.Reverse(),
|
||||
Timestamp: 1476647551,
|
||||
ConsensusData: 0xc0f0280216ff14bf,
|
||||
NextConsensus: nextCon,
|
||||
Witness: transaction.Witness{
|
||||
InvocationScript: invocationScript,
|
||||
VerificationScript: verificationScript,
|
||||
},
|
||||
})
|
||||
|
||||
assert.Equal(t, 1, len(msgHeaders.Headers))
|
||||
|
||||
err := msgHeaders.Headers[0].createHash()
|
||||
assert.Equal(t, nil, err)
|
||||
// Hash being correct, automatically verifies that the fields are encoded properly
|
||||
assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", msgHeaders.Headers[0].Hash.String())
|
||||
|
||||
}
|
||||
|
||||
func TestEncodeDecode(t *testing.T) {
|
||||
rawBlockHeaders := "010000000026b3c3df4dc1602a3b0e6989248b23275b5e4014a159af5dce69e16d4ab75f00f439321a51f425a530820cfe4d715bfd835b49687e87772f2c4737b8bc586dca7fda03580a000000bf14ff160228f0c059e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd45014036fdd23248880c1c311bcd97df04fe6d740dc1bf340c26915f0466e31e81c039012eca7a760270389e04b58b99820fe49cf8c24c9afc65d696b4d3f406a1e6b5405172a9b461e68dd399c8716de11d31f7dd2ec3be327c636b024562db6ac5df1cffdbee74c994736fd49803234d2baffbc0054f28ba5ec76494a467b4106955bb4084af7746d269241628c667003e9d39288b190ad5cef218ada625cbba8be411bb153828d8d3634e8f586638e2448425bc5b671be69800392ccbdebc945a5099c7406f6a11824105ecad345e525957053e77fbc0119d6b3fa7f854527e816cfce0d95dac66888e07e8990c95103d8e46124aac16f152e088520d7ec8325e3a2456f840e5b77ef0e3c410b347ccaf8a87516d10b88d436563c80712153273993afc320ec49b638225f58de464a1345e62a564b398939f96f6f4b7cf21b583609f85495af1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae00"
|
||||
|
||||
var headerMsg HeadersMessage
|
||||
|
||||
rawBlockBytes, _ := hex.DecodeString(rawBlockHeaders)
|
||||
|
||||
r := bytes.NewReader(rawBlockBytes)
|
||||
|
||||
err := headerMsg.DecodePayload(r)
|
||||
|
||||
assert.Equal(t, 1, len(headerMsg.Headers))
|
||||
|
||||
header := headerMsg.Headers[0]
|
||||
err = header.createHash()
|
||||
|
||||
assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", header.Hash.String())
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
err = headerMsg.EncodePayload(buf)
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
assert.Equal(t, hex.EncodeToString(rawBlockBytes), hex.EncodeToString(buf.Bytes()))
|
||||
}
|
110
pkg/wire/payload/minventory.go
Normal file
110
pkg/wire/payload/minventory.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
type InvType uint8
|
||||
|
||||
//Inventory types
|
||||
const (
|
||||
InvTypeTx InvType = 0x01
|
||||
InvTypeBlock InvType = 0x02
|
||||
InvTypeConsensus InvType = 0xe0
|
||||
)
|
||||
|
||||
const (
|
||||
maxHashes = 0x10000000
|
||||
)
|
||||
|
||||
var (
|
||||
MaxHashError = errors.New("Max size For Hashes reached")
|
||||
)
|
||||
|
||||
type InvMessage struct {
|
||||
cmd command.Type
|
||||
Type InvType
|
||||
Hashes []util.Uint256
|
||||
}
|
||||
|
||||
func NewInvMessage(typ InvType) (*InvMessage, error) {
|
||||
|
||||
inv := &InvMessage{
|
||||
command.Inv,
|
||||
typ,
|
||||
nil,
|
||||
}
|
||||
return inv, nil
|
||||
}
|
||||
|
||||
func newAbstractInv(typ InvType, cmd command.Type) (*InvMessage, error) {
|
||||
inv, err := NewInvMessage(typ)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inv.cmd = cmd
|
||||
|
||||
return inv, nil
|
||||
|
||||
}
|
||||
|
||||
func (i *InvMessage) AddHash(h util.Uint256) error {
|
||||
if len(i.Hashes)+1 > maxHashes {
|
||||
return MaxHashError
|
||||
}
|
||||
i.Hashes = append(i.Hashes, h)
|
||||
return nil
|
||||
}
|
||||
func (i *InvMessage) AddHashes(hashes []util.Uint256) error {
|
||||
var err error
|
||||
for _, hash := range hashes {
|
||||
err = i.AddHash(hash)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (v *InvMessage) DecodePayload(r io.Reader) error {
|
||||
br := &util.BinReader{R: r}
|
||||
|
||||
br.Read(&v.Type)
|
||||
|
||||
listLen := br.VarUint()
|
||||
v.Hashes = make([]util.Uint256, listLen)
|
||||
|
||||
for i := 0; i < int(listLen); i++ {
|
||||
br.Read(&v.Hashes[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *InvMessage) EncodePayload(w io.Writer) error {
|
||||
|
||||
bw := &util.BinWriter{W: w}
|
||||
bw.Write(v.Type)
|
||||
|
||||
lenhashes := len(v.Hashes)
|
||||
bw.VarUint(uint64(lenhashes))
|
||||
|
||||
for _, hash := range v.Hashes {
|
||||
|
||||
bw.Write(hash)
|
||||
|
||||
}
|
||||
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *InvMessage) Command() command.Type {
|
||||
return v.cmd
|
||||
}
|
78
pkg/wire/payload/minventory_test.go
Normal file
78
pkg/wire/payload/minventory_test.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewInventory(t *testing.T) {
|
||||
msgInv, err := NewInvMessage(InvTypeBlock)
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, command.Inv, msgInv.Command())
|
||||
|
||||
hash, _ := util.Uint256DecodeBytes([]byte("hello"))
|
||||
err = msgInv.AddHash(hash)
|
||||
assert.Equal(t, nil, err)
|
||||
}
|
||||
|
||||
// Adjust test time or it will timeout
|
||||
// func TestMaxHashes(t *testing.T) {
|
||||
// msgInv, err := NewInvMessage(InvTypeBlock)
|
||||
// assert.Equal(t, nil, err)
|
||||
|
||||
// hash, _ := util.Uint256DecodeBytes([]byte("hello"))
|
||||
|
||||
// for i := 0; i <= maxHashes+1; i++ {
|
||||
// err = msgInv.AddHash(hash)
|
||||
// }
|
||||
// if err == nil {
|
||||
// assert.Fail(t, "Max Hashes Exceeded, only allowed %v but have %v", maxHashes, len(msgInv.Hashes))
|
||||
// } else if err != MaxHashError {
|
||||
// assert.Fail(t, "Expected a MaxHashError, however we got %s", err.Error())
|
||||
// }
|
||||
// }
|
||||
func TestEncodeDecodePayload(t *testing.T) {
|
||||
msgInv, err := NewInvMessage(InvTypeBlock)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
blockOneHash := "d782db8a38b0eea0d7394e0f007c61c71798867578c77c387c08113903946cc9"
|
||||
hash, _ := util.Uint256DecodeString(blockOneHash)
|
||||
|
||||
err = msgInv.AddHash(hash)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err = msgInv.EncodePayload(buf)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
numOfHashes := []byte{1}
|
||||
expected := append([]byte{uint8(InvTypeBlock)}, numOfHashes...)
|
||||
expected = append(expected, hash.Bytes()...)
|
||||
|
||||
assert.Equal(t, hex.EncodeToString(expected), hex.EncodeToString(buf.Bytes()))
|
||||
|
||||
var InvDec InvMessage
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
err = InvDec.DecodePayload(r)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
assert.Equal(t, 1, len(InvDec.Hashes))
|
||||
assert.Equal(t, blockOneHash, hex.EncodeToString(InvDec.Hashes[0].Bytes()))
|
||||
|
||||
}
|
||||
func TestEmptyInv(t *testing.T) {
|
||||
msgInv, err := NewInvMessage(InvTypeBlock)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
msgInv.EncodePayload(buf)
|
||||
assert.Equal(t, []byte{byte(InvTypeBlock), 0}, buf.Bytes())
|
||||
assert.Equal(t, 0, len(msgInv.Hashes))
|
||||
}
|
29
pkg/wire/payload/mmempool.go
Normal file
29
pkg/wire/payload/mmempool.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
)
|
||||
|
||||
// No payload
|
||||
type GetMempool struct{}
|
||||
|
||||
func NewGetMempool() (*GetMempool, error) {
|
||||
return &GetMempool{}, nil
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (v *GetMempool) DecodePayload(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *GetMempool) EncodePayload(w io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *GetMempool) Command() command.Type {
|
||||
return command.Mempool
|
||||
}
|
34
pkg/wire/payload/mtx.go
Normal file
34
pkg/wire/payload/mtx.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
|
||||
)
|
||||
|
||||
type TXMessage struct {
|
||||
// w *bytes.Buffer
|
||||
Tx transaction.Transactioner
|
||||
}
|
||||
|
||||
func NewTXMessage(tx transaction.Transactioner) (*TXMessage, error) {
|
||||
|
||||
Tx := &TXMessage{tx}
|
||||
return Tx, nil
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (t *TXMessage) DecodePayload(r io.Reader) error {
|
||||
return t.Tx.Decode(r)
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (t *TXMessage) EncodePayload(w io.Writer) error {
|
||||
return t.Tx.Encode(w)
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *TXMessage) Command() command.Type {
|
||||
return command.TX
|
||||
}
|
29
pkg/wire/payload/mverack.go
Normal file
29
pkg/wire/payload/mverack.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
)
|
||||
|
||||
// No payload
|
||||
type VerackMessage struct{}
|
||||
|
||||
func NewVerackMessage() (*VerackMessage, error) {
|
||||
return &VerackMessage{}, nil
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (v *VerackMessage) DecodePayload(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *VerackMessage) EncodePayload(w io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *VerackMessage) Command() command.Type {
|
||||
return command.Verack
|
||||
}
|
17
pkg/wire/payload/mverack_test.go
Normal file
17
pkg/wire/payload/mverack_test.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewVerack(t *testing.T) {
|
||||
|
||||
verackMessage, err := NewVerackMessage()
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, command.Verack, verackMessage.Command())
|
||||
}
|
97
pkg/wire/payload/mversion.go
Normal file
97
pkg/wire/payload/mversion.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
// Copied and Modified for NEO from: https://github.com/decred/dcrd/blob/master/wire/VersionMessage.go
|
||||
|
||||
package payload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/command"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
const (
|
||||
minMsgVersionSize = 28
|
||||
)
|
||||
|
||||
// TODO: Refactor to pull out the useragent out of initialiser
|
||||
// and have a seperate method to add it
|
||||
|
||||
type VersionMessage struct {
|
||||
// w *bytes.Buffer
|
||||
Version protocol.Version
|
||||
Timestamp uint32
|
||||
Services protocol.ServiceFlag
|
||||
IP net.IP
|
||||
Port uint16
|
||||
Nonce uint32
|
||||
UserAgent []byte
|
||||
StartHeight uint32
|
||||
Relay bool
|
||||
}
|
||||
|
||||
var ErrInvalidNetAddr = errors.New("provided net.Addr is not a net.TCPAddr")
|
||||
|
||||
func NewVersionMessage(addr net.Addr, startHeight uint32, relay bool, pver protocol.Version, userAgent string, nonce uint32, services protocol.ServiceFlag) (*VersionMessage, error) {
|
||||
|
||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
return nil, ErrInvalidNetAddr
|
||||
}
|
||||
|
||||
version := &VersionMessage{
|
||||
pver,
|
||||
uint32(time.Now().Unix()),
|
||||
services,
|
||||
tcpAddr.IP,
|
||||
uint16(tcpAddr.Port),
|
||||
nonce,
|
||||
[]byte(userAgent),
|
||||
startHeight,
|
||||
relay,
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// Implements Messager interface
|
||||
func (v *VersionMessage) DecodePayload(r io.Reader) error {
|
||||
br := &util.BinReader{R: r}
|
||||
br.Read(&v.Version)
|
||||
br.Read(&v.Services)
|
||||
br.Read(&v.Timestamp)
|
||||
br.Read(&v.Port) // Port is not BigEndian
|
||||
br.Read(&v.Nonce)
|
||||
|
||||
var lenUA uint8
|
||||
br.Read(&lenUA)
|
||||
|
||||
v.UserAgent = make([]byte, lenUA)
|
||||
br.Read(&v.UserAgent)
|
||||
br.Read(&v.StartHeight)
|
||||
br.Read(&v.Relay)
|
||||
return br.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *VersionMessage) EncodePayload(w io.Writer) error {
|
||||
bw := &util.BinWriter{W: w}
|
||||
|
||||
bw.Write(v.Version)
|
||||
bw.Write(v.Services)
|
||||
bw.Write(v.Timestamp)
|
||||
bw.Write(v.Port) // Not big End
|
||||
bw.Write(v.Nonce)
|
||||
bw.Write(uint8(len(v.UserAgent)))
|
||||
bw.Write(v.UserAgent)
|
||||
bw.Write(v.StartHeight)
|
||||
bw.Write(v.Relay)
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
// Implements messager interface
|
||||
func (v *VersionMessage) Command() command.Type {
|
||||
return command.Version
|
||||
}
|
59
pkg/wire/payload/mversion_test.go
Normal file
59
pkg/wire/payload/mversion_test.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidNewVersionMessage(t *testing.T) {
|
||||
|
||||
expectedIP := "127.0.0.1"
|
||||
expectedPort := 8333
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
|
||||
nonce := randRange(12949672, 42949672)
|
||||
message, err := NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService)
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, expectedIP, message.IP.String())
|
||||
assert.Equal(t, uint16(expectedPort), message.Port)
|
||||
assert.Equal(t, protocol.DefaultVersion, message.Version)
|
||||
}
|
||||
func TestEncode(t *testing.T) {
|
||||
|
||||
expectedIP := "127.0.0.1"
|
||||
expectedPort := 8333
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
|
||||
nonce := randRange(12949672, 42949672)
|
||||
message, err := NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err = message.EncodePayload(buf)
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, len(message.UserAgent)+minMsgVersionSize, int(buf.Len()))
|
||||
}
|
||||
func TestLenIsCorrect(t *testing.T) {
|
||||
|
||||
expectedIP := "127.0.0.1"
|
||||
expectedPort := 8333
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
|
||||
nonce := randRange(12949672, 42949672)
|
||||
message, err := NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err = message.EncodePayload(buf)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
assert.Equal(t, len(message.UserAgent)+minMsgVersionSize, len(buf.Bytes()))
|
||||
}
|
||||
|
||||
func randRange(min, max int) uint32 {
|
||||
rand.Seed(time.Now().Unix() + int64(rand.Uint64()))
|
||||
return uint32(rand.Intn(max-min) + min)
|
||||
}
|
54
pkg/wire/payload/net_addr.go
Normal file
54
pkg/wire/payload/net_addr.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
// Once a VersionMessage is received, we can then store it inside of AddrMessage struct
|
||||
// TODO: store this inside version message and have a bool to indicate whether to encode ip
|
||||
// VersionMessage does not encodeIP
|
||||
type Net_addr struct {
|
||||
Timestamp uint32
|
||||
IP [16]byte
|
||||
Port uint16
|
||||
Service protocol.ServiceFlag
|
||||
}
|
||||
|
||||
func NewNetAddr(time uint32, ip [16]byte, port uint16, service protocol.ServiceFlag) (*Net_addr, error) {
|
||||
return &Net_addr{time, ip, port, service}, nil
|
||||
}
|
||||
|
||||
func NewAddrFromVersionMessage(version VersionMessage) (*Net_addr, error) {
|
||||
|
||||
var ip [16]byte
|
||||
|
||||
copy(ip[:], []byte(version.IP)[:16])
|
||||
|
||||
return NewNetAddr(version.Timestamp, ip, version.Port, version.Services)
|
||||
}
|
||||
|
||||
func (n *Net_addr) EncodePayload(bw *util.BinWriter) {
|
||||
|
||||
bw.Write(uint32(time.Now().Unix()))
|
||||
bw.Write(protocol.NodePeerService)
|
||||
bw.WriteBigEnd(n.IP)
|
||||
bw.WriteBigEnd(n.Port)
|
||||
}
|
||||
func (n *Net_addr) DecodePayload(br *util.BinReader) {
|
||||
|
||||
br.Read(&n.Timestamp)
|
||||
br.Read(&n.Service)
|
||||
br.ReadBigEnd(&n.IP)
|
||||
br.ReadBigEnd(&n.Port)
|
||||
}
|
||||
func (n *Net_addr) IPPort() string {
|
||||
ip := net.IP(n.IP[:]).String()
|
||||
port := strconv.Itoa(int(n.Port))
|
||||
ipport := ip + ":" + port
|
||||
return ipport
|
||||
}
|
65
pkg/wire/payload/transaction/Attribute.go
Normal file
65
pkg/wire/payload/transaction/Attribute.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
// Attribute represents a Transaction attribute.
|
||||
type Attribute struct {
|
||||
Usage AttrUsage
|
||||
Data []byte
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMaxData = errors.New("Max Size of Attribute reached")
|
||||
)
|
||||
|
||||
const (
|
||||
maxAttrSize = 65535
|
||||
)
|
||||
|
||||
func (a *Attribute) Encode(bw *util.BinWriter) {
|
||||
if len(a.Data) > maxAttrSize {
|
||||
bw.Err = ErrMaxData
|
||||
return
|
||||
}
|
||||
bw.Write(uint8(a.Usage))
|
||||
|
||||
if a.Usage == DescriptionURL || a.Usage == Vote || (a.Usage >= Hash1 && a.Usage <= Hash15) {
|
||||
bw.Write(a.Data[:32])
|
||||
|
||||
} else if a.Usage == Script {
|
||||
bw.Write(a.Data[:20])
|
||||
} else if a.Usage == ECDH02 || a.Usage == ECDH03 {
|
||||
bw.Write(a.Data[1:33])
|
||||
} else if a.Usage == CertURL || a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark {
|
||||
bw.VarUint(uint64(len(a.Data)))
|
||||
bw.Write(a.Data)
|
||||
} else {
|
||||
bw.Write(a.Data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (a *Attribute) Decode(br *util.BinReader) {
|
||||
br.Read(&a.Usage)
|
||||
if a.Usage == DescriptionURL || a.Usage == Vote || a.Usage >= Hash1 && a.Usage <= Hash15 {
|
||||
a.Data = make([]byte, 32)
|
||||
br.Read(&a.Data)
|
||||
|
||||
} else if a.Usage == Script {
|
||||
a.Data = make([]byte, 20)
|
||||
br.Read(&a.Data)
|
||||
} else if a.Usage == ECDH02 || a.Usage == ECDH03 {
|
||||
a.Data = make([]byte, 32)
|
||||
br.Read(&a.Data)
|
||||
} else if a.Usage == CertURL || a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark {
|
||||
lenData := br.VarUint()
|
||||
a.Data = make([]byte, lenData)
|
||||
br.Read(&a.Data)
|
||||
} else {
|
||||
br.Read(&a.Data)
|
||||
}
|
||||
}
|
28
pkg/wire/payload/transaction/Input.go
Normal file
28
pkg/wire/payload/transaction/Input.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package transaction
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
|
||||
// Input represents a Transaction input.
|
||||
type Input struct {
|
||||
// The hash of the previous transaction.
|
||||
PrevHash util.Uint256
|
||||
|
||||
// The index of the previous transaction.
|
||||
PrevIndex uint16
|
||||
}
|
||||
|
||||
func NewInput(prevHash util.Uint256, prevIndex uint16) *Input {
|
||||
return &Input{
|
||||
prevHash,
|
||||
prevIndex,
|
||||
}
|
||||
}
|
||||
func (i *Input) Encode(bw *util.BinWriter) {
|
||||
bw.Write(i.PrevHash)
|
||||
bw.Write(i.PrevIndex)
|
||||
}
|
||||
|
||||
func (i *Input) Decode(br *util.BinReader) {
|
||||
br.Read(&i.PrevHash)
|
||||
br.Read(&i.PrevIndex)
|
||||
}
|
34
pkg/wire/payload/transaction/Output.go
Normal file
34
pkg/wire/payload/transaction/Output.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package transaction
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
|
||||
type Output struct {
|
||||
// The NEO asset id used in the transaction.
|
||||
AssetID util.Uint256
|
||||
|
||||
// Amount of AssetType send or received.
|
||||
Amount int64
|
||||
|
||||
// The address of the remittee.
|
||||
ScriptHash util.Uint160
|
||||
}
|
||||
|
||||
func NewOutput(assetID util.Uint256, Amount int64, ScriptHash util.Uint160) *Output {
|
||||
return &Output{
|
||||
assetID,
|
||||
Amount,
|
||||
ScriptHash,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Output) Encode(bw *util.BinWriter) {
|
||||
bw.Write(o.AssetID)
|
||||
bw.Write(o.Amount)
|
||||
bw.Write(o.ScriptHash)
|
||||
}
|
||||
|
||||
func (o *Output) Decode(br *util.BinReader) {
|
||||
br.Read(&o.AssetID)
|
||||
br.Read(&o.Amount)
|
||||
br.Read(&o.ScriptHash)
|
||||
}
|
34
pkg/wire/payload/transaction/Witness.go
Normal file
34
pkg/wire/payload/transaction/Witness.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/wire/util"
|
||||
)
|
||||
|
||||
type Witness struct {
|
||||
InvocationScript []byte
|
||||
VerificationScript []byte
|
||||
}
|
||||
|
||||
func (s *Witness) Encode(bw *util.BinWriter) error {
|
||||
|
||||
bw.VarUint(uint64(len(s.InvocationScript)))
|
||||
bw.Write(s.InvocationScript)
|
||||
|
||||
bw.VarUint(uint64(len(s.VerificationScript)))
|
||||
bw.Write(s.VerificationScript)
|
||||
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
func (s *Witness) Decode(br *util.BinReader) error {
|
||||
|
||||
lenb := br.VarUint()
|
||||
s.InvocationScript = make([]byte, lenb)
|
||||
br.Read(s.InvocationScript)
|
||||
|
||||
lenb = br.VarUint()
|
||||
s.VerificationScript = make([]byte, lenb)
|
||||
br.Read(s.VerificationScript)
|
||||
|
||||
return br.Err
|
||||
}
|
16
pkg/wire/payload/transaction/assettype.go
Normal file
16
pkg/wire/payload/transaction/assettype.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package transaction
|
||||
|
||||
// AssetType represent a NEO asset type
|
||||
type AssetType uint8
|
||||
|
||||
// Valid asset types.
|
||||
const (
|
||||
CreditFlag AssetType = 0x40
|
||||
DutyFlag AssetType = 0x80
|
||||
GoverningToken AssetType = 0x00
|
||||
UtilityToken AssetType = 0x01
|
||||
Currency AssetType = 0x08
|
||||
Share AssetType = DutyFlag | 0x10
|
||||
Invoice AssetType = DutyFlag | 0x18
|
||||
Token AssetType = CreditFlag | 0x20
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue