Merge pull request #4 from ethereum/master

update
This commit is contained in:
jkcomment 2019-01-29 21:55:55 +09:00 committed by GitHub
commit 6fe1915e0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
144 changed files with 9608 additions and 1475 deletions

3
.github/CODEOWNERS vendored
View file

@ -10,4 +10,7 @@ les/ @zsfelfoldi
light/ @zsfelfoldi light/ @zsfelfoldi
mobile/ @karalabe mobile/ @karalabe
p2p/ @fjl @zsfelfoldi p2p/ @fjl @zsfelfoldi
p2p/simulations @zelig @nonsense @janos @justelad
p2p/protocols @zelig @nonsense @janos @justelad
p2p/testing @zelig @nonsense @janos @justelad
whisper/ @gballet @gluk256 whisper/ @gballet @gluk256

View file

@ -156,7 +156,7 @@ matrix:
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
before_install: before_install:
- curl https://storage.googleapis.com/golang/go1.11.4.linux-amd64.tar.gz | tar -xz - curl https://storage.googleapis.com/golang/go1.11.5.linux-amd64.tar.gz | tar -xz
- export PATH=`pwd`/go/bin:$PATH - export PATH=`pwd`/go/bin:$PATH
- export GOROOT=`pwd`/go - export GOROOT=`pwd`/go
- export GOPATH=$HOME/go - export GOPATH=$HOME/go

View file

@ -74,7 +74,7 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value {
func set(dst, src reflect.Value) error { func set(dst, src reflect.Value) error {
dstType, srcType := dst.Type(), src.Type() dstType, srcType := dst.Type(), src.Type()
switch { switch {
case dstType.Kind() == reflect.Interface: case dstType.Kind() == reflect.Interface && dst.Elem().IsValid():
return set(dst.Elem(), src) return set(dst.Elem(), src)
case dstType.Kind() == reflect.Ptr && dstType.Elem() != derefbigT: case dstType.Kind() == reflect.Ptr && dstType.Elem() != derefbigT:
return set(dst.Elem(), src) return set(dst.Elem(), src)

View file

@ -512,6 +512,11 @@ func TestMethodMultiReturn(t *testing.T) {
Int *big.Int Int *big.Int
} }
newInterfaceSlice := func(len int) interface{} {
slice := make([]interface{}, len)
return &slice
}
abi, data, expected := methodMultiReturn(require.New(t)) abi, data, expected := methodMultiReturn(require.New(t))
bigint := new(big.Int) bigint := new(big.Int)
var testCases = []struct { var testCases = []struct {
@ -539,6 +544,16 @@ func TestMethodMultiReturn(t *testing.T) {
&[2]interface{}{&expected.Int, &expected.String}, &[2]interface{}{&expected.Int, &expected.String},
"", "",
"Can unpack into an array", "Can unpack into an array",
}, {
&[2]interface{}{},
&[2]interface{}{expected.Int, expected.String},
"",
"Can unpack into interface array",
}, {
newInterfaceSlice(2),
&[]interface{}{expected.Int, expected.String},
"",
"Can unpack into interface slice",
}, { }, {
&[]interface{}{new(int), new(int)}, &[]interface{}{new(int), new(int)},
&[]interface{}{&expected.Int, &expected.String}, &[]interface{}{&expected.Int, &expected.String},

View file

@ -28,7 +28,7 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/usbwallet/internal/trezor" "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -41,6 +41,9 @@ import (
// encoded passphrase. // encoded passphrase.
var ErrTrezorPINNeeded = errors.New("trezor: pin needed") var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
// ErrTrezorPassphraseNeeded is returned if opening the trezor requires a passphrase
var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed")
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange // errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
// if the device replies with a mismatching header. This usually means the device // if the device replies with a mismatching header. This usually means the device
// is in browser mode. // is in browser mode.
@ -48,12 +51,13 @@ var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header")
// trezorDriver implements the communication with a Trezor hardware wallet. // trezorDriver implements the communication with a Trezor hardware wallet.
type trezorDriver struct { type trezorDriver struct {
device io.ReadWriter // USB device connection to communicate through device io.ReadWriter // USB device connection to communicate through
version [3]uint32 // Current version of the Trezor firmware version [3]uint32 // Current version of the Trezor firmware
label string // Current textual label of the Trezor device label string // Current textual label of the Trezor device
pinwait bool // Flags whether the device is waiting for PIN entry pinwait bool // Flags whether the device is waiting for PIN entry
failure error // Any failure that would make the device unusable passphrasewait bool // Flags whether the device is waiting for passphrase entry
log log.Logger // Contextual logger to tag the trezor with its id failure error // Any failure that would make the device unusable
log log.Logger // Contextual logger to tag the trezor with its id
} }
// newTrezorDriver creates a new instance of a Trezor USB protocol driver. // newTrezorDriver creates a new instance of a Trezor USB protocol driver.
@ -79,7 +83,7 @@ func (w *trezorDriver) Status() (string, error) {
} }
// Open implements usbwallet.driver, attempting to initialize the connection to // Open implements usbwallet.driver, attempting to initialize the connection to
// the Trezor hardware wallet. Initializing the Trezor is a two phase operation: // the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation:
// * The first phase is to initialize the connection and read the wallet's // * The first phase is to initialize the connection and read the wallet's
// features. This phase is invoked is the provided passphrase is empty. The // features. This phase is invoked is the provided passphrase is empty. The
// device will display the pinpad as a result and will return an appropriate // device will display the pinpad as a result and will return an appropriate
@ -87,11 +91,13 @@ func (w *trezorDriver) Status() (string, error) {
// * The second phase is to unlock access to the Trezor, which is done by the // * The second phase is to unlock access to the Trezor, which is done by the
// user actually providing a passphrase mapping a keyboard keypad to the pin // user actually providing a passphrase mapping a keyboard keypad to the pin
// number of the user (shuffled according to the pinpad displayed). // number of the user (shuffled according to the pinpad displayed).
// * If needed the device will ask for passphrase which will require calling
// open again with the actual passphrase (3rd phase)
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error { func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
w.device, w.failure = device, nil w.device, w.failure = device, nil
// If phase 1 is requested, init the connection and wait for user callback // If phase 1 is requested, init the connection and wait for user callback
if passphrase == "" { if passphrase == "" && !w.passphrasewait {
// If we're already waiting for a PIN entry, insta-return // If we're already waiting for a PIN entry, insta-return
if w.pinwait { if w.pinwait {
return ErrTrezorPINNeeded return ErrTrezorPINNeeded
@ -104,26 +110,46 @@ func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()} w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
w.label = features.GetLabel() w.label = features.GetLabel()
// Do a manual ping, forcing the device to ask for its PIN // Do a manual ping, forcing the device to ask for its PIN and Passphrase
askPin := true askPin := true
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin}, new(trezor.PinMatrixRequest), new(trezor.Success)) askPassphrase := true
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success))
if err != nil { if err != nil {
return err return err
} }
// Only return the PIN request if the device wasn't unlocked until now // Only return the PIN request if the device wasn't unlocked until now
if res == 1 { switch res {
return nil // Device responded with trezor.Success case 0:
w.pinwait = true
return ErrTrezorPINNeeded
case 1:
w.pinwait = false
w.passphrasewait = true
return ErrTrezorPassphraseNeeded
case 2:
return nil // responded with trezor.Success
} }
w.pinwait = true
return ErrTrezorPINNeeded
} }
// Phase 2 requested with actual PIN entry // Phase 2 requested with actual PIN entry
w.pinwait = false if w.pinwait {
w.pinwait = false
if _, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success)); err != nil { res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest))
w.failure = err if err != nil {
return err w.failure = err
return err
}
if res == 1 {
w.passphrasewait = true
return ErrTrezorPassphraseNeeded
}
} else if w.passphrasewait {
w.passphrasewait = false
if _, err := w.trezorExchange(&trezor.PassphraseAck{Passphrase: &passphrase}, new(trezor.Success)); err != nil {
w.failure = err
return err
}
} }
return nil return nil
} }

View file

@ -23,8 +23,8 @@ environment:
install: install:
- git submodule update --init - git submodule update --init
- rmdir C:\go /s /q - rmdir C:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.11.4.windows-%GETH_ARCH%.zip - appveyor DownloadFile https://storage.googleapis.com/golang/go1.11.5.windows-%GETH_ARCH%.zip
- 7z x go1.11.4.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - 7z x go1.11.5.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
- go version - go version
- gcc --version - gcc --version

View file

@ -513,7 +513,7 @@ func doDebianSource(cmdline []string) {
for _, distro := range debDistros { for _, distro := range debDistros {
meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables) meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
pkgdir := stageDebianSource(*workdir, meta) pkgdir := stageDebianSource(*workdir, meta)
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc") debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc", "-d")
debuild.Dir = pkgdir debuild.Dir = pkgdir
build.MustRun(debuild) build.MustRun(debuild)
@ -523,7 +523,7 @@ func doDebianSource(cmdline []string) {
build.MustRunCommand("debsign", changes) build.MustRunCommand("debsign", changes)
} }
if *upload != "" { if *upload != "" {
build.MustRunCommand("dput", *upload, changes) build.MustRunCommand("dput", "--passive", "--no-upload-log", *upload, changes)
} }
} }
} }

View file

@ -112,12 +112,13 @@ func main() {
if !realaddr.IP.IsLoopback() { if !realaddr.IP.IsLoopback() {
go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
} }
// TODO: react to external IP changes over time.
if ext, err := natm.ExternalIP(); err == nil { if ext, err := natm.ExternalIP(); err == nil {
realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port} realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
} }
} }
printNotice(&nodeKey.PublicKey, *realaddr)
if *runv5 { if *runv5 {
if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil { if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil {
utils.Fatalf("%v", err) utils.Fatalf("%v", err)
@ -136,3 +137,13 @@ func main() {
select {} select {}
} }
func printNotice(nodeKey *ecdsa.PublicKey, addr net.UDPAddr) {
if addr.IP.IsUnspecified() {
addr.IP = net.IP{127, 0, 0, 1}
}
n := enode.NewV4(nodeKey, addr.IP, 0, addr.Port)
fmt.Println(n.String())
fmt.Println("Note: you're using cmd/bootnode, a developer tool.")
fmt.Println("We recommend using a regular node as bootstrap node for production deployments.")
}

View file

@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/dashboard" "github.com/ethereum/go-ethereum/dashboard"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/graphql"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
@ -124,6 +125,7 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
} }
// Apply flags. // Apply flags.
utils.SetULC(ctx, &cfg.Eth)
utils.SetNodeConfig(ctx, &cfg.Node) utils.SetNodeConfig(ctx, &cfg.Node)
stack, err := node.New(&cfg.Node) stack, err := node.New(&cfg.Node)
if err != nil { if err != nil {
@ -176,6 +178,13 @@ func makeFullNode(ctx *cli.Context) *node.Node {
utils.RegisterShhService(stack, &cfg.Shh) utils.RegisterShhService(stack, &cfg.Shh)
} }
// Configure GraphQL if required
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
if err := graphql.RegisterGraphQLService(stack, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts); err != nil {
utils.Fatalf("Failed to register the Ethereum service: %v", err)
}
}
// Add the Ethereum Stats daemon if requested. // Add the Ethereum Stats daemon if requested.
if cfg.Ethstats.URL != "" { if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)

View file

@ -38,7 +38,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
) )
const ( const (
@ -82,6 +82,10 @@ var (
utils.TxPoolAccountQueueFlag, utils.TxPoolAccountQueueFlag,
utils.TxPoolGlobalQueueFlag, utils.TxPoolGlobalQueueFlag,
utils.TxPoolLifetimeFlag, utils.TxPoolLifetimeFlag,
utils.ULCModeConfigFlag,
utils.OnlyAnnounceModeFlag,
utils.ULCTrustedNodesFlag,
utils.ULCMinTrustedFractionFlag,
utils.SyncModeFlag, utils.SyncModeFlag,
utils.GCModeFlag, utils.GCModeFlag,
utils.LightServFlag, utils.LightServFlag,
@ -141,6 +145,11 @@ var (
utils.RPCEnabledFlag, utils.RPCEnabledFlag,
utils.RPCListenAddrFlag, utils.RPCListenAddrFlag,
utils.RPCPortFlag, utils.RPCPortFlag,
utils.GraphQLEnabledFlag,
utils.GraphQLListenAddrFlag,
utils.GraphQLPortFlag,
utils.GraphQLCORSDomainFlag,
utils.GraphQLVirtualHostsFlag,
utils.RPCApiFlag, utils.RPCApiFlag,
utils.WSEnabledFlag, utils.WSEnabledFlag,
utils.WSListenAddrFlag, utils.WSListenAddrFlag,
@ -164,7 +173,7 @@ var (
utils.MetricsInfluxDBDatabaseFlag, utils.MetricsInfluxDBDatabaseFlag,
utils.MetricsInfluxDBUsernameFlag, utils.MetricsInfluxDBUsernameFlag,
utils.MetricsInfluxDBPasswordFlag, utils.MetricsInfluxDBPasswordFlag,
utils.MetricsInfluxDBHostTagFlag, utils.MetricsInfluxDBTagsFlag,
} }
) )

View file

@ -26,7 +26,7 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
) )
// AppHelpTemplate is the test template for the default, global app help topic. // AppHelpTemplate is the test template for the default, global app help topic.
@ -229,7 +229,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.MetricsInfluxDBDatabaseFlag, utils.MetricsInfluxDBDatabaseFlag,
utils.MetricsInfluxDBUsernameFlag, utils.MetricsInfluxDBUsernameFlag,
utils.MetricsInfluxDBPasswordFlag, utils.MetricsInfluxDBPasswordFlag,
utils.MetricsInfluxDBHostTagFlag, utils.MetricsInfluxDBTagsFlag,
}, },
}, },
{ {

View file

@ -223,28 +223,29 @@ type parityChainSpec struct {
} `json:"engine"` } `json:"engine"`
Params struct { Params struct {
AccountStartNonce hexutil.Uint64 `json:"accountStartNonce"` AccountStartNonce hexutil.Uint64 `json:"accountStartNonce"`
MaximumExtraDataSize hexutil.Uint64 `json:"maximumExtraDataSize"` MaximumExtraDataSize hexutil.Uint64 `json:"maximumExtraDataSize"`
MinGasLimit hexutil.Uint64 `json:"minGasLimit"` MinGasLimit hexutil.Uint64 `json:"minGasLimit"`
GasLimitBoundDivisor math2.HexOrDecimal64 `json:"gasLimitBoundDivisor"` GasLimitBoundDivisor math2.HexOrDecimal64 `json:"gasLimitBoundDivisor"`
NetworkID hexutil.Uint64 `json:"networkID"` NetworkID hexutil.Uint64 `json:"networkID"`
ChainID hexutil.Uint64 `json:"chainID"` ChainID hexutil.Uint64 `json:"chainID"`
MaxCodeSize hexutil.Uint64 `json:"maxCodeSize"` MaxCodeSize hexutil.Uint64 `json:"maxCodeSize"`
MaxCodeSizeTransition hexutil.Uint64 `json:"maxCodeSizeTransition"` MaxCodeSizeTransition hexutil.Uint64 `json:"maxCodeSizeTransition"`
EIP98Transition hexutil.Uint64 `json:"eip98Transition"` EIP98Transition hexutil.Uint64 `json:"eip98Transition"`
EIP150Transition hexutil.Uint64 `json:"eip150Transition"` EIP150Transition hexutil.Uint64 `json:"eip150Transition"`
EIP160Transition hexutil.Uint64 `json:"eip160Transition"` EIP160Transition hexutil.Uint64 `json:"eip160Transition"`
EIP161abcTransition hexutil.Uint64 `json:"eip161abcTransition"` EIP161abcTransition hexutil.Uint64 `json:"eip161abcTransition"`
EIP161dTransition hexutil.Uint64 `json:"eip161dTransition"` EIP161dTransition hexutil.Uint64 `json:"eip161dTransition"`
EIP155Transition hexutil.Uint64 `json:"eip155Transition"` EIP155Transition hexutil.Uint64 `json:"eip155Transition"`
EIP140Transition hexutil.Uint64 `json:"eip140Transition"` EIP140Transition hexutil.Uint64 `json:"eip140Transition"`
EIP211Transition hexutil.Uint64 `json:"eip211Transition"` EIP211Transition hexutil.Uint64 `json:"eip211Transition"`
EIP214Transition hexutil.Uint64 `json:"eip214Transition"` EIP214Transition hexutil.Uint64 `json:"eip214Transition"`
EIP658Transition hexutil.Uint64 `json:"eip658Transition"` EIP658Transition hexutil.Uint64 `json:"eip658Transition"`
EIP145Transition hexutil.Uint64 `json:"eip145Transition"` EIP145Transition hexutil.Uint64 `json:"eip145Transition"`
EIP1014Transition hexutil.Uint64 `json:"eip1014Transition"` EIP1014Transition hexutil.Uint64 `json:"eip1014Transition"`
EIP1052Transition hexutil.Uint64 `json:"eip1052Transition"` EIP1052Transition hexutil.Uint64 `json:"eip1052Transition"`
EIP1283Transition hexutil.Uint64 `json:"eip1283Transition"` EIP1283Transition hexutil.Uint64 `json:"eip1283Transition"`
EIP1283DisableTransition hexutil.Uint64 `json:"eip1283DisableTransition"`
} `json:"params"` } `json:"params"`
Genesis struct { Genesis struct {
@ -347,6 +348,11 @@ func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []strin
if num := genesis.Config.ConstantinopleBlock; num != nil { if num := genesis.Config.ConstantinopleBlock; num != nil {
spec.setConstantinople(num) spec.setConstantinople(num)
} }
// ConstantinopleFix (remove eip-1283)
if num := genesis.Config.PetersburgBlock; num != nil {
spec.setConstantinopleFix(num)
}
spec.Params.MaximumExtraDataSize = (hexutil.Uint64)(params.MaximumExtraDataSize) spec.Params.MaximumExtraDataSize = (hexutil.Uint64)(params.MaximumExtraDataSize)
spec.Params.MinGasLimit = (hexutil.Uint64)(params.MinGasLimit) spec.Params.MinGasLimit = (hexutil.Uint64)(params.MinGasLimit)
spec.Params.GasLimitBoundDivisor = (math2.HexOrDecimal64)(params.GasLimitBoundDivisor) spec.Params.GasLimitBoundDivisor = (math2.HexOrDecimal64)(params.GasLimitBoundDivisor)
@ -441,6 +447,10 @@ func (spec *parityChainSpec) setConstantinople(num *big.Int) {
spec.Params.EIP1283Transition = n spec.Params.EIP1283Transition = n
} }
func (spec *parityChainSpec) setConstantinopleFix(num *big.Int) {
spec.Params.EIP1283DisableTransition = hexutil.Uint64(num.Uint64())
}
// pyEthereumGenesisSpec represents the genesis specification format used by the // pyEthereumGenesisSpec represents the genesis specification format used by the
// Python Ethereum implementation. // Python Ethereum implementation.
type pyEthereumGenesisSpec struct { type pyEthereumGenesisSpec struct {

View file

@ -608,30 +608,31 @@ func deployDashboard(client *sshClient, network string, conf *config, config *da
bootPython[i] = "'" + boot + "'" bootPython[i] = "'" + boot + "'"
} }
template.Must(template.New("").Parse(dashboardContent)).Execute(indexfile, map[string]interface{}{ template.Must(template.New("").Parse(dashboardContent)).Execute(indexfile, map[string]interface{}{
"Network": network, "Network": network,
"NetworkID": conf.Genesis.Config.ChainID, "NetworkID": conf.Genesis.Config.ChainID,
"NetworkTitle": strings.Title(network), "NetworkTitle": strings.Title(network),
"EthstatsPage": config.ethstats, "EthstatsPage": config.ethstats,
"ExplorerPage": config.explorer, "ExplorerPage": config.explorer,
"WalletPage": config.wallet, "WalletPage": config.wallet,
"FaucetPage": config.faucet, "FaucetPage": config.faucet,
"GethGenesis": network + ".json", "GethGenesis": network + ".json",
"Bootnodes": conf.bootnodes, "Bootnodes": conf.bootnodes,
"BootnodesFlat": strings.Join(conf.bootnodes, ","), "BootnodesFlat": strings.Join(conf.bootnodes, ","),
"Ethstats": statsLogin, "Ethstats": statsLogin,
"Ethash": conf.Genesis.Config.Ethash != nil, "Ethash": conf.Genesis.Config.Ethash != nil,
"CppGenesis": network + "-cpp.json", "CppGenesis": network + "-cpp.json",
"CppBootnodes": strings.Join(bootCpp, " "), "CppBootnodes": strings.Join(bootCpp, " "),
"HarmonyGenesis": network + "-harmony.json", "HarmonyGenesis": network + "-harmony.json",
"HarmonyBootnodes": strings.Join(bootHarmony, " "), "HarmonyBootnodes": strings.Join(bootHarmony, " "),
"ParityGenesis": network + "-parity.json", "ParityGenesis": network + "-parity.json",
"PythonGenesis": network + "-python.json", "PythonGenesis": network + "-python.json",
"PythonBootnodes": strings.Join(bootPython, ","), "PythonBootnodes": strings.Join(bootPython, ","),
"Homestead": conf.Genesis.Config.HomesteadBlock, "Homestead": conf.Genesis.Config.HomesteadBlock,
"Tangerine": conf.Genesis.Config.EIP150Block, "Tangerine": conf.Genesis.Config.EIP150Block,
"Spurious": conf.Genesis.Config.EIP155Block, "Spurious": conf.Genesis.Config.EIP155Block,
"Byzantium": conf.Genesis.Config.ByzantiumBlock, "Byzantium": conf.Genesis.Config.ByzantiumBlock,
"Constantinople": conf.Genesis.Config.ConstantinopleBlock, "Constantinople": conf.Genesis.Config.ConstantinopleBlock,
"ConstantinopleFix": conf.Genesis.Config.PetersburgBlock,
}) })
files[filepath.Join(workdir, "index.html")] = indexfile.Bytes() files[filepath.Join(workdir, "index.html")] = indexfile.Bytes()

View file

@ -223,6 +223,10 @@ func (w *wizard) manageGenesis() {
fmt.Printf("Which block should Constantinople come into effect? (default = %v)\n", w.conf.Genesis.Config.ConstantinopleBlock) fmt.Printf("Which block should Constantinople come into effect? (default = %v)\n", w.conf.Genesis.Config.ConstantinopleBlock)
w.conf.Genesis.Config.ConstantinopleBlock = w.readDefaultBigInt(w.conf.Genesis.Config.ConstantinopleBlock) w.conf.Genesis.Config.ConstantinopleBlock = w.readDefaultBigInt(w.conf.Genesis.Config.ConstantinopleBlock)
fmt.Println()
fmt.Printf("Which block should Constantinople-Fix (remove EIP-1283) come into effect? (default = %v)\n", w.conf.Genesis.Config.ConstantinopleBlock)
w.conf.Genesis.Config.PetersburgBlock = w.readDefaultBigInt(w.conf.Genesis.Config.ConstantinopleBlock)
out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ") out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ")
fmt.Printf("Chain configuration updated:\n\n%s\n", out) fmt.Printf("Chain configuration updated:\n\n%s\n", out)

View file

@ -17,61 +17,8 @@
package main package main
var SwarmBootnodes = []string{ var SwarmBootnodes = []string{
// Foundation Swarm Gateway Cluster // EF Swarm Bootnode - AWS - eu-central-1
"enode://e5c6f9215c919a5450a7b8c14c22535607b69f2c8e1e7f6f430cb25d7a2c27cd1df4c4f18ad7c1d7e5162e271ffcd3f20b1a1467fb6e790e7d727f3b2193de97@52.232.7.187:30399", "enode://4c113504601930bf2000c29bcd98d1716b6167749f58bad703bae338332fe93cc9d9204f08afb44100dc7bea479205f5d162df579f9a8f76f8b402d339709023@3.122.203.99:30301",
"enode://9b2fe07e69ccc7db5fef15793dab7d7d2e697ed92132d6e9548218e68a34613a8671ad03a6658d862b468ed693cae8a0f8f8d37274e4a657ffb59ca84676e45b@52.232.7.187:30400", // EF Swarm Bootnode - AWS - us-west-2
"enode://76c1059162c93ef9df0f01097c824d17c492634df211ef4c806935b349082233b63b90c23970254b3b7138d630400f7cf9b71e80355a446a8b733296cb04169a@52.232.7.187:30401", "enode://89f2ede3371bff1ad9f2088f2012984e280287a4e2b68007c2a6ad994909c51886b4a8e9e2ecc97f9910aca538398e0a5804b0ee80a187fde1ba4f32626322ba@52.35.212.179:30301",
"enode://ce46bbe2a8263145d65252d52da06e000ad350ed09c876a71ea9544efa42f63c1e1b6cc56307373aaad8f9dd069c90d0ed2dd1530106200e16f4ca681dd8ae2d@52.232.7.187:30402",
"enode://f431e0d6008a6c35c6e670373d828390c8323e53da8158e7bfc43cf07e632cc9e472188be8df01decadea2d4a068f1428caba769b632554a8fb0607bc296988f@52.232.7.187:30403",
"enode://174720abfff83d7392f121108ae50ea54e04889afe020df883655c0f6cb95414db945a0228d8982fe000d86fc9f4b7669161adc89cd7cd56f78f01489ab2b99b@52.232.7.187:30404",
"enode://2ae89be4be61a689b6f9ecee4360a59e185e010ab750f14b63b4ae43d4180e872e18e3437d4386ce44875dc7cc6eb761acba06412fe3178f3dac1dab3b65703e@52.232.7.187:30405",
"enode://24abebe1c0e6d75d6052ce3219a87be8573fd6397b4cb51f0773b83abba9b3d872bfb273cdc07389715b87adfac02f5235f5241442c5089802cbd8d42e310fce@52.232.7.187:30406",
"enode://d08dfa46bfbbdbcaafbb6e34abee4786610f6c91e0b76d7881f0334ac10dda41d8c1f2b6eedffb4493293c335c0ad46776443b2208d1fbbb9e1a90b25ee4eef2@52.232.7.187:30407",
"enode://8d95eb0f837d27581a43668ed3b8783d69dc4e84aa3edd7a0897e026155c8f59c8702fdc0375ee7bac15757c9c78e1315d9b73e4ce59c936db52ea4ae2f501c7@52.232.7.187:30408",
"enode://a5967cc804aebd422baaaba9f06f27c9e695ccab335b61088130f8cbe64e3cdf78793868c7051dfc06eecfe844fad54bc7f6dfaed9db3c7ecef279cb829c25fb@52.232.7.187:30409",
"enode://5f00134d81a8f2ebcc46f8766f627f492893eda48138f811b7de2168308171968f01710bca6da05764e74f14bae41652f554e6321f1aed85fa3461e89d075dbf@52.232.7.187:30410",
"enode://b2142b79b01a5aa66a5e23cc35e78219a8e97bc2412a6698cee24ae02e87078b725d71730711bd62e25ff1aa8658c6633778af8ac14c63814a337c3dd0ebda9f@52.232.7.187:30411",
"enode://1ffa7651094867d6486ce3ef46d27a052c2cb968b618346c6df7040322c7efc3337547ba85d4cbba32e8b31c42c867202554735c06d4c664b9afada2ed0c4b3c@52.232.7.187:30412",
"enode://129e0c3d5f5df12273754f6f703d2424409fa4baa599e0b758c55600169313887855e75b082028d2302ec034b303898cd697cc7ae8256ba924ce927510da2c8d@52.232.7.187:30413",
"enode://419e2dc0d2f5b022cf16b0e28842658284909fa027a0fbbb5e2b755e7f846ea02a8f0b66a7534981edf6a7bcf8a14855344c6668e2cd4476ccd35a11537c9144@52.232.7.187:30414",
"enode://23d55ad900583231b91f2f62e3f72eb498b342afd58b682be3af052eed62b5651094471065981de33d8786f075f05e3cca499503b0ac8ae84b2a06e99f5b0723@52.232.7.187:30415",
"enode://bc56e4158c00e9f616d7ea533def20a89bef959df4e62a768ff238ff4e1e9223f57ecff969941c20921bad98749baae311c0fbebce53bf7bbb9d3dc903640990@52.232.7.187:30416",
"enode://433ce15199c409875e7e72fffd69fdafe746f17b20f0d5555281722a65fde6c80328fab600d37d8624509adc072c445ce0dad4a1c01cff6acf3132c11d429d4d@52.232.7.187:30417",
"enode://632ee95b8f0eac51ef89ceb29313fef3a60050181d66a6b125583b1a225a7694b252edc016efb58aa3b251da756cb73280842a022c658ed405223b2f58626343@52.232.7.187:30418",
"enode://4a0f9bcff7a4b9ee453fb298d0fb222592efe121512e30cd72fef631beb8c6a15153a1456eb073ee18551c0e003c569651a101892dc4124e90b933733a498bb5@52.232.7.187:30419",
"enode://f0d80fbc72d16df30e19aac3051eb56a7aff0c8367686702e01ea132d8b0b3ee00cadd6a859d2cca98ec68d3d574f8a8a87dba2347ec1e2818dc84bc3fa34fae@52.232.7.187:30420",
"enode://a199146906e4f9f2b94b195a8308d9a59a3564b92efaab898a4243fe4c2ad918b7a8e4853d9d901d94fad878270a2669d644591299c3d43de1b298c00b92b4a7@52.232.7.187:30421",
"enode://052036ea8736b37adbfb684d90ce43e11b3591b51f31489d7c726b03618dea4f73b1e659deb928e6bf40564edcdcf08351643f42db3d4ca1c2b5db95dad59e94@52.232.7.187:30422",
"enode://460e2b8c6da8f12fac96c836e7d108f4b7ec55a1c64631bb8992339e117e1c28328fee83af863196e20af1487a655d13e5ceba90e980e92502d5bac5834c1f71@52.232.7.187:30423",
"enode://6d2cdd13741b2e72e9031e1b93c6d9a4e68de2844aa4e939f6a8a8498a7c1d7e2ee4c64217e92a6df08c9a32c6764d173552810ef1bd2ecb356532d389dd2136@52.232.7.187:30424",
"enode://62105fc25ce2cd5b299647f47eaa9211502dc76f0e9f461df915782df7242ac3223e3db04356ae6ed2977ccac20f0b16864406e9ca514a40a004cb6a5d0402aa@52.232.7.187:30425",
"enode://e0e388fc520fd493c33f0ce16685e6f98fb6aec28f2edc14ee6b179594ee519a896425b0025bb6f0e182dd3e468443f19c70885fbc66560d000093a668a86aa8@52.232.7.187:30426",
"enode://63f3353a72521ea10022127a4fe6b4acbef197c3fe668fd9f4805542d8a6fcf79f6335fbab62d180a35e19b739483e740858b113fdd7c13a26ad7b4e318a5aef@52.232.7.187:30427",
"enode://33a42b927085678d4aefd4e70b861cfca6ef5f6c143696c4f755973fd29e64c9e658cad57a66a687a7a156da1e3688b1fbdd17bececff2ee009fff038fa5666b@52.232.7.187:30428",
"enode://259ab5ab5c1daee3eab7e3819ab3177b82d25c29e6c2444fdd3f956e356afae79a72840ccf2d0665fe82c81ebc3b3734da1178ac9fd5d62c67e674b69f86b6be@52.232.7.187:30429",
"enode://558bccad7445ce3fd8db116ed6ab4aed1324fdbdac2348417340c1764dc46d46bffe0728e5b7d5c36f12e794c289f18f57f08f085d2c65c9910a5c7a65b6a66a@52.232.7.187:30430",
"enode://abe60937a0657ffded718e3f84a32987286983be257bdd6004775c4b525747c2b598f4fac49c8de324de5ce75b22673fa541a7ce2d555fb7f8ca325744ae3577@52.232.7.187:30431",
"enode://bce6f0aaa5b230742680084df71d4f026b3eff7f564265599216a1b06b765303fdc9325de30ffd5dfdaf302ce4b14322891d2faea50ce2ca298d7409f5858339@52.232.7.187:30432",
"enode://21b957c4e03277d42be6660730ec1b93f540764f26c6abdb54d006611139c7081248486206dfbf64fcaffd62589e9c6b8ea77a5297e4b21a605f1bcf49483ed0@52.232.7.187:30433",
"enode://ff104e30e64f24c3d7328acee8b13354e5551bc8d60bb25ecbd9632d955c7e34bb2d969482d173355baad91c8282f8b592624eb3929151090da3b4448d4d58fb@52.232.7.187:30434",
"enode://c76e2b5f81a521bceaec1518926a21380a345df9cf463461562c6845795512497fb67679e155fc96a74350f8b78de8f4c135dd52b106dbbb9795452021d09ea5@52.232.7.187:30435",
"enode://3288fd860105164f3e9b69934c4eb18f7146cfab31b5a671f994e21a36e9287766e5f9f075aefbc404538c77f7c2eb2a4495020a7633a1c3970d94e9fa770aeb@52.232.7.187:30436",
"enode://6cea859c7396d46b20cfcaa80f9a11cd112f8684f2f782f7b4c0e1e0af9212113429522075101923b9b957603e6c32095a6a07b5e5e35183c521952ee108dfaf@52.232.7.187:30437",
"enode://f628ec56e4ca8317cc24cc4ac9b27b95edcce7b96e1c7f3b53e30de4a8580fe44f2f0694a513bdb0a431acaf2824074d6ace4690247bbc34c14f426af8c056ea@52.232.7.187:30438",
"enode://055ec8b26fc105c4f97970a1cce9773a5e34c03f511b839db742198a1c571e292c54aa799e9afb991cc8a560529b8cdf3e0c344bc6c282aff2f68eec59361ddf@52.232.7.187:30439",
"enode://48cb0d430c328974226aa33a931d8446cd5a8d40f3ead8f4ce7ad60faa1278192eb6d58bed91258d63e81f255fc107eec2425ce2ae8b22350dd556076e160610@52.232.7.187:30440",
"enode://3fadb7af7f770d5ffc6b073b8d42834bebb18ce1fe8a4fe270d2b799e7051327093960dc61d9a18870db288f7746a0e6ea2a013cd6ab0e5f97ca08199473aace@52.232.7.187:30441",
"enode://a5d7168024c9992769cf380ffa559a64b4f39a29d468f579559863814eb0ae0ed689ac0871a3a2b4c78b03297485ec322d578281131ef5d5c09a4beb6200a97a@52.232.7.187:30442",
"enode://9c57744c5b2c2d71abcbe80512652f9234d4ab041b768a2a886ab390fe6f184860f40e113290698652d7e20a8ac74d27ac8671db23eb475b6c5e6253e4693bf8@52.232.7.187:30443",
"enode://daca9ff0c3176045a0e0ed228dee00ec86bc0939b135dc6b1caa23745d20fd0332e1ee74ad04020e89df56c7146d831a91b89d15ca3df05ba7618769fefab376@52.232.7.187:30444",
"enode://a3f6af59428cb4b9acb198db15ef5554fa43c2b0c18e468a269722d64a27218963a2975eaf82750b6262e42192b5e3669ea51337b4cda62b33987981bc5e0c1a@52.232.7.187:30445",
"enode://fe571422fa4651c3354c85dac61911a6a6520dd3c0332967a49d4133ca30e16a8a4946fa73ca2cb5de77917ea701a905e1c3015b2f4defcd53132b61cc84127a@52.232.7.187:30446",
// Mainframe
"enode://ee9a5a571ea6c8a59f9a8bb2c569c865e922b41c91d09b942e8c1d4dd2e1725bd2c26149da14de1f6321a2c6fdf1e07c503c3e093fb61696daebf74d6acd916b@54.186.219.160:30399",
"enode://a03f0562ecb8a992ad5242345535e73483cdc18ab934d36bf24b567d43447c2cea68f89f1d51d504dd13acc30f24ebce5a150bea2ccb1b722122ce4271dc199d@52.67.248.147:30399",
"enode://e2cbf9eafd85903d3b1c56743035284320695e0072bc8d7396e0542aa5e1c321b236f67eab66b79c2f15d4447fa4bbe74dd67d0467da23e7eb829f60ec8a812b@13.58.169.1:30399",
"enode://8b8c6bda6047f1cad9fab2db4d3d02b7aa26279902c32879f7bcd4a7d189fee77fdc36ee151ce6b84279b4792e72578fd529d2274d014132465758fbfee51cee@13.209.13.15:30399",
"enode://63f6a8818927e429585287cf2ca0cb9b11fa990b7b9b331c2962cdc6f21807a2473b26e8256225c26caff70d7218e59586d704d49061452c6852e382c885d03c@35.154.106.174:30399",
"enode://ed4bd3b794ed73f18e6dcc70c6624dfec63b5654f6ab54e8f40b16eff8afbd342d4230e099ddea40e84423f81b2d2ea79799dc345257b1fec6f6c422c9d008f7@52.213.20.99:30399",
} }

View file

@ -79,6 +79,7 @@ const (
SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH" SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH"
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY" SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY" SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
SWARM_ENV_BOOTNODE_MODE = "SWARM_BOOTNODE_MODE"
SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD" SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD"
SWARM_AUTO_DEFAULTPATH = "SWARM_AUTO_DEFAULTPATH" SWARM_AUTO_DEFAULTPATH = "SWARM_AUTO_DEFAULTPATH"
GETH_ENV_DATADIR = "GETH_DATADIR" GETH_ENV_DATADIR = "GETH_DATADIR"
@ -164,10 +165,9 @@ func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config
return config, err return config, err
} }
//override the current config with whatever is provided through the command line // cmdLineOverride overrides the current config with whatever is provided through the command line
//most values are not allowed a zero value (empty string), if not otherwise noted // most values are not allowed a zero value (empty string), if not otherwise noted
func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config { func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config {
if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" { if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" {
currentConfig.BzzAccount = keyid currentConfig.BzzAccount = keyid
} }
@ -258,14 +258,17 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
currentConfig.LocalStoreParams.CacheCapacity = storeCacheCapacity currentConfig.LocalStoreParams.CacheCapacity = storeCacheCapacity
} }
if ctx.GlobalIsSet(SwarmBootnodeModeFlag.Name) {
currentConfig.BootnodeMode = ctx.GlobalBool(SwarmBootnodeModeFlag.Name)
}
return currentConfig return currentConfig
} }
//override the current config with whatver is provided in environment variables // envVarsOverride overrides the current config with whatver is provided in environment variables
//most values are not allowed a zero value (empty string), if not otherwise noted // most values are not allowed a zero value (empty string), if not otherwise noted
func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) { func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
if keyid := os.Getenv(SWARM_ENV_ACCOUNT); keyid != "" { if keyid := os.Getenv(SWARM_ENV_ACCOUNT); keyid != "" {
currentConfig.BzzAccount = keyid currentConfig.BzzAccount = keyid
} }
@ -364,6 +367,14 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
currentConfig.Cors = cors currentConfig.Cors = cors
} }
if bm := os.Getenv(SWARM_ENV_BOOTNODE_MODE); bm != "" {
bootnodeMode, err := strconv.ParseBool(bm)
if err != nil {
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_BOOTNODE_MODE, err)
}
currentConfig.BootnodeMode = bootnodeMode
}
return currentConfig return currentConfig
} }

View file

@ -156,6 +156,10 @@ var (
Name: "compressed", Name: "compressed",
Usage: "Prints encryption keys in compressed form", Usage: "Prints encryption keys in compressed form",
} }
SwarmBootnodeModeFlag = cli.BoolFlag{
Name: "bootnode-mode",
Usage: "Run Swarm in Bootnode mode",
}
SwarmFeedNameFlag = cli.StringFlag{ SwarmFeedNameFlag = cli.StringFlag{
Name: "name", Name: "name",
Usage: "User-defined name for the new feed, limited to 32 characters. If combined with topic, it will refer to a subtopic with this name", Usage: "User-defined name for the new feed, limited to 32 characters. If combined with topic, it will refer to a subtopic with this name",

View file

@ -66,9 +66,10 @@ OPTIONS:
{{end}}{{end}} {{end}}{{end}}
` `
var ( // Git SHA1 commit hash of the release (set via linker flags)
gitCommit string // Git SHA1 commit hash of the release (set via linker flags) // this variable will be assigned if corresponding parameter is passed with install, but not with test
) // e.g.: go install -ldflags "-X main.gitCommit=ed1312d01b19e04ef578946226e5d8069d5dfd5a" ./cmd/swarm
var gitCommit string
//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
var ( var (
@ -89,6 +90,7 @@ var defaultNodeConfig = node.DefaultConfig
// This init function sets defaults so cmd/swarm can run alongside geth. // This init function sets defaults so cmd/swarm can run alongside geth.
func init() { func init() {
sv.GitCommit = gitCommit
defaultNodeConfig.Name = clientIdentifier defaultNodeConfig.Name = clientIdentifier
defaultNodeConfig.Version = sv.VersionWithCommit(gitCommit) defaultNodeConfig.Version = sv.VersionWithCommit(gitCommit)
defaultNodeConfig.P2P.ListenAddr = ":30399" defaultNodeConfig.P2P.ListenAddr = ":30399"
@ -154,7 +156,6 @@ func init() {
utils.BootnodesFlag, utils.BootnodesFlag,
utils.KeyStoreDirFlag, utils.KeyStoreDirFlag,
utils.ListenPortFlag, utils.ListenPortFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag, utils.DiscoveryV5Flag,
utils.NetrestrictFlag, utils.NetrestrictFlag,
utils.NodeKeyFileFlag, utils.NodeKeyFileFlag,
@ -187,6 +188,8 @@ func init() {
SwarmUploadDefaultPath, SwarmUploadDefaultPath,
SwarmUpFromStdinFlag, SwarmUpFromStdinFlag,
SwarmUploadMimeType, SwarmUploadMimeType,
// bootnode mode
SwarmBootnodeModeFlag,
// storage flags // storage flags
SwarmStorePath, SwarmStorePath,
SwarmStoreCapacity, SwarmStoreCapacity,
@ -227,12 +230,17 @@ func main() {
func keys(ctx *cli.Context) error { func keys(ctx *cli.Context) error {
privateKey := getPrivKey(ctx) privateKey := getPrivKey(ctx)
pub := hex.EncodeToString(crypto.FromECDSAPub(&privateKey.PublicKey)) pubkey := crypto.FromECDSAPub(&privateKey.PublicKey)
pubkeyhex := hex.EncodeToString(pubkey)
pubCompressed := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)) pubCompressed := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey))
bzzkey := crypto.Keccak256Hash(pubkey).Hex()
if !ctx.Bool(SwarmCompressedFlag.Name) { if !ctx.Bool(SwarmCompressedFlag.Name) {
fmt.Println(fmt.Sprintf("publicKey=%s", pub)) fmt.Println(fmt.Sprintf("bzzkey=%s", bzzkey[2:]))
fmt.Println(fmt.Sprintf("publicKey=%s", pubkeyhex))
} }
fmt.Println(fmt.Sprintf("publicKeyCompressed=%s", pubCompressed)) fmt.Println(fmt.Sprintf("publicKeyCompressed=%s", pubCompressed))
return nil return nil
} }
@ -272,6 +280,10 @@ func bzzd(ctx *cli.Context) error {
setSwarmBootstrapNodes(ctx, &cfg) setSwarmBootstrapNodes(ctx, &cfg)
//setup the ethereum node //setup the ethereum node
utils.SetNodeConfig(ctx, &cfg) utils.SetNodeConfig(ctx, &cfg)
//always disable discovery from p2p package - swarm discovery is done with the `hive` protocol
cfg.P2P.NoDiscovery = true
stack, err := node.New(&cfg) stack, err := node.New(&cfg)
if err != nil { if err != nil {
utils.Fatalf("can't create node: %v", err) utils.Fatalf("can't create node: %v", err)
@ -294,6 +306,15 @@ func bzzd(ctx *cli.Context) error {
stack.Stop() stack.Stop()
}() }()
// add swarm bootnodes, because swarm doesn't use p2p package's discovery discv5
go func() {
s := stack.Server()
for _, n := range cfg.P2P.BootstrapNodes {
s.AddPeer(n)
}
}()
stack.Wait() stack.Wait()
return nil return nil
} }

View file

@ -254,7 +254,6 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
node.Cmd = runSwarm(t, node.Cmd = runSwarm(t,
"--port", p2pPort, "--port", p2pPort,
"--nat", "extip:127.0.0.1", "--nat", "extip:127.0.0.1",
"--nodiscover",
"--datadir", dir, "--datadir", dir,
"--ipcpath", conf.IPCPath, "--ipcpath", conf.IPCPath,
"--ens-api", "", "--ens-api", "",
@ -330,7 +329,6 @@ func newTestNode(t *testing.T, dir string) *testNode {
node.Cmd = runSwarm(t, node.Cmd = runSwarm(t,
"--port", p2pPort, "--port", p2pPort,
"--nat", "extip:127.0.0.1", "--nat", "extip:127.0.0.1",
"--nodiscover",
"--datadir", dir, "--datadir", dir,
"--ipcpath", conf.IPCPath, "--ipcpath", conf.IPCPath,
"--ens-api", "", "--ens-api", "",

View file

@ -130,7 +130,7 @@ func main() {
swarmmetrics.MetricsInfluxDBDatabaseFlag, swarmmetrics.MetricsInfluxDBDatabaseFlag,
swarmmetrics.MetricsInfluxDBUsernameFlag, swarmmetrics.MetricsInfluxDBUsernameFlag,
swarmmetrics.MetricsInfluxDBPasswordFlag, swarmmetrics.MetricsInfluxDBPasswordFlag,
swarmmetrics.MetricsInfluxDBHostTagFlag, swarmmetrics.MetricsInfluxDBTagsFlag,
}...) }...)
app.Flags = append(app.Flags, tracing.Flags...) app.Flags = append(app.Flags, tracing.Flags...)
@ -148,6 +148,12 @@ func main() {
Usage: "feed update generate, upload and sync", Usage: "feed update generate, upload and sync",
Action: cliFeedUploadAndSync, Action: cliFeedUploadAndSync,
}, },
{
Name: "upload_speed",
Aliases: []string{"u"},
Usage: "measure upload speed",
Action: cliUploadSpeed,
},
} }
sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.FlagsByName(app.Flags))
@ -177,13 +183,14 @@ func emitMetrics(ctx *cli.Context) error {
database = ctx.GlobalString(swarmmetrics.MetricsInfluxDBDatabaseFlag.Name) database = ctx.GlobalString(swarmmetrics.MetricsInfluxDBDatabaseFlag.Name)
username = ctx.GlobalString(swarmmetrics.MetricsInfluxDBUsernameFlag.Name) username = ctx.GlobalString(swarmmetrics.MetricsInfluxDBUsernameFlag.Name)
password = ctx.GlobalString(swarmmetrics.MetricsInfluxDBPasswordFlag.Name) password = ctx.GlobalString(swarmmetrics.MetricsInfluxDBPasswordFlag.Name)
hosttag = ctx.GlobalString(swarmmetrics.MetricsInfluxDBHostTagFlag.Name) tags = ctx.GlobalString(swarmmetrics.MetricsInfluxDBTagsFlag.Name)
) )
return influxdb.InfluxDBWithTagsOnce(gethmetrics.DefaultRegistry, endpoint, database, username, password, "swarm-smoke.", map[string]string{
"host": hosttag, tagsMap := utils.SplitTagsFlag(tags)
"version": gitCommit, tagsMap["version"] = gitCommit
"filesize": fmt.Sprintf("%v", filesize), tagsMap["filesize"] = fmt.Sprintf("%v", filesize)
})
return influxdb.InfluxDBWithTagsOnce(gethmetrics.DefaultRegistry, endpoint, database, username, password, "swarm-smoke.", tagsMap)
} }
return nil return nil

View file

@ -49,6 +49,10 @@ func generateEndpoints(scheme string, cluster string, app string, from int, to i
for port := from; port < to; port++ { for port := from; port < to; port++ {
endpoints = append(endpoints, fmt.Sprintf("%s://%v.swarm-gateways.net", scheme, port)) endpoints = append(endpoints, fmt.Sprintf("%s://%v.swarm-gateways.net", scheme, port))
} }
} else if cluster == "private-internal" {
for port := from; port < to; port++ {
endpoints = append(endpoints, fmt.Sprintf("%s://swarm-private-internal-%v:8500", scheme, port))
}
} else { } else {
for port := from; port < to; port++ { for port := from; port < to; port++ {
endpoints = append(endpoints, fmt.Sprintf("%s://%s-%v-%s.stg.swarm-gateways.net", scheme, app, port, cluster)) endpoints = append(endpoints, fmt.Sprintf("%s://%s-%v-%s.stg.swarm-gateways.net", scheme, app, port, cluster))
@ -86,9 +90,8 @@ func cliUploadAndSync(c *cli.Context) error {
func uploadAndSync(c *cli.Context) error { func uploadAndSync(c *cli.Context) error {
defer func(now time.Time) { defer func(now time.Time) {
totalTime := time.Since(now) totalTime := time.Since(now)
log.Info("total time", "time", totalTime, "kb", filesize) log.Info("total time", "time", totalTime, "kb", filesize)
metrics.GetOrRegisterCounter("upload-and-sync.total-time", nil).Inc(int64(totalTime)) metrics.GetOrRegisterResettingTimer("upload-and-sync.total-time", nil).Update(totalTime)
}(time.Now()) }(time.Now())
generateEndpoints(scheme, cluster, appName, from, to) generateEndpoints(scheme, cluster, appName, from, to)
@ -103,7 +106,7 @@ func uploadAndSync(c *cli.Context) error {
log.Error(err.Error()) log.Error(err.Error())
return err return err
} }
metrics.GetOrRegisterCounter("upload-and-sync.upload-time", nil).Inc(int64(time.Since(t1))) metrics.GetOrRegisterResettingTimer("upload-and-sync.upload-time", nil).UpdateSince(t1)
fhash, err := digest(bytes.NewReader(randomBytes)) fhash, err := digest(bytes.NewReader(randomBytes))
if err != nil { if err != nil {
@ -125,30 +128,28 @@ func uploadAndSync(c *cli.Context) error {
for { for {
start := time.Now() start := time.Now()
err := fetch(hash, endpoint, fhash, ruid) err := fetch(hash, endpoint, fhash, ruid)
fetchTime := time.Since(start)
if err != nil { if err != nil {
continue continue
} }
metrics.GetOrRegisterMeter("upload-and-sync.single.fetch-time", nil).Mark(int64(fetchTime)) metrics.GetOrRegisterResettingTimer("upload-and-sync.single.fetch-time", nil).UpdateSince(start)
wg.Done() wg.Done()
return return
} }
}(endpoints[randIndex], ruid) }(endpoints[randIndex], ruid)
} else { } else {
for _, endpoint := range endpoints { for _, endpoint := range endpoints[1:] {
ruid := uuid.New()[:8] ruid := uuid.New()[:8]
wg.Add(1) wg.Add(1)
go func(endpoint string, ruid string) { go func(endpoint string, ruid string) {
for { for {
start := time.Now() start := time.Now()
err := fetch(hash, endpoint, fhash, ruid) err := fetch(hash, endpoint, fhash, ruid)
fetchTime := time.Since(start)
if err != nil { if err != nil {
continue continue
} }
metrics.GetOrRegisterMeter("upload-and-sync.each.fetch-time", nil).Mark(int64(fetchTime)) metrics.GetOrRegisterResettingTimer("upload-and-sync.each.fetch-time", nil).UpdateSince(start)
wg.Done() wg.Done()
return return
} }
@ -166,8 +167,6 @@ func fetch(hash string, endpoint string, original []byte, ruid string) error {
ctx, sp := spancontext.StartSpan(context.Background(), "upload-and-sync.fetch") ctx, sp := spancontext.StartSpan(context.Background(), "upload-and-sync.fetch")
defer sp.Finish() defer sp.Finish()
log.Trace("sleeping", "ruid", ruid)
time.Sleep(3 * time.Second)
log.Trace("http get request", "ruid", ruid, "api", endpoint, "hash", hash) log.Trace("http get request", "ruid", ruid, "api", endpoint, "hash", hash)
var tn time.Time var tn time.Time

View file

@ -0,0 +1,98 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
"fmt"
"os"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/testutil"
cli "gopkg.in/urfave/cli.v1"
)
var endpoint string
//just use the first endpoint
func generateEndpoint(scheme string, cluster string, app string, from int) {
if cluster == "prod" {
endpoint = fmt.Sprintf("%s://%v.swarm-gateways.net", scheme, from)
} else if cluster == "private-internal" {
endpoint = fmt.Sprintf("%s://swarm-private-internal-%v:8500", scheme, from)
} else {
endpoint = fmt.Sprintf("%s://%s-%v-%s.stg.swarm-gateways.net", scheme, app, from, cluster)
}
}
func cliUploadSpeed(c *cli.Context) error {
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(verbosity), log.StreamHandler(os.Stdout, log.TerminalFormat(true))))
metrics.GetOrRegisterCounter("upload-speed", nil).Inc(1)
errc := make(chan error)
go func() {
errc <- uploadSpeed(c)
}()
select {
case err := <-errc:
if err != nil {
metrics.GetOrRegisterCounter("upload-speed.fail", nil).Inc(1)
}
return err
case <-time.After(time.Duration(timeout) * time.Second):
metrics.GetOrRegisterCounter("upload-speed.timeout", nil).Inc(1)
return fmt.Errorf("timeout after %v sec", timeout)
}
}
func uploadSpeed(c *cli.Context) error {
defer func(now time.Time) {
totalTime := time.Since(now)
log.Info("total time", "time", totalTime, "kb", filesize)
metrics.GetOrRegisterCounter("upload-speed.total-time", nil).Inc(int64(totalTime))
}(time.Now())
generateEndpoint(scheme, cluster, appName, from)
seed := int(time.Now().UnixNano() / 1e6)
log.Info("uploading to "+endpoint, "seed", seed)
randomBytes := testutil.RandomBytes(seed, filesize*1000)
t1 := time.Now()
hash, err := upload(&randomBytes, endpoint)
if err != nil {
log.Error(err.Error())
return err
}
metrics.GetOrRegisterCounter("upload-speed.upload-time", nil).Inc(int64(time.Since(t1)))
fhash, err := digest(bytes.NewReader(randomBytes))
if err != nil {
log.Error(err.Error())
return err
}
log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash))
return nil
}

View file

@ -0,0 +1,157 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/simulations"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/swarm/network"
"github.com/ethereum/go-ethereum/swarm/network/simulation"
cli "gopkg.in/urfave/cli.v1"
)
// create is used as the entry function for "create" app command.
func create(ctx *cli.Context) error {
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(ctx.Int("verbosity")), log.StreamHandler(os.Stdout, log.TerminalFormat(true))))
if len(ctx.Args()) < 1 {
return errors.New("argument should be the filename to verify or write-to")
}
filename, err := touchPath(ctx.Args()[0])
if err != nil {
return err
}
return createSnapshot(filename, ctx.Int("nodes"), strings.Split(ctx.String("services"), ","))
}
// createSnapshot creates a new snapshot on filesystem with provided filename,
// number of nodes and service names.
func createSnapshot(filename string, nodes int, services []string) (err error) {
log.Debug("create snapshot", "filename", filename, "nodes", nodes, "services", services)
sim := simulation.New(map[string]simulation.ServiceFunc{
"bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) {
addr := network.NewAddr(ctx.Config.Node())
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
hp := network.NewHiveParams()
hp.KeepAliveInterval = time.Duration(200) * time.Millisecond
hp.Discovery = true // discovery must be enabled when creating a snapshot
config := &network.BzzConfig{
OverlayAddr: addr.Over(),
UnderlayAddr: addr.Under(),
HiveParams: hp,
}
return network.NewBzz(config, kad, nil, nil, nil), nil, nil
},
})
defer sim.Close()
_, err = sim.AddNodes(nodes)
if err != nil {
return fmt.Errorf("add nodes: %v", err)
}
err = sim.Net.ConnectNodesRing(nil)
if err != nil {
return fmt.Errorf("connect nodes: %v", err)
}
ctx, cancelSimRun := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancelSimRun()
if _, err := sim.WaitTillHealthy(ctx); err != nil {
return fmt.Errorf("wait for healthy kademlia: %v", err)
}
var snap *simulations.Snapshot
if len(services) > 0 {
// If service names are provided, include them in the snapshot.
// But, check if "bzz" service is not among them to remove it
// form the snapshot as it exists on snapshot creation.
var removeServices []string
var wantBzz bool
for _, s := range services {
if s == "bzz" {
wantBzz = true
break
}
}
if !wantBzz {
removeServices = []string{"bzz"}
}
snap, err = sim.Net.SnapshotWithServices(services, removeServices)
} else {
snap, err = sim.Net.Snapshot()
}
if err != nil {
return fmt.Errorf("create snapshot: %v", err)
}
jsonsnapshot, err := json.Marshal(snap)
if err != nil {
return fmt.Errorf("json encode snapshot: %v", err)
}
return ioutil.WriteFile(filename, jsonsnapshot, 0666)
}
// touchPath creates an empty file and all subdirectories
// that are missing.
func touchPath(filename string) (string, error) {
if path.IsAbs(filename) {
if _, err := os.Stat(filename); err == nil {
// path exists, overwrite
return filename, nil
}
}
d, f := path.Split(filename)
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
return "", err
}
_, err = os.Stat(path.Join(dir, filename))
if err == nil {
// path exists, overwrite
return filename, nil
}
dirPath := path.Join(dir, d)
filePath := path.Join(dirPath, f)
if d != "" {
err = os.MkdirAll(dirPath, os.ModeDir)
if err != nil {
return "", err
}
}
return filePath, nil
}

View file

@ -0,0 +1,143 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"runtime"
"sort"
"strconv"
"strings"
"testing"
"github.com/ethereum/go-ethereum/p2p/simulations"
)
// TestSnapshotCreate is a high level e2e test that tests for snapshot generation.
// It runs a few "create" commands with different flag values and loads generated
// snapshot files to validate their content.
func TestSnapshotCreate(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip()
}
for _, v := range []struct {
name string
nodes int
services string
}{
{
name: "defaults",
},
{
name: "more nodes",
nodes: defaultNodes + 5,
},
{
name: "services",
services: "stream,pss,zorglub",
},
{
name: "services with bzz",
services: "bzz,pss",
},
} {
t.Run(v.name, func(t *testing.T) {
t.Parallel()
file, err := ioutil.TempFile("", "swarm-snapshot")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
if err = file.Close(); err != nil {
t.Error(err)
}
args := []string{"create"}
if v.nodes > 0 {
args = append(args, "--nodes", strconv.Itoa(v.nodes))
}
if v.services != "" {
args = append(args, "--services", v.services)
}
testCmd := runSnapshot(t, append(args, file.Name())...)
testCmd.ExpectExit()
if code := testCmd.ExitStatus(); code != 0 {
t.Fatalf("command exit code %v, expected 0", code)
}
f, err := os.Open(file.Name())
if err != nil {
t.Fatal(err)
}
defer func() {
err := f.Close()
if err != nil {
t.Error("closing snapshot file", "err", err)
}
}()
b, err := ioutil.ReadAll(f)
if err != nil {
t.Fatal(err)
}
var snap simulations.Snapshot
err = json.Unmarshal(b, &snap)
if err != nil {
t.Fatal(err)
}
wantNodes := v.nodes
if wantNodes == 0 {
wantNodes = defaultNodes
}
gotNodes := len(snap.Nodes)
if gotNodes != wantNodes {
t.Errorf("got %v nodes, want %v", gotNodes, wantNodes)
}
if len(snap.Conns) == 0 {
t.Error("no connections in a snapshot")
}
var wantServices []string
if v.services != "" {
wantServices = strings.Split(v.services, ",")
} else {
wantServices = []string{"bzz"}
}
// sort service names so they can be comparable
// as strings to every node sorted services
sort.Strings(wantServices)
for i, n := range snap.Nodes {
gotServices := n.Node.Config.Services
sort.Strings(gotServices)
if fmt.Sprint(gotServices) != fmt.Sprint(wantServices) {
t.Errorf("got services %v for node %v, want %v", gotServices, i, wantServices)
}
}
})
}
}

View file

@ -0,0 +1,82 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"os"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/log"
cli "gopkg.in/urfave/cli.v1"
)
var gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
// default value for "create" command --nodes flag
const defaultNodes = 10
func main() {
err := newApp().Run(os.Args)
if err != nil {
log.Error(err.Error())
os.Exit(1)
}
}
// newApp construct a new instance of Swarm Snapshot Utility.
// Method Run is called on it in the main function and in tests.
func newApp() (app *cli.App) {
app = utils.NewApp(gitCommit, "Swarm Snapshot Utility")
app.Name = "swarm-snapshot"
app.Usage = ""
// app flags (for all commands)
app.Flags = []cli.Flag{
cli.IntFlag{
Name: "verbosity",
Value: 1,
Usage: "verbosity level",
},
}
app.Commands = []cli.Command{
{
Name: "create",
Aliases: []string{"c"},
Usage: "create a swarm snapshot",
Action: create,
// Flags only for "create" command.
// Allow app flags to be specified after the
// command argument.
Flags: append(app.Flags,
cli.IntFlag{
Name: "nodes",
Value: defaultNodes,
Usage: "number of nodes",
},
cli.StringFlag{
Name: "services",
Value: "bzz",
Usage: "comma separated list of services to boot the nodes with",
},
),
},
}
return app
}

View file

@ -0,0 +1,49 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/pkg/reexec"
"github.com/ethereum/go-ethereum/internal/cmdtest"
)
func init() {
reexec.Register("swarm-snapshot", func() {
if err := newApp().Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
os.Exit(0)
})
}
func runSnapshot(t *testing.T, args ...string) *cmdtest.TestCmd {
tt := cmdtest.NewTestCmd(t, nil)
tt.Run("swarm-snapshot", args...)
return tt
}
func TestMain(m *testing.M) {
if reexec.Init() {
return
}
os.Exit(m.Run())
}

View file

@ -19,6 +19,7 @@ package utils
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
@ -57,7 +58,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
) )
var ( var (
@ -161,6 +162,23 @@ var (
Usage: "Document Root for HTTPClient file scheme", Usage: "Document Root for HTTPClient file scheme",
Value: DirectoryString{homeDir()}, Value: DirectoryString{homeDir()},
} }
ULCModeConfigFlag = cli.StringFlag{
Name: "ulc.config",
Usage: "Config file to use for ultra light client mode",
}
OnlyAnnounceModeFlag = cli.BoolFlag{
Name: "ulc.onlyannounce",
Usage: "ULC server sends announcements only",
}
ULCMinTrustedFractionFlag = cli.IntFlag{
Name: "ulc.fraction",
Usage: "Minimum % of trusted ULC servers required to announce a new head",
}
ULCTrustedNodesFlag = cli.StringFlag{
Name: "ulc.trusted",
Usage: "List of trusted ULC servers",
}
defaultSyncMode = eth.DefaultConfig.SyncMode defaultSyncMode = eth.DefaultConfig.SyncMode
SyncModeFlag = TextMarshalerFlag{ SyncModeFlag = TextMarshalerFlag{
Name: "syncmode", Name: "syncmode",
@ -435,6 +453,30 @@ var (
Usage: "HTTP-RPC server listening port", Usage: "HTTP-RPC server listening port",
Value: node.DefaultHTTPPort, Value: node.DefaultHTTPPort,
} }
GraphQLEnabledFlag = cli.BoolFlag{
Name: "graphql",
Usage: "Enable the GraphQL server",
}
GraphQLListenAddrFlag = cli.StringFlag{
Name: "graphql.addr",
Usage: "GraphQL server listening interface",
Value: node.DefaultGraphQLHost,
}
GraphQLPortFlag = cli.IntFlag{
Name: "graphql.port",
Usage: "GraphQL server listening port",
Value: node.DefaultGraphQLPort,
}
GraphQLCORSDomainFlag = cli.StringFlag{
Name: "graphql.rpccorsdomain",
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
Value: "",
}
GraphQLVirtualHostsFlag = cli.StringFlag{
Name: "graphql.rpcvhosts",
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
Value: strings.Join(node.DefaultConfig.HTTPVirtualHosts, ","),
}
RPCCORSDomainFlag = cli.StringFlag{ RPCCORSDomainFlag = cli.StringFlag{
Name: "rpccorsdomain", Name: "rpccorsdomain",
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
@ -614,14 +656,14 @@ var (
Usage: "Password to authorize access to the database", Usage: "Password to authorize access to the database",
Value: "test", Value: "test",
} }
// The `host` tag is part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB. // Tags are part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB.
// It is used so that we can group all nodes and average a measurement across all of them, but also so // For example `host` tag could be used so that we can group all nodes and average a measurement
// that we can select a specific node and inspect its measurements. // across all of them, but also so that we can select a specific node and inspect its measurements.
// https://docs.influxdata.com/influxdb/v1.4/concepts/key_concepts/#tag-key // https://docs.influxdata.com/influxdb/v1.4/concepts/key_concepts/#tag-key
MetricsInfluxDBHostTagFlag = cli.StringFlag{ MetricsInfluxDBTagsFlag = cli.StringFlag{
Name: "metrics.influxdb.host.tag", Name: "metrics.influxdb.tags",
Usage: "InfluxDB `host` tag attached to all measurements", Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements",
Value: "localhost", Value: "host=localhost",
} }
EWASMInterpreterFlag = cli.StringFlag{ EWASMInterpreterFlag = cli.StringFlag{
@ -707,11 +749,13 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls)) cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls))
for _, url := range urls { for _, url := range urls {
node, err := enode.ParseV4(url) if url != "" {
if err != nil { node, err := enode.ParseV4(url)
log.Crit("Bootstrap URL invalid", "enode", url, "err", err) if err != nil {
log.Crit("Bootstrap URL invalid", "enode", url, "err", err)
}
cfg.BootstrapNodes = append(cfg.BootstrapNodes, node)
} }
cfg.BootstrapNodes = append(cfg.BootstrapNodes, node)
} }
} }
@ -796,6 +840,24 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
} }
} }
// setGraphQL creates the GraphQL listener interface string from the set
// command line flags, returning empty if the GraphQL endpoint is disabled.
func setGraphQL(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalBool(GraphQLEnabledFlag.Name) && cfg.GraphQLHost == "" {
cfg.GraphQLHost = "127.0.0.1"
if ctx.GlobalIsSet(GraphQLListenAddrFlag.Name) {
cfg.GraphQLHost = ctx.GlobalString(GraphQLListenAddrFlag.Name)
}
}
cfg.GraphQLPort = ctx.GlobalInt(GraphQLPortFlag.Name)
if ctx.GlobalIsSet(GraphQLCORSDomainFlag.Name) {
cfg.GraphQLCors = splitAndTrim(ctx.GlobalString(GraphQLCORSDomainFlag.Name))
}
if ctx.GlobalIsSet(GraphQLVirtualHostsFlag.Name) {
cfg.GraphQLVirtualHosts = splitAndTrim(ctx.GlobalString(GraphQLVirtualHostsFlag.Name))
}
}
// setWS creates the WebSocket RPC listener interface string from the set // setWS creates the WebSocket RPC listener interface string from the set
// command line flags, returning empty if the HTTP endpoint is disabled. // command line flags, returning empty if the HTTP endpoint is disabled.
func setWS(ctx *cli.Context, cfg *node.Config) { func setWS(ctx *cli.Context, cfg *node.Config) {
@ -829,6 +891,40 @@ func setIPC(ctx *cli.Context, cfg *node.Config) {
} }
} }
// SetULC setup ULC config from file if given.
func SetULC(ctx *cli.Context, cfg *eth.Config) {
// ULC config isn't loaded from global config and ULC config and ULC trusted nodes are not defined.
if cfg.ULC == nil && !(ctx.GlobalIsSet(ULCModeConfigFlag.Name) || ctx.GlobalIsSet(ULCTrustedNodesFlag.Name)) {
return
}
cfg.ULC = &eth.ULCConfig{}
path := ctx.GlobalString(ULCModeConfigFlag.Name)
if path != "" {
cfgData, err := ioutil.ReadFile(path)
if err != nil {
Fatalf("Failed to unmarshal ULC configuration: %v", err)
}
err = json.Unmarshal(cfgData, &cfg.ULC)
if err != nil {
Fatalf("Failed to unmarshal ULC configuration: %s", err.Error())
}
}
if trustedNodes := ctx.GlobalString(ULCTrustedNodesFlag.Name); trustedNodes != "" {
cfg.ULC.TrustedServers = strings.Split(trustedNodes, ",")
}
if trustedFraction := ctx.GlobalInt(ULCMinTrustedFractionFlag.Name); trustedFraction > 0 {
cfg.ULC.MinTrustedFraction = trustedFraction
}
if cfg.ULC.MinTrustedFraction <= 0 && cfg.ULC.MinTrustedFraction > 100 {
log.Error("MinTrustedFraction is invalid", "MinTrustedFraction", cfg.ULC.MinTrustedFraction, "Changed to default", eth.DefaultULCMinTrustedFraction)
cfg.ULC.MinTrustedFraction = eth.DefaultULCMinTrustedFraction
}
}
// makeDatabaseHandles raises out the number of allowed file handles per process // makeDatabaseHandles raises out the number of allowed file handles per process
// for Geth and returns half of the allowance to assign to the database. // for Geth and returns half of the allowance to assign to the database.
func makeDatabaseHandles() int { func makeDatabaseHandles() int {
@ -978,6 +1074,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
SetP2PConfig(ctx, &cfg.P2P) SetP2PConfig(ctx, &cfg.P2P)
setIPC(ctx, cfg) setIPC(ctx, cfg)
setHTTP(ctx, cfg) setHTTP(ctx, cfg)
setGraphQL(ctx, cfg)
setWS(ctx, cfg) setWS(ctx, cfg)
setNodeUserIdent(ctx, cfg) setNodeUserIdent(ctx, cfg)
@ -1179,6 +1276,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
if ctx.GlobalIsSet(LightPeersFlag.Name) { if ctx.GlobalIsSet(LightPeersFlag.Name) {
cfg.LightPeers = ctx.GlobalInt(LightPeersFlag.Name) cfg.LightPeers = ctx.GlobalInt(LightPeersFlag.Name)
} }
if ctx.GlobalIsSet(OnlyAnnounceModeFlag.Name) {
cfg.OnlyAnnounce = ctx.GlobalBool(OnlyAnnounceModeFlag.Name)
}
if ctx.GlobalIsSet(NetworkIdFlag.Name) { if ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = ctx.GlobalUint64(NetworkIdFlag.Name) cfg.NetworkId = ctx.GlobalUint64(NetworkIdFlag.Name)
} }
@ -1360,18 +1460,35 @@ func SetupMetrics(ctx *cli.Context) {
database = ctx.GlobalString(MetricsInfluxDBDatabaseFlag.Name) database = ctx.GlobalString(MetricsInfluxDBDatabaseFlag.Name)
username = ctx.GlobalString(MetricsInfluxDBUsernameFlag.Name) username = ctx.GlobalString(MetricsInfluxDBUsernameFlag.Name)
password = ctx.GlobalString(MetricsInfluxDBPasswordFlag.Name) password = ctx.GlobalString(MetricsInfluxDBPasswordFlag.Name)
hosttag = ctx.GlobalString(MetricsInfluxDBHostTagFlag.Name)
) )
if enableExport { if enableExport {
tagsMap := SplitTagsFlag(ctx.GlobalString(MetricsInfluxDBTagsFlag.Name))
log.Info("Enabling metrics export to InfluxDB") log.Info("Enabling metrics export to InfluxDB")
go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", map[string]string{
"host": hosttag, go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap)
})
} }
} }
} }
func SplitTagsFlag(tagsFlag string) map[string]string {
tags := strings.Split(tagsFlag, ",")
tagsMap := map[string]string{}
for _, t := range tags {
if t != "" {
kv := strings.Split(t, "=")
if len(kv) == 2 {
tagsMap[kv[0]] = kv[1]
}
}
}
return tagsMap
}
// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.
func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
var ( var (

64
cmd/utils/flags_test.go Normal file
View file

@ -0,0 +1,64 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// Package utils contains internal helper functions for go-ethereum commands.
package utils
import (
"reflect"
"testing"
)
func Test_SplitTagsFlag(t *testing.T) {
tests := []struct {
name string
args string
want map[string]string
}{
{
"2 tags case",
"host=localhost,bzzkey=123",
map[string]string{
"host": "localhost",
"bzzkey": "123",
},
},
{
"1 tag case",
"host=localhost123",
map[string]string{
"host": "localhost123",
},
},
{
"empty case",
"",
map[string]string{},
},
{
"garbage",
"smth=smthelse=123",
map[string]string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := SplitTagsFlag(tt.args); !reflect.DeepEqual(got, tt.want) {
t.Errorf("splitTagsFlag() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -72,6 +72,25 @@ func (b Bytes) String() string {
return Encode(b) return Encode(b)
} }
// ImplementsGraphQLType returns true if Bytes implements the specified GraphQL type.
func (b Bytes) ImplementsGraphQLType(name string) bool { return name == "Bytes" }
// UnmarshalGraphQL unmarshals the provided GraphQL query data.
func (b *Bytes) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
data, err := Decode(input)
if err != nil {
return err
}
*b = data
default:
err = fmt.Errorf("Unexpected type for Bytes: %v", input)
}
return err
}
// UnmarshalFixedJSON decodes the input as a string with 0x prefix. The length of out // UnmarshalFixedJSON decodes the input as a string with 0x prefix. The length of out
// determines the required input length. This function is commonly used to implement the // determines the required input length. This function is commonly used to implement the
// UnmarshalJSON method for fixed-size types. // UnmarshalJSON method for fixed-size types.
@ -187,6 +206,25 @@ func (b *Big) String() string {
return EncodeBig(b.ToInt()) return EncodeBig(b.ToInt())
} }
// ImplementsGraphQLType returns true if Big implements the provided GraphQL type.
func (b Big) ImplementsGraphQLType(name string) bool { return name == "BigInt" }
// UnmarshalGraphQL unmarshals the provided GraphQL query data.
func (b *Big) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
return b.UnmarshalText([]byte(input))
case int32:
var num big.Int
num.SetInt64(int64(input))
*b = Big(num)
default:
err = fmt.Errorf("Unexpected type for BigInt: %v", input)
}
return err
}
// Uint64 marshals/unmarshals as a JSON string with 0x prefix. // Uint64 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0". // The zero value marshals as "0x0".
type Uint64 uint64 type Uint64 uint64
@ -234,6 +272,23 @@ func (b Uint64) String() string {
return EncodeUint64(uint64(b)) return EncodeUint64(uint64(b))
} }
// ImplementsGraphQLType returns true if Uint64 implements the provided GraphQL type.
func (b Uint64) ImplementsGraphQLType(name string) bool { return name == "Long" }
// UnmarshalGraphQL unmarshals the provided GraphQL query data.
func (b *Uint64) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
return b.UnmarshalText([]byte(input))
case int32:
*b = Uint64(input)
default:
err = fmt.Errorf("Unexpected type for Long: %v", input)
}
return err
}
// Uint marshals/unmarshals as a JSON string with 0x prefix. // Uint marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0". // The zero value marshals as "0x0".
type Uint uint type Uint uint

View file

@ -141,6 +141,21 @@ func (h Hash) Value() (driver.Value, error) {
return h[:], nil return h[:], nil
} }
// ImplementsGraphQLType returns true if Hash implements the specified GraphQL type.
func (_ Hash) ImplementsGraphQLType(name string) bool { return name == "Bytes32" }
// UnmarshalGraphQL unmarshals the provided GraphQL query data.
func (h *Hash) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
*h = HexToHash(input)
default:
err = fmt.Errorf("Unexpected type for Bytes32: %v", input)
}
return err
}
// UnprefixedHash allows marshaling a Hash without 0x prefix. // UnprefixedHash allows marshaling a Hash without 0x prefix.
type UnprefixedHash Hash type UnprefixedHash Hash
@ -268,6 +283,21 @@ func (a Address) Value() (driver.Value, error) {
return a[:], nil return a[:], nil
} }
// ImplementsGraphQLType returns true if Hash implements the specified GraphQL type.
func (a Address) ImplementsGraphQLType(name string) bool { return name == "Address" }
// UnmarshalGraphQL unmarshals the provided GraphQL query data.
func (a *Address) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
*a = HexToAddress(input)
default:
err = fmt.Errorf("Unexpected type for Address: %v", input)
}
return err
}
// UnprefixedAddress allows marshaling an Address without 0x prefix. // UnprefixedAddress allows marshaling an Address without 0x prefix.
type UnprefixedAddress Address type UnprefixedAddress Address

View file

@ -105,9 +105,37 @@ func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
return val return val
} }
// Wallet open failed, report error unless it's a PIN entry // Wallet open failed, report error unless it's a PIN entry
if !strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()) { if strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()) {
val, err = b.readPinAndReopenWallet(call)
if err == nil {
return val
}
}
// Check if the user needs to input a passphrase
if !strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPassphraseNeeded.Error()) {
throwJSException(err.Error()) throwJSException(err.Error())
} }
val, err = b.readPassphraseAndReopenWallet(call)
if err != nil {
throwJSException(err.Error())
}
return val
}
func (b *bridge) readPassphraseAndReopenWallet(call otto.FunctionCall) (otto.Value, error) {
var passwd otto.Value
wallet := call.Argument(0)
if input, err := b.prompter.PromptPassword("Please enter your passphrase: "); err != nil {
throwJSException(err.Error())
} else {
passwd, _ = otto.ToValue(input)
}
return call.Otto.Call("jeth.openWallet", nil, wallet, passwd)
}
func (b *bridge) readPinAndReopenWallet(call otto.FunctionCall) (otto.Value, error) {
var passwd otto.Value
wallet := call.Argument(0)
// Trezor PIN matrix input requested, display the matrix to the user and fetch the data // Trezor PIN matrix input requested, display the matrix to the user and fetch the data
fmt.Fprintf(b.printer, "Look at the device for number positions\n\n") fmt.Fprintf(b.printer, "Look at the device for number positions\n\n")
fmt.Fprintf(b.printer, "7 | 8 | 9\n") fmt.Fprintf(b.printer, "7 | 8 | 9\n")
@ -121,10 +149,7 @@ func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
} else { } else {
passwd, _ = otto.ToValue(input) passwd, _ = otto.ToValue(input)
} }
if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil { return call.Otto.Call("jeth.openWallet", nil, wallet, passwd)
throwJSException(err.Error())
}
return val
} }
// UnlockAccount is a wrapper around the personal.unlockAccount RPC method that // UnlockAccount is a wrapper around the personal.unlockAccount RPC method that

View file

@ -108,7 +108,6 @@ type BlockChain struct {
scope event.SubscriptionScope scope event.SubscriptionScope
genesisBlock *types.Block genesisBlock *types.Block
mu sync.RWMutex // global mutex for locking chain operations
chainmu sync.RWMutex // blockchain insertion lock chainmu sync.RWMutex // blockchain insertion lock
procmu sync.RWMutex // block processor lock procmu sync.RWMutex // block processor lock
@ -281,8 +280,8 @@ func (bc *BlockChain) loadLastState() error {
func (bc *BlockChain) SetHead(head uint64) error { func (bc *BlockChain) SetHead(head uint64) error {
log.Warn("Rewinding blockchain", "target", head) log.Warn("Rewinding blockchain", "target", head)
bc.mu.Lock() bc.chainmu.Lock()
defer bc.mu.Unlock() defer bc.chainmu.Unlock()
// Rewind the header chain, deleting all block bodies until then // Rewind the header chain, deleting all block bodies until then
delFn := func(db rawdb.DatabaseDeleter, hash common.Hash, num uint64) { delFn := func(db rawdb.DatabaseDeleter, hash common.Hash, num uint64) {
@ -340,9 +339,9 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
return err return err
} }
// If all checks out, manually set the head block // If all checks out, manually set the head block
bc.mu.Lock() bc.chainmu.Lock()
bc.currentBlock.Store(block) bc.currentBlock.Store(block)
bc.mu.Unlock() bc.chainmu.Unlock()
log.Info("Committed new head block", "number", block.Number(), "hash", hash) log.Info("Committed new head block", "number", block.Number(), "hash", hash)
return nil return nil
@ -420,8 +419,8 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
if err := bc.SetHead(0); err != nil { if err := bc.SetHead(0); err != nil {
return err return err
} }
bc.mu.Lock() bc.chainmu.Lock()
defer bc.mu.Unlock() defer bc.chainmu.Unlock()
// Prepare the genesis block and reinitialise the chain // Prepare the genesis block and reinitialise the chain
if err := bc.hc.WriteTd(genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()); err != nil { if err := bc.hc.WriteTd(genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()); err != nil {
@ -468,8 +467,8 @@ func (bc *BlockChain) Export(w io.Writer) error {
// ExportN writes a subset of the active chain to the given writer. // ExportN writes a subset of the active chain to the given writer.
func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error { func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
bc.mu.RLock() bc.chainmu.RLock()
defer bc.mu.RUnlock() defer bc.chainmu.RUnlock()
if first > last { if first > last {
return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last) return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last)
@ -490,7 +489,6 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
reported = time.Now() reported = time.Now()
} }
} }
return nil return nil
} }
@ -646,6 +644,9 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
return nil return nil
} }
receipts := rawdb.ReadReceipts(bc.db, hash, *number) receipts := rawdb.ReadReceipts(bc.db, hash, *number)
if receipts == nil {
return nil
}
bc.receiptsCache.Add(hash, receipts) bc.receiptsCache.Add(hash, receipts)
return receipts return receipts
} }
@ -756,8 +757,8 @@ const (
// Rollback is designed to remove a chain of links from the database that aren't // Rollback is designed to remove a chain of links from the database that aren't
// certain enough to be valid. // certain enough to be valid.
func (bc *BlockChain) Rollback(chain []common.Hash) { func (bc *BlockChain) Rollback(chain []common.Hash) {
bc.mu.Lock() bc.chainmu.Lock()
defer bc.mu.Unlock() defer bc.chainmu.Unlock()
for i := len(chain) - 1; i >= 0; i-- { for i := len(chain) - 1; i >= 0; i-- {
hash := chain[i] hash := chain[i]
@ -881,7 +882,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
} }
// Update the head fast sync block if better // Update the head fast sync block if better
bc.mu.Lock() bc.chainmu.Lock()
head := blockChain[len(blockChain)-1] head := blockChain[len(blockChain)-1]
if td := bc.GetTd(head.Hash(), head.NumberU64()); td != nil { // Rewind may have occurred, skip in that case if td := bc.GetTd(head.Hash(), head.NumberU64()); td != nil { // Rewind may have occurred, skip in that case
currentFastBlock := bc.CurrentFastBlock() currentFastBlock := bc.CurrentFastBlock()
@ -890,7 +891,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
bc.currentFastBlock.Store(head) bc.currentFastBlock.Store(head)
} }
} }
bc.mu.Unlock() bc.chainmu.Unlock()
context := []interface{}{ context := []interface{}{
"count", stats.processed, "elapsed", common.PrettyDuration(time.Since(start)), "count", stats.processed, "elapsed", common.PrettyDuration(time.Since(start)),
@ -924,6 +925,15 @@ func (bc *BlockChain) WriteBlockWithoutState(block *types.Block, td *big.Int) (e
// WriteBlockWithState writes the block and all associated state to the database. // WriteBlockWithState writes the block and all associated state to the database.
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) (status WriteStatus, err error) { func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) (status WriteStatus, err error) {
bc.chainmu.Lock()
defer bc.chainmu.Unlock()
return bc.writeBlockWithState(block, receipts, state)
}
// writeBlockWithState writes the block and all associated state to the database,
// but is expects the chain mutex to be held.
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) (status WriteStatus, err error) {
bc.wg.Add(1) bc.wg.Add(1)
defer bc.wg.Done() defer bc.wg.Done()
@ -933,9 +943,6 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
return NonStatTy, consensus.ErrUnknownAncestor return NonStatTy, consensus.ErrUnknownAncestor
} }
// Make sure no inconsistent state is leaked during insertion // Make sure no inconsistent state is leaked during insertion
bc.mu.Lock()
defer bc.mu.Unlock()
currentBlock := bc.CurrentBlock() currentBlock := bc.CurrentBlock()
localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64()) localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
externTd := new(big.Int).Add(block.Difficulty(), ptd) externTd := new(big.Int).Add(block.Difficulty(), ptd)
@ -1212,7 +1219,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
proctime := time.Since(start) proctime := time.Since(start)
// Write the block to the chain and get the status. // Write the block to the chain and get the status.
status, err := bc.WriteBlockWithState(block, receipts, state) status, err := bc.writeBlockWithState(block, receipts, state)
t3 := time.Now() t3 := time.Now()
if err != nil { if err != nil {
return it.index, events, coalescedLogs, err return it.index, events, coalescedLogs, err
@ -1281,7 +1288,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
func (bc *BlockChain) insertSidechain(it *insertIterator) (int, []interface{}, []*types.Log, error) { func (bc *BlockChain) insertSidechain(it *insertIterator) (int, []interface{}, []*types.Log, error) {
var ( var (
externTd *big.Int externTd *big.Int
current = bc.CurrentBlock().NumberU64() current = bc.CurrentBlock()
) )
// The first sidechain block error is already verified to be ErrPrunedAncestor. // The first sidechain block error is already verified to be ErrPrunedAncestor.
// Since we don't import them here, we expect ErrUnknownAncestor for the remaining // Since we don't import them here, we expect ErrUnknownAncestor for the remaining
@ -1290,7 +1297,7 @@ func (bc *BlockChain) insertSidechain(it *insertIterator) (int, []interface{}, [
block, err := it.current(), consensus.ErrPrunedAncestor block, err := it.current(), consensus.ErrPrunedAncestor
for ; block != nil && (err == consensus.ErrPrunedAncestor); block, err = it.next() { for ; block != nil && (err == consensus.ErrPrunedAncestor); block, err = it.next() {
// Check the canonical state root for that number // Check the canonical state root for that number
if number := block.NumberU64(); current >= number { if number := block.NumberU64(); current.NumberU64() >= number {
if canonical := bc.GetBlockByNumber(number); canonical != nil && canonical.Root() == block.Root() { if canonical := bc.GetBlockByNumber(number); canonical != nil && canonical.Root() == block.Root() {
// This is most likely a shadow-state attack. When a fork is imported into the // This is most likely a shadow-state attack. When a fork is imported into the
// database, and it eventually reaches a block height which is not pruned, we // database, and it eventually reaches a block height which is not pruned, we
@ -1329,7 +1336,7 @@ func (bc *BlockChain) insertSidechain(it *insertIterator) (int, []interface{}, [
// //
// If the externTd was larger than our local TD, we now need to reimport the previous // If the externTd was larger than our local TD, we now need to reimport the previous
// blocks to regenerate the required state // blocks to regenerate the required state
localTd := bc.GetTd(bc.CurrentBlock().Hash(), current) localTd := bc.GetTd(current.Hash(), current.NumberU64())
if localTd.Cmp(externTd) > 0 { if localTd.Cmp(externTd) > 0 {
log.Info("Sidechain written to disk", "start", it.first().NumberU64(), "end", it.previous().NumberU64(), "sidetd", externTd, "localtd", localTd) log.Info("Sidechain written to disk", "start", it.first().NumberU64(), "end", it.previous().NumberU64(), "sidetd", externTd, "localtd", localTd)
return it.index, nil, nil, err return it.index, nil, nil, err
@ -1597,36 +1604,12 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
defer bc.wg.Done() defer bc.wg.Done()
whFunc := func(header *types.Header) error { whFunc := func(header *types.Header) error {
bc.mu.Lock()
defer bc.mu.Unlock()
_, err := bc.hc.WriteHeader(header) _, err := bc.hc.WriteHeader(header)
return err return err
} }
return bc.hc.InsertHeaderChain(chain, whFunc, start) return bc.hc.InsertHeaderChain(chain, whFunc, start)
} }
// writeHeader writes a header into the local chain, given that its parent is
// already known. If the total difficulty of the newly inserted header becomes
// greater than the current known TD, the canonical chain is re-routed.
//
// Note: This method is not concurrent-safe with inserting blocks simultaneously
// into the chain, as side effects caused by reorganisations cannot be emulated
// without the real blocks. Hence, writing headers directly should only be done
// in two scenarios: pure-header mode of operation (light clients), or properly
// separated header/block phases (non-archive clients).
func (bc *BlockChain) writeHeader(header *types.Header) error {
bc.wg.Add(1)
defer bc.wg.Done()
bc.mu.Lock()
defer bc.mu.Unlock()
_, err := bc.hc.WriteHeader(header)
return err
}
// CurrentHeader retrieves the current head header of the canonical chain. The // CurrentHeader retrieves the current head header of the canonical chain. The
// header is retrieved from the HeaderChain's internal cache. // header is retrieved from the HeaderChain's internal cache.
func (bc *BlockChain) CurrentHeader() *types.Header { func (bc *BlockChain) CurrentHeader() *types.Header {
@ -1675,8 +1658,8 @@ func (bc *BlockChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []com
// //
// Note: ancestor == 0 returns the same block, 1 returns its parent and so on. // Note: ancestor == 0 returns the same block, 1 returns its parent and so on.
func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) { func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) {
bc.chainmu.Lock() bc.chainmu.RLock()
defer bc.chainmu.Unlock() defer bc.chainmu.RUnlock()
return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical) return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
} }

View file

@ -162,11 +162,11 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
blockchain.reportBlock(block, receipts, err) blockchain.reportBlock(block, receipts, err)
return err return err
} }
blockchain.mu.Lock() blockchain.chainmu.Lock()
rawdb.WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTdByHash(block.ParentHash()))) rawdb.WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTdByHash(block.ParentHash())))
rawdb.WriteBlock(blockchain.db, block) rawdb.WriteBlock(blockchain.db, block)
statedb.Commit(false) statedb.Commit(false)
blockchain.mu.Unlock() blockchain.chainmu.Unlock()
} }
return nil return nil
} }
@ -180,10 +180,10 @@ func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error
return err return err
} }
// Manually insert the header into the database, but don't reorganise (allows subsequent testing) // Manually insert the header into the database, but don't reorganise (allows subsequent testing)
blockchain.mu.Lock() blockchain.chainmu.Lock()
rawdb.WriteTd(blockchain.db, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, blockchain.GetTdByHash(header.ParentHash))) rawdb.WriteTd(blockchain.db, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, blockchain.GetTdByHash(header.ParentHash)))
rawdb.WriteHeader(blockchain.db, header) rawdb.WriteHeader(blockchain.db, header)
blockchain.mu.Unlock() blockchain.chainmu.Unlock()
} }
return nil return nil
} }

View file

@ -183,6 +183,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, constant
newcfg := genesis.configOrDefault(stored) newcfg := genesis.configOrDefault(stored)
if constantinopleOverride != nil { if constantinopleOverride != nil {
newcfg.ConstantinopleBlock = constantinopleOverride newcfg.ConstantinopleBlock = constantinopleOverride
newcfg.PetersburgBlock = constantinopleOverride
} }
storedcfg := rawdb.ReadChainConfig(db, stored) storedcfg := rawdb.ReadChainConfig(db, stored)
if storedcfg == nil { if storedcfg == nil {

View file

@ -219,14 +219,18 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
// Generate the list of seal verification requests, and start the parallel verifier // Generate the list of seal verification requests, and start the parallel verifier
seals := make([]bool, len(chain)) seals := make([]bool, len(chain))
for i := 0; i < len(seals)/checkFreq; i++ { if checkFreq != 0 {
index := i*checkFreq + hc.rand.Intn(checkFreq) // In case of checkFreq == 0 all seals are left false.
if index >= len(seals) { for i := 0; i < len(seals)/checkFreq; i++ {
index = len(seals) - 1 index := i*checkFreq + hc.rand.Intn(checkFreq)
if index >= len(seals) {
index = len(seals) - 1
}
seals[index] = true
} }
seals[index] = true // Last should always be verified to avoid junk.
seals[len(seals)-1] = true
} }
seals[len(seals)-1] = true // Last should always be verified to avoid junk
abort, results := hc.engine.VerifyHeaders(hc, chain, seals) abort, results := hc.engine.VerifyHeaders(hc, chain, seals)
defer close(abort) defer close(abort)

View file

@ -21,10 +21,10 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"math/big" "math/big"
"reflect"
"sort" "sort"
"sync/atomic" "sync/atomic"
"time" "time"
"unsafe"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
@ -102,10 +102,12 @@ func (h *Header) Hash() common.Hash {
return rlpHash(h) return rlpHash(h)
} }
var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size())
// Size returns the approximate memory used by all internal contents. It is used // Size returns the approximate memory used by all internal contents. It is used
// to approximate and limit the memory consumption of various caches. // to approximate and limit the memory consumption of various caches.
func (h *Header) Size() common.StorageSize { func (h *Header) Size() common.StorageSize {
return common.StorageSize(unsafe.Sizeof(*h)) + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+h.Time.BitLen())/8) return headerSize + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+h.Time.BitLen())/8)
} }
func rlpHash(x interface{}) (h common.Hash) { func rlpHash(x interface{}) (h common.Hash) {

View file

@ -22,7 +22,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
// memoryGasCosts calculates the quadratic gas for memory expansion. It does so // memoryGasCost calculates the quadratic gas for memory expansion. It does so
// only for the memory region that is expanded, not the total memory. // only for the memory region that is expanded, not the total memory.
func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {
@ -121,7 +121,9 @@ func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, m
current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x))
) )
// The legacy gas metering only takes into consideration the current state // The legacy gas metering only takes into consideration the current state
if !evm.chainRules.IsConstantinople { // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283)
// OR Constantinople is not active
if evm.chainRules.IsPetersburg || !evm.chainRules.IsConstantinople {
// This checks for 3 scenario's and calculates gas accordingly: // This checks for 3 scenario's and calculates gas accordingly:
// //
// 1. From a zero-value address to a non-zero value (NEW VALUE) // 1. From a zero-value address to a non-zero value (NEW VALUE)
@ -380,7 +382,7 @@ func gasExp(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem
gas = expByteLen * gt.ExpByte // no overflow check required. Max is 256 * ExpByte gas gas = expByteLen * gt.ExpByte // no overflow check required. Max is 256 * ExpByte gas
overflow bool overflow bool
) )
if gas, overflow = math.SafeAdd(gas, GasSlowStep); overflow { if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
return gas, nil return gas, nil

View file

@ -34,7 +34,11 @@ type JSONLogger struct {
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects // NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
// into the provided stream. // into the provided stream.
func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger { func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
return &JSONLogger{json.NewEncoder(writer), cfg} l := &JSONLogger{json.NewEncoder(writer), cfg}
if l.cfg == nil {
l.cfg = &LogConfig{}
}
return l
} }
func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {

View file

@ -576,6 +576,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
vmConf vm.Config vmConf vm.Config
dump *os.File dump *os.File
writer *bufio.Writer
err error err error
) )
// If the transaction needs tracing, swap out the configs // If the transaction needs tracing, swap out the configs
@ -590,16 +591,19 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
dumps = append(dumps, dump.Name()) dumps = append(dumps, dump.Name())
// Swap out the noop logger to the standard tracer // Swap out the noop logger to the standard tracer
writer = bufio.NewWriter(dump)
vmConf = vm.Config{ vmConf = vm.Config{
Debug: true, Debug: true,
Tracer: vm.NewJSONLogger(&logConfig, bufio.NewWriter(dump)), Tracer: vm.NewJSONLogger(&logConfig, writer),
EnablePreimageRecording: true, EnablePreimageRecording: true,
} }
} }
// Execute the transaction and flush any traces to disk // Execute the transaction and flush any traces to disk
vmenv := vm.NewEVM(vmctx, statedb, api.config, vmConf) vmenv := vm.NewEVM(vmctx, statedb, api.config, vmConf)
_, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
if writer != nil {
writer.Flush()
}
if dump != nil { if dump != nil {
dump.Close() dump.Close()
log.Info("Wrote standard trace", "file", dump.Name()) log.Info("Wrote standard trace", "file", dump.Name())

View file

@ -91,8 +91,12 @@ type Config struct {
Whitelist map[uint64]common.Hash `toml:"-"` Whitelist map[uint64]common.Hash `toml:"-"`
// Light client options // Light client options
LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
LightPeers int `toml:",omitempty"` // Maximum number of LES client peers LightPeers int `toml:",omitempty"` // Maximum number of LES client peers
OnlyAnnounce bool // Maximum number of LES client peers
// Ultra Light client options
ULC *ULCConfig `toml:",omitempty"`
// Database options // Database options
SkipBcVersionCheck bool `toml:"-"` SkipBcVersionCheck bool `toml:"-"`

View file

@ -110,7 +110,6 @@ type Fetcher struct {
notify chan *announce notify chan *announce
inject chan *inject inject chan *inject
blockFilter chan chan []*types.Block
headerFilter chan chan *headerFilterTask headerFilter chan chan *headerFilterTask
bodyFilter chan chan *bodyFilterTask bodyFilter chan chan *bodyFilterTask
@ -150,7 +149,6 @@ func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBloc
return &Fetcher{ return &Fetcher{
notify: make(chan *announce), notify: make(chan *announce),
inject: make(chan *inject), inject: make(chan *inject),
blockFilter: make(chan chan []*types.Block),
headerFilter: make(chan chan *headerFilterTask), headerFilter: make(chan chan *headerFilterTask),
bodyFilter: make(chan chan *bodyFilterTask), bodyFilter: make(chan chan *bodyFilterTask),
done: make(chan common.Hash), done: make(chan common.Hash),

View file

@ -244,7 +244,7 @@ func verifyImportEvent(t *testing.T, imported chan *types.Block, arrive bool) {
select { select {
case <-imported: case <-imported:
t.Fatalf("import invoked") t.Fatalf("import invoked")
case <-time.After(10 * time.Millisecond): case <-time.After(20 * time.Millisecond):
} }
} }
} }

View file

@ -23,10 +23,12 @@ func (c Config) MarshalTOML() (interface{}, error) {
NetworkId uint64 NetworkId uint64
SyncMode downloader.SyncMode SyncMode downloader.SyncMode
NoPruning bool NoPruning bool
LightServ int `toml:",omitempty"` LightServ int `toml:",omitempty"`
LightPeers int `toml:",omitempty"` LightPeers int `toml:",omitempty"`
SkipBcVersionCheck bool `toml:"-"` OnlyAnnounce bool
DatabaseHandles int `toml:"-"` ULC *ULCConfig `toml:",omitempty"`
SkipBcVersionCheck bool `toml:"-"`
DatabaseHandles int `toml:"-"`
DatabaseCache int DatabaseCache int
TrieCleanCache int TrieCleanCache int
TrieDirtyCache int TrieDirtyCache int
@ -54,6 +56,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.NoPruning = c.NoPruning enc.NoPruning = c.NoPruning
enc.LightServ = c.LightServ enc.LightServ = c.LightServ
enc.LightPeers = c.LightPeers enc.LightPeers = c.LightPeers
enc.OnlyAnnounce = c.OnlyAnnounce
enc.ULC = c.ULC
enc.SkipBcVersionCheck = c.SkipBcVersionCheck enc.SkipBcVersionCheck = c.SkipBcVersionCheck
enc.DatabaseHandles = c.DatabaseHandles enc.DatabaseHandles = c.DatabaseHandles
enc.DatabaseCache = c.DatabaseCache enc.DatabaseCache = c.DatabaseCache
@ -71,6 +75,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.Ethash = c.Ethash enc.Ethash = c.Ethash
enc.TxPool = c.TxPool enc.TxPool = c.TxPool
enc.GPO = c.GPO enc.GPO = c.GPO
enc.EnablePreimageRecording = c.EnablePreimageRecording enc.EnablePreimageRecording = c.EnablePreimageRecording
enc.DocRoot = c.DocRoot enc.DocRoot = c.DocRoot
enc.EWASMInterpreter = c.EWASMInterpreter enc.EWASMInterpreter = c.EWASMInterpreter
@ -85,10 +90,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
NetworkId *uint64 NetworkId *uint64
SyncMode *downloader.SyncMode SyncMode *downloader.SyncMode
NoPruning *bool NoPruning *bool
LightServ *int `toml:",omitempty"` LightServ *int `toml:",omitempty"`
LightPeers *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"`
SkipBcVersionCheck *bool `toml:"-"` OnlyAnnounce *bool
DatabaseHandles *int `toml:"-"` ULC *ULCConfig `toml:",omitempty"`
SkipBcVersionCheck *bool `toml:"-"`
DatabaseHandles *int `toml:"-"`
DatabaseCache *int DatabaseCache *int
TrieCleanCache *int TrieCleanCache *int
TrieDirtyCache *int TrieDirtyCache *int
@ -131,6 +138,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.LightPeers != nil { if dec.LightPeers != nil {
c.LightPeers = *dec.LightPeers c.LightPeers = *dec.LightPeers
} }
if dec.OnlyAnnounce != nil {
c.OnlyAnnounce = *dec.OnlyAnnounce
}
if dec.ULC != nil {
c.ULC = dec.ULC
}
if dec.SkipBcVersionCheck != nil { if dec.SkipBcVersionCheck != nil {
c.SkipBcVersionCheck = *dec.SkipBcVersionCheck c.SkipBcVersionCheck = *dec.SkipBcVersionCheck
} }

9
eth/ulc_config.go Normal file
View file

@ -0,0 +1,9 @@
package eth
const DefaultULCMinTrustedFraction = 75
// ULCConfig is a Ultra Light client options.
type ULCConfig struct {
TrustedServers []string `toml:",omitempty"` // A list of trusted servers
MinTrustedFraction int `toml:",omitempty"` // Minimum percentage of connected trusted servers to validate trusted (1-100)
}

1104
graphql/grahpql.go Normal file

File diff suppressed because it is too large Load diff

95
graphql/graphiql.go Normal file
View file

@ -0,0 +1,95 @@
// The MIT License (MIT)
//
// Copyright (c) 2016 Muhammed Thanish
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package graphql
import (
"bytes"
"fmt"
"net/http"
)
// GraphiQL is an in-browser IDE for exploring GraphiQL APIs.
// This handler returns GraphiQL when requested.
//
// For more information, see https://github.com/graphql/graphiql.
type GraphiQL struct{}
func respond(w http.ResponseWriter, body []byte, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
_, _ = w.Write(body)
}
func errorJSON(msg string) []byte {
buf := bytes.Buffer{}
fmt.Fprintf(&buf, `{"error": "%s"}`, msg)
return buf.Bytes()
}
func (h GraphiQL) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
respond(w, errorJSON("only GET requests are supported"), http.StatusMethodNotAllowed)
return
}
w.Write(graphiql)
}
var graphiql = []byte(`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.js"></script>
</head>
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
<div id="graphiql" style="height: 100vh;">Loading...</div>
<script>
function fetchGQL(params) {
return fetch("/graphql", {
method: "post",
body: JSON.stringify(params),
credentials: "include",
}).then(function (resp) {
return resp.text();
}).then(function (body) {
try {
return JSON.parse(body);
} catch (error) {
return body;
}
});
}
ReactDOM.render(
React.createElement(GraphiQL, {fetcher: fetchGQL}),
document.getElementById("graphiql")
)
</script>
</body>
</html>
`)

29
graphql/graphql_test.go Normal file
View file

@ -0,0 +1,29 @@
// Copyright 2018 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 graphql
import (
"testing"
)
func TestBuildSchema(t *testing.T) {
// Make sure the schema can be parsed and matched up to the object model.
_, err := NewHandler(nil)
if err != nil {
t.Errorf("Could not construct GraphQL handler: %v", err)
}
}

305
graphql/schema.go Normal file
View file

@ -0,0 +1,305 @@
// Copyright 2018 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 graphql
const schema string = `
# Bytes32 is a 32 byte binary string, represented as 0x-prefixed hexadecimal.
scalar Bytes32
# Address is a 20 byte Ethereum address, represented as 0x-prefixed hexadecimal.
scalar Address
# Bytes is an arbitrary length binary string, represented as 0x-prefixed hexadecimal.
scalar Bytes
# BigInt is a large integer. Input is accepted as either a JSON number or as a string.
# Strings may be either decimal or 0x-prefixed hexadecimal. Output values are all
# 0x-prefixed hexadecimal.
scalar BigInt
# Long is a 64 bit unsigned integer.
scalar Long
schema {
query: Query
mutation: Mutation
}
# Account is an Ethereum account at a particular block.
type Account {
# Address is the address owning the account.
address: Address!
# Balance is the balance of the account, in wei.
balance: BigInt!
# TransactionCount is the number of transactions sent from this account,
# or in the case of a contract, the number of contracts created. Otherwise
# known as the nonce.
transactionCount: Long!
# Code contains the smart contract code for this account, if the account
# is a (non-self-destructed) contract.
code: Bytes!
# Storage provides access to the storage of a contract account, indexed
# by its 32 byte slot identifier.
storage(slot: Bytes32!): Bytes32!
}
# Log is an Ethereum event log.
type Log {
# Index is the index of this log in the block.
index: Int!
# Account is the account which generated this log - this will always
# be a contract account.
account(block: Long): Account!
# Topics is a list of 0-4 indexed topics for the log.
topics: [Bytes32!]!
# Data is unindexed data for this log.
data: Bytes!
# Transaction is the transaction that generated this log entry.
transaction: Transaction!
}
# Transaction is an Ethereum transaction.
type Transaction {
# Hash is the hash of this transaction.
hash: Bytes32!
# Nonce is the nonce of the account this transaction was generated with.
nonce: Long!
# Index is the index of this transaction in the parent block. This will
# be null if the transaction has not yet beenn mined.
index: Int
# From is the account that sent this transaction - this will always be
# an externally owned account.
from(block: Long): Account!
# To is the account the transaction was sent to. This is null for
# contract-creating transactions.
to(block: Long): Account
# Value is the value, in wei, sent along with this transaction.
value: BigInt!
# GasPrice is the price offered to miners for gas, in wei per unit.
gasPrice: BigInt!
# Gas is the maximum amount of gas this transaction can consume.
gas: Long!
# InputData is the data supplied to the target of the transaction.
inputData: Bytes!
# Block is the block this transaction was mined in. This will be null if
# the transaction has not yet been mined.
block: Block
# Status is the return status of the transaction. This will be 1 if the
# transaction succeeded, or 0 if it failed (due to a revert, or due to
# running out of gas). If the transaction has not yet been mined, this
# field will be null.
status: Long
# GasUsed is the amount of gas that was used processing this transaction.
# If the transaction has not yet been mined, this field will be null.
gasUsed: Long
# CumulativeGasUsed is the total gas used in the block up to and including
# this transaction. If the transaction has not yet been mined, this field
# will be null.
cumulativeGasUsed: Long
# CreatedContract is the account that was created by a contract creation
# transaction. If the transaction was not a contract creation transaction,
# or it has not yet been mined, this field will be null.
createdContract(block: Long): Account
# Logs is a list of log entries emitted by this transaction. If the
# transaction has not yet been mined, this field will be null.
logs: [Log!]
}
# BlockFilterCriteria encapsulates log filter criteria for a filter applied
# to a single block.
input BlockFilterCriteria {
# Addresses is list of addresses that are of interest. If this list is
# empty, results will not be filtered by address.
addresses: [Address!]
# Topics list restricts matches to particular event topics. Each event has a list
# of topics. Topics matches a prefix of that list. An empty element array matches any
# topic. Non-empty elements represent an alternative that matches any of the
# contained topics.
#
# Examples:
# - [] or nil matches any topic list
# - [[A]] matches topic A in first position
# - [[], [B]] matches any topic in first position, B in second position
# - [[A], [B]] matches topic A in first position, B in second position
# - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
topics: [[Bytes32!]!]
}
# Block is an Ethereum block.
type Block {
# Number is the number of this block, starting at 0 for the genesis block.
number: Long!
# Hash is the block hash of this block.
hash: Bytes32!
# Parent is the parent block of this block.
parent: Block
# Nonce is the block nonce, an 8 byte sequence determined by the miner.
nonce: Bytes!
# TransactionsRoot is the keccak256 hash of the root of the trie of transactions in this block.
transactionsRoot: Bytes32!
# TransactionCount is the number of transactions in this block. if
# transactions are not available for this block, this field will be null.
transactionCount: Int
# StateRoot is the keccak256 hash of the state trie after this block was processed.
stateRoot: Bytes32!
# ReceiptsRoot is the keccak256 hash of the trie of transaction receipts in this block.
receiptsRoot: Bytes32!
# Miner is the account that mined this block.
miner(block: Long): Account!
# ExtraData is an arbitrary data field supplied by the miner.
extraData: Bytes!
# GasLimit is the maximum amount of gas that was available to transactions in this block.
gasLimit: Long!
# GasUsed is the amount of gas that was used executing transactions in this block.
gasUsed: Long!
# Timestamp is the unix timestamp at which this block was mined.
timestamp: BigInt!
# LogsBloom is a bloom filter that can be used to check if a block may
# contain log entries matching a filter.
logsBloom: Bytes!
# MixHash is the hash that was used as an input to the PoW process.
mixHash: Bytes32!
# Difficulty is a measure of the difficulty of mining this block.
difficulty: BigInt!
# TotalDifficulty is the sum of all difficulty values up to and including
# this block.
totalDifficulty: BigInt!
# OmmerCount is the number of ommers (AKA uncles) associated with this
# block. If ommers are unavailable, this field will be null.
ommerCount: Int
# Ommers is a list of ommer (AKA uncle) blocks associated with this block.
# If ommers are unavailable, this field will be null. Depending on your
# node, the transactions, transactionAt, transactionCount, ommers,
# ommerCount and ommerAt fields may not be available on any ommer blocks.
ommers: [Block]
# OmmerAt returns the ommer (AKA uncle) at the specified index. If ommers
# are unavailable, or the index is out of bounds, this field will be null.
ommerAt(index: Int!): Block
# OmmerHash is the keccak256 hash of all the ommers (AKA uncles)
# associated with this block.
ommerHash: Bytes32!
# Transactions is a list of transactions associated with this block. If
# transactions are unavailable for this block, this field will be null.
transactions: [Transaction!]
# TransactionAt returns the transaction at the specified index. If
# transactions are unavailable for this block, or if the index is out of
# bounds, this field will be null.
transactionAt(index: Int!): Transaction
# Logs returns a filtered set of logs from this block.
logs(filter: BlockFilterCriteria!): [Log!]!
}
# CallData represents the data associated with a local contract call.
# All fields are optional.
input CallData {
# From is the address making the call.
from: Address
# To is the address the call is sent to.
to: Address
# Gas is the amount of gas sent with the call.
gas: Long
# GasPrice is the price, in wei, offered for each unit of gas.
gasPrice: BigInt
# Value is the value, in wei, sent along with the call.
value: BigInt
# Data is the data sent to the callee.
data: Bytes
}
# CallResult is the result of a local call operationn.
type CallResult {
# Data is the return data of the called contract.
data: Bytes!
# GasUsed is the amount of gas used by the call, after any refunds.
gasUsed: Long!
# Status is the result of the call - 1 for success or 0 for failure.
status: Long!
}
# FilterCriteria encapsulates log filter criteria for searching log entries.
input FilterCriteria {
# FromBlock is the block at which to start searching, inclusive. Defaults
# to the latest block if not supplied.
fromBlock: Long
# ToBlock is the block at which to stop searching, inclusive. Defaults
# to the latest block if not supplied.
toBlock: Long
# Addresses is a list of addresses that are of interest. If this list is
# empty, results will not be filtered by address.
addresses: [Address!]
# Topics list restricts matches to particular event topics. Each event has a list
# of topics. Topics matches a prefix of that list. An empty element array matches any
# topic. Non-empty elements represent an alternative that matches any of the
# contained topics.
#
# Examples:
# - [] or nil matches any topic list
# - [[A]] matches topic A in first position
# - [[], [B]] matches any topic in first position, B in second position
# - [[A], [B]] matches topic A in first position, B in second position
# - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
topics: [[Bytes32!]!]
}
# SyncState contains the current synchronisation state of the client.
type SyncState{
# StartingBlock is the block number at which synchronisation started.
startingBlock: Long!
# CurrentBlock is the point at which synchronisation has presently reached.
currentBlock: Long!
# HighestBlock is the latest known block number.
highestBlock: Long!
# PulledStates is the number of state entries fetched so far, or null
# if this is not known or not relevant.
pulledStates: Long
# KnownStates is the number of states the node knows of so far, or null
# if this is not known or not relevant.
knownStates: Long
}
type Query {
# Account fetches an Ethereum account at the specified block number.
# If blockNumber is not provided, it defaults to the most recent block.
account(address: Address!, blockNumber: Long): Account!
# Block fetches an Ethereum block by number or by hash. If neither is
# supplied, the most recent known block is returned.
block(number: Long, hash: Bytes32): Block
# Blocks returns all the blocks between two numbers, inclusive. If
# to is not supplied, it defaults to the most recent known block.
blocks(from: Long!, to: Long): [Block!]!
# Transaction returns a transaction specified by its hash.
transaction(hash: Bytes32!): Transaction
# Call executes a local call operation. If blockNumber is not specified,
# it defaults to the most recent known block.
call(data: CallData!, blockNumber: Long): CallResult
# EstimateGas estimates the amount of gas that will be required for
# successful execution of a transaction. If blockNumber is not specified,
# it defaults ot the most recent known block.
estimateGas(data: CallData!, blockNumber: Long): Long!
# Logs returns log entries matching the provided filter.
logs(filter: FilterCriteria!): [Log!]!
# GasPrice returns the node's estimate of a gas price sufficient to
# ensure a transaction is mined in a timely fashion.
gasPrice: BigInt!
# ProtocolVersion returns the current wire protocol version number.
protocolVersion: Int!
# Syncing returns information on the current synchronisation state.
syncing: SyncState
}
type Mutation {
# SendRawTransaction sends an RLP-encoded transaction to the network.
sendRawTransaction(data: Bytes!): Bytes32!
}
`

View file

@ -144,9 +144,9 @@ type FilterQuery struct {
// Examples: // Examples:
// {} or nil matches any topic list // {} or nil matches any topic list
// {{A}} matches topic A in first position // {{A}} matches topic A in first position
// {{}, {B}} matches any topic in first position, B in second position // {{}, {B}} matches any topic in first position AND B in second position
// {{A}, {B}} matches topic A in first position, B in second position // {{A}, {B}} matches topic A in first position AND B in second position
// {{A, B}, {C, D}} matches topic (A OR B) in first position, (C OR D) in second position // {{A, B}, {C, D}} matches topic (A OR B) in first position AND (C OR D) in second position
Topics [][]common.Hash Topics [][]common.Hash
} }

View file

@ -378,7 +378,7 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs
log.Warn("Failed transaction send attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err) log.Warn("Failed transaction send attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err)
return common.Hash{}, err return common.Hash{}, err
} }
return submitTransaction(ctx, s.b, signed) return SubmitTransaction(ctx, s.b, signed)
} }
// SignTransaction will create a transaction from the given arguments and // SignTransaction will create a transaction from the given arguments and
@ -675,41 +675,54 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A
// CallArgs represents the arguments for a call. // CallArgs represents the arguments for a call.
type CallArgs struct { type CallArgs struct {
From common.Address `json:"from"` From *common.Address `json:"from"`
To *common.Address `json:"to"` To *common.Address `json:"to"`
Gas hexutil.Uint64 `json:"gas"` Gas *hexutil.Uint64 `json:"gas"`
GasPrice hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
Value hexutil.Big `json:"value"` Value *hexutil.Big `json:"value"`
Data hexutil.Bytes `json:"data"` Data *hexutil.Bytes `json:"data"`
} }
func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, timeout time.Duration) ([]byte, uint64, bool, error) { func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) ([]byte, uint64, bool, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
state, header, err := s.b.StateAndHeaderByNumber(ctx, blockNr) state, header, err := b.StateAndHeaderByNumber(ctx, blockNr)
if state == nil || err != nil { if state == nil || err != nil {
return nil, 0, false, err return nil, 0, false, err
} }
// Set sender address or use a default if none specified // Set sender address or use a default if none specified
addr := args.From var addr common.Address
if addr == (common.Address{}) { if args.From == nil {
if wallets := s.b.AccountManager().Wallets(); len(wallets) > 0 { if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
if accounts := wallets[0].Accounts(); len(accounts) > 0 { if accounts := wallets[0].Accounts(); len(accounts) > 0 {
addr = accounts[0].Address addr = accounts[0].Address
} }
} }
} else {
addr = *args.From
} }
// Set default gas & gas price if none were set // Set default gas & gas price if none were set
gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt() gas := uint64(math.MaxUint64 / 2)
if gas == 0 { if args.Gas != nil {
gas = math.MaxUint64 / 2 gas = uint64(*args.Gas)
} }
if gasPrice.Sign() == 0 { gasPrice := new(big.Int).SetUint64(defaultGasPrice)
gasPrice = new(big.Int).SetUint64(defaultGasPrice) if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}
value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}
var data []byte
if args.Data != nil {
data = []byte(*args.Data)
} }
// Create new call message // Create new call message
msg := types.NewMessage(addr, args.To, 0, args.Value.ToInt(), gas, gasPrice, args.Data, false) msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false)
// Setup context so it may be cancelled the call has completed // Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout. // or, in case of unmetered gas, setup a context with a timeout.
@ -724,7 +737,7 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr
defer cancel() defer cancel()
// Get a new instance of the EVM. // Get a new instance of the EVM.
evm, vmError, err := s.b.GetEVM(ctx, msg, state, header) evm, vmError, err := b.GetEVM(ctx, msg, state, header)
if err != nil { if err != nil {
return nil, 0, false, err return nil, 0, false, err
} }
@ -748,24 +761,22 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr
// Call executes the given transaction on the state for the given block number. // Call executes the given transaction on the state for the given block number.
// It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. // It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values.
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
result, _, _, err := s.doCall(ctx, args, blockNr, 5*time.Second) result, _, _, err := DoCall(ctx, s.b, args, blockNr, vm.Config{}, 5*time.Second)
return (hexutil.Bytes)(result), err return (hexutil.Bytes)(result), err
} }
// EstimateGas returns an estimate of the amount of gas needed to execute the func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Uint64, error) {
// given transaction against the current pending block.
func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) {
// Binary search the gas requirement, as it may be higher than the amount used // Binary search the gas requirement, as it may be higher than the amount used
var ( var (
lo uint64 = params.TxGas - 1 lo uint64 = params.TxGas - 1
hi uint64 hi uint64
cap uint64 cap uint64
) )
if uint64(args.Gas) >= params.TxGas { if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
hi = uint64(args.Gas) hi = uint64(*args.Gas)
} else { } else {
// Retrieve the current pending block to act as the gas ceiling // Retrieve the block to act as the gas ceiling
block, err := s.b.BlockByNumber(ctx, rpc.PendingBlockNumber) block, err := b.BlockByNumber(ctx, blockNr)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -775,9 +786,9 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h
// Create a helper to check if a gas allowance results in an executable transaction // Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) bool { executable := func(gas uint64) bool {
args.Gas = hexutil.Uint64(gas) args.Gas = (*hexutil.Uint64)(&gas)
_, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, 0) _, _, failed, err := DoCall(ctx, b, args, rpc.PendingBlockNumber, vm.Config{}, 0)
if err != nil || failed { if err != nil || failed {
return false return false
} }
@ -801,6 +812,12 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h
return hexutil.Uint64(hi), nil return hexutil.Uint64(hi), nil
} }
// EstimateGas returns an estimate of the amount of gas needed to execute the
// given transaction against the current pending block.
func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) {
return DoEstimateGas(ctx, s.b, args, rpc.PendingBlockNumber)
}
// ExecutionResult groups all structured logs emitted by the EVM // ExecutionResult groups all structured logs emitted by the EVM
// while replaying a transaction in debug mode as well as transaction // while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value // execution status, the amount of gas used and the return value
@ -825,7 +842,7 @@ type StructLogRes struct {
Storage *map[string]string `json:"storage,omitempty"` Storage *map[string]string `json:"storage,omitempty"`
} }
// formatLogs formats EVM returned structured logs for json output // FormatLogs formats EVM returned structured logs for json output
func FormatLogs(logs []vm.StructLog) []StructLogRes { func FormatLogs(logs []vm.StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs)) formatted := make([]StructLogRes, len(logs))
for index, trace := range logs { for index, trace := range logs {
@ -1256,8 +1273,8 @@ func (args *SendTxArgs) toTransaction() *types.Transaction {
return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
} }
// submitTransaction is a helper function that submits tx to txPool and logs a message. // SubmitTransaction is a helper function that submits tx to txPool and logs a message.
func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) { func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
if err := b.SendTx(ctx, tx); err != nil { if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -1309,7 +1326,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
return submitTransaction(ctx, s.b, signed) return SubmitTransaction(ctx, s.b, signed)
} }
// SendRawTransaction will add the signed transaction to the transaction pool. // SendRawTransaction will add the signed transaction to the transaction pool.
@ -1319,7 +1336,7 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
if err := rlp.DecodeBytes(encodedTx, tx); err != nil { if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
return submitTransaction(ctx, s.b, tx) return SubmitTransaction(ctx, s.b, tx)
} }
// Sign calculates an ECDSA signature for: // Sign calculates an ECDSA signature for:

View file

@ -109,8 +109,12 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
} }
var trustedNodes []string
if leth.config.ULC != nil {
trustedNodes = leth.config.ULC.TrustedServers
}
leth.relay = NewLesTxRelay(peers, leth.reqDist) leth.relay = NewLesTxRelay(peers, leth.reqDist)
leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg) leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg, trustedNodes)
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever) leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever)
@ -136,10 +140,33 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
} }
leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay) leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
if leth.protocolManager, err = NewProtocolManager(leth.chainConfig, light.DefaultClientIndexerConfig, true, config.NetworkId, leth.eventMux, leth.engine, leth.peers, leth.blockchain, nil, chainDb, leth.odr, leth.relay, leth.serverPool, quitSync, &leth.wg); err != nil {
if leth.protocolManager, err = NewProtocolManager(
leth.chainConfig,
light.DefaultClientIndexerConfig,
true,
config.NetworkId,
leth.eventMux,
leth.engine,
leth.peers,
leth.blockchain,
nil,
chainDb,
leth.odr,
leth.relay,
leth.serverPool,
quitSync,
&leth.wg,
config.ULC); err != nil {
return nil, err return nil, err
} }
if leth.protocolManager.isULCEnabled() {
log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.protocolManager.ulc.trustedKeys), "minTrustedFraction", leth.protocolManager.ulc.minTrustedFraction)
leth.blockchain.DisableCheckFreq()
}
leth.ApiBackend = &LesApiBackend{leth, nil} leth.ApiBackend = &LesApiBackend{leth, nil}
gpoParams := config.GPO gpoParams := config.GPO
if gpoParams.Default == nil { if gpoParams.Default == nil {
gpoParams.Default = config.MinerGasPrice gpoParams.Default = config.MinerGasPrice

View file

@ -43,7 +43,7 @@ const (
type lightFetcher struct { type lightFetcher struct {
pm *ProtocolManager pm *ProtocolManager
odr *LesOdr odr *LesOdr
chain *light.LightChain chain lightChain
lock sync.Mutex // lock protects access to the fetcher's internal state variables except sent requests lock sync.Mutex // lock protects access to the fetcher's internal state variables except sent requests
maxConfirmedTd *big.Int maxConfirmedTd *big.Int
@ -52,11 +52,19 @@ type lightFetcher struct {
syncing bool syncing bool
syncDone chan *peer syncDone chan *peer
reqMu sync.RWMutex // reqMu protects access to sent header fetch requests reqMu sync.RWMutex // reqMu protects access to sent header fetch requests
requested map[uint64]fetchRequest requested map[uint64]fetchRequest
deliverChn chan fetchResponse deliverChn chan fetchResponse
timeoutChn chan uint64 timeoutChn chan uint64
requestChn chan bool // true if initiated from outside requestChn chan bool // true if initiated from outside
lastTrustedHeader *types.Header
}
// lightChain extends the BlockChain interface by locking.
type lightChain interface {
BlockChain
LockChain()
UnlockChain()
} }
// fetcherPeerInfo holds fetcher-specific information about each active peer // fetcherPeerInfo holds fetcher-specific information about each active peer
@ -145,6 +153,7 @@ func (f *lightFetcher) syncLoop() {
reqID uint64 reqID uint64
syncing bool syncing bool
) )
if !f.syncing && !(newAnnounce && s) { if !f.syncing && !(newAnnounce && s) {
rq, reqID, syncing = f.nextRequest() rq, reqID, syncing = f.nextRequest()
} }
@ -227,7 +236,6 @@ func (f *lightFetcher) registerPeer(p *peer) {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
f.peers[p] = &fetcherPeerInfo{nodeByHash: make(map[common.Hash]*fetcherTreeNode)} f.peers[p] = &fetcherPeerInfo{nodeByHash: make(map[common.Hash]*fetcherTreeNode)}
} }
@ -280,8 +288,10 @@ func (f *lightFetcher) announce(p *peer, head *announceData) {
fp.nodeCnt = 0 fp.nodeCnt = 0
fp.nodeByHash = make(map[common.Hash]*fetcherTreeNode) fp.nodeByHash = make(map[common.Hash]*fetcherTreeNode)
} }
// check if the node count is too high to add new nodes, discard oldest ones if necessary
if n != nil { if n != nil {
// check if the node count is too high to add new nodes, discard oldest ones if necessary // n is now the reorg common ancestor, add a new branch of nodes
// check if the node count is too high to add new nodes
locked := false locked := false
for uint64(fp.nodeCnt)+head.Number-n.number > maxNodeCount && fp.root != nil { for uint64(fp.nodeCnt)+head.Number-n.number > maxNodeCount && fp.root != nil {
if !locked { if !locked {
@ -325,6 +335,7 @@ func (f *lightFetcher) announce(p *peer, head *announceData) {
fp.nodeByHash[n.hash] = n fp.nodeByHash[n.hash] = n
} }
} }
if n == nil { if n == nil {
// could not find reorg common ancestor or had to delete entire tree, a new root and a resync is needed // could not find reorg common ancestor or had to delete entire tree, a new root and a resync is needed
if fp.root != nil { if fp.root != nil {
@ -411,25 +422,13 @@ func (f *lightFetcher) requestedID(reqID uint64) bool {
// to be downloaded starting from the head backwards is also returned // to be downloaded starting from the head backwards is also returned
func (f *lightFetcher) nextRequest() (*distReq, uint64, bool) { func (f *lightFetcher) nextRequest() (*distReq, uint64, bool) {
var ( var (
bestHash common.Hash bestHash common.Hash
bestAmount uint64 bestAmount uint64
bestTd *big.Int
bestSyncing bool
) )
bestTd := f.maxConfirmedTd bestHash, bestAmount, bestTd, bestSyncing = f.findBestRequest()
bestSyncing := false
for p, fp := range f.peers {
for hash, n := range fp.nodeByHash {
if !f.checkKnownNode(p, n) && !n.requested && (bestTd == nil || n.td.Cmp(bestTd) >= 0) {
amount := f.requestAmount(p, n)
if bestTd == nil || n.td.Cmp(bestTd) > 0 || amount < bestAmount {
bestHash = hash
bestAmount = amount
bestTd = n.td
bestSyncing = fp.bestConfirmed == nil || fp.root == nil || !f.checkKnownNode(p, fp.root)
}
}
}
}
if bestTd == f.maxConfirmedTd { if bestTd == f.maxConfirmedTd {
return nil, 0, false return nil, 0, false
} }
@ -437,74 +436,142 @@ func (f *lightFetcher) nextRequest() (*distReq, uint64, bool) {
var rq *distReq var rq *distReq
reqID := genReqID() reqID := genReqID()
if bestSyncing { if bestSyncing {
rq = &distReq{ rq = f.newFetcherDistReqForSync(bestHash)
getCost: func(dp distPeer) uint64 {
return 0
},
canSend: func(dp distPeer) bool {
p := dp.(*peer)
f.lock.Lock()
defer f.lock.Unlock()
fp := f.peers[p]
return fp != nil && fp.nodeByHash[bestHash] != nil
},
request: func(dp distPeer) func() {
go func() {
p := dp.(*peer)
p.Log().Debug("Synchronisation started")
f.pm.synchronise(p)
f.syncDone <- p
}()
return nil
},
}
} else { } else {
rq = &distReq{ rq = f.newFetcherDistReq(bestHash, reqID, bestAmount)
getCost: func(dp distPeer) uint64 {
p := dp.(*peer)
return p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount))
},
canSend: func(dp distPeer) bool {
p := dp.(*peer)
f.lock.Lock()
defer f.lock.Unlock()
fp := f.peers[p]
if fp == nil {
return false
}
n := fp.nodeByHash[bestHash]
return n != nil && !n.requested
},
request: func(dp distPeer) func() {
p := dp.(*peer)
f.lock.Lock()
fp := f.peers[p]
if fp != nil {
n := fp.nodeByHash[bestHash]
if n != nil {
n.requested = true
}
}
f.lock.Unlock()
cost := p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount))
p.fcServer.QueueRequest(reqID, cost)
f.reqMu.Lock()
f.requested[reqID] = fetchRequest{hash: bestHash, amount: bestAmount, peer: p, sent: mclock.Now()}
f.reqMu.Unlock()
go func() {
time.Sleep(hardRequestTimeout)
f.timeoutChn <- reqID
}()
return func() { p.RequestHeadersByHash(reqID, cost, bestHash, int(bestAmount), 0, true) }
},
}
} }
return rq, reqID, bestSyncing return rq, reqID, bestSyncing
} }
// findBestRequest finds the best head to request that has been announced by but not yet requested from a known peer.
// It also returns the announced Td (which should be verified after fetching the head),
// the necessary amount to request and whether a downloader sync is necessary instead of a normal header request.
func (f *lightFetcher) findBestRequest() (bestHash common.Hash, bestAmount uint64, bestTd *big.Int, bestSyncing bool) {
bestTd = f.maxConfirmedTd
bestSyncing = false
for p, fp := range f.peers {
for hash, n := range fp.nodeByHash {
if f.checkKnownNode(p, n) || n.requested {
continue
}
//if ulc mode is disabled, isTrustedHash returns true
amount := f.requestAmount(p, n)
if (bestTd == nil || n.td.Cmp(bestTd) > 0 || amount < bestAmount) && (f.isTrustedHash(hash) || f.maxConfirmedTd.Int64() == 0) {
bestHash = hash
bestTd = n.td
bestAmount = amount
bestSyncing = fp.bestConfirmed == nil || fp.root == nil || !f.checkKnownNode(p, fp.root)
}
}
}
return
}
// isTrustedHash checks if the block can be trusted by the minimum trusted fraction.
func (f *lightFetcher) isTrustedHash(hash common.Hash) bool {
if !f.pm.isULCEnabled() {
return true
}
var numAgreed int
for p, fp := range f.peers {
if !p.isTrusted {
continue
}
if _, ok := fp.nodeByHash[hash]; !ok {
continue
}
numAgreed++
}
return 100*numAgreed/len(f.pm.ulc.trustedKeys) >= f.pm.ulc.minTrustedFraction
}
func (f *lightFetcher) newFetcherDistReqForSync(bestHash common.Hash) *distReq {
return &distReq{
getCost: func(dp distPeer) uint64 {
return 0
},
canSend: func(dp distPeer) bool {
p := dp.(*peer)
f.lock.Lock()
defer f.lock.Unlock()
if p.isOnlyAnnounce {
return false
}
fp := f.peers[p]
return fp != nil && fp.nodeByHash[bestHash] != nil
},
request: func(dp distPeer) func() {
if f.pm.isULCEnabled() {
//keep last trusted header before sync
f.setLastTrustedHeader(f.chain.CurrentHeader())
}
go func() {
p := dp.(*peer)
p.Log().Debug("Synchronisation started")
f.pm.synchronise(p)
f.syncDone <- p
}()
return nil
},
}
}
// newFetcherDistReq creates a new request for the distributor.
func (f *lightFetcher) newFetcherDistReq(bestHash common.Hash, reqID uint64, bestAmount uint64) *distReq {
return &distReq{
getCost: func(dp distPeer) uint64 {
p := dp.(*peer)
return p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount))
},
canSend: func(dp distPeer) bool {
p := dp.(*peer)
f.lock.Lock()
defer f.lock.Unlock()
if p.isOnlyAnnounce {
return false
}
fp := f.peers[p]
if fp == nil {
return false
}
n := fp.nodeByHash[bestHash]
return n != nil && !n.requested
},
request: func(dp distPeer) func() {
p := dp.(*peer)
f.lock.Lock()
fp := f.peers[p]
if fp != nil {
n := fp.nodeByHash[bestHash]
if n != nil {
n.requested = true
}
}
f.lock.Unlock()
cost := p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount))
p.fcServer.QueueRequest(reqID, cost)
f.reqMu.Lock()
f.requested[reqID] = fetchRequest{hash: bestHash, amount: bestAmount, peer: p, sent: mclock.Now()}
f.reqMu.Unlock()
go func() {
time.Sleep(hardRequestTimeout)
f.timeoutChn <- reqID
}()
return func() { p.RequestHeadersByHash(reqID, cost, bestHash, int(bestAmount), 0, true) }
},
}
}
// deliverHeaders delivers header download request responses for processing // deliverHeaders delivers header download request responses for processing
func (f *lightFetcher) deliverHeaders(peer *peer, reqID uint64, headers []*types.Header) { func (f *lightFetcher) deliverHeaders(peer *peer, reqID uint64, headers []*types.Header) {
f.deliverChn <- fetchResponse{reqID: reqID, headers: headers, peer: peer} f.deliverChn <- fetchResponse{reqID: reqID, headers: headers, peer: peer}
@ -520,6 +587,7 @@ func (f *lightFetcher) processResponse(req fetchRequest, resp fetchResponse) boo
for i, header := range resp.headers { for i, header := range resp.headers {
headers[int(req.amount)-1-i] = header headers[int(req.amount)-1-i] = header
} }
if _, err := f.chain.InsertHeaderChain(headers, 1); err != nil { if _, err := f.chain.InsertHeaderChain(headers, 1); err != nil {
if err == consensus.ErrFutureBlock { if err == consensus.ErrFutureBlock {
return true return true
@ -544,6 +612,7 @@ func (f *lightFetcher) processResponse(req fetchRequest, resp fetchResponse) boo
// downloaded and validated batch or headers // downloaded and validated batch or headers
func (f *lightFetcher) newHeaders(headers []*types.Header, tds []*big.Int) { func (f *lightFetcher) newHeaders(headers []*types.Header, tds []*big.Int) {
var maxTd *big.Int var maxTd *big.Int
for p, fp := range f.peers { for p, fp := range f.peers {
if !f.checkAnnouncedHeaders(fp, headers, tds) { if !f.checkAnnouncedHeaders(fp, headers, tds) {
p.Log().Debug("Inconsistent announcement") p.Log().Debug("Inconsistent announcement")
@ -553,6 +622,7 @@ func (f *lightFetcher) newHeaders(headers []*types.Header, tds []*big.Int) {
maxTd = fp.confirmedTd maxTd = fp.confirmedTd
} }
} }
if maxTd != nil { if maxTd != nil {
f.updateMaxConfirmedTd(maxTd) f.updateMaxConfirmedTd(maxTd)
} }
@ -640,22 +710,72 @@ func (f *lightFetcher) checkSyncedHeaders(p *peer) {
p.Log().Debug("Unknown peer to check sync headers") p.Log().Debug("Unknown peer to check sync headers")
return return
} }
n := fp.lastAnnounced n := fp.lastAnnounced
var td *big.Int var td *big.Int
var h *types.Header
if f.pm.isULCEnabled() {
var unapprovedHashes []common.Hash
// Overwrite last announced for ULC mode
h, unapprovedHashes = f.lastTrustedTreeNode(p)
//rollback untrusted blocks
f.chain.Rollback(unapprovedHashes)
//overwrite to last trusted
n = fp.nodeByHash[h.Hash()]
}
//find last valid block
for n != nil { for n != nil {
if td = f.chain.GetTd(n.hash, n.number); td != nil { if td = f.chain.GetTd(n.hash, n.number); td != nil {
break break
} }
n = n.parent n = n.parent
} }
// now n is the latest downloaded header after syncing
// Now n is the latest downloaded/approved header after syncing
if n == nil { if n == nil {
p.Log().Debug("Synchronisation failed") p.Log().Debug("Synchronisation failed")
go f.pm.removePeer(p.id) go f.pm.removePeer(p.id)
} else { return
header := f.chain.GetHeader(n.hash, n.number)
f.newHeaders([]*types.Header{header}, []*big.Int{td})
} }
header := f.chain.GetHeader(n.hash, n.number)
f.newHeaders([]*types.Header{header}, []*big.Int{td})
}
// lastTrustedTreeNode return last approved treeNode and a list of unapproved hashes
func (f *lightFetcher) lastTrustedTreeNode(p *peer) (*types.Header, []common.Hash) {
unapprovedHashes := make([]common.Hash, 0)
current := f.chain.CurrentHeader()
if f.lastTrustedHeader == nil {
return current, unapprovedHashes
}
canonical := f.chain.CurrentHeader()
if canonical.Number.Uint64() > f.lastTrustedHeader.Number.Uint64() {
canonical = f.chain.GetHeaderByNumber(f.lastTrustedHeader.Number.Uint64())
}
commonAncestor := rawdb.FindCommonAncestor(f.pm.chainDb, canonical, f.lastTrustedHeader)
if commonAncestor == nil {
log.Error("Common ancestor of last trusted header and canonical header is nil", "canonical hash", canonical.Hash(), "trusted hash", f.lastTrustedHeader.Hash())
return current, unapprovedHashes
}
for current.Hash() == commonAncestor.Hash() {
if f.isTrustedHash(current.Hash()) {
break
}
unapprovedHashes = append(unapprovedHashes, current.Hash())
current = f.chain.GetHeader(current.ParentHash, current.Number.Uint64()-1)
}
return current, unapprovedHashes
}
func (f *lightFetcher) setLastTrustedHeader(h *types.Header) {
f.lock.Lock()
defer f.lock.Unlock()
f.lastTrustedHeader = h
} }
// checkKnownNode checks if a block tree node is known (downloaded and validated) // checkKnownNode checks if a block tree node is known (downloaded and validated)
@ -747,6 +867,7 @@ func (f *lightFetcher) updateMaxConfirmedTd(td *big.Int) {
if f.lastUpdateStats != nil { if f.lastUpdateStats != nil {
f.lastUpdateStats.next = newEntry f.lastUpdateStats.next = newEntry
} }
f.lastUpdateStats = newEntry f.lastUpdateStats = newEntry
for p := range f.peers { for p := range f.peers {
f.checkUpdateStats(p, newEntry) f.checkUpdateStats(p, newEntry)
@ -769,6 +890,7 @@ func (f *lightFetcher) checkUpdateStats(p *peer, newEntry *updateStatsEntry) {
p.Log().Debug("Unknown peer to check update stats") p.Log().Debug("Unknown peer to check update stats")
return return
} }
if newEntry != nil && fp.firstUpdateStats == nil { if newEntry != nil && fp.firstUpdateStats == nil {
fp.firstUpdateStats = newEntry fp.firstUpdateStats = newEntry
} }

155
les/fetcher_test.go Normal file
View file

@ -0,0 +1,155 @@
package les
import (
"math/big"
"testing"
"net"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
)
func TestFetcherULCPeerSelector(t *testing.T) {
var (
id1 enode.ID = newNodeID(t).ID()
id2 enode.ID = newNodeID(t).ID()
id3 enode.ID = newNodeID(t).ID()
id4 enode.ID = newNodeID(t).ID()
)
ftn1 := &fetcherTreeNode{
hash: common.HexToHash("1"),
td: big.NewInt(1),
}
ftn2 := &fetcherTreeNode{
hash: common.HexToHash("2"),
td: big.NewInt(2),
parent: ftn1,
}
ftn3 := &fetcherTreeNode{
hash: common.HexToHash("3"),
td: big.NewInt(3),
parent: ftn2,
}
lf := lightFetcher{
pm: &ProtocolManager{
ulc: &ulc{
trustedKeys: map[string]struct{}{
id1.String(): {},
id2.String(): {},
id3.String(): {},
id4.String(): {},
},
minTrustedFraction: 70,
},
},
maxConfirmedTd: ftn1.td,
peers: map[*peer]*fetcherPeerInfo{
{
id: "peer1",
Peer: p2p.NewPeer(id1, "peer1", []p2p.Cap{}),
isTrusted: true,
}: {
nodeByHash: map[common.Hash]*fetcherTreeNode{
ftn1.hash: ftn1,
ftn2.hash: ftn2,
},
},
{
Peer: p2p.NewPeer(id2, "peer2", []p2p.Cap{}),
id: "peer2",
isTrusted: true,
}: {
nodeByHash: map[common.Hash]*fetcherTreeNode{
ftn1.hash: ftn1,
ftn2.hash: ftn2,
},
},
{
id: "peer3",
Peer: p2p.NewPeer(id3, "peer3", []p2p.Cap{}),
isTrusted: true,
}: {
nodeByHash: map[common.Hash]*fetcherTreeNode{
ftn1.hash: ftn1,
ftn2.hash: ftn2,
ftn3.hash: ftn3,
},
},
{
id: "peer4",
Peer: p2p.NewPeer(id4, "peer4", []p2p.Cap{}),
isTrusted: true,
}: {
nodeByHash: map[common.Hash]*fetcherTreeNode{
ftn1.hash: ftn1,
},
},
},
chain: &lightChainStub{
tds: map[common.Hash]*big.Int{},
headers: map[common.Hash]*types.Header{
ftn1.hash: {},
ftn2.hash: {},
ftn3.hash: {},
},
},
}
bestHash, bestAmount, bestTD, sync := lf.findBestRequest()
if bestTD == nil {
t.Fatal("Empty result")
}
if bestTD.Cmp(ftn2.td) != 0 {
t.Fatal("bad td", bestTD)
}
if bestHash != ftn2.hash {
t.Fatal("bad hash", bestTD)
}
_, _ = bestAmount, sync
}
type lightChainStub struct {
BlockChain
tds map[common.Hash]*big.Int
headers map[common.Hash]*types.Header
insertHeaderChainAssertFunc func(chain []*types.Header, checkFreq int) (int, error)
}
func (l *lightChainStub) GetHeader(hash common.Hash, number uint64) *types.Header {
if h, ok := l.headers[hash]; ok {
return h
}
return nil
}
func (l *lightChainStub) LockChain() {}
func (l *lightChainStub) UnlockChain() {}
func (l *lightChainStub) GetTd(hash common.Hash, number uint64) *big.Int {
if td, ok := l.tds[hash]; ok {
return td
}
return nil
}
func (l *lightChainStub) InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error) {
return l.insertHeaderChainAssertFunc(chain, checkFreq)
}
func newNodeID(t *testing.T) *enode.Node {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("generate key err:", err)
}
return enode.NewV4(&key.PublicKey, net.IP{}, 35000, 35000)
}

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
@ -119,12 +120,29 @@ type ProtocolManager struct {
// wait group is used for graceful shutdowns during downloading // wait group is used for graceful shutdowns during downloading
// and processing // and processing
wg *sync.WaitGroup wg *sync.WaitGroup
ulc *ulc
} }
// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable // NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
// with the ethereum network. // with the ethereum network.
func NewProtocolManager(chainConfig *params.ChainConfig, indexerConfig *light.IndexerConfig, lightSync bool, networkId uint64, mux *event.TypeMux, engine consensus.Engine, peers *peerSet, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay, serverPool *serverPool, quitSync chan struct{}, wg *sync.WaitGroup) (*ProtocolManager, error) { func NewProtocolManager(
chainConfig *params.ChainConfig,
indexerConfig *light.IndexerConfig,
lightSync bool,
networkId uint64,
mux *event.TypeMux,
engine consensus.Engine,
peers *peerSet,
blockchain BlockChain,
txpool txPool,
chainDb ethdb.Database,
odr *LesOdr,
txrelay *LesTxRelay,
serverPool *serverPool,
quitSync chan struct{},
wg *sync.WaitGroup,
ulcConfig *eth.ULCConfig) (*ProtocolManager, error) {
// Create the protocol manager with the base fields // Create the protocol manager with the base fields
manager := &ProtocolManager{ manager := &ProtocolManager{
lightSync: lightSync, lightSync: lightSync,
@ -149,6 +167,10 @@ func NewProtocolManager(chainConfig *params.ChainConfig, indexerConfig *light.In
manager.reqDist = odr.retriever.dist manager.reqDist = odr.retriever.dist
} }
if ulcConfig != nil {
manager.ulc = newULC(ulcConfig)
}
removePeer := manager.removePeer removePeer := manager.removePeer
if disableClientRemovePeer { if disableClientRemovePeer {
removePeer = func(id string) {} removePeer = func(id string) {}
@ -234,7 +256,11 @@ func (pm *ProtocolManager) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWrit
} }
func (pm *ProtocolManager) newPeer(pv int, nv uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { func (pm *ProtocolManager) newPeer(pv int, nv uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
return newPeer(pv, nv, p, newMeteredMsgWriter(rw)) var isTrusted bool
if pm.isULCEnabled() {
isTrusted = pm.ulc.isTrusted(p.ID())
}
return newPeer(pv, nv, isTrusted, p, newMeteredMsgWriter(rw))
} }
// handle is the callback invoked to manage the life cycle of a les peer. When // handle is the callback invoked to manage the life cycle of a les peer. When
@ -276,6 +302,7 @@ func (pm *ProtocolManager) handle(p *peer) error {
if rw, ok := p.rw.(*meteredMsgReadWriter); ok { if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
rw.Init(p.version) rw.Init(p.version)
} }
// Register the peer locally // Register the peer locally
if err := pm.peers.Register(p); err != nil { if err := pm.peers.Register(p); err != nil {
p.Log().Error("Light Ethereum peer registration failed", "err", err) p.Log().Error("Light Ethereum peer registration failed", "err", err)
@ -287,6 +314,7 @@ func (pm *ProtocolManager) handle(p *peer) error {
} }
pm.removePeer(p.id) pm.removePeer(p.id)
}() }()
// Register the peer in the downloader. If the downloader considers it banned, we disconnect // Register the peer in the downloader. If the downloader considers it banned, we disconnect
if pm.lightSync { if pm.lightSync {
p.lock.Lock() p.lock.Lock()
@ -371,16 +399,15 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
// Block header query, collect the requested headers and reply // Block header query, collect the requested headers and reply
case AnnounceMsg: case AnnounceMsg:
p.Log().Trace("Received announce message") p.Log().Trace("Received announce message")
if p.requestAnnounceType == announceTypeNone { if p.announceType == announceTypeNone {
return errResp(ErrUnexpectedResponse, "") return errResp(ErrUnexpectedResponse, "")
} }
var req announceData var req announceData
if err := msg.Decode(&req); err != nil { if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "%v: %v", msg, err) return errResp(ErrDecode, "%v: %v", msg, err)
} }
if p.requestAnnounceType == announceTypeSigned { if p.announceType == announceTypeSigned {
if err := req.checkSignature(p.ID()); err != nil { if err := req.checkSignature(p.ID()); err != nil {
p.Log().Trace("Invalid announcement signature", "err", err) p.Log().Trace("Invalid announcement signature", "err", err)
return err return err
@ -1175,6 +1202,14 @@ func (pm *ProtocolManager) txStatus(hashes []common.Hash) []txStatus {
return stats return stats
} }
// isULCEnabled returns true if we can use ULC
func (pm *ProtocolManager) isULCEnabled() bool {
if pm.ulc == nil || len(pm.ulc.trustedKeys) == 0 {
return false
}
return true
}
// downloaderPeerNotify implements peerSetNotify // downloaderPeerNotify implements peerSetNotify
type downloaderPeerNotify ProtocolManager type downloaderPeerNotify ProtocolManager

View file

@ -494,7 +494,7 @@ func TestGetBloombitsProofs(t *testing.T) {
func TestTransactionStatusLes2(t *testing.T) { func TestTransactionStatusLes2(t *testing.T) {
db := ethdb.NewMemDatabase() db := ethdb.NewMemDatabase()
pm := newTestProtocolManagerMust(t, false, 0, nil, nil, nil, db) pm := newTestProtocolManagerMust(t, false, 0, nil, nil, nil, db, nil)
chain := pm.blockchain.(*core.BlockChain) chain := pm.blockchain.(*core.BlockChain)
config := core.DefaultTxPoolConfig config := core.DefaultTxPoolConfig
config.Journal = "" config.Journal = ""

View file

@ -146,7 +146,7 @@ func testRCL() RequestCostList {
// newTestProtocolManager creates a new protocol manager for testing purposes, // newTestProtocolManager creates a new protocol manager for testing purposes,
// with the given number of blocks already known, potential notification // with the given number of blocks already known, potential notification
// channels for different events and relative chain indexers array. // channels for different events and relative chain indexers array.
func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *core.BlockGen), odr *LesOdr, peers *peerSet, db ethdb.Database) (*ProtocolManager, error) { func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *core.BlockGen), odr *LesOdr, peers *peerSet, db ethdb.Database, ulcConfig *eth.ULCConfig) (*ProtocolManager, error) {
var ( var (
evmux = new(event.TypeMux) evmux = new(event.TypeMux)
engine = ethash.NewFaker() engine = ethash.NewFaker()
@ -176,7 +176,7 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
if lightSync { if lightSync {
indexConfig = light.TestClientIndexerConfig indexConfig = light.TestClientIndexerConfig
} }
pm, err := NewProtocolManager(gspec.Config, indexConfig, lightSync, NetworkId, evmux, engine, peers, chain, nil, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup)) pm, err := NewProtocolManager(gspec.Config, indexConfig, lightSync, NetworkId, evmux, engine, peers, chain, nil, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup), ulcConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -200,8 +200,8 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
// with the given number of blocks already known, potential notification // with the given number of blocks already known, potential notification
// channels for different events and relative chain indexers array. In case of an error, the constructor force- // channels for different events and relative chain indexers array. In case of an error, the constructor force-
// fails the test. // fails the test.
func newTestProtocolManagerMust(t *testing.T, lightSync bool, blocks int, generator func(int, *core.BlockGen), odr *LesOdr, peers *peerSet, db ethdb.Database) *ProtocolManager { func newTestProtocolManagerMust(t *testing.T, lightSync bool, blocks int, generator func(int, *core.BlockGen), odr *LesOdr, peers *peerSet, db ethdb.Database, ulcConfig *eth.ULCConfig) *ProtocolManager {
pm, err := newTestProtocolManager(lightSync, blocks, generator, odr, peers, db) pm, err := newTestProtocolManager(lightSync, blocks, generator, odr, peers, db, ulcConfig)
if err != nil { if err != nil {
t.Fatalf("Failed to create protocol manager: %v", err) t.Fatalf("Failed to create protocol manager: %v", err)
} }
@ -343,7 +343,7 @@ func newServerEnv(t *testing.T, blocks int, protocol int, waitIndexers func(*cor
db := ethdb.NewMemDatabase() db := ethdb.NewMemDatabase()
cIndexer, bIndexer, btIndexer := testIndexers(db, nil, light.TestServerIndexerConfig) cIndexer, bIndexer, btIndexer := testIndexers(db, nil, light.TestServerIndexerConfig)
pm := newTestProtocolManagerMust(t, false, blocks, testChainGen, nil, nil, db) pm := newTestProtocolManagerMust(t, false, blocks, testChainGen, nil, nil, db, nil)
peer, _ := newTestPeer(t, "peer", protocol, pm, true) peer, _ := newTestPeer(t, "peer", protocol, pm, true)
cIndexer.Start(pm.blockchain.(*core.BlockChain)) cIndexer.Start(pm.blockchain.(*core.BlockChain))
@ -383,8 +383,8 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, waitIndexers fun
lcIndexer, lbIndexer, lbtIndexer := testIndexers(ldb, odr, light.TestClientIndexerConfig) lcIndexer, lbIndexer, lbtIndexer := testIndexers(ldb, odr, light.TestClientIndexerConfig)
odr.SetIndexers(lcIndexer, lbtIndexer, lbIndexer) odr.SetIndexers(lcIndexer, lbtIndexer, lbIndexer)
pm := newTestProtocolManagerMust(t, false, blocks, testChainGen, nil, peers, db) pm := newTestProtocolManagerMust(t, false, blocks, testChainGen, nil, peers, db, nil)
lpm := newTestProtocolManagerMust(t, true, 0, nil, odr, lPeers, ldb) lpm := newTestProtocolManagerMust(t, true, 0, nil, odr, lPeers, ldb, nil)
startIndexers := func(clientMode bool, pm *ProtocolManager) { startIndexers := func(clientMode bool, pm *ProtocolManager) {
if clientMode { if clientMode {

View file

@ -109,7 +109,10 @@ func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err erro
}, },
canSend: func(dp distPeer) bool { canSend: func(dp distPeer) bool {
p := dp.(*peer) p := dp.(*peer)
return lreq.CanSend(p) if !p.isOnlyAnnounce {
return lreq.CanSend(p)
}
return false
}, },
request: func(dp distPeer) func() { request: func(dp distPeer) func() {
p := dp.(*peer) p := dp.(*peer)

View file

@ -56,7 +56,7 @@ type peer struct {
version int // Protocol version negotiated version int // Protocol version negotiated
network uint64 // Network ID being on network uint64 // Network ID being on
announceType, requestAnnounceType uint64 announceType uint64
id string id string
@ -74,9 +74,12 @@ type peer struct {
fcServer *flowcontrol.ServerNode // nil if the peer is client only fcServer *flowcontrol.ServerNode // nil if the peer is client only
fcServerParams *flowcontrol.ServerParams fcServerParams *flowcontrol.ServerParams
fcCosts requestCostTable fcCosts requestCostTable
isTrusted bool
isOnlyAnnounce bool
} }
func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { func newPeer(version int, network uint64, isTrusted bool, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
id := p.ID() id := p.ID()
return &peer{ return &peer{
@ -86,6 +89,7 @@ func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *pe
network: network, network: network,
id: fmt.Sprintf("%x", id[:8]), id: fmt.Sprintf("%x", id[:8]),
announceChn: make(chan announceData, 20), announceChn: make(chan announceData, 20),
isTrusted: isTrusted,
} }
} }
@ -401,23 +405,32 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
send = send.add("headNum", headNum) send = send.add("headNum", headNum)
send = send.add("genesisHash", genesis) send = send.add("genesisHash", genesis)
if server != nil { if server != nil {
send = send.add("serveHeaders", nil) if !server.onlyAnnounce {
send = send.add("serveChainSince", uint64(0)) //only announce server. It sends only announse requests
send = send.add("serveStateSince", uint64(0)) send = send.add("serveHeaders", nil)
send = send.add("txRelay", nil) send = send.add("serveChainSince", uint64(0))
send = send.add("serveStateSince", uint64(0))
send = send.add("txRelay", nil)
}
send = send.add("flowControl/BL", server.defParams.BufLimit) send = send.add("flowControl/BL", server.defParams.BufLimit)
send = send.add("flowControl/MRR", server.defParams.MinRecharge) send = send.add("flowControl/MRR", server.defParams.MinRecharge)
list := server.fcCostStats.getCurrentList() list := server.fcCostStats.getCurrentList()
send = send.add("flowControl/MRC", list) send = send.add("flowControl/MRC", list)
p.fcCosts = list.decode() p.fcCosts = list.decode()
} else { } else {
p.requestAnnounceType = announceTypeSimple // set to default until "very light" client mode is implemented //on client node
send = send.add("announceType", p.requestAnnounceType) p.announceType = announceTypeSimple
if p.isTrusted {
p.announceType = announceTypeSigned
}
send = send.add("announceType", p.announceType)
} }
recvList, err := p.sendReceiveHandshake(send) recvList, err := p.sendReceiveHandshake(send)
if err != nil { if err != nil {
return err return err
} }
recv := recvList.decode() recv := recvList.decode()
var rGenesis, rHash common.Hash var rGenesis, rHash common.Hash
@ -452,25 +465,33 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
if int(rVersion) != p.version { if int(rVersion) != p.version {
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version) return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version)
} }
if server != nil { if server != nil {
// until we have a proper peer connectivity API, allow LES connection to other servers // until we have a proper peer connectivity API, allow LES connection to other servers
/*if recv.get("serveStateSince", nil) == nil { /*if recv.get("serveStateSince", nil) == nil {
return errResp(ErrUselessPeer, "wanted client, got server") return errResp(ErrUselessPeer, "wanted client, got server")
}*/ }*/
if recv.get("announceType", &p.announceType) != nil { if recv.get("announceType", &p.announceType) != nil {
//set default announceType on server side
p.announceType = announceTypeSimple p.announceType = announceTypeSimple
} }
p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams) p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams)
} else { } else {
//mark OnlyAnnounce server if "serveHeaders", "serveChainSince", "serveStateSince" or "txRelay" fields don't exist
if recv.get("serveChainSince", nil) != nil { if recv.get("serveChainSince", nil) != nil {
return errResp(ErrUselessPeer, "peer cannot serve chain") p.isOnlyAnnounce = true
} }
if recv.get("serveStateSince", nil) != nil { if recv.get("serveStateSince", nil) != nil {
return errResp(ErrUselessPeer, "peer cannot serve state") p.isOnlyAnnounce = true
} }
if recv.get("txRelay", nil) != nil { if recv.get("txRelay", nil) != nil {
return errResp(ErrUselessPeer, "peer cannot relay transactions") p.isOnlyAnnounce = true
} }
if p.isOnlyAnnounce && !p.isTrusted {
return errResp(ErrUselessPeer, "peer cannot serve requests")
}
params := &flowcontrol.ServerParams{} params := &flowcontrol.ServerParams{}
if err := recv.get("flowControl/BL", &params.BufLimit); err != nil { if err := recv.get("flowControl/BL", &params.BufLimit); err != nil {
return err return err
@ -486,7 +507,6 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
p.fcServer = flowcontrol.NewServerNode(params) p.fcServer = flowcontrol.NewServerNode(params)
p.fcCosts = MRC.decode() p.fcCosts = MRC.decode()
} }
p.headInfo = &announceData{Td: rTd, Hash: rHash, Number: rNum} p.headInfo = &announceData{Td: rTd, Hash: rHash, Number: rNum}
return nil return nil
} }
@ -576,8 +596,10 @@ func (ps *peerSet) Unregister(id string) error {
for _, n := range peers { for _, n := range peers {
n.unregisterPeer(p) n.unregisterPeer(p)
} }
p.sendQueue.quit() p.sendQueue.quit()
p.Peer.Disconnect(p2p.DiscUselessPeer) p.Peer.Disconnect(p2p.DiscUselessPeer)
return nil return nil
} }
} }

297
les/peer_test.go Normal file
View file

@ -0,0 +1,297 @@
package les
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/les/flowcontrol"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp"
)
const (
test_networkid = 10
protocol_version = 2123
)
var (
hash = common.HexToHash("some string")
genesis = common.HexToHash("genesis hash")
headNum = uint64(1234)
td = big.NewInt(123)
)
//ulc connects to trusted peer and send announceType=announceTypeSigned
func TestPeerHandshakeSetAnnounceTypeToAnnounceTypeSignedForTrustedPeer(t *testing.T) {
var id enode.ID = newNodeID(t).ID()
//peer to connect(on ulc side)
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
isTrusted: true,
rw: &rwStub{
WriteHook: func(recvList keyValueList) {
//checking that ulc sends to peer allowedRequests=onlyAnnounceRequests and announceType = announceTypeSigned
recv := recvList.decode()
var reqType uint64
err := recv.get("announceType", &reqType)
if err != nil {
t.Fatal(err)
}
if reqType != announceTypeSigned {
t.Fatal("Expected announceTypeSigned")
}
},
ReadHook: func(l keyValueList) keyValueList {
l = l.add("serveHeaders", nil)
l = l.add("serveChainSince", uint64(0))
l = l.add("serveStateSince", uint64(0))
l = l.add("txRelay", nil)
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", RequestCostList{})
return l
},
},
network: test_networkid,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
if err != nil {
t.Fatalf("Handshake error: %s", err)
}
if p.announceType != announceTypeSigned {
t.Fatal("Incorrect announceType")
}
}
func TestPeerHandshakeAnnounceTypeSignedForTrustedPeersPeerNotInTrusted(t *testing.T) {
var id enode.ID = newNodeID(t).ID()
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
rw: &rwStub{
WriteHook: func(recvList keyValueList) {
//checking that ulc sends to peer allowedRequests=noRequests and announceType != announceTypeSigned
recv := recvList.decode()
var reqType uint64
err := recv.get("announceType", &reqType)
if err != nil {
t.Fatal(err)
}
if reqType == announceTypeSigned {
t.Fatal("Expected not announceTypeSigned")
}
},
ReadHook: func(l keyValueList) keyValueList {
l = l.add("serveHeaders", nil)
l = l.add("serveChainSince", uint64(0))
l = l.add("serveStateSince", uint64(0))
l = l.add("txRelay", nil)
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", RequestCostList{})
return l
},
},
network: test_networkid,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
if err != nil {
t.Fatal(err)
}
if p.announceType == announceTypeSigned {
t.Fatal("Incorrect announceType")
}
}
func TestPeerHandshakeDefaultAllRequests(t *testing.T) {
var id enode.ID = newNodeID(t).ID()
s := generateLesServer()
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("announceType", uint64(announceTypeSigned))
l = l.add("allowedRequests", uint64(0))
return l
},
},
network: test_networkid,
}
err := p.Handshake(td, hash, headNum, genesis, s)
if err != nil {
t.Fatal(err)
}
if p.isOnlyAnnounce {
t.Fatal("Incorrect announceType")
}
}
func TestPeerHandshakeServerSendOnlyAnnounceRequestsHeaders(t *testing.T) {
var id enode.ID = newNodeID(t).ID()
s := generateLesServer()
s.onlyAnnounce = true
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("announceType", uint64(announceTypeSigned))
return l
},
WriteHook: func(l keyValueList) {
for _, v := range l {
if v.Key == "serveHeaders" ||
v.Key == "serveChainSince" ||
v.Key == "serveStateSince" ||
v.Key == "txRelay" {
t.Fatalf("%v exists", v.Key)
}
}
},
},
network: test_networkid,
}
err := p.Handshake(td, hash, headNum, genesis, s)
if err != nil {
t.Fatal(err)
}
}
func TestPeerHandshakeClientReceiveOnlyAnnounceRequestsHeaders(t *testing.T) {
var id enode.ID = newNodeID(t).ID()
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", RequestCostList{})
l = l.add("announceType", uint64(announceTypeSigned))
return l
},
},
network: test_networkid,
isTrusted: true,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
if err != nil {
t.Fatal(err)
}
if !p.isOnlyAnnounce {
t.Fatal("isOnlyAnnounce must be true")
}
}
func TestPeerHandshakeClientReturnErrorOnUselessPeer(t *testing.T) {
var id enode.ID = newNodeID(t).ID()
p := peer{
Peer: p2p.NewPeer(id, "test peer", []p2p.Cap{}),
version: protocol_version,
rw: &rwStub{
ReadHook: func(l keyValueList) keyValueList {
l = l.add("flowControl/BL", uint64(0))
l = l.add("flowControl/MRR", uint64(0))
l = l.add("flowControl/MRC", RequestCostList{})
l = l.add("announceType", uint64(announceTypeSigned))
return l
},
},
network: test_networkid,
}
err := p.Handshake(td, hash, headNum, genesis, nil)
if err == nil {
t.FailNow()
}
}
func generateLesServer() *LesServer {
s := &LesServer{
defParams: &flowcontrol.ServerParams{
BufLimit: uint64(300000000),
MinRecharge: uint64(50000),
},
fcManager: flowcontrol.NewClientManager(1, 2, 3),
fcCostStats: &requestCostStats{
stats: make(map[uint64]*linReg, len(reqList)),
},
}
for _, code := range reqList {
s.fcCostStats.stats[code] = &linReg{cnt: 100}
}
return s
}
type rwStub struct {
ReadHook func(l keyValueList) keyValueList
WriteHook func(l keyValueList)
}
func (s *rwStub) ReadMsg() (p2p.Msg, error) {
payload := keyValueList{}
payload = payload.add("protocolVersion", uint64(protocol_version))
payload = payload.add("networkId", uint64(test_networkid))
payload = payload.add("headTd", td)
payload = payload.add("headHash", hash)
payload = payload.add("headNum", headNum)
payload = payload.add("genesisHash", genesis)
if s.ReadHook != nil {
payload = s.ReadHook(payload)
}
size, p, err := rlp.EncodeToReader(payload)
if err != nil {
return p2p.Msg{}, err
}
return p2p.Msg{
Size: uint32(size),
Payload: p,
}, nil
}
func (s *rwStub) WriteMsg(m p2p.Msg) error {
recvList := keyValueList{}
if err := m.Decode(&recvList); err != nil {
return err
}
if s.WriteHook != nil {
s.WriteHook(recvList)
}
return nil
}

View file

@ -41,17 +41,34 @@ import (
type LesServer struct { type LesServer struct {
lesCommons lesCommons
fcManager *flowcontrol.ClientManager // nil if our node is client only fcManager *flowcontrol.ClientManager // nil if our node is client only
fcCostStats *requestCostStats fcCostStats *requestCostStats
defParams *flowcontrol.ServerParams defParams *flowcontrol.ServerParams
lesTopics []discv5.Topic lesTopics []discv5.Topic
privateKey *ecdsa.PrivateKey privateKey *ecdsa.PrivateKey
quitSync chan struct{} quitSync chan struct{}
onlyAnnounce bool
} }
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) { func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
quitSync := make(chan struct{}) quitSync := make(chan struct{})
pm, err := NewProtocolManager(eth.BlockChain().Config(), light.DefaultServerIndexerConfig, false, config.NetworkId, eth.EventMux(), eth.Engine(), newPeerSet(), eth.BlockChain(), eth.TxPool(), eth.ChainDb(), nil, nil, nil, quitSync, new(sync.WaitGroup)) pm, err := NewProtocolManager(
eth.BlockChain().Config(),
light.DefaultServerIndexerConfig,
false,
config.NetworkId,
eth.EventMux(),
eth.Engine(),
newPeerSet(),
eth.BlockChain(),
eth.TxPool(),
eth.ChainDb(),
nil,
nil,
nil,
quitSync,
new(sync.WaitGroup),
config.ULC)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -70,8 +87,9 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency), bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency),
protocolManager: pm, protocolManager: pm,
}, },
quitSync: quitSync, quitSync: quitSync,
lesTopics: lesTopics, lesTopics: lesTopics,
onlyAnnounce: config.OnlyAnnounce,
} }
logger := log.New() logger := log.New()
@ -289,10 +307,8 @@ func (s *requestCostStats) getCurrentList() RequestCostList {
defer s.lock.Unlock() defer s.lock.Unlock()
list := make(RequestCostList, len(reqList)) list := make(RequestCostList, len(reqList))
//fmt.Println("RequestCostList")
for idx, code := range reqList { for idx, code := range reqList {
b, m := s.stats[code].calc() b, m := s.stats[code].calc()
//fmt.Println(code, s.stats[code].cnt, b/1000000, m/1000000)
if m < 0 { if m < 0 {
b += m b += m
m = 0 m = 0

View file

@ -126,22 +126,22 @@ type serverPool struct {
discNodes chan *enode.Node discNodes chan *enode.Node
discLookups chan bool discLookups chan bool
trustedNodes map[enode.ID]*enode.Node
entries map[enode.ID]*poolEntry entries map[enode.ID]*poolEntry
timeout, enableRetry chan *poolEntry timeout, enableRetry chan *poolEntry
adjustStats chan poolStatAdjust adjustStats chan poolStatAdjust
connCh chan *connReq
disconnCh chan *disconnReq
registerCh chan *registerReq
knownQueue, newQueue poolEntryQueue knownQueue, newQueue poolEntryQueue
knownSelect, newSelect *weightedRandomSelect knownSelect, newSelect *weightedRandomSelect
knownSelected, newSelected int knownSelected, newSelected int
fastDiscover bool fastDiscover bool
connCh chan *connReq
disconnCh chan *disconnReq
registerCh chan *registerReq
} }
// newServerPool creates a new serverPool instance // newServerPool creates a new serverPool instance
func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool { func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup, trustedNodes []string) *serverPool {
pool := &serverPool{ pool := &serverPool{
db: db, db: db,
quit: quit, quit: quit,
@ -156,7 +156,9 @@ func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *s
knownSelect: newWeightedRandomSelect(), knownSelect: newWeightedRandomSelect(),
newSelect: newWeightedRandomSelect(), newSelect: newWeightedRandomSelect(),
fastDiscover: true, fastDiscover: true,
trustedNodes: parseTrustedNodes(trustedNodes),
} }
pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry) pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry)
pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry) pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry)
return pool return pool
@ -168,6 +170,7 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
pool.dbKey = append([]byte("serverPool/"), []byte(topic)...) pool.dbKey = append([]byte("serverPool/"), []byte(topic)...)
pool.wg.Add(1) pool.wg.Add(1)
pool.loadNodes() pool.loadNodes()
pool.connectToTrustedNodes()
if pool.server.DiscV5 != nil { if pool.server.DiscV5 != nil {
pool.discSetPeriod = make(chan time.Duration, 1) pool.discSetPeriod = make(chan time.Duration, 1)
@ -337,8 +340,10 @@ func (pool *serverPool) eventLoop() {
} }
case node := <-pool.discNodes: case node := <-pool.discNodes:
entry := pool.findOrNewNode(node) if pool.trustedNodes[node.ID()] == nil {
pool.updateCheckDial(entry) entry := pool.findOrNewNode(node)
pool.updateCheckDial(entry)
}
case conv := <-pool.discLookups: case conv := <-pool.discLookups:
if conv { if conv {
@ -355,29 +360,34 @@ func (pool *serverPool) eventLoop() {
} }
case req := <-pool.connCh: case req := <-pool.connCh:
// Handle peer connection requests. if pool.trustedNodes[req.p.ID()] != nil {
entry := pool.entries[req.p.ID()] // ignore trusted nodes
if entry == nil {
entry = pool.findOrNewNode(req.node)
}
if entry.state == psConnected || entry.state == psRegistered {
req.result <- nil req.result <- nil
continue } else {
// Handle peer connection requests.
entry := pool.entries[req.p.ID()]
if entry == nil {
entry = pool.findOrNewNode(req.node)
}
if entry.state == psConnected || entry.state == psRegistered {
req.result <- nil
continue
}
pool.connWg.Add(1)
entry.peer = req.p
entry.state = psConnected
addr := &poolEntryAddress{
ip: req.node.IP(),
port: uint16(req.node.TCP()),
lastSeen: mclock.Now(),
}
entry.lastConnected = addr
entry.addr = make(map[string]*poolEntryAddress)
entry.addr[addr.strKey()] = addr
entry.addrSelect = *newWeightedRandomSelect()
entry.addrSelect.update(addr)
req.result <- entry
} }
pool.connWg.Add(1)
entry.peer = req.p
entry.state = psConnected
addr := &poolEntryAddress{
ip: req.node.IP(),
port: uint16(req.node.TCP()),
lastSeen: mclock.Now(),
}
entry.lastConnected = addr
entry.addr = make(map[string]*poolEntryAddress)
entry.addr[addr.strKey()] = addr
entry.addrSelect = *newWeightedRandomSelect()
entry.addrSelect.update(addr)
req.result <- entry
case req := <-pool.registerCh: case req := <-pool.registerCh:
// Handle peer registration requests. // Handle peer registration requests.
@ -470,11 +480,42 @@ func (pool *serverPool) loadNodes() {
"response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight), "response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight),
"timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight)) "timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight))
pool.entries[e.node.ID()] = e pool.entries[e.node.ID()] = e
pool.knownQueue.setLatest(e) if pool.trustedNodes[e.node.ID()] == nil {
pool.knownSelect.update((*knownEntry)(e)) pool.knownQueue.setLatest(e)
pool.knownSelect.update((*knownEntry)(e))
}
} }
} }
// connectToTrustedNodes adds trusted server nodes as static trusted peers.
//
// Note: trusted nodes are not handled by the server pool logic, they are not
// added to either the known or new selection pools. They are connected/reconnected
// by p2p.Server whenever possible.
func (pool *serverPool) connectToTrustedNodes() {
//connect to trusted nodes
for _, node := range pool.trustedNodes {
pool.server.AddTrustedPeer(node)
pool.server.AddPeer(node)
log.Debug("Added trusted node", "id", node.ID().String())
}
}
// parseTrustedNodes returns valid and parsed enodes
func parseTrustedNodes(trustedNodes []string) map[enode.ID]*enode.Node {
nodes := make(map[enode.ID]*enode.Node)
for _, node := range trustedNodes {
node, err := enode.ParseV4(node)
if err != nil {
log.Warn("Trusted node URL invalid", "enode", node, "err", err)
continue
}
nodes[node.ID()] = node
}
return nodes
}
// saveNodes saves known nodes and their statistics into the database. Nodes are // saveNodes saves known nodes and their statistics into the database. Nodes are
// ordered from least to most recently connected. // ordered from least to most recently connected.
func (pool *serverPool) saveNodes() { func (pool *serverPool) saveNodes() {

View file

@ -121,7 +121,7 @@ func (self *LesTxRelay) send(txs types.Transactions, count int) {
return peer.GetRequestCost(SendTxMsg, len(ll)) return peer.GetRequestCost(SendTxMsg, len(ll))
}, },
canSend: func(dp distPeer) bool { canSend: func(dp distPeer) bool {
return dp.(*peer) == pp return !dp.(*peer).isOnlyAnnounce && dp.(*peer) == pp
}, },
request: func(dp distPeer) func() { request: func(dp distPeer) func() {
peer := dp.(*peer) peer := dp.(*peer)

39
les/ulc.go Normal file
View file

@ -0,0 +1,39 @@
package les
import (
"fmt"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/p2p/enode"
)
type ulc struct {
trustedKeys map[string]struct{}
minTrustedFraction int
}
func newULC(ulcConfig *eth.ULCConfig) *ulc {
if ulcConfig == nil {
return nil
}
m := make(map[string]struct{}, len(ulcConfig.TrustedServers))
for _, id := range ulcConfig.TrustedServers {
node, err := enode.ParseV4(id)
if err != nil {
fmt.Println("node:", id, " err:", err)
continue
}
m[node.ID().String()] = struct{}{}
}
return &ulc{m, ulcConfig.MinTrustedFraction}
}
func (u *ulc) isTrusted(p enode.ID) bool {
if u.trustedKeys == nil {
return false
}
_, ok := u.trustedKeys[p.String()]
return ok
}

239
les/ulc_test.go Normal file
View file

@ -0,0 +1,239 @@
package les
import (
"fmt"
"reflect"
"testing"
"time"
"net"
"crypto/ecdsa"
"math/big"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
)
func TestULCSyncWithOnePeer(t *testing.T) {
f := newFullPeerPair(t, 1, 4, testChainGen)
ulcConfig := &eth.ULCConfig{
MinTrustedFraction: 100,
TrustedServers: []string{f.Node.String()},
}
l := newLightPeer(t, ulcConfig)
if reflect.DeepEqual(f.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("blocks are equal")
}
_, _, err := connectPeers(f, l, 2)
if err != nil {
t.Fatal(err)
}
l.PM.fetcher.lock.Lock()
l.PM.fetcher.nextRequest()
l.PM.fetcher.lock.Unlock()
if !reflect.DeepEqual(f.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("sync doesn't work")
}
}
func TestULCReceiveAnnounce(t *testing.T) {
f := newFullPeerPair(t, 1, 4, testChainGen)
ulcConfig := &eth.ULCConfig{
MinTrustedFraction: 100,
TrustedServers: []string{f.Node.String()},
}
l := newLightPeer(t, ulcConfig)
fPeer, lPeer, err := connectPeers(f, l, 2)
if err != nil {
t.Fatal(err)
}
l.PM.synchronise(fPeer)
//check that the sync is finished correctly
if !reflect.DeepEqual(f.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("sync doesn't work")
}
l.PM.peers.lock.Lock()
if len(l.PM.peers.peers) == 0 {
t.Fatal("peer list should not be empty")
}
l.PM.peers.lock.Unlock()
time.Sleep(time.Second)
//send a signed announce message(payload doesn't matter)
td := f.PM.blockchain.GetTd(l.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Number.Uint64())
announce := announceData{
Number: l.PM.blockchain.CurrentHeader().Number.Uint64() + 1,
Td: td.Add(td, big.NewInt(1)),
}
announce.sign(f.Key)
lPeer.SendAnnounce(announce)
}
func TestULCShouldNotSyncWithTwoPeersOneHaveEmptyChain(t *testing.T) {
f1 := newFullPeerPair(t, 1, 4, testChainGen)
f2 := newFullPeerPair(t, 2, 0, nil)
ulcConf := &ulc{minTrustedFraction: 100, trustedKeys: make(map[string]struct{})}
ulcConf.trustedKeys[f1.Node.ID().String()] = struct{}{}
ulcConf.trustedKeys[f2.Node.ID().String()] = struct{}{}
ulcConfig := &eth.ULCConfig{
MinTrustedFraction: 100,
TrustedServers: []string{f1.Node.String(), f2.Node.String()},
}
l := newLightPeer(t, ulcConfig)
l.PM.ulc.minTrustedFraction = 100
_, _, err := connectPeers(f1, l, 2)
if err != nil {
t.Fatal(err)
}
_, _, err = connectPeers(f2, l, 2)
if err != nil {
t.Fatal(err)
}
l.PM.fetcher.lock.Lock()
l.PM.fetcher.nextRequest()
l.PM.fetcher.lock.Unlock()
if reflect.DeepEqual(f2.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("Incorrect hash: second peer has empty chain")
}
}
func TestULCShouldNotSyncWithThreePeersOneHaveEmptyChain(t *testing.T) {
f1 := newFullPeerPair(t, 1, 3, testChainGen)
f2 := newFullPeerPair(t, 2, 4, testChainGen)
f3 := newFullPeerPair(t, 3, 0, nil)
ulcConfig := &eth.ULCConfig{
MinTrustedFraction: 60,
TrustedServers: []string{f1.Node.String(), f2.Node.String(), f3.Node.String()},
}
l := newLightPeer(t, ulcConfig)
_, _, err := connectPeers(f1, l, 2)
if err != nil {
t.Fatal(err)
}
_, _, err = connectPeers(f2, l, 2)
if err != nil {
t.Fatal(err)
}
_, _, err = connectPeers(f3, l, 2)
if err != nil {
t.Fatal(err)
}
l.PM.fetcher.lock.Lock()
l.PM.fetcher.nextRequest()
l.PM.fetcher.lock.Unlock()
if !reflect.DeepEqual(f1.PM.blockchain.CurrentHeader().Hash(), l.PM.blockchain.CurrentHeader().Hash()) {
t.Fatal("Incorrect hash")
}
}
type pairPeer struct {
Name string
Node *enode.Node
PM *ProtocolManager
Key *ecdsa.PrivateKey
}
func connectPeers(full, light pairPeer, version int) (*peer, *peer, error) {
// Create a message pipe to communicate through
app, net := p2p.MsgPipe()
peerLight := full.PM.newPeer(version, NetworkId, p2p.NewPeer(light.Node.ID(), light.Name, nil), net)
peerFull := light.PM.newPeer(version, NetworkId, p2p.NewPeer(full.Node.ID(), full.Name, nil), app)
// Start the peerLight on a new thread
errc1 := make(chan error, 1)
errc2 := make(chan error, 1)
go func() {
select {
case light.PM.newPeerCh <- peerFull:
errc1 <- light.PM.handle(peerFull)
case <-light.PM.quitSync:
errc1 <- p2p.DiscQuitting
}
}()
go func() {
select {
case full.PM.newPeerCh <- peerLight:
errc2 <- full.PM.handle(peerLight)
case <-full.PM.quitSync:
errc2 <- p2p.DiscQuitting
}
}()
select {
case <-time.After(time.Millisecond * 100):
case err := <-errc1:
return nil, nil, fmt.Errorf("peerLight handshake error: %v", err)
case err := <-errc2:
return nil, nil, fmt.Errorf("peerFull handshake error: %v", err)
}
return peerFull, peerLight, nil
}
// newFullPeerPair creates node with full sync mode
func newFullPeerPair(t *testing.T, index int, numberOfblocks int, chainGen func(int, *core.BlockGen)) pairPeer {
db := ethdb.NewMemDatabase()
pmFull := newTestProtocolManagerMust(t, false, numberOfblocks, chainGen, nil, nil, db, nil)
peerPairFull := pairPeer{
Name: "full node",
PM: pmFull,
}
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("generate key err:", err)
}
peerPairFull.Key = key
peerPairFull.Node = enode.NewV4(&key.PublicKey, net.ParseIP("127.0.0.1"), 35000, 35000)
return peerPairFull
}
// newLightPeer creates node with light sync mode
func newLightPeer(t *testing.T, ulcConfig *eth.ULCConfig) pairPeer {
peers := newPeerSet()
dist := newRequestDistributor(peers, make(chan struct{}))
rm := newRetrieveManager(peers, dist, nil)
ldb := ethdb.NewMemDatabase()
odr := NewLesOdr(ldb, light.DefaultClientIndexerConfig, rm)
pmLight := newTestProtocolManagerMust(t, true, 0, nil, odr, peers, ldb, ulcConfig)
peerPairLight := pairPeer{
Name: "ulc node",
PM: pmLight,
}
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("generate key err:", err)
}
peerPairLight.Key = key
peerPairLight.Node = enode.NewV4(&key.PublicKey, net.IP{}, 35000, 35000)
return peerPairLight
}

View file

@ -35,7 +35,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
) )
var ( var (
@ -50,6 +50,7 @@ type LightChain struct {
hc *core.HeaderChain hc *core.HeaderChain
indexerConfig *IndexerConfig indexerConfig *IndexerConfig
chainDb ethdb.Database chainDb ethdb.Database
engine consensus.Engine
odr OdrBackend odr OdrBackend
chainFeed event.Feed chainFeed event.Feed
chainSideFeed event.Feed chainSideFeed event.Feed
@ -57,20 +58,18 @@ type LightChain struct {
scope event.SubscriptionScope scope event.SubscriptionScope
genesisBlock *types.Block genesisBlock *types.Block
mu sync.RWMutex
chainmu sync.RWMutex
bodyCache *lru.Cache // Cache for the most recent block bodies bodyCache *lru.Cache // Cache for the most recent block bodies
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
blockCache *lru.Cache // Cache for the most recent entire blocks blockCache *lru.Cache // Cache for the most recent entire blocks
chainmu sync.RWMutex // protects header inserts
quit chan struct{} quit chan struct{}
running int32 // running must be called automically wg sync.WaitGroup
// procInterrupt must be atomically called
procInterrupt int32 // interrupt signaler for block processing
wg sync.WaitGroup
engine consensus.Engine // Atomic boolean switches:
running int32 // whether LightChain is running or stopped
procInterrupt int32 // interrupts chain insert
disableCheckFreq int32 // disables header verification
} }
// NewLightChain returns a fully initialised light chain using information // NewLightChain returns a fully initialised light chain using information
@ -165,8 +164,8 @@ func (self *LightChain) loadLastState() error {
// SetHead rewinds the local chain to a new head. Everything above the new // SetHead rewinds the local chain to a new head. Everything above the new
// head will be deleted and the new one set. // head will be deleted and the new one set.
func (bc *LightChain) SetHead(head uint64) { func (bc *LightChain) SetHead(head uint64) {
bc.mu.Lock() bc.chainmu.Lock()
defer bc.mu.Unlock() defer bc.chainmu.Unlock()
bc.hc.SetHead(head, nil) bc.hc.SetHead(head, nil)
bc.loadLastState() bc.loadLastState()
@ -188,8 +187,8 @@ func (bc *LightChain) ResetWithGenesisBlock(genesis *types.Block) {
// Dump the entire block chain and purge the caches // Dump the entire block chain and purge the caches
bc.SetHead(0) bc.SetHead(0)
bc.mu.Lock() bc.chainmu.Lock()
defer bc.mu.Unlock() defer bc.chainmu.Unlock()
// Prepare the genesis block and reinitialise the chain // Prepare the genesis block and reinitialise the chain
rawdb.WriteTd(bc.chainDb, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) rawdb.WriteTd(bc.chainDb, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty())
@ -315,8 +314,8 @@ func (bc *LightChain) Stop() {
// Rollback is designed to remove a chain of links from the database that aren't // Rollback is designed to remove a chain of links from the database that aren't
// certain enough to be valid. // certain enough to be valid.
func (self *LightChain) Rollback(chain []common.Hash) { func (self *LightChain) Rollback(chain []common.Hash) {
self.mu.Lock() self.chainmu.Lock()
defer self.mu.Unlock() defer self.chainmu.Unlock()
for i := len(chain) - 1; i >= 0; i-- { for i := len(chain) - 1; i >= 0; i-- {
hash := chain[i] hash := chain[i]
@ -355,6 +354,9 @@ func (self *LightChain) postChainEvents(events []interface{}) {
// In the case of a light chain, InsertHeaderChain also creates and posts light // In the case of a light chain, InsertHeaderChain also creates and posts light
// chain events when necessary. // chain events when necessary.
func (self *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error) { func (self *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error) {
if atomic.LoadInt32(&self.disableCheckFreq) == 1 {
checkFreq = 0
}
start := time.Now() start := time.Now()
if i, err := self.hc.ValidateHeaderChain(chain, checkFreq); err != nil { if i, err := self.hc.ValidateHeaderChain(chain, checkFreq); err != nil {
return i, err return i, err
@ -362,19 +364,13 @@ func (self *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int)
// Make sure only one thread manipulates the chain at once // Make sure only one thread manipulates the chain at once
self.chainmu.Lock() self.chainmu.Lock()
defer func() { defer self.chainmu.Unlock()
self.chainmu.Unlock()
time.Sleep(time.Millisecond * 10) // ugly hack; do not hog chain lock in case syncing is CPU-limited by validation
}()
self.wg.Add(1) self.wg.Add(1)
defer self.wg.Done() defer self.wg.Done()
var events []interface{} var events []interface{}
whFunc := func(header *types.Header) error { whFunc := func(header *types.Header) error {
self.mu.Lock()
defer self.mu.Unlock()
status, err := self.hc.WriteHeader(header) status, err := self.hc.WriteHeader(header)
switch status { switch status {
@ -441,8 +437,8 @@ func (self *LightChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []c
// //
// Note: ancestor == 0 returns the same block, 1 returns its parent and so on. // Note: ancestor == 0 returns the same block, 1 returns its parent and so on.
func (bc *LightChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) { func (bc *LightChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) {
bc.chainmu.Lock() bc.chainmu.RLock()
defer bc.chainmu.Unlock() defer bc.chainmu.RUnlock()
return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical) return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
} }
@ -483,8 +479,8 @@ func (self *LightChain) SyncCht(ctx context.Context) bool {
} }
// Retrieve the latest useful header and update to it // Retrieve the latest useful header and update to it
if header, err := GetHeaderByNumber(ctx, self.odr, latest); header != nil && err == nil { if header, err := GetHeaderByNumber(ctx, self.odr, latest); header != nil && err == nil {
self.mu.Lock() self.chainmu.Lock()
defer self.mu.Unlock() defer self.chainmu.Unlock()
// Ensure the chain didn't move past the latest block while retrieving it // Ensure the chain didn't move past the latest block while retrieving it
if self.hc.CurrentHeader().Number.Uint64() < header.Number.Uint64() { if self.hc.CurrentHeader().Number.Uint64() < header.Number.Uint64() {
@ -533,3 +529,13 @@ func (self *LightChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
func (self *LightChain) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { func (self *LightChain) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
return self.scope.Track(new(event.Feed).Subscribe(ch)) return self.scope.Track(new(event.Feed).Subscribe(ch))
} }
// DisableCheckFreq disables header validation. This is used for ultralight mode.
func (self *LightChain) DisableCheckFreq() {
atomic.StoreInt32(&self.disableCheckFreq, 1)
}
// EnableCheckFreq enables header validation.
func (self *LightChain) EnableCheckFreq() {
atomic.StoreInt32(&self.disableCheckFreq, 0)
}

View file

@ -122,10 +122,10 @@ func testHeaderChainImport(chain []*types.Header, lightchain *LightChain) error
return err return err
} }
// Manually insert the header into the database, but don't reorganize (allows subsequent testing) // Manually insert the header into the database, but don't reorganize (allows subsequent testing)
lightchain.mu.Lock() lightchain.chainmu.Lock()
rawdb.WriteTd(lightchain.chainDb, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, lightchain.GetTdByHash(header.ParentHash))) rawdb.WriteTd(lightchain.chainDb, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, lightchain.GetTdByHash(header.ParentHash)))
rawdb.WriteHeader(lightchain.chainDb, header) rawdb.WriteHeader(lightchain.chainDb, header)
lightchain.mu.Unlock() lightchain.chainmu.Unlock()
} }
return nil return nil
} }

View file

@ -312,8 +312,9 @@ func (r *PrefixedRegistry) UnregisterAll() {
} }
var ( var (
DefaultRegistry = NewRegistry() DefaultRegistry = NewRegistry()
EphemeralRegistry = NewRegistry() EphemeralRegistry = NewRegistry()
AccountingRegistry = NewRegistry() // registry used in swarm
) )
// Call the given function for each registered metric. // Call the given function for each registered metric.

View file

@ -224,7 +224,7 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens
for i := 0; i < 2; i += 1 { for i := 0; i < 2; i += 1 {
select { select {
case <-taskCh: case <-taskCh:
case <-time.NewTimer(time.Second).C: case <-time.NewTimer(2 * time.Second).C:
t.Error("new task timeout") t.Error("new task timeout")
} }
} }

View file

@ -76,6 +76,9 @@ type NodeConfig struct {
// Listening address of pprof server. // Listening address of pprof server.
PprofAddress string PprofAddress string
// Ultra Light client options
ULC *eth.ULCConfig
} }
// defaultNodeConfig contains the default node configuration values to use if all // defaultNodeConfig contains the default node configuration values to use if all
@ -131,6 +134,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
MaxPeers: config.MaxPeers, MaxPeers: config.MaxPeers,
}, },
} }
rawStack, err := node.New(nodeConf) rawStack, err := node.New(nodeConf)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -102,6 +102,29 @@ type Config struct {
// for ephemeral nodes). // for ephemeral nodes).
HTTPPort int `toml:",omitempty"` HTTPPort int `toml:",omitempty"`
// GraphQLHost is the host interface on which to start the GraphQL server. If this
// field is empty, no GraphQL API endpoint will be started.
GraphQLHost string `toml:",omitempty"`
// GraphQLPort is the TCP port number on which to start the GraphQL server. The
// default zero value is/ valid and will pick a port number randomly (useful
// for ephemeral nodes).
GraphQLPort int `toml:",omitempty"`
// GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting
// clients. Please be aware that CORS is a browser enforced security, it's fully
// useless for custom HTTP clients.
GraphQLCors []string `toml:",omitempty"`
// GraphQLVirtualHosts is the list of virtual hostnames which are allowed on incoming requests.
// This is by default {'localhost'}. Using this prevents attacks like
// DNS rebinding, which bypasses SOP by simply masquerading as being within the same
// origin. These attacks do not utilize CORS, since they are not cross-domain.
// By explicitly checking the Host-header, the server will not allow requests
// made against the server with a malicious host domain.
// Requests using ip address directly are not affected
GraphQLVirtualHosts []string `toml:",omitempty"`
// HTTPCors is the Cross-Origin Resource Sharing header to send to requesting // HTTPCors is the Cross-Origin Resource Sharing header to send to requesting
// clients. Please be aware that CORS is a browser enforced security, it's fully // clients. Please be aware that CORS is a browser enforced security, it's fully
// useless for custom HTTP clients. // useless for custom HTTP clients.
@ -213,6 +236,15 @@ func (c *Config) HTTPEndpoint() string {
return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort)
} }
// GraphQLEndpoint resolves a GraphQL endpoint based on the configured host interface
// and port parameters.
func (c *Config) GraphQLEndpoint() string {
if c.GraphQLHost == "" {
return ""
}
return fmt.Sprintf("%s:%d", c.GraphQLHost, c.GraphQLPort)
}
// DefaultHTTPEndpoint returns the HTTP endpoint used by default. // DefaultHTTPEndpoint returns the HTTP endpoint used by default.
func DefaultHTTPEndpoint() string { func DefaultHTTPEndpoint() string {
config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort} config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort}

View file

@ -28,10 +28,12 @@ import (
) )
const ( const (
DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server
DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server
DefaultWSHost = "localhost" // Default host interface for the websocket RPC server DefaultWSHost = "localhost" // Default host interface for the websocket RPC server
DefaultWSPort = 8546 // Default TCP port for the websocket RPC server DefaultWSPort = 8546 // Default TCP port for the websocket RPC server
DefaultGraphQLHost = "localhost" // Default host interface for the GraphQL server
DefaultGraphQLPort = 8547 // Default TCP port for the GraphQL server
) )
// DefaultConfig contains reasonable default settings. // DefaultConfig contains reasonable default settings.

View file

@ -27,23 +27,21 @@ var (
// All metrics are cumulative // All metrics are cumulative
// total amount of units credited // total amount of units credited
mBalanceCredit metrics.Counter mBalanceCredit = metrics.NewRegisteredCounterForced("account.balance.credit", metrics.AccountingRegistry)
// total amount of units debited // total amount of units debited
mBalanceDebit metrics.Counter mBalanceDebit = metrics.NewRegisteredCounterForced("account.balance.debit", metrics.AccountingRegistry)
// total amount of bytes credited // total amount of bytes credited
mBytesCredit metrics.Counter mBytesCredit = metrics.NewRegisteredCounterForced("account.bytes.credit", metrics.AccountingRegistry)
// total amount of bytes debited // total amount of bytes debited
mBytesDebit metrics.Counter mBytesDebit = metrics.NewRegisteredCounterForced("account.bytes.debit", metrics.AccountingRegistry)
// total amount of credited messages // total amount of credited messages
mMsgCredit metrics.Counter mMsgCredit = metrics.NewRegisteredCounterForced("account.msg.credit", metrics.AccountingRegistry)
// total amount of debited messages // total amount of debited messages
mMsgDebit metrics.Counter mMsgDebit = metrics.NewRegisteredCounterForced("account.msg.debit", metrics.AccountingRegistry)
// how many times local node had to drop remote peers // how many times local node had to drop remote peers
mPeerDrops metrics.Counter mPeerDrops = metrics.NewRegisteredCounterForced("account.peerdrops", metrics.AccountingRegistry)
// how many times local node overdrafted and dropped // how many times local node overdrafted and dropped
mSelfDrops metrics.Counter mSelfDrops = metrics.NewRegisteredCounterForced("account.selfdrops", metrics.AccountingRegistry)
MetricsRegistry metrics.Registry
) )
// Prices defines how prices are being passed on to the accounting instance // Prices defines how prices are being passed on to the accounting instance
@ -110,24 +108,13 @@ func NewAccounting(balance Balance, po Prices) *Accounting {
return ah return ah
} }
// SetupAccountingMetrics creates a separate registry for p2p accounting metrics; // SetupAccountingMetrics uses a separate registry for p2p accounting metrics;
// this registry should be independent of any other metrics as it persists at different endpoints. // this registry should be independent of any other metrics as it persists at different endpoints.
// It also instantiates the given metrics and starts the persisting go-routine which // It also starts the persisting go-routine which
// at the passed interval writes the metrics to a LevelDB // at the passed interval writes the metrics to a LevelDB
func SetupAccountingMetrics(reportInterval time.Duration, path string) *AccountingMetrics { func SetupAccountingMetrics(reportInterval time.Duration, path string) *AccountingMetrics {
// create an empty registry
MetricsRegistry = metrics.NewRegistry()
// instantiate the metrics
mBalanceCredit = metrics.NewRegisteredCounterForced("account.balance.credit", MetricsRegistry)
mBalanceDebit = metrics.NewRegisteredCounterForced("account.balance.debit", MetricsRegistry)
mBytesCredit = metrics.NewRegisteredCounterForced("account.bytes.credit", MetricsRegistry)
mBytesDebit = metrics.NewRegisteredCounterForced("account.bytes.debit", MetricsRegistry)
mMsgCredit = metrics.NewRegisteredCounterForced("account.msg.credit", MetricsRegistry)
mMsgDebit = metrics.NewRegisteredCounterForced("account.msg.debit", MetricsRegistry)
mPeerDrops = metrics.NewRegisteredCounterForced("account.peerdrops", MetricsRegistry)
mSelfDrops = metrics.NewRegisteredCounterForced("account.selfdrops", MetricsRegistry)
// create the DB and start persisting // create the DB and start persisting
return NewAccountingMetrics(MetricsRegistry, reportInterval, path) return NewAccountingMetrics(metrics.AccountingRegistry, reportInterval, path)
} }
// Send takes a peer, a size and a msg and // Send takes a peer, a size and a msg and

View file

@ -423,3 +423,17 @@ func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interf
} }
return rhs, nil return rhs, nil
} }
// HasCap returns true if Peer has a capability
// with provided name.
func (p *Peer) HasCap(capName string) (yes bool) {
if p == nil || p.Peer == nil {
return false
}
for _, c := range p.Caps() {
if c.Name == capName {
return true
}
}
return false
}

View file

@ -142,9 +142,9 @@ func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) er
} }
} }
func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester { func protocolTester(pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester {
conf := adapters.RandomNodeConfig() conf := adapters.RandomNodeConfig()
return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp)) return p2ptest.NewProtocolTester(conf.ID, 2, newProtocol(pp))
} }
func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchange { func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchange {
@ -173,7 +173,7 @@ func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchan
func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) { func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) {
pp := p2ptest.NewTestPeerPool() pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp) s := protocolTester(pp)
// TODO: make this more than one handshake // TODO: make this more than one handshake
node := s.Nodes[0] node := s.Nodes[0]
if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil { if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil {
@ -250,7 +250,7 @@ func TestProtocolHook(t *testing.T) {
} }
conf := adapters.RandomNodeConfig() conf := adapters.RandomNodeConfig()
tester := p2ptest.NewProtocolTester(t, conf.ID, 2, runFunc) tester := p2ptest.NewProtocolTester(conf.ID, 2, runFunc)
err := tester.TestExchanges(p2ptest.Exchange{ err := tester.TestExchanges(p2ptest.Exchange{
Expects: []p2ptest.Expect{ Expects: []p2ptest.Expect{
{ {
@ -269,8 +269,11 @@ func TestProtocolHook(t *testing.T) {
if !testHook.send { if !testHook.send {
t.Fatal("Expected a send message, but it is not") t.Fatal("Expected a send message, but it is not")
} }
if testHook.peer == nil || testHook.peer.ID() != tester.Nodes[0].ID() { if testHook.peer == nil {
t.Fatal("Expected peer ID to be set correctly, but it is not") t.Fatal("Expected peer to be set, is nil")
}
if peerId := testHook.peer.ID(); peerId != tester.Nodes[0].ID() && peerId != tester.Nodes[1].ID() {
t.Fatalf("Expected peer ID to be set correctly, but it is not (got %v, exp %v or %v", peerId, tester.Nodes[0].ID(), tester.Nodes[1].ID())
} }
if testHook.size != 11 { //11 is the length of the encoded message if testHook.size != 11 { //11 is the length of the encoded message
t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size)
@ -389,7 +392,7 @@ func moduleHandshakeExchange(id enode.ID, resp uint) []p2ptest.Exchange {
func runModuleHandshake(t *testing.T, resp uint, errs ...error) { func runModuleHandshake(t *testing.T, resp uint, errs ...error) {
pp := p2ptest.NewTestPeerPool() pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp) s := protocolTester(pp)
node := s.Nodes[0] node := s.Nodes[0]
if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil { if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil {
t.Fatal(err) t.Fatal(err)
@ -469,7 +472,7 @@ func testMultiPeerSetup(a, b enode.ID) []p2ptest.Exchange {
func runMultiplePeers(t *testing.T, peer int, errs ...error) { func runMultiplePeers(t *testing.T, peer int, errs ...error) {
pp := p2ptest.NewTestPeerPool() pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp) s := protocolTester(pp)
if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); err != nil { if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -39,25 +39,31 @@ func TestReporter(t *testing.T) {
//setup the metrics //setup the metrics
log.Debug("Setting up metrics first time") log.Debug("Setting up metrics first time")
reportInterval := 5 * time.Millisecond reportInterval := 2 * time.Millisecond
metrics := SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db")) metrics := SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db"))
log.Debug("Done.") log.Debug("Done.")
//do some metrics //change metrics
mBalanceCredit.Inc(12) mBalanceCredit.Inc(12)
mBytesCredit.Inc(34) mBytesCredit.Inc(34)
mMsgDebit.Inc(9) mMsgDebit.Inc(9)
//store expected metrics
expectedBalanceCredit := mBalanceCredit.Count()
expectedBytesCredit := mBytesCredit.Count()
expectedMsgDebit := mMsgDebit.Count()
//give the reporter time to write the metrics to DB //give the reporter time to write the metrics to DB
time.Sleep(20 * time.Millisecond) time.Sleep(20 * time.Millisecond)
//set the metrics to nil - this effectively simulates the node having shut down...
mBalanceCredit = nil
mBytesCredit = nil
mMsgDebit = nil
//close the DB also, or we can't create a new one //close the DB also, or we can't create a new one
metrics.Close() metrics.Close()
//clear the metrics - this effectively simulates the node having shut down...
mBalanceCredit.Clear()
mBytesCredit.Clear()
mMsgDebit.Clear()
//setup the metrics again //setup the metrics again
log.Debug("Setting up metrics second time") log.Debug("Setting up metrics second time")
metrics = SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db")) metrics = SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db"))
@ -65,13 +71,13 @@ func TestReporter(t *testing.T) {
log.Debug("Done.") log.Debug("Done.")
//now check the metrics, they should have the same value as before "shutdown" //now check the metrics, they should have the same value as before "shutdown"
if mBalanceCredit.Count() != 12 { if mBalanceCredit.Count() != expectedBalanceCredit {
t.Fatalf("Expected counter to be %d, but is %d", 12, mBalanceCredit.Count()) t.Fatalf("Expected counter to be %d, but is %d", expectedBalanceCredit, mBalanceCredit.Count())
} }
if mBytesCredit.Count() != 34 { if mBytesCredit.Count() != expectedBytesCredit {
t.Fatalf("Expected counter to be %d, but is %d", 23, mBytesCredit.Count()) t.Fatalf("Expected counter to be %d, but is %d", expectedBytesCredit, mBytesCredit.Count())
} }
if mMsgDebit.Count() != 9 { if mMsgDebit.Count() != expectedMsgDebit {
t.Fatalf("Expected counter to be %d, but is %d", 9, mMsgDebit.Count()) t.Fatalf("Expected counter to be %d, but is %d", expectedMsgDebit, mMsgDebit.Count())
} }
} }

View file

@ -168,24 +168,28 @@ func (net *Network) Start(id enode.ID) error {
// snapshots // snapshots
func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) error { func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) error {
net.lock.Lock() net.lock.Lock()
defer net.lock.Unlock()
node := net.getNode(id) node := net.getNode(id)
if node == nil { if node == nil {
net.lock.Unlock()
return fmt.Errorf("node %v does not exist", id) return fmt.Errorf("node %v does not exist", id)
} }
if node.Up { if node.Up {
net.lock.Unlock()
return fmt.Errorf("node %v already up", id) return fmt.Errorf("node %v already up", id)
} }
log.Trace("Starting node", "id", id, "adapter", net.nodeAdapter.Name()) log.Trace("Starting node", "id", id, "adapter", net.nodeAdapter.Name())
if err := node.Start(snapshots); err != nil { if err := node.Start(snapshots); err != nil {
net.lock.Unlock()
log.Warn("Node startup failed", "id", id, "err", err) log.Warn("Node startup failed", "id", id, "err", err)
return err return err
} }
node.Up = true node.Up = true
log.Info("Started node", "id", id) log.Info("Started node", "id", id)
ev := NewEvent(node)
net.lock.Unlock()
net.events.Send(NewEvent(node)) net.events.Send(ev)
// subscribe to peer events // subscribe to peer events
client, err := node.Client() client, err := node.Client()
@ -210,12 +214,14 @@ func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub
// assume the node is now down // assume the node is now down
net.lock.Lock() net.lock.Lock()
defer net.lock.Unlock() defer net.lock.Unlock()
node := net.getNode(id) node := net.getNode(id)
if node == nil { if node == nil {
return return
} }
node.Up = false node.Up = false
net.events.Send(NewEvent(node)) ev := NewEvent(node)
net.events.Send(ev)
}() }()
for { for {
select { select {
@ -254,9 +260,11 @@ func (net *Network) Stop(id enode.ID) error {
net.lock.Lock() net.lock.Lock()
node := net.getNode(id) node := net.getNode(id)
if node == nil { if node == nil {
net.lock.Unlock()
return fmt.Errorf("node %v does not exist", id) return fmt.Errorf("node %v does not exist", id)
} }
if !node.Up { if !node.Up {
net.lock.Unlock()
return fmt.Errorf("node %v already down", id) return fmt.Errorf("node %v already down", id)
} }
node.Up = false node.Up = false
@ -270,7 +278,10 @@ func (net *Network) Stop(id enode.ID) error {
return err return err
} }
log.Info("Stopped node", "id", id, "err", err) log.Info("Stopped node", "id", id, "err", err)
net.events.Send(ControlEvent(node)) net.lock.Lock()
ev := ControlEvent(node)
net.lock.Unlock()
net.events.Send(ev)
return nil return nil
} }

View file

@ -30,7 +30,6 @@ import (
"io/ioutil" "io/ioutil"
"strings" "strings"
"sync" "sync"
"testing"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
@ -52,7 +51,7 @@ type ProtocolTester struct {
// NewProtocolTester constructs a new ProtocolTester // NewProtocolTester constructs a new ProtocolTester
// it takes as argument the pivot node id, the number of dummy peers and the // 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 // protocol run function called on a peer connection by the p2p server
func NewProtocolTester(t *testing.T, id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { func NewProtocolTester(id enode.ID, 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) {
return &testNode{run}, nil return &testNode{run}, nil

View file

@ -42,7 +42,8 @@ var (
EIP155Block: big.NewInt(2675000), EIP155Block: big.NewInt(2675000),
EIP158Block: big.NewInt(2675000), EIP158Block: big.NewInt(2675000),
ByzantiumBlock: big.NewInt(4370000), ByzantiumBlock: big.NewInt(4370000),
ConstantinopleBlock: nil, ConstantinopleBlock: big.NewInt(7280000),
PetersburgBlock: big.NewInt(7280000),
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
} }
@ -67,6 +68,7 @@ var (
EIP158Block: big.NewInt(10), EIP158Block: big.NewInt(10),
ByzantiumBlock: big.NewInt(1700000), ByzantiumBlock: big.NewInt(1700000),
ConstantinopleBlock: big.NewInt(4230000), ConstantinopleBlock: big.NewInt(4230000),
PetersburgBlock: big.NewInt(4939394),
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
} }
@ -91,6 +93,7 @@ var (
EIP158Block: big.NewInt(3), EIP158Block: big.NewInt(3),
ByzantiumBlock: big.NewInt(1035301), ByzantiumBlock: big.NewInt(1035301),
ConstantinopleBlock: big.NewInt(3660663), ConstantinopleBlock: big.NewInt(3660663),
PetersburgBlock: big.NewInt(9999999), //TODO! Insert Rinkeby block number
Clique: &CliqueConfig{ Clique: &CliqueConfig{
Period: 15, Period: 15,
Epoch: 30000, Epoch: 30000,
@ -111,16 +114,16 @@ var (
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // adding flags to the config to also have to set these fields.
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil} AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus. // and accepted by the Ethereum core developers into the Clique consensus.
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // adding flags to the config to also have to set these fields.
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil} TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
TestRules = TestChainConfig.Rules(new(big.Int)) TestRules = TestChainConfig.Rules(new(big.Int))
) )
@ -158,6 +161,7 @@ type ChainConfig struct {
ByzantiumBlock *big.Int `json:"byzantiumBlock,omitempty"` // Byzantium switch block (nil = no fork, 0 = already on byzantium) ByzantiumBlock *big.Int `json:"byzantiumBlock,omitempty"` // Byzantium switch block (nil = no fork, 0 = already on byzantium)
ConstantinopleBlock *big.Int `json:"constantinopleBlock,omitempty"` // Constantinople switch block (nil = no fork, 0 = already activated) ConstantinopleBlock *big.Int `json:"constantinopleBlock,omitempty"` // Constantinople switch block (nil = no fork, 0 = already activated)
PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg switch block (nil = same as Constantinople)
EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated)
// Various consensus engines // Various consensus engines
@ -195,7 +199,7 @@ func (c *ChainConfig) String() string {
default: default:
engine = "unknown" engine = "unknown"
} }
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Engine: %v}", return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v ConstantinopleFix: %v Engine: %v}",
c.ChainID, c.ChainID,
c.HomesteadBlock, c.HomesteadBlock,
c.DAOForkBlock, c.DAOForkBlock,
@ -205,6 +209,7 @@ func (c *ChainConfig) String() string {
c.EIP158Block, c.EIP158Block,
c.ByzantiumBlock, c.ByzantiumBlock,
c.ConstantinopleBlock, c.ConstantinopleBlock,
c.PetersburgBlock,
engine, engine,
) )
} }
@ -244,6 +249,13 @@ func (c *ChainConfig) IsConstantinople(num *big.Int) bool {
return isForked(c.ConstantinopleBlock, num) return isForked(c.ConstantinopleBlock, num)
} }
// IsPetersburg returns whether num is either
// - equal to or greater than the PetersburgBlock fork block,
// - OR is nil, and Constantinople is active
func (c *ChainConfig) IsPetersburg(num *big.Int) bool {
return isForked(c.PetersburgBlock, num) || c.PetersburgBlock == nil && isForked(c.ConstantinopleBlock, num)
}
// IsEWASM returns whether num represents a block number after the EWASM fork // IsEWASM returns whether num represents a block number after the EWASM fork
func (c *ChainConfig) IsEWASM(num *big.Int) bool { func (c *ChainConfig) IsEWASM(num *big.Int) bool {
return isForked(c.EWASMBlock, num) return isForked(c.EWASMBlock, num)
@ -314,6 +326,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi
if isForkIncompatible(c.ConstantinopleBlock, newcfg.ConstantinopleBlock, head) { if isForkIncompatible(c.ConstantinopleBlock, newcfg.ConstantinopleBlock, head) {
return newCompatError("Constantinople fork block", c.ConstantinopleBlock, newcfg.ConstantinopleBlock) return newCompatError("Constantinople fork block", c.ConstantinopleBlock, newcfg.ConstantinopleBlock)
} }
if isForkIncompatible(c.PetersburgBlock, newcfg.PetersburgBlock, head) {
return newCompatError("ConstantinopleFix fork block", c.PetersburgBlock, newcfg.PetersburgBlock)
}
if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) {
return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock)
} }
@ -381,9 +396,9 @@ func (err *ConfigCompatError) Error() string {
// Rules is a one time interface meaning that it shouldn't be used in between transition // Rules is a one time interface meaning that it shouldn't be used in between transition
// phases. // phases.
type Rules struct { type Rules struct {
ChainID *big.Int ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsByzantium, IsConstantinople bool IsByzantium, IsConstantinople, IsPetersburg bool
} }
// Rules ensures c's ChainID is not nil. // Rules ensures c's ChainID is not nil.
@ -400,5 +415,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
IsEIP158: c.IsEIP158(num), IsEIP158: c.IsEIP158(num),
IsByzantium: c.IsByzantium(num), IsByzantium: c.IsByzantium(num),
IsConstantinople: c.IsConstantinople(num), IsConstantinople: c.IsConstantinople(num),
IsPetersburg: c.IsPetersburg(num),
} }
} }

View file

@ -52,7 +52,7 @@ const (
NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value
NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value
JumpdestGas uint64 = 1 // Refunded gas, once per SSTORE operation if the zeroness changes to zero. JumpdestGas uint64 = 1 // Once per JUMPDEST operation.
EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs.
CallGas uint64 = 40 // Once per CALL operation & message call transaction. CallGas uint64 = 40 // Once per CALL operation & message call transaction.
CreateDataGas uint64 = 200 // CreateDataGas uint64 = 200 //

View file

@ -198,7 +198,7 @@ func (t *httpReadWriteNopCloser) Close() error {
// NewHTTPServer creates a new HTTP RPC server around an API provider. // NewHTTPServer creates a new HTTP RPC server around an API provider.
// //
// Deprecated: Server implements http.Handler // Deprecated: Server implements http.Handler
func NewHTTPServer(cors []string, vhosts []string, timeouts HTTPTimeouts, srv *Server) *http.Server { func NewHTTPServer(cors []string, vhosts []string, timeouts HTTPTimeouts, srv http.Handler) *http.Server {
// Wrap the CORS-handler within a host-handler // Wrap the CORS-handler within a host-handler
handler := newCorsHandler(srv, cors) handler := newCorsHandler(srv, cors)
handler = newVHostHandler(vhosts, handler) handler = newVHostHandler(vhosts, handler)
@ -284,7 +284,7 @@ func validateRequest(r *http.Request) (int, error) {
return http.StatusUnsupportedMediaType, err return http.StatusUnsupportedMediaType, err
} }
func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler { func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler {
// disable CORS support if user has not specified a custom CORS configuration // disable CORS support if user has not specified a custom CORS configuration
if len(allowedOrigins) == 0 { if len(allowedOrigins) == 0 {
return srv return srv

View file

@ -66,6 +66,7 @@ type Config struct {
DeliverySkipCheck bool DeliverySkipCheck bool
MaxStreamPeerServers int MaxStreamPeerServers int
LightNodeEnabled bool LightNodeEnabled bool
BootnodeMode bool
SyncUpdateDelay time.Duration SyncUpdateDelay time.Duration
SwapAPI string SwapAPI string
Cors string Cors string

View file

@ -23,7 +23,7 @@ import (
gethmetrics "github.com/ethereum/go-ethereum/metrics" gethmetrics "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/metrics/influxdb" "github.com/ethereum/go-ethereum/metrics/influxdb"
"github.com/ethereum/go-ethereum/swarm/log" "github.com/ethereum/go-ethereum/swarm/log"
"gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
) )
var ( var (
@ -31,6 +31,10 @@ var (
Name: "metrics.influxdb.export", Name: "metrics.influxdb.export",
Usage: "Enable metrics export/push to an external InfluxDB database", Usage: "Enable metrics export/push to an external InfluxDB database",
} }
MetricsEnableInfluxDBAccountingExportFlag = cli.BoolFlag{
Name: "metrics.influxdb.accounting",
Usage: "Enable accounting metrics export/push to an external InfluxDB database",
}
MetricsInfluxDBEndpointFlag = cli.StringFlag{ MetricsInfluxDBEndpointFlag = cli.StringFlag{
Name: "metrics.influxdb.endpoint", Name: "metrics.influxdb.endpoint",
Usage: "Metrics InfluxDB endpoint", Usage: "Metrics InfluxDB endpoint",
@ -51,14 +55,14 @@ var (
Usage: "Metrics InfluxDB password", Usage: "Metrics InfluxDB password",
Value: "", Value: "",
} }
// The `host` tag is part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB. // Tags are part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB.
// It is used so that we can group all nodes and average a measurement across all of them, but also so // For example `host` tag could be used so that we can group all nodes and average a measurement
// that we can select a specific node and inspect its measurements. // across all of them, but also so that we can select a specific node and inspect its measurements.
// https://docs.influxdata.com/influxdb/v1.4/concepts/key_concepts/#tag-key // https://docs.influxdata.com/influxdb/v1.4/concepts/key_concepts/#tag-key
MetricsInfluxDBHostTagFlag = cli.StringFlag{ MetricsInfluxDBTagsFlag = cli.StringFlag{
Name: "metrics.influxdb.host.tag", Name: "metrics.influxdb.tags",
Usage: "Metrics InfluxDB `host` tag attached to all measurements", Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements",
Value: "localhost", Value: "host=localhost",
} }
) )
@ -66,33 +70,39 @@ var (
var Flags = []cli.Flag{ var Flags = []cli.Flag{
utils.MetricsEnabledFlag, utils.MetricsEnabledFlag,
MetricsEnableInfluxDBExportFlag, MetricsEnableInfluxDBExportFlag,
MetricsEnableInfluxDBAccountingExportFlag,
MetricsInfluxDBEndpointFlag, MetricsInfluxDBEndpointFlag,
MetricsInfluxDBDatabaseFlag, MetricsInfluxDBDatabaseFlag,
MetricsInfluxDBUsernameFlag, MetricsInfluxDBUsernameFlag,
MetricsInfluxDBPasswordFlag, MetricsInfluxDBPasswordFlag,
MetricsInfluxDBHostTagFlag, MetricsInfluxDBTagsFlag,
} }
func Setup(ctx *cli.Context) { func Setup(ctx *cli.Context) {
if gethmetrics.Enabled { if gethmetrics.Enabled {
log.Info("Enabling swarm metrics collection") log.Info("Enabling swarm metrics collection")
var ( var (
enableExport = ctx.GlobalBool(MetricsEnableInfluxDBExportFlag.Name) endpoint = ctx.GlobalString(MetricsInfluxDBEndpointFlag.Name)
endpoint = ctx.GlobalString(MetricsInfluxDBEndpointFlag.Name) database = ctx.GlobalString(MetricsInfluxDBDatabaseFlag.Name)
database = ctx.GlobalString(MetricsInfluxDBDatabaseFlag.Name) username = ctx.GlobalString(MetricsInfluxDBUsernameFlag.Name)
username = ctx.GlobalString(MetricsInfluxDBUsernameFlag.Name) password = ctx.GlobalString(MetricsInfluxDBPasswordFlag.Name)
password = ctx.GlobalString(MetricsInfluxDBPasswordFlag.Name) enableExport = ctx.GlobalBool(MetricsEnableInfluxDBExportFlag.Name)
hosttag = ctx.GlobalString(MetricsInfluxDBHostTagFlag.Name) enableAccountingExport = ctx.GlobalBool(MetricsEnableInfluxDBAccountingExportFlag.Name)
) )
// Start system runtime metrics collection // Start system runtime metrics collection
go gethmetrics.CollectProcessMetrics(2 * time.Second) go gethmetrics.CollectProcessMetrics(2 * time.Second)
tagsMap := utils.SplitTagsFlag(ctx.GlobalString(MetricsInfluxDBTagsFlag.Name))
if enableExport { if enableExport {
log.Info("Enabling swarm metrics export to InfluxDB") log.Info("Enabling swarm metrics export to InfluxDB")
go influxdb.InfluxDBWithTags(gethmetrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "swarm.", map[string]string{ go influxdb.InfluxDBWithTags(gethmetrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "swarm.", tagsMap)
"host": hosttag, }
})
if enableAccountingExport {
log.Info("Exporting swarm accounting metrics to InfluxDB")
go influxdb.InfluxDBWithTags(gethmetrics.AccountingRegistry, 10*time.Second, endpoint, database, username, password, "accounting.", tagsMap)
} }
} }
} }

View file

@ -26,20 +26,23 @@ import (
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
var searchTimeout = 1 * time.Second const (
defaultSearchTimeout = 1 * time.Second
// maximum number of forwarded requests (hops), to make sure requests are not
// forwarded forever in peer loops
maxHopCount uint8 = 20
)
// Time to consider peer to be skipped. // Time to consider peer to be skipped.
// Also used in stream delivery. // Also used in stream delivery.
var RequestTimeout = 10 * time.Second var RequestTimeout = 10 * time.Second
var maxHopCount uint8 = 20 // maximum number of forwarded requests (hops), to make sure requests are not forwarded forever in peer loops
type RequestFunc func(context.Context, *Request) (*enode.ID, chan struct{}, error) type RequestFunc func(context.Context, *Request) (*enode.ID, chan struct{}, error)
// Fetcher is created when a chunk is not found locally. It starts a request handler loop once and // Fetcher is created when a chunk is not found locally. It starts a request handler loop once and
// keeps it alive until all active requests are completed. This can happen: // keeps it alive until all active requests are completed. This can happen:
// 1. either because the chunk is delivered // 1. either because the chunk is delivered
// 2. or becuse the requestor cancelled/timed out // 2. or because the requester cancelled/timed out
// Fetcher self destroys itself after it is completed. // Fetcher self destroys itself after it is completed.
// TODO: cancel all forward requests after termination // TODO: cancel all forward requests after termination
type Fetcher struct { type Fetcher struct {
@ -47,6 +50,7 @@ type Fetcher struct {
addr storage.Address // the address of the chunk to be fetched addr storage.Address // the address of the chunk to be fetched
offerC chan *enode.ID // channel of sources (peer node id strings) offerC chan *enode.ID // channel of sources (peer node id strings)
requestC chan uint8 // channel for incoming requests (with the hopCount value in it) requestC chan uint8 // channel for incoming requests (with the hopCount value in it)
searchTimeout time.Duration
skipCheck bool skipCheck bool
} }
@ -79,7 +83,7 @@ func (r *Request) SkipPeer(nodeID string) bool {
} }
t, ok := val.(time.Time) t, ok := val.(time.Time)
if ok && time.Now().After(t.Add(RequestTimeout)) { if ok && time.Now().After(t.Add(RequestTimeout)) {
// deadine expired // deadline expired
r.peersToSkip.Delete(nodeID) r.peersToSkip.Delete(nodeID)
return false return false
} }
@ -100,9 +104,10 @@ func NewFetcherFactory(request RequestFunc, skipCheck bool) *FetcherFactory {
} }
} }
// New contructs a new Fetcher, for the given chunk. All peers in peersToSkip are not requested to // New constructs a new Fetcher, for the given chunk. All peers in peersToSkip
// deliver the given chunk. peersToSkip should always contain the peers which are actively requesting // are not requested to deliver the given chunk. peersToSkip should always
// this chunk, to make sure we don't request back the chunks from them. // contain the peers which are actively requesting this chunk, to make sure we
// don't request back the chunks from them.
// The created Fetcher is started and returned. // The created Fetcher is started and returned.
func (f *FetcherFactory) New(ctx context.Context, source storage.Address, peersToSkip *sync.Map) storage.NetFetcher { func (f *FetcherFactory) New(ctx context.Context, source storage.Address, peersToSkip *sync.Map) storage.NetFetcher {
fetcher := NewFetcher(source, f.request, f.skipCheck) fetcher := NewFetcher(source, f.request, f.skipCheck)
@ -117,6 +122,7 @@ func NewFetcher(addr storage.Address, rf RequestFunc, skipCheck bool) *Fetcher {
protoRequestFunc: rf, protoRequestFunc: rf,
offerC: make(chan *enode.ID), offerC: make(chan *enode.ID),
requestC: make(chan uint8), requestC: make(chan uint8),
searchTimeout: defaultSearchTimeout,
skipCheck: skipCheck, skipCheck: skipCheck,
} }
} }
@ -176,7 +182,7 @@ func (f *Fetcher) run(ctx context.Context, peers *sync.Map) {
// loop that keeps the fetching process alive // loop that keeps the fetching process alive
// after every request a timer is set. If this goes off we request again from another peer // after every request a timer is set. If this goes off we request again from another peer
// note that the previous request is still alive and has the chance to deliver, so // note that the previous request is still alive and has the chance to deliver, so
// rerequesting extends the search. ie., // requesting again extends the search. ie.,
// if a peer we requested from is gone we issue a new request, so the number of active // if a peer we requested from is gone we issue a new request, so the number of active
// requests never decreases // requests never decreases
for { for {
@ -209,13 +215,13 @@ func (f *Fetcher) run(ctx context.Context, peers *sync.Map) {
// search timeout: too much time passed since the last request, // search timeout: too much time passed since the last request,
// extend the search to a new peer if we can find one // extend the search to a new peer if we can find one
case <-waitC: case <-waitC:
log.Trace("search timed out: rerequesting", "request addr", f.addr) log.Trace("search timed out: requesting", "request addr", f.addr)
doRequest = requested doRequest = requested
// all Fetcher context closed, can quit // all Fetcher context closed, can quit
case <-ctx.Done(): case <-ctx.Done():
log.Trace("terminate fetcher", "request addr", f.addr) log.Trace("terminate fetcher", "request addr", f.addr)
// TODO: send cancelations to all peers left over in peers map (i.e., those we requested from) // TODO: send cancellations to all peers left over in peers map (i.e., those we requested from)
return return
} }
@ -231,7 +237,7 @@ func (f *Fetcher) run(ctx context.Context, peers *sync.Map) {
// if wait channel is not set, set it to a timer // if wait channel is not set, set it to a timer
if requested { if requested {
if wait == nil { if wait == nil {
wait = time.NewTimer(searchTimeout) wait = time.NewTimer(f.searchTimeout)
defer wait.Stop() defer wait.Stop()
waitC = wait.C waitC = wait.C
} else { } else {
@ -242,8 +248,8 @@ func (f *Fetcher) run(ctx context.Context, peers *sync.Map) {
default: default:
} }
} }
// reset the timer to go off after searchTimeout // reset the timer to go off after defaultSearchTimeout
wait.Reset(searchTimeout) wait.Reset(f.searchTimeout)
} }
} }
doRequest = false doRequest = false

View file

@ -284,15 +284,11 @@ func TestFetcherRetryOnTimeout(t *testing.T) {
requester := newMockRequester() requester := newMockRequester()
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true) fetcher := NewFetcher(addr, requester.doRequest, true)
// set searchTimeOut to low value so the test is quicker
fetcher.searchTimeout = 250 * time.Millisecond
peersToSkip := &sync.Map{} peersToSkip := &sync.Map{}
// set searchTimeOut to low value so the test is quicker
defer func(t time.Duration) {
searchTimeout = t
}(searchTimeout)
searchTimeout = 250 * time.Millisecond
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -359,11 +355,9 @@ func TestFetcherRequestQuitRetriesRequest(t *testing.T) {
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true) fetcher := NewFetcher(addr, requester.doRequest, true)
// make sure searchTimeout is long so it is sure the request is not retried because of timeout // make sure the searchTimeout is long so it is sure the request is not
defer func(t time.Duration) { // retried because of timeout
searchTimeout = t fetcher.searchTimeout = 10 * time.Second
}(searchTimeout)
searchTimeout = 10 * time.Second
peersToSkip := &sync.Map{} peersToSkip := &sync.Map{}

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/swarm/log" "github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/pot" "github.com/ethereum/go-ethereum/swarm/pot"
sv "github.com/ethereum/go-ethereum/swarm/version"
) )
/* /*
@ -168,82 +169,115 @@ func (k *Kademlia) Register(peers ...*BzzAddr) error {
return nil return nil
} }
// SuggestPeer returns a known peer for the lowest proximity bin for the // SuggestPeer returns an unconnected peer address as a peer suggestion for connection
// lowest bincount below depth func (k *Kademlia) SuggestPeer() (suggestedPeer *BzzAddr, saturationDepth int, changed bool) {
// naturally if there is an empty row it returns a peer for that
func (k *Kademlia) SuggestPeer() (a *BzzAddr, o int, want bool) {
k.lock.Lock() k.lock.Lock()
defer k.lock.Unlock() defer k.lock.Unlock()
minsize := k.MinBinSize radius := neighbourhoodRadiusForPot(k.conns, k.NeighbourhoodSize, k.base)
depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) // collect undersaturated bins in ascending order of number of connected peers
// if there is a callable neighbour within the current proxBin, connect // and from shallow to deep (ascending order of PO)
// this makes sure nearest neighbour set is fully connected // insert them in a map of bin arrays, keyed with the number of connected peers
var ppo int saturation := make(map[int][]int)
k.addrs.EachNeighbour(k.base, Pof, func(val pot.Val, po int) bool { var lastPO int // the last non-empty PO bin in the iteration
if po < depth { saturationDepth = -1 // the deepest PO such that all shallower bins have >= k.MinBinSize peers
return false var pastDepth bool // whether po of iteration >= depth
}
e := val.(*entry)
c := k.callable(e)
if c {
a = e.BzzAddr
}
ppo = po
return !c
})
if a != nil {
log.Trace(fmt.Sprintf("%08x candidate nearest neighbour found: %v (%v)", k.BaseAddr()[:4], a, ppo))
return a, 0, false
}
var bpo []int
prev := -1
k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool { k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
prev++ // process skipped empty bins
for ; prev < po; prev++ { for ; lastPO < po; lastPO++ {
bpo = append(bpo, prev) // find the lowest unsaturated bin
minsize = 0 if saturationDepth == -1 {
saturationDepth = lastPO
}
// if there is an empty bin, depth is surely passed
pastDepth = true
saturation[0] = append(saturation[0], lastPO)
} }
if size < minsize { lastPO = po + 1
bpo = append(bpo, po) // past radius, depth is surely passed
minsize = size if po >= radius {
pastDepth = true
} }
return size > 0 && po < depth // beyond depth the bin is treated as unsaturated even if size >= k.MinBinSize
// in order to achieve full connectivity to all neighbours
if pastDepth && size >= k.MinBinSize {
size = k.MinBinSize - 1
}
// process non-empty unsaturated bins
if size < k.MinBinSize {
// find the lowest unsaturated bin
if saturationDepth == -1 {
saturationDepth = po
}
saturation[size] = append(saturation[size], po)
}
return true
}) })
// all buckets are full, ie., minsize == k.MinBinSize // to trigger peer requests for peers closer than closest connection, include
if len(bpo) == 0 { // all bins from nearest connection upto nearest address as unsaturated
var nearestAddrAt int
k.addrs.EachNeighbour(k.base, Pof, func(_ pot.Val, po int) bool {
nearestAddrAt = po
return false
})
// including bins as size 0 has the effect that requesting connection
// is prioritised over non-empty shallower bins
for ; lastPO <= nearestAddrAt; lastPO++ {
saturation[0] = append(saturation[0], lastPO)
}
// all PO bins are saturated, ie., minsize >= k.MinBinSize, no peer suggested
if len(saturation) == 0 {
return nil, 0, false return nil, 0, false
} }
// as long as we got candidate peers to connect to // find the first callable peer in the address book
// dont ask for new peers (want = false) // starting from the bins with smallest size proceeding from shallow to deep
// try to select a candidate peer // for each bin (up until neighbourhood radius) we find callable candidate peers
// find the first callable peer for size := 0; size < k.MinBinSize && suggestedPeer == nil; size++ {
nxt := bpo[0] bins, ok := saturation[size]
k.addrs.EachBin(k.base, Pof, nxt, func(po, _ int, f func(func(pot.Val) bool) bool) bool { if !ok {
// for each bin (up until depth) we find callable candidate peers // no bin with this size
if po >= depth { continue
return false
} }
return f(func(val pot.Val) bool { cur := 0
e := val.(*entry) curPO := bins[0]
c := k.callable(e) k.addrs.EachBin(k.base, Pof, curPO, func(po, _ int, f func(func(pot.Val) bool) bool) bool {
if c { curPO = bins[cur]
a = e.BzzAddr // find the next bin that has size size
if curPO == po {
cur++
} else {
// skip bins that have no addresses
for ; cur < len(bins) && curPO < po; cur++ {
curPO = bins[cur]
}
if po < curPO {
cur--
return true
}
// stop if there are no addresses
if curPO < po {
return false
}
} }
return !c // curPO found
// find a callable peer out of the addresses in the unsaturated bin
// stop if found
f(func(val pot.Val) bool {
e := val.(*entry)
if k.callable(e) {
suggestedPeer = e.BzzAddr
return false
}
return true
})
return cur < len(bins) && suggestedPeer == nil
}) })
})
// found a candidate
if a != nil {
return a, 0, false
} }
// no candidate peer found, request for the short bin
var changed bool if uint8(saturationDepth) < k.depth {
if uint8(nxt) < k.depth { k.depth = uint8(saturationDepth)
k.depth = uint8(nxt) return suggestedPeer, saturationDepth, true
changed = true
} }
return a, nxt, changed return suggestedPeer, 0, false
} }
// On inserts the peer as a kademlia peer into the live peers // On inserts the peer as a kademlia peer into the live peers
@ -398,29 +432,25 @@ func (k *Kademlia) eachAddr(base []byte, o int, f func(*BzzAddr, int) bool) {
}) })
} }
// NeighbourhoodDepth returns the depth for the pot, see depthForPot
func (k *Kademlia) NeighbourhoodDepth() (depth int) { func (k *Kademlia) NeighbourhoodDepth() (depth int) {
k.lock.RLock() k.lock.RLock()
defer k.lock.RUnlock() defer k.lock.RUnlock()
return depthForPot(k.conns, k.NeighbourhoodSize, k.base) return depthForPot(k.conns, k.NeighbourhoodSize, k.base)
} }
// depthForPot returns the proximity order that defines the distance of // neighbourhoodRadiusForPot returns the neighbourhood radius of the kademlia
// the nearest neighbour set with cardinality >= NeighbourhoodSize // neighbourhood radius encloses the nearest neighbour set with size >= neighbourhoodSize
// if there is altogether less than NeighbourhoodSize peers it returns 0 // i.e., neighbourhood radius is the deepest PO such that all bins not shallower altogether
// contain at least neighbourhoodSize connected peers
// if there is altogether less than neighbourhoodSize peers connected, it returns 0
// caller must hold the lock // caller must hold the lock
func depthForPot(p *pot.Pot, neighbourhoodSize int, pivotAddr []byte) (depth int) { func neighbourhoodRadiusForPot(p *pot.Pot, neighbourhoodSize int, pivotAddr []byte) (depth int) {
if p.Size() <= neighbourhoodSize { if p.Size() <= neighbourhoodSize {
return 0 return 0
} }
// total number of peers in iteration // total number of peers in iteration
var size int var size int
// determining the depth is a two-step process
// first we find the proximity bin of the shallowest of the NeighbourhoodSize peers
// the numeric value of depth cannot be higher than this
var maxDepth int
f := func(v pot.Val, i int) bool { f := func(v pot.Val, i int) bool {
// po == 256 means that addr is the pivot address(self) // po == 256 means that addr is the pivot address(self)
if i == 256 { if i == 256 {
@ -431,13 +461,30 @@ func depthForPot(p *pot.Pot, neighbourhoodSize int, pivotAddr []byte) (depth int
// this means we have all nn-peers. // this means we have all nn-peers.
// depth is by default set to the bin of the farthest nn-peer // depth is by default set to the bin of the farthest nn-peer
if size == neighbourhoodSize { if size == neighbourhoodSize {
maxDepth = i depth = i
return false return false
} }
return true return true
} }
p.EachNeighbour(pivotAddr, Pof, f) p.EachNeighbour(pivotAddr, Pof, f)
return depth
}
// depthForPot returns the depth for the pot
// depth is the radius of the minimal extension of nearest neighbourhood that
// includes all empty PO bins. I.e., depth is the deepest PO such that
// - it is not deeper than neighbourhood radius
// - all bins shallower than depth are not empty
// caller must hold the lock
func depthForPot(p *pot.Pot, neighbourhoodSize int, pivotAddr []byte) (depth int) {
if p.Size() <= neighbourhoodSize {
return 0
}
// determining the depth is a two-step process
// first we find the proximity bin of the shallowest of the neighbourhoodSize peers
// the numeric value of depth cannot be higher than this
maxDepth := neighbourhoodRadiusForPot(p, neighbourhoodSize, pivotAddr)
// the second step is to test for empty bins in order from shallowest to deepest // the second step is to test for empty bins in order from shallowest to deepest
// if an empty bin is found, this will be the actual depth // if an empty bin is found, this will be the actual depth
@ -506,6 +553,9 @@ func (k *Kademlia) string() string {
var rows []string var rows []string
rows = append(rows, "=========================================================================") rows = append(rows, "=========================================================================")
if len(sv.GitCommit) > 0 {
rows = append(rows, fmt.Sprintf("commit hash: %s", sv.GitCommit))
}
rows = append(rows, fmt.Sprintf("%v KΛÐΞMLIΛ hive: queen's address: %x", time.Now().UTC().Format(time.UnixDate), k.BaseAddr()[:3])) rows = append(rows, fmt.Sprintf("%v KΛÐΞMLIΛ hive: queen's address: %x", time.Now().UTC().Format(time.UnixDate), k.BaseAddr()[:3]))
rows = append(rows, fmt.Sprintf("population: %d (%d), NeighbourhoodSize: %d, MinBinSize: %d, MaxBinSize: %d", k.conns.Size(), k.addrs.Size(), k.NeighbourhoodSize, k.MinBinSize, k.MaxBinSize)) rows = append(rows, fmt.Sprintf("population: %d (%d), NeighbourhoodSize: %d, MinBinSize: %d, MaxBinSize: %d", k.conns.Size(), k.addrs.Size(), k.NeighbourhoodSize, k.MinBinSize, k.MaxBinSize))
@ -627,21 +677,20 @@ func NewPeerPotMap(neighbourhoodSize int, addrs [][]byte) map[string]*PeerPot {
return ppmap return ppmap
} }
// saturation iterates through all peers and // saturation returns the smallest po value in which the node has less than MinBinSize peers
// returns the smallest po value in which the node has less than n peers // if the iterator reaches neighbourhood radius, then the last bin + 1 is returned
// if the iterator reaches depth, then value for depth is returned
// TODO move to separate testing tools file
// TODO this function will stop at the first bin with less than MinBinSize peers, even if there are empty bins between that bin and the depth. This may not be correct behavior
func (k *Kademlia) saturation() int { func (k *Kademlia) saturation() int {
prev := -1 prev := -1
k.addrs.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool { radius := neighbourhoodRadiusForPot(k.conns, k.NeighbourhoodSize, k.base)
k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
prev++ prev++
if po >= radius {
return false
}
return prev == po && size >= k.MinBinSize return prev == po && size >= k.MinBinSize
}) })
// TODO evaluate whether this check cannot just as well be done within the eachbin if prev < 0 {
depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) return 0
if depth < prev {
return depth
} }
return prev return prev
} }
@ -743,6 +792,9 @@ type Health struct {
func (k *Kademlia) Healthy(pp *PeerPot) *Health { func (k *Kademlia) Healthy(pp *PeerPot) *Health {
k.lock.RLock() k.lock.RLock()
defer k.lock.RUnlock() defer k.lock.RUnlock()
if len(pp.NNSet) < k.NeighbourhoodSize {
log.Warn("peerpot NNSet < NeighbourhoodSize")
}
gotnn, countgotnn, culpritsgotnn := k.connectedNeighbours(pp.NNSet) gotnn, countgotnn, culpritsgotnn := k.connectedNeighbours(pp.NNSet)
knownn, countknownn, culpritsknownn := k.knowNeighbours(pp.NNSet) knownn, countknownn, culpritsknownn := k.knowNeighbours(pp.NNSet)
depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base)

View file

@ -17,7 +17,6 @@
package network package network
import ( import (
"bytes"
"fmt" "fmt"
"os" "os"
"testing" "testing"
@ -43,39 +42,46 @@ func testKadPeerAddr(s string) *BzzAddr {
func newTestKademliaParams() *KadParams { func newTestKademliaParams() *KadParams {
params := NewKadParams() params := NewKadParams()
// TODO why is this 1? params.MinBinSize = 2
params.MinBinSize = 1
params.NeighbourhoodSize = 2 params.NeighbourhoodSize = 2
return params return params
} }
func newTestKademlia(b string) *Kademlia { type testKademlia struct {
*Kademlia
t *testing.T
}
func newTestKademlia(t *testing.T, b string) *testKademlia {
base := pot.NewAddressFromString(b) base := pot.NewAddressFromString(b)
return NewKademlia(base, newTestKademliaParams()) return &testKademlia{
Kademlia: NewKademlia(base, newTestKademliaParams()),
t: t,
}
} }
func newTestKadPeer(k *Kademlia, s string, lightNode bool) *Peer { func (tk *testKademlia) newTestKadPeer(s string, lightNode bool) *Peer {
return NewPeer(&BzzPeer{BzzAddr: testKadPeerAddr(s), LightNode: lightNode}, k) return NewPeer(&BzzPeer{BzzAddr: testKadPeerAddr(s), LightNode: lightNode}, tk.Kademlia)
} }
func On(k *Kademlia, ons ...string) { func (tk *testKademlia) On(ons ...string) {
for _, s := range ons { for _, s := range ons {
k.On(newTestKadPeer(k, s, false)) tk.Kademlia.On(tk.newTestKadPeer(s, false))
} }
} }
func Off(k *Kademlia, offs ...string) { func (tk *testKademlia) Off(offs ...string) {
for _, s := range offs { for _, s := range offs {
k.Off(newTestKadPeer(k, s, false)) tk.Kademlia.Off(tk.newTestKadPeer(s, false))
} }
} }
func Register(k *Kademlia, regs ...string) { func (tk *testKademlia) Register(regs ...string) {
var as []*BzzAddr var as []*BzzAddr
for _, s := range regs { for _, s := range regs {
as = append(as, testKadPeerAddr(s)) as = append(as, testKadPeerAddr(s))
} }
err := k.Register(as...) err := tk.Kademlia.Register(as...)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }
@ -169,76 +175,71 @@ func TestHealthStrict(t *testing.T) {
// base address is all zeros // base address is all zeros
// no peers // no peers
// unhealthy (and lonely) // unhealthy (and lonely)
k := newTestKademlia("11111111") tk := newTestKademlia(t, "11111111")
assertHealth(t, k, false, false) tk.checkHealth(false, false)
// know one peer but not connected // know one peer but not connected
// unhealthy // unhealthy
Register(k, "11100000") tk.Register("11100000")
log.Trace(k.String()) tk.checkHealth(false, false)
assertHealth(t, k, false, false)
// know one peer and connected // know one peer and connected
// healthy // healthy
On(k, "11100000") tk.On("11100000")
assertHealth(t, k, true, false) tk.checkHealth(true, false)
// know two peers, only one connected // know two peers, only one connected
// unhealthy // unhealthy
Register(k, "11111100") tk.Register("11111100")
log.Trace(k.String()) tk.checkHealth(false, false)
assertHealth(t, k, false, false)
// know two peers and connected to both // know two peers and connected to both
// healthy // healthy
On(k, "11111100") tk.On("11111100")
assertHealth(t, k, true, false) tk.checkHealth(true, false)
// know three peers, connected to the two deepest // know three peers, connected to the two deepest
// healthy // healthy
Register(k, "00000000") tk.Register("00000000")
log.Trace(k.String()) tk.checkHealth(true, false)
assertHealth(t, k, true, false)
// know three peers, connected to all three // know three peers, connected to all three
// healthy // healthy
On(k, "00000000") tk.On("00000000")
assertHealth(t, k, true, false) tk.checkHealth(true, false)
// add fourth peer deeper than current depth // add fourth peer deeper than current depth
// unhealthy // unhealthy
Register(k, "11110000") tk.Register("11110000")
log.Trace(k.String()) tk.checkHealth(false, false)
assertHealth(t, k, false, false)
// connected to three deepest peers // connected to three deepest peers
// healthy // healthy
On(k, "11110000") tk.On("11110000")
assertHealth(t, k, true, false) tk.checkHealth(true, false)
// add additional peer in same bin as deepest peer // add additional peer in same bin as deepest peer
// unhealthy // unhealthy
Register(k, "11111101") tk.Register("11111101")
log.Trace(k.String()) tk.checkHealth(false, false)
assertHealth(t, k, false, false)
// four deepest of five peers connected // four deepest of five peers connected
// healthy // healthy
On(k, "11111101") tk.On("11111101")
assertHealth(t, k, true, false) tk.checkHealth(true, false)
} }
func assertHealth(t *testing.T, k *Kademlia, expectHealthy bool, expectSaturation bool) { func (tk *testKademlia) checkHealth(expectHealthy bool, expectSaturation bool) {
t.Helper() tk.t.Helper()
kid := common.Bytes2Hex(k.BaseAddr()) kid := common.Bytes2Hex(tk.BaseAddr())
addrs := [][]byte{k.BaseAddr()} addrs := [][]byte{tk.BaseAddr()}
k.EachAddr(nil, 255, func(addr *BzzAddr, po int) bool { tk.EachAddr(nil, 255, func(addr *BzzAddr, po int) bool {
addrs = append(addrs, addr.Address()) addrs = append(addrs, addr.Address())
return true return true
}) })
pp := NewPeerPotMap(k.NeighbourhoodSize, addrs) pp := NewPeerPotMap(tk.NeighbourhoodSize, addrs)
healthParams := k.Healthy(pp[kid]) healthParams := tk.Healthy(pp[kid])
// definition of health, all conditions but be true: // definition of health, all conditions but be true:
// - we at least know one peer // - we at least know one peer
@ -246,23 +247,23 @@ func assertHealth(t *testing.T, k *Kademlia, expectHealthy bool, expectSaturatio
// - we are connected to all known neighbors // - we are connected to all known neighbors
health := healthParams.KnowNN && healthParams.ConnectNN && healthParams.CountKnowNN > 0 health := healthParams.KnowNN && healthParams.ConnectNN && healthParams.CountKnowNN > 0
if expectHealthy != health { if expectHealthy != health {
t.Fatalf("expected kademlia health %v, is %v\n%v", expectHealthy, health, k.String()) tk.t.Fatalf("expected kademlia health %v, is %v\n%v", expectHealthy, health, tk.String())
} }
} }
func testSuggestPeer(k *Kademlia, expAddr string, expPo int, expWant bool) error { func (tk *testKademlia) checkSuggestPeer(expAddr string, expDepth int, expChanged bool) {
addr, o, want := k.SuggestPeer() tk.t.Helper()
log.Trace("suggestpeer return", "a", addr, "o", o, "want", want) addr, depth, changed := tk.SuggestPeer()
log.Trace("suggestPeer return", "addr", addr, "depth", depth, "changed", changed)
if binStr(addr) != expAddr { if binStr(addr) != expAddr {
return fmt.Errorf("incorrect peer address suggested. expected %v, got %v", expAddr, binStr(addr)) tk.t.Fatalf("incorrect peer address suggested. expected %v, got %v", expAddr, binStr(addr))
} }
if o != expPo { if depth != expDepth {
return fmt.Errorf("incorrect prox order suggested. expected %v, got %v", expPo, o) tk.t.Fatalf("incorrect saturation depth suggested. expected %v, got %v", expDepth, depth)
} }
if want != expWant { if changed != expChanged {
return fmt.Errorf("expected SuggestPeer to want peers: %v", expWant) tk.t.Fatalf("expected depth change = %v, got %v", expChanged, changed)
} }
return nil
} }
func binStr(a *BzzAddr) string { func binStr(a *BzzAddr) string {
@ -272,609 +273,184 @@ func binStr(a *BzzAddr) string {
return pot.ToBin(a.Address())[:8] return pot.ToBin(a.Address())[:8]
} }
// TODO explain why this bug occurred and how it should have been mitigated
func TestSuggestPeerBug(t *testing.T) {
// 2 row gap, unsaturated proxbin, no callables -> want PO 0
k := newTestKademlia("00000000")
On(k,
"10000000", "11000000",
"01000000",
"00010000", "00011000",
)
Off(k,
"01000000",
)
err := testSuggestPeer(k, "01000000", 0, false)
if err != nil {
t.Fatal(err.Error())
}
}
func TestSuggestPeerFindPeers(t *testing.T) { func TestSuggestPeerFindPeers(t *testing.T) {
t.Skip("The SuggestPeers implementation seems to have weaknesses exposed by the change in the new depth calculation. The results are no longer predictable") tk := newTestKademlia(t, "00000000")
tk.On("00100000")
tk.checkSuggestPeer("<nil>", 0, false)
testnum := 0 tk.On("00010000")
// test 0 tk.checkSuggestPeer("<nil>", 0, false)
// 2 row gap, unsaturated proxbin, no callables -> want PO 0
k := newTestKademlia("00000000")
On(k, "00100000")
err := testSuggestPeer(k, "<nil>", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 1 tk.On("10000000", "10000001")
// 2 row gap, saturated proxbin, no callables -> want PO 0 tk.checkSuggestPeer("<nil>", 0, false)
On(k, "00010000")
err = testSuggestPeer(k, "<nil>", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 2 tk.On("01000000")
// 1 row gap (1 less), saturated proxbin, no callables -> want PO 1 tk.Off("10000001")
On(k, "10000000") tk.checkSuggestPeer("10000001", 0, true)
err = testSuggestPeer(k, "<nil>", 1, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 3 tk.On("00100001")
// no gap (1 less), saturated proxbin, no callables -> do not want more tk.Off("01000000")
On(k, "01000000", "00100001") tk.checkSuggestPeer("01000000", 0, false)
err = testSuggestPeer(k, "<nil>", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 4
// oversaturated proxbin, > do not want more
On(k, "00100001")
err = testSuggestPeer(k, "<nil>", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 5
// reintroduce gap, disconnected peer callable
Off(k, "01000000")
log.Trace(k.String())
err = testSuggestPeer(k, "01000000", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 6
// second time disconnected peer not callable // second time disconnected peer not callable
// with reasonably set Interval // with reasonably set Interval
log.Trace("foo") tk.checkSuggestPeer("<nil>", 0, false)
log.Trace(k.String())
err = testSuggestPeer(k, "<nil>", 1, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 6
// on and off again, peer callable again // on and off again, peer callable again
On(k, "01000000") tk.On("01000000")
Off(k, "01000000") tk.Off("01000000")
log.Trace(k.String()) tk.checkSuggestPeer("01000000", 0, false)
err = testSuggestPeer(k, "01000000", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 7 tk.On("01000000", "10000001")
// new closer peer appears, it is immediately wanted tk.checkSuggestPeer("<nil>", 0, false)
On(k, "01000000")
Register(k, "00010001")
err = testSuggestPeer(k, "00010001", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 8 tk.Register("00010001")
// PO1 disconnects tk.checkSuggestPeer("00010001", 0, false)
On(k, "00010001")
log.Info(k.String())
Off(k, "01000000")
log.Info(k.String())
// second time, gap filling
err = testSuggestPeer(k, "01000000", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 9 tk.On("00010001")
On(k, "01000000") tk.Off("01000000")
log.Info(k.String()) tk.checkSuggestPeer("01000000", 0, false)
err = testSuggestPeer(k, "<nil>", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 10 tk.On("01000000")
k.MinBinSize = 2 tk.checkSuggestPeer("<nil>", 0, false)
log.Info(k.String())
err = testSuggestPeer(k, "<nil>", 0, true)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 11 tk.Register("01000001")
Register(k, "01000001") tk.checkSuggestPeer("01000001", 0, false)
log.Info(k.String())
err = testSuggestPeer(k, "01000001", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 12 tk.On("01000001")
On(k, "10000001") tk.checkSuggestPeer("<nil>", 0, false)
log.Trace(fmt.Sprintf("Kad:\n%v", k.String()))
err = testSuggestPeer(k, "<nil>", 1, true)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 13 tk.Register("10000010", "01000010", "00100010")
On(k, "01000001") tk.checkSuggestPeer("<nil>", 0, false)
err = testSuggestPeer(k, "<nil>", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 14 tk.Register("00010010")
k.MinBinSize = 3 tk.checkSuggestPeer("00010010", 0, false)
Register(k, "10000010")
err = testSuggestPeer(k, "10000010", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 15 tk.Off("00100001")
On(k, "10000010") tk.checkSuggestPeer("00100010", 2, true)
err = testSuggestPeer(k, "<nil>", 1, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 16 tk.Off("01000001")
On(k, "01000010") tk.checkSuggestPeer("01000010", 1, true)
err = testSuggestPeer(k, "<nil>", 2, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 17 tk.checkSuggestPeer("01000001", 0, false)
On(k, "00100010") tk.checkSuggestPeer("00100001", 0, false)
err = testSuggestPeer(k, "<nil>", 3, false) tk.checkSuggestPeer("<nil>", 0, false)
if err != nil {
t.Fatalf("%d %v", testnum, err.Error())
}
testnum++
// test 18 tk.On("01000001", "00100001")
On(k, "00010010") tk.Register("10000100", "01000100", "00100100")
err = testSuggestPeer(k, "<nil>", 0, false) tk.Register("00000100", "00000101", "00000110")
if err != nil { tk.Register("00000010", "00000011", "00000001")
t.Fatalf("%d %v", testnum, err.Error())
} tk.checkSuggestPeer("00000110", 0, false)
testnum++ tk.checkSuggestPeer("00000101", 0, false)
tk.checkSuggestPeer("00000100", 0, false)
tk.checkSuggestPeer("00000011", 0, false)
tk.checkSuggestPeer("00000010", 0, false)
tk.checkSuggestPeer("00000001", 0, false)
tk.checkSuggestPeer("<nil>", 0, false)
} }
// a node should stay in the address book if it's removed from the kademlia // a node should stay in the address book if it's removed from the kademlia
func TestOffEffectingAddressBookNormalNode(t *testing.T) { func TestOffEffectingAddressBookNormalNode(t *testing.T) {
k := newTestKademlia("00000000") tk := newTestKademlia(t, "00000000")
// peer added to kademlia // peer added to kademlia
k.On(newTestKadPeer(k, "01000000", false)) tk.On("01000000")
// peer should be in the address book // peer should be in the address book
if k.addrs.Size() != 1 { if tk.addrs.Size() != 1 {
t.Fatal("known peer addresses should contain 1 entry") t.Fatal("known peer addresses should contain 1 entry")
} }
// peer should be among live connections // peer should be among live connections
if k.conns.Size() != 1 { if tk.conns.Size() != 1 {
t.Fatal("live peers should contain 1 entry") t.Fatal("live peers should contain 1 entry")
} }
// remove peer from kademlia // remove peer from kademlia
k.Off(newTestKadPeer(k, "01000000", false)) tk.Off("01000000")
// peer should be in the address book // peer should be in the address book
if k.addrs.Size() != 1 { if tk.addrs.Size() != 1 {
t.Fatal("known peer addresses should contain 1 entry") t.Fatal("known peer addresses should contain 1 entry")
} }
// peer should not be among live connections // peer should not be among live connections
if k.conns.Size() != 0 { if tk.conns.Size() != 0 {
t.Fatal("live peers should contain 0 entry") t.Fatal("live peers should contain 0 entry")
} }
} }
// a light node should not be in the address book // a light node should not be in the address book
func TestOffEffectingAddressBookLightNode(t *testing.T) { func TestOffEffectingAddressBookLightNode(t *testing.T) {
k := newTestKademlia("00000000") tk := newTestKademlia(t, "00000000")
// light node peer added to kademlia // light node peer added to kademlia
k.On(newTestKadPeer(k, "01000000", true)) tk.Kademlia.On(tk.newTestKadPeer("01000000", true))
// peer should not be in the address book // peer should not be in the address book
if k.addrs.Size() != 0 { if tk.addrs.Size() != 0 {
t.Fatal("known peer addresses should contain 0 entry") t.Fatal("known peer addresses should contain 0 entry")
} }
// peer should be among live connections // peer should be among live connections
if k.conns.Size() != 1 { if tk.conns.Size() != 1 {
t.Fatal("live peers should contain 1 entry") t.Fatal("live peers should contain 1 entry")
} }
// remove peer from kademlia // remove peer from kademlia
k.Off(newTestKadPeer(k, "01000000", true)) tk.Kademlia.Off(tk.newTestKadPeer("01000000", true))
// peer should not be in the address book // peer should not be in the address book
if k.addrs.Size() != 0 { if tk.addrs.Size() != 0 {
t.Fatal("known peer addresses should contain 0 entry") t.Fatal("known peer addresses should contain 0 entry")
} }
// peer should not be among live connections // peer should not be among live connections
if k.conns.Size() != 0 { if tk.conns.Size() != 0 {
t.Fatal("live peers should contain 0 entry") t.Fatal("live peers should contain 0 entry")
} }
} }
func TestSuggestPeerRetries(t *testing.T) { func TestSuggestPeerRetries(t *testing.T) {
k := newTestKademlia("00000000") tk := newTestKademlia(t, "00000000")
k.RetryInterval = int64(300 * time.Millisecond) // cycle tk.RetryInterval = int64(300 * time.Millisecond) // cycle
k.MaxRetries = 50 tk.MaxRetries = 50
k.RetryExponent = 2 tk.RetryExponent = 2
sleep := func(n int) { sleep := func(n int) {
ts := k.RetryInterval ts := tk.RetryInterval
for i := 1; i < n; i++ { for i := 1; i < n; i++ {
ts *= int64(k.RetryExponent) ts *= int64(tk.RetryExponent)
} }
time.Sleep(time.Duration(ts)) time.Sleep(time.Duration(ts))
} }
Register(k, "01000000") tk.Register("01000000")
On(k, "00000001", "00000010") tk.On("00000001", "00000010")
err := testSuggestPeer(k, "01000000", 0, false) tk.checkSuggestPeer("01000000", 0, false)
if err != nil {
t.Fatal(err.Error())
}
err = testSuggestPeer(k, "<nil>", 0, false) tk.checkSuggestPeer("<nil>", 0, false)
if err != nil {
t.Fatal(err.Error())
}
sleep(1) sleep(1)
err = testSuggestPeer(k, "01000000", 0, false) tk.checkSuggestPeer("01000000", 0, false)
if err != nil {
t.Fatal(err.Error())
}
err = testSuggestPeer(k, "<nil>", 0, false) tk.checkSuggestPeer("<nil>", 0, false)
if err != nil {
t.Fatal(err.Error())
}
sleep(1) sleep(1)
err = testSuggestPeer(k, "01000000", 0, false) tk.checkSuggestPeer("01000000", 0, false)
if err != nil {
t.Fatal(err.Error())
}
err = testSuggestPeer(k, "<nil>", 0, false) tk.checkSuggestPeer("<nil>", 0, false)
if err != nil {
t.Fatal(err.Error())
}
sleep(2) sleep(2)
err = testSuggestPeer(k, "01000000", 0, false) tk.checkSuggestPeer("01000000", 0, false)
if err != nil {
t.Fatal(err.Error())
}
err = testSuggestPeer(k, "<nil>", 0, false) tk.checkSuggestPeer("<nil>", 0, false)
if err != nil {
t.Fatal(err.Error())
}
sleep(2) sleep(2)
err = testSuggestPeer(k, "<nil>", 0, false) tk.checkSuggestPeer("<nil>", 0, false)
if err != nil {
t.Fatal(err.Error())
}
} }
func TestKademliaHiveString(t *testing.T) { func TestKademliaHiveString(t *testing.T) {
k := newTestKademlia("00000000") tk := newTestKademlia(t, "00000000")
On(k, "01000000", "00100000") tk.On("01000000", "00100000")
Register(k, "10000000", "10000001") tk.Register("10000000", "10000001")
k.MaxProxDisplay = 8 tk.MaxProxDisplay = 8
h := k.String() h := tk.String()
expH := "\n=========================================================================\nMon Feb 27 12:10:28 UTC 2017 KΛÐΞMLIΛ hive: queen's address: 000000\npopulation: 2 (4), NeighbourhoodSize: 2, MinBinSize: 1, MaxBinSize: 4\n============ DEPTH: 0 ==========================================\n000 0 | 2 8100 (0) 8000 (0)\n001 1 4000 | 1 4000 (0)\n002 1 2000 | 1 2000 (0)\n003 0 | 0\n004 0 | 0\n005 0 | 0\n006 0 | 0\n007 0 | 0\n=========================================================================" expH := "\n=========================================================================\nMon Feb 27 12:10:28 UTC 2017 KΛÐΞMLIΛ hive: queen's address: 000000\npopulation: 2 (4), NeighbourhoodSize: 2, MinBinSize: 2, MaxBinSize: 4\n============ DEPTH: 0 ==========================================\n000 0 | 2 8100 (0) 8000 (0)\n001 1 4000 | 1 4000 (0)\n002 1 2000 | 1 2000 (0)\n003 0 | 0\n004 0 | 0\n005 0 | 0\n006 0 | 0\n007 0 | 0\n========================================================================="
if expH[104:] != h[104:] { if expH[104:] != h[104:] {
t.Fatalf("incorrect hive output. expected %v, got %v", expH, h) t.Fatalf("incorrect hive output. expected %v, got %v", expH, h)
} }
} }
// testKademliaCase constructs the kademlia and PeerPot map to validate
// the SuggestPeer and Healthy methods for provided hex-encoded addresses.
// Argument pivotAddr is the address of the kademlia.
func testKademliaCase(t *testing.T, pivotAddr string, addrs ...string) {
t.Skip("this test relies on SuggestPeer which is now not reliable. See description in TestSuggestPeerFindPeers")
addr := common.Hex2Bytes(pivotAddr)
var byteAddrs [][]byte
for _, ahex := range addrs {
byteAddrs = append(byteAddrs, common.Hex2Bytes(ahex))
}
k := NewKademlia(addr, NewKadParams())
// our pivot kademlia is the last one in the array
for _, a := range byteAddrs {
if bytes.Equal(a, addr) {
continue
}
p := &BzzAddr{OAddr: a, UAddr: a}
if err := k.Register(p); err != nil {
t.Fatalf("a %x addr %x: %v", a, addr, err)
}
}
ppmap := NewPeerPotMap(k.NeighbourhoodSize, byteAddrs)
pp := ppmap[pivotAddr]
for {
a, _, _ := k.SuggestPeer()
if a == nil {
break
}
k.On(NewPeer(&BzzPeer{BzzAddr: a}, k))
}
h := k.Healthy(pp)
if !(h.ConnectNN && h.KnowNN && h.CountKnowNN > 0) {
t.Fatalf("not healthy: %#v\n%v", h, k.String())
}
}
/*
The regression test for the following invalid kademlia edge case.
Addresses used in this test are discovered as part of the simulation network
in higher level tests for streaming. They were generated randomly.
=========================================================================
Mon Apr 9 12:18:24 UTC 2018 KΛÐΞMLIΛ hive: queen's address: 7efef1
population: 9 (49), NeighbourhoodSize: 2, MinBinSize: 2, MaxBinSize: 4
000 2 d7e5 ec56 | 18 ec56 (0) d7e5 (0) d9e0 (0) c735 (0)
001 2 18f1 3176 | 14 18f1 (0) 10bb (0) 10d1 (0) 0421 (0)
002 2 52aa 47cd | 11 52aa (0) 51d9 (0) 5161 (0) 5130 (0)
003 1 646e | 1 646e (0)
004 0 | 3 769c (0) 76d1 (0) 7656 (0)
============ DEPTH: 5 ==========================================
005 1 7a48 | 1 7a48 (0)
006 1 7cbd | 1 7cbd (0)
007 0 | 0
008 0 | 0
009 0 | 0
010 0 | 0
011 0 | 0
012 0 | 0
013 0 | 0
014 0 | 0
015 0 | 0
=========================================================================
*/
func TestKademliaCase1(t *testing.T) {
testKademliaCase(t,
"7efef1c41d77f843ad167be95f6660567eb8a4a59f39240000cce2e0d65baf8e",
"ec560e6a4806aa37f147ee83687f3cf044d9953e61eedb8c34b6d50d9e2c5623",
"646e9540c84f6a2f9cf6585d45a4c219573b4fd1b64a3c9a1386fc5cf98c0d4d",
"18f13c5fba653781019025ab10e8d2fdc916d6448729268afe9e928ffcdbb8e8",
"317617acf99b4ffddda8a736f8fc6c6ede0bf690bc23d834123823e6d03e2f69",
"d7e52d9647a5d1c27a68c3ee65d543be3947ae4b68537b236d71ef9cb15fb9ab",
"7a48f75f8ca60487ae42d6f92b785581b40b91f2da551ae73d5eae46640e02e8",
"7cbd42350bde8e18ae5b955b5450f8e2cef3419f92fbf5598160c60fd78619f0",
"52aa3ddec61f4d48dd505a2385403c634f6ad06ee1d99c5c90a5ba6006f9af9c",
"47cdb6fa93eeb8bc91a417ff4e3b14a9c2ea85137462e2f575fae97f0c4be60d",
"5161943eb42e2a03e715fe8afa1009ff5200060c870ead6ab103f63f26cb107f",
"a38eaa1255f76bf883ca0830c86e8c4bb7eed259a8348aae9b03f21f90105bee",
"b2522bdf1ab26f324e75424fdf6e493b47e8a27687fe76347607b344fc010075",
"5bd7213964efb2580b91d02ac31ef126838abeba342f5dbdbe8d4d03562671a2",
"0b531adb82744768b694d7f94f73d4f0c9de591266108daeb8c74066bfc9c9ca",
"28501f59f70e888d399570145ed884353e017443c675aa12731ada7c87ea14f7",
"4a45f1fc63e1a9cb9dfa44c98da2f3d20c2923e5d75ff60b2db9d1bdb0c54d51",
"b193431ee35cd32de95805e7c1c749450c47486595aae7195ea6b6019a64fd61",
"baebf36a1e35a7ed834e1c72faf44ba16c159fa47d3289ceb3ca35fefa8739b5",
"a3659bd32e05fa36c8d20dbaaed8362bf1a8a7bd116aed62d8a43a2efbdf513f",
"10d1b50881a4770ebebdd0a75589dabb931e6716747b0f65fd6b080b88c4fdb6",
"3c76b8ca5c7ce6a03320646826213f59229626bf5b9d25da0c3ec0662dcb8ff3",
"4d72a04ddeb851a68cd197ef9a92a3e2ff01fbbff638e64929dd1a9c2e150112",
"c7353d320987956075b5bc1668571c7a36c800d5598fdc4832ec6569561e15d1",
"d9e0c7c90878c20ab7639d5954756f54775404b3483407fe1b483635182734f6",
"8fca67216b7939c0824fb06c5279901a94da41da9482b000f56df9906736ee75",
"460719d7f7aa7d7438f0eaf30333484fa3bd0f233632c10ba89e6e46dd3604be",
"0421d92c8a1c79ed5d01305a3d25aaf22a8f5f9e3d4bc80da47ee16ce20465fe",
"3441d9d9c0f05820a1bb6459fc7d8ef266a1bd929e7db939a10f544efe8261ea",
"ab198a66c293586746758468c610e5d3914d4ce629147eff6dd55a31f863ff8f",
"3a1c8c16b0763f3d2c35269f454ff779d1255e954d2deaf6c040fb3f0bcdc945",
"5561c0ea3b203e173b11e6aa9d0e621a4e10b1d8b178b8fe375220806557b823",
"7656caccdc79cd8d7ce66d415cc96a718e8271c62fb35746bfc2b49faf3eebf3",
"5130594fd54c1652cf2debde2c4204573ed76555d1e26757fe345b409af1544a",
"76d1e83c71ca246d042e37ff1db181f2776265fbcfdc890ce230bfa617c9c2f0",
"89580231962624c53968c1b0095b4a2732b2a2640a19fdd7d21fd064fcc0a5ef",
"3d10d001fff44680c7417dd66ecf2e984f0baa20a9bbcea348583ba5ff210c4f",
"43754e323f0f3a1155b1852bd6edd55da86b8c4cfe3df8b33733fca50fc202b8",
"a9e7b1bb763ae6452ddcacd174993f82977d81a85206bb2ae3c842e2d8e19b4c",
"10bb07da7bc7c7757f74149eff167d528a94a253cdc694a863f4d50054c00b6d",
"28f0bc1b44658548d6e05dd16d4c2fe77f1da5d48b6774bc4263b045725d0c19",
"835fbbf1d16ba7347b6e2fc552d6e982148d29c624ea20383850df3c810fa8fc",
"8e236c56a77d7f46e41e80f7092b1a68cd8e92f6156365f41813ad1ca2c6b6f3",
"51d9c857e9238c49186e37b4eccf17a82de3d5739f026f6043798ab531456e73",
"bbddf7db6a682225301f36a9fd5b0d0121d2951753e1681295f3465352ad511f",
"2690a910c33ee37b91eb6c4e0731d1d345e2dc3b46d308503a6e85bbc242c69e",
"769ce86aa90b518b7ed382f9fdacfbed93574e18dc98fe6c342e4f9f409c2d5a",
"ba3bebec689ce51d3e12776c45f80d25164fdfb694a8122d908081aaa2e7122c",
"3a51f4146ea90a815d0d283d1ceb20b928d8b4d45875e892696986a3c0d8fb9b",
"81968a2d8fb39114342ee1da85254ec51e0608d7f0f6997c2a8354c260a71009",
)
}
/*
The regression test for the following invalid kademlia edge case.
Addresses used in this test are discovered as part of the simulation network
in higher level tests for streaming. They were generated randomly.
=========================================================================
Mon Apr 9 18:43:48 UTC 2018 KΛÐΞMLIΛ hive: queen's address: bc7f3b
population: 9 (49), NeighbourhoodSize: 2, MinBinSize: 2, MaxBinSize: 4
000 2 0f49 67ff | 28 0f49 (0) 0211 (0) 07b2 (0) 0703 (0)
001 2 e84b f3a4 | 13 f3a4 (0) e84b (0) e58b (0) e60b (0)
002 1 8dba | 1 8dba (0)
003 2 a008 ad72 | 2 ad72 (0) a008 (0)
004 0 | 3 b61f (0) b27f (0) b027 (0)
============ DEPTH: 5 ==========================================
005 1 ba19 | 1 ba19 (0)
006 0 | 0
007 1 bdd6 | 1 bdd6 (0)
008 0 | 0
009 0 | 0
010 0 | 0
011 0 | 0
012 0 | 0
013 0 | 0
014 0 | 0
015 0 | 0
=========================================================================
*/
func TestKademliaCase2(t *testing.T) {
testKademliaCase(t,
"bc7f3b6a4a7e3c91b100ca6680b6c06ff407972b88956324ca853295893e0237", "67ffb61d3aa27449d277016188f35f19e2321fbda5008c68cf6303faa080534f", "600cd54c842eadac1729c04abfc369bc244572ca76117105b9dd910283b82730", "d955a05409650de151218557425105a8aa2867bb6a0e0462fa1cf90abcf87ad6", "7a6b726de45abdf7bb3e5fd9fb0dc8932270ca4dedef92238c80c05bcdb570e3", "263e99424ebfdb652adb4e3dcd27d59e11bb7ae1c057b3ef6f390d0228006254", "ba195d1a53aafde68e661c64d39db8c2a73505bf336125c15c3560de3b48b7ed", "3458c762169937115f67cabc35a6c384ed70293a8aec37b077a6c1b8e02d510e", "4ef4dc2e28ac6efdba57e134ac24dd4e0be68b9d54f7006515eb9509105f700c", "2a8782b79b0c24b9714dfd2c8ff1932bebc08aa6520b4eaeaa59ff781238890c", "625d02e960506f4524e9cdeac85b33faf3ea437fceadbd478b62b78720cf24fc", "e051a36a8c8637f520ba259c9ed3fadaf740dadc6a04c3f0e21778ebd4cd6ac4", "e34bc014fa2504f707bb3d904872b56c2fa250bee3cb19a147a0418541f1bd90", "28036dc79add95799916893890add5d8972f3b95325a509d6ded3d448f4dc652", "1b013c407794fa2e4c955d8f51cbc6bd78588a174b6548246b291281304b5409", "34f71b68698e1534095ff23ee9c35bf64c7f12b8463e7c6f6b19c25cf03928b4", "c712c6e9bbb7076832972a95890e340b94ed735935c3c0bb788e61f011b59479", "a008d5becdcda4b9dbfdaafc3cec586cf61dcf2d4b713b6168fff02e3b9f0b08", "29de15555cdbebaab214009e416ee92f947dcec5dab9894129f50f1b17138f34", "5df9449f700bd4b5a23688b68b293f2e92fa6ca524c93bc6bb9936efba9d9ada", "3ab0168a5f87fedc6a39b53c628256ac87a98670d8691bbdaaecec22418d13a2", "1ee299b2d2a74a568494130e6869e66d57982d345c482a0e0eeb285ac219ae3b", "e0e0e3b860cea9b7a74cf1b0675cc632dc64e80a02f20bbc5e96e2e8bb670606", "dc1ba6f169b0fcdcca021dcebaf39fe5d4875e7e69b854fad65687c1d7719ec0", "d321f73e42fcfb1d3a303eddf018ca5dffdcfd5567cd5ec1212f045f6a07e47d", "070320c3da7b542e5ca8aaf6a0a53d2bb5113ed264ab1db2dceee17c729edcb1", "17d314d65fdd136b50d182d2c8f5edf16e7838c2be8cf2c00abe4b406dbcd1d8", "e60b99e0a06f7d2d99d84085f67cdf8cc22a9ae22c339365d80f90289834a2b4", "02115771e18932e1f67a45f11f5bf743c5dae97fbc477d34d35c996012420eac", "3102a40eb2e5060353dd19bf61eeec8782dd1bebfcb57f4c796912252b591827", "8dbaf231062f2dc7ddaba5f9c7761b0c21292be51bf8c2ef503f31d4a2f63f79", "b02787b713c83a9f9183216310f04251994e04c2763a9024731562e8978e7cc4", "b27fe6cd33989e10909ce794c4b0b88feae286b614a59d49a3444c1a7b51ea82", "07b2d2c94fdc6fd148fe23be2ed9eff54f5e12548f29ed8416e6860fc894466f", "e58bf9f451ef62ac44ff0a9bb0610ec0fd14d423235954f0d3695e83017cbfc4", "bdd600b91bb79d1ee0053b854de308cfaa7e2abce575ea6815a0a7b3449609c2", "0f49c93c1edc7999920b21977cedd51a763940dac32e319feb9c1df2da0f3071", "7cbf0297cd41acf655cd6f960d7aaf61479edb4189d5c001cbc730861f0deb41", "79265193778d87ad626a5f59397bc075872d7302a12634ce2451a767d0a82da2", "2fe7d705f7c370b9243dbaafe007d555ff58d218822fca49d347b12a0282457c", "e84bc0c83d05e55a0080eed41dda5a795da4b9313a4da697142e69a65834cbb3", "cc4d278bd9aa0e9fb3cd8d2e0d68fb791aab5de4b120b845c409effbed47a180", "1a2317a8646cd4b6d3c4aa4cc25f676533abb689cf180787db216880a1239ad8", "cbafd6568cf8e99076208e6b6843f5808a7087897c67aad0c54694669398f889", "7b7c8357255fc37b4dae0e1af61589035fd39ff627e0938c6b3da8b4e4ec5d23", "2b8d782c1f5bac46c922cf439f6aa79f91e9ba5ffc0020d58455188a2075b334", "b61f45af2306705740742e76197a119235584ced01ef3f7cf3d4370f6c557cd1", "2775612e7cdae2780bf494c370bdcbe69c55e4a1363b1dc79ea0135e61221cce", "f3a49bb22f40885e961299abfa697a7df690a79f067bf3a4847a3ad48d826c9f", "ad724ac218dc133c0aadf4618eae21fdd0c2f3787af279846b49e2b4f97ff167",
)
}
/*
The regression test for the following invalid kademlia edge case.
Addresses used in this test are discovered as part of the simulation network
in higher level tests for streaming. They were generated randomly.
=========================================================================
Mon Apr 9 19:04:35 UTC 2018 KΛÐΞMLIΛ hive: queen's address: b4822e
population: 8 (49), NeighbourhoodSize: 2, MinBinSize: 2, MaxBinSize: 4
000 2 786c 774b | 29 774b (0) 786c (0) 7a79 (0) 7d2f (0)
001 2 d9de cf19 | 10 cf19 (0) d9de (0) d2ff (0) d2a2 (0)
002 2 8ca1 8d74 | 5 8d74 (0) 8ca1 (0) 9793 (0) 9f51 (0)
003 0 | 0
004 0 | 3 bfac (0) bcbb (0) bde9 (0)
005 0 | 0
============ DEPTH: 6 ==========================================
006 1 b660 | 1 b660 (0)
007 0 | 0
008 1 b450 | 1 b450 (0)
009 0 | 0
010 0 | 0
011 0 | 0
012 0 | 0
013 0 | 0
014 0 | 0
015 0 | 0
=========================================================================
*/
func TestKademliaCase3(t *testing.T) {
testKademliaCase(t,
"b4822e874a01b94ac3a35c821e6db131e785c2fcbb3556e84b36102caf09b091", "2ecf54ea38d58f9cfc3862e54e5854a7c506fbc640e0b38e46d7d45a19794999", "442374092be50fc7392e8dd3f6fab3158ff7f14f26ff98060aed9b2eecf0b97d", "b450a4a67fcfa3b976cf023d8f1f15052b727f712198ce901630efe2f95db191", "9a7291638eb1c989a6dd6661a42c735b23ac6605b5d3e428aa5ffe650e892c85", "67f62eeab9804cfcac02b25ebeab9113d1b9d03dd5200b1c5a324cc0163e722f", "2e4a0e4b53bca4a9d7e2734150e9f579f29a255ade18a268461b20d026c9ee90", "30dd79c5fcdaa1b106f6960c45c9fde7c046aa3d931088d98c52ab759d0b2ac4", "97936fb5a581e59753c54fa5feec493714f2218245f61f97a62eafd4699433e4", "3a2899b6e129e3e193f6e2aefb82589c948c246d2ec1d4272af32ef3b2660f44", "f0e2a8aa88e67269e9952431ef12e5b29b7f41a1871fbfc38567fad95655d607", "7fa12b3f3c5f8383bfc644b958f72a486969733fa097d8952b3eb4f7b4f73192", "360c167aad5fc992656d6010ec45fdce5bcd492ad9608bc515e2be70d4e430c1", "fe21bc969b3d8e5a64a6484a829c1e04208f26f3cd4de6afcbc172a5bd17f1f1", "b660a1f40141d7ccd282fe5bd9838744119bd1cb3780498b5173578cc5ad308f", "44dcb3370e76680e2fba8cd986ad45ff0b77ca45680ee8d950e47922c4af6226", "8ca126923d17fccb689647307b89f38aa14e2a7b9ebcf3c1e31ccf3d2291a3bc", "f0ae19ae9ce6329327cbf42baf090e084c196b0877d8c7b69997e0123be23ef8", "d2a2a217385158e3e1e348883a14bc423e57daa12077e8c49797d16121ea0810", "f5467ccd85bb4ebe768527db520a210459969a5f1fae6e07b43f519799f0b224", "68be5fd9f9d142a5099e3609011fe3bab7bb992c595999e31e0b3d1668dfb3cf", "4d49a8a476e4934afc6b5c36db9bece3ed1804f20b952da5a21b2b0de766aa73", "ea7155745ef3fb2d099513887a2ba279333ced65c65facbd890ce58bd3fce772", "cf19f51f4e848053d289ac95a9138cdd23fc3077ae913cd58cda8cc7a521b2e1", "590b1cd41c7e6144e76b5cd515a3a4d0a4317624620a3f1685f43ae68bdcd890", "d2ffe0626b5f94a7e00fa0b506e7455a3d9399c15800db108d5e715ef5f6e346", "69630878c50a91f6c2edd23a706bfa0b50bd5661672a37d67bab38e6bca3b698", "445e9067079899bb5faafaca915ae6c0f6b1b730a5a628835dd827636f7feb1e", "6461c77491f1c4825958949f23c153e6e1759a5be53abbcee17c9da3867f3141", "23a235f4083771ccc207771daceda700b525a59ab586788d4f6892e69e34a6e2", "bde99f79ef41a81607ddcf92b9f95dcbc6c3537e91e8bf740e193dc73b19485e", "177957c0e5f0fbd12b88022a91768095d193830986caec8d888097d3ff4310b8", "bcbbdbaa4cdf8352422072f332e05111b732354a35c4d7c617ce1fc3b8b42a5a", "774b6717fdfb0d1629fb9d4c04a9ca40079ae2955d7f82e897477055ed017abb", "16443bf625be6d39ecaa6f114e5d2c1d47a64bfd3c13808d94b55b6b6acef2ee", "8d7495d9008066505ed00ce8198af82bfa5a6b4c08768b4c9fb3aa4eb0b0cca2", "15800849a53349508cb382959527f6c3cf1a46158ff1e6e2316b7dea7967e35f", "7a792f0f4a2b731781d1b244b2a57947f1a2e32900a1c0793449f9f7ae18a7b7", "5e517c2832c9deaa7df77c7bad4d20fd6eda2b7815e155e68bc48238fac1416f", "9f51a14f0019c72bd1d472706d8c80a18c1873c6a0663e754b60eae8094483d7", "7d2fabb565122521d22ba99fed9e5be6a458fbc93156d54db27d97a00b8c3a97", "786c9e412a7db4ec278891fa534caa9a1d1a028c631c6f3aeb9c4d96ad895c36", "3bd6341d40641c2632a5a0cd7a63553a04e251efd7195897a1d27e02a7a8bfde", "31efd1f5fb57b8cff0318d77a1a9e8d67e1d1c8d18ce90f99c3a240dff48cdc8", "d9de3e1156ce1380150948acbcfecd99c96e7f4b0bc97745f4681593d017f74f", "427a2201e09f9583cd990c03b81b58148c297d474a3b50f498d83b1c7a9414cd", "bfaca11596d3dec406a9fcf5d97536516dfe7f0e3b12078428a7e1700e25218a", "351c4770a097248a650008152d0cab5825d048bef770da7f3364f59d1e721bc0", "ee00f205d1486b2be7381d962bd2867263758e880529e4e2bfedfa613bbc0e71", "6aa3b6418d89e3348e4859c823ef4d6d7cd46aa7f7e77aba586c4214d760d8f8",
)
}
/*
The regression test for the following invalid kademlia edge case.
Addresses used in this test are discovered as part of the simulation network
in higher level tests for streaming. They were generated randomly.
=========================================================================
Mon Apr 9 19:16:25 UTC 2018 KΛÐΞMLIΛ hive: queen's address: 9a90fe
population: 8 (49), NeighbourhoodSize: 2, MinBinSize: 2, MaxBinSize: 4
000 2 72ef 4e6c | 24 0b1e (0) 0d66 (0) 17f5 (0) 17e8 (0)
001 2 fc2b fa47 | 13 fa47 (0) fc2b (0) fffd (0) ecef (0)
002 2 b847 afa8 | 6 afa8 (0) ad77 (0) bb7c (0) b847 (0)
003 0 | 0
004 0 | 4 91fc (0) 957d (0) 9482 (0) 949a (0)
============ DEPTH: 5 ==========================================
005 1 9ccf | 1 9ccf (0)
006 0 | 0
007 1 9bb2 | 1 9bb2 (0)
008 0 | 0
009 0 | 0
010 0 | 0
011 0 | 0
012 0 | 0
013 0 | 0
014 0 | 0
015 0 | 0
=========================================================================
*/
func TestKademliaCase4(t *testing.T) {
testKademliaCase(t,
"9a90fe3506277244549064b8c3276abb06284a199d9063a97331947f2b7da7f4",
"c19359eddef24b7be1a833b4475f212cd944263627a53f9ef4837d106c247730", "fc2b6fef99ef947f7e57c3df376891769e2a2fd83d2b8e634e0fc1e91eaa080c", "ecefc0e1a8ea7bb4b48c469e077401fce175dd75294255b96c4e54f6a2950a55", "bb7ce598efc056bba343cc2614aa3f67a575557561290b44c73a63f8f433f9f7", "55fbee6ca52dfd7f0be0db969ee8e524b654ab4f0cce7c05d83887d7d2a15460", "afa852b6b319998c6a283cc0c82d2f5b8e9410075d7700f3012761f1cfbd0f76", "36c370cfb63f2087971ba6e58d7585b04e16b8f0da335efb91554c2dd8fe191c", "6be41e029985edebc901fb77fc4fb65516b6d85086e2a98bfa3159c99391e585", "dd3cfc72ea553e7d2b28f0037a65646b30955b929d29ba4c40f4a2a811248e77", "da3a8f18e09c7b0ca235c4e33e1441a5188f1df023138bf207753ee63e768f7d", "de9e3ab4dc572d54a2d4b878329fd832bb51a149f4ce167316eeb177b61e7e01", "4e6c1ecde6ed917706257fe020a1d02d2e9d87fca4c85f0f7b132491008c5032", "72ef04b77a070e13463b3529dd312bcacfb7a12d20dc597f5ec3de0501e9b834", "3fef57186675d524ab8bb1f54ba8cb68610babca1247c0c46dbb60aed003c69d", "1d8e6b71f7a052865d6558d4ba44ad5fab7b908cc1badf5766822e1c20d0d823", "6be2f2b4ffa173014d4ec7df157d289744a2bda54bb876b264ccfa898a0da315", "b0ba3fff8643f9985c744327b0c4c869763509fd5da2de9a80a4a0a082021255", "9ccf40b9406ba2e6567101fb9b4e5334a9ec74263eff47267da266ba45e6c158", "d7347f02c180a448e60f73931845062ce00048750b584790278e9c93ef31ad81", "b68c6359a22b3bee6fecb8804311cfd816648ea31d530c9fb48e477e029d707a", "0d668a18ad7c2820214df6df95a6c855ce19fb1cb765f8ca620e45db76686d37", "3fbd2663bff65533246f1fabb9f38086854c6218aeb3dc9ac6ac73d4f0988f91", "949aa5719ca846052bfaa1b38c97b6eca3df3e24c0e0630042c6bccafbb4cdb5", "77b8a2b917bef5d54f3792183b014cca7798f713ff14fe0b2ac79b4c9f6f996d", "17e853cbd8dc00cba3cd9ffeb36f26a9f41a0eb92f80b62c2cda16771c935388", "5f682ed7a8cf2f98387c3def7c97f9f05ae39e39d393eeca3cf621268d6347f8", "ad77487eaf11fd8084ba4517a51766eb0e5b77dd3492dfa79aa3a2802fb29d20", "d247cfcacf9a8200ebaddf639f8c926ab0a001abe682f40df3785e80ed124e91", "195589442e11907eede1ee6524157f1125f68399f3170c835ff81c603b069f6c", "5b5ca0a67f3c54e7d3a6a862ef56168ec9ed1f4945e6c24de6d336b2be2e6f8c", "56430e4caa253015f1f998dce4a48a88af1953f68e94eca14f53074ae9c3e467", "0b1eed6a5bf612d1d8e08f5c546f3d12e838568fd3aa43ed4c537f10c65545d6", "7058db19a56dfff01988ac4a62e1310597f9c8d7ebde6890dadabf047d722d39", "b847380d6888ff7cd11402d086b19eccc40950b52c9d67e73cb4f8462f5df078", "df6c048419a2290ab546d527e9eeba349e7f7e1759bafe4adac507ce60ef9670", "91fc5b4b24fc3fbfea7f9a3d0f0437cb5733c0c2345d8bdffd7048d6e3b8a37b", "957d8ea51b37523952b6f5ae95462fcd4aed1483ef32cc80b69580aaeee03606", "efa82e4e91ad9ab781977400e9ac0bb9de7389aaedebdae979b73d1d3b8d72b0", "7400c9f3f3fc0cc6fe8cc37ab24b9771f44e9f78be913f73cd35fc4be030d6bd", "9bb28f4122d61f7bb56fe27ef706159fb802fef0f5de9dfa32c9c5b3183235f1", "40a8de6e98953498b806614532ea4abf8b99ad7f9719fb68203a6eae2efa5b2a", "412de0b218b8f7dcacc9205cd16ffb4eca5b838f46a2f4f9f534026061a47308", "17f56ecad51075080680ad9faa0fd8946b824d3296ddb20be07f9809fe8d1c5a", "fffd4e7ae885a41948a342b6647955a7ec8a8039039f510cff467ef597675457", "35e78e11b5ac46a29dd04ab0043136c3291f4ca56cb949ace33111ed56395463", "94824fc80230af82077c83bfc01dc9675b1f9d3d538b1e5f41c21ac753598691", "fa470ae314ca3fce493f21b423eef2a49522e09126f6f2326fa3c9cac0b344f7", "7078860b5b621b21ac7b95f9fc4739c8235ce5066a8b9bd7d938146a34fa88ec", "eea53560f0428bfd2eca4f86a5ce9dec5ff1309129a975d73465c1c9e9da71d1",
)
}
/*
The regression test for the following invalid kademlia edge case.
Addresses used in this test are discovered as part of the simulation network
in higher level tests for streaming. They were generated randomly.
=========================================================================
Mon Apr 9 19:25:18 UTC 2018 KΛÐΞMLIΛ hive: queen's address: 5dd5c7
population: 13 (49), NeighbourhoodSize: 2, MinBinSize: 2, MaxBinSize: 4
000 2 e528 fad0 | 22 fad0 (0) e528 (0) e3bb (0) ed13 (0)
001 3 3f30 18e0 1dd3 | 7 3f30 (0) 23db (0) 10b6 (0) 18e0 (0)
002 4 7c54 7804 61e4 60f9 | 10 61e4 (0) 60f9 (0) 636c (0) 7186 (0)
003 2 40ae 4bae | 5 4bae (0) 4d5c (0) 403a (0) 40ae (0)
004 0 | 0
005 0 | 3 5808 (0) 5a0e (0) 5bdb (0)
============ DEPTH: 6 ==========================================
006 2 5f14 5f61 | 2 5f14 (0) 5f61 (0)
007 0 | 0
008 0 | 0
009 0 | 0
010 0 | 0
011 0 | 0
012 0 | 0
013 0 | 0
014 0 | 0
015 0 | 0
=========================================================================
*/
func TestKademliaCase5(t *testing.T) {
testKademliaCase(t,
"5dd5c77dd9006a800478fcebb02d48d4036389e7d3c8f6a83b97dbad13f4c0a9",
"78fafa0809929a1279ece089a51d12457c2d8416dff859aeb2ccc24bb50df5ec", "1dd39b1257e745f147cbbc3cadd609ccd6207c41056dbc4254bba5d2527d3ee5", "5f61dd66d4d94aec8fcc3ce0e7885c7edf30c43143fa730e2841c5d28e3cd081", "8aa8b0472cb351d967e575ad05c4b9f393e76c4b01ef4b3a54aac5283b78abc9", "4502f385152a915b438a6726ce3ea9342e7a6db91a23c2f6bee83a885ed7eb82", "718677a504249db47525e959ef1784bed167e1c46f1e0275b9c7b588e28a3758", "7c54c6ed1f8376323896ed3a4e048866410de189e9599dd89bf312ca4adb96b5", "18e03bd3378126c09e799a497150da5c24c895aedc84b6f0dbae41fc4bac081a", "23db76ac9e6e58d9f5395ca78252513a7b4118b4155f8462d3d5eec62486cadc", "40ae0e8f065e96c7adb7fa39505136401f01780481e678d718b7f6dbb2c906ec", "c1539998b8bae19d339d6bbb691f4e9daeb0e86847545229e80fe0dffe716e92", "ed139d73a2699e205574c08722ca9f030ad2d866c662f1112a276b91421c3cb9", "5bdb19584b7a36d09ca689422ef7e6bb681b8f2558a6b2177a8f7c812f631022", "636c9de7fe234ffc15d67a504c69702c719f626c17461d3f2918e924cd9d69e2", "de4455413ff9335c440d52458c6544191bd58a16d85f700c1de53b62773064ea", "de1963310849527acabc7885b6e345a56406a8f23e35e436b6d9725e69a79a83", "a80a50a467f561210a114cba6c7fb1489ed43a14d61a9edd70e2eb15c31f074d", "7804f12b8d8e6e4b375b242058242068a3809385e05df0e64973cde805cf729c", "60f9aa320c02c6f2e6370aa740cf7cea38083fa95fca8c99552cda52935c1520", "d8da963602390f6c002c00ce62a84b514edfce9ebde035b277a957264bb54d21", "8463d93256e026fe436abad44697152b9a56ac8e06a0583d318e9571b83d073c", "9a3f78fcefb9a05e40a23de55f6153d7a8b9d973ede43a380bf46bb3b3847de1", "e3bb576f4b3760b9ca6bff59326f4ebfc4a669d263fb7d67ab9797adea54ed13", "4d5cdbd6dcca5bdf819a0fe8d175dc55cc96f088d37462acd5ea14bc6296bdbe", "5a0ed28de7b5258c727cb85447071c74c00a5fbba9e6bc0393bc51944d04ab2a", "61e4ddb479c283c638f4edec24353b6cc7a3a13b930824aad016b0996ca93c47", "7e3610868acf714836cafaaa7b8c009a9ac6e3a6d443e5586cf661530a204ee2", "d74b244d4345d2c86e30a097105e4fb133d53c578320285132a952cdaa64416e", "cfeed57d0f935bfab89e3f630a7c97e0b1605f0724d85a008bbfb92cb47863a8", "580837af95055670e20d494978f60c7f1458dc4b9e389fc7aa4982b2aca3bce3", "df55c0c49e6c8a83d82dfa1c307d3bf6a20e18721c80d8ec4f1f68dc0a137ced", "5f149c51ce581ba32a285439a806c063ced01ccd4211cd024e6a615b8f216f95", "1eb76b00aeb127b10dd1b7cd4c3edeb4d812b5a658f0feb13e85c4d2b7c6fe06", "7a56ba7c3fb7cbfb5561a46a75d95d7722096b45771ec16e6fa7bbfab0b35dfe", "4bae85ad88c28470f0015246d530adc0cd1778bdd5145c3c6b538ee50c4e04bd", "afd1892e2a7145c99ec0ebe9ded0d3fec21089b277a68d47f45961ec5e39e7e0", "953138885d7b36b0ef79e46030f8e61fd7037fbe5ce9e0a94d728e8c8d7eab86", "de761613ef305e4f628cb6bf97d7b7dc69a9d513dc233630792de97bcda777a6", "3f3087280063d09504c084bbf7fdf984347a72b50d097fd5b086ffabb5b3fb4c", "7d18a94bb1ebfdef4d3e454d2db8cb772f30ca57920dd1e402184a9e598581a0", "a7d6fbdc9126d9f10d10617f49fb9f5474ffe1b229f76b7dd27cebba30eccb5d", "fad0246303618353d1387ec10c09ee991eb6180697ed3470ed9a6b377695203d", "1cf66e09ea51ee5c23df26615a9e7420be2ac8063f28f60a3bc86020e94fe6f3", "8269cdaa153da7c358b0b940791af74d7c651cd4d3f5ed13acfe6d0f2c539e7f", "90d52eaaa60e74bf1c79106113f2599471a902d7b1c39ac1f55b20604f453c09", "9788fd0c09190a3f3d0541f68073a2f44c2fcc45bb97558a7c319f36c25a75b3", "10b68fc44157ecfdae238ee6c1ce0333f906ad04d1a4cb1505c8e35c3c87fbb0", "e5284117fdf3757920475c786e0004cb00ba0932163659a89b36651a01e57394", "403ad51d911e113dcd5f9ff58c94f6d278886a2a4da64c3ceca2083282c92de3",
)
}
func newTestDiscoveryPeer(addr pot.Address, kad *Kademlia) *Peer { func newTestDiscoveryPeer(addr pot.Address, kad *Kademlia) *Peer {
rw := &p2p.MsgPipeRW{} rw := &p2p.MsgPipeRW{}
p := p2p.NewPeer(enode.ID{}, "foo", []p2p.Cap{}) p := p2p.NewPeer(enode.ID{}, "foo", []p2p.Cap{})

View file

@ -76,13 +76,12 @@ func TestNetworkID(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error setting up network: %v", err) t.Fatalf("Error setting up network: %v", err)
} }
defer func() {
//shutdown the snapshot network
log.Trace("Shutting down network")
net.Shutdown()
}()
//let's sleep to ensure all nodes are connected //let's sleep to ensure all nodes are connected
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
// shutdown the the network to avoid race conditions
// on accessing kademlias global map while network nodes
// are accepting messages
net.Shutdown()
//for each group sharing the same network ID... //for each group sharing the same network ID...
for _, netIDGroup := range nodeMap { for _, netIDGroup := range nodeMap {
log.Trace("netIDGroup size", "size", len(netIDGroup)) log.Trace("netIDGroup size", "size", len(netIDGroup))

View file

@ -67,6 +67,7 @@ type BzzConfig struct {
HiveParams *HiveParams HiveParams *HiveParams
NetworkID uint64 NetworkID uint64
LightNode bool LightNode bool
BootnodeMode bool
} }
// Bzz is the swarm protocol bundle // Bzz is the swarm protocol bundle
@ -87,7 +88,7 @@ type Bzz struct {
// * overlay driver // * overlay driver
// * peer store // * peer store
func NewBzz(config *BzzConfig, kad *Kademlia, store state.Store, streamerSpec *protocols.Spec, streamerRun func(*BzzPeer) error) *Bzz { func NewBzz(config *BzzConfig, kad *Kademlia, store state.Store, streamerSpec *protocols.Spec, streamerRun func(*BzzPeer) error) *Bzz {
return &Bzz{ bzz := &Bzz{
Hive: NewHive(config.HiveParams, kad, store), Hive: NewHive(config.HiveParams, kad, store),
NetworkID: config.NetworkID, NetworkID: config.NetworkID,
LightNode: config.LightNode, LightNode: config.LightNode,
@ -96,6 +97,13 @@ func NewBzz(config *BzzConfig, kad *Kademlia, store state.Store, streamerSpec *p
streamerRun: streamerRun, streamerRun: streamerRun,
streamerSpec: streamerSpec, streamerSpec: streamerSpec,
} }
if config.BootnodeMode {
bzz.streamerRun = nil
bzz.streamerSpec = nil
}
return bzz
} }
// UpdateLocalAddr updates underlayaddress of the running node // UpdateLocalAddr updates underlayaddress of the running node
@ -168,7 +176,7 @@ func (b *Bzz) APIs() []rpc.API {
func (b *Bzz) RunProtocol(spec *protocols.Spec, run func(*BzzPeer) error) func(*p2p.Peer, p2p.MsgReadWriter) error { func (b *Bzz) RunProtocol(spec *protocols.Spec, run func(*BzzPeer) error) func(*p2p.Peer, p2p.MsgReadWriter) error {
return func(p *p2p.Peer, rw p2p.MsgReadWriter) error { return func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
// wait for the bzz protocol to perform the handshake // wait for the bzz protocol to perform the handshake
handshake, _ := b.GetHandshake(p.ID()) handshake, _ := b.GetOrCreateHandshake(p.ID())
defer b.removeHandshake(p.ID()) defer b.removeHandshake(p.ID())
select { select {
case <-handshake.done: case <-handshake.done:
@ -213,7 +221,7 @@ func (b *Bzz) performHandshake(p *protocols.Peer, handshake *HandshakeMsg) error
// runBzz is the p2p protocol run function for the bzz base protocol // runBzz is the p2p protocol run function for the bzz base protocol
// that negotiates the bzz handshake // that negotiates the bzz handshake
func (b *Bzz) runBzz(p *p2p.Peer, rw p2p.MsgReadWriter) error { func (b *Bzz) runBzz(p *p2p.Peer, rw p2p.MsgReadWriter) error {
handshake, _ := b.GetHandshake(p.ID()) handshake, _ := b.GetOrCreateHandshake(p.ID())
if !<-handshake.init { if !<-handshake.init {
return fmt.Errorf("%08x: bzz already started on peer %08x", b.localAddr.Over()[:4], p.ID().Bytes()[:4]) return fmt.Errorf("%08x: bzz already started on peer %08x", b.localAddr.Over()[:4], p.ID().Bytes()[:4])
} }
@ -303,7 +311,7 @@ func (b *Bzz) removeHandshake(peerID enode.ID) {
} }
// GetHandshake returns the bzz handhake that the remote peer with peerID sent // GetHandshake returns the bzz handhake that the remote peer with peerID sent
func (b *Bzz) GetHandshake(peerID enode.ID) (*HandshakeMsg, bool) { func (b *Bzz) GetOrCreateHandshake(peerID enode.ID) (*HandshakeMsg, bool) {
b.mtx.Lock() b.mtx.Lock()
defer b.mtx.Unlock() defer b.mtx.Unlock()
handshake, found := b.handshakes[peerID] handshake, found := b.handshakes[peerID]

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"os" "os"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -82,7 +83,7 @@ func newBzzBaseTester(t *testing.T, n int, addr *BzzAddr, spec *protocols.Spec,
return srv(&BzzPeer{Peer: protocols.NewPeer(p, rw, spec), BzzAddr: NewAddr(p.Node())}) return srv(&BzzPeer{Peer: protocols.NewPeer(p, rw, spec), BzzAddr: NewAddr(p.Node())})
} }
s := p2ptest.NewProtocolTester(t, addr.ID(), n, protocol) s := p2ptest.NewProtocolTester(addr.ID(), n, protocol)
for _, node := range s.Nodes { for _, node := range s.Nodes {
cs[node.ID().String()] = make(chan bool) cs[node.ID().String()] = make(chan bool)
@ -115,9 +116,9 @@ func newBzz(addr *BzzAddr, lightNode bool) *Bzz {
return bzz return bzz
} }
func newBzzHandshakeTester(t *testing.T, n int, addr *BzzAddr, lightNode bool) *bzzTester { func newBzzHandshakeTester(n int, addr *BzzAddr, lightNode bool) *bzzTester {
bzz := newBzz(addr, lightNode) bzz := newBzz(addr, lightNode)
pt := p2ptest.NewProtocolTester(t, addr.ID(), n, bzz.runBzz) pt := p2ptest.NewProtocolTester(addr.ID(), n, bzz.runBzz)
return &bzzTester{ return &bzzTester{
addr: addr, addr: addr,
@ -165,7 +166,7 @@ func correctBzzHandshake(addr *BzzAddr, lightNode bool) *HandshakeMsg {
func TestBzzHandshakeNetworkIDMismatch(t *testing.T) { func TestBzzHandshakeNetworkIDMismatch(t *testing.T) {
lightNode := false lightNode := false
addr := RandomAddr() addr := RandomAddr()
s := newBzzHandshakeTester(t, 1, addr, lightNode) s := newBzzHandshakeTester(1, addr, lightNode)
node := s.Nodes[0] node := s.Nodes[0]
err := s.testHandshake( err := s.testHandshake(
@ -182,7 +183,7 @@ func TestBzzHandshakeNetworkIDMismatch(t *testing.T) {
func TestBzzHandshakeVersionMismatch(t *testing.T) { func TestBzzHandshakeVersionMismatch(t *testing.T) {
lightNode := false lightNode := false
addr := RandomAddr() addr := RandomAddr()
s := newBzzHandshakeTester(t, 1, addr, lightNode) s := newBzzHandshakeTester(1, addr, lightNode)
node := s.Nodes[0] node := s.Nodes[0]
err := s.testHandshake( err := s.testHandshake(
@ -199,7 +200,7 @@ func TestBzzHandshakeVersionMismatch(t *testing.T) {
func TestBzzHandshakeSuccess(t *testing.T) { func TestBzzHandshakeSuccess(t *testing.T) {
lightNode := false lightNode := false
addr := RandomAddr() addr := RandomAddr()
s := newBzzHandshakeTester(t, 1, addr, lightNode) s := newBzzHandshakeTester(1, addr, lightNode)
node := s.Nodes[0] node := s.Nodes[0]
err := s.testHandshake( err := s.testHandshake(
@ -224,7 +225,8 @@ func TestBzzHandshakeLightNode(t *testing.T) {
for _, test := range lightNodeTests { for _, test := range lightNodeTests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
randomAddr := RandomAddr() randomAddr := RandomAddr()
pt := newBzzHandshakeTester(t, 1, randomAddr, false) pt := newBzzHandshakeTester(1, randomAddr, false)
node := pt.Nodes[0] node := pt.Nodes[0]
addr := NewAddr(node) addr := NewAddr(node)
@ -237,8 +239,14 @@ func TestBzzHandshakeLightNode(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if pt.bzz.handshakes[node.ID()].LightNode != test.lightNode { select {
t.Fatalf("peer LightNode flag is %v, should be %v", pt.bzz.handshakes[node.ID()].LightNode, test.lightNode)
case <-pt.bzz.handshakes[node.ID()].done:
if pt.bzz.handshakes[node.ID()].LightNode != test.lightNode {
t.Fatalf("peer LightNode flag is %v, should be %v", pt.bzz.handshakes[node.ID()].LightNode, test.lightNode)
}
case <-time.After(10 * time.Second):
t.Fatal("test timeout")
} }
}) })
} }

View file

@ -28,8 +28,6 @@ import (
) )
func TestWaitTillHealthy(t *testing.T) { func TestWaitTillHealthy(t *testing.T) {
t.Skip("WaitTillHealthy depends on discovery, which relies on a reliable SuggestPeer, which is not reliable")
sim := New(map[string]ServiceFunc{ sim := New(map[string]ServiceFunc{
"bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { "bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) {
addr := network.NewAddr(ctx.Config.Node()) addr := network.NewAddr(ctx.Config.Node())

View file

@ -18,16 +18,12 @@ package discovery
import ( import (
"context" "context"
"encoding/json"
"errors"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/rand"
"os" "os"
"path" "path"
"strings" "strings"
"sync"
"testing" "testing"
"time" "time"
@ -86,12 +82,10 @@ func getDbStore(nodeID string) (*state.DBStore, error) {
} }
var ( var (
nodeCount = flag.Int("nodes", 10, "number of nodes to create (default 10)") nodeCount = flag.Int("nodes", 32, "number of nodes to create (default 32)")
initCount = flag.Int("conns", 1, "number of originally connected peers (default 1)") initCount = flag.Int("conns", 1, "number of originally connected peers (default 1)")
snapshotFile = flag.String("snapshot", "", "path to create snapshot file in") loglevel = flag.Int("loglevel", 3, "verbosity of logs")
loglevel = flag.Int("loglevel", 3, "verbosity of logs") rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs")
rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs")
serviceOverride = flag.String("services", "", "remove or add services to the node snapshot; prefix with \"+\" to add, \"-\" to remove; example: +pss,-discovery")
) )
func init() { func init() {
@ -157,7 +151,6 @@ func testDiscoverySimulationSimAdapter(t *testing.T, nodes, conns int) {
} }
func testDiscoverySimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) { func testDiscoverySimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) {
t.Skip("discovery tests depend on suggestpeer, which is unreliable after kademlia depth change.")
startedAt := time.Now() startedAt := time.Now()
result, err := discoverySimulation(nodes, conns, adapter) result, err := discoverySimulation(nodes, conns, adapter)
if err != nil { if err != nil {
@ -185,7 +178,6 @@ func testDiscoverySimulation(t *testing.T, nodes, conns int, adapter adapters.No
} }
func testDiscoveryPersistenceSimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) map[int][]byte { func testDiscoveryPersistenceSimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) map[int][]byte {
t.Skip("discovery tests depend on suggestpeer, which is unreliable after kademlia depth change.")
persistenceEnabled = true persistenceEnabled = true
discoveryEnabled = true discoveryEnabled = true
@ -247,25 +239,14 @@ func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simul
action := func(ctx context.Context) error { action := func(ctx context.Context) error {
return nil return nil
} }
wg := sync.WaitGroup{}
for i := range ids { for i := range ids {
// collect the overlay addresses, to // collect the overlay addresses, to
addrs = append(addrs, ids[i].Bytes()) addrs = append(addrs, ids[i].Bytes())
for j := 0; j < conns; j++ {
var k int
if j == 0 {
k = (i + 1) % len(ids)
} else {
k = rand.Intn(len(ids))
}
wg.Add(1)
go func(i, k int) {
defer wg.Done()
net.Connect(ids[i], ids[k])
}(i, k)
}
} }
wg.Wait() err := net.ConnectNodesChain(nil)
if err != nil {
return nil, err
}
log.Debug(fmt.Sprintf("nodes: %v", len(addrs))) log.Debug(fmt.Sprintf("nodes: %v", len(addrs)))
// construct the peer pot, so that kademlia health can be checked // construct the peer pot, so that kademlia health can be checked
ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs) ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs)
@ -286,10 +267,10 @@ func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simul
} }
healthy := &network.Health{} healthy := &network.Health{}
if err := client.Call(&healthy, "hive_healthy", ppmap); err != nil { if err := client.Call(&healthy, "hive_healthy", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil {
return false, fmt.Errorf("error getting node health: %s", err) return false, fmt.Errorf("error getting node health: %s", err)
} }
log.Info(fmt.Sprintf("node %4s healthy: connected nearest neighbours: %v, know nearest neighbours: %v,\n\n%v", id, healthy.ConnectNN, healthy.KnowNN, healthy.Hive)) log.Debug(fmt.Sprintf("node %4s healthy: connected nearest neighbours: %v, know nearest neighbours: %v,\n\n%v", id, healthy.ConnectNN, healthy.KnowNN, healthy.Hive))
return healthy.KnowNN && healthy.ConnectNN, nil return healthy.KnowNN && healthy.ConnectNN, nil
} }
@ -309,40 +290,6 @@ func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simul
if result.Error != nil { if result.Error != nil {
return result, nil return result, nil
} }
if *snapshotFile != "" {
var err error
var snap *simulations.Snapshot
if len(*serviceOverride) > 0 {
var addServices []string
var removeServices []string
for _, osvc := range strings.Split(*serviceOverride, ",") {
if strings.Index(osvc, "+") == 0 {
addServices = append(addServices, osvc[1:])
} else if strings.Index(osvc, "-") == 0 {
removeServices = append(removeServices, osvc[1:])
} else {
panic("stick to the rules, you know what they are")
}
}
snap, err = net.SnapshotWithServices(addServices, removeServices)
} else {
snap, err = net.Snapshot()
}
if err != nil {
return nil, errors.New("no shapshot dude")
}
jsonsnapshot, err := json.Marshal(snap)
if err != nil {
return nil, fmt.Errorf("corrupt json snapshot: %v", err)
}
log.Info("writing snapshot", "file", *snapshotFile)
err = ioutil.WriteFile(*snapshotFile, jsonsnapshot, 0755)
if err != nil {
return nil, err
}
}
return result, nil return result, nil
} }
@ -405,7 +352,7 @@ func discoveryPersistenceSimulation(nodes, conns int, adapter adapters.NodeAdapt
healthy := &network.Health{} healthy := &network.Health{}
addr := id.String() addr := id.String()
ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs) ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs)
if err := client.Call(&healthy, "hive_healthy", ppmap); err != nil { if err := client.Call(&healthy, "hive_healthy", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil {
return fmt.Errorf("error getting node health: %s", err) return fmt.Errorf("error getting node health: %s", err)
} }
@ -457,23 +404,7 @@ func discoveryPersistenceSimulation(nodes, conns int, adapter adapters.NodeAdapt
return nil return nil
} }
//connects in a chain net.ConnectNodesChain(nil)
wg := sync.WaitGroup{}
//connects in a ring
for i := range ids {
for j := 1; j <= conns; j++ {
k := (i + j) % len(ids)
if k == i {
k = (k + 1) % len(ids)
}
wg.Add(1)
go func(i, k int) {
defer wg.Done()
net.Connect(ids[i], ids[k])
}(i, k)
}
}
wg.Wait()
log.Debug(fmt.Sprintf("nodes: %v", len(addrs))) log.Debug(fmt.Sprintf("nodes: %v", len(addrs)))
// construct the peer pot, so that kademlia health can be checked // construct the peer pot, so that kademlia health can be checked
check := func(ctx context.Context, id enode.ID) (bool, error) { check := func(ctx context.Context, id enode.ID) (bool, error) {
@ -494,7 +425,7 @@ func discoveryPersistenceSimulation(nodes, conns int, adapter adapters.NodeAdapt
healthy := &network.Health{} healthy := &network.Health{}
ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs) ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs)
if err := client.Call(&healthy, "hive_healthy", ppmap); err != nil { if err := client.Call(&healthy, "hive_healthy", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil {
return false, fmt.Errorf("error getting node health: %s", err) return false, fmt.Errorf("error getting node health: %s", err)
} }
log.Info(fmt.Sprintf("node %4s healthy: got nearest neighbours: %v, know nearest neighbours: %v", id, healthy.ConnectNN, healthy.KnowNN)) log.Info(fmt.Sprintf("node %4s healthy: got nearest neighbours: %v, know nearest neighbours: %v", id, healthy.ConnectNN, healthy.KnowNN))

View file

@ -43,7 +43,6 @@ var (
//It also provides a documentation on the steps needed by frontends //It also provides a documentation on the steps needed by frontends
//to use the simulations //to use the simulations
func TestOverlaySim(t *testing.T) { func TestOverlaySim(t *testing.T) {
t.Skip("Test is flaky, see: https://github.com/ethersphere/go-ethereum/issues/592")
//start the simulation //start the simulation
log.Info("Start simulation backend") log.Info("Start simulation backend")
//get the simulation networ; needed to subscribe for up events //get the simulation networ; needed to subscribe for up events

View file

@ -27,7 +27,6 @@ import (
"os" "os"
"strings" "strings"
"sync/atomic" "sync/atomic"
"testing"
"time" "time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -67,7 +66,7 @@ func init() {
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
} }
func newStreamerTester(t *testing.T, registryOptions *RegistryOptions) (*p2ptest.ProtocolTester, *Registry, *storage.LocalStore, func(), error) { func newStreamerTester(registryOptions *RegistryOptions) (*p2ptest.ProtocolTester, *Registry, *storage.LocalStore, func(), error) {
// setup // setup
addr := network.RandomAddr() // tested peers peer address addr := network.RandomAddr() // tested peers peer address
to := network.NewKademlia(addr.OAddr, network.NewKadParams()) to := network.NewKademlia(addr.OAddr, network.NewKadParams())
@ -102,7 +101,7 @@ func newStreamerTester(t *testing.T, registryOptions *RegistryOptions) (*p2ptest
streamer.Close() streamer.Close()
removeDataDir() removeDataDir()
} }
protocolTester := p2ptest.NewProtocolTester(t, addr.ID(), 1, streamer.runProtocol) protocolTester := p2ptest.NewProtocolTester(addr.ID(), 1, streamer.runProtocol)
err = waitForPeers(streamer, 1*time.Second, 1) err = waitForPeers(streamer, 1*time.Second, 1)
if err != nil { if err != nil {

View file

@ -255,8 +255,15 @@ func (d *Delivery) RequestFromPeers(ctx context.Context, req *network.Request) (
return true return true
} }
sp = d.getPeer(id) sp = d.getPeer(id)
// sp is nil, when we encounter a peer that is not registered for delivery, i.e. doesn't support the `stream` protocol
if sp == nil { if sp == nil {
//log.Warn("Delivery.RequestFromPeers: peer not found", "id", id) return true
}
// nodes that do not provide stream protocol
// should not be requested, e.g. bootnodes
if !p.HasCap("stream") {
// TODO: if we have no errors, delete this if
log.Error("Delivery.RequestFromPeers: peer doesn't have stream cap. we should have returned at sp == nil")
return true return true
} }
spID = &id spID = &id

View file

@ -48,7 +48,7 @@ func TestStreamerRetrieveRequest(t *testing.T) {
Retrieval: RetrievalClientOnly, Retrieval: RetrievalClientOnly,
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
} }
tester, streamer, _, teardown, err := newStreamerTester(t, regOpts) tester, streamer, _, teardown, err := newStreamerTester(regOpts)
defer teardown() defer teardown()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -97,7 +97,7 @@ func TestStreamerRetrieveRequest(t *testing.T) {
//Test requesting a chunk from a peer then issuing a "empty" OfferedHashesMsg (no hashes available yet) //Test requesting a chunk from a peer then issuing a "empty" OfferedHashesMsg (no hashes available yet)
//Should time out as the peer does not have the chunk (no syncing happened previously) //Should time out as the peer does not have the chunk (no syncing happened previously)
func TestStreamerUpstreamRetrieveRequestMsgExchangeWithoutStore(t *testing.T) { func TestStreamerUpstreamRetrieveRequestMsgExchangeWithoutStore(t *testing.T) {
tester, streamer, _, teardown, err := newStreamerTester(t, &RegistryOptions{ tester, streamer, _, teardown, err := newStreamerTester(&RegistryOptions{
Retrieval: RetrievalEnabled, Retrieval: RetrievalEnabled,
Syncing: SyncingDisabled, //do no syncing Syncing: SyncingDisabled, //do no syncing
}) })
@ -169,7 +169,7 @@ func TestStreamerUpstreamRetrieveRequestMsgExchangeWithoutStore(t *testing.T) {
// upstream request server receives a retrieve Request and responds with // upstream request server receives a retrieve Request and responds with
// offered hashes or delivery if skipHash is set to true // offered hashes or delivery if skipHash is set to true
func TestStreamerUpstreamRetrieveRequestMsgExchange(t *testing.T) { func TestStreamerUpstreamRetrieveRequestMsgExchange(t *testing.T) {
tester, streamer, localStore, teardown, err := newStreamerTester(t, &RegistryOptions{ tester, streamer, localStore, teardown, err := newStreamerTester(&RegistryOptions{
Retrieval: RetrievalEnabled, Retrieval: RetrievalEnabled,
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
}) })
@ -285,7 +285,7 @@ func TestRequestFromPeers(t *testing.T) {
addr := network.RandomAddr() addr := network.RandomAddr()
to := network.NewKademlia(addr.OAddr, network.NewKadParams()) to := network.NewKademlia(addr.OAddr, network.NewKadParams())
delivery := NewDelivery(to, nil) delivery := NewDelivery(to, nil)
protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", nil), nil, nil) protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", []p2p.Cap{{Name: "stream"}}), nil, nil)
peer := network.NewPeer(&network.BzzPeer{ peer := network.NewPeer(&network.BzzPeer{
BzzAddr: network.RandomAddr(), BzzAddr: network.RandomAddr(),
LightNode: false, LightNode: false,
@ -359,7 +359,7 @@ func TestRequestFromPeersWithLightNode(t *testing.T) {
} }
func TestStreamerDownstreamChunkDeliveryMsgExchange(t *testing.T) { func TestStreamerDownstreamChunkDeliveryMsgExchange(t *testing.T) {
tester, streamer, localStore, teardown, err := newStreamerTester(t, &RegistryOptions{ tester, streamer, localStore, teardown, err := newStreamerTester(&RegistryOptions{
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
}) })

Some files were not shown because too many files have changed in this diff Show more