mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 09:53:48 +00:00
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:
commit
cc22311bf9
22 changed files with 1035 additions and 311 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
if !pp.Has(s.IDs[0]) {
|
case <-timeout.C:
|
||||||
t.Fatalf("missing peer test-0: %v (%v)", pp, s.IDs)
|
t.Fatal("timeout")
|
||||||
}
|
}
|
||||||
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"),
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,6 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
// construct a map of expectations for each node
|
||||||
|
peerExpects := make(map[discover.NodeID][]Expect)
|
||||||
|
for _, exp := range exps {
|
||||||
if exp.Msg == nil {
|
if exp.Msg == nil {
|
||||||
return errors.New("no message to expect")
|
return errors.New("no message to expect")
|
||||||
}
|
}
|
||||||
simNode, ok := self.adapter.GetNode(exp.Peer)
|
peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
if !ok {
|
||||||
return fmt.Errorf("trigger: peer %v does not exist (1- %v)", exp.Peer, len(self.IDs))
|
return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(self.IDs))
|
||||||
}
|
}
|
||||||
mockNode, ok := simNode.Services()[0].(*mockNode)
|
mockNode, ok := simNode.Services()[0].(*mockNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("trigger: peer %v is not a mock", exp.Peer)
|
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() {
|
go func() {
|
||||||
log.Trace(fmt.Sprintf("waiting for msg, %v", exp.Msg))
|
defer wg.Done()
|
||||||
errc <- mockNode.Expect(&exp)
|
|
||||||
|
// 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:
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
t := exp.Timeout
|
select {
|
||||||
if t == time.Duration(0) {
|
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() {
|
||||||
|
wg.Wait()
|
||||||
|
// close errc when all goroutines finish to return nill err from errc
|
||||||
|
close(errc)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return <-errc
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
137
swarm/api/api.go
137
swarm/api/api.go
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
137
swarm/swarm.go
137
swarm/swarm.go
|
|
@ -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)
|
||||||
|
r, err := newEnsClient(endpoint, addr, config)
|
||||||
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
|
||||||
}
|
}
|
||||||
log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar @ address %v", config.EnsRoot.Hex()))
|
|
||||||
|
|
||||||
var resourceHandler *storage.ResourceHandler
|
var resourceHandler *storage.ResourceHandler
|
||||||
// if use resource updates
|
// if use resource updates
|
||||||
if self.config.ResourceEnabled {
|
if self.config.ResourceEnabled && resolver != nil {
|
||||||
var resourceValidator storage.ResourceValidator
|
resourceValidator := storage.NewENSValidator(config.EnsRoot, resolver, storage.NewGenericResourceSigner(self.privateKey))
|
||||||
if self.dns != nil {
|
resolver.SetNameHash(resourceValidator.NameHash)
|
||||||
resourceValidator, err = storage.NewENSValidator(config.EnsRoot, ensClient, transactOpts, storage.NewGenericResourceSigner(self.privateKey))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
119
swarm/swarm_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue