move non-proto code into extras/, switch to go modules, drop old dht

This commit is contained in:
Alex Grintsvayg 2019-01-09 13:05:41 -05:00
parent f986bd3066
commit a8bc4d4e36
91 changed files with 264 additions and 3383 deletions

View file

@ -1,5 +1,18 @@
os: linux
dist: trusty
dist: xenial
language: go
go:
- "1.10.x"
- 1.11.x
env:
- GO111MODULE=on
install: true
script:
# Fail if a .go file hasn't been formatted with gofmt
- test -z $(gofmt -s -l $(find . -iname '*.go' -type f | grep -v /vendor/ ))
- make
notifications:
email: false

352
Gopkg.lock generated
View file

@ -1,352 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:cc8ebf0c6745d09f728f1fa4fbd29baaa2e3a65efb49b5fefb0c163171ee7863"
name = "github.com/btcsuite/btcd"
packages = [
"btcec",
"btcjson",
"chaincfg",
"chaincfg/chainhash",
"rpcclient",
"wire",
]
pruneopts = ""
revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64"
[[projects]]
branch = "master"
digest = "1:30d4a548e09bca4a0c77317c58e7407e2a65c15325e944f9c08a7b7992f8a59e"
name = "github.com/btcsuite/btclog"
packages = ["."]
pruneopts = ""
revision = "84c8d2346e9fc8c7b947e243b9c24e6df9fd206a"
[[projects]]
branch = "master"
digest = "1:b0f4d2431c167d7127a029210c1a7cdc33c9114c1b3fd3582347baad5e832588"
name = "github.com/btcsuite/btcutil"
packages = [
".",
"base58",
"bech32",
]
pruneopts = ""
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
[[projects]]
branch = "master"
digest = "1:422f38d57f1bc0fdc34f26d0f1026869a3710400b09b5478c9288efa13573cfa"
name = "github.com/btcsuite/go-socks"
packages = ["socks"]
pruneopts = ""
revision = "4720035b7bfd2a9bb130b1c184f8bbe41b6f0d0f"
[[projects]]
branch = "master"
digest = "1:dfc248d5e6e1582fdec83796d3d1d451aa6cae773c4e4ba1dac2838caef6d381"
name = "github.com/btcsuite/websocket"
packages = ["."]
pruneopts = ""
revision = "31079b6807923eb23992c421b114992b95131b55"
[[projects]]
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = ""
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
digest = "1:968d8903d598e3fae738325d3410f33f07ea6a2b9ee5591e9c262ee37df6845a"
name = "github.com/go-errors/errors"
packages = ["."]
pruneopts = ""
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
version = "v1.0.1"
[[projects]]
branch = "master"
digest = "1:cd5bab9c9e23ffa6858eaa79dc827fd84bc24bc00b0cfb0b14036e393da2b1fa"
name = "github.com/go-ini/ini"
packages = ["."]
pruneopts = ""
revision = "5cf292cae48347c2490ac1a58fe36735fb78df7e"
[[projects]]
digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b"
name = "github.com/golang/protobuf"
packages = [
"jsonpb",
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/struct",
"ptypes/timestamp",
]
pruneopts = ""
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]]
digest = "1:64d212c703a2b94054be0ce470303286b177ad260b2f89a307e3d1bb6c073ef6"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = ""
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = ""
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:5e30b8342813a6a85a647f9277e34ffcd5872dc57ab590dd9b251b145b6ec88f"
name = "github.com/lbryio/ozzo-validation"
packages = ["."]
pruneopts = ""
revision = "d1008ad1fd04ceb5faedaf34881df0c504382706"
version = "v3.1"
[[projects]]
branch = "master"
digest = "1:349bf8c6b66272abd25af9538be35071991811b6ba3e5c849515c0bfcf3f2bd0"
name = "github.com/lbryio/types"
packages = ["go"]
pruneopts = ""
revision = "594241d24e0025d769d2cb58168536b6963d51cf"
[[projects]]
branch = "master"
digest = "1:eb9117392ee8e7aa44f78e0db603f70b1050ee0ebda4bd40040befb5b218c546"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
pruneopts = ""
revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b"
[[projects]]
digest = "1:3cb50c403fa46c85697dbc4e06a95008689e058f33466b7eb8d31ea0eb291ea3"
name = "github.com/nlopes/slack"
packages = ["."]
pruneopts = ""
revision = "8ab4d0b364ef1e9af5d102531da20d5ec902b6c4"
version = "v0.2.0"
[[projects]]
digest = "1:3962f553b77bf6c03fc07cd687a22dd3b00fe11aa14d31194f5505f5bb65cdc8"
name = "github.com/sergi/go-diff"
packages = ["diffmatchpatch"]
pruneopts = ""
revision = "1744e2970ca51c86172c8190fadad617561ed6e7"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:67b7dcb3b7e67cb6f96fb38fe7358bc1210453189da210e40cf357a92d57c1c1"
name = "github.com/shopspring/decimal"
packages = ["."]
pruneopts = ""
revision = "19e3cb6c29303990525b56f51acf77c5630dd88a"
[[projects]]
branch = "master"
digest = "1:c92f01303e3ab3b5da92657841639cb53d1548f0d2733d12ef3b9fd9d47c869e"
name = "github.com/sirupsen/logrus"
packages = ["."]
pruneopts = ""
revision = "ea8897e79973357ba785ac2533559a6297e83c44"
[[projects]]
branch = "master"
digest = "1:d0b38ba6da419a6d4380700218eeec8623841d44a856bb57369c172fbf692ab4"
name = "github.com/spf13/cast"
packages = ["."]
pruneopts = ""
revision = "8965335b8c7107321228e3e3702cab9832751bac"
[[projects]]
branch = "master"
digest = "1:bfbf4a9c265ef41f8d03c9d91e340aaddae835710eaed6cd2e6be889cbc05f56"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = ""
revision = "1e58aa3361fd650121dceeedc399e7189c05674a"
[[projects]]
digest = "1:8e243c568f36b09031ec18dff5f7d2769dcf5ca4d624ea511c8e3197dc3d352d"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = ""
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
branch = "master"
digest = "1:22d3674d44ee93f52a9c0b6a22d1f736a0ad9ac3f9d2c1ca8648f3c9ce9910bd"
name = "github.com/ybbus/jsonrpc"
packages = ["."]
pruneopts = ""
revision = "2a548b7d822dd62717337a6b1e817fae1b14660a"
[[projects]]
branch = "master"
digest = "1:3610c577942fbfd2c8975d70a2342bbd13f30cf214237fb8f920c9a6cec0f14a"
name = "github.com/zeebo/bencode"
packages = ["."]
pruneopts = ""
revision = "d522839ac797fc43269dae6a04a1f8be475a915d"
[[projects]]
branch = "master"
digest = "1:8af4dda167d0ef21ab0affc797bff87ed0e87c57bd1d9bf57ad8f72d348c7932"
name = "golang.org/x/crypto"
packages = [
"ripemd160",
"sha3",
"ssh/terminal",
]
pruneopts = ""
revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9"
[[projects]]
branch = "master"
digest = "1:5dc6753986b9eeba4abdf05dedc5ba06bb52dad43cc8aad35ffb42bb7adfa68f"
name = "golang.org/x/net"
packages = [
"context",
"http/httpguts",
"http2",
"http2/hpack",
"idna",
"internal/timeseries",
"trace",
]
pruneopts = ""
revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196"
[[projects]]
branch = "master"
digest = "1:baee54aa41cb93366e76a9c29f8dd2e4c4e6a35ff89551721d5275d2c858edc9"
name = "golang.org/x/sys"
packages = [
"unix",
"windows",
]
pruneopts = ""
revision = "bff228c7b664c5fce602223a05fb708fd8654986"
[[projects]]
digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
]
pruneopts = ""
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
digest = "1:1b3b4ec811695907c4a3cb92e4f32834a4a42459bff7e02068b6b2b5344803cd"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = ""
revision = "af9cb2a35e7f169ec875002c1829c9b315cddc04"
[[projects]]
digest = "1:15656947b87a6a240e61dcfae9e71a55a8d5677f240d12ab48f02cdbabf1e309"
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"codes",
"connectivity",
"credentials",
"encoding",
"encoding/proto",
"grpclog",
"internal",
"internal/backoff",
"internal/channelz",
"internal/envconfig",
"internal/grpcrand",
"internal/transport",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
]
pruneopts = ""
revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1"
version = "v1.15.0"
[[projects]]
digest = "1:f771bf87a3253de520c2af6fb6e75314dce0fedc0b30b208134fe502932bb15d"
name = "gopkg.in/nullbio/null.v6"
packages = ["convert"]
pruneopts = ""
revision = "40264a2e6b7972d183906cf17663983c23231c82"
version = "v6.3"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/btcsuite/btcd/chaincfg",
"github.com/btcsuite/btcd/chaincfg/chainhash",
"github.com/btcsuite/btcd/rpcclient",
"github.com/btcsuite/btcutil",
"github.com/btcsuite/btcutil/base58",
"github.com/davecgh/go-spew/spew",
"github.com/go-errors/errors",
"github.com/go-ini/ini",
"github.com/golang/protobuf/jsonpb",
"github.com/golang/protobuf/proto",
"github.com/lbryio/ozzo-validation",
"github.com/lbryio/types/go",
"github.com/mitchellh/mapstructure",
"github.com/nlopes/slack",
"github.com/sergi/go-diff/diffmatchpatch",
"github.com/shopspring/decimal",
"github.com/sirupsen/logrus",
"github.com/spf13/cast",
"github.com/spf13/cobra",
"github.com/ybbus/jsonrpc",
"github.com/zeebo/bencode",
"golang.org/x/crypto/ripemd160",
"golang.org/x/crypto/sha3",
"golang.org/x/net/context",
"google.golang.org/grpc",
"gopkg.in/nullbio/null.v6/convert",
]
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -1,47 +0,0 @@
[[constraint]]
name = "github.com/davecgh/go-spew"
version = "1.1.0"
[[constraint]]
name = "github.com/go-errors/errors"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/mitchellh/mapstructure"
[[constraint]]
branch = "master"
name = "github.com/shopspring/decimal"
[[constraint]]
name = "github.com/sirupsen/logrus"
branch = "master"
[[constraint]]
name = "github.com/spf13/cast"
branch = "master"
[[constraint]]
branch = "master"
name = "github.com/spf13/cobra"
[[constraint]]
branch = "master"
name = "github.com/ybbus/jsonrpc"
[[constraint]]
branch = "master"
name = "github.com/zeebo/bencode"
[[constraint]]
branch = "master"
name = "github.com/btcsuite/btcd"
[[constraint]]
branch = "master"
name = "github.com/go-ini/ini"
[[constraint]]
branch = "master"
name = "github.com/btcsuite/btcutil"

View file

@ -1,23 +1,17 @@
BINARY=lbry
DIR = $(shell cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
VENDOR_DIR = vendor
VERSION=$(shell git --git-dir=${DIR}/.git describe --dirty --always --long --abbrev=7)
LDFLAGS = -ldflags "-X main.Version=${VERSION}"
.PHONY: build dep clean
.PHONY: build clean
.DEFAULT_GOAL: build
build: dep
build:
CGO_ENABLED=0 go build ${LDFLAGS} -asmflags -trimpath=${DIR} -o ${DIR}/${BINARY} main.go
dep: | $(VENDOR_DIR)
$(VENDOR_DIR):
go get github.com/golang/dep/cmd/dep && dep ensure
clean:
if [ -f ${DIR}/${BINARY} ]; then rm ${DIR}/${BINARY}; fi

View file

@ -1,69 +0,0 @@
[![Build Status](https://travis-ci.org/lbryio/lbry.go.svg?branch=master)](https://travis-ci.org/lbryio/lbry.go)
# LBRY in Golang
lbry.go is a set of tools and projects implemented in Golang. See each subfolder for more details
## Installation
No installation required for lbry.go
## Usage
See individual subfolders for usage instructions
## Running from Source
### Go
Make sure you have Go 1.10.1+
- Ubuntu: https://launchpad.net/~longsleep/+archive/ubuntu/golang-backports or https://github.com/golang/go/wiki/Ubuntu
- OSX: `brew install go`
### Lbrycrd
_not strictly necessary, but recommended_
- Install lbrycrdd (https://github.com/lbryio/lbrycrd/releases)
- Ensure `~/.lbrycrd/lbrycrd.conf` file exists with username and password.
If you don't have one, run:
```
mkdir -p ~/.lbrycrd
echo -e "rpcuser=lbryrpc\nrpcpassword=$(env LC_CTYPE=C LC_ALL=C tr -dc A-Za-z0-9 < /dev/urandom | head -c 16 | xargs)" > ~/.lbrycrd/lbrycrd.conf
```
- Run `./lbrycrdd -server -daemon -txindex`. If you get an error about indexing, add the `-reindex` flag for one run. You will only need to
reindex once.
### building lbry.go
clone the repository
```
go get -u github.com/lbryio/lbry.go
cd "$(go env GOPATH)/src/github.com/lbryio/lbry.go"
```
run `make` from the root directory to build the binary
## Contributing
Contributions to this project are welcome, encouraged, and compensated. For more details, see [lbry.io/faq/contributing](https://lbry.io/faq/contributing)
GO strictly enforces a correct syntax therefore you might need to run `go fmt` from inside the each working directory.
When using an IDE like `Goland` you should set up file watchers such as to automatically format your code and sort your imports.
![alt text](img/filewatchers.png "file watchers")
## License
See [LICENSE](LICENSE)
## Security
We take security seriously. Please contact security@lbry.io regarding any issues you may encounter.
Our PGP key is [here](https://keybase.io/lbry/key.asc) if you need it.
## Contact
The primary contact for this project is [@nikooo777](https://github.com/nikooo777) (niko@lbry.io)

View file

@ -4,7 +4,7 @@ import (
"fmt"
"net"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
"golang.org/x/net/context"
"google.golang.org/grpc"

View file

@ -3,9 +3,10 @@ package claim
import (
"bytes"
types "github.com/lbryio/types/go"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
types "github.com/lbryio/types/go"
)
func ToJSON(value []byte) (string, error) {

View file

@ -1,63 +0,0 @@
package cmd
import (
"strconv"
"time"
"github.com/lbryio/lbry.go/dht"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
d := &cobra.Command{
Use: "dht <action>",
Args: cobra.ExactArgs(1),
Short: "Do DHT things",
Run: dhtCmd,
}
RootCmd.AddCommand(d)
ping := &cobra.Command{
Use: "ping <ip>",
Args: cobra.ExactArgs(1),
Short: "Ping a node on the DHT",
Run: dhtPingCmd,
}
d.AddCommand(ping)
}
func dhtCmd(cmd *cobra.Command, args []string) {
log.Errorln("chose a command")
}
func dhtPingCmd(cmd *cobra.Command, args []string) {
//ip := args[0]
port := 49449 // + (rand.Int() % 10)
config := dht.NewStandardConfig()
config.Address = "127.0.0.1:" + strconv.Itoa(port)
config.PrimeNodes = []string{
"127.0.0.1:10001",
}
d := dht.New(config)
log.Println("Starting...")
go d.Run()
time.Sleep(2 * time.Second)
for {
peers, err := d.FindNode("012b66fc7052d9a0c8cb563b8ede7662003ba65f425c2661b5c6919d445deeb31469be8b842d6faeea3f2b3ebcaec845")
if err != nil {
time.Sleep(time.Second * 1)
continue
}
log.Println("Found peers:", peers)
break
}
log.Println("done")
}

View file

@ -1,187 +0,0 @@
package cmd
import (
"strconv"
"sync"
"time"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/jsonrpc"
"github.com/shopspring/decimal"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
var franklinCmd = &cobra.Command{
Use: "franklin",
Short: "Test availability of homepage content",
Run: func(cmd *cobra.Command, args []string) {
franklin()
},
}
RootCmd.AddCommand(franklinCmd)
}
const (
maxPrice = float64(999)
waitForStart = 5 * time.Second
waitForEnd = 60 * time.Minute
maxParallelTests = 5
)
type Result struct {
started bool
finished bool
}
func franklin() {
conn := jsonrpc.NewClient("")
var wg sync.WaitGroup
queue := make(chan string)
var mutex sync.Mutex
results := map[string]Result{}
for i := 0; i < maxParallelTests; i++ {
go func() {
wg.Add(1)
defer wg.Done()
for {
url, more := <-queue
if !more {
return
}
res, err := doURL(conn, url)
mutex.Lock()
results[url] = res
mutex.Unlock()
if err != nil {
log.Errorln(url + ": " + err.Error())
}
}
}()
}
urls := []string{"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"}
for _, url := range urls {
queue <- url
}
close(queue)
wg.Wait()
countStarted := 0
countFinished := 0
for _, r := range results {
if r.started {
countStarted++
}
if r.finished {
countFinished++
}
}
log.Println("Started: " + strconv.Itoa(countStarted) + " of " + strconv.Itoa(len(results)))
log.Println("Finished: " + strconv.Itoa(countFinished) + " of " + strconv.Itoa(len(results)))
}
func doURL(conn *jsonrpc.Client, url string) (Result, error) {
log.Infoln(url + ": Starting")
result := Result{}
price, err := conn.StreamCostEstimate(url, nil)
if err != nil {
return result, err
}
if price == nil {
return result, errors.Err("could not get price of " + url)
}
if decimal.Decimal(*price).Cmp(decimal.NewFromFloat(maxPrice)) == 1 {
return result, errors.Err("the price of " + url + " is too damn high")
}
startTime := time.Now()
get, err := conn.Get(url, nil, nil)
if err != nil {
return result, err
} else if get == nil {
return result, errors.Err("received no response for 'get' of " + url)
}
if get.Completed {
log.Infoln(url + ": cannot test because we already have it")
return result, nil
}
log.Infoln(url + ": get took " + time.Since(startTime).String())
log.Infoln(url + ": waiting " + waitForStart.String() + " to see if it starts")
time.Sleep(waitForStart)
fileStartedResult, err := conn.FileList(jsonrpc.FileListOptions{Outpoint: &get.Outpoint})
if err != nil {
return result, err
}
if fileStartedResult == nil || len(*fileStartedResult) < 1 {
log.Errorln(url + ": failed to start in " + waitForStart.String())
} else if (*fileStartedResult)[0].Completed {
log.Infoln(url + ": already finished after " + waitForStart.String() + ". boom!")
result.started = true
result.finished = true
return result, nil
} else if (*fileStartedResult)[0].WrittenBytes == 0 {
log.Errorln(url + ": says it started, but has 0 bytes downloaded after " + waitForStart.String())
} else {
log.Infoln(url + ": started, with " + strconv.FormatUint((*fileStartedResult)[0].WrittenBytes, 10) + " bytes downloaded")
result.started = true
}
log.Infoln(url + ": waiting up to " + waitForEnd.String() + " for file to finish")
var fileFinishedResult *jsonrpc.FileListResponse
ticker := time.NewTicker(15 * time.Second)
// todo: timeout should be based on file size
timeout := time.After(waitForEnd)
WaitForFinish:
for {
select {
case <-ticker.C:
fileFinishedResult, err = conn.FileList(jsonrpc.FileListOptions{Outpoint: &get.Outpoint})
if err != nil {
return result, err
}
if fileFinishedResult != nil && len(*fileFinishedResult) > 0 {
if (*fileFinishedResult)[0].Completed {
ticker.Stop()
break WaitForFinish
} else {
log.Infoln(url + ": " + strconv.FormatUint((*fileFinishedResult)[0].WrittenBytes, 10) + " bytes downloaded after " + time.Since(startTime).String())
}
}
case <-timeout:
ticker.Stop()
break WaitForFinish
}
}
if fileFinishedResult == nil || len(*fileFinishedResult) < 1 {
log.Errorln(url + ": failed to start at all")
} else if !(*fileFinishedResult)[0].Completed {
log.Errorln(url + ": says it started, but has not finished after " + waitForEnd.String() + " (" + strconv.FormatUint((*fileFinishedResult)[0].WrittenBytes, 10) + " bytes written)")
} else {
log.Infoln(url + ": finished after " + time.Since(startTime).String() + " , with " + strconv.FormatUint((*fileFinishedResult)[0].WrittenBytes, 10) + " bytes downloaded")
result.finished = true
}
return result, nil
}

View file

@ -1,26 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "lbry",
Short: "A command-line swiss army knife for LBRY",
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View file

@ -1,31 +0,0 @@
package cmd
import (
"encoding/hex"
"fmt"
"github.com/lbryio/lbry.go/claim"
"github.com/spf13/cobra"
)
func init() {
var testCmd = &cobra.Command{
Use: "test",
Short: "For testing stuff",
Run: test,
}
RootCmd.AddCommand(testCmd)
}
func test(cmd *cobra.Command, args []string) {
value, err := hex.DecodeString("080110011ac10108011279080410011a0343617422002a00320d5075626c696320446f6d61696e38004a5568747470733a2f2f737065652e63682f373961653031353766356165356535336232326562646465666663326564623862363130666437372f68773978754137686d326b32645a35477479744a336448372e6a706752005a001a42080110011a30970015f05a30531c465a3f889ab516b972c57c529cd4b57b0bd1685a19c0caa8073f6d4f3db338c1034481fb3eb37241220a696d6167652f6a7065672a5c080110031a4088d15f554d64776f3b43bc63b50c16a69162eb256c9e7afe04505f88a36d7455069de25244834f6d14479b45064d4766fa359bd041886b612040c9dbc9d1d0ec221412bcd69bf6a7d503002f09d34c76f904253a4be2")
if err != nil {
panic(err)
}
s, err := claim.ToJSON(value)
if err != nil {
panic(err)
}
fmt.Println(s)
}

1
dht/.gitignore vendored
View file

@ -1 +0,0 @@
.DS_Store

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Dean Karn
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.

View file

@ -1,87 +0,0 @@
![](https://raw.githubusercontent.com/shiyanhui/dht/master/doc/screen-shot.png)
See the video on the [Youtube](https://www.youtube.com/watch?v=AIpeQtw22kc).
[中文版README](https://github.com/shiyanhui/dht/blob/master/README_CN.md)
## Introduction
DHT implements the bittorrent DHT protocol in Go. Now it includes:
- [BEP-3 (part)](http://www.bittorrent.org/beps/bep_0003.html)
- [BEP-5](http://www.bittorrent.org/beps/bep_0005.html)
- [BEP-9](http://www.bittorrent.org/beps/bep_0009.html)
- [BEP-10](http://www.bittorrent.org/beps/bep_0010.html)
It contains two modes, the standard mode and the crawling mode. The standard
mode follows the BEPs, and you can use it as a standard dht server. The crawling
mode aims to crawl as more metadata info as possiple. It doesn't follow the
standard BEPs protocol. With the crawling mode, you can build another [BTDigg](http://btdigg.org/).
[bthub.io](http://bthub.io) is a BT search engine based on the crawling mode.
## Installation
go get github.com/shiyanhui/dht
## Example
Below is a simple spider. You can move [here](https://github.com/shiyanhui/dht/blob/master/sample)
to see more samples.
```go
import (
"fmt"
"github.com/shiyanhui/dht"
)
func main() {
downloader := dht.NewWire(65535)
go func() {
// once we got the request result
for resp := range downloader.Response() {
fmt.Println(resp.InfoHash, resp.MetadataInfo)
}
}()
go downloader.Run()
config := dht.NewCrawlConfig()
config.OnAnnouncePeer = func(infoHash, ip string, port int) {
// request to download the metadata info
downloader.Request([]byte(infoHash), ip, port)
}
d := dht.New(config)
d.Run()
}
```
## Download
You can download the demo compiled binary file [here](https://github.com/shiyanhui/dht/files/407021/spider.zip).
## Note
- The default crawl mode configure costs about 300M RAM. Set **MaxNodes**
and **BlackListMaxSize** to fit yourself.
- Now it cant't run in LAN because of NAT.
## TODO
- [ ] NAT Traversal.
- [ ] Implements the full BEP-3.
- [ ] Optimization.
## FAQ
#### Why it is slow compared to other spiders ?
Well, maybe there are several reasons.
- DHT aims to implements the standard BitTorrent DHT protocol, not born for crawling the DHT network.
- NAT Traversal issue. You run the crawler in a local network.
- It will block ip which looks like bad and a good ip may be mis-judged.
## License
MIT, read more [here](https://github.com/shiyanhui/dht/blob/master/LICENSE)

View file

@ -1,163 +0,0 @@
package dht
import (
"fmt"
"strings"
)
// bitmap represents a bit array.
type bitmap struct {
Size int
data []byte
}
// newBitmap returns a size-length bitmap pointer.
func newBitmap(size int) *bitmap {
div, mod := size/8, size%8
if mod > 0 {
div++
}
return &bitmap{size, make([]byte, div)}
}
// newBitmapFrom returns a new copyed bitmap pointer which
// newBitmap.data = other.data[:size].
func newBitmapFrom(other *bitmap, size int) *bitmap {
bitmap := newBitmap(size)
if size > other.Size {
size = other.Size
}
div := size / 8
for i := 0; i < div; i++ {
bitmap.data[i] = other.data[i]
}
for i := div * 8; i < size; i++ {
if other.Bit(i) == 1 {
bitmap.Set(i)
}
}
return bitmap
}
// newBitmapFromBytes returns a bitmap pointer created from a byte array.
func newBitmapFromBytes(data []byte) *bitmap {
bitmap := newBitmap(len(data) * 8)
copy(bitmap.data, data)
return bitmap
}
// newBitmapFromString returns a bitmap pointer created from a string.
func newBitmapFromString(data string) *bitmap {
return newBitmapFromBytes([]byte(data))
}
// Bit returns the bit at index.
func (bitmap *bitmap) Bit(index int) int {
if index >= bitmap.Size {
panic("index out of range")
}
div, mod := index/8, index%8
return int((uint(bitmap.data[div]) & (1 << uint(7-mod))) >> uint(7-mod))
}
// set sets the bit at index `index`. If bit is true, set 1, otherwise set 0.
func (bitmap *bitmap) set(index int, bit int) {
if index >= bitmap.Size {
panic("index out of range")
}
div, mod := index/8, index%8
shift := byte(1 << uint(7-mod))
bitmap.data[div] &= ^shift
if bit > 0 {
bitmap.data[div] |= shift
}
}
// Set sets the bit at idnex to 1.
func (bitmap *bitmap) Set(index int) {
bitmap.set(index, 1)
}
// Unset sets the bit at idnex to 0.
func (bitmap *bitmap) Unset(index int) {
bitmap.set(index, 0)
}
// Compare compares the prefixLen-prefix of two bitmap.
// - If bitmap.data[:prefixLen] < other.data[:prefixLen], return -1.
// - If bitmap.data[:prefixLen] > other.data[:prefixLen], return 1.
// - Otherwise return 0.
func (bitmap *bitmap) Compare(other *bitmap, prefixLen int) int {
if prefixLen > bitmap.Size || prefixLen > other.Size {
panic("index out of range")
}
div, mod := prefixLen/8, prefixLen%8
for i := 0; i < div; i++ {
if bitmap.data[i] > other.data[i] {
return 1
} else if bitmap.data[i] < other.data[i] {
return -1
}
}
for i := div * 8; i < div*8+mod; i++ {
bit1, bit2 := bitmap.Bit(i), other.Bit(i)
if bit1 > bit2 {
return 1
} else if bit1 < bit2 {
return -1
}
}
return 0
}
// Xor returns the xor value of two bitmap.
func (bitmap *bitmap) Xor(other *bitmap) *bitmap {
if bitmap.Size != other.Size {
panic("size not the same")
}
distance := newBitmap(bitmap.Size)
div, mod := distance.Size/8, distance.Size%8
for i := 0; i < div; i++ {
distance.data[i] = bitmap.data[i] ^ other.data[i]
}
for i := div * 8; i < div*8+mod; i++ {
distance.set(i, bitmap.Bit(i)^other.Bit(i))
}
return distance
}
// String returns the bit sequence string of the bitmap.
func (bitmap *bitmap) String() string {
div, mod := bitmap.Size/8, bitmap.Size%8
buff := make([]string, div+mod)
for i := 0; i < div; i++ {
buff[i] = fmt.Sprintf("%08b", bitmap.data[i])
}
for i := div; i < div+mod; i++ {
buff[i] = fmt.Sprintf("%1b", bitmap.Bit(div*8+(i-div)))
}
return strings.Join(buff, "")
}
// RawString returns the string value of bitmap.data.
func (bitmap *bitmap) RawString() string {
return string(bitmap.data)
}

View file

@ -1,69 +0,0 @@
package dht
import (
"testing"
)
func TestBitmap(t *testing.T) {
a := newBitmap(10)
b := newBitmapFrom(a, 10)
c := newBitmapFromBytes([]byte{48, 49, 50, 51, 52, 53, 54, 55, 56, 57})
d := newBitmapFromString("0123456789")
e := newBitmap(10)
// Bit
for i := 0; i < a.Size; i++ {
if a.Bit(i) != 0 {
t.Fail()
}
}
// Compare
if c.Compare(d, d.Size) != 0 {
t.Fail()
}
// RawString
if c.RawString() != d.RawString() || c.RawString() != "0123456789" {
t.Fail()
}
// Set
b.Set(5)
if b.Bit(5) != 1 {
t.Fail()
}
// Unset
b.Unset(5)
if b.Bit(5) == 1 {
t.Fail()
}
// String
if e.String() != "0000000000" {
t.Fail()
}
e.Set(9)
if e.String() != "0000000001" {
t.Fail()
}
e.Set(2)
if e.String() != "0010000001" {
t.Fail()
}
a.Set(0)
a.Set(5)
a.Set(8)
if a.String() != "1000010010" {
t.Fail()
}
// Xor
b.Set(5)
b.Set(9)
if a.Xor(b).String() != "1000000011" {
t.Fail()
}
}

View file

@ -1,289 +0,0 @@
package dht
import (
"container/list"
"sync"
)
type mapItem struct {
key interface{}
val interface{}
}
// syncedMap represents a goroutine-safe map.
type syncedMap struct {
*sync.RWMutex
data map[interface{}]interface{}
}
// newSyncedMap returns a syncedMap pointer.
func newSyncedMap() *syncedMap {
return &syncedMap{
RWMutex: &sync.RWMutex{},
data: make(map[interface{}]interface{}),
}
}
// Get returns the value mapped to key.
func (smap *syncedMap) Get(key interface{}) (val interface{}, ok bool) {
smap.RLock()
defer smap.RUnlock()
val, ok = smap.data[key]
return
}
// Has returns whether the syncedMap contains the key.
func (smap *syncedMap) Has(key interface{}) bool {
_, ok := smap.Get(key)
return ok
}
// Set sets pair {key: val}.
func (smap *syncedMap) Set(key interface{}, val interface{}) {
smap.Lock()
defer smap.Unlock()
smap.data[key] = val
}
// Delete deletes the key in the map.
func (smap *syncedMap) Delete(key interface{}) {
smap.Lock()
defer smap.Unlock()
delete(smap.data, key)
}
// DeleteMulti deletes keys in batch.
func (smap *syncedMap) DeleteMulti(keys []interface{}) {
smap.Lock()
defer smap.Unlock()
for _, key := range keys {
delete(smap.data, key)
}
}
// Clear resets the data.
func (smap *syncedMap) Clear() {
smap.Lock()
defer smap.Unlock()
smap.data = make(map[interface{}]interface{})
}
// Iter returns a chan which output all items.
func (smap *syncedMap) Iter() <-chan mapItem {
ch := make(chan mapItem)
go func() {
smap.RLock()
for key, val := range smap.data {
ch <- mapItem{
key: key,
val: val,
}
}
smap.RUnlock()
close(ch)
}()
return ch
}
// Len returns the length of syncedMap.
func (smap *syncedMap) Len() int {
smap.RLock()
defer smap.RUnlock()
return len(smap.data)
}
// syncedList represents a goroutine-safe list.
type syncedList struct {
*sync.RWMutex
queue *list.List
}
// newSyncedList returns a syncedList pointer.
func newSyncedList() *syncedList {
return &syncedList{
RWMutex: &sync.RWMutex{},
queue: list.New(),
}
}
// Front returns the first element of slist.
func (slist *syncedList) Front() *list.Element {
slist.RLock()
defer slist.RUnlock()
return slist.queue.Front()
}
// Back returns the last element of slist.
func (slist *syncedList) Back() *list.Element {
slist.RLock()
defer slist.RUnlock()
return slist.queue.Back()
}
// PushFront pushs an element to the head of slist.
func (slist *syncedList) PushFront(v interface{}) *list.Element {
slist.Lock()
defer slist.Unlock()
return slist.queue.PushFront(v)
}
// PushBack pushs an element to the tail of slist.
func (slist *syncedList) PushBack(v interface{}) *list.Element {
slist.Lock()
defer slist.Unlock()
return slist.queue.PushBack(v)
}
// InsertBefore inserts v before mark.
func (slist *syncedList) InsertBefore(
v interface{}, mark *list.Element) *list.Element {
slist.Lock()
defer slist.Unlock()
return slist.queue.InsertBefore(v, mark)
}
// InsertAfter inserts v after mark.
func (slist *syncedList) InsertAfter(
v interface{}, mark *list.Element) *list.Element {
slist.Lock()
defer slist.Unlock()
return slist.queue.InsertAfter(v, mark)
}
// Remove removes e from the slist.
func (slist *syncedList) Remove(e *list.Element) interface{} {
slist.Lock()
defer slist.Unlock()
return slist.queue.Remove(e)
}
// Clear resets the list queue.
func (slist *syncedList) Clear() {
slist.Lock()
defer slist.Unlock()
slist.queue.Init()
}
// Len returns length of the slist.
func (slist *syncedList) Len() int {
slist.RLock()
defer slist.RUnlock()
return slist.queue.Len()
}
// Iter returns a chan which output all elements.
func (slist *syncedList) Iter() <-chan *list.Element {
ch := make(chan *list.Element)
go func() {
slist.RLock()
for e := slist.queue.Front(); e != nil; e = e.Next() {
ch <- e
}
slist.RUnlock()
close(ch)
}()
return ch
}
// KeyedDeque represents a keyed deque.
type keyedDeque struct {
*sync.RWMutex
*syncedList
index map[interface{}]*list.Element
invertedIndex map[*list.Element]interface{}
}
// newKeyedDeque returns a newKeyedDeque pointer.
func newKeyedDeque() *keyedDeque {
return &keyedDeque{
RWMutex: &sync.RWMutex{},
syncedList: newSyncedList(),
index: make(map[interface{}]*list.Element),
invertedIndex: make(map[*list.Element]interface{}),
}
}
// Push pushs a keyed-value to the end of deque.
func (deque *keyedDeque) Push(key interface{}, val interface{}) {
deque.Lock()
defer deque.Unlock()
if e, ok := deque.index[key]; ok {
deque.syncedList.Remove(e)
}
deque.index[key] = deque.syncedList.PushBack(val)
deque.invertedIndex[deque.index[key]] = key
}
// Get returns the keyed value.
func (deque *keyedDeque) Get(key interface{}) (*list.Element, bool) {
deque.RLock()
defer deque.RUnlock()
v, ok := deque.index[key]
return v, ok
}
// Has returns whether key already exists.
func (deque *keyedDeque) HasKey(key interface{}) bool {
_, ok := deque.Get(key)
return ok
}
// Delete deletes a value named key.
func (deque *keyedDeque) Delete(key interface{}) (v interface{}) {
deque.RLock()
e, ok := deque.index[key]
deque.RUnlock()
deque.Lock()
defer deque.Unlock()
if ok {
v = deque.syncedList.Remove(e)
delete(deque.index, key)
delete(deque.invertedIndex, e)
}
return
}
// Removes overwrites list.List.Remove.
func (deque *keyedDeque) Remove(e *list.Element) (v interface{}) {
deque.RLock()
key, ok := deque.invertedIndex[e]
deque.RUnlock()
if ok {
v = deque.Delete(key)
}
return
}
// Clear resets the deque.
func (deque *keyedDeque) Clear() {
deque.Lock()
defer deque.Unlock()
deque.syncedList.Clear()
deque.index = make(map[interface{}]*list.Element)
deque.invertedIndex = make(map[*list.Element]interface{})
}

View file

@ -1,196 +0,0 @@
package dht
import (
"sync"
"testing"
)
func TestSyncedMap(t *testing.T) {
cases := []mapItem{
{"a", 0},
{"b", 1},
{"c", 2},
}
sm := newSyncedMap()
set := func() {
group := sync.WaitGroup{}
for _, item := range cases {
group.Add(1)
go func(item mapItem) {
sm.Set(item.key, item.val)
group.Done()
}(item)
}
group.Wait()
}
isEmpty := func() {
if sm.Len() != 0 {
t.Fail()
}
}
// Set
set()
if sm.Len() != len(cases) {
t.Fail()
}
Loop:
// Iter
for item := range sm.Iter() {
for _, c := range cases {
if item.key == c.key && item.val == c.val {
continue Loop
}
}
t.Fail()
}
// Get, Delete, Has
for _, item := range cases {
val, ok := sm.Get(item.key)
if !ok || val != item.val {
t.Fail()
}
sm.Delete(item.key)
if sm.Has(item.key) {
t.Fail()
}
}
isEmpty()
// DeleteMulti
set()
sm.DeleteMulti([]interface{}{"a", "b", "c"})
isEmpty()
// Clear
set()
sm.Clear()
isEmpty()
}
func TestSyncedList(t *testing.T) {
sl := newSyncedList()
insert := func() {
for i := 0; i < 10; i++ {
sl.PushBack(i)
}
}
isEmpty := func() {
if sl.Len() != 0 {
t.Fail()
}
}
// PushBack
insert()
// Len
if sl.Len() != 10 {
t.Fail()
}
// Iter
i := 0
for item := range sl.Iter() {
if item.Value.(int) != i {
t.Fail()
}
i++
}
// Front
if sl.Front().Value.(int) != 0 {
t.Fail()
}
// Back
if sl.Back().Value.(int) != 9 {
t.Fail()
}
// Remove
for i := 0; i < 10; i++ {
if sl.Remove(sl.Front()).(int) != i {
t.Fail()
}
}
isEmpty()
// Clear
insert()
sl.Clear()
isEmpty()
}
func TestKeyedDeque(t *testing.T) {
cases := []mapItem{
{"a", 0},
{"b", 1},
{"c", 2},
}
deque := newKeyedDeque()
insert := func() {
for _, item := range cases {
deque.Push(item.key, item.val)
}
}
isEmpty := func() {
if deque.Len() != 0 {
t.Fail()
}
}
// Push
insert()
// Len
if deque.Len() != 3 {
t.Fail()
}
// Iter
i := 0
for e := range deque.Iter() {
if e.Value.(int) != i {
t.Fail()
}
i++
}
// HasKey, Get, Delete
for _, item := range cases {
if !deque.HasKey(item.key) {
t.Fail()
}
e, ok := deque.Get(item.key)
if !ok || e.Value.(int) != item.val {
t.Fail()
}
if deque.Delete(item.key) != item.val {
t.Fail()
}
if deque.HasKey(item.key) {
t.Fail()
}
}
isEmpty()
// Clear
insert()
deque.Clear()
isEmpty()
}

View file

@ -1,228 +0,0 @@
// Package dht implements the bittorrent dht protocol. For more information
// see http://www.bittorrent.org/beps/bep_0005.html.
package dht
import (
"encoding/hex"
"errors"
log "github.com/sirupsen/logrus"
"math"
"net"
"time"
)
// Config represents the configure of dht.
type Config struct {
// in mainline dht, k = 8
K int
// candidates are udp, udp4, udp6
Network string
// format is `ip:port`
Address string
// the prime nodes through which we can join in dht network
PrimeNodes []string
// the kbucket expired duration
KBucketExpiredAfter time.Duration
// the node expired duration
NodeExpriedAfter time.Duration
// how long it checks whether the bucket is expired
CheckKBucketPeriod time.Duration
// peer token expired duration
TokenExpiredAfter time.Duration
// the max transaction id
MaxTransactionCursor uint64
// how many nodes routing table can hold
MaxNodes int
// callback when got get_peers request
OnGetPeers func(string, string, int)
// callback when got announce_peer request
OnAnnouncePeer func(string, string, int)
// the times it tries when send fails
Try int
// the size of packet need to be dealt with
PacketJobLimit int
// the size of packet handler
PacketWorkerLimit int
// the nodes num to be fresh in a kbucket
RefreshNodeNum int
}
// NewStandardConfig returns a Config pointer with default values.
func NewStandardConfig() *Config {
return &Config{
K: 8,
Network: "udp4",
Address: ":4444",
PrimeNodes: []string{
"lbrynet1.lbry.io:4444",
"lbrynet2.lbry.io:4444",
"lbrynet3.lbry.io:4444",
},
NodeExpriedAfter: time.Duration(time.Minute * 15),
KBucketExpiredAfter: time.Duration(time.Minute * 15),
CheckKBucketPeriod: time.Duration(time.Second * 30),
TokenExpiredAfter: time.Duration(time.Minute * 10),
MaxTransactionCursor: math.MaxUint32,
MaxNodes: 5000,
Try: 2,
PacketJobLimit: 1024,
PacketWorkerLimit: 256,
RefreshNodeNum: 8,
}
}
// DHT represents a DHT node.
type DHT struct {
*Config
node *node
conn *net.UDPConn
routingTable *routingTable
transactionManager *transactionManager
peersManager *peersManager
tokenManager *tokenManager
Ready bool
packets chan packet
workerTokens chan struct{}
}
// New returns a DHT pointer. If config is nil, then config will be set to
// the default config.
func New(config *Config) *DHT {
if config == nil {
config = NewStandardConfig()
}
node, err := newNode(randomString(nodeIDLength), config.Network, config.Address)
if err != nil {
panic(err)
}
d := &DHT{
Config: config,
node: node,
packets: make(chan packet, config.PacketJobLimit),
workerTokens: make(chan struct{}, config.PacketWorkerLimit),
}
return d
}
// init initializes global variables.
func (dht *DHT) init() {
log.Info("Initializing DHT on " + dht.Address)
log.Infof("Node ID is %s", dht.node.HexID())
listener, err := net.ListenPacket(dht.Network, dht.Address)
if err != nil {
panic(err)
}
dht.conn = listener.(*net.UDPConn)
dht.routingTable = newRoutingTable(dht.K, dht)
dht.peersManager = newPeersManager(dht)
dht.tokenManager = newTokenManager(dht.TokenExpiredAfter, dht)
dht.transactionManager = newTransactionManager(dht.MaxTransactionCursor, dht)
go dht.transactionManager.run()
go dht.tokenManager.clear()
}
// join makes current node join the dht network.
func (dht *DHT) join() {
for _, addr := range dht.PrimeNodes {
raddr, err := net.ResolveUDPAddr(dht.Network, addr)
if err != nil {
continue
}
// NOTE: Temporary node has NO node id.
dht.transactionManager.findNode(
&node{addr: raddr},
dht.node.id.RawString(),
)
}
}
// listen receives message from udp.
func (dht *DHT) listen() {
go func() {
buff := make([]byte, 8192)
for {
n, raddr, err := dht.conn.ReadFromUDP(buff)
if err != nil {
continue
}
dht.packets <- packet{buff[:n], raddr}
}
}()
}
// FindNode returns peers who have announced having key.
func (dht *DHT) FindNode(key string) ([]*Peer, error) {
if !dht.Ready {
return nil, errors.New("dht not ready")
}
if len(key) == nodeIDLength*2 {
data, err := hex.DecodeString(key)
if err != nil {
return nil, err
}
key = string(data)
}
peers := dht.peersManager.GetPeers(key, dht.K)
if len(peers) != 0 {
return peers, nil
}
ch := make(chan struct{})
go func() {
neighbors := dht.routingTable.GetNeighbors(newBitmapFromString(key), dht.K)
for _, no := range neighbors {
dht.transactionManager.findNode(no, key)
}
i := 0
for range time.Tick(time.Second * 1) {
i++
peers = dht.peersManager.GetPeers(key, dht.K)
if len(peers) != 0 || i >= 30 {
break
}
}
ch <- struct{}{}
}()
<-ch
return peers, nil
}
// Run starts the dht.
func (dht *DHT) Run() {
dht.init()
dht.listen()
dht.join()
dht.Ready = true
log.Info("DHT ready")
var pkt packet
tick := time.Tick(dht.CheckKBucketPeriod)
for {
select {
case pkt = <-dht.packets:
handle(dht, pkt)
case <-tick:
if dht.routingTable.Len() == 0 {
dht.join()
} else if dht.transactionManager.len() == 0 {
go dht.routingTable.Fresh()
}
}
}
}

View file

@ -1,655 +0,0 @@
package dht
import (
"fmt"
"github.com/davecgh/go-spew/spew"
log "github.com/sirupsen/logrus"
"github.com/spf13/cast"
"github.com/zeebo/bencode"
"net"
"reflect"
"strings"
"sync"
"time"
)
const (
pingMethod = "ping"
storeMethod = "store"
findNodeMethod = "findNode"
findValueMethod = "findValue"
)
const (
generalError = 201 + iota
serverError
protocolError
unknownError
)
const (
requestType = 0
responseType = 1
errorType = 2
)
const (
// these are strings because bencode requires bytestring keys
headerTypeField = "0"
headerMessageIDField = "1"
headerNodeIDField = "2"
headerPayloadField = "3"
headerArgsField = "4"
)
type Message interface {
GetID() string
Encode() ([]byte, error)
}
type Request struct {
ID string
NodeID string
Method string
Args []string
}
func (r Request) GetID() string { return r.ID }
func (r Request) Encode() ([]byte, error) {
return bencode.EncodeBytes(map[string]interface{}{
headerTypeField: requestType,
headerMessageIDField: r.ID,
headerNodeIDField: r.NodeID,
headerPayloadField: r.Method,
headerArgsField: r.Args,
})
}
type findNodeDatum struct {
ID string
IP string
Port int
}
type Response struct {
ID string
NodeID string
Data string
FindNodeData []findNodeDatum
}
func (r Response) GetID() string { return r.ID }
func (r Response) Encode() ([]byte, error) {
data := map[string]interface{}{
headerTypeField: responseType,
headerMessageIDField: r.ID,
headerNodeIDField: r.NodeID,
}
if r.Data != "" {
data[headerPayloadField] = r.Data
} else {
var nodes []interface{}
for _, n := range r.FindNodeData {
nodes = append(nodes, []interface{}{n.ID, n.IP, n.Port})
}
data[headerPayloadField] = nodes
}
log.Info("Response data is ")
spew.Dump(data)
return bencode.EncodeBytes(data)
}
type Error struct {
ID string
NodeID string
Response []string
ExceptionType string
}
func (e Error) GetID() string { return e.ID }
func (e Error) Encode() ([]byte, error) {
return bencode.EncodeBytes(map[string]interface{}{
headerTypeField: errorType,
headerMessageIDField: e.ID,
headerNodeIDField: e.NodeID,
headerPayloadField: e.ExceptionType,
headerArgsField: e.Response,
})
}
// packet represents the information receive from udp.
type packet struct {
data []byte
raddr *net.UDPAddr
}
// token represents the token when response getPeers request.
type token struct {
data string
createTime time.Time
}
// tokenManager managers the tokens.
type tokenManager struct {
*syncedMap
expiredAfter time.Duration
dht *DHT
}
// newTokenManager returns a new tokenManager.
func newTokenManager(expiredAfter time.Duration, dht *DHT) *tokenManager {
return &tokenManager{
syncedMap: newSyncedMap(),
expiredAfter: expiredAfter,
dht: dht,
}
}
// token returns a token. If it doesn't exist or is expired, it will add a
// new token.
func (tm *tokenManager) token(addr *net.UDPAddr) string {
v, ok := tm.Get(addr.IP.String())
tk, _ := v.(token)
if !ok || time.Now().Sub(tk.createTime) > tm.expiredAfter {
tk = token{
data: randomString(5),
createTime: time.Now(),
}
tm.Set(addr.IP.String(), tk)
}
return tk.data
}
// clear removes expired tokens.
func (tm *tokenManager) clear() {
for range time.Tick(time.Minute * 3) {
keys := make([]interface{}, 0, 100)
for item := range tm.Iter() {
if time.Now().Sub(item.val.(token).createTime) > tm.expiredAfter {
keys = append(keys, item.key)
}
}
tm.DeleteMulti(keys)
}
}
// check returns whether the token is valid.
func (tm *tokenManager) check(addr *net.UDPAddr, tokenString string) bool {
key := addr.IP.String()
v, ok := tm.Get(key)
tk, _ := v.(token)
if ok {
tm.Delete(key)
}
return ok && tokenString == tk.data
}
// send sends data to the udp.
func send(dht *DHT, addr *net.UDPAddr, data Message) error {
log.Infof("Sending %s", spew.Sdump(data))
encoded, err := data.Encode()
if err != nil {
return err
}
log.Infof("Encoded: %s", string(encoded))
dht.conn.SetWriteDeadline(time.Now().Add(time.Second * 15))
_, err = dht.conn.WriteToUDP(encoded, addr)
return err
}
// query represents the query data included queried node and query-formed data.
type query struct {
node *node
request Request
}
// transaction implements transaction.
type transaction struct {
*query
id string
response chan struct{}
}
// transactionManager represents the manager of transactions.
type transactionManager struct {
*sync.RWMutex
transactions *syncedMap
index *syncedMap
cursor uint64
maxCursor uint64
queryChan chan *query
dht *DHT
}
// newTransactionManager returns new transactionManager pointer.
func newTransactionManager(maxCursor uint64, dht *DHT) *transactionManager {
return &transactionManager{
RWMutex: &sync.RWMutex{},
transactions: newSyncedMap(),
index: newSyncedMap(),
maxCursor: maxCursor,
queryChan: make(chan *query, 1024),
dht: dht,
}
}
// genTransID generates a transaction id and returns it.
func (tm *transactionManager) genTransID() string {
tm.Lock()
defer tm.Unlock()
tm.cursor = (tm.cursor + 1) % tm.maxCursor
return string(int2bytes(tm.cursor))
}
// newTransaction creates a new transaction.
func (tm *transactionManager) newTransaction(id string, q *query) *transaction {
return &transaction{
id: id,
query: q,
response: make(chan struct{}, tm.dht.Try+1),
}
}
// genIndexKey generates an indexed key which consists of queryType and
// address.
func (tm *transactionManager) genIndexKey(queryType, address string) string {
return strings.Join([]string{queryType, address}, ":")
}
// genIndexKeyByTrans generates an indexed key by a transaction.
func (tm *transactionManager) genIndexKeyByTrans(trans *transaction) string {
return tm.genIndexKey(trans.request.Method, trans.node.addr.String())
}
// insert adds a transaction to transactionManager.
func (tm *transactionManager) insert(trans *transaction) {
tm.Lock()
defer tm.Unlock()
tm.transactions.Set(trans.id, trans)
tm.index.Set(tm.genIndexKeyByTrans(trans), trans)
}
// delete removes a transaction from transactionManager.
func (tm *transactionManager) delete(transID string) {
v, ok := tm.transactions.Get(transID)
if !ok {
return
}
tm.Lock()
defer tm.Unlock()
trans := v.(*transaction)
tm.transactions.Delete(trans.id)
tm.index.Delete(tm.genIndexKeyByTrans(trans))
}
// len returns how many transactions are requesting now.
func (tm *transactionManager) len() int {
return tm.transactions.Len()
}
// transaction returns a transaction. keyType should be one of 0, 1 which
// represents transId and index each.
func (tm *transactionManager) transaction(key string, keyType int) *transaction {
sm := tm.transactions
if keyType == 1 {
sm = tm.index
}
v, ok := sm.Get(key)
if !ok {
return nil
}
return v.(*transaction)
}
// getByTransID returns a transaction by transID.
func (tm *transactionManager) getByTransID(transID string) *transaction {
return tm.transaction(transID, 0)
}
// getByIndex returns a transaction by indexed key.
func (tm *transactionManager) getByIndex(index string) *transaction {
return tm.transaction(index, 1)
}
// transaction gets the proper transaction with whose id is transId and
// address is addr.
func (tm *transactionManager) filterOne(transID string, addr *net.UDPAddr) *transaction {
trans := tm.getByTransID(transID)
if trans == nil || trans.node.addr.String() != addr.String() {
return nil
}
return trans
}
// query sends the query-formed data to udp and wait for the response.
// When timeout, it will retry `try - 1` times, which means it will query
// `try` times totally.
func (tm *transactionManager) query(q *query, try int) {
trans := tm.newTransaction(q.request.ID, q)
tm.insert(trans)
defer tm.delete(trans.id)
success := false
for i := 0; i < try; i++ {
if err := send(tm.dht, q.node.addr, q.request); err != nil {
break
}
select {
case <-trans.response:
success = true
break
case <-time.After(time.Second * 15):
}
}
if !success && q.node.id != nil {
tm.dht.routingTable.RemoveByAddr(q.node.addr.String())
}
}
// run starts to listen and consume the query chan.
func (tm *transactionManager) run() {
var q *query
for {
select {
case q = <-tm.queryChan:
go tm.query(q, tm.dht.Try)
}
}
}
// sendQuery send query-formed data to the chan.
func (tm *transactionManager) sendQuery(no *node, request Request) {
// If the target is self, then stop.
if no.id != nil && no.id.RawString() == tm.dht.node.id.RawString() ||
tm.getByIndex(tm.genIndexKey(request.Method, no.addr.String())) != nil {
return
}
request.ID = tm.genTransID()
request.NodeID = tm.dht.node.id.RawString()
tm.queryChan <- &query{node: no, request: request}
}
// ping sends ping query to the chan.
func (tm *transactionManager) ping(no *node) {
tm.sendQuery(no, Request{Method: pingMethod})
}
// findNode sends find_node query to the chan.
func (tm *transactionManager) findNode(no *node, target string) {
tm.sendQuery(no, Request{Method: findNodeMethod, Args: []string{target}})
}
// handle handles packets received from udp.
func handle(dht *DHT, pkt packet) {
log.Infof("Received message from %s: %s", pkt.raddr.IP.String(), string(pkt.data))
if len(dht.workerTokens) == dht.PacketWorkerLimit {
return
}
dht.workerTokens <- struct{}{}
go func() {
defer func() {
<-dht.workerTokens
}()
var data map[string]interface{}
err := bencode.DecodeBytes(pkt.data, &data)
if err != nil {
log.Errorf("Error decoding data: %s\n%s", err, pkt.data)
return
}
msgType, ok := data[headerTypeField]
if !ok {
log.Errorf("Decoded data has no message type: %s", data)
return
}
switch msgType.(int64) {
case requestType:
request := Request{
ID: data[headerMessageIDField].(string),
NodeID: data[headerNodeIDField].(string),
Method: data[headerPayloadField].(string),
Args: getArgs(data[headerArgsField]),
}
spew.Dump(request)
handleRequest(dht, pkt.raddr, request)
case responseType:
response := Response{
ID: data[headerMessageIDField].(string),
NodeID: data[headerNodeIDField].(string),
}
if reflect.TypeOf(data[headerPayloadField]).Kind() == reflect.String {
response.Data = data[headerPayloadField].(string)
} else {
response.FindNodeData = getFindNodeResponse(data[headerPayloadField])
}
spew.Dump(response)
handleResponse(dht, pkt.raddr, response)
case errorType:
e := Error{
ID: data[headerMessageIDField].(string),
NodeID: data[headerNodeIDField].(string),
ExceptionType: data[headerPayloadField].(string),
Response: getArgs(data[headerArgsField]),
}
handleError(dht, pkt.raddr, e)
default:
log.Errorf("Invalid message type: %s", msgType)
return
}
}()
}
func getFindNodeResponse(i interface{}) (data []findNodeDatum) {
if reflect.TypeOf(i).Kind() != reflect.Slice {
return
}
v := reflect.ValueOf(i)
for i := 0; i < v.Len(); i++ {
if v.Index(i).Kind() != reflect.Interface {
continue
}
contact := v.Index(i).Elem()
if contact.Type().Kind() != reflect.Slice || contact.Len() != 3 {
continue
}
if contact.Index(0).Elem().Kind() != reflect.String ||
contact.Index(1).Elem().Kind() != reflect.String ||
!(contact.Index(2).Elem().Kind() == reflect.Int64 ||
contact.Index(2).Elem().Kind() == reflect.Int) {
continue
}
data = append(data, findNodeDatum{
ID: contact.Index(0).Elem().String(),
IP: contact.Index(1).Elem().String(),
Port: int(contact.Index(2).Elem().Int()),
})
}
return
}
func getArgs(argsInt interface{}) (args []string) {
if reflect.TypeOf(argsInt).Kind() == reflect.Slice {
v := reflect.ValueOf(argsInt)
for i := 0; i < v.Len(); i++ {
args = append(args, cast.ToString(v.Index(i).Interface()))
}
}
return
}
// handleRequest handles the requests received from udp.
func handleRequest(dht *DHT, addr *net.UDPAddr, request Request) (success bool) {
if request.NodeID == dht.node.id.RawString() {
return
}
if len(request.NodeID) != nodeIDLength {
send(dht, addr, Error{ID: request.ID, NodeID: dht.node.id.RawString(), Response: []string{"Invalid ID"}})
return
}
if no, ok := dht.routingTable.GetNodeByAddress(addr.String()); ok && no.id.RawString() != request.NodeID {
dht.routingTable.RemoveByAddr(addr.String())
send(dht, addr, Error{ID: request.ID, NodeID: dht.node.id.RawString(), Response: []string{"Invalid ID"}})
return
}
switch request.Method {
case pingMethod:
send(dht, addr, Response{ID: request.ID, NodeID: dht.node.id.RawString(), Data: "pong"})
case findNodeMethod:
if len(request.Args) < 1 {
send(dht, addr, Error{ID: request.ID, NodeID: dht.node.id.RawString(), Response: []string{"No target"}})
return
}
target := request.Args[0]
if len(target) != nodeIDLength {
send(dht, addr, Error{ID: request.ID, NodeID: dht.node.id.RawString(), Response: []string{"Invalid target"}})
return
}
nodes := []findNodeDatum{}
targetID := newBitmapFromString(target)
no, _ := dht.routingTable.GetNodeKBucktByID(targetID)
if no != nil {
nodes = []findNodeDatum{{ID: no.id.RawString(), IP: no.addr.IP.String(), Port: no.addr.Port}}
} else {
neighbors := dht.routingTable.GetNeighbors(targetID, dht.K)
for _, n := range neighbors {
nodes = append(nodes, findNodeDatum{ID: n.id.RawString(), IP: n.addr.IP.String(), Port: n.addr.Port})
}
}
send(dht, addr, Response{ID: request.ID, NodeID: dht.node.id.RawString(), FindNodeData: nodes})
default:
// send(dht, addr, makeError(t, protocolError, "invalid q"))
return
}
no, _ := newNode(request.NodeID, addr.Network(), addr.String())
dht.routingTable.Insert(no)
return true
}
// findOn puts nodes in the response to the routingTable, then if target is in
// the nodes or all nodes are in the routingTable, it stops. Otherwise it
// continues to findNode or getPeers.
func findOn(dht *DHT, nodes []findNodeDatum, target *bitmap, queryType string) error {
hasNew, found := false, false
for _, n := range nodes {
no, err := newNode(n.ID, dht.Network, fmt.Sprintf("%s:%d", n.IP, n.Port))
if err != nil {
return err
}
if no.id.RawString() == target.RawString() {
found = true
}
if dht.routingTable.Insert(no) {
hasNew = true
}
}
if found || !hasNew {
return nil
}
targetID := target.RawString()
for _, no := range dht.routingTable.GetNeighbors(target, dht.K) {
switch queryType {
case findNodeMethod:
dht.transactionManager.findNode(no, targetID)
default:
panic("invalid find type")
}
}
return nil
}
// handleResponse handles responses received from udp.
func handleResponse(dht *DHT, addr *net.UDPAddr, response Response) (success bool) {
trans := dht.transactionManager.filterOne(response.ID, addr)
if trans == nil {
return
}
// If response's node id is not the same with the node id in the
// transaction, raise error.
// TODO: is this necessary??? why??
if trans.node.id != nil && trans.node.id.RawString() != response.NodeID {
dht.routingTable.RemoveByAddr(addr.String())
return
}
node, err := newNode(response.NodeID, addr.Network(), addr.String())
if err != nil {
return
}
switch trans.request.Method {
case pingMethod:
case findNodeMethod:
target := trans.request.Args[0]
if findOn(dht, response.FindNodeData, newBitmapFromString(target), findNodeMethod) != nil {
return
}
default:
return
}
// inform transManager to delete transaction.
trans.response <- struct{}{}
dht.routingTable.Insert(node)
return true
}
// handleError handles errors received from udp.
func handleError(dht *DHT, addr *net.UDPAddr, e Error) (success bool) {
if trans := dht.transactionManager.filterOne(e.ID, addr); trans != nil {
trans.response <- struct{}{}
}
return true
}

View file

@ -1,39 +0,0 @@
package dht
import (
"math/rand"
"strconv"
"testing"
"time"
)
func TestDHT(t *testing.T) {
rand.Seed(time.Now().UnixNano())
port := 49449 // + (rand.Int() % 10)
config := NewStandardConfig()
config.Address = "127.0.0.1:" + strconv.Itoa(port)
config.PrimeNodes = []string{
"127.0.0.1:10001",
}
d := New(config)
t.Log("Starting...")
go d.Run()
time.Sleep(2 * time.Second)
for {
peers, err := d.FindNode("012b66fc7052d9a0c8cb563b8ede7662003ba65f425c2661b5c6919d445deeb31469be8b842d6faeea3f2b3ebcaec845")
if err != nil {
time.Sleep(time.Second * 1)
continue
}
t.Log("Found peers:", peers)
break
}
t.Error("failed")
}

View file

@ -1,597 +0,0 @@
package dht
import (
"container/heap"
"encoding/hex"
"fmt"
log "github.com/sirupsen/logrus"
"net"
"strings"
"sync"
"time"
)
// maxPrefixLength is the length of DHT node.
const maxPrefixLength = 160
const nodeIDLength = 48
const compactNodeInfoLength = nodeIDLength + 6
// node represents a DHT node.
type node struct {
id *bitmap
addr *net.UDPAddr
lastActiveTime time.Time
}
// newNode returns a node pointer.
func newNode(id, network, address string) (*node, error) {
if len(id) != nodeIDLength {
return nil, fmt.Errorf("node id should be a %d-length string", nodeIDLength)
}
addr, err := net.ResolveUDPAddr(network, address)
if err != nil {
return nil, err
}
return &node{newBitmapFromString(id), addr, time.Now()}, nil
}
// newNodeFromCompactInfo parses compactNodeInfo and returns a node pointer.
func newNodeFromCompactInfo(compactNodeInfo string, network string) (*node, error) {
if len(compactNodeInfo) != compactNodeInfoLength {
return nil, fmt.Errorf("compactNodeInfo should be a %d-length string", compactNodeInfoLength)
}
id := compactNodeInfo[:nodeIDLength]
ip, port, _ := decodeCompactIPPortInfo(compactNodeInfo[nodeIDLength:])
return newNode(id, network, genAddress(ip.String(), port))
}
// CompactIPPortInfo returns "Compact IP-address/port info".
// See http://www.bittorrent.org/beps/bep_0005.html.
func (node *node) CompactIPPortInfo() string {
info, _ := encodeCompactIPPortInfo(node.addr.IP, node.addr.Port)
return info
}
// CompactNodeInfo returns "Compact node info".
// See http://www.bittorrent.org/beps/bep_0005.html.
func (node *node) CompactNodeInfo() string {
return strings.Join([]string{
node.id.RawString(), node.CompactIPPortInfo(),
}, "")
}
func (node *node) HexID() string {
if node.id == nil {
return ""
}
return hex.EncodeToString([]byte(node.id.RawString()))
}
// Peer represents a peer contact.
type Peer struct {
IP net.IP
Port int
token string
}
// newPeer returns a new peer pointer.
func newPeer(ip net.IP, port int, token string) *Peer {
return &Peer{
IP: ip,
Port: port,
token: token,
}
}
// newPeerFromCompactIPPortInfo create a peer pointer by compact ip/port info.
func newPeerFromCompactIPPortInfo(compactInfo, token string) (*Peer, error) {
ip, port, err := decodeCompactIPPortInfo(compactInfo)
if err != nil {
return nil, err
}
return newPeer(ip, port, token), nil
}
// CompactIPPortInfo returns "Compact node info".
// See http://www.bittorrent.org/beps/bep_0005.html.
func (p *Peer) CompactIPPortInfo() string {
info, _ := encodeCompactIPPortInfo(p.IP, p.Port)
return info
}
// peersManager represents a proxy that manipulates peers.
type peersManager struct {
sync.RWMutex
table *syncedMap
dht *DHT
}
// newPeersManager returns a new peersManager.
func newPeersManager(dht *DHT) *peersManager {
return &peersManager{
table: newSyncedMap(),
dht: dht,
}
}
// Insert adds a peer into peersManager.
func (pm *peersManager) Insert(infoHash string, peer *Peer) {
pm.Lock()
if _, ok := pm.table.Get(infoHash); !ok {
pm.table.Set(infoHash, newKeyedDeque())
}
pm.Unlock()
v, _ := pm.table.Get(infoHash)
queue := v.(*keyedDeque)
queue.Push(peer.CompactIPPortInfo(), peer)
if queue.Len() > pm.dht.K {
queue.Remove(queue.Front())
}
}
// GetPeers returns size-length peers who announces having infoHash.
func (pm *peersManager) GetPeers(infoHash string, size int) []*Peer {
peers := make([]*Peer, 0, size)
v, ok := pm.table.Get(infoHash)
if !ok {
return peers
}
for e := range v.(*keyedDeque).Iter() {
peers = append(peers, e.Value.(*Peer))
}
if len(peers) > size {
peers = peers[len(peers)-size:]
}
return peers
}
// kbucket represents a k-size bucket.
type kbucket struct {
sync.RWMutex
nodes, candidates *keyedDeque
lastChanged time.Time
prefix *bitmap
}
// newKBucket returns a new kbucket pointer.
func newKBucket(prefix *bitmap) *kbucket {
bucket := &kbucket{
nodes: newKeyedDeque(),
candidates: newKeyedDeque(),
lastChanged: time.Now(),
prefix: prefix,
}
return bucket
}
// LastChanged return the last time when it changes.
func (bucket *kbucket) LastChanged() time.Time {
bucket.RLock()
defer bucket.RUnlock()
return bucket.lastChanged
}
// RandomChildID returns a random id that has the same prefix with bucket.
func (bucket *kbucket) RandomChildID() string {
prefixLen := bucket.prefix.Size / 8
return strings.Join([]string{
bucket.prefix.RawString()[:prefixLen],
randomString(nodeIDLength - prefixLen),
}, "")
}
// UpdateTimestamp update bucket's last changed time..
func (bucket *kbucket) UpdateTimestamp() {
bucket.Lock()
defer bucket.Unlock()
bucket.lastChanged = time.Now()
}
// Insert inserts node to the bucket. It returns whether the node is new in
// the bucket.
func (bucket *kbucket) Insert(no *node) bool {
isNew := !bucket.nodes.HasKey(no.id.RawString())
bucket.nodes.Push(no.id.RawString(), no)
bucket.UpdateTimestamp()
return isNew
}
// Replace removes node, then put bucket.candidates.Back() to the right
// place of bucket.nodes.
func (bucket *kbucket) Replace(no *node) {
bucket.nodes.Delete(no.id.RawString())
bucket.UpdateTimestamp()
if bucket.candidates.Len() == 0 {
return
}
no = bucket.candidates.Remove(bucket.candidates.Back()).(*node)
inserted := false
for e := range bucket.nodes.Iter() {
if e.Value.(*node).lastActiveTime.After(
no.lastActiveTime) && !inserted {
bucket.nodes.InsertBefore(no, e)
inserted = true
}
}
if !inserted {
bucket.nodes.PushBack(no)
}
}
// Fresh pings the expired nodes in the bucket.
func (bucket *kbucket) Fresh(dht *DHT) {
for e := range bucket.nodes.Iter() {
no := e.Value.(*node)
if time.Since(no.lastActiveTime) > dht.NodeExpriedAfter {
dht.transactionManager.ping(no)
}
}
}
// routingTableNode represents routing table tree node.
type routingTableNode struct {
sync.RWMutex
children []*routingTableNode
bucket *kbucket
}
// newRoutingTableNode returns a new routingTableNode pointer.
func newRoutingTableNode(prefix *bitmap) *routingTableNode {
return &routingTableNode{
children: make([]*routingTableNode, 2),
bucket: newKBucket(prefix),
}
}
// Child returns routingTableNode's left or right child.
func (tableNode *routingTableNode) Child(index int) *routingTableNode {
if index >= 2 {
return nil
}
tableNode.RLock()
defer tableNode.RUnlock()
return tableNode.children[index]
}
// SetChild sets routingTableNode's left or right child. When index is 0, it's
// the left child, if 1, it's the right child.
func (tableNode *routingTableNode) SetChild(index int, c *routingTableNode) {
tableNode.Lock()
defer tableNode.Unlock()
tableNode.children[index] = c
}
// KBucket returns the bucket routingTableNode holds.
func (tableNode *routingTableNode) KBucket() *kbucket {
tableNode.RLock()
defer tableNode.RUnlock()
return tableNode.bucket
}
// SetKBucket sets the bucket.
func (tableNode *routingTableNode) SetKBucket(bucket *kbucket) {
tableNode.Lock()
defer tableNode.Unlock()
tableNode.bucket = bucket
}
// Split splits current routingTableNode and sets it's two children.
func (tableNode *routingTableNode) Split() {
prefixLen := tableNode.KBucket().prefix.Size
if prefixLen == maxPrefixLength {
return
}
for i := 0; i < 2; i++ {
tableNode.SetChild(i, newRoutingTableNode(newBitmapFrom(
tableNode.KBucket().prefix, prefixLen+1)))
}
tableNode.Lock()
tableNode.children[1].bucket.prefix.Set(prefixLen)
tableNode.Unlock()
for e := range tableNode.KBucket().nodes.Iter() {
nd := e.Value.(*node)
tableNode.Child(nd.id.Bit(prefixLen)).KBucket().nodes.PushBack(nd)
}
for e := range tableNode.KBucket().candidates.Iter() {
nd := e.Value.(*node)
tableNode.Child(nd.id.Bit(prefixLen)).KBucket().candidates.PushBack(nd)
}
for i := 0; i < 2; i++ {
tableNode.Child(i).KBucket().UpdateTimestamp()
}
}
// routingTable implements the routing table in DHT protocol.
type routingTable struct {
*sync.RWMutex
k int
root *routingTableNode
cachedNodes *syncedMap
cachedKBuckets *keyedDeque
dht *DHT
clearQueue *syncedList
}
// newRoutingTable returns a new routingTable pointer.
func newRoutingTable(k int, dht *DHT) *routingTable {
root := newRoutingTableNode(newBitmap(0))
rt := &routingTable{
RWMutex: &sync.RWMutex{},
k: k,
root: root,
cachedNodes: newSyncedMap(),
cachedKBuckets: newKeyedDeque(),
dht: dht,
clearQueue: newSyncedList(),
}
rt.cachedKBuckets.Push(root.bucket.prefix.String(), root.bucket)
return rt
}
// Insert adds a node to routing table. It returns whether the node is new
// in the routingtable.
func (rt *routingTable) Insert(nd *node) bool {
rt.Lock()
defer rt.Unlock()
log.Infof("Adding node to routing table: %s (%s:%d)", nd.id.RawString(), nd.addr.IP, nd.addr.Port)
var (
next *routingTableNode
bucket *kbucket
)
root := rt.root
for prefixLen := 1; prefixLen <= maxPrefixLength; prefixLen++ {
next = root.Child(nd.id.Bit(prefixLen - 1))
if next != nil {
// If next is not the leaf.
root = next
} else if root.KBucket().nodes.Len() < rt.k ||
root.KBucket().nodes.HasKey(nd.id.RawString()) {
bucket = root.KBucket()
isNew := bucket.Insert(nd)
rt.cachedNodes.Set(nd.addr.String(), nd)
rt.cachedKBuckets.Push(bucket.prefix.String(), bucket)
return isNew
} else if root.KBucket().prefix.Compare(nd.id, prefixLen-1) == 0 {
// If node has the same prefix with bucket, split it.
root.Split()
rt.cachedKBuckets.Delete(root.KBucket().prefix.String())
root.SetKBucket(nil)
for i := 0; i < 2; i++ {
bucket = root.Child(i).KBucket()
rt.cachedKBuckets.Push(bucket.prefix.String(), bucket)
}
root = root.Child(nd.id.Bit(prefixLen - 1))
} else {
// Finally, store node as a candidate and fresh the bucket.
root.KBucket().candidates.PushBack(nd)
if root.KBucket().candidates.Len() > rt.k {
root.KBucket().candidates.Remove(
root.KBucket().candidates.Front())
}
go root.KBucket().Fresh(rt.dht)
return false
}
}
return false
}
// GetNeighbors returns the size-length nodes closest to id.
func (rt *routingTable) GetNeighbors(id *bitmap, size int) []*node {
rt.RLock()
nodes := make([]interface{}, 0, rt.cachedNodes.Len())
for item := range rt.cachedNodes.Iter() {
nodes = append(nodes, item.val.(*node))
}
rt.RUnlock()
neighbors := getTopK(nodes, id, size)
result := make([]*node, len(neighbors))
for i, nd := range neighbors {
result[i] = nd.(*node)
}
return result
}
// GetNeighborIds return the size-length compact node info closest to id.
func (rt *routingTable) GetNeighborCompactInfos(id *bitmap, size int) []string {
neighbors := rt.GetNeighbors(id, size)
infos := make([]string, len(neighbors))
for i, no := range neighbors {
infos[i] = no.CompactNodeInfo()
}
return infos
}
// GetNodeKBucktById returns node whose id is `id` and the bucket it
// belongs to.
func (rt *routingTable) GetNodeKBucktByID(id *bitmap) (
nd *node, bucket *kbucket) {
rt.RLock()
defer rt.RUnlock()
var next *routingTableNode
root := rt.root
for prefixLen := 1; prefixLen <= maxPrefixLength; prefixLen++ {
next = root.Child(id.Bit(prefixLen - 1))
if next == nil {
v, ok := root.KBucket().nodes.Get(id.RawString())
if !ok {
return
}
nd, bucket = v.Value.(*node), root.KBucket()
return
}
root = next
}
return
}
// GetNodeByAddress finds node by address.
func (rt *routingTable) GetNodeByAddress(address string) (no *node, ok bool) {
rt.RLock()
defer rt.RUnlock()
v, ok := rt.cachedNodes.Get(address)
if ok {
no = v.(*node)
}
return
}
// Remove deletes the node whose id is `id`.
func (rt *routingTable) Remove(id *bitmap) {
if nd, bucket := rt.GetNodeKBucktByID(id); nd != nil {
bucket.Replace(nd)
rt.cachedNodes.Delete(nd.addr.String())
rt.cachedKBuckets.Push(bucket.prefix.String(), bucket)
}
}
// Remove deletes the node whose address is `ip:port`.
func (rt *routingTable) RemoveByAddr(address string) {
v, ok := rt.cachedNodes.Get(address)
if ok {
rt.Remove(v.(*node).id)
}
}
// Fresh sends findNode to all nodes in the expired nodes.
func (rt *routingTable) Fresh() {
now := time.Now()
for e := range rt.cachedKBuckets.Iter() {
bucket := e.Value.(*kbucket)
if now.Sub(bucket.LastChanged()) < rt.dht.KBucketExpiredAfter ||
bucket.nodes.Len() == 0 {
continue
}
i := 0
for e := range bucket.nodes.Iter() {
if i < rt.dht.RefreshNodeNum {
no := e.Value.(*node)
rt.dht.transactionManager.findNode(no, bucket.RandomChildID())
rt.clearQueue.PushBack(no)
}
i++
}
}
rt.clearQueue.Clear()
}
// Len returns the number of nodes in table.
func (rt *routingTable) Len() int {
rt.RLock()
defer rt.RUnlock()
return rt.cachedNodes.Len()
}
// Implementation of heap with heap.Interface.
type heapItem struct {
distance *bitmap
value interface{}
}
type topKHeap []*heapItem
func (kHeap topKHeap) Len() int {
return len(kHeap)
}
func (kHeap topKHeap) Less(i, j int) bool {
return kHeap[i].distance.Compare(kHeap[j].distance, maxPrefixLength) == 1
}
func (kHeap topKHeap) Swap(i, j int) {
kHeap[i], kHeap[j] = kHeap[j], kHeap[i]
}
func (kHeap *topKHeap) Push(x interface{}) {
*kHeap = append(*kHeap, x.(*heapItem))
}
func (kHeap *topKHeap) Pop() interface{} {
n := len(*kHeap)
x := (*kHeap)[n-1]
*kHeap = (*kHeap)[:n-1]
return x
}
// getTopK solves the top-k problem with heap. It's time complexity is
// O(n*log(k)). When n is large, time complexity will be too high, need to be
// optimized.
func getTopK(queue []interface{}, id *bitmap, k int) []interface{} {
topkHeap := make(topKHeap, 0, k+1)
for _, value := range queue {
node := value.(*node)
item := &heapItem{
id.Xor(node.id),
value,
}
heap.Push(&topkHeap, item)
if topkHeap.Len() > k {
heap.Pop(&topkHeap)
}
}
tops := make([]interface{}, topkHeap.Len())
for i := len(tops) - 1; i >= 0; i-- {
tops[i] = heap.Pop(&topkHeap).(*heapItem).value
}
return tops
}

View file

@ -1,133 +0,0 @@
package dht
import (
"crypto/rand"
"errors"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"
"time"
)
// randomString generates a size-length string randomly.
func randomString(size int) string {
buff := make([]byte, size)
rand.Read(buff)
return string(buff)
}
// bytes2int returns the int value it represents.
func bytes2int(data []byte) uint64 {
n, val := len(data), uint64(0)
if n > 8 {
panic("data too long")
}
for i, b := range data {
val += uint64(b) << uint64((n-i-1)*8)
}
return val
}
// int2bytes returns the byte array it represents.
func int2bytes(val uint64) []byte {
data, j := make([]byte, 8), -1
for i := 0; i < 8; i++ {
shift := uint64((7 - i) * 8)
data[i] = byte((val & (0xff << shift)) >> shift)
if j == -1 && data[i] != 0 {
j = i
}
}
if j != -1 {
return data[j:]
}
return data[:1]
}
// decodeCompactIPPortInfo decodes compactIP-address/port info in BitTorrent
// DHT Protocol. It returns the ip and port number.
func decodeCompactIPPortInfo(info string) (ip net.IP, port int, err error) {
if len(info) != 6 {
err = errors.New("compact info should be 6-length long")
return
}
ip = net.IPv4(info[0], info[1], info[2], info[3])
port = int((uint16(info[4]) << 8) | uint16(info[5]))
return
}
// encodeCompactIPPortInfo encodes an ip and a port number to
// compactIP-address/port info.
func encodeCompactIPPortInfo(ip net.IP, port int) (info string, err error) {
if port > 65535 || port < 0 {
err = errors.New("port should be no greater than 65535 and no less than 0")
return
}
p := int2bytes(uint64(port))
if len(p) < 2 {
p = append(p, p[0])
p[0] = 0
}
info = string(append(ip, p...))
return
}
// getLocalIPs returns local ips.
func getLocalIPs() (ips []string) {
ips = make([]string, 0, 6)
addrs, err := net.InterfaceAddrs()
if err != nil {
return
}
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err != nil {
continue
}
ips = append(ips, ip.String())
}
return
}
// getRemoteIP returns the wlan ip.
func getRemoteIP() (ip string, err error) {
client := &http.Client{
Timeout: time.Second * 30,
}
req, err := http.NewRequest("GET", "http://ifconfig.me", nil)
if err != nil {
return
}
req.Header.Set("User-Agent", "curl")
res, err := client.Do(req)
if err != nil {
return
}
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return
}
ip = string(data)
return
}
// genAddress returns a ip:port address.
func genAddress(ip string, port int) string {
return strings.Join([]string{ip, strconv.Itoa(port)}, ":")
}

View file

@ -1,100 +0,0 @@
package dht
import (
"testing"
)
func TestInt2Bytes(t *testing.T) {
cases := []struct {
in uint64
out []byte
}{
{0, []byte{0}},
{1, []byte{1}},
{256, []byte{1, 0}},
{22129, []byte{86, 113}},
}
for _, c := range cases {
r := int2bytes(c.in)
if len(r) != len(c.out) {
t.Fail()
}
for i, v := range r {
if v != c.out[i] {
t.Fail()
}
}
}
}
func TestBytes2Int(t *testing.T) {
cases := []struct {
in []byte
out uint64
}{
{[]byte{0}, 0},
{[]byte{1}, 1},
{[]byte{1, 0}, 256},
{[]byte{86, 113}, 22129},
}
for _, c := range cases {
if bytes2int(c.in) != c.out {
t.Fail()
}
}
}
func TestDecodeCompactIPPortInfo(t *testing.T) {
cases := []struct {
in string
out struct {
ip string
port int
}
}{
{"123456", struct {
ip string
port int
}{"49.50.51.52", 13622}},
{"abcdef", struct {
ip string
port int
}{"97.98.99.100", 25958}},
}
for _, item := range cases {
ip, port, err := decodeCompactIPPortInfo(item.in)
if err != nil || ip.String() != item.out.ip || port != item.out.port {
t.Fail()
}
}
}
func TestEncodeCompactIPPortInfo(t *testing.T) {
cases := []struct {
in struct {
ip []byte
port int
}
out string
}{
{struct {
ip []byte
port int
}{[]byte{49, 50, 51, 52}, 13622}, "123456"},
{struct {
ip []byte
port int
}{[]byte{97, 98, 99, 100}, 25958}, "abcdef"},
}
for _, item := range cases {
info, err := encodeCompactIPPortInfo(item.in.ip, item.in.port)
if err != nil || info != item.out {
t.Fail()
}
}
}

View file

@ -6,9 +6,9 @@ import (
"reflect"
"strings"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/util"
"github.com/lbryio/lbry.go/validator"
"github.com/lbryio/lbry.go/extras/errors"
"github.com/lbryio/lbry.go/extras/util"
"github.com/lbryio/lbry.go/extras/validator"
v "github.com/lbryio/ozzo-validation"
"github.com/spf13/cast"

View file

@ -7,7 +7,7 @@ import (
"sort"
"strings"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
"github.com/btcsuite/btcutil/base58"
"golang.org/x/crypto/sha3"

View file

@ -10,7 +10,7 @@ import (
"strings"
"time"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
"github.com/mitchellh/mapstructure"
"github.com/shopspring/decimal"

View file

@ -4,7 +4,7 @@ import (
"encoding/json"
"reflect"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
lbryschema "github.com/lbryio/types/go"
"github.com/shopspring/decimal"

View file

@ -234,7 +234,7 @@ func TestConversions(t *testing.T) {
if bp, boolTest := ct.d.(*bool); boolTest && *bp != ct.wantbool && ct.wanterr == "" {
errf("want bool %v, got %v", ct.wantbool, *bp)
}
if !ct.wanttime.IsNull() && !ct.wanttime.Equal(getTimeValue(ct.d)) {
if !ct.wanttime.IsZero() && !ct.wanttime.Equal(getTimeValue(ct.d)) {
errf("want time %v, got %v", ct.wanttime, getTimeValue(ct.d))
}
if ct.wantnil && *ct.d.(**int64) != nil {

View file

@ -8,8 +8,8 @@ import (
"strings"
"time"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/null"
"github.com/lbryio/lbry.go/extras/errors"
"github.com/lbryio/lbry.go/extras/null"
)
func InterpolateParams(query string, args ...interface{}) (string, error) {

View file

@ -23,7 +23,7 @@ import (
"encoding/pem"
"net/http"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
)
func publicKey(isPrivateRepo bool) (*rsa.PublicKey, error) {

View file

@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
"github.com/nlopes/slack"
log "github.com/sirupsen/logrus"

50
go.mod Normal file
View file

@ -0,0 +1,50 @@
module github.com/lbryio/lbry.go
require (
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf // indirect
github.com/btcsuite/btcd v0.0.0-20180531025944-86fed781132a
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/btcsuite/btcutil v0.0.0-20180524032703-d4cc87b86016
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
github.com/davecgh/go-spew v1.1.0
github.com/go-errors/errors v1.0.1
github.com/go-ini/ini v1.38.2
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible // indirect
github.com/golang/protobuf v1.2.0
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2
github.com/gorilla/rpc v1.1.0
github.com/gorilla/websocket v1.2.0 // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c
github.com/lbryio/ozzo-validation v0.0.0-20170323141101-d1008ad1fd04
github.com/lbryio/types v0.0.0-20181001180206-594241d24e00
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5
github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675
github.com/nlopes/slack v0.2.0
github.com/onsi/gomega v1.4.3 // indirect
github.com/sebdah/goldie v0.0.0-20180424091453-8784dd1ab561
github.com/sergi/go-diff v1.0.0
github.com/shopspring/decimal v0.0.0-20180607144847-19e3cb6c2930
github.com/sirupsen/logrus v0.0.0-20180523074243-ea8897e79973
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/spf13/cast v1.2.0
github.com/stretchr/testify v1.3.0 // indirect
github.com/uber-go/atomic v1.3.2
github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d
go.uber.org/atomic v1.3.2 // indirect
golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c
google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f // indirect
google.golang.org/grpc v1.17.0
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
gopkg.in/ini.v1 v1.41.0 // indirect
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79
)

131
go.sum Normal file
View file

@ -0,0 +1,131 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/btcsuite/btcd v0.0.0-20180531025944-86fed781132a h1:fuEu3WaOzatbWFGlCa2e/TL/GR397Da8QSKNKrDRa3s=
github.com/btcsuite/btcd v0.0.0-20180531025944-86fed781132a/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20180524032703-d4cc87b86016 h1:BsZAJgCuMsoFZMZNyj7Lyt6sS8anDhedVrAMCOyPMIo=
github.com/btcsuite/btcutil v0.0.0-20180524032703-d4cc87b86016/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-ini/ini v1.38.2 h1:6Hl/z3p3iFkA0dlDfzYxuFuUGD+kaweypF6btsR2/Q4=
github.com/go-ini/ini v1.38.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible h1:sUy/in/P6askYr16XJgTKq/0SZhiWsdg4WZGaLsGQkM=
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/rpc v1.1.0 h1:marKfvVP0Gpd/jHlVBKCQ8RAoUPdX7K1Nuh6l1BNh7A=
github.com/gorilla/rpc v1.1.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c h1:BhdcWGsuKif/XoSZnqVGNqJ1iEmH0czWR5upj+AuR8M=
github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c/go.mod h1:muH7wpUqE8hRA3OrYYosw9+Sl681BF9cwcjzE+OCNK8=
github.com/lbryio/ozzo-validation v0.0.0-20170323141101-d1008ad1fd04 h1:Nze+C2HbeKvhjI/kVn+9Poj/UuEW5sOQxcsxqO7L3GI=
github.com/lbryio/ozzo-validation v0.0.0-20170323141101-d1008ad1fd04/go.mod h1:fbG/dzobG8r95KzMwckXiLMHfFjZaBRQqC9hPs2XAQ4=
github.com/lbryio/types v0.0.0-20181001180206-594241d24e00 h1:1qRpd8lcyVigX+kYkwQL13gpOURyytgvxZtuIQfPPX8=
github.com/lbryio/types v0.0.0-20181001180206-594241d24e00/go.mod h1:CG3wsDv5BiVYQd5i1Jp7wGsaVyjZTJshqXeWMVKsISE=
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU=
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0=
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 h1:iOAVXzZyXtW408TMYejlUPo6BIn92HmOacWtIfNyYns=
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5 h1:mG83tLXWSRdcXMWfkoumVwhcCbf3jHF9QKv/m37BkM0=
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5/go.mod h1:H0aPCWffGOaDcjkw1iB7W9DVLp6GXmfcJY/7YZCWPA4=
github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675 h1:/rdJjIiKG5rRdwG5yxHmSE/7ZREjpyC0kL7GxGT/qJw=
github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nlopes/slack v0.2.0 h1:ygNVH3HWrOPFbzFoAmRKPcMcmYMmsLf+vPV9DhJdqJI=
github.com/nlopes/slack v0.2.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sebdah/goldie v0.0.0-20180424091453-8784dd1ab561 h1:IY+sDBJR/wRtsxq+626xJnt4Tw7/ROA9cDIR8MMhWyg=
github.com/sebdah/goldie v0.0.0-20180424091453-8784dd1ab561/go.mod h1:lvjGftC8oe7XPtyrOidaMi0rp5B9+XY/ZRUynGnuaxQ=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v0.0.0-20180607144847-19e3cb6c2930 h1:pSgp2x9zCkCjb8rxXFNpGE8hDIrt+UXW7jUQ5fbTlzM=
github.com/shopspring/decimal v0.0.0-20180607144847-19e3cb6c2930/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/sirupsen/logrus v0.0.0-20180523074243-ea8897e79973 h1:3AJZYTzw3gm3TNTt30x0CCKD7GOn2sdd50Hn35fQkGY=
github.com/sirupsen/logrus v0.0.0-20180523074243-ea8897e79973/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d h1:tQo6hjclyv3RHUgZOl6iWb2Y44A/sN9bf9LAYfuioEg=
github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4 h1:wviDUSmtheHRBfoY8B9U8ELl2USoXi2YFwdGdpIIkzI=
golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f h1:FU37niK8AQ59mHcskRyQL7H0ErSeNh650vdcj8HqdSI=
google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/ini.v1 v1.41.0 h1:Ka3ViY6gNYSKiVy71zXBEqKplnV35ImDLVG+8uoIklE=
gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79 h1:FpCr9V8wuOei4BAen+93HtVJ+XSi+KPbaPKm0Vj5R64=
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79/go.mod h1:gWkaRU7CoXpezCBWfWjm3999QqS+1pYPXGbqQCTMzo8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View file

@ -3,7 +3,7 @@ package lbrycrd
import (
"encoding/hex"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"

View file

@ -5,7 +5,7 @@ import (
"os"
"strconv"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"

15
main.go
View file

@ -1,10 +1,12 @@
package main
import (
"encoding/hex"
"fmt"
"math/rand"
"time"
"github.com/lbryio/lbry.go/cmd"
"github.com/lbryio/lbry.go/claim"
log "github.com/sirupsen/logrus"
)
@ -14,5 +16,14 @@ var Version string
func main() {
rand.Seed(time.Now().UnixNano())
log.SetLevel(log.DebugLevel)
cmd.Execute()
value, err := hex.DecodeString("080110011ac10108011279080410011a0343617422002a00320d5075626c696320446f6d61696e38004a5568747470733a2f2f737065652e63682f373961653031353766356165356535336232326562646465666663326564623862363130666437372f68773978754137686d326b32645a35477479744a336448372e6a706752005a001a42080110011a30970015f05a30531c465a3f889ab516b972c57c529cd4b57b0bd1685a19c0caa8073f6d4f3db338c1034481fb3eb37241220a696d6167652f6a7065672a5c080110031a4088d15f554d64776f3b43bc63b50c16a69162eb256c9e7afe04505f88a36d7455069de25244834f6d14479b45064d4766fa359bd041886b612040c9dbc9d1d0ec221412bcd69bf6a7d503002f09d34c76f904253a4be2")
if err != nil {
panic(err)
}
s, err := claim.ToJSON(value)
if err != nil {
panic(err)
}
fmt.Println(s)
}

34
readme.md Normal file
View file

@ -0,0 +1,34 @@
# The LBRY Protocol in Go
lbry.go is a set of tools and projects implemented in Golang. See each subfolder for more details
[![Build Status](https://travis-ci.org/lbryio/lbry.go.svg?branch=master)](https://travis-ci.org/lbryio/lbry.go)
This project uses Go modules. Make sure you have Go 1.11+ installed.
- Ubuntu: https://launchpad.net/~longsleep/+archive/ubuntu/golang-backports or https://github.com/golang/go/wiki/Ubuntu
- OSX: `brew install go`
### Building
- clone the repository
- run `make` from the root directory to build the binary
## Contributing
Contributions to this project are welcome, encouraged, and compensated. For more details, see [lbry.io/faq/contributing](https://lbry.io/faq/contributing)
Make sure you `go fmt` your code before submitting PRs.
## License
This project is MIT licensed. For the full license, see [LICENSE](LICENSE).
## Security
We take security seriously. Please contact security@lbry.io regarding any issues you may encounter.
Our PGP key is [here](https://keybase.io/lbry/key.asc) if you need it.
## Contact
The primary contact for this project is [@lyoshenka](https://github.com/lyoshenka) (grin@lbry.io)

View file

@ -8,7 +8,7 @@ import (
"encoding/hex"
"strconv"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
)
const MaxBlobSize = 2097152 // 2mb, or 2 * 2^20

View file

@ -4,7 +4,7 @@ import (
"encoding/hex"
"encoding/json"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
)
// inspired by https://blog.gopheracademy.com/advent-2016/advanced-encoding-decoding/

View file

@ -5,7 +5,7 @@ import (
"math"
"strings"
"github.com/lbryio/lbry.go/errors"
"github.com/lbryio/lbry.go/extras/errors"
)
type Stream []Blob