Merge branch 'master' into swarm-network-rewrite

# Conflicts:
#	cmd/swarm/main.go
#	p2p/protocols/protocol.go
#	p2p/protocols/protocol_test.go
#	p2p/testing/protocolsession.go
#	p2p/testing/protocoltester.go
#	swarm/api/config.go
#	swarm/swarm.go
This commit is contained in:
Balint Gabor 2018-02-26 11:27:20 +01:00
commit cc22311bf9
22 changed files with 1035 additions and 311 deletions

View file

@ -686,8 +686,6 @@ func authTwitter(url string) (string, string, common.Address, error) {
if len(parts) < 4 || parts[len(parts)-2] != "status" { if len(parts) < 4 || parts[len(parts)-2] != "status" {
return "", "", common.Address{}, errors.New("Invalid Twitter status URL") return "", "", common.Address{}, errors.New("Invalid Twitter status URL")
} }
username := parts[len(parts)-3]
// Twitter's API isn't really friendly with direct links. Still, we don't // Twitter's API isn't really friendly with direct links. Still, we don't
// want to do ask read permissions from users, so just load the public posts and // want to do ask read permissions from users, so just load the public posts and
// scrape it for the Ethereum address and profile URL. // scrape it for the Ethereum address and profile URL.
@ -697,6 +695,13 @@ func authTwitter(url string) (string, string, common.Address, error) {
} }
defer res.Body.Close() defer res.Body.Close()
// Resolve the username from the final redirect, no intermediate junk
parts = strings.Split(res.Request.URL.String(), "/")
if len(parts) < 4 || parts[len(parts)-2] != "status" {
return "", "", common.Address{}, errors.New("Invalid Twitter status URL")
}
username := parts[len(parts)-3]
body, err := ioutil.ReadAll(res.Body) body, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
return "", "", common.Address{}, err return "", "", common.Address{}, err

View file

@ -23,6 +23,7 @@ import (
"os" "os"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"unicode" "unicode"
cli "gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
@ -102,10 +103,15 @@ func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
config = bzzapi.NewConfig() config = bzzapi.NewConfig()
//first load settings from config file (if provided) //first load settings from config file (if provided)
config, err = configFileOverride(config, ctx) config, err = configFileOverride(config, ctx)
if err != nil {
return nil, err
}
//override settings provided by environment variables //override settings provided by environment variables
config = envVarsOverride(config) config = envVarsOverride(config)
//override settings provided by command line //override settings provided by command line
config = cmdLineOverride(config, ctx) config = cmdLineOverride(config, ctx)
//validate configuration parameters
err = validateConfig(config)
return return
} }
@ -199,12 +205,16 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API) utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
} }
//EnsApi can be set to "", so can't check for empty string, as it is allowed!
if ctx.GlobalIsSet(EnsAPIFlag.Name) { if ctx.GlobalIsSet(EnsAPIFlag.Name) {
currentConfig.EnsApi = ctx.GlobalString(EnsAPIFlag.Name) ensAPIs := ctx.GlobalStringSlice(EnsAPIFlag.Name)
// preserve backward compatibility to disable ENS with --ens-api=""
if len(ensAPIs) == 1 && ensAPIs[0] == "" {
ensAPIs = nil
}
currentConfig.EnsAPIs = ensAPIs
} }
if ensaddr := ctx.GlobalString(EnsAddrFlag.Name); ensaddr != "" { if ensaddr := ctx.GlobalString(DeprecatedEnsAddrFlag.Name); ensaddr != "" {
currentConfig.EnsRoot = common.HexToAddress(ensaddr) currentConfig.EnsRoot = common.HexToAddress(ensaddr)
} }
@ -291,9 +301,8 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API) utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
} }
//EnsApi can be set to "", so can't check for empty string, as it is allowed if ensapi := os.Getenv(SWARM_ENV_ENS_API); ensapi != "" {
if ensapi, exists := os.LookupEnv(SWARM_ENV_ENS_API); exists { currentConfig.EnsAPIs = strings.Split(ensapi, ",")
currentConfig.EnsApi = ensapi
} }
if ensaddr := os.Getenv(SWARM_ENV_ENS_ADDR); ensaddr != "" { if ensaddr := os.Getenv(SWARM_ENV_ENS_ADDR); ensaddr != "" {
@ -340,6 +349,43 @@ func checkDeprecated(ctx *cli.Context) {
if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" { if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" {
utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.") utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.")
} }
// warn if --ens-api flag is set
if ctx.GlobalString(DeprecatedEnsAddrFlag.Name) != "" {
log.Warn("--ens-addr is no longer a valid command line flag, please use --ens-api to specify contract address.")
}
}
//validate configuration parameters
func validateConfig(cfg *bzzapi.Config) (err error) {
for _, ensAPI := range cfg.EnsAPIs {
if ensAPI != "" {
if err := validateEnsAPIs(ensAPI); err != nil {
return fmt.Errorf("invalid format [tld:][contract-addr@]url for ENS API endpoint configuration %q: %v", ensAPI, err)
}
}
}
return nil
}
//validate EnsAPIs configuration parameter
func validateEnsAPIs(s string) (err error) {
// missing contract address
if strings.HasPrefix(s, "@") {
return errors.New("missing contract address")
}
// missing url
if strings.HasSuffix(s, "@") {
return errors.New("missing url")
}
// missing tld
if strings.HasPrefix(s, ":") {
return errors.New("missing tld")
}
// missing url
if strings.HasSuffix(s, ":") {
return errors.New("missing url")
}
return nil
} }
//print a Config as string //print a Config as string

View file

@ -478,3 +478,98 @@ func TestConfigCmdLineOverridesFile(t *testing.T) {
node.Shutdown() node.Shutdown()
} }
func TestValidateConfig(t *testing.T) {
for _, c := range []struct {
cfg *api.Config
err string
}{
{
cfg: &api.Config{EnsAPIs: []string{
"/data/testnet/geth.ipc",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"http://127.0.0.1:1234",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"ws://127.0.0.1:1234",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"test:/data/testnet/geth.ipc",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"test:ws://127.0.0.1:1234",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:1234",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"test:314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"eth:314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"eth:314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:12344",
}},
},
{
cfg: &api.Config{EnsAPIs: []string{
"eth:",
}},
err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"eth:\": missing url",
},
{
cfg: &api.Config{EnsAPIs: []string{
"314159265dD8dbb310642f98f50C066173C1259b@",
}},
err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"314159265dD8dbb310642f98f50C066173C1259b@\": missing url",
},
{
cfg: &api.Config{EnsAPIs: []string{
":314159265dD8dbb310642f98f50C066173C1259",
}},
err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \":314159265dD8dbb310642f98f50C066173C1259\": missing tld",
},
{
cfg: &api.Config{EnsAPIs: []string{
"@/data/testnet/geth.ipc",
}},
err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"@/data/testnet/geth.ipc\": missing contract address",
},
} {
err := validateConfig(c.cfg)
if c.err != "" && err.Error() != c.err {
t.Errorf("expected error %q, got %q", c.err, err)
}
if c.err == "" && err != nil {
t.Errorf("unexpected error %q", err)
}
}
}

View file

@ -17,11 +17,9 @@
package main package main
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"os" "os"
"os/signal" "os/signal"
"runtime" "runtime"
@ -29,14 +27,12 @@ import (
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/contracts/ens"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
@ -45,7 +41,6 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm" "github.com/ethereum/go-ethereum/swarm"
bzzapi "github.com/ethereum/go-ethereum/swarm/api" bzzapi "github.com/ethereum/go-ethereum/swarm/api"
@ -110,16 +105,11 @@ var (
Usage: "Swarm Syncing enabled (default true)", Usage: "Swarm Syncing enabled (default true)",
EnvVar: SWARM_ENV_SYNC_ENABLE, EnvVar: SWARM_ENV_SYNC_ENABLE,
} }
EnsAPIFlag = cli.StringFlag{ EnsAPIFlag = cli.StringSliceFlag{
Name: "ens-api", Name: "ens-api",
Usage: "URL of the Ethereum API provider to use for ENS record lookups", Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
EnvVar: SWARM_ENV_ENS_API, EnvVar: SWARM_ENV_ENS_API,
} }
EnsAddrFlag = cli.StringFlag{
Name: "ens-addr",
Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)",
EnvVar: SWARM_ENV_ENS_ADDR,
}
SwarmApiFlag = cli.StringFlag{ SwarmApiFlag = cli.StringFlag{
Name: "bzzapi", Name: "bzzapi",
Usage: "Swarm HTTP endpoint", Usage: "Swarm HTTP endpoint",
@ -180,6 +170,10 @@ var (
Name: "ethapi", Name: "ethapi",
Usage: "DEPRECATED: please use --ens-api and --swap-api", Usage: "DEPRECATED: please use --ens-api and --swap-api",
} }
DeprecatedEnsAddrFlag = cli.StringFlag{
Name: "ens-addr",
Usage: "DEPRECATED: ENS contract address, please use --ens-api with contract address according to its format",
}
) )
//declare a few constant error messages, useful for later error check comparisons in test //declare a few constant error messages, useful for later error check comparisons in test
@ -367,7 +361,6 @@ DEPRECATED: use 'swarm db clean'.
// bzzd-specific flags // bzzd-specific flags
CorsStringFlag, CorsStringFlag,
EnsAPIFlag, EnsAPIFlag,
EnsAddrFlag,
SwarmTomlConfigPathFlag, SwarmTomlConfigPathFlag,
SwarmConfigPathFlag, SwarmConfigPathFlag,
SwarmSwapEnabledFlag, SwarmSwapEnabledFlag,
@ -394,6 +387,7 @@ DEPRECATED: use 'swarm db clean'.
SwarmStoreRadius, SwarmStoreRadius,
//deprecated flags //deprecated flags
DeprecatedEthAPIFlag, DeprecatedEthAPIFlag,
DeprecatedEnsAddrFlag,
} }
rpcFlags := []cli.Flag{ rpcFlags := []cli.Flag{
utils.WSEnabledFlag, utils.WSEnabledFlag,
@ -493,38 +487,6 @@ func bzzd(ctx *cli.Context) error {
return nil return nil
} }
// detectEnsAddr determines the ENS contract address by getting both the
// version and genesis hash using the client and matching them to either
// mainnet or testnet addresses
func detectEnsAddr(client *rpc.Client) (common.Address, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var version string
if err := client.CallContext(ctx, &version, "net_version"); err != nil {
return common.Address{}, err
}
block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0))
if err != nil {
return common.Address{}, err
}
switch {
case version == "1" && block.Hash() == params.MainnetGenesisHash:
log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress)
return ens.MainNetAddress, nil
case version == "3" && block.Hash() == params.TestnetGenesisHash:
log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress)
return ens.TestNetAddress, nil
default:
return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash())
}
}
func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) { func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) {
//define the swarm service boot function //define the swarm service boot function
@ -539,28 +501,8 @@ func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.
} }
} }
var ensClient *ethclient.Client
if bzzconfig.EnsApi != "" {
log.Info("connecting to ENS API", "url", bzzconfig.EnsApi)
client, err := rpc.Dial(bzzconfig.EnsApi)
if err != nil {
return nil, fmt.Errorf("error connecting to ENS API %s: %s", bzzconfig.EnsApi, err)
}
ensClient = ethclient.NewClient(client)
//no ENS root address set yet
if bzzconfig.EnsRoot == (common.Address{}) {
ensAddr, err := detectEnsAddr(client)
if err == nil {
bzzconfig.EnsRoot = ensAddr
} else {
log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", bzzconfig.EnsRoot), "err", err)
}
}
}
// In production, mockStore must be always nil. // In production, mockStore must be always nil.
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, nil) return swarm.NewSwarm(ctx, swapClient, bzzconfig, nil)
} }
//register within the ethereum node //register within the ethereum node
if err := stack.Register(boot); err != nil { if err := stack.Register(boot); err != nil {

View file

@ -22,8 +22,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -112,30 +110,6 @@ func SendItems(w MsgWriter, msgcode uint64, elems ...interface{}) error {
return Send(w, msgcode, elems) return Send(w, msgcode, elems)
} }
// netWrapper wraps a MsgReadWriter with locks around
// ReadMsg/WriteMsg and applies read/write deadlines.
type netWrapper struct {
rmu, wmu sync.Mutex
rtimeout, wtimeout time.Duration
conn net.Conn
wrapped MsgReadWriter
}
func (rw *netWrapper) ReadMsg() (Msg, error) {
rw.rmu.Lock()
defer rw.rmu.Unlock()
rw.conn.SetReadDeadline(time.Now().Add(rw.rtimeout))
return rw.wrapped.ReadMsg()
}
func (rw *netWrapper) WriteMsg(msg Msg) error {
rw.wmu.Lock()
defer rw.wmu.Unlock()
rw.conn.SetWriteDeadline(time.Now().Add(rw.wtimeout))
return rw.wrapped.WriteMsg(msg)
}
// eofSignal wraps a reader with eof signaling. the eof channel is // eofSignal wraps a reader with eof signaling. the eof channel is
// closed when the wrapped reader returns an error or when count bytes // closed when the wrapped reader returns an error or when count bytes
// have been read. // have been read.

View file

@ -22,8 +22,8 @@ devp2p subprotocols by abstracting away code standardly shared by protocols.
* automate RLP decoding/encoding based on reflecting * automate RLP decoding/encoding based on reflecting
* provide the forever loop to read incoming messages * provide the forever loop to read incoming messages
* standardise error handling related to communication * standardise error handling related to communication
* standardised handshake negotiation
* TODO: automatic generation of wire protocol specification for peers * TODO: automatic generation of wire protocol specification for peers
* standardise handshake negotiation
*/ */
package protocols package protocols
@ -84,7 +84,7 @@ type Error struct {
} }
func (e Error) Error() (message string) { func (e Error) Error() (message string) {
if len(message) == 0 { if len(e.message) == 0 {
name, ok := errorToString[e.Code] name, ok := errorToString[e.Code]
if !ok { if !ok {
panic("invalid message code") panic("invalid message code")
@ -98,13 +98,11 @@ func (e Error) Error() (message string) {
} }
func errorf(code int, format string, params ...interface{}) *Error { func errorf(code int, format string, params ...interface{}) *Error {
e := &Error{ return &Error{
Code: code, Code: code,
format: format, format: format,
params: params, params: params,
} }
return e
} }
// Spec is a protocol specification including its name and version as well as // Spec is a protocol specification including its name and version as well as
@ -119,10 +117,11 @@ type Spec struct {
// MaxMsgSize is the maximum accepted length of the message payload // MaxMsgSize is the maximum accepted length of the message payload
MaxMsgSize uint32 MaxMsgSize uint32
// Messages is a list of message types which this protocol uses, with // Messages is a list of message data types which this protocol uses, with
// each message type being sent with its array index as the code (so // each message type being sent with its array index as the code (so
// [&foo{}, &bar{}, &baz{}] would send foo, bar and baz with codes // [&foo{}, &bar{}, &baz{}] would send foo, bar and baz with codes
// 0, 1 and 2 respectively) // 0, 1 and 2 respectively)
// each message must have a single unique data type
Messages []interface{} Messages []interface{}
initOnce sync.Once initOnce sync.Once
@ -183,8 +182,8 @@ type Peer struct {
// NewPeer constructs a new peer // NewPeer constructs a new peer
// this constructor is called by the p2p.Protocol#Run function // this constructor is called by the p2p.Protocol#Run function
// the first two arguments are coming the arguments passed to p2p.Protocol.Run function // the first two arguments are the arguments passed to p2p.Protocol.Run function
// the third argument is the CodeMap describing the protocol messages and options // the third argument is the Spec describing the protocol
func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, spec *Spec) *Peer { func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, spec *Spec) *Peer {
return &Peer{ return &Peer{
Peer: p, Peer: p,
@ -195,6 +194,9 @@ func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, spec *Spec) *Peer {
// Run starts the forever loop that handles incoming messages // Run starts the forever loop that handles incoming messages
// called within the p2p.Protocol#Run function // called within the p2p.Protocol#Run function
// the handler argument is a function which is called for each message received
// from the remote peer, a returned error causes the loop to exit
// resulting in disconnection
func (p *Peer) Run(handler func(msg interface{}) error) error { func (p *Peer) Run(handler func(msg interface{}) error) error {
for { for {
if err := p.handleIncoming(handler); err != nil { if err := p.handleIncoming(handler); err != nil {
@ -204,11 +206,8 @@ func (p *Peer) Run(handler func(msg interface{}) error) error {
} }
// Drop disconnects a peer. // Drop disconnects a peer.
// falls back to self.disconnect which is set as p2p.Peer#Disconnect except
// for test peers where it calls p2p.MsgPipe#Close so that the readloop can terminate
// TODO: may need to implement protocol drop only? don't want to kick off the peer // TODO: may need to implement protocol drop only? don't want to kick off the peer
// if they are useful for other protocols // if they are useful for other protocols
// overwrite Disconnect for testing, so that protocol readloop quits
func (p *Peer) Drop(err error) { func (p *Peer) Drop(err error) {
p.Disconnect(p2p.DiscSubprotocolError) p.Disconnect(p2p.DiscSubprotocolError)
} }
@ -222,7 +221,6 @@ func (p *Peer) Send(msg interface{}) error {
if !found { if !found {
return errorf(ErrInvalidMsgType, "%v", code) return errorf(ErrInvalidMsgType, "%v", code)
} }
// log.Trace(fmt.Sprintf("=> msg %s#%d TO %v : %v", p.spec.Name, code, p.ID(), msg))
return p2p.Send(p.rw, code, msg) return p2p.Send(p.rw, code, msg)
} }
@ -253,7 +251,6 @@ func (p *Peer) handleIncoming(handle func(msg interface{}) error) error {
if err := msg.Decode(val); err != nil { if err := msg.Decode(val); err != nil {
return errorf(ErrDecode, "<= %v: %v", msg, err) return errorf(ErrDecode, "<= %v: %v", msg, err)
} }
// log.Trace(fmt.Sprintf("<= %s/%v FROM %v %T %v", p.spec.Name, msg, p.ID(), val, val))
// call the registered handler callbacks // call the registered handler callbacks
// a registered callback take the decoded message as argument as an interface // a registered callback take the decoded message as argument as an interface
@ -274,7 +271,7 @@ func (p *Peer) handleIncoming(handle func(msg interface{}) error) error {
// * expects a remote handshake back of the same type // * expects a remote handshake back of the same type
// * the dialing peer needs to send the handshake first and then waits for remote // * the dialing peer needs to send the handshake first and then waits for remote
// * the listening peer waits for the remote handshake and then sends it // * the listening peer waits for the remote handshake and then sends it
// returns the remote hs and an error // returns the remote handshake and an error
func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interface{}) error) (rhs interface{}, err error) { func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interface{}) error) (rhs interface{}, err error) {
if _, ok := p.spec.GetCode(hs); !ok { if _, ok := p.spec.GetCode(hs); !ok {
return nil, errorf(ErrHandshake, "unknown handshake message type: %T", hs) return nil, errorf(ErrHandshake, "unknown handshake message type: %T", hs)
@ -289,13 +286,18 @@ func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interf
} }
send := func() { errc <- p.Send(hs) } send := func() { errc <- p.Send(hs) }
receive := func() { errc <- p.handleIncoming(handle) } receive := func() { errc <- p.handleIncoming(handle) }
var last bool
for { go func() {
if p.Inbound() == last { if p.Inbound() {
go send() receive()
send()
} else { } else {
go receive() send()
receive()
} }
}()
for i := 0; i < 2; i++ {
select { select {
case err = <-errc: case err = <-errc:
case <-ctx.Done(): case <-ctx.Done():
@ -304,10 +306,6 @@ func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interf
if err != nil { if err != nil {
return nil, errorf(ErrHandshake, err.Error()) return nil, errorf(ErrHandshake, err.Error())
} }
if last {
break
}
last = true
} }
return rhs, nil return rhs, nil
} }

View file

@ -20,21 +20,15 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
p2ptest "github.com/ethereum/go-ethereum/p2p/testing" p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
) )
func init() {
log.Root().SetHandler(log.LvlFilterHandler(log.LvlWarn, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
}
// handshake message type // handshake message type
type hs0 struct { type hs0 struct {
C uint C uint
@ -141,8 +135,7 @@ func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) er
pp.Add(peer) pp.Add(peer)
defer pp.Remove(peer) defer pp.Remove(peer)
err = peer.Run(handle) return peer.Run(handle)
return err
} }
} }
@ -232,8 +225,12 @@ func runModuleHandshake(t *testing.T, resp uint, errs ...error) {
pp := p2ptest.NewTestPeerPool() pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp) s := protocolTester(t, pp)
id := s.IDs[0] id := s.IDs[0]
s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...) if err := s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...); err != nil {
s.TestExchanges(moduleHandshakeExchange(id, resp)...) t.Fatal(err)
}
if err := s.TestExchanges(moduleHandshakeExchange(id, resp)...); err != nil {
t.Fatal(err)
}
var disconnects []*p2ptest.Disconnect var disconnects []*p2ptest.Disconnect
for i, err := range errs { for i, err := range errs {
disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err}) disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
@ -308,29 +305,32 @@ func runMultiplePeers(t *testing.T, peer int, errs ...error) {
pp := p2ptest.NewTestPeerPool() pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp) s := protocolTester(t, pp)
s.TestExchanges(testMultiPeerSetup(s.IDs[0], s.IDs[1])...) if err := s.TestExchanges(testMultiPeerSetup(s.IDs[0], s.IDs[1])...); err != nil {
t.Fatal(err)
}
// after some exchanges of messages, we can test state changes // after some exchanges of messages, we can test state changes
// here this is simply demonstrated by the peerPool // here this is simply demonstrated by the peerPool
// after the handshake negotiations peers must be added to the pool // after the handshake negotiations peers must be added to the pool
// time.Sleep(1) // time.Sleep(1)
for !pp.Has(s.IDs[0]) { tick := time.NewTicker(10 * time.Millisecond)
time.Sleep(1) timeout := time.NewTimer(1 * time.Second)
log.Trace(fmt.Sprintf("missing peer test-0: %v (%v)", pp, s.IDs)) WAIT:
for {
select {
case <-tick.C:
if pp.Has(s.IDs[0]) {
break WAIT
}
case <-timeout.C:
t.Fatal("timeout")
}
} }
if !pp.Has(s.IDs[0]) {
t.Fatalf("missing peer test-0: %v (%v)", pp, s.IDs)
}
for !pp.Has(s.IDs[1]) {
time.Sleep(1)
log.Trace(fmt.Sprintf("missing peer test-1: %v (%v)", pp, s.IDs))
}
if !pp.Has(s.IDs[1]) { if !pp.Has(s.IDs[1]) {
t.Fatalf("missing peer test-1: %v (%v)", pp, s.IDs) t.Fatalf("missing peer test-1: %v (%v)", pp, s.IDs)
} }
// peer 0 sends kill request for peer with index <peer> // peer 0 sends kill request for peer with index <peer>
s.TestExchanges(p2ptest.Exchange{ err := s.TestExchanges(p2ptest.Exchange{
Triggers: []p2ptest.Trigger{ Triggers: []p2ptest.Trigger{
{ {
Code: 2, Code: 2,
@ -340,8 +340,12 @@ func runMultiplePeers(t *testing.T, peer int, errs ...error) {
}, },
}) })
if err != nil {
t.Fatal(err)
}
// the peer not killed sends a drop request // the peer not killed sends a drop request
s.TestExchanges(p2ptest.Exchange{ err = s.TestExchanges(p2ptest.Exchange{
Triggers: []p2ptest.Trigger{ Triggers: []p2ptest.Trigger{
{ {
Code: 3, Code: 3,
@ -350,6 +354,11 @@ func runMultiplePeers(t *testing.T, peer int, errs ...error) {
}, },
}, },
}) })
if err != nil {
t.Fatal(err)
}
// check the actual discconnect errors on the individual peers // check the actual discconnect errors on the individual peers
var disconnects []*p2ptest.Disconnect var disconnects []*p2ptest.Disconnect
for i, err := range errs { for i, err := range errs {
@ -364,7 +373,6 @@ func runMultiplePeers(t *testing.T, peer int, errs ...error) {
} }
} }
func XTestMultiplePeersDropSelf(t *testing.T) { func XTestMultiplePeersDropSelf(t *testing.T) {
runMultiplePeers(t, 0, runMultiplePeers(t, 0,
fmt.Errorf("subprotocol error"), fmt.Errorf("subprotocol error"),

View file

@ -36,9 +36,7 @@ import (
) )
const ( const (
defaultDialTimeout = 15 * time.Second defaultDialTimeout = 15 * time.Second
refreshPeersInterval = 30 * time.Second
staticPeerCheckInterval = 15 * time.Second
// Connectivity defaults. // Connectivity defaults.
maxActiveDialTasks = 16 maxActiveDialTasks = 16

View file

@ -28,6 +28,11 @@ import (
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
) )
var errTimedOut = errors.New("timed out")
// ProtocolSession is a quasi simulation of a pivot node running
// a service and a number of dummy peers that can send (trigger) or
// receive (expect) messages
type ProtocolSession struct { type ProtocolSession struct {
Server *p2p.Server Server *p2p.Server
IDs []discover.NodeID IDs []discover.NodeID
@ -35,9 +40,9 @@ type ProtocolSession struct {
events chan *p2p.PeerEvent events chan *p2p.PeerEvent
} }
// exchanges are the basic units of protocol tests // Exchange is the basic units of protocol tests
// the triggers and expects in the arrays are run immediately and asynchronously // the triggers and expects in the arrays are run immediately and asynchronously
// thus one cannot have multiple expects for the SAME peer with the DIFFERENT messagetypes // thus one cannot have multiple expects for the SAME peer with DIFFERENT message types
// because it's unpredictable which expect will receive which message // because it's unpredictable which expect will receive which message
// (with expect #1 and #2, messages might be sent #2 and #1, and both expects will complain about wrong message code) // (with expect #1 and #2, messages might be sent #2 and #1, and both expects will complain about wrong message code)
// an exchange is defined on a session // an exchange is defined on a session
@ -45,9 +50,11 @@ type Exchange struct {
Label string Label string
Triggers []Trigger Triggers []Trigger
Expects []Expect Expects []Expect
Timeout time.Duration
} }
// part of the exchange, incoming message from a set of peers // Trigger is part of the exchange, incoming message for the pivot node
// sent by a peer
type Trigger struct { type Trigger struct {
Msg interface{} // type of message to be sent Msg interface{} // type of message to be sent
Code uint64 // code of message is given Code uint64 // code of message is given
@ -55,6 +62,8 @@ type Trigger struct {
Timeout time.Duration // timeout duration for the sending Timeout time.Duration // timeout duration for the sending
} }
// Expect is part of an exchange, outgoing message from the pivot node
// received by a peer
type Expect struct { type Expect struct {
Msg interface{} // type of message to expect Msg interface{} // type of message to expect
Code uint64 // code of message is now given Code uint64 // code of message is now given
@ -62,6 +71,7 @@ type Expect struct {
Timeout time.Duration // timeout duration for receiving Timeout time.Duration // timeout duration for receiving
} }
// Disconnect represents a disconnect event, used and checked by TestDisconnected
type Disconnect struct { type Disconnect struct {
Peer discover.NodeID // discconnected peer Peer discover.NodeID // discconnected peer
Error error // disconnect reason Error error // disconnect reason
@ -90,108 +100,158 @@ func (self *ProtocolSession) trigger(trig Trigger) error {
if t == time.Duration(0) { if t == time.Duration(0) {
t = 1000 * time.Millisecond t = 1000 * time.Millisecond
} }
alarm := time.NewTimer(t)
select { select {
case err := <-errc: case err := <-errc:
return err return err
case <-alarm.C: case <-time.After(t):
return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer) return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer)
} }
} }
// expect checks an expectation // expect checks an expectation of a message sent out by the pivot node
func (self *ProtocolSession) expect(exp Expect) error { func (self *ProtocolSession) expect(exps []Expect) error {
if exp.Msg == nil { // construct a map of expectations for each node
return errors.New("no message to expect") peerExpects := make(map[discover.NodeID][]Expect)
} for _, exp := range exps {
simNode, ok := self.adapter.GetNode(exp.Peer) if exp.Msg == nil {
if !ok { return errors.New("no message to expect")
return fmt.Errorf("trigger: peer %v does not exist (1- %v)", exp.Peer, len(self.IDs)) }
} peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp)
mockNode, ok := simNode.Services()[0].(*mockNode)
if !ok {
return fmt.Errorf("trigger: peer %v is not a mock", exp.Peer)
} }
// construct a map of mockNodes for each node
mockNodes := make(map[discover.NodeID]*mockNode)
for nodeID := range peerExpects {
simNode, ok := self.adapter.GetNode(nodeID)
if !ok {
return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(self.IDs))
}
mockNode, ok := simNode.Services()[0].(*mockNode)
if !ok {
return fmt.Errorf("trigger: peer %v is not a mock", nodeID)
}
mockNodes[nodeID] = mockNode
}
// done chanell cancels all created goroutines when function returns
done := make(chan struct{})
defer close(done)
// errc catches the first error from
errc := make(chan error) errc := make(chan error)
wg := &sync.WaitGroup{}
wg.Add(len(mockNodes))
for nodeID, mockNode := range mockNodes {
nodeID := nodeID
mockNode := mockNode
go func() {
defer wg.Done()
// Sum all Expect timeouts to give the maximum
// time for all expectations to finish.
// mockNode.Expect checks all received messages against
// a list of expected messages and timeout for each
// of them can not be checked separately.
var t time.Duration
for _, exp := range peerExpects[nodeID] {
if exp.Timeout == time.Duration(0) {
t += 2000 * time.Millisecond
} else {
t += exp.Timeout
}
}
alarm := time.NewTimer(t)
defer alarm.Stop()
// expectErrc is used to check if error returned
// from mockNode.Expect is not nil and to send it to
// errc only in that case.
// done channel will be closed when function
expectErrc := make(chan error)
go func() {
select {
case expectErrc <- mockNode.Expect(peerExpects[nodeID]...):
case <-done:
case <-alarm.C:
}
}()
select {
case err := <-expectErrc:
if err != nil {
select {
case errc <- err:
case <-done:
case <-alarm.C:
errc <- errTimedOut
}
}
case <-done:
case <-alarm.C:
errc <- errTimedOut
}
}()
}
go func() { go func() {
log.Trace(fmt.Sprintf("waiting for msg, %v", exp.Msg)) wg.Wait()
errc <- mockNode.Expect(&exp) // close errc when all goroutines finish to return nill err from errc
close(errc)
}() }()
t := exp.Timeout return <-errc
if t == time.Duration(0) { }
// TestExchanges tests a series of exchanges against the session
func (self *ProtocolSession) TestExchanges(exchanges ...Exchange) error {
for i, e := range exchanges {
if err := self.testExchange(e); err != nil {
return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err)
}
log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label))
}
return nil
}
// testExchange tests a single Exchange.
// Default timeout value is 2 seconds.
func (self *ProtocolSession) testExchange(e Exchange) error {
errc := make(chan error)
done := make(chan struct{})
defer close(done)
go func() {
for _, trig := range e.Triggers {
err := self.trigger(trig)
if err != nil {
errc <- err
return
}
}
select {
case errc <- self.expect(e.Expects):
case <-done:
}
}()
// time out globally or finish when all expectations satisfied
t := e.Timeout
if t == 0 {
t = 2000 * time.Millisecond t = 2000 * time.Millisecond
} }
alarm := time.NewTimer(t) alarm := time.NewTimer(t)
select { select {
case err := <-errc: case err := <-errc:
log.Trace(fmt.Sprintf("expected msg arrives with error %v", err))
return err return err
case <-alarm.C: case <-alarm.C:
return fmt.Errorf("timout expecting %v sent to peer %v", exp.Msg, exp.Peer) return errTimedOut
} }
} }
// TestExchanges tests a series of exchanges againsts the session // TestDisconnected tests the disconnections given as arguments
func (self *ProtocolSession) TestExchanges(exchanges ...Exchange) error { // the disconnect structs describe what disconnect error is expected on which peer
// launch all triggers of this exchanges
for i, e := range exchanges {
errc := make(chan error)
wg := &sync.WaitGroup{}
for _, trig := range e.Triggers {
err := self.trigger(trig)
if err != nil {
errc <- err
}
}
// each expectation is spawned in separate go-routine
// expectations of an exchange are conjunctive but unordered, i.e.,
// only all of them arriving constitutes a pass
// each expectation is meant to be for a different peer, otherwise they are expected to panic
// testing of an exchange blocks until all expectations are decided
// an expectation is decided if
// expected message arrives OR
// an unexpected message arrives (panic)
// times out on their individual timeout
for _, ex := range e.Expects {
wg.Add(1)
// expect msg spawned to separate go routine
go func(exp Expect) {
defer wg.Done()
err := self.expect(exp)
if err != nil {
log.Trace(fmt.Sprintf("expect msg fails %v", err))
errc <- err
}
}(ex)
}
// wait for all expectations
go func() {
wg.Wait()
close(errc)
}()
// time out globally or finish when all expectations satisfied
alarm := time.NewTimer(1000 * time.Millisecond)
select {
case err := <-errc:
if err != nil {
return fmt.Errorf("exchange failed with: %v", err)
} else {
log.Trace(fmt.Sprintf("exchange %v: '%v' run successfully", i, e.Label))
}
case <-alarm.C:
return fmt.Errorf("exchange %v: '%v' timed out", i, e.Label)
}
}
return nil
}
func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error { func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
expects := make(map[discover.NodeID]error) expects := make(map[discover.NodeID]error)
for _, disconnect := range disconnects { for _, disconnect := range disconnects {
@ -209,10 +269,8 @@ func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error
if !ok { if !ok {
continue continue
} }
log.Trace("disconnects: ", "peer", event.Peer, "event type", event.Type, "expect", expectErr, "error", event.Error)
if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) { if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) {
log.Trace("error!!!")
return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error) return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error)
} }
delete(expects, event.Peer) delete(expects, event.Peer)

View file

@ -15,13 +15,20 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
/* /*
TODO: documentation the p2p/testing package provides a unit test scheme to check simple
protocol message exchanges with one pivot node and a number of dummy peers
The pivot test node runs a node.Service, the dummy peers run a mock node
that can be used to send and receive messages
*/ */
package testing package testing
import ( import (
"bytes"
"fmt" "fmt"
"io"
"io/ioutil"
"strings"
"sync" "sync"
"testing" "testing"
@ -31,14 +38,20 @@ import (
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/simulations" "github.com/ethereum/go-ethereum/p2p/simulations"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
// ProtocolTester is the tester environment used for unit testing protocol
// message exchanges. It uses p2p/simulations framework
type ProtocolTester struct { type ProtocolTester struct {
*ProtocolSession *ProtocolSession
network *simulations.Network network *simulations.Network
} }
// NewProtocolTester constructs a new ProtocolTester
// it takes as argument the pivot node id, the number of dummy peers and the
// protocol run function called on a peer connection by the p2p server
func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
services := adapters.Services{ services := adapters.Services{
"test": func(ctx *adapters.ServiceContext) (node.Service, error) { "test": func(ctx *adapters.ServiceContext) (node.Service, error) {
@ -87,11 +100,14 @@ func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Pe
return self return self
} }
// Stop stops the p2p server
func (self *ProtocolTester) Stop() error { func (self *ProtocolTester) Stop() error {
self.Server.Stop() self.Server.Stop()
return nil return nil
} }
// Connect brings up the remote peer node and connects it using the
// p2p/simulations network connection with the in memory network adapter
func (self *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) { func (self *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) {
for _, peer := range peers { for _, peer := range peers {
log.Trace(fmt.Sprintf("start node %v", peer.ID)) log.Trace(fmt.Sprintf("start node %v", peer.ID))
@ -141,7 +157,7 @@ type mockNode struct {
testNode testNode
trigger chan *Trigger trigger chan *Trigger
expect chan *Expect expect chan []Expect
err chan error err chan error
stop chan struct{} stop chan struct{}
stopOnce sync.Once stopOnce sync.Once
@ -150,7 +166,7 @@ type mockNode struct {
func newMockNode() *mockNode { func newMockNode() *mockNode {
mock := &mockNode{ mock := &mockNode{
trigger: make(chan *Trigger), trigger: make(chan *Trigger),
expect: make(chan *Expect), expect: make(chan []Expect),
err: make(chan error), err: make(chan error),
stop: make(chan struct{}), stop: make(chan struct{}),
} }
@ -165,8 +181,8 @@ func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
select { select {
case trig := <-m.trigger: case trig := <-m.trigger:
m.err <- p2p.Send(rw, trig.Code, trig.Msg) m.err <- p2p.Send(rw, trig.Code, trig.Msg)
case exp := <-m.expect: case exps := <-m.expect:
m.err <- p2p.ExpectMsg(rw, exp.Code, exp.Msg) m.err <- expectMsgs(rw, exps)
case <-m.stop: case <-m.stop:
return nil return nil
} }
@ -178,7 +194,7 @@ func (m *mockNode) Trigger(trig *Trigger) error {
return <-m.err return <-m.err
} }
func (m *mockNode) Expect(exp *Expect) error { func (m *mockNode) Expect(exp ...Expect) error {
m.expect <- exp m.expect <- exp
return <-m.err return <-m.err
} }
@ -187,3 +203,67 @@ func (m *mockNode) Stop() error {
m.stopOnce.Do(func() { close(m.stop) }) m.stopOnce.Do(func() { close(m.stop) })
return nil return nil
} }
func expectMsgs(rw p2p.MsgReadWriter, exps []Expect) error {
matched := make([]bool, len(exps))
for {
msg, err := rw.ReadMsg()
if err != nil {
if err == io.EOF {
break
}
return err
}
actualContent, err := ioutil.ReadAll(msg.Payload)
if err != nil {
return err
}
var found bool
for i, exp := range exps {
if exp.Code == msg.Code && bytes.Equal(actualContent, mustEncodeMsg(exp.Msg)) {
if matched[i] {
return fmt.Errorf("message #%d received two times", i)
}
matched[i] = true
found = true
break
}
}
if !found {
expected := make([]string, 0)
for i, exp := range exps {
if matched[i] {
continue
}
expected = append(expected, fmt.Sprintf("code %d payload %x", exp.Code, mustEncodeMsg(exp.Msg)))
}
return fmt.Errorf("unexpected message code %d payload %x, expected %s", msg.Code, actualContent, strings.Join(expected, " or "))
}
done := true
for _, m := range matched {
if !m {
done = false
break
}
}
if done {
return nil
}
}
for i, m := range matched {
if !m {
return fmt.Errorf("expected message #%d not received", i)
}
}
return nil
}
// mustEncodeMsg uses rlp to encode a message.
// In case of error it panics.
func mustEncodeMsg(msg interface{}) []byte {
contentEnc, err := rlp.EncodeToBytes(msg)
if err != nil {
panic("content encode error: " + err.Error())
}
return contentEnc
}

View file

@ -421,7 +421,7 @@ func (s *Server) readRequest(codec ServerCodec) ([]*serverRequest, bool, Error)
} }
} }
} else { } else {
requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.method, r.method}} requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}}
} }
continue continue
} }

View file

@ -20,7 +20,9 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"math/big"
"net/http" "net/http"
"path"
"regexp" "regexp"
"strings" "strings"
@ -30,6 +32,8 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/contracts/ens"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
@ -52,6 +56,139 @@ type Resolver interface {
Resolve(string) (common.Hash, error) Resolve(string) (common.Hash, error)
} }
type ResolveValidator interface {
Resolver
Owner(node [32]byte) (common.Address, error)
HeaderByNumber(context.Context, *big.Int) (*types.Header, error)
}
// NoResolverError is returned by MultiResolver.Resolve if no resolver
// can be found for the address.
type NoResolverError struct {
TLD string
}
func NewNoResolverError(tld string) *NoResolverError {
return &NoResolverError{TLD: tld}
}
func (e *NoResolverError) Error() string {
if e.TLD == "" {
return "no ENS resolver"
}
return fmt.Sprintf("no ENS endpoint configured to resolve .%s TLD names", e.TLD)
}
// MultiResolver is used to resolve URL addresses based on their TLDs.
// Each TLD can have multiple resolvers, and the resoluton from the
// first one in the sequence will be returned.
type MultiResolver struct {
resolvers map[string][]ResolveValidator
nameHash func(string) common.Hash
}
// MultiResolverOption sets options for MultiResolver and is used as
// arguments for its constructor.
type MultiResolverOption func(*MultiResolver)
// MultiResolverOptionWithResolver adds a Resolver to a list of resolvers
// for a specific TLD. If TLD is an empty string, the resolver will be added
// to the list of default resolver, the ones that will be used for resolution
// of addresses which do not have their TLD resolver specified.
func MultiResolverOptionWithResolver(r ResolveValidator, tld string) MultiResolverOption {
return func(m *MultiResolver) {
m.resolvers[tld] = append(m.resolvers[tld], r)
}
}
func MultiResolverOptionWithNameHash(nameHash func(string) common.Hash) MultiResolverOption {
return func(m *MultiResolver) {
m.nameHash = nameHash
}
}
// NewMultiResolver creates a new instance of MultiResolver.
func NewMultiResolver(opts ...MultiResolverOption) (m *MultiResolver) {
m = &MultiResolver{
resolvers: make(map[string][]ResolveValidator),
nameHash: ens.EnsNode,
}
for _, o := range opts {
o(m)
}
return m
}
// Resolve resolves address by choosing a Resolver by TLD.
// If there are more default Resolvers, or for a specific TLD,
// the Hash from the the first one which does not return error
// will be returned.
func (m *MultiResolver) Resolve(addr string) (h common.Hash, err error) {
rs, err := m.getResolveValidator(addr)
if err != nil {
return h, err
}
for _, r := range rs {
h, err = r.Resolve(addr)
if err == nil {
return
}
}
return
}
func (m *MultiResolver) ValidateOwner(name string, address common.Address) (bool, error) {
rs, err := m.getResolveValidator(name)
if err != nil {
return false, err
}
var addr common.Address
for _, r := range rs {
addr, err = r.Owner(m.nameHash(name))
// we hide the error if it is not for the last resolver we check
if err == nil {
return addr == address, nil
}
}
return false, err
}
func (m *MultiResolver) HeaderByNumber(ctx context.Context, name string, blockNr *big.Int) (*types.Header, error) {
rs, err := m.getResolveValidator(name)
if err != nil {
return nil, err
}
for _, r := range rs {
var header *types.Header
header, err = r.HeaderByNumber(ctx, blockNr)
// we hide the error if it is not for the last resolver we check
if err == nil {
return header, nil
}
}
return nil, err
}
func (m *MultiResolver) getResolveValidator(name string) ([]ResolveValidator, error) {
rs := m.resolvers[""]
tld := path.Ext(name)
if tld != "" {
tld = tld[1:]
rstld, ok := m.resolvers[tld]
if ok {
return rstld, nil
}
}
if len(rs) == 0 {
return rs, NewNoResolverError(tld)
}
return rs, nil
}
func (m *MultiResolver) SetNameHash(nameHash func(string) common.Hash) {
m.nameHash = nameHash
}
/* /*
Api implements webserver/file system related content storage and retrieval Api implements webserver/file system related content storage and retrieval
on top of the dpa on top of the dpa

View file

@ -17,14 +17,17 @@
package api package api
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/big"
"os" "os"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
@ -121,12 +124,12 @@ func TestApiPut(t *testing.T) {
// testResolver implements the Resolver interface and either returns the given // testResolver implements the Resolver interface and either returns the given
// hash if it is set, or returns a "name not found" error // hash if it is set, or returns a "name not found" error
type testResolver struct { type testResolveValidator struct {
hash *common.Hash hash *common.Hash
} }
func newTestResolver(addr string) *testResolver { func newTestResolveValidator(addr string) *testResolveValidator {
r := &testResolver{} r := &testResolveValidator{}
if addr != "" { if addr != "" {
hash := common.HexToHash(addr) hash := common.HexToHash(addr)
r.hash = &hash r.hash = &hash
@ -134,21 +137,28 @@ func newTestResolver(addr string) *testResolver {
return r return r
} }
func (t *testResolver) Resolve(addr string) (common.Hash, error) { func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) {
if t.hash == nil { if t.hash == nil {
return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr) return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr)
} }
return *t.hash, nil return *t.hash, nil
} }
func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) {
return
}
func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) {
return
}
// TestAPIResolve tests resolving URIs which can either contain content hashes // TestAPIResolve tests resolving URIs which can either contain content hashes
// or ENS names // or ENS names
func TestAPIResolve(t *testing.T) { func TestAPIResolve(t *testing.T) {
ensAddr := "swarm.eth" ensAddr := "swarm.eth"
hashAddr := "1111111111111111111111111111111111111111111111111111111111111111" hashAddr := "1111111111111111111111111111111111111111111111111111111111111111"
resolvedAddr := "2222222222222222222222222222222222222222222222222222222222222222" resolvedAddr := "2222222222222222222222222222222222222222222222222222222222222222"
doesResolve := newTestResolver(resolvedAddr) doesResolve := newTestResolveValidator(resolvedAddr)
doesntResolve := newTestResolver("") doesntResolve := newTestResolveValidator("")
type test struct { type test struct {
desc string desc string
@ -237,3 +247,128 @@ func TestAPIResolve(t *testing.T) {
}) })
} }
} }
func TestMultiResolver(t *testing.T) {
doesntResolve := newTestResolveValidator("")
ethAddr := "swarm.eth"
ethHash := "0x2222222222222222222222222222222222222222222222222222222222222222"
ethResolve := newTestResolveValidator(ethHash)
testAddr := "swarm.test"
testHash := "0x1111111111111111111111111111111111111111111111111111111111111111"
testResolve := newTestResolveValidator(testHash)
tests := []struct {
desc string
r Resolver
addr string
result string
err error
}{
{
desc: "No resolvers, returns error",
r: NewMultiResolver(),
err: NewNoResolverError(""),
},
{
desc: "One default resolver, returns resolved address",
r: NewMultiResolver(MultiResolverOptionWithResolver(ethResolve, "")),
addr: ethAddr,
result: ethHash,
},
{
desc: "Two default resolvers, returns resolved address",
r: NewMultiResolver(
MultiResolverOptionWithResolver(ethResolve, ""),
MultiResolverOptionWithResolver(ethResolve, ""),
),
addr: ethAddr,
result: ethHash,
},
{
desc: "Two default resolvers, first doesn't resolve, returns resolved address",
r: NewMultiResolver(
MultiResolverOptionWithResolver(doesntResolve, ""),
MultiResolverOptionWithResolver(ethResolve, ""),
),
addr: ethAddr,
result: ethHash,
},
{
desc: "Default resolver doesn't resolve, tld resolver resolve, returns resolved address",
r: NewMultiResolver(
MultiResolverOptionWithResolver(doesntResolve, ""),
MultiResolverOptionWithResolver(ethResolve, "eth"),
),
addr: ethAddr,
result: ethHash,
},
{
desc: "Three TLD resolvers, third resolves, returns resolved address",
r: NewMultiResolver(
MultiResolverOptionWithResolver(doesntResolve, "eth"),
MultiResolverOptionWithResolver(doesntResolve, "eth"),
MultiResolverOptionWithResolver(ethResolve, "eth"),
),
addr: ethAddr,
result: ethHash,
},
{
desc: "One TLD resolver doesn't resolve, returns error",
r: NewMultiResolver(
MultiResolverOptionWithResolver(doesntResolve, ""),
MultiResolverOptionWithResolver(ethResolve, "eth"),
),
addr: ethAddr,
result: ethHash,
},
{
desc: "One defautl and one TLD resolver, all doesn't resolve, returns error",
r: NewMultiResolver(
MultiResolverOptionWithResolver(doesntResolve, ""),
MultiResolverOptionWithResolver(doesntResolve, "eth"),
),
addr: ethAddr,
result: ethHash,
err: errors.New(`DNS name not found: "swarm.eth"`),
},
{
desc: "Two TLD resolvers, both resolve, returns resolved address",
r: NewMultiResolver(
MultiResolverOptionWithResolver(ethResolve, "eth"),
MultiResolverOptionWithResolver(testResolve, "test"),
),
addr: testAddr,
result: testHash,
},
{
desc: "One TLD resolver, no default resolver, returns error for different TLD",
r: NewMultiResolver(
MultiResolverOptionWithResolver(ethResolve, "eth"),
),
addr: testAddr,
err: NewNoResolverError("test"),
},
}
for _, x := range tests {
t.Run(x.desc, func(t *testing.T) {
res, err := x.r.Resolve(x.addr)
if err == nil {
if x.err != nil {
t.Fatalf("expected error %q, got result %q", x.err, res.Hex())
}
if res.Hex() != x.result {
t.Fatalf("expected result %q, got %q", x.result, res.Hex())
}
} else {
if x.err == nil {
t.Fatalf("expected no error, got %q", err)
}
if err.Error() != x.err.Error() {
t.Fatalf("expected error %q, got %q", x.err, err)
}
}
})
}
}

View file

@ -48,7 +48,7 @@ type Config struct {
//*network.SyncParams //*network.SyncParams
Contract common.Address Contract common.Address
EnsRoot common.Address EnsRoot common.Address
EnsApi string EnsAPIs []string
Path string Path string
ListenAddr string ListenAddr string
Port string Port string
@ -78,7 +78,7 @@ func NewConfig() (self *Config) {
ListenAddr: DefaultHTTPListenAddr, ListenAddr: DefaultHTTPListenAddr,
Port: DefaultHTTPPort, Port: DefaultHTTPPort,
Path: node.DefaultDataDir(), Path: node.DefaultDataDir(),
EnsApi: node.DefaultIPCEndpoint("geth"), EnsAPIs: nil,
EnsRoot: ens.TestNetAddress, EnsRoot: ens.TestNetAddress,
NetworkId: network.NetworkID, NetworkId: network.NetworkID,
SwapEnabled: false, SwapEnabled: false,

View file

@ -119,7 +119,7 @@ func TestStreamerUpstreamRetrieveRequestMsgExchangeWithoutStore(t *testing.T) {
}, },
}) })
expectedError := "exchange 0: 'RetrieveRequestMsg' timed out" expectedError := `exchange #0 "RetrieveRequestMsg": timed out`
if err == nil || err.Error() != expectedError { if err == nil || err.Error() != expectedError {
t.Fatalf("Expected error %v, got %v", expectedError, err) t.Fatalf("Expected error %v, got %v", expectedError, err)
} }

View file

@ -1,4 +1,4 @@
// Copyright 2014 The go-ethereum Authors // Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
// The go-ethereum library is free software: you can redistribute it and/or modify // The go-ethereum library is free software: you can redistribute it and/or modify
@ -14,10 +14,26 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core package storage
import ( //
"math/big" // import "github.com/ethereum/go-ethereum/swarm/storage/encryption"
) //
// type HasherStore interface {
var BlockReward = big.NewInt(5e+18) // Put([]byte) Key
// Get(Key) ([]byte, error)
// }
//
// type PlainHasherStore struct {
// store ChunkStore
// }
//
// type EncryptedHasherStore struct {
// PlainHasherStore
// dataEncryption encryption.Encryption
// spanEncryption encryption.Encryption
// }
//
// func (e *PlainHasherStore) Put([]byte) Key {
//
// }

View file

@ -88,12 +88,12 @@ func (self *resource) NameHash() common.Hash {
type ResourceValidator interface { type ResourceValidator interface {
hashSize() int hashSize() int
checkAccess(string, common.Address) (bool, error) checkAccess(string, common.Address) (bool, error)
nameHash(string) common.Hash // nameHashFunc NameHash(string) common.Hash // nameHashFunc
sign(common.Hash) (Signature, error) // SignFunc sign(common.Hash) (Signature, error) // SignFunc
} }
type ethApi interface { type headerGetter interface {
HeaderByNumber(context.Context, *big.Int) (*types.Header, error) HeaderByNumber(context.Context, string, *big.Int) (*types.Header, error)
} }
// Mutable resource is an entity which allows updates to a resource // Mutable resource is an entity which allows updates to a resource
@ -158,7 +158,7 @@ type ethApi interface {
type ResourceHandler struct { type ResourceHandler struct {
ChunkStore ChunkStore
validator ResourceValidator validator ResourceValidator
ethClient ethApi ethClient headerGetter
resources map[string]*resource resources map[string]*resource
hashPool sync.Pool hashPool sync.Pool
resourceLock sync.RWMutex resourceLock sync.RWMutex
@ -167,7 +167,7 @@ type ResourceHandler struct {
} }
// Create or open resource update chunk store // Create or open resource update chunk store
func NewResourceHandler(hasher SwarmHasher, chunkStore ChunkStore, ethClient ethApi, validator ResourceValidator) (*ResourceHandler, error) { func NewResourceHandler(hasher SwarmHasher, chunkStore ChunkStore, ethClient headerGetter, validator ResourceValidator) (*ResourceHandler, error) {
rh := &ResourceHandler{ rh := &ResourceHandler{
ChunkStore: chunkStore, ChunkStore: chunkStore,
ethClient: ethClient, ethClient: ethClient,
@ -182,7 +182,7 @@ func NewResourceHandler(hasher SwarmHasher, chunkStore ChunkStore, ethClient eth
} }
if rh.validator != nil { if rh.validator != nil {
rh.nameHash = rh.validator.nameHash rh.nameHash = rh.validator.NameHash
} else { } else {
rh.nameHash = func(name string) common.Hash { rh.nameHash = func(name string) common.Hash {
hasher := rh.hashPool.Get().(SwarmHash) hasher := rh.hashPool.Get().(SwarmHash)
@ -281,7 +281,7 @@ func (self *ResourceHandler) NewResource(ctx context.Context, name string, frequ
} }
// get our blockheight at this time // get our blockheight at this time
currentblock, err := self.getBlock(ctx) currentblock, err := self.getBlock(ctx, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -374,7 +374,7 @@ func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.H
if err != nil { if err != nil {
return nil, err return nil, err
} }
currentblock, err := self.getBlock(ctx) currentblock, err := self.getBlock(ctx, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -583,7 +583,7 @@ func (self *ResourceHandler) update(ctx context.Context, name string, data []byt
} }
// get our blockheight at this time and the next block of the update period // get our blockheight at this time and the next block of the update period
currentblock, err := self.getBlock(ctx) currentblock, err := self.getBlock(ctx, name)
if err != nil { if err != nil {
return nil, NewResourceError(ErrIO, fmt.Sprintf("Could not get block height: %v", err)) return nil, NewResourceError(ErrIO, fmt.Sprintf("Could not get block height: %v", err))
} }
@ -655,8 +655,8 @@ func (self *ResourceHandler) Close() {
self.ChunkStore.Close() self.ChunkStore.Close()
} }
func (self *ResourceHandler) getBlock(ctx context.Context) (uint64, error) { func (self *ResourceHandler) getBlock(ctx context.Context, name string) (uint64, error) {
blockheader, err := self.ethClient.HeaderByNumber(ctx, nil) blockheader, err := self.ethClient.HeaderByNumber(ctx, name, nil)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -866,7 +866,7 @@ func isMultihash(data []byte) int {
} }
// TODO: this should not be exposed, but swarm/testutil/http.go needs it // TODO: this should not be exposed, but swarm/testutil/http.go needs it
func NewTestResourceHandler(datadir string, ethClient ethApi, validator ResourceValidator) (*ResourceHandler, error) { func NewTestResourceHandler(datadir string, ethClient headerGetter, validator ResourceValidator) (*ResourceHandler, error) {
path := filepath.Join(datadir, DbDirName) path := filepath.Join(datadir, DbDirName)
basekey := make([]byte, 32) basekey := make([]byte, 32)
hasher := MakeHashFunc(SHA3Hash) hasher := MakeHashFunc(SHA3Hash)

View file

@ -3,7 +3,6 @@ package storage
import ( import (
"errors" "errors"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/contracts/ens" "github.com/ethereum/go-ethereum/contracts/ens"
) )
@ -24,35 +23,30 @@ func (b *baseValidator) hashSize() int {
return b.hashsize return b.hashsize
} }
type OwnerValidator interface {
ValidateOwner(name string, address common.Address) (bool, error)
}
// ENS validation of mutable resource owners // ENS validation of mutable resource owners
type ENSValidator struct { type ENSValidator struct {
*baseValidator *baseValidator
api *ens.ENS api OwnerValidator
} }
func NewENSValidator(contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts, signFunc SignFunc) (*ENSValidator, error) { func NewENSValidator(contractaddress common.Address, ownerValidator OwnerValidator, signFunc SignFunc) *ENSValidator {
var err error return &ENSValidator{
validator := &ENSValidator{
baseValidator: &baseValidator{ baseValidator: &baseValidator{
signFunc: signFunc, signFunc: signFunc,
hashsize: common.HashLength, hashsize: common.HashLength,
}, },
api: ownerValidator,
} }
validator.api, err = ens.NewENS(transactOpts, contractaddress, backend)
if err != nil {
return nil, err
}
return validator, nil
} }
func (self *ENSValidator) checkAccess(name string, address common.Address) (bool, error) { func (self *ENSValidator) checkAccess(name string, address common.Address) (bool, error) {
owneraddr, err := self.api.Owner(self.nameHash(name)) return self.api.ValidateOwner(name, address)
if err != nil {
return false, err
}
return owneraddr == address, nil
} }
func (self *ENSValidator) nameHash(name string) common.Hash { func (self *ENSValidator) NameHash(name string) common.Hash {
return ens.EnsNode(name) return ens.EnsNode(name)
} }

View file

@ -65,7 +65,7 @@ func (f *fakeBackend) Commit() {
f.blocknumber++ f.blocknumber++
} }
func (f *fakeBackend) HeaderByNumber(context context.Context, bigblock *big.Int) (*types.Header, error) { func (f *fakeBackend) HeaderByNumber(context context.Context, name string, bigblock *big.Int) (*types.Header, error) {
f.blocknumber++ f.blocknumber++
biggie := big.NewInt(f.blocknumber) biggie := big.NewInt(f.blocknumber)
return &types.Header{ return &types.Header{
@ -411,10 +411,11 @@ func TestResourceENSOwner(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
validator, err := NewENSValidator(contractAddr, contractbackend, transactOpts, signer.signContent) ensClient, err := ens.NewENS(transactOpts, contractAddr, contractbackend)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
validator := NewENSValidator(contractAddr, newTestResolver(ensClient), signer.signContent)
// set up rpc and create resourcehandler with ENS sim backend // set up rpc and create resourcehandler with ENS sim backend
rh, _, _, teardownTest, err := setupTest(contractbackend, validator) rh, _, _, teardownTest, err := setupTest(contractbackend, validator)
@ -458,7 +459,7 @@ func fwdBlocks(count int, backend *fakeBackend) {
} }
// create rpc and resourcehandler // create rpc and resourcehandler
func setupTest(backend ethApi, validator ResourceValidator) (rh *ResourceHandler, datadir string, signer *testSigner, teardown func(), err error) { func setupTest(backend headerGetter, validator ResourceValidator) (rh *ResourceHandler, datadir string, signer *testSigner, teardown func(), err error) {
var fsClean func() var fsClean func()
var rpcClean func() var rpcClean func()
@ -570,7 +571,7 @@ func (self *testValidator) checkAccess(name string, address common.Address) (boo
return true, nil return true, nil
} }
func (self *testValidator) nameHash(name string) common.Hash { func (self *testValidator) NameHash(name string) common.Hash {
return self.hashFunc(name) return self.hashFunc(name)
} }
@ -585,3 +586,18 @@ func getUpdateDirect(rh *ResourceHandler, key Key) ([]byte, error) {
} }
return data, nil return data, nil
} }
type testResolver struct {
ens *ens.ENS
}
func newTestResolver(ens *ens.ENS) *testResolver {
return &testResolver{
ens: ens,
}
}
func (r *testResolver) ValidateOwner(name string, address common.Address) (bool, error) {
addr, err := r.ens.Owner(ens.EnsNode(name))
return addr == address, err
}

View file

@ -21,7 +21,11 @@ import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"math/big"
"net" "net"
"strings"
"time"
"unicode"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -34,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/protocols" "github.com/ethereum/go-ethereum/p2p/protocols"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
httpapi "github.com/ethereum/go-ethereum/swarm/api/http" httpapi "github.com/ethereum/go-ethereum/swarm/api/http"
@ -84,7 +89,7 @@ func (self *Swarm) API() *SwarmAPI {
// implements node.Service // implements node.Service
// If mockStore is not nil, it will be used as the storage for chunk data. // If mockStore is not nil, it will be used as the storage for chunk data.
// MockStore should be used only for testing. // MockStore should be used only for testing.
func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, ensClient *ethclient.Client, config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err error) { func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err error) {
if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroKey) { if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroKey) {
return nil, fmt.Errorf("empty public key") return nil, fmt.Errorf("empty public key")
@ -154,31 +159,31 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, ensClient *e
} }
// set up high level api // set up high level api
transactOpts := bind.NewKeyedTransactor(self.privateKey) //transactOpts := bind.NewKeyedTransactor(self.privateKey)
var resolver *api.MultiResolver
if ensClient == nil { if len(config.EnsAPIs) > 0 {
log.Warn("No ENS, please specify non-empty --ens-api to use domain name resolution") opts := []api.MultiResolverOption{}
} else { for _, c := range config.EnsAPIs {
self.dns, err = ens.NewENS(transactOpts, config.EnsRoot, ensClient) tld, endpoint, addr := parseEnsAPIAddress(c)
if err != nil { r, err := newEnsClient(endpoint, addr, config)
return nil, err
}
}
log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar @ address %v", config.EnsRoot.Hex()))
var resourceHandler *storage.ResourceHandler
// if use resource updates
if self.config.ResourceEnabled {
var resourceValidator storage.ResourceValidator
if self.dns != nil {
resourceValidator, err = storage.NewENSValidator(config.EnsRoot, ensClient, transactOpts, storage.NewGenericResourceSigner(self.privateKey))
if err != nil { if err != nil {
return nil, err return nil, err
} }
opts = append(opts, api.MultiResolverOptionWithResolver(r, tld))
} }
resolver = api.NewMultiResolver(opts...)
self.dns = resolver
}
var resourceHandler *storage.ResourceHandler
// if use resource updates
if self.config.ResourceEnabled && resolver != nil {
resourceValidator := storage.NewENSValidator(config.EnsRoot, resolver, storage.NewGenericResourceSigner(self.privateKey))
resolver.SetNameHash(resourceValidator.NameHash)
hashfunc := storage.MakeHashFunc(storage.SHA3Hash) hashfunc := storage.MakeHashFunc(storage.SHA3Hash)
chunkStore := storage.NewResourceChunkStore(self.lstore, func(*storage.Chunk) error { return nil }) chunkStore := storage.NewResourceChunkStore(self.lstore, func(*storage.Chunk) error { return nil })
resourceHandler, err = storage.NewResourceHandler(hashfunc, chunkStore, ensClient, resourceValidator) resourceHandler, err = storage.NewResourceHandler(hashfunc, chunkStore, resolver, resourceValidator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -194,6 +199,104 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, ensClient *e
return self, nil return self, nil
} }
// parseEnsAPIAddress parses string according to format
// [tld:][contract-addr@]url and returns ENSClientConfig structure
// with endpoint, contract address and TLD.
func parseEnsAPIAddress(s string) (tld, endpoint string, addr common.Address) {
isAllLetterString := func(s string) bool {
for _, r := range s {
if !unicode.IsLetter(r) {
return false
}
}
return true
}
endpoint = s
if i := strings.Index(endpoint, ":"); i > 0 {
if isAllLetterString(endpoint[:i]) && len(endpoint) > i+2 && endpoint[i+1:i+3] != "//" {
tld = endpoint[:i]
endpoint = endpoint[i+1:]
}
}
if i := strings.Index(endpoint, "@"); i > 0 {
addr = common.HexToAddress(endpoint[:i])
endpoint = endpoint[i+1:]
}
return
}
// ensClient provides functionality for api.ResolveValidator
type ensClient struct {
*ens.ENS
*ethclient.Client
}
// newEnsClient creates a new ENS client for that is a consumer of
// a ENS API on a specific endpoint. It is used as a helper function
// for creating multiple resolvers in NewSwarm function.
func newEnsClient(endpoint string, addr common.Address, config *api.Config) (*ensClient, error) {
log.Info("connecting to ENS API", "url", endpoint)
client, err := rpc.Dial(endpoint)
if err != nil {
return nil, fmt.Errorf("error connecting to ENS API %s: %s", endpoint, err)
}
ethClient := ethclient.NewClient(client)
ensRoot := config.EnsRoot
if addr != (common.Address{}) {
ensRoot = addr
} else {
a, err := detectEnsAddr(client)
if err == nil {
ensRoot = a
} else {
log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", ensRoot), "err", err)
}
}
transactOpts := bind.NewKeyedTransactor(config.Swap.PrivateKey())
dns, err := ens.NewENS(transactOpts, ensRoot, ethClient)
if err != nil {
return nil, err
}
log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar %v @ address %v", endpoint, ensRoot.Hex()))
return &ensClient{
ENS: dns,
Client: ethClient,
}, err
}
// detectEnsAddr determines the ENS contract address by getting both the
// version and genesis hash using the client and matching them to either
// mainnet or testnet addresses
func detectEnsAddr(client *rpc.Client) (common.Address, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var version string
if err := client.CallContext(ctx, &version, "net_version"); err != nil {
return common.Address{}, err
}
block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0))
if err != nil {
return common.Address{}, err
}
switch {
case version == "1" && block.Hash() == params.MainnetGenesisHash:
log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress)
return ens.MainNetAddress, nil
case version == "3" && block.Hash() == params.TestnetGenesisHash:
log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress)
return ens.TestNetAddress, nil
default:
return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash())
}
}
/* /*
Start is called when the stack is started Start is called when the stack is started
* starts the network kademlia hive peer management * starts the network kademlia hive peer management

119
swarm/swarm_test.go Normal file
View file

@ -0,0 +1,119 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package swarm
import (
"testing"
"github.com/ethereum/go-ethereum/common"
)
func TestParseEnsAPIAddress(t *testing.T) {
for _, x := range []struct {
description string
value string
tld string
endpoint string
addr common.Address
}{
{
description: "IPC endpoint",
value: "/data/testnet/geth.ipc",
endpoint: "/data/testnet/geth.ipc",
},
{
description: "HTTP endpoint",
value: "http://127.0.0.1:1234",
endpoint: "http://127.0.0.1:1234",
},
{
description: "WS endpoint",
value: "ws://127.0.0.1:1234",
endpoint: "ws://127.0.0.1:1234",
},
{
description: "IPC Endpoint and TLD",
value: "test:/data/testnet/geth.ipc",
endpoint: "/data/testnet/geth.ipc",
tld: "test",
},
{
description: "HTTP endpoint and TLD",
value: "test:http://127.0.0.1:1234",
endpoint: "http://127.0.0.1:1234",
tld: "test",
},
{
description: "WS endpoint and TLD",
value: "test:ws://127.0.0.1:1234",
endpoint: "ws://127.0.0.1:1234",
tld: "test",
},
{
description: "IPC Endpoint and contract address",
value: "314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
endpoint: "/data/testnet/geth.ipc",
addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"),
},
{
description: "HTTP endpoint and contract address",
value: "314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
endpoint: "http://127.0.0.1:1234",
addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"),
},
{
description: "WS endpoint and contract address",
value: "314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:1234",
endpoint: "ws://127.0.0.1:1234",
addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"),
},
{
description: "IPC Endpoint, TLD and contract address",
value: "test:314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
endpoint: "/data/testnet/geth.ipc",
addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"),
tld: "test",
},
{
description: "HTTP endpoint, TLD and contract address",
value: "eth:314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
endpoint: "http://127.0.0.1:1234",
addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"),
tld: "eth",
},
{
description: "WS endpoint, TLD and contract address",
value: "eth:314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:1234",
endpoint: "ws://127.0.0.1:1234",
addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"),
tld: "eth",
},
} {
t.Run(x.description, func(t *testing.T) {
tld, endpoint, addr := parseEnsAPIAddress(x.value)
if endpoint != x.endpoint {
t.Errorf("expected Endpoint %q, got %q", x.endpoint, endpoint)
}
if addr != x.addr {
t.Errorf("expected ContractAddress %q, got %q", x.addr.String(), addr.String())
}
if tld != x.tld {
t.Errorf("expected TLD %q, got %q", x.tld, tld)
}
})
}
}

View file

@ -34,7 +34,7 @@ type fakeBackend struct {
blocknumber int64 blocknumber int64
} }
func (f *fakeBackend) HeaderByNumber(context context.Context, bigblock *big.Int) (*types.Header, error) { func (f *fakeBackend) HeaderByNumber(context context.Context, _ string, bigblock *big.Int) (*types.Header, error) {
f.blocknumber++ f.blocknumber++
biggie := big.NewInt(f.blocknumber) biggie := big.NewInt(f.blocknumber)
return &types.Header{ return &types.Header{