move non-proto code into extras/, switch to go modules, drop old dht
This commit is contained in:
parent
f986bd3066
commit
a8bc4d4e36
91 changed files with 264 additions and 3383 deletions
17
.travis.yml
17
.travis.yml
|
@ -1,5 +1,18 @@
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: xenial
|
||||||
language: go
|
language: go
|
||||||
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
352
Gopkg.lock
generated
|
@ -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
|
|
47
Gopkg.toml
47
Gopkg.toml
|
@ -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"
|
|
10
Makefile
10
Makefile
|
@ -1,23 +1,17 @@
|
||||||
BINARY=lbry
|
BINARY=lbry
|
||||||
|
|
||||||
DIR = $(shell cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
DIR = $(shell cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||||
VENDOR_DIR = vendor
|
|
||||||
|
|
||||||
VERSION=$(shell git --git-dir=${DIR}/.git describe --dirty --always --long --abbrev=7)
|
VERSION=$(shell git --git-dir=${DIR}/.git describe --dirty --always --long --abbrev=7)
|
||||||
LDFLAGS = -ldflags "-X main.Version=${VERSION}"
|
LDFLAGS = -ldflags "-X main.Version=${VERSION}"
|
||||||
|
|
||||||
|
|
||||||
.PHONY: build dep clean
|
.PHONY: build clean
|
||||||
.DEFAULT_GOAL: build
|
.DEFAULT_GOAL: build
|
||||||
|
|
||||||
|
|
||||||
build: dep
|
build:
|
||||||
CGO_ENABLED=0 go build ${LDFLAGS} -asmflags -trimpath=${DIR} -o ${DIR}/${BINARY} main.go
|
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:
|
clean:
|
||||||
if [ -f ${DIR}/${BINARY} ]; then rm ${DIR}/${BINARY}; fi
|
if [ -f ${DIR}/${BINARY} ]; then rm ${DIR}/${BINARY}; fi
|
||||||
|
|
69
README.md
69
README.md
|
@ -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)
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
|
@ -3,9 +3,10 @@ package claim
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
|
types "github.com/lbryio/types/go"
|
||||||
|
|
||||||
"github.com/golang/protobuf/jsonpb"
|
"github.com/golang/protobuf/jsonpb"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
types "github.com/lbryio/types/go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ToJSON(value []byte) (string, error) {
|
func ToJSON(value []byte) (string, error) {
|
||||||
|
|
63
cmd/dht.go
63
cmd/dht.go
|
@ -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")
|
|
||||||
}
|
|
187
cmd/franklin.go
187
cmd/franklin.go
|
@ -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
|
|
||||||
}
|
|
26
cmd/root.go
26
cmd/root.go
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
31
cmd/test.go
31
cmd/test.go
|
@ -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
1
dht/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
.DS_Store
|
|
21
dht/LICENSE
21
dht/LICENSE
|
@ -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.
|
|
|
@ -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)
|
|
163
dht/bitmap.go
163
dht/bitmap.go
|
@ -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)
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
289
dht/container.go
289
dht/container.go
|
@ -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{})
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
228
dht/dht.go
228
dht/dht.go
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
655
dht/krpc.go
655
dht/krpc.go
|
@ -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
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
133
dht/util.go
133
dht/util.go
|
@ -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)}, ":")
|
|
||||||
}
|
|
100
dht/util_test.go
100
dht/util_test.go
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/util"
|
"github.com/lbryio/lbry.go/extras/util"
|
||||||
"github.com/lbryio/lbry.go/validator"
|
"github.com/lbryio/lbry.go/extras/validator"
|
||||||
v "github.com/lbryio/ozzo-validation"
|
v "github.com/lbryio/ozzo-validation"
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
|
|
||||||
"github.com/btcsuite/btcutil/base58"
|
"github.com/btcsuite/btcutil/base58"
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
lbryschema "github.com/lbryio/types/go"
|
lbryschema "github.com/lbryio/types/go"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
|
@ -234,7 +234,7 @@ func TestConversions(t *testing.T) {
|
||||||
if bp, boolTest := ct.d.(*bool); boolTest && *bp != ct.wantbool && ct.wanterr == "" {
|
if bp, boolTest := ct.d.(*bool); boolTest && *bp != ct.wantbool && ct.wanterr == "" {
|
||||||
errf("want bool %v, got %v", ct.wantbool, *bp)
|
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))
|
errf("want time %v, got %v", ct.wanttime, getTimeValue(ct.d))
|
||||||
}
|
}
|
||||||
if ct.wantnil && *ct.d.(**int64) != nil {
|
if ct.wantnil && *ct.d.(**int64) != nil {
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/null"
|
"github.com/lbryio/lbry.go/extras/null"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InterpolateParams(query string, args ...interface{}) (string, error) {
|
func InterpolateParams(query string, args ...interface{}) (string, error) {
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func publicKey(isPrivateRepo bool) (*rsa.PublicKey, error) {
|
func publicKey(isPrivateRepo bool) (*rsa.PublicKey, error) {
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
|
|
||||||
"github.com/nlopes/slack"
|
"github.com/nlopes/slack"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
50
go.mod
Normal file
50
go.mod
Normal 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
131
go.sum
Normal 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 |
|
@ -3,7 +3,7 @@ package lbrycrd
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
|
15
main.go
15
main.go
|
@ -1,10 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/cmd"
|
"github.com/lbryio/lbry.go/claim"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -14,5 +16,14 @@ var Version string
|
||||||
func main() {
|
func main() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
log.SetLevel(log.DebugLevel)
|
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
34
readme.md
Normal 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)
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const MaxBlobSize = 2097152 // 2mb, or 2 * 2^20
|
const MaxBlobSize = 2097152 // 2mb, or 2 * 2^20
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"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/
|
// inspired by https://blog.gopheracademy.com/advent-2016/advanced-encoding-decoding/
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/errors"
|
"github.com/lbryio/lbry.go/extras/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Stream []Blob
|
type Stream []Blob
|
||||||
|
|
Loading…
Reference in a new issue