diff options
-rw-r--r-- | Godeps/Godeps.json | 2 | ||||
-rw-r--r-- | Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml | 3 | ||||
-rw-r--r-- | Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml | 1 | ||||
-rw-r--r-- | Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go | 2 | ||||
-rw-r--r-- | Makefile | 53 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | build/ci.go | 32 | ||||
-rwxr-xr-x | build/flags.sh | 22 | ||||
-rw-r--r-- | containers/docker/develop-alpine/Dockerfile | 11 | ||||
-rw-r--r-- | containers/docker/master-alpine/Dockerfile | 10 | ||||
-rw-r--r-- | core/vm/contracts.go | 4 | ||||
-rw-r--r-- | eth/downloader/downloader.go | 9 | ||||
-rw-r--r-- | internal/web3ext/web3ext.go | 5 | ||||
-rw-r--r-- | node/api.go | 16 | ||||
-rw-r--r-- | p2p/dial.go | 5 | ||||
-rw-r--r-- | p2p/server.go | 20 | ||||
-rw-r--r-- | p2p/server_test.go | 2 |
17 files changed, 129 insertions, 70 deletions
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index edfa8ad1e..6c4e10412 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -112,7 +112,7 @@ }, { "ImportPath": "github.com/rjeczalik/notify", - "Rev": "5dd6205716539662f8f14ab513552b41eab69d5d" + "Rev": "f627deca7a510d96f0ef9388f2d0e8b16d21f87f" }, { "ImportPath": "github.com/robertkrimen/otto", diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml b/Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml index 4f1f5f25e..c92863d50 100644 --- a/Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml +++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml @@ -21,10 +21,9 @@ env: - PATH=$HOME/bin:$PATH install: - - go get golang.org/x/tools/cmd/vet - go get -t -v ./... script: - - go tool vet -all . + - "(go version | grep -q 1.4) || go tool vet -all ." - go install $GOFLAGS ./... - go test -v -race $GOFLAGS ./... diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml b/Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml index 16d09ac3b..8e762d05c 100644 --- a/Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml +++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml @@ -11,7 +11,6 @@ environment: install: - go version - - go get golang.org/x/tools/cmd/vet - go get -v -t ./... build_script: diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go index 54334912e..9062c17c7 100644 --- a/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go +++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go @@ -133,7 +133,7 @@ func (w *watch) Dispatch(ev []FSEvent) { ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev)) if ev[i].Flags&failure != 0 { // TODO(rjeczalik): missing error handling - panic("unhandled error: " + Event(ev[i].Flags).String()) + continue } if !strings.HasPrefix(ev[i].Path, w.path) { continue @@ -2,7 +2,7 @@ # with Go source code. If you know what GOPATH is then you probably # don't need to bother with make. -.PHONY: geth geth-cross evm all test xgo clean +.PHONY: geth geth-cross evm all test clean .PHONY: geth-linux geth-linux-386 geth-linux-amd64 .PHONY: geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64 .PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64 @@ -33,9 +33,6 @@ clean: # Cross Compilation Targets (xgo) -xgo: - build/env.sh go get github.com/karalabe/xgo - geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios @echo "Full cross compilation done:" @ls -ld $(GOBIN)/geth-* @@ -44,13 +41,13 @@ geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm @echo "Linux cross compilation done:" @ls -ld $(GOBIN)/geth-linux-* -geth-linux-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth +geth-linux-386: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/386 -v ./cmd/geth @echo "Linux 386 cross compilation done:" @ls -ld $(GOBIN)/geth-linux-* | grep 386 -geth-linux-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/geth +geth-linux-amd64: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/amd64 -v ./cmd/geth @echo "Linux amd64 cross compilation done:" @ls -ld $(GOBIN)/geth-linux-* | grep amd64 @@ -58,23 +55,23 @@ geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-ar @echo "Linux ARM cross compilation done:" @ls -ld $(GOBIN)/geth-linux-* | grep arm -geth-linux-arm-5: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/geth +geth-linux-arm-5: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v ./cmd/geth @echo "Linux ARMv5 cross compilation done:" @ls -ld $(GOBIN)/geth-linux-* | grep arm-5 -geth-linux-arm-6: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/geth +geth-linux-arm-6: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v ./cmd/geth @echo "Linux ARMv6 cross compilation done:" @ls -ld $(GOBIN)/geth-linux-* | grep arm-6 -geth-linux-arm-7: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/geth +geth-linux-arm-7: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v ./cmd/geth @echo "Linux ARMv7 cross compilation done:" @ls -ld $(GOBIN)/geth-linux-* | grep arm-7 -geth-linux-arm64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/geth +geth-linux-arm64: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v ./cmd/geth @echo "Linux ARM64 cross compilation done:" @ls -ld $(GOBIN)/geth-linux-* | grep arm64 @@ -82,13 +79,13 @@ geth-darwin: geth-darwin-386 geth-darwin-amd64 @echo "Darwin cross compilation done:" @ls -ld $(GOBIN)/geth-darwin-* -geth-darwin-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/geth +geth-darwin-386: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/386 -v ./cmd/geth @echo "Darwin 386 cross compilation done:" @ls -ld $(GOBIN)/geth-darwin-* | grep 386 -geth-darwin-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/geth +geth-darwin-amd64: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/amd64 -v ./cmd/geth @echo "Darwin amd64 cross compilation done:" @ls -ld $(GOBIN)/geth-darwin-* | grep amd64 @@ -96,22 +93,22 @@ geth-windows: geth-windows-386 geth-windows-amd64 @echo "Windows cross compilation done:" @ls -ld $(GOBIN)/geth-windows-* -geth-windows-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/geth +geth-windows-386: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/386 -v ./cmd/geth @echo "Windows 386 cross compilation done:" @ls -ld $(GOBIN)/geth-windows-* | grep 386 -geth-windows-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/geth +geth-windows-amd64: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/amd64 -v ./cmd/geth @echo "Windows amd64 cross compilation done:" @ls -ld $(GOBIN)/geth-windows-* | grep amd64 -geth-android: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v $(shell build/flags.sh) ./cmd/geth +geth-android: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v ./cmd/geth @echo "Android cross compilation done:" @ls -ld $(GOBIN)/geth-android-* -geth-ios: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/framework -v $(shell build/flags.sh) ./cmd/geth +geth-ios: + build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/framework -v ./cmd/geth @echo "iOS framework cross compilation done:" @ls -ld $(GOBIN)/geth-ios-* @@ -50,7 +50,7 @@ The go-ethereum project comes with several wrappers/executables found in the `cm | `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. | | `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. | | `disasm` | Bytecode disassembler to convert EVM (Ethereum Virtual Machine) bytecode into more user friendly assembly-like opcodes (e.g. `echo "6001" | disasm`). For details on the individual opcodes, please see pages 22-30 of the [Ethereum Yellow Paper](http://gavwood.com/paper.pdf). | -| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow insolated, fine graned debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). | +| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow insolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). | | `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. | | `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | diff --git a/build/ci.go b/build/ci.go index 33d97c182..3011a6976 100644 --- a/build/ci.go +++ b/build/ci.go @@ -28,6 +28,7 @@ Available commands are: archive [ -type zip|tar ] -- archives build artefacts importkeys -- imports signing keys from env debsrc [ -sign key-id ] [ -upload dest ] -- creates a debian source package + xgo [ options ] -- cross builds according to options For all commands, -n prevents execution of external programs (dry run mode). @@ -121,6 +122,8 @@ func main() { doDebianSource(os.Args[2:]) case "travis-debsrc": doTravisDebianSource(os.Args[2:]) + case "xgo": + doXgo(os.Args[2:]) default: log.Fatal("unknown command ", os.Args[1]) } @@ -463,3 +466,32 @@ func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) { return pkgdir } + +// Cross compilation + +func doXgo(cmdline []string) { + // Make sure xgo is available for cross compilation + gogetxgo := goTool("get", "github.com/karalabe/xgo") + build.MustRun(gogetxgo) + + // Execute the actual cross compilation + pkg := cmdline[len(cmdline)-1] + args := append(cmdline[:len(cmdline)-1], makeBuildFlags("")...) + + build.MustRun(xgoTool(append(args, pkg)...)) +} + +func xgoTool(args ...string) *exec.Cmd { + cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...) + cmd.Env = []string{ + "GOPATH=" + build.GOPATH(), + "GOBIN=" + GOBIN, + } + for _, e := range os.Environ() { + if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") { + continue + } + cmd.Env = append(cmd.Env, e) + } + return cmd +} diff --git a/build/flags.sh b/build/flags.sh deleted file mode 100755 index e021dbad4..000000000 --- a/build/flags.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -set -e - -if [ ! -f "build/env.sh" ]; then - echo "$0 must be run from the root of the repository." - exit 2 -fi - -# Since Go 1.5, the separator char for link time assignments -# is '=' and using ' ' prints a warning. However, Go < 1.5 does -# not support using '='. -sep=$(go version | awk '{ if ($3 >= "go1.5" || index($3, "devel")) print "="; else print " "; }' -) - -# set gitCommit when running from a Git checkout. -if [ -f ".git/HEAD" ]; then - echo "-ldflags '-X main.gitCommit$sep$(git rev-parse HEAD)'" -fi - -if [ ! -z "$GO_OPENCL" ]; then - echo "-tags opencl" -fi diff --git a/containers/docker/develop-alpine/Dockerfile b/containers/docker/develop-alpine/Dockerfile index 70aee9f0f..f3247d178 100644 --- a/containers/docker/develop-alpine/Dockerfile +++ b/containers/docker/develop-alpine/Dockerfile @@ -1,12 +1,11 @@ FROM alpine:3.4 RUN \ - apk add --update go git make gcc musl-dev && \ - git clone https://github.com/ethereum/go-ethereum && \ - (cd go-ethereum && git checkout develop) && \ - (cd go-ethereum && make geth) && \ - cp go-ethereum/build/bin/geth /geth && \ - apk del go git make gcc musl-dev && \ + apk add --update go git make gcc musl-dev && \ + git clone --depth 1 --branch develop https://github.com/ethereum/go-ethereum && \ + (cd go-ethereum && make geth) && \ + cp go-ethereum/build/bin/geth /geth && \ + apk del go git make gcc musl-dev && \ rm -rf /go-ethereum && rm -rf /var/cache/apk/* EXPOSE 8545 diff --git a/containers/docker/master-alpine/Dockerfile b/containers/docker/master-alpine/Dockerfile index ffccd43e2..3393c4337 100644 --- a/containers/docker/master-alpine/Dockerfile +++ b/containers/docker/master-alpine/Dockerfile @@ -1,11 +1,11 @@ FROM alpine:3.4 RUN \ - apk add --update go git make gcc musl-dev && \ - git clone https://github.com/ethereum/go-ethereum && \ - (cd go-ethereum && make geth) && \ - cp go-ethereum/build/bin/geth /geth && \ - apk del go git make gcc musl-dev && \ + apk add --update go git make gcc musl-dev && \ + git clone --depth 1 https://github.com/ethereum/go-ethereum && \ + (cd go-ethereum && make geth) && \ + cp go-ethereum/build/bin/geth /geth && \ + apk del go git make gcc musl-dev && \ rm -rf /go-ethereum && rm -rf /var/cache/apk/* EXPOSE 8545 diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 5cc9f903b..b45f14724 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -95,7 +95,7 @@ func ecrecoverFunc(in []byte) []byte { // tighter sig s values in homestead only apply to tx sigs if !crypto.ValidateSignatureValues(v, r, s, false) { - glog.V(logger.Debug).Infof("EC RECOVER FAIL: v, r or s value invalid") + glog.V(logger.Detail).Infof("ECRECOVER error: v, r or s value invalid") return nil } @@ -106,7 +106,7 @@ func ecrecoverFunc(in []byte) []byte { pubKey, err := crypto.Ecrecover(in[:32], rsv) // make sure the public key is a valid one if err != nil { - glog.V(logger.Error).Infof("EC RECOVER FAIL: ", err) + glog.V(logger.Detail).Infoln("ECRECOVER error: ", err) return nil } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index bf1cb5932..1f9ef598c 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -1045,7 +1045,14 @@ func (d *Downloader) fetchParts(errCancel error, deliveryCh chan dataPack, deliv // Check for fetch request timeouts and demote the responsible peers for pid, fails := range expire() { if peer := d.peers.Peer(pid); peer != nil { - if fails > 1 { + // If a lot of retrieval elements expired, we might have overestimated the remote peer or perhaps + // ourselves. Only reset to minimal throughput but don't drop just yet. If even the minimal times + // out that sync wise we need to get rid of the peer. + // + // The reason the minimum threshold is 2 is because the downloader tries to estimate the bandwidth + // and latency of a peer separately, which requires pushing the measures capacity a bit and seeing + // how response times reacts, to it always requests one more than the minimum (i.e. min 2). + if fails > 2 { glog.V(logger.Detail).Infof("%s: %s delivery timeout", peer, strings.ToLower(kind)) setIdle(peer, 0) } else { diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 190fd57d2..e76e15177 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -40,6 +40,11 @@ web3._extend({ params: 1 }), new web3._extend.Method({ + name: 'removePeer', + call: 'admin_removePeer', + params: 1 + }), + new web3._extend.Method({ name: 'exportChain', call: 'admin_exportChain', params: 1, diff --git a/node/api.go b/node/api.go index 9b2be9c2e..3523874ab 100644 --- a/node/api.go +++ b/node/api.go @@ -58,6 +58,22 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { return true, nil } +// RemovePeer disconnects from a a remote node if the connection exists +func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { + // Make sure the server is running, fail otherwise + server := api.node.Server() + if server == nil { + return false, ErrNodeStopped + } + // Try to remove the url as a static peer and return + node, err := discover.ParseNode(url) + if err != nil { + return false, fmt.Errorf("invalid enode: %v", err) + } + server.RemovePeer(node) + return true, nil +} + // StartRPC starts the HTTP RPC API server. func (api *PrivateAdminAPI) StartRPC(host *string, port *rpc.HexNumber, cors *string, apis *string) (bool, error) { api.node.lock.Lock() diff --git a/p2p/dial.go b/p2p/dial.go index c0e703d7d..691b8539e 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -121,6 +121,11 @@ func (s *dialstate) addStatic(n *discover.Node) { s.static[n.ID] = &dialTask{flags: staticDialedConn, dest: n} } +func (s *dialstate) removeStatic(n *discover.Node) { + // This removes a task so future attempts to connect will not be made. + delete(s.static, n.ID) +} + func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now time.Time) []task { var newtasks []task isDialing := func(id discover.NodeID) bool { diff --git a/p2p/server.go b/p2p/server.go index 880aa7cf1..8e3cd93f9 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -142,6 +142,7 @@ type Server struct { quit chan struct{} addstatic chan *discover.Node + removestatic chan *discover.Node posthandshake chan *conn addpeer chan *conn delpeer chan *Peer @@ -257,6 +258,14 @@ func (srv *Server) AddPeer(node *discover.Node) { } } +// RemovePeer disconnects from the given node +func (srv *Server) RemovePeer(node *discover.Node) { + select { + case srv.removestatic <- node: + case <-srv.quit: + } +} + // Self returns the local node's endpoint information. func (srv *Server) Self() *discover.Node { srv.lock.Lock() @@ -327,6 +336,7 @@ func (srv *Server) Start() (err error) { srv.delpeer = make(chan *Peer) srv.posthandshake = make(chan *conn) srv.addstatic = make(chan *discover.Node) + srv.removestatic = make(chan *discover.Node) srv.peerOp = make(chan peerOpFunc) srv.peerOpDone = make(chan struct{}) @@ -395,6 +405,7 @@ type dialer interface { newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task taskDone(task, time.Time) addStatic(*discover.Node) + removeStatic(*discover.Node) } func (srv *Server) run(dialstate dialer) { @@ -458,6 +469,15 @@ running: // it will keep the node connected. glog.V(logger.Detail).Infoln("<-addstatic:", n) dialstate.addStatic(n) + case n := <-srv.removestatic: + // This channel is used by RemovePeer to send a + // disconnect request to a peer and begin the + // stop keeping the node connected + glog.V(logger.Detail).Infoln("<-removestatic:", n) + dialstate.removeStatic(n) + if p, ok := peers[n.ID]; ok { + p.Disconnect(DiscRequested) + } case op := <-srv.peerOp: // This channel is used by Peers and PeerCount. op(peers) diff --git a/p2p/server_test.go b/p2p/server_test.go index deb34f5bb..313d086ec 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -301,6 +301,8 @@ func (tg taskgen) taskDone(t task, now time.Time) { } func (tg taskgen) addStatic(*discover.Node) { } +func (tg taskgen) removeStatic(*discover.Node) { +} type testTask struct { index int |