mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 01:43:47 +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" {
|
||||
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
|
||||
// want to do ask read permissions from users, so just load the public posts and
|
||||
// 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()
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return "", "", common.Address{}, err
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
|
|
@ -102,10 +103,15 @@ func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
|
|||
config = bzzapi.NewConfig()
|
||||
//first load settings from config file (if provided)
|
||||
config, err = configFileOverride(config, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//override settings provided by environment variables
|
||||
config = envVarsOverride(config)
|
||||
//override settings provided by command line
|
||||
config = cmdLineOverride(config, ctx)
|
||||
//validate configuration parameters
|
||||
err = validateConfig(config)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
@ -199,12 +205,16 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
|||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -291,9 +301,8 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
|||
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, exists := os.LookupEnv(SWARM_ENV_ENS_API); exists {
|
||||
currentConfig.EnsApi = ensapi
|
||||
if ensapi := os.Getenv(SWARM_ENV_ENS_API); ensapi != "" {
|
||||
currentConfig.EnsAPIs = strings.Split(ensapi, ",")
|
||||
}
|
||||
|
||||
if ensaddr := os.Getenv(SWARM_ENV_ENS_ADDR); ensaddr != "" {
|
||||
|
|
@ -340,6 +349,43 @@ func checkDeprecated(ctx *cli.Context) {
|
|||
if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -478,3 +478,98 @@ func TestConfigCmdLineOverridesFile(t *testing.T) {
|
|||
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
|
|
@ -29,14 +27,12 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
|
|
@ -45,7 +41,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||
|
||||
|
|
@ -110,16 +105,11 @@ var (
|
|||
Usage: "Swarm Syncing enabled (default true)",
|
||||
EnvVar: SWARM_ENV_SYNC_ENABLE,
|
||||
}
|
||||
EnsAPIFlag = cli.StringFlag{
|
||||
EnsAPIFlag = cli.StringSliceFlag{
|
||||
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,
|
||||
}
|
||||
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{
|
||||
Name: "bzzapi",
|
||||
Usage: "Swarm HTTP endpoint",
|
||||
|
|
@ -180,6 +170,10 @@ var (
|
|||
Name: "ethapi",
|
||||
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
|
||||
|
|
@ -367,7 +361,6 @@ DEPRECATED: use 'swarm db clean'.
|
|||
// bzzd-specific flags
|
||||
CorsStringFlag,
|
||||
EnsAPIFlag,
|
||||
EnsAddrFlag,
|
||||
SwarmTomlConfigPathFlag,
|
||||
SwarmConfigPathFlag,
|
||||
SwarmSwapEnabledFlag,
|
||||
|
|
@ -394,6 +387,7 @@ DEPRECATED: use 'swarm db clean'.
|
|||
SwarmStoreRadius,
|
||||
//deprecated flags
|
||||
DeprecatedEthAPIFlag,
|
||||
DeprecatedEnsAddrFlag,
|
||||
}
|
||||
rpcFlags := []cli.Flag{
|
||||
utils.WSEnabledFlag,
|
||||
|
|
@ -493,38 +487,6 @@ func bzzd(ctx *cli.Context) error {
|
|||
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) {
|
||||
|
||||
//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.
|
||||
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, nil)
|
||||
return swarm.NewSwarm(ctx, swapClient, bzzconfig, nil)
|
||||
}
|
||||
//register within the ethereum node
|
||||
if err := stack.Register(boot); err != nil {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
|
@ -112,30 +110,6 @@ func SendItems(w MsgWriter, msgcode uint64, elems ...interface{}) error {
|
|||
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
|
||||
// closed when the wrapped reader returns an error or when count bytes
|
||||
// have been read.
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ devp2p subprotocols by abstracting away code standardly shared by protocols.
|
|||
* automate RLP decoding/encoding based on reflecting
|
||||
* provide the forever loop to read incoming messages
|
||||
* standardise error handling related to communication
|
||||
* standardised handshake negotiation
|
||||
* TODO: automatic generation of wire protocol specification for peers
|
||||
* standardise handshake negotiation
|
||||
|
||||
*/
|
||||
package protocols
|
||||
|
|
@ -84,7 +84,7 @@ type Error struct {
|
|||
}
|
||||
|
||||
func (e Error) Error() (message string) {
|
||||
if len(message) == 0 {
|
||||
if len(e.message) == 0 {
|
||||
name, ok := errorToString[e.Code]
|
||||
if !ok {
|
||||
panic("invalid message code")
|
||||
|
|
@ -98,13 +98,11 @@ func (e Error) Error() (message string) {
|
|||
}
|
||||
|
||||
func errorf(code int, format string, params ...interface{}) *Error {
|
||||
e := &Error{
|
||||
return &Error{
|
||||
Code: code,
|
||||
format: format,
|
||||
params: params,
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// 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 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
|
||||
// [&foo{}, &bar{}, &baz{}] would send foo, bar and baz with codes
|
||||
// 0, 1 and 2 respectively)
|
||||
// each message must have a single unique data type
|
||||
Messages []interface{}
|
||||
|
||||
initOnce sync.Once
|
||||
|
|
@ -183,8 +182,8 @@ type Peer struct {
|
|||
|
||||
// NewPeer constructs a new peer
|
||||
// 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 third argument is the CodeMap describing the protocol messages and options
|
||||
// the first two arguments are the arguments passed to p2p.Protocol.Run function
|
||||
// the third argument is the Spec describing the protocol
|
||||
func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, spec *Spec) *Peer {
|
||||
return &Peer{
|
||||
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
|
||||
// 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 {
|
||||
for {
|
||||
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.
|
||||
// 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
|
||||
// if they are useful for other protocols
|
||||
// overwrite Disconnect for testing, so that protocol readloop quits
|
||||
func (p *Peer) Drop(err error) {
|
||||
p.Disconnect(p2p.DiscSubprotocolError)
|
||||
}
|
||||
|
|
@ -222,7 +221,6 @@ func (p *Peer) Send(msg interface{}) error {
|
|||
if !found {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +251,6 @@ func (p *Peer) handleIncoming(handle func(msg interface{}) error) error {
|
|||
if err := msg.Decode(val); err != nil {
|
||||
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
|
||||
// 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
|
||||
// * 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
|
||||
// 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) {
|
||||
if _, ok := p.spec.GetCode(hs); !ok {
|
||||
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) }
|
||||
receive := func() { errc <- p.handleIncoming(handle) }
|
||||
var last bool
|
||||
for {
|
||||
if p.Inbound() == last {
|
||||
go send()
|
||||
|
||||
go func() {
|
||||
if p.Inbound() {
|
||||
receive()
|
||||
send()
|
||||
} else {
|
||||
go receive()
|
||||
send()
|
||||
receive()
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case err = <-errc:
|
||||
case <-ctx.Done():
|
||||
|
|
@ -304,10 +306,6 @@ func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interf
|
|||
if err != nil {
|
||||
return nil, errorf(ErrHandshake, err.Error())
|
||||
}
|
||||
if last {
|
||||
break
|
||||
}
|
||||
last = true
|
||||
}
|
||||
return rhs, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,21 +20,15 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||
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
|
||||
type hs0 struct {
|
||||
C uint
|
||||
|
|
@ -141,8 +135,7 @@ func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) er
|
|||
|
||||
pp.Add(peer)
|
||||
defer pp.Remove(peer)
|
||||
err = peer.Run(handle)
|
||||
return err
|
||||
return peer.Run(handle)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,8 +225,12 @@ func runModuleHandshake(t *testing.T, resp uint, errs ...error) {
|
|||
pp := p2ptest.NewTestPeerPool()
|
||||
s := protocolTester(t, pp)
|
||||
id := s.IDs[0]
|
||||
s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...)
|
||||
s.TestExchanges(moduleHandshakeExchange(id, resp)...)
|
||||
if err := s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.TestExchanges(moduleHandshakeExchange(id, resp)...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var disconnects []*p2ptest.Disconnect
|
||||
for i, err := range errs {
|
||||
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()
|
||||
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
|
||||
// here this is simply demonstrated by the peerPool
|
||||
// after the handshake negotiations peers must be added to the pool
|
||||
// time.Sleep(1)
|
||||
for !pp.Has(s.IDs[0]) {
|
||||
time.Sleep(1)
|
||||
log.Trace(fmt.Sprintf("missing peer test-0: %v (%v)", pp, s.IDs))
|
||||
tick := time.NewTicker(10 * time.Millisecond)
|
||||
timeout := time.NewTimer(1 * time.Second)
|
||||
WAIT:
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
if pp.Has(s.IDs[0]) {
|
||||
break WAIT
|
||||
}
|
||||
if !pp.Has(s.IDs[0]) {
|
||||
t.Fatalf("missing peer test-0: %v (%v)", pp, s.IDs)
|
||||
case <-timeout.C:
|
||||
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]) {
|
||||
t.Fatalf("missing peer test-1: %v (%v)", pp, s.IDs)
|
||||
}
|
||||
|
||||
// peer 0 sends kill request for peer with index <peer>
|
||||
s.TestExchanges(p2ptest.Exchange{
|
||||
err := s.TestExchanges(p2ptest.Exchange{
|
||||
Triggers: []p2ptest.Trigger{
|
||||
{
|
||||
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
|
||||
s.TestExchanges(p2ptest.Exchange{
|
||||
err = s.TestExchanges(p2ptest.Exchange{
|
||||
Triggers: []p2ptest.Trigger{
|
||||
{
|
||||
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
|
||||
var disconnects []*p2ptest.Disconnect
|
||||
for i, err := range errs {
|
||||
|
|
@ -364,7 +373,6 @@ func runMultiplePeers(t *testing.T, peer int, errs ...error) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func XTestMultiplePeersDropSelf(t *testing.T) {
|
||||
runMultiplePeers(t, 0,
|
||||
fmt.Errorf("subprotocol error"),
|
||||
|
|
|
|||
|
|
@ -37,8 +37,6 @@ import (
|
|||
|
||||
const (
|
||||
defaultDialTimeout = 15 * time.Second
|
||||
refreshPeersInterval = 30 * time.Second
|
||||
staticPeerCheckInterval = 15 * time.Second
|
||||
|
||||
// Connectivity defaults.
|
||||
maxActiveDialTasks = 16
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ import (
|
|||
"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 {
|
||||
Server *p2p.Server
|
||||
IDs []discover.NodeID
|
||||
|
|
@ -35,9 +40,9 @@ type ProtocolSession struct {
|
|||
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
|
||||
// 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
|
||||
// (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
|
||||
|
|
@ -45,9 +50,11 @@ type Exchange struct {
|
|||
Label string
|
||||
Triggers []Trigger
|
||||
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 {
|
||||
Msg interface{} // type of message to be sent
|
||||
Code uint64 // code of message is given
|
||||
|
|
@ -55,6 +62,8 @@ type Trigger struct {
|
|||
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 {
|
||||
Msg interface{} // type of message to expect
|
||||
Code uint64 // code of message is now given
|
||||
|
|
@ -62,6 +71,7 @@ type Expect struct {
|
|||
Timeout time.Duration // timeout duration for receiving
|
||||
}
|
||||
|
||||
// Disconnect represents a disconnect event, used and checked by TestDisconnected
|
||||
type Disconnect struct {
|
||||
Peer discover.NodeID // discconnected peer
|
||||
Error error // disconnect reason
|
||||
|
|
@ -90,108 +100,158 @@ func (self *ProtocolSession) trigger(trig Trigger) error {
|
|||
if t == time.Duration(0) {
|
||||
t = 1000 * time.Millisecond
|
||||
}
|
||||
alarm := time.NewTimer(t)
|
||||
select {
|
||||
case err := <-errc:
|
||||
return err
|
||||
case <-alarm.C:
|
||||
case <-time.After(t):
|
||||
return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer)
|
||||
}
|
||||
}
|
||||
|
||||
// expect checks an expectation
|
||||
func (self *ProtocolSession) expect(exp Expect) error {
|
||||
// expect checks an expectation of a message sent out by the pivot node
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
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)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(mockNodes))
|
||||
for nodeID, mockNode := range mockNodes {
|
||||
nodeID := nodeID
|
||||
mockNode := mockNode
|
||||
go func() {
|
||||
log.Trace(fmt.Sprintf("waiting for msg, %v", exp.Msg))
|
||||
errc <- mockNode.Expect(&exp)
|
||||
defer wg.Done()
|
||||
|
||||
// Sum all Expect timeouts to give the maximum
|
||||
// time for all expectations to finish.
|
||||
// mockNode.Expect checks all received messages against
|
||||
// a list of expected messages and timeout for each
|
||||
// of them can not be checked separately.
|
||||
var t time.Duration
|
||||
for _, exp := range peerExpects[nodeID] {
|
||||
if exp.Timeout == time.Duration(0) {
|
||||
t += 2000 * time.Millisecond
|
||||
} else {
|
||||
t += exp.Timeout
|
||||
}
|
||||
}
|
||||
alarm := time.NewTimer(t)
|
||||
defer alarm.Stop()
|
||||
|
||||
// expectErrc is used to check if error returned
|
||||
// from mockNode.Expect is not nil and to send it to
|
||||
// errc only in that case.
|
||||
// done channel will be closed when function
|
||||
expectErrc := make(chan error)
|
||||
go func() {
|
||||
select {
|
||||
case expectErrc <- mockNode.Expect(peerExpects[nodeID]...):
|
||||
case <-done:
|
||||
case <-alarm.C:
|
||||
}
|
||||
}()
|
||||
|
||||
t := exp.Timeout
|
||||
if t == time.Duration(0) {
|
||||
select {
|
||||
case err := <-expectErrc:
|
||||
if err != nil {
|
||||
select {
|
||||
case errc <- err:
|
||||
case <-done:
|
||||
case <-alarm.C:
|
||||
errc <- errTimedOut
|
||||
}
|
||||
}
|
||||
case <-done:
|
||||
case <-alarm.C:
|
||||
errc <- errTimedOut
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
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
|
||||
}
|
||||
alarm := time.NewTimer(t)
|
||||
select {
|
||||
case err := <-errc:
|
||||
log.Trace(fmt.Sprintf("expected msg arrives with error %v", err))
|
||||
return err
|
||||
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
|
||||
func (self *ProtocolSession) TestExchanges(exchanges ...Exchange) error {
|
||||
// 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
|
||||
}
|
||||
|
||||
// TestDisconnected tests the disconnections given as arguments
|
||||
// the disconnect structs describe what disconnect error is expected on which peer
|
||||
func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
|
||||
expects := make(map[discover.NodeID]error)
|
||||
for _, disconnect := range disconnects {
|
||||
|
|
@ -209,10 +269,8 @@ func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error
|
|||
if !ok {
|
||||
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) {
|
||||
log.Trace("error!!!")
|
||||
return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error)
|
||||
}
|
||||
delete(expects, event.Peer)
|
||||
|
|
|
|||
|
|
@ -15,13 +15,20 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
|
|
@ -31,14 +38,20 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"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 {
|
||||
*ProtocolSession
|
||||
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 {
|
||||
services := adapters.Services{
|
||||
"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
|
||||
}
|
||||
|
||||
// Stop stops the p2p server
|
||||
func (self *ProtocolTester) Stop() error {
|
||||
self.Server.Stop()
|
||||
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) {
|
||||
for _, peer := range peers {
|
||||
log.Trace(fmt.Sprintf("start node %v", peer.ID))
|
||||
|
|
@ -141,7 +157,7 @@ type mockNode struct {
|
|||
testNode
|
||||
|
||||
trigger chan *Trigger
|
||||
expect chan *Expect
|
||||
expect chan []Expect
|
||||
err chan error
|
||||
stop chan struct{}
|
||||
stopOnce sync.Once
|
||||
|
|
@ -150,7 +166,7 @@ type mockNode struct {
|
|||
func newMockNode() *mockNode {
|
||||
mock := &mockNode{
|
||||
trigger: make(chan *Trigger),
|
||||
expect: make(chan *Expect),
|
||||
expect: make(chan []Expect),
|
||||
err: make(chan error),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
|
|
@ -165,8 +181,8 @@ func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
|
|||
select {
|
||||
case trig := <-m.trigger:
|
||||
m.err <- p2p.Send(rw, trig.Code, trig.Msg)
|
||||
case exp := <-m.expect:
|
||||
m.err <- p2p.ExpectMsg(rw, exp.Code, exp.Msg)
|
||||
case exps := <-m.expect:
|
||||
m.err <- expectMsgs(rw, exps)
|
||||
case <-m.stop:
|
||||
return nil
|
||||
}
|
||||
|
|
@ -178,7 +194,7 @@ func (m *mockNode) Trigger(trig *Trigger) error {
|
|||
return <-m.err
|
||||
}
|
||||
|
||||
func (m *mockNode) Expect(exp *Expect) error {
|
||||
func (m *mockNode) Expect(exp ...Expect) error {
|
||||
m.expect <- exp
|
||||
return <-m.err
|
||||
}
|
||||
|
|
@ -187,3 +203,67 @@ func (m *mockNode) Stop() error {
|
|||
m.stopOnce.Do(func() { close(m.stop) })
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
137
swarm/api/api.go
137
swarm/api/api.go
|
|
@ -20,7 +20,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
|
|
@ -30,6 +32,8 @@ import (
|
|||
"time"
|
||||
|
||||
"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/swarm/storage"
|
||||
)
|
||||
|
|
@ -52,6 +56,139 @@ type Resolver interface {
|
|||
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
|
||||
on top of the dpa
|
||||
|
|
|
|||
|
|
@ -17,14 +17,17 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"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
|
||||
// hash if it is set, or returns a "name not found" error
|
||||
type testResolver struct {
|
||||
type testResolveValidator struct {
|
||||
hash *common.Hash
|
||||
}
|
||||
|
||||
func newTestResolver(addr string) *testResolver {
|
||||
r := &testResolver{}
|
||||
func newTestResolveValidator(addr string) *testResolveValidator {
|
||||
r := &testResolveValidator{}
|
||||
if addr != "" {
|
||||
hash := common.HexToHash(addr)
|
||||
r.hash = &hash
|
||||
|
|
@ -134,21 +137,28 @@ func newTestResolver(addr string) *testResolver {
|
|||
return r
|
||||
}
|
||||
|
||||
func (t *testResolver) Resolve(addr string) (common.Hash, error) {
|
||||
func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) {
|
||||
if t.hash == nil {
|
||||
return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr)
|
||||
}
|
||||
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
|
||||
// or ENS names
|
||||
func TestAPIResolve(t *testing.T) {
|
||||
ensAddr := "swarm.eth"
|
||||
hashAddr := "1111111111111111111111111111111111111111111111111111111111111111"
|
||||
resolvedAddr := "2222222222222222222222222222222222222222222222222222222222222222"
|
||||
doesResolve := newTestResolver(resolvedAddr)
|
||||
doesntResolve := newTestResolver("")
|
||||
doesResolve := newTestResolveValidator(resolvedAddr)
|
||||
doesntResolve := newTestResolveValidator("")
|
||||
|
||||
type test struct {
|
||||
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
|
||||
Contract common.Address
|
||||
EnsRoot common.Address
|
||||
EnsApi string
|
||||
EnsAPIs []string
|
||||
Path string
|
||||
ListenAddr string
|
||||
Port string
|
||||
|
|
@ -78,7 +78,7 @@ func NewConfig() (self *Config) {
|
|||
ListenAddr: DefaultHTTPListenAddr,
|
||||
Port: DefaultHTTPPort,
|
||||
Path: node.DefaultDataDir(),
|
||||
EnsApi: node.DefaultIPCEndpoint("geth"),
|
||||
EnsAPIs: nil,
|
||||
EnsRoot: ens.TestNetAddress,
|
||||
NetworkId: network.NetworkID,
|
||||
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 {
|
||||
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.
|
||||
//
|
||||
// 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
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
package storage
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var BlockReward = big.NewInt(5e+18)
|
||||
//
|
||||
// import "github.com/ethereum/go-ethereum/swarm/storage/encryption"
|
||||
//
|
||||
// type HasherStore interface {
|
||||
// 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 {
|
||||
hashSize() int
|
||||
checkAccess(string, common.Address) (bool, error)
|
||||
nameHash(string) common.Hash // nameHashFunc
|
||||
NameHash(string) common.Hash // nameHashFunc
|
||||
sign(common.Hash) (Signature, error) // SignFunc
|
||||
}
|
||||
|
||||
type ethApi interface {
|
||||
HeaderByNumber(context.Context, *big.Int) (*types.Header, error)
|
||||
type headerGetter interface {
|
||||
HeaderByNumber(context.Context, string, *big.Int) (*types.Header, error)
|
||||
}
|
||||
|
||||
// Mutable resource is an entity which allows updates to a resource
|
||||
|
|
@ -158,7 +158,7 @@ type ethApi interface {
|
|||
type ResourceHandler struct {
|
||||
ChunkStore
|
||||
validator ResourceValidator
|
||||
ethClient ethApi
|
||||
ethClient headerGetter
|
||||
resources map[string]*resource
|
||||
hashPool sync.Pool
|
||||
resourceLock sync.RWMutex
|
||||
|
|
@ -167,7 +167,7 @@ type ResourceHandler struct {
|
|||
}
|
||||
|
||||
// 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{
|
||||
ChunkStore: chunkStore,
|
||||
ethClient: ethClient,
|
||||
|
|
@ -182,7 +182,7 @@ func NewResourceHandler(hasher SwarmHasher, chunkStore ChunkStore, ethClient eth
|
|||
}
|
||||
|
||||
if rh.validator != nil {
|
||||
rh.nameHash = rh.validator.nameHash
|
||||
rh.nameHash = rh.validator.NameHash
|
||||
} else {
|
||||
rh.nameHash = func(name string) common.Hash {
|
||||
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
|
||||
currentblock, err := self.getBlock(ctx)
|
||||
currentblock, err := self.getBlock(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -374,7 +374,7 @@ func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.H
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentblock, err := self.getBlock(ctx)
|
||||
currentblock, err := self.getBlock(ctx, name)
|
||||
if err != nil {
|
||||
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
|
||||
currentblock, err := self.getBlock(ctx)
|
||||
currentblock, err := self.getBlock(ctx, name)
|
||||
if err != nil {
|
||||
return nil, NewResourceError(ErrIO, fmt.Sprintf("Could not get block height: %v", err))
|
||||
}
|
||||
|
|
@ -655,8 +655,8 @@ func (self *ResourceHandler) Close() {
|
|||
self.ChunkStore.Close()
|
||||
}
|
||||
|
||||
func (self *ResourceHandler) getBlock(ctx context.Context) (uint64, error) {
|
||||
blockheader, err := self.ethClient.HeaderByNumber(ctx, nil)
|
||||
func (self *ResourceHandler) getBlock(ctx context.Context, name string) (uint64, error) {
|
||||
blockheader, err := self.ethClient.HeaderByNumber(ctx, name, nil)
|
||||
if err != nil {
|
||||
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
|
||||
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)
|
||||
basekey := make([]byte, 32)
|
||||
hasher := MakeHashFunc(SHA3Hash)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package storage
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||
)
|
||||
|
|
@ -24,35 +23,30 @@ func (b *baseValidator) hashSize() int {
|
|||
return b.hashsize
|
||||
}
|
||||
|
||||
type OwnerValidator interface {
|
||||
ValidateOwner(name string, address common.Address) (bool, error)
|
||||
}
|
||||
|
||||
// ENS validation of mutable resource owners
|
||||
type ENSValidator struct {
|
||||
*baseValidator
|
||||
api *ens.ENS
|
||||
api OwnerValidator
|
||||
}
|
||||
|
||||
func NewENSValidator(contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts, signFunc SignFunc) (*ENSValidator, error) {
|
||||
var err error
|
||||
validator := &ENSValidator{
|
||||
func NewENSValidator(contractaddress common.Address, ownerValidator OwnerValidator, signFunc SignFunc) *ENSValidator {
|
||||
return &ENSValidator{
|
||||
baseValidator: &baseValidator{
|
||||
signFunc: signFunc,
|
||||
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) {
|
||||
owneraddr, err := self.api.Owner(self.nameHash(name))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return owneraddr == address, nil
|
||||
return self.api.ValidateOwner(name, address)
|
||||
}
|
||||
|
||||
func (self *ENSValidator) nameHash(name string) common.Hash {
|
||||
func (self *ENSValidator) NameHash(name string) common.Hash {
|
||||
return ens.EnsNode(name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func (f *fakeBackend) Commit() {
|
|||
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++
|
||||
biggie := big.NewInt(f.blocknumber)
|
||||
return &types.Header{
|
||||
|
|
@ -411,10 +411,11 @@ func TestResourceENSOwner(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validator, err := NewENSValidator(contractAddr, contractbackend, transactOpts, signer.signContent)
|
||||
ensClient, err := ens.NewENS(transactOpts, contractAddr, contractbackend)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
validator := NewENSValidator(contractAddr, newTestResolver(ensClient), signer.signContent)
|
||||
|
||||
// set up rpc and create resourcehandler with ENS sim backend
|
||||
rh, _, _, teardownTest, err := setupTest(contractbackend, validator)
|
||||
|
|
@ -458,7 +459,7 @@ func fwdBlocks(count int, backend *fakeBackend) {
|
|||
}
|
||||
|
||||
// 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 rpcClean func()
|
||||
|
|
@ -570,7 +571,7 @@ func (self *testValidator) checkAccess(name string, address common.Address) (boo
|
|||
return true, nil
|
||||
}
|
||||
|
||||
func (self *testValidator) nameHash(name string) common.Hash {
|
||||
func (self *testValidator) NameHash(name string) common.Hash {
|
||||
return self.hashFunc(name)
|
||||
}
|
||||
|
||||
|
|
@ -585,3 +586,18 @@ func getUpdateDirect(rh *ResourceHandler, key Key) ([]byte, error) {
|
|||
}
|
||||
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"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -34,6 +38,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/protocols"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
httpapi "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||
|
|
@ -84,7 +89,7 @@ func (self *Swarm) API() *SwarmAPI {
|
|||
// implements node.Service
|
||||
// If mockStore is not nil, it will be used as the storage for chunk data.
|
||||
// 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) {
|
||||
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
|
||||
transactOpts := bind.NewKeyedTransactor(self.privateKey)
|
||||
|
||||
if ensClient == nil {
|
||||
log.Warn("No ENS, please specify non-empty --ens-api to use domain name resolution")
|
||||
} else {
|
||||
self.dns, err = ens.NewENS(transactOpts, config.EnsRoot, ensClient)
|
||||
//transactOpts := bind.NewKeyedTransactor(self.privateKey)
|
||||
var resolver *api.MultiResolver
|
||||
if len(config.EnsAPIs) > 0 {
|
||||
opts := []api.MultiResolverOption{}
|
||||
for _, c := range config.EnsAPIs {
|
||||
tld, endpoint, addr := parseEnsAPIAddress(c)
|
||||
r, err := newEnsClient(endpoint, addr, config)
|
||||
if err != nil {
|
||||
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
|
||||
// if use resource updates
|
||||
if self.config.ResourceEnabled {
|
||||
var resourceValidator storage.ResourceValidator
|
||||
if self.dns != nil {
|
||||
resourceValidator, err = storage.NewENSValidator(config.EnsRoot, ensClient, transactOpts, storage.NewGenericResourceSigner(self.privateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if self.config.ResourceEnabled && resolver != nil {
|
||||
resourceValidator := storage.NewENSValidator(config.EnsRoot, resolver, storage.NewGenericResourceSigner(self.privateKey))
|
||||
resolver.SetNameHash(resourceValidator.NameHash)
|
||||
hashfunc := storage.MakeHashFunc(storage.SHA3Hash)
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -194,6 +199,104 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, ensClient *e
|
|||
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
|
||||
* 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
|
||||
}
|
||||
|
||||
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++
|
||||
biggie := big.NewInt(f.blocknumber)
|
||||
return &types.Header{
|
||||
|
|
|
|||
Loading…
Reference in a new issue