From f27e826b140089cbeb722feec96b1aae3e7645a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 23 Nov 2015 13:03:31 +0200 Subject: [PATCH 01/78] jrse: fix #1082, fail if setTimeout/setInterval lack callback --- jsre/jsre.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/jsre/jsre.go b/jsre/jsre.go index af7d507c6d..a4c9d970be 100644 --- a/jsre/jsre.go +++ b/jsre/jsre.go @@ -85,7 +85,6 @@ func (self *JSRE) runEventLoop() { ready := make(chan *jsTimer) newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) { - delay, _ := call.Argument(1).ToInteger() if 0 >= delay { delay = 1 @@ -105,7 +104,6 @@ func (self *JSRE) runEventLoop() { if err != nil { panic(err) } - return timer, value } @@ -127,8 +125,20 @@ func (self *JSRE) runEventLoop() { } return otto.UndefinedValue() } - vm.Set("setTimeout", setTimeout) - vm.Set("setInterval", setInterval) + vm.Set("_setTimeout", setTimeout) + vm.Set("_setInterval", setInterval) + vm.Run(`var setTimeout = function(args) { + if (arguments.length < 1) { + throw TypeError("Failed to execute 'setTimeout': 1 argument required, but only 0 present."); + } + return _setTimeout.apply(this, arguments); + }`) + vm.Run(`var setInterval = function(args) { + if (arguments.length < 1) { + throw TypeError("Failed to execute 'setInterval': 1 argument required, but only 0 present."); + } + return _setInterval.apply(this, arguments); + }`) vm.Set("clearTimeout", clearTimeout) vm.Set("clearInterval", clearTimeout) @@ -154,7 +164,7 @@ loop: if err != nil { fmt.Println("js error:", err, arguments) } - + _, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it if timer.interval && inreg { timer.timer.Reset(timer.duration) From 2c42e54519afaa5426e9c389fa2ee911b0699baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 24 Nov 2015 17:24:43 +0200 Subject: [PATCH 02/78] Makefile: add support for iOS cross compilation --- Godeps/Godeps.json | 2 +- .../src/github.com/nsf/termbox-go/README.md | 2 ++ .../src/github.com/nsf/termbox-go/api.go | 24 ++++++++++++------- .../github.com/nsf/termbox-go/api_windows.go | 4 ++++ ...calls_darwin_386.go => syscalls_darwin.go} | 0 .../src/github.com/nsf/termbox-go/termbox.go | 14 +++++++---- .../nsf/termbox-go/termbox_windows.go | 4 ++-- Makefile | 17 ++++++++++++- 8 files changed, 51 insertions(+), 16 deletions(-) rename Godeps/_workspace/src/github.com/nsf/termbox-go/{syscalls_darwin_386.go => syscalls_darwin.go} (100%) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index cf23ad6a68..153e61d148 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -55,7 +55,7 @@ }, { "ImportPath": "github.com/nsf/termbox-go", - "Rev": "675ffd907b7401b8a709a5ef2249978af5616bb2" + "Rev": "c4d5eeeb18b378c361f9f2a731680e8b835969ed" }, { "ImportPath": "github.com/pborman/uuid", diff --git a/Godeps/_workspace/src/github.com/nsf/termbox-go/README.md b/Godeps/_workspace/src/github.com/nsf/termbox-go/README.md index 334d751024..5fc1874b11 100644 --- a/Godeps/_workspace/src/github.com/nsf/termbox-go/README.md +++ b/Godeps/_workspace/src/github.com/nsf/termbox-go/README.md @@ -16,6 +16,8 @@ There are also some interesting projects using termbox-go: - [httopd](https://github.com/verdverm/httopd) is top for httpd logs. - [mop](https://github.com/michaeldv/mop) is stock market tracker for hackers. - [termui](https://github.com/gizak/termui) is a terminal dashboard. + - [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine. + - [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart. ### API reference [godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go) diff --git a/Godeps/_workspace/src/github.com/nsf/termbox-go/api.go b/Godeps/_workspace/src/github.com/nsf/termbox-go/api.go index b08bca61a5..1e284060e6 100644 --- a/Godeps/_workspace/src/github.com/nsf/termbox-go/api.go +++ b/Godeps/_workspace/src/github.com/nsf/termbox-go/api.go @@ -351,7 +351,7 @@ func PollEvent() Event { // terminal's window size in characters). But it doesn't always match the size // of the terminal window, after the terminal size has changed, the internal // back buffer will get in sync only after Clear or Flush function calls. -func Size() (int, int) { +func Size() (width int, height int) { return termw, termh } @@ -380,6 +380,12 @@ func SetInputMode(mode InputMode) InputMode { if mode == InputCurrent { return input_mode } + if mode&(InputEsc|InputAlt) == 0 { + mode |= InputEsc + } + if mode&(InputEsc|InputAlt) == InputEsc|InputAlt { + mode &^= InputAlt + } if mode&InputMouse != 0 { out.WriteString(funcs[t_enter_mouse]) } else { @@ -391,6 +397,7 @@ func SetInputMode(mode InputMode) InputMode { } // Sets the termbox output mode. Termbox has four output options: +// // 1. OutputNormal => [1..8] // This mode provides 8 different colors: // black, red, green, yellow, blue, magenta, cyan, white @@ -402,10 +409,10 @@ func SetInputMode(mode InputMode) InputMode { // // 2. Output256 => [1..256] // In this mode you can leverage the 256 terminal mode: -// 0x00 - 0x07: the 8 colors as in OutputNormal -// 0x08 - 0x0f: Color* | AttrBold -// 0x10 - 0xe7: 216 different colors -// 0xe8 - 0xff: 24 different shades of grey +// 0x01 - 0x08: the 8 colors as in OutputNormal +// 0x09 - 0x10: Color* | AttrBold +// 0x11 - 0xe8: 216 different colors +// 0xe9 - 0x1ff: 24 different shades of grey // // Example usage: // SetCell(x, y, '@', 184, 240); @@ -415,11 +422,12 @@ func SetInputMode(mode InputMode) InputMode { // This mode supports the 3rd range of the 256 mode only. // But you dont need to provide an offset. // -// 4. OutputGrayscale => [1..24] -// This mode supports the 4th range of the 256 mode only. +// 4. OutputGrayscale => [1..26] +// This mode supports the 4th range of the 256 mode +// and black and white colors from 3th range of the 256 mode // But you dont need to provide an offset. // -// In all modes, 0 represents the default color. +// In all modes, 0x00 represents the default color. // // `go run _demos/output.go` to see its impact on your terminal. // diff --git a/Godeps/_workspace/src/github.com/nsf/termbox-go/api_windows.go b/Godeps/_workspace/src/github.com/nsf/termbox-go/api_windows.go index 78d954b36c..203544bbff 100644 --- a/Godeps/_workspace/src/github.com/nsf/termbox-go/api_windows.go +++ b/Godeps/_workspace/src/github.com/nsf/termbox-go/api_windows.go @@ -80,6 +80,10 @@ func Close() { // stop event producer cancel_comm <- true set_event(interrupt) + select { + case <-input_comm: + default: + } <-cancel_done_comm set_console_cursor_info(out, &orig_cursor_info) diff --git a/Godeps/_workspace/src/github.com/nsf/termbox-go/syscalls_darwin_386.go b/Godeps/_workspace/src/github.com/nsf/termbox-go/syscalls_darwin.go similarity index 100% rename from Godeps/_workspace/src/github.com/nsf/termbox-go/syscalls_darwin_386.go rename to Godeps/_workspace/src/github.com/nsf/termbox-go/syscalls_darwin.go diff --git a/Godeps/_workspace/src/github.com/nsf/termbox-go/termbox.go b/Godeps/_workspace/src/github.com/nsf/termbox-go/termbox.go index 0aee8aca90..f754880d29 100644 --- a/Godeps/_workspace/src/github.com/nsf/termbox-go/termbox.go +++ b/Godeps/_workspace/src/github.com/nsf/termbox-go/termbox.go @@ -72,6 +72,12 @@ var ( input_comm = make(chan input_event) interrupt_comm = make(chan struct{}) intbuf = make([]byte, 0, 16) + + // grayscale indexes + grayscale = []Attribute{ + 0, 17, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 232, + } ) func write_cursor(x, y int) { @@ -171,17 +177,17 @@ func send_attr(fg, bg Attribute) { case OutputGrayscale: fgcol = fg & 0x1F bgcol = bg & 0x1F - if fgcol > 24 { + if fgcol > 26 { fgcol = ColorDefault } - if bgcol > 24 { + if bgcol > 26 { bgcol = ColorDefault } if fgcol != ColorDefault { - fgcol += 0xe8 + fgcol = grayscale[fgcol] } if bgcol != ColorDefault { - bgcol += 0xe8 + bgcol = grayscale[bgcol] } default: fgcol = fg & 0x0F diff --git a/Godeps/_workspace/src/github.com/nsf/termbox-go/termbox_windows.go b/Godeps/_workspace/src/github.com/nsf/termbox-go/termbox_windows.go index 17d1bdc84b..f345d0eb00 100644 --- a/Godeps/_workspace/src/github.com/nsf/termbox-go/termbox_windows.go +++ b/Godeps/_workspace/src/github.com/nsf/termbox-go/termbox_windows.go @@ -129,7 +129,7 @@ func create_console_screen_buffer() (h syscall.Handle, err error) { err = syscall.EINVAL } } - return syscall.Handle(r0), nil + return syscall.Handle(r0), err } func get_console_screen_buffer_info(h syscall.Handle, info *console_screen_buffer_info) (err error) { @@ -305,7 +305,7 @@ func create_event() (out syscall.Handle, err error) { err = syscall.EINVAL } } - return syscall.Handle(r0), nil + return syscall.Handle(r0), err } func wait_for_multiple_objects(objects []syscall.Handle) (err error) { diff --git a/Makefile b/Makefile index 41cbc1ce6c..00db7cde76 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,11 @@ .PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64 .PHONY: geth-windows geth-windows-386 geth-windows-amd64 .PHONY: geth-android geth-android-16 geth-android-21 +.PHONY: geth-ios geth-ios-5.0 geth-ios-8.1 GOBIN = build/bin -CROSSDEPS = https://gmplib.org/download/gmp/gmp-6.0.0a.tar.bz2 +CROSSDEPS = https://gmplib.org/download/gmp/gmp-6.1.0.tar.bz2 GO ?= latest geth: @@ -83,6 +84,20 @@ geth-android-21: xgo @echo "Android 21 cross compilation done:" @ls -l $(GOBIN)/geth-android-21-* +geth-ios: xgo geth-ios-5.0 geth-ios-8.1 + @echo "iOS cross compilation done:" + @ls -l $(GOBIN)/geth-ios-* + +geth-ios-5.0: + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-5.0/* -v $(shell build/flags.sh) ./cmd/geth + @echo "iOS 5.0 cross compilation done:" + @ls -l $(GOBIN)/geth-ios-5.0-* + +geth-ios-8.1: + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-8.1/* -v $(shell build/flags.sh) ./cmd/geth + @echo "iOS 8.1 cross compilation done:" + @ls -l $(GOBIN)/geth-ios-8.1-* + evm: build/env.sh $(GOROOT)/bin/go install -v $(shell build/flags.sh) ./cmd/evm @echo "Done building." From 83a22b457c492a7622b5f8f4b420d0dbaa47b173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 24 Nov 2015 19:49:08 +0200 Subject: [PATCH 03/78] Makefile: support library build modes --- Godeps/Godeps.json | 2 +- .../nsf/termbox-go/syscalls_darwin.go | 2 ++ Makefile | 23 ++++++++++--------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 153e61d148..b467215d1e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -55,7 +55,7 @@ }, { "ImportPath": "github.com/nsf/termbox-go", - "Rev": "c4d5eeeb18b378c361f9f2a731680e8b835969ed" + "Rev": "ca2931516914070bb7f934c83e408689cea8dfb7" }, { "ImportPath": "github.com/pborman/uuid", diff --git a/Godeps/_workspace/src/github.com/nsf/termbox-go/syscalls_darwin.go b/Godeps/_workspace/src/github.com/nsf/termbox-go/syscalls_darwin.go index e03624ebc7..25b78f7ab7 100644 --- a/Godeps/_workspace/src/github.com/nsf/termbox-go/syscalls_darwin.go +++ b/Godeps/_workspace/src/github.com/nsf/termbox-go/syscalls_darwin.go @@ -1,6 +1,8 @@ // Created by cgo -godefs - DO NOT EDIT // cgo -godefs syscalls.go +// +build !amd64 + package termbox type syscall_Termios struct { diff --git a/Makefile b/Makefile index 00db7cde76..5fc5e9a9de 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ GOBIN = build/bin CROSSDEPS = https://gmplib.org/download/gmp/gmp-6.1.0.tar.bz2 +MODE ?= default GO ?= latest geth: @@ -28,17 +29,17 @@ geth-linux: xgo geth-linux-arm geth-linux-386 geth-linux-amd64 @ls -l $(GOBIN)/geth-linux-* geth-linux-arm: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm -v $(shell build/flags.sh) ./cmd/geth @echo "Linux ARM cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm geth-linux-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux 386 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep 386 geth-linux-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux amd64 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep amd64 @@ -47,12 +48,12 @@ geth-darwin: xgo geth-darwin-386 geth-darwin-amd64 @ls -l $(GOBIN)/geth-darwin-* geth-darwin-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Darwin 386 cross compilation done:" @ls -l $(GOBIN)/geth-darwin-* | grep 386 geth-darwin-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/geth @echo "Darwin amd64 cross compilation done:" @ls -l $(GOBIN)/geth-darwin-* | grep amd64 @@ -61,12 +62,12 @@ geth-windows: xgo geth-windows-386 geth-windows-amd64 @ls -l $(GOBIN)/geth-windows-* geth-windows-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Windows 386 cross compilation done:" @ls -l $(GOBIN)/geth-windows-* | grep 386 geth-windows-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/geth @echo "Windows amd64 cross compilation done:" @ls -l $(GOBIN)/geth-windows-* | grep amd64 @@ -75,12 +76,12 @@ geth-android: xgo geth-android-16 geth-android-21 @ls -l $(GOBIN)/geth-android-* geth-android-16: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=android-16/* -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=android-16/* -v $(shell build/flags.sh) ./cmd/geth @echo "Android 16 cross compilation done:" @ls -l $(GOBIN)/geth-android-16-* geth-android-21: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=android-21/* -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=android-21/* -v $(shell build/flags.sh) ./cmd/geth @echo "Android 21 cross compilation done:" @ls -l $(GOBIN)/geth-android-21-* @@ -89,12 +90,12 @@ geth-ios: xgo geth-ios-5.0 geth-ios-8.1 @ls -l $(GOBIN)/geth-ios-* geth-ios-5.0: - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-5.0/* -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-5.0/* -v $(shell build/flags.sh) ./cmd/geth @echo "iOS 5.0 cross compilation done:" @ls -l $(GOBIN)/geth-ios-5.0-* geth-ios-8.1: - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-8.1/* -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-8.1/* -v $(shell build/flags.sh) ./cmd/geth @echo "iOS 8.1 cross compilation done:" @ls -l $(GOBIN)/geth-ios-8.1-* From 5f0a4416db4339176e31fab5f4b7b79f4e7cf20b Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Fri, 20 Nov 2015 19:23:55 +0100 Subject: [PATCH 04/78] whisper: fixed broadcast race Whisper's expire and broadcast loops happen in two separate go routines. Whenever an envelope is being expired it's removed from the set of envelopes and it looses all information about the envelope, including the "known hash". After the envelope has been removed it can be re-accepted by a broadcasting peer putting back the envelope in the set of envelopes. Since the envelope broadcast loop is separate of the expire loop expired messages may be broadcast to other peer, resulting in messages **never** being dropped. This PR includes an expire check before adding new messages to the set of envelopes. --- whisper/peer_test.go | 7 +++++-- whisper/whisper.go | 5 +++++ whisper/whisper_test.go | 9 +++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/whisper/peer_test.go b/whisper/peer_test.go index 594671ee14..b3d2031c19 100644 --- a/whisper/peer_test.go +++ b/whisper/peer_test.go @@ -239,14 +239,17 @@ func TestPeerMessageExpiration(t *testing.T) { } payload := []interface{}{envelope} if err := p2p.ExpectMsg(tester.stream, messagesCode, payload); err != nil { - t.Fatalf("message mismatch: %v", err) + // A premature empty message may have been broadcast, check the next too + if err := p2p.ExpectMsg(tester.stream, messagesCode, payload); err != nil { + t.Fatalf("message mismatch: %v", err) + } } // Check that the message is inside the cache if !peer.known.Has(envelope.Hash()) { t.Fatalf("message not found in cache") } // Discard messages until expiration and check cache again - exp := time.Now().Add(time.Second + expirationCycle) + exp := time.Now().Add(time.Second + 2*expirationCycle + 100*time.Millisecond) for time.Now().Before(exp) { if err := p2p.ExpectMsg(tester.stream, messagesCode, []interface{}{}); err != nil { t.Fatalf("message mismatch: %v", err) diff --git a/whisper/whisper.go b/whisper/whisper.go index 676d8ae7a3..a341f23e48 100644 --- a/whisper/whisper.go +++ b/whisper/whisper.go @@ -234,6 +234,11 @@ func (self *Whisper) add(envelope *Envelope) error { self.poolMu.Lock() defer self.poolMu.Unlock() + // short circuit when a received envelope has already expired + if envelope.Expiry <= uint32(time.Now().Unix()) { + return nil + } + // Insert the message into the tracked pool hash := envelope.Hash() if _, ok := self.messages[hash]; ok { diff --git a/whisper/whisper_test.go b/whisper/whisper_test.go index 1a9a8667ad..b83ce0fe70 100644 --- a/whisper/whisper_test.go +++ b/whisper/whisper_test.go @@ -207,4 +207,13 @@ func TestMessageExpiration(t *testing.T) { if found { t.Fatalf("message not expired from cache") } + + node.add(envelope) + node.poolMu.RLock() + _, found = node.messages[envelope.Hash()] + node.poolMu.RUnlock() + if found { + t.Fatalf("message was added to cache") + } + } From a0bf2ea7e732b114518c3d8c66db337e0a7932f1 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Fri, 20 Nov 2015 13:45:37 +0100 Subject: [PATCH 05/78] accounts/abi: added output parsing & added call mechanism Added calling mechanism and return value parsing --- accounts/abi/abi.go | 146 ++++++++++++++++++--------------------- accounts/abi/abi_test.go | 47 ++++++++++--- accounts/abi/argument.go | 48 +++++++++++++ accounts/abi/method.go | 76 ++++++++++++++++++++ accounts/abi/numbers.go | 2 + accounts/abi/type.go | 12 +++- 6 files changed, 243 insertions(+), 88 deletions(-) create mode 100644 accounts/abi/argument.go create mode 100644 accounts/abi/method.go diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 3f05bfe2d4..635dc43fe4 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -20,75 +20,17 @@ import ( "encoding/json" "fmt" "io" - "strings" + "math" - "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" ) -// Callable method given a `Name` and whether the method is a constant. -// If the method is `Const` no transaction needs to be created for this -// particular Method call. It can easily be simulated using a local VM. -// For example a `Balance()` method only needs to retrieve something -// from the storage and therefor requires no Tx to be send to the -// network. A method such as `Transact` does require a Tx and thus will -// be flagged `true`. -// Input specifies the required input parameters for this gives method. -type Method struct { - Name string - Const bool - Inputs []Argument - Return Type // not yet implemented -} - -// Returns the methods string signature according to the ABI spec. -// -// Example -// -// function foo(uint32 a, int b) = "foo(uint32,int256)" -// -// Please note that "int" is substitute for its canonical representation "int256" -func (m Method) String() (out string) { - out += m.Name - types := make([]string, len(m.Inputs)) - i := 0 - for _, input := range m.Inputs { - types[i] = input.Type.String() - i++ - } - out += "(" + strings.Join(types, ",") + ")" - - return -} - -func (m Method) Id() []byte { - return crypto.Sha3([]byte(m.String()))[:4] -} - -// Argument holds the name of the argument and the corresponding type. -// Types are used when packing and testing arguments. -type Argument struct { - Name string - Type Type -} - -func (a *Argument) UnmarshalJSON(data []byte) error { - var extarg struct { - Name string - Type string - } - err := json.Unmarshal(data, &extarg) - if err != nil { - return fmt.Errorf("argument json err: %v", err) - } - - a.Type, err = NewType(extarg.Type) - if err != nil { - return err - } - a.Name = extarg.Name - - return nil -} +// Executer is an executer method for performing state executions. It takes one +// argument which is the input data and expects output data to be returned as +// multiple 32 byte word length concatenated slice +type Executer func(datain []byte) []byte // The ABI holds information about a contract's context and available // invokable methods. It will allow you to type check function calls and @@ -97,6 +39,18 @@ type ABI struct { Methods map[string]Method } +// JSON returns a parsed ABI interface and error if it failed. +func JSON(reader io.Reader) (ABI, error) { + dec := json.NewDecoder(reader) + + var abi ABI + if err := dec.Decode(&abi); err != nil { + return ABI{}, err + } + + return abi, nil +} + // tests, tests whether the given input would result in a successful // call. Checks argument list count and matches input to `input`. func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) { @@ -145,6 +99,55 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { return packed, nil } +// toGoType parses the input and casts it to the proper type defined by the ABI +// argument in t. +func toGoType(t Argument, input []byte) interface{} { + switch t.Type.T { + case IntTy: + return common.BytesToBig(input) + case UintTy: + return common.BytesToBig(input) + case BoolTy: + return common.BytesToBig(input).Uint64() > 0 + case AddressTy: + return common.BytesToAddress(input) + case HashTy: + return common.BytesToHash(input) + } + return nil +} + +// Call executes a call and attemps to parse the return values and returns it as +// an interface. It uses the executer method to perform the actual call since +// the abi knows nothing of the lower level calling mechanism. +// +// Call supports all abi types and includes multiple return values. When only +// one item is returned a single interface{} will be returned, if a contract +// method returns multiple values an []interface{} slice is returned. +func (abi ABI) Call(executer Executer, name string, args ...interface{}) interface{} { + callData, err := abi.Pack(name, args...) + if err != nil { + glog.V(logger.Debug).Infoln("pack error:", err) + return nil + } + + output := executer(callData) + + method := abi.Methods[name] + ret := make([]interface{}, int(math.Max(float64(len(method.Outputs)), float64(len(output)/32)))) + for i := 0; i < len(ret); i += 32 { + index := i / 32 + ret[index] = toGoType(method.Outputs[index], output[i:i+32]) + } + + // return single interface + if len(ret) == 1 { + return ret[0] + } + + return ret +} + func (abi *ABI) UnmarshalJSON(data []byte) error { var methods []Method if err := json.Unmarshal(data, &methods); err != nil { @@ -158,14 +161,3 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { return nil } - -func JSON(reader io.Reader) (ABI, error) { - dec := json.NewDecoder(reader) - - var abi ABI - if err := dec.Decode(&abi); err != nil { - return ABI{}, err - } - - return abi, nil -} diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 96dd3ee62b..d514fb8c72 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -92,12 +92,12 @@ func TestReader(t *testing.T) { exp := ABI{ Methods: map[string]Method{ "balance": Method{ - "balance", true, nil, Type{}, + "balance", true, nil, nil, }, "send": Method{ "send", false, []Argument{ Argument{"amount", Uint256}, - }, Type{}, + }, nil, }, }, } @@ -238,10 +238,10 @@ func TestTestAddress(t *testing.T) { func TestMethodSignature(t *testing.T) { String, _ := NewType("string") String32, _ := NewType("string32") - m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, Type{}} + m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, nil} exp := "foo(string32,string)" - if m.String() != exp { - t.Error("signature mismatch", exp, "!=", m.String()) + if m.Sig() != exp { + t.Error("signature mismatch", exp, "!=", m.Sig()) } idexp := crypto.Sha3([]byte(exp))[:4] @@ -250,10 +250,10 @@ func TestMethodSignature(t *testing.T) { } uintt, _ := NewType("uint") - m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, Type{}} + m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, nil} exp = "foo(uint256)" - if m.String() != exp { - t.Error("signature mismatch", exp, "!=", m.String()) + if m.Sig() != exp { + t.Error("signature mismatch", exp, "!=", m.Sig()) } } @@ -393,3 +393,34 @@ func TestBytes(t *testing.T) { t.Error("expected error") } } + +func TestReturn(t *testing.T) { + const definition = `[ + { "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] }, + { "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] } + +]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + r := abi.Call(func([]byte) []byte { + t := make([]byte, 32) + t[0] = 1 + return t + }, "balance") + if _, ok := r.(common.Hash); !ok { + t.Errorf("expected type common.Hash, got %T", r) + } + + r = abi.Call(func([]byte) []byte { + t := make([]byte, 32) + t[0] = 1 + return t + }, "name") + if _, ok := r.(common.Address); !ok { + t.Errorf("expected type common.Address, got %T", r) + } +} diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go new file mode 100644 index 0000000000..8907b29793 --- /dev/null +++ b/accounts/abi/argument.go @@ -0,0 +1,48 @@ +// Copyright 2015 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 . + +package abi + +import ( + "encoding/json" + "fmt" +) + +// Argument holds the name of the argument and the corresponding type. +// Types are used when packing and testing arguments. +type Argument struct { + Name string + Type Type +} + +func (a *Argument) UnmarshalJSON(data []byte) error { + var extarg struct { + Name string + Type string + } + err := json.Unmarshal(data, &extarg) + if err != nil { + return fmt.Errorf("argument json err: %v", err) + } + + a.Type, err = NewType(extarg.Type) + if err != nil { + return err + } + a.Name = extarg.Name + + return nil +} diff --git a/accounts/abi/method.go b/accounts/abi/method.go new file mode 100644 index 0000000000..63194e7880 --- /dev/null +++ b/accounts/abi/method.go @@ -0,0 +1,76 @@ +// Copyright 2015 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 . + +package abi + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/crypto" +) + +// Callable method given a `Name` and whether the method is a constant. +// If the method is `Const` no transaction needs to be created for this +// particular Method call. It can easily be simulated using a local VM. +// For example a `Balance()` method only needs to retrieve something +// from the storage and therefor requires no Tx to be send to the +// network. A method such as `Transact` does require a Tx and thus will +// be flagged `true`. +// Input specifies the required input parameters for this gives method. +type Method struct { + Name string + Const bool + Inputs []Argument + Outputs []Argument +} + +// Sig returns the methods string signature according to the ABI spec. +// +// Example +// +// function foo(uint32 a, int b) = "foo(uint32,int256)" +// +// Please note that "int" is substitute for its canonical representation "int256" +func (m Method) Sig() string { + types := make([]string, len(m.Inputs)) + i := 0 + for _, input := range m.Inputs { + types[i] = input.Type.String() + i++ + } + return fmt.Sprintf("%v(%v)", m.Name, strings.Join(types, ",")) +} + +func (m Method) String() string { + inputs := make([]string, len(m.Inputs)) + for i, input := range m.Inputs { + inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type) + } + outputs := make([]string, len(m.Outputs)) + for i, output := range m.Outputs { + if len(output.Name) > 0 { + outputs[i] = fmt.Sprintf("%v ", output.Name) + } + outputs[i] += output.Type.String() + } + + return fmt.Sprintf("function %v(%v) returns(%v)", m.Name, strings.Join(inputs, ", "), strings.Join(outputs, ", ")) +} + +func (m Method) Id() []byte { + return crypto.Sha3([]byte(m.Sig()))[:4] +} diff --git a/accounts/abi/numbers.go b/accounts/abi/numbers.go index 2a7049425a..c37cd5f687 100644 --- a/accounts/abi/numbers.go +++ b/accounts/abi/numbers.go @@ -37,6 +37,8 @@ var int8_t = reflect.TypeOf(int8(0)) var int16_t = reflect.TypeOf(int16(0)) var int32_t = reflect.TypeOf(int32(0)) var int64_t = reflect.TypeOf(int64(0)) +var hash_t = reflect.TypeOf(common.Hash{}) +var address_t = reflect.TypeOf(common.Address{}) var uint_ts = reflect.TypeOf([]uint(nil)) var uint8_ts = reflect.TypeOf([]uint8(nil)) diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 16d7491e7c..8f0238fc99 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -31,6 +31,7 @@ const ( BoolTy SliceTy AddressTy + HashTy RealTy ) @@ -121,7 +122,7 @@ func NewType(t string) (typ Type, err error) { typ.Kind = reflect.Invalid case "address": typ.Kind = reflect.Slice - typ.Type = byte_ts + typ.Type = address_t typ.Size = 20 typ.T = AddressTy case "string": @@ -130,6 +131,11 @@ func NewType(t string) (typ Type, err error) { if vsize > 0 { typ.Size = 32 } + case "hash": + typ.Kind = reflect.Slice + typ.Size = 32 + typ.Type = hash_t + typ.T = HashTy case "bytes": typ.Kind = reflect.Slice typ.Type = byte_ts @@ -206,9 +212,9 @@ func (t Type) pack(v interface{}) ([]byte, error) { } case reflect.Array: if v, ok := value.Interface().(common.Address); ok { - return t.pack(v[:]) + return common.LeftPadBytes(v[:], 32), nil } else if v, ok := value.Interface().(common.Hash); ok { - return t.pack(v[:]) + return v[:], nil } } From b1e014344494d2713f6091299a5b86dc53e1fb4e Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 24 Nov 2015 09:37:29 +0100 Subject: [PATCH 06/78] cmd, crypto: fixed nil public keys and updated web3 --- cmd/geth/js.go | 4 +- jsre/ethereum_js.go | 7266 +++++++++++++++++++++++++++++++++++++------ xeth/xeth.go | 4 +- 3 files changed, 6278 insertions(+), 996 deletions(-) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 4d5462539b..17f781f06b 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -299,12 +299,12 @@ func (js *jsre) apiBindings(f xeth.Frontend) error { utils.Fatalf("Error loading web3.js: %v", err) } - _, err = js.re.Run("var web3 = require('web3');") + _, err = js.re.Run("var Web3 = require('web3');") if err != nil { utils.Fatalf("Error requiring web3: %v", err) } - _, err = js.re.Run("web3.setProvider(jeth)") + _, err = js.re.Run("var web3 = new Web3(jeth);") if err != nil { utils.Fatalf("Error setting web3 provider: %v", err) } diff --git a/jsre/ethereum_js.go b/jsre/ethereum_js.go index 2d7dbfec0a..7063a90ec6 100644 --- a/jsre/ethereum_js.go +++ b/jsre/ethereum_js.go @@ -272,6 +272,7 @@ module.exports=[ "type": "event" } ] + },{}],2:[function(require,module,exports){ module.exports=[ { @@ -381,6 +382,7 @@ module.exports=[ "type": "event" } ] + },{}],3:[function(require,module,exports){ module.exports=[ { @@ -528,6 +530,7 @@ module.exports=[ "type": "event" } ] + },{}],4:[function(require,module,exports){ var f = require('./formatters'); var SolidityType = require('./type'); @@ -635,22 +638,22 @@ module.exports = SolidityTypeBytes; },{"./formatters":9,"./type":14}],7:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file coder.js * @author Marek Kotewicz * @date 2015 @@ -680,7 +683,7 @@ var SolidityCoder = function (types) { * * @method _requireType * @param {String} type - * @returns {SolidityType} + * @returns {SolidityType} * @throws {Error} throws if no matching type is found */ SolidityCoder.prototype._requireType = function (type) { @@ -723,10 +726,12 @@ SolidityCoder.prototype.encodeParams = function (types, params) { }); var dynamicOffset = solidityTypes.reduce(function (acc, solidityType, index) { - return acc + solidityType.staticPartLength(types[index]); + var staticPartLength = solidityType.staticPartLength(types[index]); + var roundedStaticPartLength = Math.floor((staticPartLength + 31) / 32) * 32; + return acc + roundedStaticPartLength; }, 0); - var result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset); + var result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset); return result; }; @@ -751,7 +756,7 @@ SolidityCoder.prototype.encodeMultiWithOffset = function (types, solidityTypes, // TODO: figure out nested arrays }); - + types.forEach(function (type, i) { if (isDynamic(i)) { var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); @@ -771,7 +776,7 @@ SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded var nestedName = solidityType.nestedName(type); var nestedStaticPartLength = solidityType.staticPartLength(nestedName); var result = encoded[0]; - + (function () { var previousLength = 2; // in int if (solidityType.isDynamicArray(nestedName)) { @@ -781,7 +786,7 @@ SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded } } })(); - + // first element is length, skip it (function () { for (var i = 0; i < encoded.length - 1; i++) { @@ -792,7 +797,7 @@ SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded return result; })(); - + } else if (solidityType.isStaticArray(type)) { return (function () { var nestedName = solidityType.nestedName(type); @@ -805,7 +810,7 @@ SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded var previousLength = 0; // in int for (var i = 0; i < encoded.length; i++) { // calculate length of previous item - previousLength += +(encoded[i - 1] || [])[0] || 0; + previousLength += +(encoded[i - 1] || [])[0] || 0; result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode(); } })(); @@ -848,7 +853,7 @@ SolidityCoder.prototype.decodeParam = function (type, bytes) { SolidityCoder.prototype.decodeParams = function (types, bytes) { var solidityTypes = this.getSolidityTypes(types); var offsets = this.getOffsets(types, solidityTypes); - + return solidityTypes.map(function (solidityType, index) { return solidityType.decode(bytes, offsets[index], types[index], index); }); @@ -857,18 +862,17 @@ SolidityCoder.prototype.decodeParams = function (types, bytes) { SolidityCoder.prototype.getOffsets = function (types, solidityTypes) { var lengths = solidityTypes.map(function (solidityType, index) { return solidityType.staticPartLength(types[index]); - // get length }); - - for (var i = 0; i < lengths.length; i++) { + + for (var i = 1; i < lengths.length; i++) { // sum with length of previous element - var previous = (lengths[i - 1] || 0); - lengths[i] += previous; + lengths[i] += lengths[i - 1]; } return lengths.map(function (length, index) { // remove the current length, so the length is sum of previous elements - return length - solidityTypes[index].staticPartLength(types[index]); + var staticPartLength = solidityTypes[index].staticPartLength(types[index]); + return length - staticPartLength; }); }; @@ -923,22 +927,22 @@ module.exports = SolidityTypeDynamicBytes; },{"./formatters":9,"./type":14}],9:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file formatters.js * @author Marek Kotewicz * @date 2015 @@ -1082,7 +1086,7 @@ var formatOutputUInt = function (param) { * @returns {BigNumber} input bytes formatted to real */ var formatOutputReal = function (param) { - return formatOutputInt(param).dividedBy(new BigNumber(2).pow(128)); + return formatOutputInt(param).dividedBy(new BigNumber(2).pow(128)); }; /** @@ -1093,7 +1097,7 @@ var formatOutputReal = function (param) { * @returns {BigNumber} input bytes formatted to ureal */ var formatOutputUReal = function (param) { - return formatOutputUInt(param).dividedBy(new BigNumber(2).pow(128)); + return formatOutputUInt(param).dividedBy(new BigNumber(2).pow(128)); }; /** @@ -1213,22 +1217,22 @@ module.exports = SolidityTypeInt; },{"./formatters":9,"./type":14}],11:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file param.js * @author Marek Kotewicz * @date 2015 @@ -1247,7 +1251,7 @@ var SolidityParam = function (value, offset) { /** * This method should be used to get length of params's dynamic part - * + * * @method dynamicPartLength * @returns {Number} length of dynamic part (in bytes) */ @@ -1275,7 +1279,7 @@ SolidityParam.prototype.withOffset = function (offset) { * @param {SolidityParam} result of combination */ SolidityParam.prototype.combine = function (param) { - return new SolidityParam(this.value + param.value); + return new SolidityParam(this.value + param.value); }; /** @@ -1307,8 +1311,8 @@ SolidityParam.prototype.offsetAsBytes = function () { */ SolidityParam.prototype.staticPart = function () { if (!this.isDynamic()) { - return this.value; - } + return this.value; + } return this.offsetAsBytes(); }; @@ -1340,7 +1344,7 @@ SolidityParam.prototype.encode = function () { * @returns {String} */ SolidityParam.encodeList = function (params) { - + // updating offsets var totalOffset = params.length * 32; var offsetParams = params.map(function (param) { @@ -1466,13 +1470,13 @@ SolidityType.prototype.staticPartLength = function (name) { /** * Should be used to determine if type is dynamic array - * eg: + * eg: * "type[]" => true * "type[4]" => false * * @method isDynamicArray * @param {String} name - * @return {Bool} true if the type is dynamic array + * @return {Bool} true if the type is dynamic array */ SolidityType.prototype.isDynamicArray = function (name) { var nestedTypes = this.nestedTypes(name); @@ -1481,13 +1485,13 @@ SolidityType.prototype.isDynamicArray = function (name) { /** * Should be used to determine if type is static array - * eg: + * eg: * "type[]" => false * "type[4]" => true * * @method isStaticArray * @param {String} name - * @return {Bool} true if the type is static array + * @return {Bool} true if the type is static array */ SolidityType.prototype.isStaticArray = function (name) { var nestedTypes = this.nestedTypes(name); @@ -1496,7 +1500,7 @@ SolidityType.prototype.isStaticArray = function (name) { /** * Should return length of static array - * eg. + * eg. * "int[32]" => 32 * "int256[14]" => 14 * "int[2][3]" => 3 @@ -1571,7 +1575,7 @@ SolidityType.prototype.nestedTypes = function (name) { * Should be used to encode the value * * @method encode - * @param {Object} value + * @param {Object} value * @param {String} name * @return {String} encoded value */ @@ -1585,7 +1589,7 @@ SolidityType.prototype.encode = function (value, name) { var result = []; result.push(f.formatInputInt(length).encode()); - + value.forEach(function (v) { result.push(self.encode(v, nestedName)); }); @@ -1633,9 +1637,10 @@ SolidityType.prototype.decode = function (bytes, offset, name) { var nestedName = self.nestedName(name); var nestedStaticPartLength = self.staticPartLength(nestedName); // in bytes + var roundedNestedStaticPartLength = Math.floor((nestedStaticPartLength + 31) / 32) * 32; var result = []; - for (var i = 0; i < length * nestedStaticPartLength; i += nestedStaticPartLength) { + for (var i = 0; i < length * roundedNestedStaticPartLength; i += roundedNestedStaticPartLength) { result.push(self.decode(bytes, arrayStart + i, nestedName)); } @@ -1650,21 +1655,22 @@ SolidityType.prototype.decode = function (bytes, offset, name) { var nestedName = self.nestedName(name); var nestedStaticPartLength = self.staticPartLength(nestedName); // in bytes + var roundedNestedStaticPartLength = Math.floor((nestedStaticPartLength + 31) / 32) * 32; var result = []; - for (var i = 0; i < length * nestedStaticPartLength; i += nestedStaticPartLength) { + for (var i = 0; i < length * roundedNestedStaticPartLength; i += roundedNestedStaticPartLength) { result.push(self.decode(bytes, arrayStart + i, nestedName)); } return result; })(); } else if (this.isDynamicType(name)) { - + return (function () { var dynamicOffset = parseInt('0x' + bytes.substr(offset * 2, 64)); // in bytes var length = parseInt('0x' + bytes.substr(dynamicOffset * 2, 64)); // in bytes var roundedLength = Math.floor((length + 31) / 32); // in int - + return self._outputFormatter(new SolidityParam(bytes.substr(dynamicOffset * 2, ( 1 + roundedLength) * 64), 0)); })(); } @@ -1764,20 +1770,20 @@ if (typeof XMLHttpRequest === 'undefined') { },{}],18:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file config.js * @authors: @@ -1787,13 +1793,13 @@ if (typeof XMLHttpRequest === 'undefined') { /** * Utils - * + * * @module utils */ /** * Utility functions - * + * * @class [utils] config * @constructor */ @@ -1845,62 +1851,60 @@ module.exports = { },{"bignumber.js":"bignumber.js"}],19:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file sha3.js * @author Marek Kotewicz * @date 2015 */ - -var utils = require('./utils'); +var CryptoJS = require('crypto-js'); var sha3 = require('crypto-js/sha3'); -module.exports = function (str, isNew) { - if (str.substr(0, 2) === '0x' && !isNew) { - console.warn('requirement of using web3.fromAscii before sha3 is deprecated'); - console.warn('new usage: \'web3.sha3("hello")\''); - console.warn('see https://github.com/ethereum/web3.js/pull/205'); - console.warn('if you need to hash hex value, you can do \'sha3("0xfff", true)\''); - str = utils.toUtf8(str); +module.exports = function (value, options) { + if (options && options.encoding === 'hex') { + if (value.length > 2 && value.substr(0, 2) === '0x') { + value = value.substr(2); + } + value = CryptoJS.enc.Hex.parse(value); } - return sha3(str, { + return sha3(value, { outputLength: 256 }).toString(); }; -},{"./utils":20,"crypto-js/sha3":48}],20:[function(require,module,exports){ +},{"crypto-js":57,"crypto-js/sha3":78}],20:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** * @file utils.js @@ -1994,6 +1998,8 @@ var toUtf8 = function(hex) { } for (; i < l; i+=2) { var code = parseInt(hex.substr(i, 2), 16); + if (code === 0) + break; str += String.fromCharCode(code); } @@ -2023,7 +2029,7 @@ var toAscii = function(hex) { }; /** - * Shold be called to get hex representation (prefixed by 0x) of utf8 string + * Should be called to get hex representation (prefixed by 0x) of utf8 a string * * @method fromUtf8 * @param {String} string @@ -2034,7 +2040,10 @@ var fromUtf8 = function(str) { str = utf8.encode(str); var hex = ""; for(var i = 0; i < str.length; i++) { - var n = str.charCodeAt(i).toString(16); + var code = str.charCodeAt(i); + if (code === 0) + break; + var n = code.toString(16); hex += n.length < 2 ? '0' + n : n; } @@ -2042,7 +2051,7 @@ var fromUtf8 = function(str) { }; /** - * Shold be called to get hex representation (prefixed by 0x) of ascii string + * Should be called to get hex representation (prefixed by 0x) of ascii string * * @method fromAscii * @param {String} string @@ -2301,7 +2310,6 @@ var toAddress = function (address) { return '0x' + padLeft(toHex(address).substr(2), 40); }; - /** * Returns true if object is BigNumber, otherwise false * @@ -2414,29 +2422,30 @@ module.exports = { isJson: isJson }; -},{"bignumber.js":"bignumber.js","utf8":50}],21:[function(require,module,exports){ +},{"bignumber.js":"bignumber.js","utf8":83}],21:[function(require,module,exports){ module.exports={ - "version": "0.13.0" + "version": "0.15.1" } },{}],22:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** @file web3.js +/** + * @file web3.js * @authors: * Jeffrey Wilcke * Marek Kotewicz @@ -2446,181 +2455,136 @@ module.exports={ * @date 2014 */ -var version = require('./version.json'); -var net = require('./web3/methods/net'); -var eth = require('./web3/methods/eth'); -var db = require('./web3/methods/db'); -var shh = require('./web3/methods/shh'); -var watches = require('./web3/methods/watches'); -var Filter = require('./web3/filter'); -var IsSyncing = require('./web3/syncing'); -var utils = require('./utils/utils'); -var formatters = require('./web3/formatters'); var RequestManager = require('./web3/requestmanager'); -var c = require('./utils/config'); -var Property = require('./web3/property'); -var Batch = require('./web3/batch'); +var Iban = require('./web3/iban'); +var Eth = require('./web3/methods/eth'); +var DB = require('./web3/methods/db'); +var Shh = require('./web3/methods/shh'); +var Net = require('./web3/methods/net'); +var Settings = require('./web3/settings'); +var version = require('./version.json'); +var utils = require('./utils/utils'); var sha3 = require('./utils/sha3'); +var extend = require('./web3/extend'); +var Batch = require('./web3/batch'); +var Property = require('./web3/property'); +var HttpProvider = require('./web3/httpprovider'); +var IpcProvider = require('./web3/ipcprovider'); -var web3Properties = [ - new Property({ - name: 'version.client', - getter: 'web3_clientVersion' - }), - new Property({ - name: 'version.network', - getter: 'net_version', - inputFormatter: utils.toDecimal - }), - new Property({ - name: 'version.ethereum', - getter: 'eth_protocolVersion', - inputFormatter: utils.toDecimal - }), - new Property({ - name: 'version.whisper', - getter: 'shh_version', - inputFormatter: utils.toDecimal - }) -]; -/// creates methods in a given object based on method description on input -/// setups api calls for these methods -var setupMethods = function (obj, methods) { - methods.forEach(function (method) { - method.attachToObject(obj); - }); -}; -/// creates properties in a given object based on properties description on input -/// setups api calls for these properties -var setupProperties = function (obj, properties) { - properties.forEach(function (property) { - property.attachToObject(obj); - }); -}; - -/// setups web3 object, and it's in-browser executed methods -var web3 = {}; -web3.providers = {}; -web3.currentProvider = null; -web3.version = {}; -web3.version.api = version.version; -web3.eth = {}; - -web3.eth.isSyncing = function (callback) { - return new IsSyncing(callback); -}; - -/*jshint maxparams:4 */ -web3.eth.filter = function (fil, callback) { - return new Filter(fil, watches.eth(), formatters.outputLogFormatter, callback); -}; -/*jshint maxparams:3 */ - -web3.shh = {}; -web3.shh.filter = function (fil, callback) { - return new Filter(fil, watches.shh(), formatters.outputPostFormatter, callback); -}; -web3.net = {}; -web3.db = {}; -web3.setProvider = function (provider) { +function Web3 (provider) { + this._requestManager = new RequestManager(provider); this.currentProvider = provider; - RequestManager.getInstance().setProvider(provider); -}; -web3.isConnected = function(){ - return (this.currentProvider && this.currentProvider.isConnected()); -}; -web3.reset = function (keepIsSyncing) { - RequestManager.getInstance().reset(keepIsSyncing); - c.defaultBlock = 'latest'; - c.defaultAccount = undefined; -}; -web3.toHex = utils.toHex; -web3.toAscii = utils.toAscii; -web3.toUtf8 = utils.toUtf8; -web3.fromAscii = utils.fromAscii; -web3.fromUtf8 = utils.fromUtf8; -web3.toDecimal = utils.toDecimal; -web3.fromDecimal = utils.fromDecimal; -web3.toBigNumber = utils.toBigNumber; -web3.toWei = utils.toWei; -web3.fromWei = utils.fromWei; -web3.isAddress = utils.isAddress; -web3.isIBAN = utils.isIBAN; -web3.sha3 = sha3; -web3.createBatch = function () { - return new Batch(); + this.eth = new Eth(this); + this.db = new DB(this); + this.shh = new Shh(this); + this.net = new Net(this); + this.settings = new Settings(); + this.version = { + api: version.version + }; + this.providers = { + HttpProvider: HttpProvider, + IpcProvider: IpcProvider + }; + this._extend = extend(this); + this._extend({ + properties: properties() + }); +} + +// expose providers on the class +Web3.providers = { + HttpProvider: HttpProvider, + IpcProvider: IpcProvider }; -// ADD defaultblock -Object.defineProperty(web3.eth, 'defaultBlock', { - get: function () { - return c.defaultBlock; - }, - set: function (val) { - c.defaultBlock = val; - return val; - } -}); - -Object.defineProperty(web3.eth, 'defaultAccount', { - get: function () { - return c.defaultAccount; - }, - set: function (val) { - c.defaultAccount = val; - return val; - } -}); - - -// EXTEND -web3._extend = function(extension){ - /*jshint maxcomplexity: 6 */ - - if(extension.property && !web3[extension.property]) - web3[extension.property] = {}; - - setupMethods(web3[extension.property] || web3, extension.methods || []); - setupProperties(web3[extension.property] || web3, extension.properties || []); +Web3.prototype.setProvider = function (provider) { + this._requestManager.setProvider(provider); + this.currentProvider = provider; }; -web3._extend.formatters = formatters; -web3._extend.utils = utils; -web3._extend.Method = require('./web3/method'); -web3._extend.Property = require('./web3/property'); + +Web3.prototype.reset = function (keepIsSyncing) { + this._requestManager.reset(keepIsSyncing); + this.settings = new Settings(); +}; + +Web3.prototype.toHex = utils.toHex; +Web3.prototype.toAscii = utils.toAscii; +Web3.prototype.toUtf8 = utils.toUtf8; +Web3.prototype.fromAscii = utils.fromAscii; +Web3.prototype.fromUtf8 = utils.fromUtf8; +Web3.prototype.toDecimal = utils.toDecimal; +Web3.prototype.fromDecimal = utils.fromDecimal; +Web3.prototype.toBigNumber = utils.toBigNumber; +Web3.prototype.toWei = utils.toWei; +Web3.prototype.fromWei = utils.fromWei; +Web3.prototype.isAddress = utils.isAddress; +Web3.prototype.isIBAN = utils.isIBAN; +Web3.prototype.sha3 = sha3; + +/** + * Transforms direct icap to address + */ +Web3.prototype.fromICAP = function (icap) { + var iban = new Iban(icap); + return iban.address(); +}; + +var properties = function () { + return [ + new Property({ + name: 'version.node', + getter: 'web3_clientVersion' + }), + new Property({ + name: 'version.network', + getter: 'net_version', + inputFormatter: utils.toDecimal + }), + new Property({ + name: 'version.ethereum', + getter: 'eth_protocolVersion', + inputFormatter: utils.toDecimal + }), + new Property({ + name: 'version.whisper', + getter: 'shh_version', + inputFormatter: utils.toDecimal + }) + ]; +}; + +Web3.prototype.isConnected = function(){ + return (this.currentProvider && this.currentProvider.isConnected()); +}; + +Web3.prototype.createBatch = function () { + return new Batch(this); +}; + +module.exports = Web3; -/// setups all api methods -setupProperties(web3, web3Properties); -setupMethods(web3.net, net.methods); -setupProperties(web3.net, net.properties); -setupMethods(web3.eth, eth.methods); -setupProperties(web3.eth, eth.properties); -setupMethods(web3.db, db.methods); -setupMethods(web3.shh, shh.methods); - -module.exports = web3; - - -},{"./utils/config":18,"./utils/sha3":19,"./utils/utils":20,"./version.json":21,"./web3/batch":24,"./web3/filter":28,"./web3/formatters":29,"./web3/method":35,"./web3/methods/db":36,"./web3/methods/eth":37,"./web3/methods/net":38,"./web3/methods/shh":39,"./web3/methods/watches":40,"./web3/property":42,"./web3/requestmanager":43,"./web3/syncing":44}],23:[function(require,module,exports){ +},{"./utils/sha3":19,"./utils/utils":20,"./version.json":21,"./web3/batch":24,"./web3/extend":28,"./web3/httpprovider":32,"./web3/iban":33,"./web3/ipcprovider":34,"./web3/methods/db":37,"./web3/methods/eth":38,"./web3/methods/net":39,"./web3/methods/shh":40,"./web3/property":43,"./web3/requestmanager":44,"./web3/settings":45}],23:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file allevents.js * @author Marek Kotewicz * @date 2014 @@ -2633,7 +2597,8 @@ var utils = require('../utils/utils'); var Filter = require('./filter'); var watches = require('./methods/watches'); -var AllSolidityEvents = function (json, address) { +var AllSolidityEvents = function (requestManager, json, address) { + this._requestManager = requestManager; this._json = json; this._address = address; }; @@ -2667,7 +2632,7 @@ AllSolidityEvents.prototype.decode = function (data) { return data; } - var event = new SolidityEvent(match, this._address); + var event = new SolidityEvent(this._requestManager, match, this._address); return event.decode(data); }; @@ -2681,7 +2646,7 @@ AllSolidityEvents.prototype.execute = function (options, callback) { var o = this.encode(options); var formatter = this.decode.bind(this); - return new Filter(o, watches.eth(), formatter, callback); + return new Filter(this._requestManager, o, watches.eth(), formatter, callback); }; AllSolidityEvents.prototype.attachToContract = function (contract) { @@ -2692,34 +2657,34 @@ AllSolidityEvents.prototype.attachToContract = function (contract) { module.exports = AllSolidityEvents; -},{"../utils/sha3":19,"../utils/utils":20,"./event":27,"./filter":28,"./formatters":29,"./methods/watches":40}],24:[function(require,module,exports){ +},{"../utils/sha3":19,"../utils/utils":20,"./event":27,"./filter":29,"./formatters":30,"./methods/watches":41}],24:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file batch.js * @author Marek Kotewicz * @date 2015 */ -var RequestManager = require('./requestmanager'); var Jsonrpc = require('./jsonrpc'); var errors = require('./errors'); -var Batch = function () { +var Batch = function (web3) { + this.requestManager = web3._requestManager; this.requests = []; }; @@ -2740,7 +2705,7 @@ Batch.prototype.add = function (request) { */ Batch.prototype.execute = function () { var requests = this.requests; - RequestManager.getInstance().sendBatch(requests, function (err, results) { + this.requestManager.sendBatch(requests, function (err, results) { results = results || []; requests.map(function (request, index) { return results[index] || {}; @@ -2754,36 +2719,35 @@ Batch.prototype.execute = function () { requests[index].callback(null, (requests[index].format ? requests[index].format(result.result) : result.result)); } }); - }); + }); }; module.exports = Batch; -},{"./errors":26,"./jsonrpc":34,"./requestmanager":43}],25:[function(require,module,exports){ +},{"./errors":26,"./jsonrpc":35}],25:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file contract.js * @author Marek Kotewicz * @date 2014 */ -var web3 = require('../web3'); var utils = require('../utils/utils'); var coder = require('../solidity/coder'); var SolidityEvent = require('./event'); @@ -2816,11 +2780,11 @@ var encodeConstructorParams = function (abi, params) { * @param {Contract} contract * @param {Array} abi */ -var addFunctionsToContract = function (contract, abi) { - abi.filter(function (json) { +var addFunctionsToContract = function (contract) { + contract.abi.filter(function (json) { return json.type === 'function'; }).map(function (json) { - return new SolidityFunction(json, contract.address); + return new SolidityFunction(contract._eth, json, contract.address); }).forEach(function (f) { f.attachToContract(contract); }); @@ -2833,31 +2797,21 @@ var addFunctionsToContract = function (contract, abi) { * @param {Contract} contract * @param {Array} abi */ -var addEventsToContract = function (contract, abi) { - var events = abi.filter(function (json) { +var addEventsToContract = function (contract) { + var events = contract.abi.filter(function (json) { return json.type === 'event'; }); - var All = new AllEvents(events, contract.address); + var All = new AllEvents(contract._eth._requestManager, events, contract.address); All.attachToContract(contract); - + events.map(function (json) { - return new SolidityEvent(json, contract.address); + return new SolidityEvent(contract._eth._requestManager, json, contract.address); }).forEach(function (e) { e.attachToContract(contract); }); }; -/** - * Should be called to create new ContractFactory - * - * @method contract - * @param {Array} abi - * @returns {ContractFactory} new contract factory - */ -var contract = function (abi) { - return new ContractFactory(abi); -}; /** * Should be called to check if the contract gets properly deployed on the blockchain. @@ -2867,24 +2821,22 @@ var contract = function (abi) { * @param {Function} callback * @returns {Undefined} */ -var checkForContractAddress = function(contract, abi, callback){ +var checkForContractAddress = function(contract, callback){ var count = 0, callbackFired = false; // wait for receipt - var filter = web3.eth.filter('latest', function(e){ - if(!e && !callbackFired) { + var filter = contract._eth.filter('latest', function(e){ + if (!e && !callbackFired) { count++; - // console.log('Checking for contract address', count); - // stop watching after 50 blocks (timeout) - if(count > 50) { - + if (count > 50) { + filter.stopWatching(); callbackFired = true; - if(callback) + if (callback) callback(new Error('Contract transaction couldn\'t be found after 50 blocks')); else throw new Error('Contract transaction couldn\'t be found after 50 blocks'); @@ -2892,15 +2844,15 @@ var checkForContractAddress = function(contract, abi, callback){ } else { - web3.eth.getTransactionReceipt(contract.transactionHash, function(e, receipt){ + contract._eth.getTransactionReceipt(contract.transactionHash, function(e, receipt){ if(receipt && !callbackFired) { - web3.eth.getCode(receipt.contractAddress, function(e, code){ - /*jshint maxcomplexity: 5 */ + contract._eth.getCode(receipt.contractAddress, function(e, code){ + /*jshint maxcomplexity: 6 */ - if(callbackFired) + if(callbackFired || !code) return; - + filter.stopWatching(); callbackFired = true; @@ -2910,9 +2862,9 @@ var checkForContractAddress = function(contract, abi, callback){ contract.address = receipt.contractAddress; - // attach events and methods - addFunctionsToContract(contract, abi); - addEventsToContract(contract, abi); + // attach events and methods again after we have + addFunctionsToContract(contract); + addEventsToContract(contract); // call callback for the second time if(callback) @@ -2938,13 +2890,27 @@ var checkForContractAddress = function(contract, abi, callback){ * @method ContractFactory * @param {Array} abi */ -var ContractFactory = function (abi) { +var ContractFactory = function (eth, abi) { + this.eth = eth; this.abi = abi; + + this.new.getData = this.getData.bind(this); }; /** - * Should be called to create new contract on a blockchain + * Should be called to create new ContractFactory * + * @method contract + * @param {Array} abi + * @returns {ContractFactory} new contract factory + */ +//var contract = function (abi) { + //return new ContractFactory(abi); +//}; + +/** + * Should be called to create new contract on a blockchain + * * @method new * @param {Any} contract constructor param1 (optional) * @param {Any} contract constructor param2 (optional) @@ -2953,8 +2919,7 @@ var ContractFactory = function (abi) { * @returns {Contract} returns contract instance */ ContractFactory.prototype.new = function () { - var _this = this; - var contract = new Contract(this.abi); + var contract = new Contract(this.eth, this.abi); // parse arguments var options = {}; // required! @@ -2970,16 +2935,13 @@ ContractFactory.prototype.new = function () { options = args.pop(); } - // throw an error if there are no options - var bytes = encodeConstructorParams(this.abi, args); options.data += bytes; - - if(callback) { + if (callback) { // wait for the contract address adn check if the code was deployed - web3.eth.sendTransaction(options, function (err, hash) { + this.eth.sendTransaction(options, function (err, hash) { if (err) { callback(err); } else { @@ -2989,14 +2951,14 @@ ContractFactory.prototype.new = function () { // call callback for the first time callback(null, contract); - checkForContractAddress(contract, _this.abi, callback); + checkForContractAddress(contract, callback); } }); } else { - var hash = web3.eth.sendTransaction(options); + var hash = this.eth.sendTransaction(options); // add the transaction hash contract.transactionHash = hash; - checkForContractAddress(contract, _this.abi); + checkForContractAddress(contract); } return contract; @@ -3012,19 +2974,39 @@ ContractFactory.prototype.new = function () { * otherwise calls callback function (err, contract) */ ContractFactory.prototype.at = function (address, callback) { - var contract = new Contract(this.abi, address); - // TODO: address is required - - // attach functions - addFunctionsToContract(contract, this.abi); - addEventsToContract(contract, this.abi); + var contract = new Contract(this.eth, this.abi, address); + // this functions are not part of prototype, + // because we dont want to spoil the interface + addFunctionsToContract(contract); + addEventsToContract(contract); + if (callback) { callback(null, contract); - } + } return contract; }; +/** + * Gets the data, which is data to deploy plus constructor params + * + * @method getData + */ +ContractFactory.prototype.getData = function () { + var options = {}; // required! + var args = Array.prototype.slice.call(arguments); + + var last = args[args.length - 1]; + if (utils.isObject(last) && !utils.isArray(last)) { + options = args.pop(); + } + + var bytes = encodeConstructorParams(this.abi, args); + options.data += bytes; + + return options.data; +}; + /** * Should be called to create new contract instance * @@ -3032,31 +3014,34 @@ ContractFactory.prototype.at = function (address, callback) { * @param {Array} abi * @param {Address} contract address */ -var Contract = function (abi, address) { +var Contract = function (eth, abi, address) { + this._eth = eth; + this.transactionHash = null; this.address = address; + this.abi = abi; }; -module.exports = contract; +module.exports = ContractFactory; -},{"../solidity/coder":7,"../utils/utils":20,"../web3":22,"./allevents":23,"./event":27,"./function":30}],26:[function(require,module,exports){ +},{"../solidity/coder":7,"../utils/utils":20,"./allevents":23,"./event":27,"./function":31}],26:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file errors.js * @author Marek Kotewicz * @date 2015 @@ -3067,13 +3052,13 @@ module.exports = { return new Error('Invalid number of input parameters'); }, InvalidConnection: function (host){ - return new Error('CONNECTION ERROR: Couldn\'t connect to node '+ host +', is it running?'); + return new Error('CONNECTION ERROR: Couldn\'t connect to node '+ host +'.'); }, InvalidProvider: function () { - return new Error('Providor not set or invalid'); + return new Error('Provider not set or invalid'); }, InvalidResponse: function (result){ - var message = !!result && !!result.error && !!result.error.message ? result.error.message : 'Invalid JSON RPC response: '+ result; + var message = !!result && !!result.error && !!result.error.message ? result.error.message : 'Invalid JSON RPC response: ' + JSON.stringify(result); return new Error(message); } }; @@ -3081,22 +3066,22 @@ module.exports = { },{}],27:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file event.js * @author Marek Kotewicz * @date 2014 @@ -3112,7 +3097,8 @@ var watches = require('./methods/watches'); /** * This prototype should be used to create event filters */ -var SolidityEvent = function (json, address) { +var SolidityEvent = function (requestManager, json, address) { + this._requestManager = requestManager; this._params = json.inputs; this._name = utils.transformToFullName(json); this._address = address; @@ -3166,7 +3152,7 @@ SolidityEvent.prototype.signature = function () { /** * Should be used to encode indexed params and options to one final object - * + * * @method encode * @param {Object} indexed * @param {Object} options @@ -3197,7 +3183,7 @@ SolidityEvent.prototype.encode = function (indexed, options) { if (value === undefined || value === null) { return null; } - + if (utils.isArray(value)) { return value.map(function (v) { return '0x' + coder.encodeParam(i.type, v); @@ -3219,17 +3205,17 @@ SolidityEvent.prototype.encode = function (indexed, options) { * @return {Object} result object with decoded indexed && not indexed params */ SolidityEvent.prototype.decode = function (data) { - + data.data = data.data || ''; data.topics = data.topics || []; var argTopics = this._anonymous ? data.topics : data.topics.slice(1); var indexedData = argTopics.map(function (topics) { return topics.slice(2); }).join(""); - var indexedParams = coder.decodeParams(this.types(true), indexedData); + var indexedParams = coder.decodeParams(this.types(true), indexedData); var notIndexedData = data.data.slice(2); var notIndexedParams = coder.decodeParams(this.types(false), notIndexedData); - + var result = formatters.outputLogFormatter(data); result.event = this.displayName(); result.address = data.address; @@ -3264,10 +3250,10 @@ SolidityEvent.prototype.execute = function (indexed, options, callback) { indexed = {}; } } - + var o = this.encode(indexed, options); var formatter = this.decode.bind(this); - return new Filter(o, watches.eth(), formatter, callback); + return new Filter(this._requestManager, o, watches.eth(), formatter, callback); }; /** @@ -3288,22 +3274,72 @@ SolidityEvent.prototype.attachToContract = function (contract) { module.exports = SolidityEvent; -},{"../solidity/coder":7,"../utils/sha3":19,"../utils/utils":20,"./filter":28,"./formatters":29,"./methods/watches":40}],28:[function(require,module,exports){ -/* - This file is part of ethereum.js. +},{"../solidity/coder":7,"../utils/sha3":19,"../utils/utils":20,"./filter":29,"./formatters":30,"./methods/watches":41}],28:[function(require,module,exports){ +var formatters = require('./formatters'); +var utils = require('./../utils/utils'); +var Method = require('./method'); +var Property = require('./property'); - ethereum.js is free software: you can redistribute it and/or modify +// TODO: refactor, so the input params are not altered. +// it's necessary to make same 'extension' work with multiple providers +var extend = function (web3) { + /* jshint maxcomplexity:5 */ + var ex = function (extension) { + + var extendedObject; + if (extension.property) { + if (!web3[extension.property]) { + web3[extension.property] = {}; + } + extendedObject = web3[extension.property]; + } else { + extendedObject = web3; + } + + if (extension.methods) { + extension.methods.forEach(function (method) { + method.attachToObject(extendedObject); + method.setRequestManager(web3._requestManager); + }); + } + + if (extension.properties) { + extension.properties.forEach(function (property) { + property.attachToObject(extendedObject); + property.setRequestManager(web3._requestManager); + }); + } + }; + + ex.formatters = formatters; + ex.utils = utils; + ex.Method = Method; + ex.Property = Property; + + return ex; +}; + + + +module.exports = extend; + + +},{"./../utils/utils":20,"./formatters":30,"./method":36,"./property":43}],29:[function(require,module,exports){ +/* + This file is part of web3.js. + + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file filter.js * @authors: @@ -3315,7 +3351,6 @@ module.exports = SolidityEvent; * @date 2014 */ -var RequestManager = require('./requestmanager'); var formatters = require('./formatters'); var utils = require('../utils/utils'); @@ -3345,7 +3380,7 @@ var getOptions = function (options) { if (utils.isString(options)) { return options; - } + } options = options || {}; @@ -3355,14 +3390,14 @@ var getOptions = function (options) { return (utils.isArray(topic)) ? topic.map(toTopic) : toTopic(topic); }); - // lazy load return { topics: options.topics, + from: options.from, to: options.to, address: options.address, fromBlock: formatters.inputBlockNumberFormatter(options.fromBlock), - toBlock: formatters.inputBlockNumberFormatter(options.toBlock) - }; + toBlock: formatters.inputBlockNumberFormatter(options.toBlock) + }; }; /** @@ -3370,7 +3405,7 @@ Adds the callback and sets up the methods, to iterate over the results. @method getLogsAtStart @param {Object} self -@param {funciton} +@param {funciton} */ var getLogsAtStart = function(self, callback){ // call getFilterLogs for the first watch callback start @@ -3415,19 +3450,21 @@ var pollFilter = function(self) { } }; - RequestManager.getInstance().startPolling({ + self.requestManager.startPolling({ method: self.implementation.poll.call, params: [self.filterId], }, self.filterId, onMessage, self.stopWatching.bind(self)); }; -var Filter = function (options, methods, formatter, callback) { +var Filter = function (requestManager, options, methods, formatter, callback) { var self = this; var implementation = {}; methods.forEach(function (method) { + method.setRequestManager(requestManager); method.attachToObject(implementation); }); + this.requestManager = requestManager; this.options = getOptions(options); this.implementation = implementation; this.filterId = null; @@ -3479,7 +3516,7 @@ Filter.prototype.watch = function (callback) { }; Filter.prototype.stopWatching = function () { - RequestManager.getInstance().stopPolling(this.filterId); + this.requestManager.stopPolling(this.filterId); // remove filter async this.implementation.uninstallFilter(this.filterId, function(){}); this.callbacks = []; @@ -3519,24 +3556,24 @@ Filter.prototype.get = function (callback) { module.exports = Filter; -},{"../utils/utils":20,"./formatters":29,"./requestmanager":43}],29:[function(require,module,exports){ +},{"../utils/utils":20,"./formatters":30}],30:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file formatters.js * @author Marek Kotewicz * @author Fabian Vogelsteller @@ -3603,7 +3640,7 @@ var inputCallFormatter = function (options){ options[key] = utils.fromDecimal(options[key]); }); - return options; + return options; }; /** @@ -3628,12 +3665,12 @@ var inputTransactionFormatter = function (options){ options[key] = utils.fromDecimal(options[key]); }); - return options; + return options; }; /** * Formats the output of a transaction to its proper values - * + * * @method outputTransactionFormatter * @param {Object} tx * @returns {Object} @@ -3652,7 +3689,7 @@ var outputTransactionFormatter = function (tx){ /** * Formats the output of a transaction receipt to its proper values - * + * * @method outputTransactionReceiptFormatter * @param {Object} receipt * @returns {Object} @@ -3678,7 +3715,7 @@ var outputTransactionReceiptFormatter = function (receipt){ * Formats the output of a block to its proper values * * @method outputBlockFormatter - * @param {Object} block + * @param {Object} block * @returns {Object} */ var outputBlockFormatter = function(block) { @@ -3706,7 +3743,7 @@ var outputBlockFormatter = function(block) { /** * Formats the output of a log - * + * * @method outputLogFormatter * @param {Object} log object * @returns {Object} log @@ -3731,7 +3768,7 @@ var outputLogFormatter = function(log) { */ var inputPostFormatter = function(post) { - post.payload = utils.toHex(post.payload); + // post.payload = utils.toHex(post.payload); post.ttl = utils.fromDecimal(post.ttl); post.workToProve = utils.fromDecimal(post.workToProve); post.priority = utils.fromDecimal(post.priority); @@ -3743,10 +3780,11 @@ var inputPostFormatter = function(post) { // format the following options post.topics = post.topics.map(function(topic){ - return utils.fromUtf8(topic); + // convert only if not hex + return (topic.indexOf('0x') === 0) ? topic : utils.fromUtf8(topic); }); - return post; + return post; }; /** @@ -3762,19 +3800,19 @@ var outputPostFormatter = function(post){ post.sent = utils.toDecimal(post.sent); post.ttl = utils.toDecimal(post.ttl); post.workProved = utils.toDecimal(post.workProved); - post.payloadRaw = post.payload; - post.payload = utils.toUtf8(post.payload); + // post.payloadRaw = post.payload; + // post.payload = utils.toAscii(post.payload); - if (utils.isJson(post.payload)) { - post.payload = JSON.parse(post.payload); - } + // if (utils.isJson(post.payload)) { + // post.payload = JSON.parse(post.payload); + // } // format the following options if (!post.topics) { post.topics = []; } post.topics = post.topics.map(function(topic){ - return utils.toUtf8(topic); + return utils.toAscii(topic); }); return post; @@ -3819,22 +3857,22 @@ module.exports = { }; -},{"../utils/config":18,"../utils/utils":20,"./iban":32}],30:[function(require,module,exports){ +},{"../utils/config":18,"../utils/utils":20,"./iban":33}],31:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** * @file function.js @@ -3842,7 +3880,6 @@ module.exports = { * @date 2015 */ -var web3 = require('../web3'); var coder = require('../solidity/coder'); var utils = require('../utils/utils'); var formatters = require('./formatters'); @@ -3851,7 +3888,8 @@ var sha3 = require('../utils/sha3'); /** * This prototype should be used to call/sendTransaction to solidity functions */ -var SolidityFunction = function (json, address) { +var SolidityFunction = function (eth, json, address) { + this._eth = eth; this._inputTypes = json.inputs.map(function (i) { return i.type; }); @@ -3931,12 +3969,12 @@ SolidityFunction.prototype.call = function () { if (!callback) { - var output = web3.eth.call(payload, defaultBlock); + var output = this._eth.call(payload, defaultBlock); return this.unpackOutput(output); - } - + } + var self = this; - web3.eth.call(payload, defaultBlock, function (error, output) { + this._eth.call(payload, defaultBlock, function (error, output) { callback(error, self.unpackOutput(output)); }); }; @@ -3945,7 +3983,6 @@ SolidityFunction.prototype.call = function () { * Should be used to sendTransaction to solidity function * * @method sendTransaction - * @param {Object} options */ SolidityFunction.prototype.sendTransaction = function () { var args = Array.prototype.slice.call(arguments).filter(function (a) {return a !== undefined; }); @@ -3953,17 +3990,16 @@ SolidityFunction.prototype.sendTransaction = function () { var payload = this.toPayload(args); if (!callback) { - return web3.eth.sendTransaction(payload); + return this._eth.sendTransaction(payload); } - web3.eth.sendTransaction(payload, callback); + this._eth.sendTransaction(payload, callback); }; /** * Should be used to estimateGas of solidity function * * @method estimateGas - * @param {Object} options */ SolidityFunction.prototype.estimateGas = function () { var args = Array.prototype.slice.call(arguments); @@ -3971,10 +4007,23 @@ SolidityFunction.prototype.estimateGas = function () { var payload = this.toPayload(args); if (!callback) { - return web3.eth.estimateGas(payload); + return this._eth.estimateGas(payload); } - web3.eth.estimateGas(payload, callback); + this._eth.estimateGas(payload, callback); +}; + +/** + * Return the encoded data of the call + * + * @method getData + * @return {String} the encoded data + */ +SolidityFunction.prototype.getData = function () { + var args = Array.prototype.slice.call(arguments); + var payload = this.toPayload(args); + + return payload.data; }; /** @@ -4008,11 +4057,11 @@ SolidityFunction.prototype.request = function () { var callback = this.extractCallback(args); var payload = this.toPayload(args); var format = this.unpackOutput.bind(this); - + return { method: this._constant ? 'eth_call' : 'eth_sendTransaction', callback: callback, - params: [payload], + params: [payload], format: format }; }; @@ -4046,6 +4095,7 @@ SolidityFunction.prototype.attachToContract = function (contract) { execute.call = this.call.bind(this); execute.sendTransaction = this.sendTransaction.bind(this); execute.estimateGas = this.estimateGas.bind(this); + execute.getData = this.getData.bind(this); var displayName = this.displayName(); if (!contract[displayName]) { contract[displayName] = execute; @@ -4056,22 +4106,22 @@ SolidityFunction.prototype.attachToContract = function (contract) { module.exports = SolidityFunction; -},{"../solidity/coder":7,"../utils/sha3":19,"../utils/utils":20,"../web3":22,"./formatters":29}],31:[function(require,module,exports){ +},{"../solidity/coder":7,"../utils/sha3":19,"../utils/utils":20,"./formatters":30}],32:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file httpprovider.js * @authors: @@ -4143,7 +4193,7 @@ HttpProvider.prototype.send = function (payload) { try { result = JSON.parse(result); } catch(e) { - throw errors.InvalidResponse(request.responseText); + throw errors.InvalidResponse(request.responseText); } return result; @@ -4157,7 +4207,7 @@ HttpProvider.prototype.send = function (payload) { * @param {Function} callback triggered on end with (err, result) */ HttpProvider.prototype.sendAsync = function (payload, callback) { - var request = this.prepareRequest(true); + var request = this.prepareRequest(true); request.onreadystatechange = function() { if (request.readyState === 4) { @@ -4167,13 +4217,13 @@ HttpProvider.prototype.sendAsync = function (payload, callback) { try { result = JSON.parse(result); } catch(e) { - error = errors.InvalidResponse(request.responseText); + error = errors.InvalidResponse(request.responseText); } callback(error, result); } }; - + try { request.send(JSON.stringify(payload)); } catch(error) { @@ -4204,24 +4254,24 @@ HttpProvider.prototype.isConnected = function() { module.exports = HttpProvider; -},{"./errors":26,"xmlhttprequest":17}],32:[function(require,module,exports){ +},{"./errors":26,"xmlhttprequest":17}],33:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file iban.js * @author Marek Kotewicz * @date 2015 @@ -4421,7 +4471,7 @@ Iban.prototype.address = function () { var base36 = this._iban.substr(4); var asBn = new BigNumber(base36, 36); return padLeft(asBn.toString(16), 20); - } + } return ''; }; @@ -4433,22 +4483,22 @@ Iban.prototype.toString = function () { module.exports = Iban; -},{"bignumber.js":"bignumber.js"}],33:[function(require,module,exports){ +},{"bignumber.js":"bignumber.js"}],34:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file ipcprovider.js * @authors: @@ -4461,23 +4511,12 @@ module.exports = Iban; var utils = require('../utils/utils'); var errors = require('./errors'); -var errorTimeout = function (method, id) { - var err = { - "jsonrpc": "2.0", - "error": { - "code": -32603, - "message": "IPC Request timed out for method \'" + method + "\'" - }, - "id": id - }; - return JSON.stringify(err); -}; var IpcProvider = function (path, net) { var _this = this; this.responseCallbacks = {}; this.path = path; - + this.connection = net.connect({path: this.path}); this.connection.on('error', function(e){ @@ -4487,7 +4526,7 @@ var IpcProvider = function (path, net) { this.connection.on('end', function(){ _this._timeout(); - }); + }); // LISTEN FOR CONNECTION RESPONSES @@ -4526,7 +4565,7 @@ Will parse the response and make an array out of it. IpcProvider.prototype._parseResponse = function(data) { var _this = this, returnValues = []; - + // DE-CHUNKER var dechunkedData = data .replace(/\}\{/g,'}|--|{') // }{ @@ -4594,7 +4633,7 @@ Timeout all requests when the end/error event is fired IpcProvider.prototype._timeout = function() { for(var key in this.responseCallbacks) { if(this.responseCallbacks.hasOwnProperty(key)){ - this.responseCallbacks[key](errorTimeout(this.responseCallbacks[key].method, key)); + this.responseCallbacks[key](errors.InvalidConnection('on IPC')); delete this.responseCallbacks[key]; } } @@ -4630,7 +4669,7 @@ IpcProvider.prototype.send = function (payload) { try { result = JSON.parse(data); } catch(e) { - throw errors.InvalidResponse(data); + throw errors.InvalidResponse(data); } return result; @@ -4653,22 +4692,22 @@ IpcProvider.prototype.sendAsync = function (payload, callback) { module.exports = IpcProvider; -},{"../utils/utils":20,"./errors":26}],34:[function(require,module,exports){ +},{"../utils/utils":20,"./errors":26}],35:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file jsonrpc.js * @authors: @@ -4746,22 +4785,22 @@ Jsonrpc.prototype.toBatchPayload = function (messages) { module.exports = Jsonrpc; -},{}],35:[function(require,module,exports){ +},{}],36:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** * @file method.js @@ -4769,7 +4808,6 @@ module.exports = Jsonrpc; * @date 2015 */ -var RequestManager = require('./requestmanager'); var utils = require('../utils/utils'); var errors = require('./errors'); @@ -4779,6 +4817,11 @@ var Method = function (options) { this.params = options.params || 0; this.inputFormatter = options.inputFormatter; this.outputFormatter = options.outputFormatter; + this.requestManager = null; +}; + +Method.prototype.setRequestManager = function (rm) { + this.requestManager = rm; }; /** @@ -4807,7 +4850,7 @@ Method.prototype.extractCallback = function (args) { /** * Should be called to check if the number of arguments is correct - * + * * @method validateArgs * @param {Array} arguments * @throws {Error} if it is not @@ -4820,7 +4863,7 @@ Method.prototype.validateArgs = function (args) { /** * Should be called to format input args of method - * + * * @method formatInput * @param {Array} * @return {Array} @@ -4846,26 +4889,6 @@ Method.prototype.formatOutput = function (result) { return this.outputFormatter && result ? this.outputFormatter(result) : result; }; -/** - * Should attach function to method - * - * @method attachToObject - * @param {Object} - * @param {Function} - */ -Method.prototype.attachToObject = function (obj) { - var func = this.send.bind(this); - func.request = this.request.bind(this); - func.call = this.call; // that's ugly. filter.js uses it - var name = this.name.split('.'); - if (name.length > 1) { - obj[name[0]] = obj[name[0]] || {}; - obj[name[0]][name[1]] = func; - } else { - obj[name[0]] = func; - } -}; - /** * Should create payload from given input args * @@ -4886,6 +4909,33 @@ Method.prototype.toPayload = function (args) { }; }; +Method.prototype.attachToObject = function (obj) { + var func = this.buildCall(); + func.call = this.call; // TODO!!! that's ugly. filter.js uses it + var name = this.name.split('.'); + if (name.length > 1) { + obj[name[0]] = obj[name[0]] || {}; + obj[name[0]][name[1]] = func; + } else { + obj[name[0]] = func; + } +}; + +Method.prototype.buildCall = function() { + var method = this; + var send = function () { + var payload = method.toPayload(Array.prototype.slice.call(arguments)); + if (payload.callback) { + return method.requestManager.sendAsync(payload, function (err, result) { + payload.callback(err, method.formatOutput(result)); + }); + } + return method.formatOutput(method.requestManager.send(payload)); + }; + send.request = this.request.bind(this); + return send; +}; + /** * Should be called to create pure JSONRPC request which can be used in batch request * @@ -4899,43 +4949,25 @@ Method.prototype.request = function () { return payload; }; -/** - * Should send request to the API - * - * @method send - * @param list of params - * @return result - */ -Method.prototype.send = function () { - var payload = this.toPayload(Array.prototype.slice.call(arguments)); - if (payload.callback) { - var self = this; - return RequestManager.getInstance().sendAsync(payload, function (err, result) { - payload.callback(err, self.formatOutput(result)); - }); - } - return this.formatOutput(RequestManager.getInstance().send(payload)); -}; - module.exports = Method; -},{"../utils/utils":20,"./errors":26,"./requestmanager":43}],36:[function(require,module,exports){ +},{"../utils/utils":20,"./errors":26}],37:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file db.js * @authors: @@ -4945,55 +4977,65 @@ module.exports = Method; var Method = require('../method'); -var putString = new Method({ - name: 'putString', - call: 'db_putString', - params: 3 -}); +var DB = function (web3) { + this._requestManager = web3._requestManager; - -var getString = new Method({ - name: 'getString', - call: 'db_getString', - params: 2 -}); - -var putHex = new Method({ - name: 'putHex', - call: 'db_putHex', - params: 3 -}); - -var getHex = new Method({ - name: 'getHex', - call: 'db_getHex', - params: 2 -}); - -var methods = [ - putString, getString, putHex, getHex -]; - -module.exports = { - methods: methods + var self = this; + + methods().forEach(function(method) { + method.attachToObject(self); + method.setRequestManager(web3._requestManager); + }); }; -},{"../method":35}],37:[function(require,module,exports){ -/* - This file is part of ethereum.js. +var methods = function () { + var putString = new Method({ + name: 'putString', + call: 'db_putString', + params: 3 + }); - ethereum.js is free software: you can redistribute it and/or modify + var getString = new Method({ + name: 'getString', + call: 'db_getString', + params: 2 + }); + + var putHex = new Method({ + name: 'putHex', + call: 'db_putHex', + params: 3 + }); + + var getHex = new Method({ + name: 'getHex', + call: 'db_getHex', + params: 2 + }); + + return [ + putString, getString, putHex, getHex + ]; +}; + +module.exports = DB; + +},{"../method":36}],38:[function(require,module,exports){ +/* + This file is part of web3.js. + + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** * @file eth.js @@ -5002,38 +5044,20 @@ module.exports = { * @date 2015 */ -/** - * Web3 - * - * @module web3 - */ - -/** - * Eth methods and properties - * - * An example method object can look as follows: - * - * { - * name: 'getBlock', - * call: blockCall, - * params: 2, - * outputFormatter: formatters.outputBlockFormatter, - * inputFormatter: [ // can be a formatter funciton or an array of functions. Where each item in the array will be used for one parameter - * utils.toHex, // formats paramter 1 - * function(param){ return !!param; } // formats paramter 2 - * ] - * }, - * - * @class [web3] eth - * @constructor - */ - "use strict"; var formatters = require('../formatters'); var utils = require('../../utils/utils'); var Method = require('../method'); var Property = require('../property'); +var c = require('../../utils/config'); +var Contract = require('../contract'); +var watches = require('./watches'); +var Filter = require('../filter'); +var IsSyncing = require('../syncing'); +var namereg = require('../namereg'); +var Iban = require('../iban'); +var transfer = require('../transfer'); var blockCall = function (args) { return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? "eth_getBlockByHash" : "eth_getBlockByNumber"; @@ -5055,243 +5079,300 @@ var uncleCountCall = function (args) { return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getUncleCountByBlockHash' : 'eth_getUncleCountByBlockNumber'; }; -/// @returns an array of objects describing web3.eth api methods +function Eth(web3) { + this._requestManager = web3._requestManager; -var getBalance = new Method({ - name: 'getBalance', - call: 'eth_getBalance', - params: 2, - inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter], - outputFormatter: formatters.outputBigNumberFormatter + var self = this; + + methods().forEach(function(method) { + method.attachToObject(self); + method.setRequestManager(self._requestManager); + }); + + properties().forEach(function(p) { + p.attachToObject(self); + p.setRequestManager(self._requestManager); + }); + + + this.iban = Iban; + this.sendIBANTransaction = transfer.bind(null, this); +} + +Object.defineProperty(Eth.prototype, 'defaultBlock', { + get: function () { + return c.defaultBlock; + }, + set: function (val) { + c.defaultBlock = val; + return val; + } }); -var getStorageAt = new Method({ - name: 'getStorageAt', - call: 'eth_getStorageAt', - params: 3, - inputFormatter: [null, utils.toHex, formatters.inputDefaultBlockNumberFormatter] +Object.defineProperty(Eth.prototype, 'defaultAccount', { + get: function () { + return c.defaultAccount; + }, + set: function (val) { + c.defaultAccount = val; + return val; + } }); -var getCode = new Method({ - name: 'getCode', - call: 'eth_getCode', - params: 2, - inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter] -}); - -var getBlock = new Method({ - name: 'getBlock', - call: blockCall, - params: 2, - inputFormatter: [formatters.inputBlockNumberFormatter, function (val) { return !!val; }], - outputFormatter: formatters.outputBlockFormatter -}); - -var getUncle = new Method({ - name: 'getUncle', - call: uncleCall, - params: 2, - inputFormatter: [formatters.inputBlockNumberFormatter, utils.toHex], - outputFormatter: formatters.outputBlockFormatter, - -}); - -var getCompilers = new Method({ - name: 'getCompilers', - call: 'eth_getCompilers', - params: 0 -}); - -var getBlockTransactionCount = new Method({ - name: 'getBlockTransactionCount', - call: getBlockTransactionCountCall, - params: 1, - inputFormatter: [formatters.inputBlockNumberFormatter], - outputFormatter: utils.toDecimal -}); - -var getBlockUncleCount = new Method({ - name: 'getBlockUncleCount', - call: uncleCountCall, - params: 1, - inputFormatter: [formatters.inputBlockNumberFormatter], - outputFormatter: utils.toDecimal -}); - -var getTransaction = new Method({ - name: 'getTransaction', - call: 'eth_getTransactionByHash', - params: 1, - outputFormatter: formatters.outputTransactionFormatter -}); - -var getTransactionFromBlock = new Method({ - name: 'getTransactionFromBlock', - call: transactionFromBlockCall, - params: 2, - inputFormatter: [formatters.inputBlockNumberFormatter, utils.toHex], - outputFormatter: formatters.outputTransactionFormatter -}); - -var getTransactionReceipt = new Method({ - name: 'getTransactionReceipt', - call: 'eth_getTransactionReceipt', - params: 1, - outputFormatter: formatters.outputTransactionReceiptFormatter -}); - -var getTransactionCount = new Method({ - name: 'getTransactionCount', - call: 'eth_getTransactionCount', - params: 2, - inputFormatter: [null, formatters.inputDefaultBlockNumberFormatter], - outputFormatter: utils.toDecimal -}); - -var sendRawTransaction = new Method({ - name: 'sendRawTransaction', - call: 'eth_sendRawTransaction', - params: 1, - inputFormatter: [null] -}); - -var sendTransaction = new Method({ - name: 'sendTransaction', - call: 'eth_sendTransaction', - params: 1, - inputFormatter: [formatters.inputTransactionFormatter] -}); - -var call = new Method({ - name: 'call', - call: 'eth_call', - params: 2, - inputFormatter: [formatters.inputCallFormatter, formatters.inputDefaultBlockNumberFormatter] -}); - -var estimateGas = new Method({ - name: 'estimateGas', - call: 'eth_estimateGas', - params: 1, - inputFormatter: [formatters.inputCallFormatter], - outputFormatter: utils.toDecimal -}); - -var compileSolidity = new Method({ - name: 'compile.solidity', - call: 'eth_compileSolidity', - params: 1 -}); - -var compileLLL = new Method({ - name: 'compile.lll', - call: 'eth_compileLLL', - params: 1 -}); - -var compileSerpent = new Method({ - name: 'compile.serpent', - call: 'eth_compileSerpent', - params: 1 -}); - -var submitWork = new Method({ - name: 'submitWork', - call: 'eth_submitWork', - params: 3 -}); - -var getWork = new Method({ - name: 'getWork', - call: 'eth_getWork', - params: 0 -}); - -var methods = [ - getBalance, - getStorageAt, - getCode, - getBlock, - getUncle, - getCompilers, - getBlockTransactionCount, - getBlockUncleCount, - getTransaction, - getTransactionFromBlock, - getTransactionReceipt, - getTransactionCount, - call, - estimateGas, - sendRawTransaction, - sendTransaction, - compileSolidity, - compileLLL, - compileSerpent, - submitWork, - getWork -]; - -/// @returns an array of objects describing web3.eth api properties - - - -var properties = [ - new Property({ - name: 'coinbase', - getter: 'eth_coinbase' - }), - new Property({ - name: 'mining', - getter: 'eth_mining' - }), - new Property({ - name: 'hashrate', - getter: 'eth_hashrate', - outputFormatter: utils.toDecimal - }), - new Property({ - name: 'syncing', - getter: 'eth_syncing', - outputFormatter: formatters.outputSyncingFormatter - }), - new Property({ - name: 'gasPrice', - getter: 'eth_gasPrice', +var methods = function () { + var getBalance = new Method({ + name: 'getBalance', + call: 'eth_getBalance', + params: 2, + inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter], outputFormatter: formatters.outputBigNumberFormatter - }), - new Property({ - name: 'accounts', - getter: 'eth_accounts' - }), - new Property({ - name: 'blockNumber', - getter: 'eth_blockNumber', - outputFormatter: utils.toDecimal - }) -]; + }); -module.exports = { - methods: methods, - properties: properties + var getStorageAt = new Method({ + name: 'getStorageAt', + call: 'eth_getStorageAt', + params: 3, + inputFormatter: [null, utils.toHex, formatters.inputDefaultBlockNumberFormatter] + }); + + var getCode = new Method({ + name: 'getCode', + call: 'eth_getCode', + params: 2, + inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter] + }); + + var getBlock = new Method({ + name: 'getBlock', + call: blockCall, + params: 2, + inputFormatter: [formatters.inputBlockNumberFormatter, function (val) { return !!val; }], + outputFormatter: formatters.outputBlockFormatter + }); + + var getUncle = new Method({ + name: 'getUncle', + call: uncleCall, + params: 2, + inputFormatter: [formatters.inputBlockNumberFormatter, utils.toHex], + outputFormatter: formatters.outputBlockFormatter, + + }); + + var getCompilers = new Method({ + name: 'getCompilers', + call: 'eth_getCompilers', + params: 0 + }); + + var getBlockTransactionCount = new Method({ + name: 'getBlockTransactionCount', + call: getBlockTransactionCountCall, + params: 1, + inputFormatter: [formatters.inputBlockNumberFormatter], + outputFormatter: utils.toDecimal + }); + + var getBlockUncleCount = new Method({ + name: 'getBlockUncleCount', + call: uncleCountCall, + params: 1, + inputFormatter: [formatters.inputBlockNumberFormatter], + outputFormatter: utils.toDecimal + }); + + var getTransaction = new Method({ + name: 'getTransaction', + call: 'eth_getTransactionByHash', + params: 1, + outputFormatter: formatters.outputTransactionFormatter + }); + + var getTransactionFromBlock = new Method({ + name: 'getTransactionFromBlock', + call: transactionFromBlockCall, + params: 2, + inputFormatter: [formatters.inputBlockNumberFormatter, utils.toHex], + outputFormatter: formatters.outputTransactionFormatter + }); + + var getTransactionReceipt = new Method({ + name: 'getTransactionReceipt', + call: 'eth_getTransactionReceipt', + params: 1, + outputFormatter: formatters.outputTransactionReceiptFormatter + }); + + var getTransactionCount = new Method({ + name: 'getTransactionCount', + call: 'eth_getTransactionCount', + params: 2, + inputFormatter: [null, formatters.inputDefaultBlockNumberFormatter], + outputFormatter: utils.toDecimal + }); + + var sendRawTransaction = new Method({ + name: 'sendRawTransaction', + call: 'eth_sendRawTransaction', + params: 1, + inputFormatter: [null] + }); + + var sendTransaction = new Method({ + name: 'sendTransaction', + call: 'eth_sendTransaction', + params: 1, + inputFormatter: [formatters.inputTransactionFormatter] + }); + + var call = new Method({ + name: 'call', + call: 'eth_call', + params: 2, + inputFormatter: [formatters.inputCallFormatter, formatters.inputDefaultBlockNumberFormatter] + }); + + var estimateGas = new Method({ + name: 'estimateGas', + call: 'eth_estimateGas', + params: 1, + inputFormatter: [formatters.inputCallFormatter], + outputFormatter: utils.toDecimal + }); + + var compileSolidity = new Method({ + name: 'compile.solidity', + call: 'eth_compileSolidity', + params: 1 + }); + + var compileLLL = new Method({ + name: 'compile.lll', + call: 'eth_compileLLL', + params: 1 + }); + + var compileSerpent = new Method({ + name: 'compile.serpent', + call: 'eth_compileSerpent', + params: 1 + }); + + var submitWork = new Method({ + name: 'submitWork', + call: 'eth_submitWork', + params: 3 + }); + + var getWork = new Method({ + name: 'getWork', + call: 'eth_getWork', + params: 0 + }); + + return [ + getBalance, + getStorageAt, + getCode, + getBlock, + getUncle, + getCompilers, + getBlockTransactionCount, + getBlockUncleCount, + getTransaction, + getTransactionFromBlock, + getTransactionReceipt, + getTransactionCount, + call, + estimateGas, + sendRawTransaction, + sendTransaction, + compileSolidity, + compileLLL, + compileSerpent, + submitWork, + getWork + ]; }; -},{"../../utils/utils":20,"../formatters":29,"../method":35,"../property":42}],38:[function(require,module,exports){ -/* - This file is part of ethereum.js. +var properties = function () { + return [ + new Property({ + name: 'coinbase', + getter: 'eth_coinbase' + }), + new Property({ + name: 'mining', + getter: 'eth_mining' + }), + new Property({ + name: 'hashrate', + getter: 'eth_hashrate', + outputFormatter: utils.toDecimal + }), + new Property({ + name: 'syncing', + getter: 'eth_syncing', + outputFormatter: formatters.outputSyncingFormatter + }), + new Property({ + name: 'gasPrice', + getter: 'eth_gasPrice', + outputFormatter: formatters.outputBigNumberFormatter + }), + new Property({ + name: 'accounts', + getter: 'eth_accounts' + }), + new Property({ + name: 'blockNumber', + getter: 'eth_blockNumber', + outputFormatter: utils.toDecimal + }) + ]; +}; - ethereum.js is free software: you can redistribute it and/or modify +Eth.prototype.contract = function (abi) { + var factory = new Contract(this, abi); + return factory; +}; + +Eth.prototype.filter = function (fil, callback) { + return new Filter(this._requestManager, fil, watches.eth(), formatters.outputLogFormatter, callback); +}; + +Eth.prototype.namereg = function () { + return this.contract(namereg.global.abi).at(namereg.global.address); +}; + +Eth.prototype.icapNamereg = function () { + return this.contract(namereg.icap.abi).at(namereg.icap.address); +}; + +Eth.prototype.isSyncing = function (callback) { + return new IsSyncing(this._requestManager, callback); +}; + +module.exports = Eth; + + +},{"../../utils/config":18,"../../utils/utils":20,"../contract":25,"../filter":29,"../formatters":30,"../iban":33,"../method":36,"../namereg":42,"../property":43,"../syncing":46,"../transfer":47,"./watches":41}],39:[function(require,module,exports){ +/* + This file is part of web3.js. + + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file eth.js * @authors: @@ -5302,46 +5383,50 @@ module.exports = { var utils = require('../../utils/utils'); var Property = require('../property'); -/// @returns an array of objects describing web3.eth api methods -var methods = [ -]; +var Net = function (web3) { + this._requestManager = web3._requestManager; -/// @returns an array of objects describing web3.eth api properties -var properties = [ - new Property({ - name: 'listening', - getter: 'net_listening' - }), - new Property({ - name: 'peerCount', - getter: 'net_peerCount', - outputFormatter: utils.toDecimal - }) -]; + var self = this; - -module.exports = { - methods: methods, - properties: properties + properties().forEach(function(p) { + p.attachToObject(self); + p.setRequestManager(web3._requestManager); + }); }; +/// @returns an array of objects describing web3.eth api properties +var properties = function () { + return [ + new Property({ + name: 'listening', + getter: 'net_listening' + }), + new Property({ + name: 'peerCount', + getter: 'net_peerCount', + outputFormatter: utils.toDecimal + }) + ]; +}; -},{"../../utils/utils":20,"../property":42}],39:[function(require,module,exports){ +module.exports = Net; + +},{"../../utils/utils":20,"../property":43}],40:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file shh.js * @authors: @@ -5351,67 +5436,85 @@ module.exports = { var Method = require('../method'); var formatters = require('../formatters'); +var Filter = require('../filter'); +var watches = require('./watches'); -var post = new Method({ - name: 'post', - call: 'shh_post', - params: 1, - inputFormatter: [formatters.inputPostFormatter] -}); +var Shh = function (web3) { + this._requestManager = web3._requestManager; -var newIdentity = new Method({ - name: 'newIdentity', - call: 'shh_newIdentity', - params: 0 -}); + var self = this; -var hasIdentity = new Method({ - name: 'hasIdentity', - call: 'shh_hasIdentity', - params: 1 -}); - -var newGroup = new Method({ - name: 'newGroup', - call: 'shh_newGroup', - params: 0 -}); - -var addToGroup = new Method({ - name: 'addToGroup', - call: 'shh_addToGroup', - params: 0 -}); - -var methods = [ - post, - newIdentity, - hasIdentity, - newGroup, - addToGroup -]; - -module.exports = { - methods: methods + methods().forEach(function(method) { + method.attachToObject(self); + method.setRequestManager(self._requestManager); + }); }; +Shh.prototype.filter = function (fil, callback) { + return new Filter(this._requestManager, fil, watches.shh(), formatters.outputPostFormatter, callback); +}; -},{"../formatters":29,"../method":35}],40:[function(require,module,exports){ +var methods = function () { + + var post = new Method({ + name: 'post', + call: 'shh_post', + params: 1, + inputFormatter: [formatters.inputPostFormatter] + }); + + var newIdentity = new Method({ + name: 'newIdentity', + call: 'shh_newIdentity', + params: 0 + }); + + var hasIdentity = new Method({ + name: 'hasIdentity', + call: 'shh_hasIdentity', + params: 1 + }); + + var newGroup = new Method({ + name: 'newGroup', + call: 'shh_newGroup', + params: 0 + }); + + var addToGroup = new Method({ + name: 'addToGroup', + call: 'shh_addToGroup', + params: 0 + }); + + return [ + post, + newIdentity, + hasIdentity, + newGroup, + addToGroup + ]; +}; + +module.exports = Shh; + + +},{"../filter":29,"../formatters":30,"../method":36,"./watches":41}],41:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file watches.js * @authors: @@ -5512,58 +5615,63 @@ module.exports = { }; -},{"../method":35}],41:[function(require,module,exports){ +},{"../method":36}],42:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file namereg.js * @author Marek Kotewicz * @date 2015 */ -var contract = require('./contract'); var globalRegistrarAbi = require('../contracts/GlobalRegistrar.json'); var icapRegistrarAbi= require('../contracts/ICAPRegistrar.json'); var globalNameregAddress = '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; -var ibanNameregAddress = '0xa1a111bc074c9cfa781f0c38e63bd51c91b8af00'; +var icapNameregAddress = '0xa1a111bc074c9cfa781f0c38e63bd51c91b8af00'; module.exports = { - namereg: contract(globalRegistrarAbi).at(globalNameregAddress), - ibanNamereg: contract(icapRegistrarAbi).at(ibanNameregAddress) + global: { + abi: globalRegistrarAbi, + address: globalNameregAddress + }, + icap: { + abi: icapRegistrarAbi, + address: icapNameregAddress + } }; -},{"../contracts/GlobalRegistrar.json":1,"../contracts/ICAPRegistrar.json":2,"./contract":25}],42:[function(require,module,exports){ +},{"../contracts/GlobalRegistrar.json":1,"../contracts/ICAPRegistrar.json":2}],43:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** * @file property.js @@ -5572,7 +5680,6 @@ module.exports = { * @date 2015 */ -var RequestManager = require('./requestmanager'); var utils = require('../utils/utils'); var Property = function (options) { @@ -5581,11 +5688,16 @@ var Property = function (options) { this.setter = options.setter; this.outputFormatter = options.outputFormatter; this.inputFormatter = options.inputFormatter; + this.requestManager = null; +}; + +Property.prototype.setRequestManager = function (rm) { + this.requestManager = rm; }; /** * Should be called to format input args of method - * + * * @method formatInput * @param {Array} * @return {Array} @@ -5618,16 +5730,17 @@ Property.prototype.extractCallback = function (args) { } }; + /** * Should attach function to method - * + * * @method attachToObject * @param {Object} * @param {Function} */ Property.prototype.attachToObject = function (obj) { var proto = { - get: this.get.bind(this), + get: this.buildGet() }; var names = this.name.split('.'); @@ -5639,45 +5752,33 @@ Property.prototype.attachToObject = function (obj) { } Object.defineProperty(obj, name, proto); + obj[asyncGetterName(name)] = this.buildAsyncGet(); +}; - var toAsyncName = function (prefix, name) { - return prefix + name.charAt(0).toUpperCase() + name.slice(1); +var asyncGetterName = function (name) { + return 'get' + name.charAt(0).toUpperCase() + name.slice(1); +}; + +Property.prototype.buildGet = function () { + var property = this; + return function get() { + return property.formatOutput(property.requestManager.send({ + method: property.getter + })); }; - - var func = this.getAsync.bind(this); - func.request = this.request.bind(this); - - obj[toAsyncName('get', name)] = func; }; -/** - * Should be used to get value of the property - * - * @method get - * @return {Object} value of the property - */ -Property.prototype.get = function () { - return this.formatOutput(RequestManager.getInstance().send({ - method: this.getter - })); -}; - -/** - * Should be used to asynchrounously get value of property - * - * @method getAsync - * @param {Function} - */ -Property.prototype.getAsync = function (callback) { - var self = this; - RequestManager.getInstance().sendAsync({ - method: this.getter - }, function (err, result) { - if (err) { - return callback(err); - } - callback(err, self.formatOutput(result)); - }); +Property.prototype.buildAsyncGet = function () { + var property = this; + var get = function (callback) { + property.requestManager.sendAsync({ + method: property.getter + }, function (err, result) { + callback(err, property.formatOutput(result)); + }); + }; + get.request = this.request.bind(this); + return get; }; /** @@ -5700,24 +5801,24 @@ Property.prototype.request = function () { module.exports = Property; -},{"../utils/utils":20,"./requestmanager":43}],43:[function(require,module,exports){ +},{"../utils/utils":20}],44:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file requestmanager.js * @author Jeffrey Wilcke * @author Marek Kotewicz @@ -5739,24 +5840,9 @@ var errors = require('./errors'); * Singleton */ var RequestManager = function (provider) { - // singleton pattern - if (arguments.callee._singletonInstance) { - return arguments.callee._singletonInstance; - } - arguments.callee._singletonInstance = this; - this.provider = provider; this.polls = {}; this.timeout = null; - this.isPolling = false; -}; - -/** - * @return {RequestManager} singleton - */ -RequestManager.getInstance = function () { - var instance = new RequestManager(); - return instance; }; /** @@ -5799,7 +5885,7 @@ RequestManager.prototype.sendAsync = function (data, callback) { if (err) { return callback(err); } - + if (!Jsonrpc.getInstance().isValidResponse(result)) { return callback(errors.InvalidResponse(result)); } @@ -5832,7 +5918,7 @@ RequestManager.prototype.sendBatch = function (data, callback) { } callback(err, results); - }); + }); }; /** @@ -5843,11 +5929,6 @@ RequestManager.prototype.sendBatch = function (data, callback) { */ RequestManager.prototype.setProvider = function (p) { this.provider = p; - - if (this.provider && !this.isPolling) { - this.poll(); - this.isPolling = true; - } }; /** @@ -5863,6 +5944,12 @@ RequestManager.prototype.setProvider = function (p) { */ RequestManager.prototype.startPolling = function (data, pollId, callback, uninstall) { this.polls[pollId] = {data: data, id: pollId, callback: callback, uninstall: uninstall}; + + + // start polling + if (!this.timeout) { + this.poll(); + } }; /** @@ -5873,6 +5960,12 @@ RequestManager.prototype.startPolling = function (data, pollId, callback, uninst */ RequestManager.prototype.stopPolling = function (pollId) { delete this.polls[pollId]; + + // stop polling + if(Object.keys(this.polls).length === 0 && this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } }; /** @@ -5881,6 +5974,8 @@ RequestManager.prototype.stopPolling = function (pollId) { * @method reset */ RequestManager.prototype.reset = function (keepIsSyncing) { + /*jshint maxcomplexity:5 */ + for (var key in this.polls) { // remove all polls, except sync polls, // they need to be removed manually by calling syncing.stopWatching() @@ -5890,11 +5985,11 @@ RequestManager.prototype.reset = function (keepIsSyncing) { } } - if (this.timeout) { + // stop polling + if(Object.keys(this.polls).length === 0 && this.timeout) { clearTimeout(this.timeout); this.timeout = null; } - this.poll(); }; /** @@ -5927,7 +6022,7 @@ RequestManager.prototype.poll = function () { } var payload = Jsonrpc.getInstance().toBatchPayload(pollsData); - + // map the request id to they poll id var pollsIdMap = {}; payload.forEach(function(load, index){ @@ -5957,7 +6052,7 @@ RequestManager.prototype.poll = function () { } else return false; }).filter(function (result) { - return !!result; + return !!result; }).filter(function (result) { var valid = Jsonrpc.getInstance().isValidResponse(result); if (!valid) { @@ -5973,22 +6068,33 @@ RequestManager.prototype.poll = function () { module.exports = RequestManager; -},{"../utils/config":18,"../utils/utils":20,"./errors":26,"./jsonrpc":34}],44:[function(require,module,exports){ -/* - This file is part of ethereum.js. +},{"../utils/config":18,"../utils/utils":20,"./errors":26,"./jsonrpc":35}],45:[function(require,module,exports){ - ethereum.js is free software: you can redistribute it and/or modify + +var Settings = function () { + this.defaultBlock = 'latest'; + this.defaultAccount = undefined; +}; + +module.exports = Settings; + + +},{}],46:[function(require,module,exports){ +/* + This file is part of web3.js. + + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ /** @file syncing.js * @authors: @@ -5996,12 +6102,10 @@ module.exports = RequestManager; * @date 2015 */ -var RequestManager = require('./requestmanager'); -var Method = require('./method'); var formatters = require('./formatters'); var utils = require('../utils/utils'); - +var count = 1; /** Adds the callback and sets up the methods, to iterate over the results. @@ -6010,7 +6114,6 @@ Adds the callback and sets up the methods, to iterate over the results. @param {Object} self */ var pollSyncing = function(self) { - var lastSyncState = false; var onMessage = function (error, sync) { if (error) { @@ -6019,44 +6122,39 @@ var pollSyncing = function(self) { }); } - if(utils.isObject(sync)) - sync = self.implementation.outputFormatter(sync); + if(utils.isObject(sync) && sync.startingBlock) + sync = formatters.outputSyncingFormatter(sync); self.callbacks.forEach(function (callback) { - if(lastSyncState !== sync) { - + if (self.lastSyncState !== sync) { + // call the callback with true first so the app can stop anything, before receiving the sync data - if(!lastSyncState && utils.isObject(sync)) + if(!self.lastSyncState && utils.isObject(sync)) callback(null, true); - + // call on the next CPU cycle, so the actions of the sync stop can be processes first setTimeout(function() { callback(null, sync); - }, 1); - - lastSyncState = sync; + }, 0); + + self.lastSyncState = sync; } }); }; - RequestManager.getInstance().startPolling({ - method: self.implementation.call, + self.requestManager.startPolling({ + method: 'eth_syncing', params: [], }, self.pollId, onMessage, self.stopWatching.bind(self)); }; -var IsSyncing = function (callback) { - this.pollId = 'syncPoll_'+ Math.floor(Math.random() * 1000); +var IsSyncing = function (requestManager, callback) { + this.requestManager = requestManager; + this.pollId = 'syncPoll_'+ count++; this.callbacks = []; - this.implementation = new Method({ - name: 'isSyncing', - call: 'eth_syncing', - params: 0, - outputFormatter: formatters.outputSyncingFormatter - }); - this.addCallback(callback); + this.lastSyncState = false; pollSyncing(this); return this; @@ -6069,40 +6167,37 @@ IsSyncing.prototype.addCallback = function (callback) { }; IsSyncing.prototype.stopWatching = function () { - RequestManager.getInstance().stopPolling(this.pollId); + this.requestManager.stopPolling(this.pollId); this.callbacks = []; }; module.exports = IsSyncing; -},{"../utils/utils":20,"./formatters":29,"./method":35,"./requestmanager":43}],45:[function(require,module,exports){ +},{"../utils/utils":20,"./formatters":30}],47:[function(require,module,exports){ /* - This file is part of ethereum.js. + This file is part of web3.js. - ethereum.js is free software: you can redistribute it and/or modify + web3.js 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. - ethereum.js is distributed in the hope that it will be useful, + web3.js 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 ethereum.js. If not, see . + along with web3.js. If not, see . */ -/** +/** * @file transfer.js * @author Marek Kotewicz * @date 2015 */ -var web3 = require('../web3'); var Iban = require('./iban'); -var namereg = require('./namereg').ibanNamereg; -var contract = require('./contract'); var exchangeAbi = require('../contracts/SmartExchange.json'); /** @@ -6114,25 +6209,25 @@ var exchangeAbi = require('../contracts/SmartExchange.json'); * @param {Value} value to be tranfered * @param {Function} callback, callback */ -var transfer = function (from, to, value, callback) { - var iban = new Iban(to); +var transfer = function (eth, from, to, value, callback) { + var iban = new Iban(to); if (!iban.isValid()) { throw new Error('invalid iban address'); } if (iban.isDirect()) { - return transferToAddress(from, iban.address(), value, callback); + return transferToAddress(eth, from, iban.address(), value, callback); } - + if (!callback) { - var address = namereg.addr(iban.institution()); - return deposit(from, address, value, iban.client()); + var address = eth.icapNamereg().addr(iban.institution()); + return deposit(eth, from, address, value, iban.client()); } - namereg.addr(iban.institution(), function (err, address) { - return deposit(from, address, value, iban.client(), callback); + eth.icapNamereg().addr(iban.institution(), function (err, address) { + return deposit(eth, from, address, value, iban.client(), callback); }); - + }; /** @@ -6144,8 +6239,8 @@ var transfer = function (from, to, value, callback) { * @param {Value} value to be tranfered * @param {Function} callback, callback */ -var transferToAddress = function (from, to, value, callback) { - return web3.eth.sendTransaction({ +var transferToAddress = function (eth, from, to, value, callback) { + return eth.sendTransaction({ address: to, from: from, value: value @@ -6162,9 +6257,9 @@ var transferToAddress = function (from, to, value, callback) { * @param {String} client unique identifier * @param {Function} callback, callback */ -var deposit = function (from, to, value, client, callback) { +var deposit = function (eth, from, to, value, client, callback) { var abi = exchangeAbi; - return contract(abi).at(to).deposit(client, { + return eth.contract(abi).at(to).deposit(client, { from: from, value: value }, callback); @@ -6173,9 +6268,1113 @@ var deposit = function (from, to, value, client, callback) { module.exports = transfer; -},{"../contracts/SmartExchange.json":3,"../web3":22,"./contract":25,"./iban":32,"./namereg":41}],46:[function(require,module,exports){ +},{"../contracts/SmartExchange.json":3,"./iban":33}],48:[function(require,module,exports){ -},{}],47:[function(require,module,exports){ +},{}],49:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Lookup tables + var SBOX = []; + var INV_SBOX = []; + var SUB_MIX_0 = []; + var SUB_MIX_1 = []; + var SUB_MIX_2 = []; + var SUB_MIX_3 = []; + var INV_SUB_MIX_0 = []; + var INV_SUB_MIX_1 = []; + var INV_SUB_MIX_2 = []; + var INV_SUB_MIX_3 = []; + + // Compute lookup tables + (function () { + // Compute double table + var d = []; + for (var i = 0; i < 256; i++) { + if (i < 128) { + d[i] = i << 1; + } else { + d[i] = (i << 1) ^ 0x11b; + } + } + + // Walk GF(2^8) + var x = 0; + var xi = 0; + for (var i = 0; i < 256; i++) { + // Compute sbox + var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); + sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63; + SBOX[x] = sx; + INV_SBOX[sx] = x; + + // Compute multiplication + var x2 = d[x]; + var x4 = d[x2]; + var x8 = d[x4]; + + // Compute sub bytes, mix columns tables + var t = (d[sx] * 0x101) ^ (sx * 0x1010100); + SUB_MIX_0[x] = (t << 24) | (t >>> 8); + SUB_MIX_1[x] = (t << 16) | (t >>> 16); + SUB_MIX_2[x] = (t << 8) | (t >>> 24); + SUB_MIX_3[x] = t; + + // Compute inv sub bytes, inv mix columns tables + var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); + INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8); + INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16); + INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24); + INV_SUB_MIX_3[sx] = t; + + // Compute next counter + if (!x) { + x = xi = 1; + } else { + x = x2 ^ d[d[d[x8 ^ x2]]]; + xi ^= d[d[xi]]; + } + } + }()); + + // Precomputed Rcon lookup + var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; + + /** + * AES block cipher algorithm. + */ + var AES = C_algo.AES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + var keySize = key.sigBytes / 4; + + // Compute number of rounds + var nRounds = this._nRounds = keySize + 6 + + // Compute number of key schedule rows + var ksRows = (nRounds + 1) * 4; + + // Compute key schedule + var keySchedule = this._keySchedule = []; + for (var ksRow = 0; ksRow < ksRows; ksRow++) { + if (ksRow < keySize) { + keySchedule[ksRow] = keyWords[ksRow]; + } else { + var t = keySchedule[ksRow - 1]; + + if (!(ksRow % keySize)) { + // Rot word + t = (t << 8) | (t >>> 24); + + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + + // Mix Rcon + t ^= RCON[(ksRow / keySize) | 0] << 24; + } else if (keySize > 6 && ksRow % keySize == 4) { + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + } + + keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t; + } + } + + // Compute inv key schedule + var invKeySchedule = this._invKeySchedule = []; + for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) { + var ksRow = ksRows - invKsRow; + + if (invKsRow % 4) { + var t = keySchedule[ksRow]; + } else { + var t = keySchedule[ksRow - 4]; + } + + if (invKsRow < 4 || ksRow <= 4) { + invKeySchedule[invKsRow] = t; + } else { + invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^ + INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]]; + } + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX); + }, + + decryptBlock: function (M, offset) { + // Swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + + this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX); + + // Inv swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + }, + + _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) { + // Shortcut + var nRounds = this._nRounds; + + // Get input, add round key + var s0 = M[offset] ^ keySchedule[0]; + var s1 = M[offset + 1] ^ keySchedule[1]; + var s2 = M[offset + 2] ^ keySchedule[2]; + var s3 = M[offset + 3] ^ keySchedule[3]; + + // Key schedule row counter + var ksRow = 4; + + // Rounds + for (var round = 1; round < nRounds; round++) { + // Shift rows, sub bytes, mix columns, add round key + var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++]; + var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++]; + var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++]; + var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++]; + + // Update state + s0 = t0; + s1 = t1; + s2 = t2; + s3 = t3; + } + + // Shift rows, sub bytes, add round key + var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++]; + var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++]; + var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++]; + var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++]; + + // Set output + M[offset] = t0; + M[offset + 1] = t1; + M[offset + 2] = t2; + M[offset + 3] = t3; + }, + + keySize: 256/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); + */ + C.AES = BlockCipher._createHelper(AES); + }()); + + + return CryptoJS.AES; + +})); +},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],50:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * Cipher core components. + */ + CryptoJS.lib.Cipher || (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var Base64 = C_enc.Base64; + var C_algo = C.algo; + var EvpKDF = C_algo.EvpKDF; + + /** + * Abstract base cipher template. + * + * @property {number} keySize This cipher's key size. Default: 4 (128 bits) + * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits) + * @property {number} _ENC_XFORM_MODE A constant representing encryption mode. + * @property {number} _DEC_XFORM_MODE A constant representing decryption mode. + */ + var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + * + * @property {WordArray} iv The IV to use for this operation. + */ + cfg: Base.extend(), + + /** + * Creates this cipher in encryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); + */ + createEncryptor: function (key, cfg) { + return this.create(this._ENC_XFORM_MODE, key, cfg); + }, + + /** + * Creates this cipher in decryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); + */ + createDecryptor: function (key, cfg) { + return this.create(this._DEC_XFORM_MODE, key, cfg); + }, + + /** + * Initializes a newly created cipher. + * + * @param {number} xformMode Either the encryption or decryption transormation mode constant. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @example + * + * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); + */ + init: function (xformMode, key, cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Store transform mode and key + this._xformMode = xformMode; + this._key = key; + + // Set initial values + this.reset(); + }, + + /** + * Resets this cipher to its initial state. + * + * @example + * + * cipher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-cipher logic + this._doReset(); + }, + + /** + * Adds data to be encrypted or decrypted. + * + * @param {WordArray|string} dataUpdate The data to encrypt or decrypt. + * + * @return {WordArray} The data after processing. + * + * @example + * + * var encrypted = cipher.process('data'); + * var encrypted = cipher.process(wordArray); + */ + process: function (dataUpdate) { + // Append + this._append(dataUpdate); + + // Process available blocks + return this._process(); + }, + + /** + * Finalizes the encryption or decryption process. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt. + * + * @return {WordArray} The data after final processing. + * + * @example + * + * var encrypted = cipher.finalize(); + * var encrypted = cipher.finalize('data'); + * var encrypted = cipher.finalize(wordArray); + */ + finalize: function (dataUpdate) { + // Final data update + if (dataUpdate) { + this._append(dataUpdate); + } + + // Perform concrete-cipher logic + var finalProcessedData = this._doFinalize(); + + return finalProcessedData; + }, + + keySize: 128/32, + + ivSize: 128/32, + + _ENC_XFORM_MODE: 1, + + _DEC_XFORM_MODE: 2, + + /** + * Creates shortcut functions to a cipher's object interface. + * + * @param {Cipher} cipher The cipher to create a helper for. + * + * @return {Object} An object with encrypt and decrypt shortcut functions. + * + * @static + * + * @example + * + * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); + */ + _createHelper: (function () { + function selectCipherStrategy(key) { + if (typeof key == 'string') { + return PasswordBasedCipher; + } else { + return SerializableCipher; + } + } + + return function (cipher) { + return { + encrypt: function (message, key, cfg) { + return selectCipherStrategy(key).encrypt(cipher, message, key, cfg); + }, + + decrypt: function (ciphertext, key, cfg) { + return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg); + } + }; + }; + }()) + }); + + /** + * Abstract base stream cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits) + */ + var StreamCipher = C_lib.StreamCipher = Cipher.extend({ + _doFinalize: function () { + // Process partial blocks + var finalProcessedBlocks = this._process(!!'flush'); + + return finalProcessedBlocks; + }, + + blockSize: 1 + }); + + /** + * Mode namespace. + */ + var C_mode = C.mode = {}; + + /** + * Abstract base block cipher mode template. + */ + var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({ + /** + * Creates this mode for encryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor: function (cipher, iv) { + return this.Encryptor.create(cipher, iv); + }, + + /** + * Creates this mode for decryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); + */ + createDecryptor: function (cipher, iv) { + return this.Decryptor.create(cipher, iv); + }, + + /** + * Initializes a newly created mode. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); + */ + init: function (cipher, iv) { + this._cipher = cipher; + this._iv = iv; + } + }); + + /** + * Cipher Block Chaining mode. + */ + var CBC = C_mode.CBC = (function () { + /** + * Abstract base CBC mode. + */ + var CBC = BlockCipherMode.extend(); + + /** + * CBC encryptor. + */ + CBC.Encryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // XOR and encrypt + xorBlock.call(this, words, offset, blockSize); + cipher.encryptBlock(words, offset); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + /** + * CBC decryptor. + */ + CBC.Decryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + // Decrypt and XOR + cipher.decryptBlock(words, offset); + xorBlock.call(this, words, offset, blockSize); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function xorBlock(words, offset, blockSize) { + // Shortcut + var iv = this._iv; + + // Choose mixing block + if (iv) { + var block = iv; + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var block = this._prevBlock; + } + + // XOR blocks + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= block[i]; + } + } + + return CBC; + }()); + + /** + * Padding namespace. + */ + var C_pad = C.pad = {}; + + /** + * PKCS #5/7 padding strategy. + */ + var Pkcs7 = C_pad.Pkcs7 = { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to pad. + * @param {number} blockSize The multiple that the data should be padded to. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding word + var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes; + + // Create padding + var paddingWords = []; + for (var i = 0; i < nPaddingBytes; i += 4) { + paddingWords.push(paddingWord); + } + var padding = WordArray.create(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to unpad. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + /** + * Abstract base block cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits) + */ + var BlockCipher = C_lib.BlockCipher = Cipher.extend({ + /** + * Configuration options. + * + * @property {Mode} mode The block mode to use. Default: CBC + * @property {Padding} padding The padding strategy to use. Default: Pkcs7 + */ + cfg: Cipher.cfg.extend({ + mode: CBC, + padding: Pkcs7 + }), + + reset: function () { + // Reset cipher + Cipher.reset.call(this); + + // Shortcuts + var cfg = this.cfg; + var iv = cfg.iv; + var mode = cfg.mode; + + // Reset block mode + if (this._xformMode == this._ENC_XFORM_MODE) { + var modeCreator = mode.createEncryptor; + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + var modeCreator = mode.createDecryptor; + + // Keep at least one block in the buffer for unpadding + this._minBufferSize = 1; + } + this._mode = modeCreator.call(mode, this, iv && iv.words); + }, + + _doProcessBlock: function (words, offset) { + this._mode.processBlock(words, offset); + }, + + _doFinalize: function () { + // Shortcut + var padding = this.cfg.padding; + + // Finalize + if (this._xformMode == this._ENC_XFORM_MODE) { + // Pad data + padding.pad(this._data, this.blockSize); + + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + + // Unpad data + padding.unpad(finalProcessedBlocks); + } + + return finalProcessedBlocks; + }, + + blockSize: 128/32 + }); + + /** + * A collection of cipher parameters. + * + * @property {WordArray} ciphertext The raw ciphertext. + * @property {WordArray} key The key to this ciphertext. + * @property {WordArray} iv The IV used in the ciphering operation. + * @property {WordArray} salt The salt used with a key derivation function. + * @property {Cipher} algorithm The cipher algorithm. + * @property {Mode} mode The block mode used in the ciphering operation. + * @property {Padding} padding The padding scheme used in the ciphering operation. + * @property {number} blockSize The block size of the cipher. + * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string. + */ + var CipherParams = C_lib.CipherParams = Base.extend({ + /** + * Initializes a newly created cipher params object. + * + * @param {Object} cipherParams An object with any of the possible cipher parameters. + * + * @example + * + * var cipherParams = CryptoJS.lib.CipherParams.create({ + * ciphertext: ciphertextWordArray, + * key: keyWordArray, + * iv: ivWordArray, + * salt: saltWordArray, + * algorithm: CryptoJS.algo.AES, + * mode: CryptoJS.mode.CBC, + * padding: CryptoJS.pad.PKCS7, + * blockSize: 4, + * formatter: CryptoJS.format.OpenSSL + * }); + */ + init: function (cipherParams) { + this.mixIn(cipherParams); + }, + + /** + * Converts this cipher params object to a string. + * + * @param {Format} formatter (Optional) The formatting strategy to use. + * + * @return {string} The stringified cipher params. + * + * @throws Error If neither the formatter nor the default formatter is set. + * + * @example + * + * var string = cipherParams + ''; + * var string = cipherParams.toString(); + * var string = cipherParams.toString(CryptoJS.format.OpenSSL); + */ + toString: function (formatter) { + return (formatter || this.formatter).stringify(this); + } + }); + + /** + * Format namespace. + */ + var C_format = C.format = {}; + + /** + * OpenSSL formatting strategy. + */ + var OpenSSLFormatter = C_format.OpenSSL = { + /** + * Converts a cipher params object to an OpenSSL-compatible string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The OpenSSL-compatible string. + * + * @static + * + * @example + * + * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); + */ + stringify: function (cipherParams) { + // Shortcuts + var ciphertext = cipherParams.ciphertext; + var salt = cipherParams.salt; + + // Format + if (salt) { + var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); + } else { + var wordArray = ciphertext; + } + + return wordArray.toString(Base64); + }, + + /** + * Converts an OpenSSL-compatible string to a cipher params object. + * + * @param {string} openSSLStr The OpenSSL-compatible string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); + */ + parse: function (openSSLStr) { + // Parse base64 + var ciphertext = Base64.parse(openSSLStr); + + // Shortcut + var ciphertextWords = ciphertext.words; + + // Test for salt + if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) { + // Extract salt + var salt = WordArray.create(ciphertextWords.slice(2, 4)); + + // Remove salt from ciphertext + ciphertextWords.splice(0, 4); + ciphertext.sigBytes -= 16; + } + + return CipherParams.create({ ciphertext: ciphertext, salt: salt }); + } + }; + + /** + * A cipher wrapper that returns ciphertext as a serializable cipher params object. + */ + var SerializableCipher = C_lib.SerializableCipher = Base.extend({ + /** + * Configuration options. + * + * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL + */ + cfg: Base.extend({ + format: OpenSSLFormatter + }), + + /** + * Encrypts a message. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Encrypt + var encryptor = cipher.createEncryptor(key, cfg); + var ciphertext = encryptor.finalize(message); + + // Shortcut + var cipherCfg = encryptor.cfg; + + // Create and return serializable cipher params + return CipherParams.create({ + ciphertext: ciphertext, + key: key, + iv: cipherCfg.iv, + algorithm: cipher, + mode: cipherCfg.mode, + padding: cipherCfg.padding, + blockSize: cipher.blockSize, + formatter: cfg.format + }); + }, + + /** + * Decrypts serialized ciphertext. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Decrypt + var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext); + + return plaintext; + }, + + /** + * Converts serialized ciphertext to CipherParams, + * else assumed CipherParams already and returns ciphertext unchanged. + * + * @param {CipherParams|string} ciphertext The ciphertext. + * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext. + * + * @return {CipherParams} The unserialized ciphertext. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); + */ + _parse: function (ciphertext, format) { + if (typeof ciphertext == 'string') { + return format.parse(ciphertext, this); + } else { + return ciphertext; + } + } + }); + + /** + * Key derivation function namespace. + */ + var C_kdf = C.kdf = {}; + + /** + * OpenSSL key derivation function. + */ + var OpenSSLKdf = C_kdf.OpenSSL = { + /** + * Derives a key and IV from a password. + * + * @param {string} password The password to derive from. + * @param {number} keySize The size in words of the key to generate. + * @param {number} ivSize The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. + * + * @return {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + */ + execute: function (password, keySize, ivSize, salt) { + // Generate random salt + if (!salt) { + salt = WordArray.random(64/8); + } + + // Derive key and IV + var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + var iv = WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CipherParams.create({ key: key, iv: iv, salt: salt }); + } + }; + + /** + * A serializable cipher wrapper that derives the key from a password, + * and returns ciphertext as a serializable cipher params object. + */ + var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({ + /** + * Configuration options. + * + * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL + */ + cfg: SerializableCipher.cfg.extend({ + kdf: OpenSSLKdf + }), + + /** + * Encrypts a message using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Encrypt + var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg); + + // Mix in derived params + ciphertext.mixIn(derivedParams); + + return ciphertext; + }, + + /** + * Decrypts serialized ciphertext using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Decrypt + var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg); + + return plaintext; + } + }); + }()); + + +})); +},{"./core":51}],51:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -6918,7 +8117,2924 @@ module.exports = transfer; return CryptoJS; })); -},{}],48:[function(require,module,exports){ +},{}],52:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * Base64 encoding strategy. + */ + var Base64 = C_enc.Base64 = { + /** + * Converts a word array to a Base64 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Base64 string. + * + * @static + * + * @example + * + * var base64String = CryptoJS.enc.Base64.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var map = this._map; + + // Clamp excess bits + wordArray.clamp(); + + // Convert + var base64Chars = []; + for (var i = 0; i < sigBytes; i += 3) { + var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; + var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; + + var triplet = (byte1 << 16) | (byte2 << 8) | byte3; + + for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { + base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); + } + } + + // Add padding + var paddingChar = map.charAt(64); + if (paddingChar) { + while (base64Chars.length % 4) { + base64Chars.push(paddingChar); + } + } + + return base64Chars.join(''); + }, + + /** + * Converts a Base64 string to a word array. + * + * @param {string} base64Str The Base64 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Base64.parse(base64String); + */ + parse: function (base64Str) { + // Shortcuts + var base64StrLength = base64Str.length; + var map = this._map; + + // Ignore padding + var paddingChar = map.charAt(64); + if (paddingChar) { + var paddingIndex = base64Str.indexOf(paddingChar); + if (paddingIndex != -1) { + base64StrLength = paddingIndex; + } + } + + // Convert + var words = []; + var nBytes = 0; + for (var i = 0; i < base64StrLength; i++) { + if (i % 4) { + var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2); + var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2); + words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); + nBytes++; + } + } + + return WordArray.create(words, nBytes); + }, + + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + }; + }()); + + + return CryptoJS.enc.Base64; + +})); +},{"./core":51}],53:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * UTF-16 BE encoding strategy. + */ + var Utf16BE = C_enc.Utf16 = C_enc.Utf16BE = { + /** + * Converts a word array to a UTF-16 BE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 BE string. + * + * @static + * + * @example + * + * var utf16String = CryptoJS.enc.Utf16.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = (words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff; + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 BE string to a word array. + * + * @param {string} utf16Str The UTF-16 BE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16.parse(utf16String); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= utf16Str.charCodeAt(i) << (16 - (i % 2) * 16); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + /** + * UTF-16 LE encoding strategy. + */ + C_enc.Utf16LE = { + /** + * Converts a word array to a UTF-16 LE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 LE string. + * + * @static + * + * @example + * + * var utf16Str = CryptoJS.enc.Utf16LE.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = swapEndian((words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff); + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 LE string to a word array. + * + * @param {string} utf16Str The UTF-16 LE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16LE.parse(utf16Str); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << (16 - (i % 2) * 16)); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + function swapEndian(word) { + return ((word << 8) & 0xff00ff00) | ((word >>> 8) & 0x00ff00ff); + } + }()); + + + return CryptoJS.enc.Utf16; + +})); +},{"./core":51}],54:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./sha1"), require("./hmac")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./sha1", "./hmac"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var MD5 = C_algo.MD5; + + /** + * This key derivation function is meant to conform with EVP_BytesToKey. + * www.openssl.org/docs/crypto/EVP_BytesToKey.html + */ + var EvpKDF = C_algo.EvpKDF = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hash algorithm to use. Default: MD5 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: MD5, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.EvpKDF.create(); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init hasher + var hasher = cfg.hasher.create(); + + // Initial values + var derivedKey = WordArray.create(); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + if (block) { + hasher.update(block); + } + var block = hasher.update(password).finalize(salt); + hasher.reset(); + + // Iterations + for (var i = 1; i < iterations; i++) { + block = hasher.finalize(block); + hasher.reset(); + } + + derivedKey.concat(block); + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.EvpKDF(password, salt); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.EvpKDF = function (password, salt, cfg) { + return EvpKDF.create(cfg).compute(password, salt); + }; + }()); + + + return CryptoJS.EvpKDF; + +})); +},{"./core":51,"./hmac":56,"./sha1":75}],55:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var CipherParams = C_lib.CipherParams; + var C_enc = C.enc; + var Hex = C_enc.Hex; + var C_format = C.format; + + var HexFormatter = C_format.Hex = { + /** + * Converts the ciphertext of a cipher params object to a hexadecimally encoded string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The hexadecimally encoded string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.format.Hex.stringify(cipherParams); + */ + stringify: function (cipherParams) { + return cipherParams.ciphertext.toString(Hex); + }, + + /** + * Converts a hexadecimally encoded ciphertext string to a cipher params object. + * + * @param {string} input The hexadecimally encoded string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.Hex.parse(hexString); + */ + parse: function (input) { + var ciphertext = Hex.parse(input); + return CipherParams.create({ ciphertext: ciphertext }); + } + }; + }()); + + + return CryptoJS.format.Hex; + +})); +},{"./cipher-core":50,"./core":51}],56:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var C_algo = C.algo; + + /** + * HMAC algorithm. + */ + var HMAC = C_algo.HMAC = Base.extend({ + /** + * Initializes a newly created HMAC. + * + * @param {Hasher} hasher The hash algorithm to use. + * @param {WordArray|string} key The secret key. + * + * @example + * + * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); + */ + init: function (hasher, key) { + // Init hasher + hasher = this._hasher = new hasher.init(); + + // Convert string to WordArray, else assume WordArray already + if (typeof key == 'string') { + key = Utf8.parse(key); + } + + // Shortcuts + var hasherBlockSize = hasher.blockSize; + var hasherBlockSizeBytes = hasherBlockSize * 4; + + // Allow arbitrary length keys + if (key.sigBytes > hasherBlockSizeBytes) { + key = hasher.finalize(key); + } + + // Clamp excess bits + key.clamp(); + + // Clone key for inner and outer pads + var oKey = this._oKey = key.clone(); + var iKey = this._iKey = key.clone(); + + // Shortcuts + var oKeyWords = oKey.words; + var iKeyWords = iKey.words; + + // XOR keys with pad constants + for (var i = 0; i < hasherBlockSize; i++) { + oKeyWords[i] ^= 0x5c5c5c5c; + iKeyWords[i] ^= 0x36363636; + } + oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; + + // Set initial values + this.reset(); + }, + + /** + * Resets this HMAC to its initial state. + * + * @example + * + * hmacHasher.reset(); + */ + reset: function () { + // Shortcut + var hasher = this._hasher; + + // Reset + hasher.reset(); + hasher.update(this._iKey); + }, + + /** + * Updates this HMAC with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {HMAC} This HMAC instance. + * + * @example + * + * hmacHasher.update('message'); + * hmacHasher.update(wordArray); + */ + update: function (messageUpdate) { + this._hasher.update(messageUpdate); + + // Chainable + return this; + }, + + /** + * Finalizes the HMAC computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The HMAC. + * + * @example + * + * var hmac = hmacHasher.finalize(); + * var hmac = hmacHasher.finalize('message'); + * var hmac = hmacHasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Shortcut + var hasher = this._hasher; + + // Compute HMAC + var innerHash = hasher.finalize(messageUpdate); + hasher.reset(); + var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); + + return hmac; + } + }); + }()); + + +})); +},{"./core":51}],57:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./x64-core"), require("./lib-typedarrays"), require("./enc-utf16"), require("./enc-base64"), require("./md5"), require("./sha1"), require("./sha256"), require("./sha224"), require("./sha512"), require("./sha384"), require("./sha3"), require("./ripemd160"), require("./hmac"), require("./pbkdf2"), require("./evpkdf"), require("./cipher-core"), require("./mode-cfb"), require("./mode-ctr"), require("./mode-ctr-gladman"), require("./mode-ofb"), require("./mode-ecb"), require("./pad-ansix923"), require("./pad-iso10126"), require("./pad-iso97971"), require("./pad-zeropadding"), require("./pad-nopadding"), require("./format-hex"), require("./aes"), require("./tripledes"), require("./rc4"), require("./rabbit"), require("./rabbit-legacy")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./x64-core", "./lib-typedarrays", "./enc-utf16", "./enc-base64", "./md5", "./sha1", "./sha256", "./sha224", "./sha512", "./sha384", "./sha3", "./ripemd160", "./hmac", "./pbkdf2", "./evpkdf", "./cipher-core", "./mode-cfb", "./mode-ctr", "./mode-ctr-gladman", "./mode-ofb", "./mode-ecb", "./pad-ansix923", "./pad-iso10126", "./pad-iso97971", "./pad-zeropadding", "./pad-nopadding", "./format-hex", "./aes", "./tripledes", "./rc4", "./rabbit", "./rabbit-legacy"], factory); + } + else { + // Global (browser) + root.CryptoJS = factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + return CryptoJS; + +})); +},{"./aes":49,"./cipher-core":50,"./core":51,"./enc-base64":52,"./enc-utf16":53,"./evpkdf":54,"./format-hex":55,"./hmac":56,"./lib-typedarrays":58,"./md5":59,"./mode-cfb":60,"./mode-ctr":62,"./mode-ctr-gladman":61,"./mode-ecb":63,"./mode-ofb":64,"./pad-ansix923":65,"./pad-iso10126":66,"./pad-iso97971":67,"./pad-nopadding":68,"./pad-zeropadding":69,"./pbkdf2":70,"./rabbit":72,"./rabbit-legacy":71,"./rc4":73,"./ripemd160":74,"./sha1":75,"./sha224":76,"./sha256":77,"./sha3":78,"./sha384":79,"./sha512":80,"./tripledes":81,"./x64-core":82}],58:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Check if typed arrays are supported + if (typeof ArrayBuffer != 'function') { + return; + } + + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + + // Reference original init + var superInit = WordArray.init; + + // Augment WordArray.init to handle typed arrays + var subInit = WordArray.init = function (typedArray) { + // Convert buffers to uint8 + if (typedArray instanceof ArrayBuffer) { + typedArray = new Uint8Array(typedArray); + } + + // Convert other array views to uint8 + if ( + typedArray instanceof Int8Array || + (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray) || + typedArray instanceof Int16Array || + typedArray instanceof Uint16Array || + typedArray instanceof Int32Array || + typedArray instanceof Uint32Array || + typedArray instanceof Float32Array || + typedArray instanceof Float64Array + ) { + typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); + } + + // Handle Uint8Array + if (typedArray instanceof Uint8Array) { + // Shortcut + var typedArrayByteLength = typedArray.byteLength; + + // Extract bytes + var words = []; + for (var i = 0; i < typedArrayByteLength; i++) { + words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); + } + + // Initialize this word array + superInit.call(this, words, typedArrayByteLength); + } else { + // Else call normal init + superInit.apply(this, arguments); + } + }; + + subInit.prototype = WordArray; + }()); + + + return CryptoJS.lib.WordArray; + +})); +},{"./core":51}],59:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var T = []; + + // Compute constants + (function () { + for (var i = 0; i < 64; i++) { + T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; + } + }()); + + /** + * MD5 hash algorithm. + */ + var MD5 = C_algo.MD5 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + + // Shortcuts + var H = this._hash.words; + + var M_offset_0 = M[offset + 0]; + var M_offset_1 = M[offset + 1]; + var M_offset_2 = M[offset + 2]; + var M_offset_3 = M[offset + 3]; + var M_offset_4 = M[offset + 4]; + var M_offset_5 = M[offset + 5]; + var M_offset_6 = M[offset + 6]; + var M_offset_7 = M[offset + 7]; + var M_offset_8 = M[offset + 8]; + var M_offset_9 = M[offset + 9]; + var M_offset_10 = M[offset + 10]; + var M_offset_11 = M[offset + 11]; + var M_offset_12 = M[offset + 12]; + var M_offset_13 = M[offset + 13]; + var M_offset_14 = M[offset + 14]; + var M_offset_15 = M[offset + 15]; + + // Working varialbes + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + + // Computation + a = FF(a, b, c, d, M_offset_0, 7, T[0]); + d = FF(d, a, b, c, M_offset_1, 12, T[1]); + c = FF(c, d, a, b, M_offset_2, 17, T[2]); + b = FF(b, c, d, a, M_offset_3, 22, T[3]); + a = FF(a, b, c, d, M_offset_4, 7, T[4]); + d = FF(d, a, b, c, M_offset_5, 12, T[5]); + c = FF(c, d, a, b, M_offset_6, 17, T[6]); + b = FF(b, c, d, a, M_offset_7, 22, T[7]); + a = FF(a, b, c, d, M_offset_8, 7, T[8]); + d = FF(d, a, b, c, M_offset_9, 12, T[9]); + c = FF(c, d, a, b, M_offset_10, 17, T[10]); + b = FF(b, c, d, a, M_offset_11, 22, T[11]); + a = FF(a, b, c, d, M_offset_12, 7, T[12]); + d = FF(d, a, b, c, M_offset_13, 12, T[13]); + c = FF(c, d, a, b, M_offset_14, 17, T[14]); + b = FF(b, c, d, a, M_offset_15, 22, T[15]); + + a = GG(a, b, c, d, M_offset_1, 5, T[16]); + d = GG(d, a, b, c, M_offset_6, 9, T[17]); + c = GG(c, d, a, b, M_offset_11, 14, T[18]); + b = GG(b, c, d, a, M_offset_0, 20, T[19]); + a = GG(a, b, c, d, M_offset_5, 5, T[20]); + d = GG(d, a, b, c, M_offset_10, 9, T[21]); + c = GG(c, d, a, b, M_offset_15, 14, T[22]); + b = GG(b, c, d, a, M_offset_4, 20, T[23]); + a = GG(a, b, c, d, M_offset_9, 5, T[24]); + d = GG(d, a, b, c, M_offset_14, 9, T[25]); + c = GG(c, d, a, b, M_offset_3, 14, T[26]); + b = GG(b, c, d, a, M_offset_8, 20, T[27]); + a = GG(a, b, c, d, M_offset_13, 5, T[28]); + d = GG(d, a, b, c, M_offset_2, 9, T[29]); + c = GG(c, d, a, b, M_offset_7, 14, T[30]); + b = GG(b, c, d, a, M_offset_12, 20, T[31]); + + a = HH(a, b, c, d, M_offset_5, 4, T[32]); + d = HH(d, a, b, c, M_offset_8, 11, T[33]); + c = HH(c, d, a, b, M_offset_11, 16, T[34]); + b = HH(b, c, d, a, M_offset_14, 23, T[35]); + a = HH(a, b, c, d, M_offset_1, 4, T[36]); + d = HH(d, a, b, c, M_offset_4, 11, T[37]); + c = HH(c, d, a, b, M_offset_7, 16, T[38]); + b = HH(b, c, d, a, M_offset_10, 23, T[39]); + a = HH(a, b, c, d, M_offset_13, 4, T[40]); + d = HH(d, a, b, c, M_offset_0, 11, T[41]); + c = HH(c, d, a, b, M_offset_3, 16, T[42]); + b = HH(b, c, d, a, M_offset_6, 23, T[43]); + a = HH(a, b, c, d, M_offset_9, 4, T[44]); + d = HH(d, a, b, c, M_offset_12, 11, T[45]); + c = HH(c, d, a, b, M_offset_15, 16, T[46]); + b = HH(b, c, d, a, M_offset_2, 23, T[47]); + + a = II(a, b, c, d, M_offset_0, 6, T[48]); + d = II(d, a, b, c, M_offset_7, 10, T[49]); + c = II(c, d, a, b, M_offset_14, 15, T[50]); + b = II(b, c, d, a, M_offset_5, 21, T[51]); + a = II(a, b, c, d, M_offset_12, 6, T[52]); + d = II(d, a, b, c, M_offset_3, 10, T[53]); + c = II(c, d, a, b, M_offset_10, 15, T[54]); + b = II(b, c, d, a, M_offset_1, 21, T[55]); + a = II(a, b, c, d, M_offset_8, 6, T[56]); + d = II(d, a, b, c, M_offset_15, 10, T[57]); + c = II(c, d, a, b, M_offset_6, 15, T[58]); + b = II(b, c, d, a, M_offset_13, 21, T[59]); + a = II(a, b, c, d, M_offset_4, 6, T[60]); + d = II(d, a, b, c, M_offset_11, 10, T[61]); + c = II(c, d, a, b, M_offset_2, 15, T[62]); + b = II(b, c, d, a, M_offset_9, 21, T[63]); + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + + var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); + var nBitsTotalL = nBitsTotal; + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( + (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | + (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + ); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | + (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + ); + + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 4; i++) { + // Shortcut + var H_i = H[i]; + + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + function FF(a, b, c, d, x, s, t) { + var n = a + ((b & c) | (~b & d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function GG(a, b, c, d, x, s, t) { + var n = a + ((b & d) | (c & ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function HH(a, b, c, d, x, s, t) { + var n = a + (b ^ c ^ d) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function II(a, b, c, d, x, s, t) { + var n = a + (c ^ (b | ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + C.MD5 = Hasher._createHelper(MD5); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + C.HmacMD5 = Hasher._createHmacHelper(MD5); + }(Math)); + + + return CryptoJS.MD5; + +})); +},{"./core":51}],60:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * Cipher Feedback block mode. + */ + CryptoJS.mode.CFB = (function () { + var CFB = CryptoJS.lib.BlockCipherMode.extend(); + + CFB.Encryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + CFB.Decryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) { + // Shortcut + var iv = this._iv; + + // Generate keystream + if (iv) { + var keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var keystream = this._prevBlock; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + + return CFB; + }()); + + + return CryptoJS.mode.CFB; + +})); +},{"./cipher-core":50,"./core":51}],61:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** @preserve + * Counter block mode compatible with Dr Brian Gladman fileenc.c + * derived from CryptoJS.mode.CTR + * Jan Hruby jhruby.web@gmail.com + */ + CryptoJS.mode.CTRGladman = (function () { + var CTRGladman = CryptoJS.lib.BlockCipherMode.extend(); + + function incWord(word) + { + if (((word >> 24) & 0xff) === 0xff) { //overflow + var b1 = (word >> 16)&0xff; + var b2 = (word >> 8)&0xff; + var b3 = word & 0xff; + + if (b1 === 0xff) // overflow b1 + { + b1 = 0; + if (b2 === 0xff) + { + b2 = 0; + if (b3 === 0xff) + { + b3 = 0; + } + else + { + ++b3; + } + } + else + { + ++b2; + } + } + else + { + ++b1; + } + + word = 0; + word += (b1 << 16); + word += (b2 << 8); + word += b3; + } + else + { + word += (0x01 << 24); + } + return word; + } + + function incCounter(counter) + { + if ((counter[0] = incWord(counter[0])) === 0) + { + // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 + counter[1] = incWord(counter[1]); + } + return counter; + } + + var Encryptor = CTRGladman.Encryptor = CTRGladman.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + + incCounter(counter); + + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTRGladman.Decryptor = Encryptor; + + return CTRGladman; + }()); + + + + + return CryptoJS.mode.CTRGladman; + +})); +},{"./cipher-core":50,"./core":51}],62:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * Counter block mode. + */ + CryptoJS.mode.CTR = (function () { + var CTR = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = CTR.Encryptor = CTR.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Increment counter + counter[blockSize - 1] = (counter[blockSize - 1] + 1) | 0 + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTR.Decryptor = Encryptor; + + return CTR; + }()); + + + return CryptoJS.mode.CTR; + +})); +},{"./cipher-core":50,"./core":51}],63:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * Electronic Codebook block mode. + */ + CryptoJS.mode.ECB = (function () { + var ECB = CryptoJS.lib.BlockCipherMode.extend(); + + ECB.Encryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.encryptBlock(words, offset); + } + }); + + ECB.Decryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.decryptBlock(words, offset); + } + }); + + return ECB; + }()); + + + return CryptoJS.mode.ECB; + +})); +},{"./cipher-core":50,"./core":51}],64:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * Output Feedback block mode. + */ + CryptoJS.mode.OFB = (function () { + var OFB = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = OFB.Encryptor = OFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var keystream = this._keystream; + + // Generate keystream + if (iv) { + keystream = this._keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + OFB.Decryptor = Encryptor; + + return OFB; + }()); + + + return CryptoJS.mode.OFB; + +})); +},{"./cipher-core":50,"./core":51}],65:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * ANSI X.923 padding strategy. + */ + CryptoJS.pad.AnsiX923 = { + pad: function (data, blockSize) { + // Shortcuts + var dataSigBytes = data.sigBytes; + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes; + + // Compute last byte position + var lastBytePos = dataSigBytes + nPaddingBytes - 1; + + // Pad + data.clamp(); + data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8); + data.sigBytes += nPaddingBytes; + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + return CryptoJS.pad.Ansix923; + +})); +},{"./cipher-core":50,"./core":51}],66:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * ISO 10126 padding strategy. + */ + CryptoJS.pad.Iso10126 = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Pad + data.concat(CryptoJS.lib.WordArray.random(nPaddingBytes - 1)). + concat(CryptoJS.lib.WordArray.create([nPaddingBytes << 24], 1)); + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + return CryptoJS.pad.Iso10126; + +})); +},{"./cipher-core":50,"./core":51}],67:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * ISO/IEC 9797-1 Padding Method 2. + */ + CryptoJS.pad.Iso97971 = { + pad: function (data, blockSize) { + // Add 0x80 byte + data.concat(CryptoJS.lib.WordArray.create([0x80000000], 1)); + + // Zero pad the rest + CryptoJS.pad.ZeroPadding.pad(data, blockSize); + }, + + unpad: function (data) { + // Remove zero padding + CryptoJS.pad.ZeroPadding.unpad(data); + + // Remove one more byte -- the 0x80 byte + data.sigBytes--; + } + }; + + + return CryptoJS.pad.Iso97971; + +})); +},{"./cipher-core":50,"./core":51}],68:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * A noop padding strategy. + */ + CryptoJS.pad.NoPadding = { + pad: function () { + }, + + unpad: function () { + } + }; + + + return CryptoJS.pad.NoPadding; + +})); +},{"./cipher-core":50,"./core":51}],69:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** + * Zero padding strategy. + */ + CryptoJS.pad.ZeroPadding = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Pad + data.clamp(); + data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes); + }, + + unpad: function (data) { + // Shortcut + var dataWords = data.words; + + // Unpad + var i = data.sigBytes - 1; + while (!((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) { + i--; + } + data.sigBytes = i + 1; + } + }; + + + return CryptoJS.pad.ZeroPadding; + +})); +},{"./cipher-core":50,"./core":51}],70:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./sha1"), require("./hmac")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./sha1", "./hmac"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA1 = C_algo.SHA1; + var HMAC = C_algo.HMAC; + + /** + * Password-Based Key Derivation Function 2 algorithm. + */ + var PBKDF2 = C_algo.PBKDF2 = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hasher to use. Default: SHA1 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: SHA1, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.PBKDF2.create(); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init HMAC + var hmac = HMAC.create(cfg.hasher, password); + + // Initial values + var derivedKey = WordArray.create(); + var blockIndex = WordArray.create([0x00000001]); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var blockIndexWords = blockIndex.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + var block = hmac.update(salt).finalize(blockIndex); + hmac.reset(); + + // Shortcuts + var blockWords = block.words; + var blockWordsLength = blockWords.length; + + // Iterations + var intermediate = block; + for (var i = 1; i < iterations; i++) { + intermediate = hmac.finalize(intermediate); + hmac.reset(); + + // Shortcut + var intermediateWords = intermediate.words; + + // XOR intermediate with block + for (var j = 0; j < blockWordsLength; j++) { + blockWords[j] ^= intermediateWords[j]; + } + } + + derivedKey.concat(block); + blockIndexWords[0]++; + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.PBKDF2(password, salt); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.PBKDF2 = function (password, salt, cfg) { + return PBKDF2.create(cfg).compute(password, salt); + }; + }()); + + + return CryptoJS.PBKDF2; + +})); +},{"./core":51,"./hmac":56,"./sha1":75}],71:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm. + * + * This is a legacy version that neglected to convert the key to little-endian. + * This error doesn't affect the cipher's security, + * but it does affect its compatibility with other implementations. + */ + var RabbitLegacy = C_algo.RabbitLegacy = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RabbitLegacy.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RabbitLegacy.decrypt(ciphertext, key, cfg); + */ + C.RabbitLegacy = StreamCipher._createHelper(RabbitLegacy); + }()); + + + return CryptoJS.RabbitLegacy; + +})); +},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],72:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm + */ + var Rabbit = C_algo.Rabbit = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Swap endian + for (var i = 0; i < 4; i++) { + K[i] = (((K[i] << 8) | (K[i] >>> 24)) & 0x00ff00ff) | + (((K[i] << 24) | (K[i] >>> 8)) & 0xff00ff00); + } + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.Rabbit.encrypt(message, key, cfg); + * var plaintext = CryptoJS.Rabbit.decrypt(ciphertext, key, cfg); + */ + C.Rabbit = StreamCipher._createHelper(Rabbit); + }()); + + + return CryptoJS.Rabbit; + +})); +},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],73:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + /** + * RC4 stream cipher algorithm. + */ + var RC4 = C_algo.RC4 = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + var keySigBytes = key.sigBytes; + + // Init sbox + var S = this._S = []; + for (var i = 0; i < 256; i++) { + S[i] = i; + } + + // Key setup + for (var i = 0, j = 0; i < 256; i++) { + var keyByteIndex = i % keySigBytes; + var keyByte = (keyWords[keyByteIndex >>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff; + + j = (j + S[i] + keyByte) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + } + + // Counters + this._i = this._j = 0; + }, + + _doProcessBlock: function (M, offset) { + M[offset] ^= generateKeystreamWord.call(this); + }, + + keySize: 256/32, + + ivSize: 0 + }); + + function generateKeystreamWord() { + // Shortcuts + var S = this._S; + var i = this._i; + var j = this._j; + + // Generate keystream word + var keystreamWord = 0; + for (var n = 0; n < 4; n++) { + i = (i + 1) % 256; + j = (j + S[i]) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + + keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8); + } + + // Update counters + this._i = i; + this._j = j; + + return keystreamWord; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); + */ + C.RC4 = StreamCipher._createHelper(RC4); + + /** + * Modified RC4 stream cipher algorithm. + */ + var RC4Drop = C_algo.RC4Drop = RC4.extend({ + /** + * Configuration options. + * + * @property {number} drop The number of keystream words to drop. Default 192 + */ + cfg: RC4.cfg.extend({ + drop: 192 + }), + + _doReset: function () { + RC4._doReset.call(this); + + // Drop + for (var i = this.cfg.drop; i > 0; i--) { + generateKeystreamWord.call(this); + } + } + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); + */ + C.RC4Drop = StreamCipher._createHelper(RC4Drop); + }()); + + + return CryptoJS.RC4; + +})); +},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],74:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + /** @preserve + (c) 2012 by Cédric Mesnil. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var _zl = WordArray.create([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]); + var _zr = WordArray.create([ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]); + var _sl = WordArray.create([ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]); + var _sr = WordArray.create([ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]); + + var _hl = WordArray.create([ 0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]); + var _hr = WordArray.create([ 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]); + + /** + * RIPEMD160 hash algorithm. + */ + var RIPEMD160 = C_algo.RIPEMD160 = Hasher.extend({ + _doReset: function () { + this._hash = WordArray.create([0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]); + }, + + _doProcessBlock: function (M, offset) { + + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + // Swap + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + // Shortcut + var H = this._hash.words; + var hl = _hl.words; + var hr = _hr.words; + var zl = _zl.words; + var zr = _zr.words; + var sl = _sl.words; + var sr = _sr.words; + + // Working variables + var al, bl, cl, dl, el; + var ar, br, cr, dr, er; + + ar = al = H[0]; + br = bl = H[1]; + cr = cl = H[2]; + dr = dl = H[3]; + er = el = H[4]; + // Computation + var t; + for (var i = 0; i < 80; i += 1) { + t = (al + M[offset+zl[i]])|0; + if (i<16){ + t += f1(bl,cl,dl) + hl[0]; + } else if (i<32) { + t += f2(bl,cl,dl) + hl[1]; + } else if (i<48) { + t += f3(bl,cl,dl) + hl[2]; + } else if (i<64) { + t += f4(bl,cl,dl) + hl[3]; + } else {// if (i<80) { + t += f5(bl,cl,dl) + hl[4]; + } + t = t|0; + t = rotl(t,sl[i]); + t = (t+el)|0; + al = el; + el = dl; + dl = rotl(cl, 10); + cl = bl; + bl = t; + + t = (ar + M[offset+zr[i]])|0; + if (i<16){ + t += f5(br,cr,dr) + hr[0]; + } else if (i<32) { + t += f4(br,cr,dr) + hr[1]; + } else if (i<48) { + t += f3(br,cr,dr) + hr[2]; + } else if (i<64) { + t += f2(br,cr,dr) + hr[3]; + } else {// if (i<80) { + t += f1(br,cr,dr) + hr[4]; + } + t = t|0; + t = rotl(t,sr[i]) ; + t = (t+er)|0; + ar = er; + er = dr; + dr = rotl(cr, 10); + cr = br; + br = t; + } + // Intermediate hash value + t = (H[1] + cl + dr)|0; + H[1] = (H[2] + dl + er)|0; + H[2] = (H[3] + el + ar)|0; + H[3] = (H[4] + al + br)|0; + H[4] = (H[0] + bl + cr)|0; + H[0] = t; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) | + (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00) + ); + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 5; i++) { + // Shortcut + var H_i = H[i]; + + // Swap + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + + function f1(x, y, z) { + return ((x) ^ (y) ^ (z)); + + } + + function f2(x, y, z) { + return (((x)&(y)) | ((~x)&(z))); + } + + function f3(x, y, z) { + return (((x) | (~(y))) ^ (z)); + } + + function f4(x, y, z) { + return (((x) & (z)) | ((y)&(~(z)))); + } + + function f5(x, y, z) { + return ((x) ^ ((y) |(~(z)))); + + } + + function rotl(x,n) { + return (x<>>(32-n)); + } + + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.RIPEMD160('message'); + * var hash = CryptoJS.RIPEMD160(wordArray); + */ + C.RIPEMD160 = Hasher._createHelper(RIPEMD160); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacRIPEMD160(message, key); + */ + C.HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160); + }(Math)); + + + return CryptoJS.RIPEMD160; + +})); +},{"./core":51}],75:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Reusable object + var W = []; + + /** + * SHA-1 hash algorithm. + */ + var SHA1 = C_algo.SHA1 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476, + 0xc3d2e1f0 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + + // Computation + for (var i = 0; i < 80; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; + W[i] = (n << 1) | (n >>> 31); + } + + var t = ((a << 5) | (a >>> 27)) + e + W[i]; + if (i < 20) { + t += ((b & c) | (~b & d)) + 0x5a827999; + } else if (i < 40) { + t += (b ^ c ^ d) + 0x6ed9eba1; + } else if (i < 60) { + t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; + } else /* if (i < 80) */ { + t += (b ^ c ^ d) - 0x359d3e2a; + } + + e = d; + d = c; + c = (b << 30) | (b >>> 2); + b = a; + a = t; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA1('message'); + * var hash = CryptoJS.SHA1(wordArray); + */ + C.SHA1 = Hasher._createHelper(SHA1); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA1(message, key); + */ + C.HmacSHA1 = Hasher._createHmacHelper(SHA1); + }()); + + + return CryptoJS.SHA1; + +})); +},{"./core":51}],76:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./sha256")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./sha256"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA256 = C_algo.SHA256; + + /** + * SHA-224 hash algorithm. + */ + var SHA224 = C_algo.SHA224 = SHA256.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 + ]); + }, + + _doFinalize: function () { + var hash = SHA256._doFinalize.call(this); + + hash.sigBytes -= 4; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA224('message'); + * var hash = CryptoJS.SHA224(wordArray); + */ + C.SHA224 = SHA256._createHelper(SHA224); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA224(message, key); + */ + C.HmacSHA224 = SHA256._createHmacHelper(SHA224); + }()); + + + return CryptoJS.SHA224; + +})); +},{"./core":51,"./sha256":77}],77:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Initialization and round constants tables + var H = []; + var K = []; + + // Compute constants + (function () { + function isPrime(n) { + var sqrtN = Math.sqrt(n); + for (var factor = 2; factor <= sqrtN; factor++) { + if (!(n % factor)) { + return false; + } + } + + return true; + } + + function getFractionalBits(n) { + return ((n - (n | 0)) * 0x100000000) | 0; + } + + var n = 2; + var nPrime = 0; + while (nPrime < 64) { + if (isPrime(n)) { + if (nPrime < 8) { + H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); + } + K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); + + nPrime++; + } + + n++; + } + }()); + + // Reusable object + var W = []; + + /** + * SHA-256 hash algorithm. + */ + var SHA256 = C_algo.SHA256 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init(H.slice(0)); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + var f = H[5]; + var g = H[6]; + var h = H[7]; + + // Computation + for (var i = 0; i < 64; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var gamma0x = W[i - 15]; + var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ + ((gamma0x << 14) | (gamma0x >>> 18)) ^ + (gamma0x >>> 3); + + var gamma1x = W[i - 2]; + var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ + ((gamma1x << 13) | (gamma1x >>> 19)) ^ + (gamma1x >>> 10); + + W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; + } + + var ch = (e & f) ^ (~e & g); + var maj = (a & b) ^ (a & c) ^ (b & c); + + var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); + var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); + + var t1 = h + sigma1 + ch + K[i] + W[i]; + var t2 = sigma0 + maj; + + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + H[5] = (H[5] + f) | 0; + H[6] = (H[6] + g) | 0; + H[7] = (H[7] + h) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA256('message'); + * var hash = CryptoJS.SHA256(wordArray); + */ + C.SHA256 = Hasher._createHelper(SHA256); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA256(message, key); + */ + C.HmacSHA256 = Hasher._createHmacHelper(SHA256); + }(Math)); + + + return CryptoJS.SHA256; + +})); +},{"./core":51}],78:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -7242,7 +11358,1186 @@ module.exports = transfer; return CryptoJS.SHA3; })); -},{"./core":47,"./x64-core":49}],49:[function(require,module,exports){ +},{"./core":51,"./x64-core":82}],79:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./x64-core"), require("./sha512")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./x64-core", "./sha512"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + var SHA512 = C_algo.SHA512; + + /** + * SHA-384 hash algorithm. + */ + var SHA384 = C_algo.SHA384 = SHA512.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0xcbbb9d5d, 0xc1059ed8), new X64Word.init(0x629a292a, 0x367cd507), + new X64Word.init(0x9159015a, 0x3070dd17), new X64Word.init(0x152fecd8, 0xf70e5939), + new X64Word.init(0x67332667, 0xffc00b31), new X64Word.init(0x8eb44a87, 0x68581511), + new X64Word.init(0xdb0c2e0d, 0x64f98fa7), new X64Word.init(0x47b5481d, 0xbefa4fa4) + ]); + }, + + _doFinalize: function () { + var hash = SHA512._doFinalize.call(this); + + hash.sigBytes -= 16; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA384('message'); + * var hash = CryptoJS.SHA384(wordArray); + */ + C.SHA384 = SHA512._createHelper(SHA384); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA384(message, key); + */ + C.HmacSHA384 = SHA512._createHmacHelper(SHA384); + }()); + + + return CryptoJS.SHA384; + +})); +},{"./core":51,"./sha512":80,"./x64-core":82}],80:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./x64-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./x64-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + + function X64Word_create() { + return X64Word.create.apply(X64Word, arguments); + } + + // Constants + var K = [ + X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), + X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), + X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), + X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), + X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), + X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), + X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), + X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), + X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), + X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), + X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), + X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), + X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), + X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), + X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), + X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), + X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), + X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), + X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), + X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), + X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), + X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), + X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), + X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), + X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), + X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), + X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), + X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), + X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), + X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), + X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), + X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), + X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), + X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), + X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), + X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), + X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), + X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), + X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), + X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817) + ]; + + // Reusable objects + var W = []; + (function () { + for (var i = 0; i < 80; i++) { + W[i] = X64Word_create(); + } + }()); + + /** + * SHA-512 hash algorithm. + */ + var SHA512 = C_algo.SHA512 = Hasher.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), + new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), + new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), + new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179) + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var H = this._hash.words; + + var H0 = H[0]; + var H1 = H[1]; + var H2 = H[2]; + var H3 = H[3]; + var H4 = H[4]; + var H5 = H[5]; + var H6 = H[6]; + var H7 = H[7]; + + var H0h = H0.high; + var H0l = H0.low; + var H1h = H1.high; + var H1l = H1.low; + var H2h = H2.high; + var H2l = H2.low; + var H3h = H3.high; + var H3l = H3.low; + var H4h = H4.high; + var H4l = H4.low; + var H5h = H5.high; + var H5l = H5.low; + var H6h = H6.high; + var H6l = H6.low; + var H7h = H7.high; + var H7l = H7.low; + + // Working variables + var ah = H0h; + var al = H0l; + var bh = H1h; + var bl = H1l; + var ch = H2h; + var cl = H2l; + var dh = H3h; + var dl = H3l; + var eh = H4h; + var el = H4l; + var fh = H5h; + var fl = H5l; + var gh = H6h; + var gl = H6l; + var hh = H7h; + var hl = H7l; + + // Rounds + for (var i = 0; i < 80; i++) { + // Shortcut + var Wi = W[i]; + + // Extend message + if (i < 16) { + var Wih = Wi.high = M[offset + i * 2] | 0; + var Wil = Wi.low = M[offset + i * 2 + 1] | 0; + } else { + // Gamma0 + var gamma0x = W[i - 15]; + var gamma0xh = gamma0x.high; + var gamma0xl = gamma0x.low; + var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7); + var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); + + // Gamma1 + var gamma1x = W[i - 2]; + var gamma1xh = gamma1x.high; + var gamma1xl = gamma1x.low; + var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6); + var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); + + // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] + var Wi7 = W[i - 7]; + var Wi7h = Wi7.high; + var Wi7l = Wi7.low; + + var Wi16 = W[i - 16]; + var Wi16h = Wi16.high; + var Wi16l = Wi16.low; + + var Wil = gamma0l + Wi7l; + var Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); + var Wil = Wil + gamma1l; + var Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); + var Wil = Wil + Wi16l; + var Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); + + Wi.high = Wih; + Wi.low = Wil; + } + + var chh = (eh & fh) ^ (~eh & gh); + var chl = (el & fl) ^ (~el & gl); + var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); + var majl = (al & bl) ^ (al & cl) ^ (bl & cl); + + var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); + var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); + var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9)); + var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9)); + + // t1 = h + sigma1 + ch + K[i] + W[i] + var Ki = K[i]; + var Kih = Ki.high; + var Kil = Ki.low; + + var t1l = hl + sigma1l; + var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); + var t1l = t1l + chl; + var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); + var t1l = t1l + Kil; + var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); + var t1l = t1l + Wil; + var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); + + // t2 = sigma0 + maj + var t2l = sigma0l + majl; + var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); + + // Update working variables + hh = gh; + hl = gl; + gh = fh; + gl = fl; + fh = eh; + fl = el; + el = (dl + t1l) | 0; + eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + dh = ch; + dl = cl; + ch = bh; + cl = bl; + bh = ah; + bl = al; + al = (t1l + t2l) | 0; + ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; + } + + // Intermediate hash value + H0l = H0.low = (H0l + al); + H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); + H1l = H1.low = (H1l + bl); + H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); + H2l = H2.low = (H2l + cl); + H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); + H3l = H3.low = (H3l + dl); + H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); + H4l = H4.low = (H4l + el); + H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); + H5l = H5.low = (H5l + fl); + H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); + H6l = H6.low = (H6l + gl); + H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); + H7l = H7.low = (H7l + hl); + H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Convert hash to 32-bit word array before returning + var hash = this._hash.toX32(); + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + }, + + blockSize: 1024/32 + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA512('message'); + * var hash = CryptoJS.SHA512(wordArray); + */ + C.SHA512 = Hasher._createHelper(SHA512); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA512(message, key); + */ + C.HmacSHA512 = Hasher._createHmacHelper(SHA512); + }()); + + + return CryptoJS.SHA512; + +})); +},{"./core":51,"./x64-core":82}],81:[function(require,module,exports){ +;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } +}(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Permuted Choice 1 constants + var PC1 = [ + 57, 49, 41, 33, 25, 17, 9, 1, + 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 27, 19, 11, 3, + 60, 52, 44, 36, 63, 55, 47, 39, + 31, 23, 15, 7, 62, 54, 46, 38, + 30, 22, 14, 6, 61, 53, 45, 37, + 29, 21, 13, 5, 28, 20, 12, 4 + ]; + + // Permuted Choice 2 constants + var PC2 = [ + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 + ]; + + // Cumulative bit shift constants + var BIT_SHIFTS = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; + + // SBOXes and round permutation constants + var SBOX_P = [ + { + 0x0: 0x808200, + 0x10000000: 0x8000, + 0x20000000: 0x808002, + 0x30000000: 0x2, + 0x40000000: 0x200, + 0x50000000: 0x808202, + 0x60000000: 0x800202, + 0x70000000: 0x800000, + 0x80000000: 0x202, + 0x90000000: 0x800200, + 0xa0000000: 0x8200, + 0xb0000000: 0x808000, + 0xc0000000: 0x8002, + 0xd0000000: 0x800002, + 0xe0000000: 0x0, + 0xf0000000: 0x8202, + 0x8000000: 0x0, + 0x18000000: 0x808202, + 0x28000000: 0x8202, + 0x38000000: 0x8000, + 0x48000000: 0x808200, + 0x58000000: 0x200, + 0x68000000: 0x808002, + 0x78000000: 0x2, + 0x88000000: 0x800200, + 0x98000000: 0x8200, + 0xa8000000: 0x808000, + 0xb8000000: 0x800202, + 0xc8000000: 0x800002, + 0xd8000000: 0x8002, + 0xe8000000: 0x202, + 0xf8000000: 0x800000, + 0x1: 0x8000, + 0x10000001: 0x2, + 0x20000001: 0x808200, + 0x30000001: 0x800000, + 0x40000001: 0x808002, + 0x50000001: 0x8200, + 0x60000001: 0x200, + 0x70000001: 0x800202, + 0x80000001: 0x808202, + 0x90000001: 0x808000, + 0xa0000001: 0x800002, + 0xb0000001: 0x8202, + 0xc0000001: 0x202, + 0xd0000001: 0x800200, + 0xe0000001: 0x8002, + 0xf0000001: 0x0, + 0x8000001: 0x808202, + 0x18000001: 0x808000, + 0x28000001: 0x800000, + 0x38000001: 0x200, + 0x48000001: 0x8000, + 0x58000001: 0x800002, + 0x68000001: 0x2, + 0x78000001: 0x8202, + 0x88000001: 0x8002, + 0x98000001: 0x800202, + 0xa8000001: 0x202, + 0xb8000001: 0x808200, + 0xc8000001: 0x800200, + 0xd8000001: 0x0, + 0xe8000001: 0x8200, + 0xf8000001: 0x808002 + }, + { + 0x0: 0x40084010, + 0x1000000: 0x4000, + 0x2000000: 0x80000, + 0x3000000: 0x40080010, + 0x4000000: 0x40000010, + 0x5000000: 0x40084000, + 0x6000000: 0x40004000, + 0x7000000: 0x10, + 0x8000000: 0x84000, + 0x9000000: 0x40004010, + 0xa000000: 0x40000000, + 0xb000000: 0x84010, + 0xc000000: 0x80010, + 0xd000000: 0x0, + 0xe000000: 0x4010, + 0xf000000: 0x40080000, + 0x800000: 0x40004000, + 0x1800000: 0x84010, + 0x2800000: 0x10, + 0x3800000: 0x40004010, + 0x4800000: 0x40084010, + 0x5800000: 0x40000000, + 0x6800000: 0x80000, + 0x7800000: 0x40080010, + 0x8800000: 0x80010, + 0x9800000: 0x0, + 0xa800000: 0x4000, + 0xb800000: 0x40080000, + 0xc800000: 0x40000010, + 0xd800000: 0x84000, + 0xe800000: 0x40084000, + 0xf800000: 0x4010, + 0x10000000: 0x0, + 0x11000000: 0x40080010, + 0x12000000: 0x40004010, + 0x13000000: 0x40084000, + 0x14000000: 0x40080000, + 0x15000000: 0x10, + 0x16000000: 0x84010, + 0x17000000: 0x4000, + 0x18000000: 0x4010, + 0x19000000: 0x80000, + 0x1a000000: 0x80010, + 0x1b000000: 0x40000010, + 0x1c000000: 0x84000, + 0x1d000000: 0x40004000, + 0x1e000000: 0x40000000, + 0x1f000000: 0x40084010, + 0x10800000: 0x84010, + 0x11800000: 0x80000, + 0x12800000: 0x40080000, + 0x13800000: 0x4000, + 0x14800000: 0x40004000, + 0x15800000: 0x40084010, + 0x16800000: 0x10, + 0x17800000: 0x40000000, + 0x18800000: 0x40084000, + 0x19800000: 0x40000010, + 0x1a800000: 0x40004010, + 0x1b800000: 0x80010, + 0x1c800000: 0x0, + 0x1d800000: 0x4010, + 0x1e800000: 0x40080010, + 0x1f800000: 0x84000 + }, + { + 0x0: 0x104, + 0x100000: 0x0, + 0x200000: 0x4000100, + 0x300000: 0x10104, + 0x400000: 0x10004, + 0x500000: 0x4000004, + 0x600000: 0x4010104, + 0x700000: 0x4010000, + 0x800000: 0x4000000, + 0x900000: 0x4010100, + 0xa00000: 0x10100, + 0xb00000: 0x4010004, + 0xc00000: 0x4000104, + 0xd00000: 0x10000, + 0xe00000: 0x4, + 0xf00000: 0x100, + 0x80000: 0x4010100, + 0x180000: 0x4010004, + 0x280000: 0x0, + 0x380000: 0x4000100, + 0x480000: 0x4000004, + 0x580000: 0x10000, + 0x680000: 0x10004, + 0x780000: 0x104, + 0x880000: 0x4, + 0x980000: 0x100, + 0xa80000: 0x4010000, + 0xb80000: 0x10104, + 0xc80000: 0x10100, + 0xd80000: 0x4000104, + 0xe80000: 0x4010104, + 0xf80000: 0x4000000, + 0x1000000: 0x4010100, + 0x1100000: 0x10004, + 0x1200000: 0x10000, + 0x1300000: 0x4000100, + 0x1400000: 0x100, + 0x1500000: 0x4010104, + 0x1600000: 0x4000004, + 0x1700000: 0x0, + 0x1800000: 0x4000104, + 0x1900000: 0x4000000, + 0x1a00000: 0x4, + 0x1b00000: 0x10100, + 0x1c00000: 0x4010000, + 0x1d00000: 0x104, + 0x1e00000: 0x10104, + 0x1f00000: 0x4010004, + 0x1080000: 0x4000000, + 0x1180000: 0x104, + 0x1280000: 0x4010100, + 0x1380000: 0x0, + 0x1480000: 0x10004, + 0x1580000: 0x4000100, + 0x1680000: 0x100, + 0x1780000: 0x4010004, + 0x1880000: 0x10000, + 0x1980000: 0x4010104, + 0x1a80000: 0x10104, + 0x1b80000: 0x4000004, + 0x1c80000: 0x4000104, + 0x1d80000: 0x4010000, + 0x1e80000: 0x4, + 0x1f80000: 0x10100 + }, + { + 0x0: 0x80401000, + 0x10000: 0x80001040, + 0x20000: 0x401040, + 0x30000: 0x80400000, + 0x40000: 0x0, + 0x50000: 0x401000, + 0x60000: 0x80000040, + 0x70000: 0x400040, + 0x80000: 0x80000000, + 0x90000: 0x400000, + 0xa0000: 0x40, + 0xb0000: 0x80001000, + 0xc0000: 0x80400040, + 0xd0000: 0x1040, + 0xe0000: 0x1000, + 0xf0000: 0x80401040, + 0x8000: 0x80001040, + 0x18000: 0x40, + 0x28000: 0x80400040, + 0x38000: 0x80001000, + 0x48000: 0x401000, + 0x58000: 0x80401040, + 0x68000: 0x0, + 0x78000: 0x80400000, + 0x88000: 0x1000, + 0x98000: 0x80401000, + 0xa8000: 0x400000, + 0xb8000: 0x1040, + 0xc8000: 0x80000000, + 0xd8000: 0x400040, + 0xe8000: 0x401040, + 0xf8000: 0x80000040, + 0x100000: 0x400040, + 0x110000: 0x401000, + 0x120000: 0x80000040, + 0x130000: 0x0, + 0x140000: 0x1040, + 0x150000: 0x80400040, + 0x160000: 0x80401000, + 0x170000: 0x80001040, + 0x180000: 0x80401040, + 0x190000: 0x80000000, + 0x1a0000: 0x80400000, + 0x1b0000: 0x401040, + 0x1c0000: 0x80001000, + 0x1d0000: 0x400000, + 0x1e0000: 0x40, + 0x1f0000: 0x1000, + 0x108000: 0x80400000, + 0x118000: 0x80401040, + 0x128000: 0x0, + 0x138000: 0x401000, + 0x148000: 0x400040, + 0x158000: 0x80000000, + 0x168000: 0x80001040, + 0x178000: 0x40, + 0x188000: 0x80000040, + 0x198000: 0x1000, + 0x1a8000: 0x80001000, + 0x1b8000: 0x80400040, + 0x1c8000: 0x1040, + 0x1d8000: 0x80401000, + 0x1e8000: 0x400000, + 0x1f8000: 0x401040 + }, + { + 0x0: 0x80, + 0x1000: 0x1040000, + 0x2000: 0x40000, + 0x3000: 0x20000000, + 0x4000: 0x20040080, + 0x5000: 0x1000080, + 0x6000: 0x21000080, + 0x7000: 0x40080, + 0x8000: 0x1000000, + 0x9000: 0x20040000, + 0xa000: 0x20000080, + 0xb000: 0x21040080, + 0xc000: 0x21040000, + 0xd000: 0x0, + 0xe000: 0x1040080, + 0xf000: 0x21000000, + 0x800: 0x1040080, + 0x1800: 0x21000080, + 0x2800: 0x80, + 0x3800: 0x1040000, + 0x4800: 0x40000, + 0x5800: 0x20040080, + 0x6800: 0x21040000, + 0x7800: 0x20000000, + 0x8800: 0x20040000, + 0x9800: 0x0, + 0xa800: 0x21040080, + 0xb800: 0x1000080, + 0xc800: 0x20000080, + 0xd800: 0x21000000, + 0xe800: 0x1000000, + 0xf800: 0x40080, + 0x10000: 0x40000, + 0x11000: 0x80, + 0x12000: 0x20000000, + 0x13000: 0x21000080, + 0x14000: 0x1000080, + 0x15000: 0x21040000, + 0x16000: 0x20040080, + 0x17000: 0x1000000, + 0x18000: 0x21040080, + 0x19000: 0x21000000, + 0x1a000: 0x1040000, + 0x1b000: 0x20040000, + 0x1c000: 0x40080, + 0x1d000: 0x20000080, + 0x1e000: 0x0, + 0x1f000: 0x1040080, + 0x10800: 0x21000080, + 0x11800: 0x1000000, + 0x12800: 0x1040000, + 0x13800: 0x20040080, + 0x14800: 0x20000000, + 0x15800: 0x1040080, + 0x16800: 0x80, + 0x17800: 0x21040000, + 0x18800: 0x40080, + 0x19800: 0x21040080, + 0x1a800: 0x0, + 0x1b800: 0x21000000, + 0x1c800: 0x1000080, + 0x1d800: 0x40000, + 0x1e800: 0x20040000, + 0x1f800: 0x20000080 + }, + { + 0x0: 0x10000008, + 0x100: 0x2000, + 0x200: 0x10200000, + 0x300: 0x10202008, + 0x400: 0x10002000, + 0x500: 0x200000, + 0x600: 0x200008, + 0x700: 0x10000000, + 0x800: 0x0, + 0x900: 0x10002008, + 0xa00: 0x202000, + 0xb00: 0x8, + 0xc00: 0x10200008, + 0xd00: 0x202008, + 0xe00: 0x2008, + 0xf00: 0x10202000, + 0x80: 0x10200000, + 0x180: 0x10202008, + 0x280: 0x8, + 0x380: 0x200000, + 0x480: 0x202008, + 0x580: 0x10000008, + 0x680: 0x10002000, + 0x780: 0x2008, + 0x880: 0x200008, + 0x980: 0x2000, + 0xa80: 0x10002008, + 0xb80: 0x10200008, + 0xc80: 0x0, + 0xd80: 0x10202000, + 0xe80: 0x202000, + 0xf80: 0x10000000, + 0x1000: 0x10002000, + 0x1100: 0x10200008, + 0x1200: 0x10202008, + 0x1300: 0x2008, + 0x1400: 0x200000, + 0x1500: 0x10000000, + 0x1600: 0x10000008, + 0x1700: 0x202000, + 0x1800: 0x202008, + 0x1900: 0x0, + 0x1a00: 0x8, + 0x1b00: 0x10200000, + 0x1c00: 0x2000, + 0x1d00: 0x10002008, + 0x1e00: 0x10202000, + 0x1f00: 0x200008, + 0x1080: 0x8, + 0x1180: 0x202000, + 0x1280: 0x200000, + 0x1380: 0x10000008, + 0x1480: 0x10002000, + 0x1580: 0x2008, + 0x1680: 0x10202008, + 0x1780: 0x10200000, + 0x1880: 0x10202000, + 0x1980: 0x10200008, + 0x1a80: 0x2000, + 0x1b80: 0x202008, + 0x1c80: 0x200008, + 0x1d80: 0x0, + 0x1e80: 0x10000000, + 0x1f80: 0x10002008 + }, + { + 0x0: 0x100000, + 0x10: 0x2000401, + 0x20: 0x400, + 0x30: 0x100401, + 0x40: 0x2100401, + 0x50: 0x0, + 0x60: 0x1, + 0x70: 0x2100001, + 0x80: 0x2000400, + 0x90: 0x100001, + 0xa0: 0x2000001, + 0xb0: 0x2100400, + 0xc0: 0x2100000, + 0xd0: 0x401, + 0xe0: 0x100400, + 0xf0: 0x2000000, + 0x8: 0x2100001, + 0x18: 0x0, + 0x28: 0x2000401, + 0x38: 0x2100400, + 0x48: 0x100000, + 0x58: 0x2000001, + 0x68: 0x2000000, + 0x78: 0x401, + 0x88: 0x100401, + 0x98: 0x2000400, + 0xa8: 0x2100000, + 0xb8: 0x100001, + 0xc8: 0x400, + 0xd8: 0x2100401, + 0xe8: 0x1, + 0xf8: 0x100400, + 0x100: 0x2000000, + 0x110: 0x100000, + 0x120: 0x2000401, + 0x130: 0x2100001, + 0x140: 0x100001, + 0x150: 0x2000400, + 0x160: 0x2100400, + 0x170: 0x100401, + 0x180: 0x401, + 0x190: 0x2100401, + 0x1a0: 0x100400, + 0x1b0: 0x1, + 0x1c0: 0x0, + 0x1d0: 0x2100000, + 0x1e0: 0x2000001, + 0x1f0: 0x400, + 0x108: 0x100400, + 0x118: 0x2000401, + 0x128: 0x2100001, + 0x138: 0x1, + 0x148: 0x2000000, + 0x158: 0x100000, + 0x168: 0x401, + 0x178: 0x2100400, + 0x188: 0x2000001, + 0x198: 0x2100000, + 0x1a8: 0x0, + 0x1b8: 0x2100401, + 0x1c8: 0x100401, + 0x1d8: 0x400, + 0x1e8: 0x2000400, + 0x1f8: 0x100001 + }, + { + 0x0: 0x8000820, + 0x1: 0x20000, + 0x2: 0x8000000, + 0x3: 0x20, + 0x4: 0x20020, + 0x5: 0x8020820, + 0x6: 0x8020800, + 0x7: 0x800, + 0x8: 0x8020000, + 0x9: 0x8000800, + 0xa: 0x20800, + 0xb: 0x8020020, + 0xc: 0x820, + 0xd: 0x0, + 0xe: 0x8000020, + 0xf: 0x20820, + 0x80000000: 0x800, + 0x80000001: 0x8020820, + 0x80000002: 0x8000820, + 0x80000003: 0x8000000, + 0x80000004: 0x8020000, + 0x80000005: 0x20800, + 0x80000006: 0x20820, + 0x80000007: 0x20, + 0x80000008: 0x8000020, + 0x80000009: 0x820, + 0x8000000a: 0x20020, + 0x8000000b: 0x8020800, + 0x8000000c: 0x0, + 0x8000000d: 0x8020020, + 0x8000000e: 0x8000800, + 0x8000000f: 0x20000, + 0x10: 0x20820, + 0x11: 0x8020800, + 0x12: 0x20, + 0x13: 0x800, + 0x14: 0x8000800, + 0x15: 0x8000020, + 0x16: 0x8020020, + 0x17: 0x20000, + 0x18: 0x0, + 0x19: 0x20020, + 0x1a: 0x8020000, + 0x1b: 0x8000820, + 0x1c: 0x8020820, + 0x1d: 0x20800, + 0x1e: 0x820, + 0x1f: 0x8000000, + 0x80000010: 0x20000, + 0x80000011: 0x800, + 0x80000012: 0x8020020, + 0x80000013: 0x20820, + 0x80000014: 0x20, + 0x80000015: 0x8020000, + 0x80000016: 0x8000000, + 0x80000017: 0x8000820, + 0x80000018: 0x8020820, + 0x80000019: 0x8000020, + 0x8000001a: 0x8000800, + 0x8000001b: 0x0, + 0x8000001c: 0x20800, + 0x8000001d: 0x820, + 0x8000001e: 0x20020, + 0x8000001f: 0x8020800 + } + ]; + + // Masks that select the SBOX input + var SBOX_MASK = [ + 0xf8000001, 0x1f800000, 0x01f80000, 0x001f8000, + 0x0001f800, 0x00001f80, 0x000001f8, 0x8000001f + ]; + + /** + * DES block cipher algorithm. + */ + var DES = C_algo.DES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Select 56 bits according to PC1 + var keyBits = []; + for (var i = 0; i < 56; i++) { + var keyBitPos = PC1[i] - 1; + keyBits[i] = (keyWords[keyBitPos >>> 5] >>> (31 - keyBitPos % 32)) & 1; + } + + // Assemble 16 subkeys + var subKeys = this._subKeys = []; + for (var nSubKey = 0; nSubKey < 16; nSubKey++) { + // Create subkey + var subKey = subKeys[nSubKey] = []; + + // Shortcut + var bitShift = BIT_SHIFTS[nSubKey]; + + // Select 48 bits according to PC2 + for (var i = 0; i < 24; i++) { + // Select from the left 28 key bits + subKey[(i / 6) | 0] |= keyBits[((PC2[i] - 1) + bitShift) % 28] << (31 - i % 6); + + // Select from the right 28 key bits + subKey[4 + ((i / 6) | 0)] |= keyBits[28 + (((PC2[i + 24] - 1) + bitShift) % 28)] << (31 - i % 6); + } + + // Since each subkey is applied to an expanded 32-bit input, + // the subkey can be broken into 8 values scaled to 32-bits, + // which allows the key to be used without expansion + subKey[0] = (subKey[0] << 1) | (subKey[0] >>> 31); + for (var i = 1; i < 7; i++) { + subKey[i] = subKey[i] >>> ((i - 1) * 4 + 3); + } + subKey[7] = (subKey[7] << 5) | (subKey[7] >>> 27); + } + + // Compute inverse subkeys + var invSubKeys = this._invSubKeys = []; + for (var i = 0; i < 16; i++) { + invSubKeys[i] = subKeys[15 - i]; + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._subKeys); + }, + + decryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._invSubKeys); + }, + + _doCryptBlock: function (M, offset, subKeys) { + // Get input + this._lBlock = M[offset]; + this._rBlock = M[offset + 1]; + + // Initial permutation + exchangeLR.call(this, 4, 0x0f0f0f0f); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeRL.call(this, 2, 0x33333333); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeLR.call(this, 1, 0x55555555); + + // Rounds + for (var round = 0; round < 16; round++) { + // Shortcuts + var subKey = subKeys[round]; + var lBlock = this._lBlock; + var rBlock = this._rBlock; + + // Feistel function + var f = 0; + for (var i = 0; i < 8; i++) { + f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0]; + } + this._lBlock = rBlock; + this._rBlock = lBlock ^ f; + } + + // Undo swap from last round + var t = this._lBlock; + this._lBlock = this._rBlock; + this._rBlock = t; + + // Final permutation + exchangeLR.call(this, 1, 0x55555555); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeRL.call(this, 2, 0x33333333); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeLR.call(this, 4, 0x0f0f0f0f); + + // Set output + M[offset] = this._lBlock; + M[offset + 1] = this._rBlock; + }, + + keySize: 64/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + // Swap bits across the left and right words + function exchangeLR(offset, mask) { + var t = ((this._lBlock >>> offset) ^ this._rBlock) & mask; + this._rBlock ^= t; + this._lBlock ^= t << offset; + } + + function exchangeRL(offset, mask) { + var t = ((this._rBlock >>> offset) ^ this._lBlock) & mask; + this._lBlock ^= t; + this._rBlock ^= t << offset; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.DES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg); + */ + C.DES = BlockCipher._createHelper(DES); + + /** + * Triple-DES block cipher algorithm. + */ + var TripleDES = C_algo.TripleDES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Create DES instances + this._des1 = DES.createEncryptor(WordArray.create(keyWords.slice(0, 2))); + this._des2 = DES.createEncryptor(WordArray.create(keyWords.slice(2, 4))); + this._des3 = DES.createEncryptor(WordArray.create(keyWords.slice(4, 6))); + }, + + encryptBlock: function (M, offset) { + this._des1.encryptBlock(M, offset); + this._des2.decryptBlock(M, offset); + this._des3.encryptBlock(M, offset); + }, + + decryptBlock: function (M, offset) { + this._des3.decryptBlock(M, offset); + this._des2.encryptBlock(M, offset); + this._des1.decryptBlock(M, offset); + }, + + keySize: 192/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg); + */ + C.TripleDES = BlockCipher._createHelper(TripleDES); + }()); + + + return CryptoJS.TripleDES; + +})); +},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],82:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -7547,19 +12842,15 @@ module.exports = transfer; return CryptoJS; })); -},{"./core":47}],50:[function(require,module,exports){ +},{"./core":51}],83:[function(require,module,exports){ /*! https://mths.be/utf8js v2.0.0 by @mathias */ ;(function(root) { - // Detect free variables 'exports' var freeExports = typeof exports == 'object' && exports; - // Detect free variable 'module' var freeModule = typeof module == 'object' && module && module.exports == freeExports && module; - // Detect free variable 'global', from Node.js or Browserified code, - // and use it as 'root' var freeGlobal = typeof global == 'object' && global; if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { root = freeGlobal; @@ -7800,26 +13091,15 @@ module.exports = BigNumber; // jshint ignore:line },{}],"web3":[function(require,module,exports){ -var web3 = require('./lib/web3'); -var namereg = require('./lib/web3/namereg'); - -web3.providers.HttpProvider = require('./lib/web3/httpprovider'); -web3.providers.IpcProvider = require('./lib/web3/ipcprovider'); - -web3.eth.contract = require('./lib/web3/contract'); -web3.eth.namereg = namereg.namereg; -web3.eth.ibanNamereg = namereg.ibanNamereg; -web3.eth.sendIBANTransaction = require('./lib/web3/transfer'); -web3.eth.iban = require('./lib/web3/iban'); +var Web3 = require('./lib/web3'); // dont override global variable -if (typeof window !== 'undefined' && typeof window.web3 === 'undefined') { - window.web3 = web3; +if (typeof window !== 'undefined' && typeof window.Web3 === 'undefined') { + window.Web3 = Web3; } -module.exports = web3; +module.exports = Web3; - -},{"./lib/web3":22,"./lib/web3/contract":25,"./lib/web3/httpprovider":31,"./lib/web3/iban":32,"./lib/web3/ipcprovider":33,"./lib/web3/namereg":41,"./lib/web3/transfer":45}]},{},["web3"]) +},{"./lib/web3":22}]},{},["web3"]) //# sourceMappingURL=web3-light.js.map ` diff --git a/xeth/xeth.go b/xeth/xeth.go index 19c42a9a32..a45ce688bf 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -644,7 +644,9 @@ func (p *XEth) NewWhisperFilter(to, from string, topics [][]string) int { callback := func(msg WhisperMessage) { p.messagesMu.RLock() // Only read lock to the filter pool defer p.messagesMu.RUnlock() - p.messages[id].insert(msg) + if p.messages[id] != nil { + p.messages[id].insert(msg) + } } // Initialize the core whisper filter and wrap into xeth id = p.Whisper().Watch(to, from, topics, callback) From 9e1d9bff3b18cfa09ba96cd027fa653daae10816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 5 Nov 2015 23:57:57 +0200 Subject: [PATCH 07/78] node: customizable protocol and service stacks --- node/config.go | 171 +++++++++++++ node/config_test.go | 120 +++++++++ node/errors.go | 31 +++ node/node.go | 252 +++++++++++++++++++ node/node_example_test.go | 87 +++++++ node/node_test.go | 492 +++++++++++++++++++++++++++++++++++++ node/service.go | 67 +++++ node/service_test.go | 60 +++++ node/utils.go | 33 +++ p2p/discover/table.go | 7 +- p2p/discover/table_test.go | 8 +- p2p/discover/udp.go | 16 +- p2p/discover/udp_test.go | 2 +- 13 files changed, 1333 insertions(+), 13 deletions(-) create mode 100644 node/config.go create mode 100644 node/config_test.go create mode 100644 node/errors.go create mode 100644 node/node.go create mode 100644 node/node_example_test.go create mode 100644 node/node_test.go create mode 100644 node/service.go create mode 100644 node/service_test.go create mode 100644 node/utils.go diff --git a/node/config.go b/node/config.go new file mode 100644 index 0000000000..93f0ba79d6 --- /dev/null +++ b/node/config.go @@ -0,0 +1,171 @@ +// Copyright 2015 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 . + +package node + +import ( + "crypto/ecdsa" + "encoding/json" + "io/ioutil" + "net" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/nat" +) + +var ( + datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key + datadirStaticNodes = "static-nodes.json" // Path within the datadir to the static node list + datadirTrustedNodes = "trusted-nodes.json" // Path within the datadir to the trusted node list + datadirNodeDatabase = "nodes" // Path within the datadir to store the node infos +) + +// Config represents a small collection of configuration values to fine tune the +// P2P network layer of a protocol stack. These values can be further extended by +// all registered services. +type Config struct { + // DataDir is the file system folder the node should use for any data storage + // requirements. The configured data directory will not be directly shared with + // registered services, instead those can use utility methods to create/access + // databases or flat files. This enables ephemeral nodes which can fully reside + // in memory. + DataDir string + + // This field should be a valid secp256k1 private key that will be used for both + // remote peer identification as well as network traffic encryption. If no key + // is configured, the preset one is loaded from the data dir, generating it if + // needed. + PrivateKey *ecdsa.PrivateKey + + // Name sets the node name of this server. Use common.MakeName to create a name + // that follows existing conventions. + Name string + + // NoDiscovery specifies whether the peer discovery mechanism should be started + // or not. Disabling is usually useful for protocol debugging (manual topology). + NoDiscovery bool + + // Bootstrap nodes used to establish connectivity with the rest of the network. + BootstrapNodes []*discover.Node + + // Network interface address on which the node should listen for inbound peers. + ListenAddr string + + // If set to a non-nil value, the given NAT port mapper is used to make the + // listening port available to the Internet. + NAT nat.Interface + + // If Dialer is set to a non-nil value, the given Dialer is used to dial outbound + // peer connections. + Dialer *net.Dialer + + // If NoDial is true, the node will not dial any peers. + NoDial bool + + // MaxPeers is the maximum number of peers that can be connected. If this is + // set to zero, then only the configured static and trusted peers can connect. + MaxPeers int + + // MaxPendingPeers is the maximum number of peers that can be pending in the + // handshake phase, counted separately for inbound and outbound connections. + // Zero defaults to preset values. + MaxPendingPeers int +} + +// NodeKey retrieves the currently configured private key of the node, checking +// first any manually set key, falling back to the one found in the configured +// data folder. If no key can be found, a new one is generated. +func (c *Config) NodeKey() *ecdsa.PrivateKey { + // Use any specifically configured key + if c.PrivateKey != nil { + return c.PrivateKey + } + // Generate ephemeral key if no datadir is being used + if c.DataDir == "" { + key, err := crypto.GenerateKey() + if err != nil { + glog.Fatalf("Failed to generate ephemeral node key: %v", err) + } + return key + } + // Fall back to persistent key from the data directory + keyfile := filepath.Join(c.DataDir, datadirPrivateKey) + if key, err := crypto.LoadECDSA(keyfile); err == nil { + return key + } + // No persistent key found, generate and store a new one + key, err := crypto.GenerateKey() + if err != nil { + glog.Fatalf("Failed to generate node key: %v", err) + } + if err := crypto.SaveECDSA(keyfile, key); err != nil { + glog.V(logger.Error).Infof("Failed to persist node key: %v", err) + } + return key +} + +// StaticNodes returns a list of node enode URLs configured as static nodes. +func (c *Config) StaticNodes() []*discover.Node { + return c.parsePersistentNodes(datadirStaticNodes) +} + +// TrusterNodes returns a list of node enode URLs configured as trusted nodes. +func (c *Config) TrusterNodes() []*discover.Node { + return c.parsePersistentNodes(datadirTrustedNodes) +} + +// parsePersistentNodes parses a list of discovery node URLs loaded from a .json +// file from within the data directory. +func (c *Config) parsePersistentNodes(file string) []*discover.Node { + // Short circuit if no node config is present + if c.DataDir == "" { + return nil + } + path := filepath.Join(c.DataDir, file) + if _, err := os.Stat(path); err != nil { + return nil + } + // Load the nodes from the config file + blob, err := ioutil.ReadFile(path) + if err != nil { + glog.V(logger.Error).Infof("Failed to access nodes: %v", err) + return nil + } + nodelist := []string{} + if err := json.Unmarshal(blob, &nodelist); err != nil { + glog.V(logger.Error).Infof("Failed to load nodes: %v", err) + return nil + } + // Interpret the list as a discovery node array + var nodes []*discover.Node + for _, url := range nodelist { + if url == "" { + continue + } + node, err := discover.ParseNode(url) + if err != nil { + glog.V(logger.Error).Infof("Node URL %s: %v\n", url, err) + continue + } + nodes = append(nodes, node) + } + return nodes +} diff --git a/node/config_test.go b/node/config_test.go new file mode 100644 index 0000000000..f59f3c0fe0 --- /dev/null +++ b/node/config_test.go @@ -0,0 +1,120 @@ +// Copyright 2015 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 . + +package node + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/ethereum/go-ethereum/crypto" +) + +// Tests that datadirs can be successfully created, be them manually configured +// ones or automatically generated temporary ones. +func TestDatadirCreation(t *testing.T) { + // Create a temporary data dir and check that it can be used by a node + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create manual data dir: %v", err) + } + defer os.RemoveAll(dir) + + if _, err := New(&Config{DataDir: dir}); err != nil { + t.Fatalf("failed to create stack with existing datadir: %v", err) + } + // Generate a long non-existing datadir path and check that it gets created by a node + dir = filepath.Join(dir, "a", "b", "c", "d", "e", "f") + if _, err := New(&Config{DataDir: dir}); err != nil { + t.Fatalf("failed to create stack with creatable datadir: %v", err) + } + if _, err := os.Stat(dir); err != nil { + t.Fatalf("freshly created datadir not accessible: %v", err) + } + // Verify that an impossible datadir fails creation + file, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("failed to create temporary file: %v", err) + } + defer os.Remove(file.Name()) + + dir = filepath.Join(file.Name(), "invalid/path") + if _, err := New(&Config{DataDir: dir}); err == nil { + t.Fatalf("protocol stack created with an invalid datadir") + } +} + +// Tests that node keys can be correctly created, persisted, loaded and/or made +// ephemeral. +func TestNodeKeyPersistency(t *testing.T) { + // Create a temporary folder and make sure no key is present + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temporary data directory: %v", err) + } + defer os.RemoveAll(dir) + + if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil { + t.Fatalf("non-created node key already exists") + } + // Configure a node with a preset key and ensure it's not persisted + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate one-shot node key: %v", err) + } + if _, err := New(&Config{DataDir: dir, PrivateKey: key}); err != nil { + t.Fatalf("failed to create empty stack: %v", err) + } + if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil { + t.Fatalf("one-shot node key persisted to data directory") + } + // Configure a node with no preset key and ensure it is persisted this time + if _, err := New(&Config{DataDir: dir}); err != nil { + t.Fatalf("failed to create newly keyed stack: %v", err) + } + if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err != nil { + t.Fatalf("node key not persisted to data directory: %v", err) + } + key, err = crypto.LoadECDSA(filepath.Join(dir, datadirPrivateKey)) + if err != nil { + t.Fatalf("failed to load freshly persisted node key: %v", err) + } + blob1, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey)) + if err != nil { + t.Fatalf("failed to read freshly persisted node key: %v", err) + } + // Configure a new node and ensure the previously persisted key is loaded + if _, err := New(&Config{DataDir: dir}); err != nil { + t.Fatalf("failed to create previously keyed stack: %v", err) + } + blob2, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey)) + if err != nil { + t.Fatalf("failed to read previously persisted node key: %v", err) + } + if bytes.Compare(blob1, blob2) != 0 { + t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1) + } + // Configure ephemeral node and ensure no key is dumped locally + if _, err := New(&Config{DataDir: ""}); err != nil { + t.Fatalf("failed to create ephemeral stack: %v", err) + } + if _, err := os.Stat(filepath.Join(".", datadirPrivateKey)); err == nil { + t.Fatalf("ephemeral node key persisted to disk") + } +} diff --git a/node/errors.go b/node/errors.go new file mode 100644 index 0000000000..f8eece06d1 --- /dev/null +++ b/node/errors.go @@ -0,0 +1,31 @@ +// Copyright 2015 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 . + +package node + +import "fmt" + +// StopError is returned if a node fails to stop either any of its registered +// services or itself. +type StopError struct { + Server error + Services map[string]error +} + +// Error generates a textual representation of the stop error. +func (e *StopError) Error() string { + return fmt.Sprintf("server: %v, services: %v", e.Server, e.Services) +} diff --git a/node/node.go b/node/node.go new file mode 100644 index 0000000000..ad4e69414c --- /dev/null +++ b/node/node.go @@ -0,0 +1,252 @@ +// Copyright 2015 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 . + +// Package node represents the Ethereum protocol stack container. +package node + +import ( + "errors" + "os" + "path/filepath" + "sync" + "syscall" + + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/p2p" +) + +var ( + ErrDatadirUsed = errors.New("datadir already used") + ErrNodeStopped = errors.New("node not started") + ErrNodeRunning = errors.New("node already running") + ErrServiceUnknown = errors.New("service not registered") + ErrServiceRegistered = errors.New("service already registered") + + datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} +) + +// Node represents a P2P node into which arbitrary services might be registered. +type Node struct { + datadir string // Path to the currently used data directory + config *p2p.Server // Configuration of the underlying P2P networking layer + stack map[string]ServiceConstructor // Protocol stack registered into this node + emux *event.TypeMux // Event multiplexer used between the services of a stack + + running *p2p.Server // Currently running P2P networking layer + services map[string]Service // Currently running services + + lock sync.RWMutex +} + +// New creates a new P2P node, ready for protocol registration. +func New(conf *Config) (*Node, error) { + // Ensure the data directory exists, failing if it cannot be created + if conf.DataDir != "" { + if err := os.MkdirAll(conf.DataDir, 0700); err != nil { + return nil, err + } + } + // Assemble the networking layer and the node itself + nodeDbPath := "" + if conf.DataDir != "" { + nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase) + } + return &Node{ + datadir: conf.DataDir, + config: &p2p.Server{ + PrivateKey: conf.NodeKey(), + Name: conf.Name, + Discovery: !conf.NoDiscovery, + BootstrapNodes: conf.BootstrapNodes, + StaticNodes: conf.StaticNodes(), + TrustedNodes: conf.TrusterNodes(), + NodeDatabase: nodeDbPath, + ListenAddr: conf.ListenAddr, + NAT: conf.NAT, + Dialer: conf.Dialer, + NoDial: conf.NoDial, + MaxPeers: conf.MaxPeers, + MaxPendingPeers: conf.MaxPendingPeers, + }, + stack: make(map[string]ServiceConstructor), + emux: new(event.TypeMux), + }, nil +} + +// Register injects a new service into the node's stack. +func (n *Node) Register(id string, constructor ServiceConstructor) error { + n.lock.Lock() + defer n.lock.Unlock() + + // Short circuit if the node is running or if the id is taken + if n.running != nil { + return ErrNodeRunning + } + if _, ok := n.stack[id]; ok { + return ErrServiceRegistered + } + // Otherwise register the service and return + n.stack[id] = constructor + + return nil +} + +// Unregister removes a service from a node's stack. If the node is currently +// running, an error will be returned. +func (n *Node) Unregister(id string) error { + n.lock.Lock() + defer n.lock.Unlock() + + // Short circuit if the node is running, or if the service is unknown + if n.running != nil { + return ErrNodeRunning + } + if _, ok := n.stack[id]; !ok { + return ErrServiceUnknown + } + // Otherwise drop the service and return + delete(n.stack, id) + return nil +} + +// Start create a live P2P node and starts running it. +func (n *Node) Start() error { + n.lock.Lock() + defer n.lock.Unlock() + + // Short circuit if the node's already running + if n.running != nil { + return ErrNodeRunning + } + // Otherwise copy and specialize the P2P configuration + running := new(p2p.Server) + *running = *n.config + + ctx := &ServiceContext{ + dataDir: n.datadir, + EventMux: n.emux, + } + services := make(map[string]Service) + for id, constructor := range n.stack { + service, err := constructor(ctx) + if err != nil { + return err + } + services[id] = service + } + // Gather the protocols and start the freshly assembled P2P server + for _, service := range services { + running.Protocols = append(running.Protocols, service.Protocols()...) + } + if err := running.Start(); err != nil { + if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { + return ErrDatadirUsed + } + return err + } + // Start each of the services + started := []string{} + for id, service := range services { + // Start the next service, stopping all previous upon failure + if err := service.Start(); err != nil { + for _, id := range started { + services[id].Stop() + } + return err + } + // Mark the service started for potential cleanup + started = append(started, id) + } + // Finish initializing the startup + n.services = services + n.running = running + + return nil +} + +// Stop terminates a running node along with all it's services. In the node was +// not started, an error is returned. +func (n *Node) Stop() error { + n.lock.Lock() + defer n.lock.Unlock() + + // Short circuit if the node's not running + if n.running == nil { + return ErrNodeStopped + } + // Otherwise terminate all the services and the P2P server too + failure := &StopError{ + Services: make(map[string]error), + } + for id, service := range n.services { + if err := service.Stop(); err != nil { + failure.Services[id] = err + } + } + n.running.Stop() + + n.services = nil + n.running = nil + + if len(failure.Services) > 0 { + return failure + } + return nil +} + +// Restart terminates a running node and boots up a new one in its place. If the +// node isn't running, an error is returned. +func (n *Node) Restart() error { + if err := n.Stop(); err != nil { + return err + } + if err := n.Start(); err != nil { + return err + } + return nil +} + +// Server retrieves the currently running P2P network layer. This method is meant +// only to inspect fields of the currently running server, life cycle management +// should be left to this Node entity. +func (n *Node) Server() *p2p.Server { + n.lock.RLock() + defer n.lock.RUnlock() + + return n.running +} + +// Service retrieves a currently running services registered under a given id. +func (n *Node) Service(id string) Service { + n.lock.RLock() + defer n.lock.RUnlock() + + if n.services == nil { + return nil + } + return n.services[id] +} + +// DataDir retrieves the current datadir used by the protocol stack. +func (n *Node) DataDir() string { + return n.datadir +} + +// EventMux retrieves the event multiplexer used by all the network services in +// the current protocol stack. +func (n *Node) EventMux() *event.TypeMux { + return n.emux +} diff --git a/node/node_example_test.go b/node/node_example_test.go new file mode 100644 index 0000000000..f2bd014b06 --- /dev/null +++ b/node/node_example_test.go @@ -0,0 +1,87 @@ +// Copyright 2015 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 . + +package node_test + +import ( + "fmt" + "log" + + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" +) + +// SampleService is a trivial network service that can be attached to a node for +// life cycle management. +// +// The following methods are needed to implement a node.Service: +// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on +// - Start() error - method invoked when the node is ready to start the service +// - Stop() error - method invoked when the node terminates the service +type SampleService struct{} + +func (s *SampleService) Protocols() []p2p.Protocol { return nil } +func (s *SampleService) Start() error { fmt.Println("Sample service starting..."); return nil } +func (s *SampleService) Stop() error { fmt.Println("Sample service stopping..."); return nil } + +func ExampleUsage() { + // Create a network node to run protocols with the default values. The below list + // is only used to display each of the configuration options. All of these could + // have been ommited if the default behavior is desired. + nodeConfig := &node.Config{ + DataDir: "", // Empty uses ephemeral storage + PrivateKey: nil, // Nil generates a node key on the fly + Name: "", // Any textual node name is allowed + NoDiscovery: false, // Can disable discovering remote nodes + BootstrapNodes: []*discover.Node{}, // List of bootstrap nodes to use + ListenAddr: ":0", // Network interface to listen on + NAT: nil, // UPnP port mapper to use for crossing firewalls + Dialer: nil, // Custom dialer to use for establishing peer connections + NoDial: false, // Can prevent this node from dialing out + MaxPeers: 0, // Number of peers to allow + MaxPendingPeers: 0, // Number of peers allowed to handshake concurrently + } + stack, err := node.New(nodeConfig) + if err != nil { + log.Fatalf("Failed to create network node: %v", err) + } + // Create and register a simple network service. This is done through the definition + // of a node.ServiceConstructor that will instantiate a node.Service. The reason for + // the factory method approach is to support service restarts without relying on the + // individual implementations' support for such operations. + constructor := func(context *node.ServiceContext) (node.Service, error) { + return new(SampleService), nil + } + if err := stack.Register("my sample service", constructor); err != nil { + log.Fatalf("Failed to register service: %v", err) + } + // Boot up the entire protocol stack, do a restart and terminate + if err := stack.Start(); err != nil { + log.Fatalf("Failed to start the protocol stack: %v", err) + } + if err := stack.Restart(); err != nil { + log.Fatalf("Failed to restart the protocol stack: %v", err) + } + if err := stack.Stop(); err != nil { + log.Fatalf("Failed to stop the protocol stack: %v", err) + } + // Output: + // Sample service starting... + // Sample service stopping... + // Sample service starting... + // Sample service stopping... +} diff --git a/node/node_test.go b/node/node_test.go new file mode 100644 index 0000000000..7201276b59 --- /dev/null +++ b/node/node_test.go @@ -0,0 +1,492 @@ +// Copyright 2015 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 . + +package node + +import ( + "errors" + "io/ioutil" + "os" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p" +) + +var ( + testNodeKey, _ = crypto.GenerateKey() + + testNodeConfig = &Config{ + PrivateKey: testNodeKey, + Name: "test node", + } +) + +// Tests that an empty protocol stack can be started, restarted and stopped. +func TestNodeLifeCycle(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Ensure that a stopped node can be stopped again + for i := 0; i < 3; i++ { + if err := stack.Stop(); err != ErrNodeStopped { + t.Fatalf("iter %d: stop failure mismatch: have %v, want %v", i, err, ErrNodeStopped) + } + } + // Ensure that a node can be successfully started, but only once + if err := stack.Start(); err != nil { + t.Fatalf("failed to start node: %v", err) + } + if err := stack.Start(); err != ErrNodeRunning { + t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning) + } + // Ensure that a node can be restarted arbitrarily many times + for i := 0; i < 3; i++ { + if err := stack.Restart(); err != nil { + t.Fatalf("iter %d: failed to restart node: %v", i, err) + } + } + // Ensure that a node can be stopped, but only once + if err := stack.Stop(); err != nil { + t.Fatalf("failed to stop node: %v", err) + } + if err := stack.Stop(); err != ErrNodeStopped { + t.Fatalf("stop failure mismatch: have %v, want %v ", err, ErrNodeStopped) + } +} + +// Tests that if the data dir is already in use, an appropriate error is returned. +func TestNodeUsedDataDir(t *testing.T) { + // Create a temporary folder to use as the data directory + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temporary data directory: %v", err) + } + defer os.RemoveAll(dir) + + // Create a new node based on the data directory + original, err := New(&Config{DataDir: dir}) + if err != nil { + t.Fatalf("failed to create original protocol stack: %v", err) + } + if err := original.Start(); err != nil { + t.Fatalf("failed to start original protocol stack: %v", err) + } + defer original.Stop() + + // Create a second node based on the same data directory and ensure failure + duplicate, err := New(&Config{DataDir: dir}) + if err != nil { + t.Fatalf("failed to create duplicate protocol stack: %v", err) + } + if err := duplicate.Start(); err != ErrDatadirUsed { + t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed) + } +} + +// NoopService is a trivial implementation of the Service interface. +type NoopService struct{} + +func (s *NoopService) Protocols() []p2p.Protocol { return nil } +func (s *NoopService) Start() error { return nil } +func (s *NoopService) Stop() error { return nil } + +func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil } + +// Tests whether services can be registered and unregistered. +func TestServiceRegistry(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Create a batch of dummy services and ensure they don't exist + ids := []string{"A", "B", "C"} + for i, id := range ids { + if err := stack.Unregister(id); err != ErrServiceUnknown { + t.Fatalf("service %d: pre-unregistration failure mismatch: have %v, want %v", i, err, ErrServiceUnknown) + } + } + // Register the services, checking that the operation succeeds only once + for i, id := range ids { + if err := stack.Register(id, NewNoopService); err != nil { + t.Fatalf("service %d: registration failed: %v", i, err) + } + if err := stack.Register(id, NewNoopService); err != ErrServiceRegistered { + t.Fatalf("service %d: registration failure mismatch: have %v, want %v", i, err, ErrServiceRegistered) + } + } + // Unregister the services, checking that the operation succeeds only once + for i, id := range ids { + if err := stack.Unregister(id); err != nil { + t.Fatalf("service %d: unregistration failed: %v", i, err) + } + if err := stack.Unregister(id); err != ErrServiceUnknown { + t.Fatalf("service %d: unregistration failure mismatch: have %v, want %v", i, err, ErrServiceUnknown) + } + } +} + +// InstrumentedService is an implementation of Service for which all interface +// methods can be instrumented both return value as well as event hook wise. +type InstrumentedService struct { + protocols []p2p.Protocol + start error + stop error + + protocolsHook func() + startHook func() + stopHook func() +} + +func (s *InstrumentedService) Protocols() []p2p.Protocol { + if s.protocolsHook != nil { + s.protocolsHook() + } + return s.protocols +} + +func (s *InstrumentedService) Start() error { + if s.startHook != nil { + s.startHook() + } + return s.start +} + +func (s *InstrumentedService) Stop() error { + if s.stopHook != nil { + s.stopHook() + } + return s.stop +} + +// Tests that registered services get started and stopped correctly. +func TestServiceLifeCycle(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of life-cycle instrumented services + ids := []string{"A", "B", "C"} + + started := make(map[string]bool) + stopped := make(map[string]bool) + + for i, id := range ids { + id := id // Closure for the constructor + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + startHook: func() { started[id] = true }, + stopHook: func() { stopped[id] = true }, + }, nil + } + if err := stack.Register(id, constructor); err != nil { + t.Fatalf("service %d: registration failed: %v", i, err) + } + } + // Start the node and check that all services are running + if err := stack.Start(); err != nil { + t.Fatalf("failed to start protocol stack: %v", err) + } + for i, id := range ids { + if !started[id] { + t.Fatalf("service %d: freshly started service not running", i) + } + if stopped[id] { + t.Fatalf("service %d: freshly started service already stopped", i) + } + if stack.Service(id) == nil { + t.Fatalf("service %d: freshly started service unaccessible", i) + } + } + // Stop the node and check that all services have been stopped + if err := stack.Stop(); err != nil { + t.Fatalf("failed to stop protocol stack: %v", err) + } + for i, id := range ids { + if !stopped[id] { + t.Fatalf("service %d: freshly terminated service still running", i) + } + if service := stack.Service(id); service != nil { + t.Fatalf("service %d: freshly terminated service still accessible: %v", i, service) + } + } +} + +// Tests that services are restarted cleanly as new instances. +func TestServiceRestarts(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Define a service that does not support restarts + var ( + running bool + started int + ) + constructor := func(*ServiceContext) (Service, error) { + running = false + + return &InstrumentedService{ + startHook: func() { + if running { + panic("already running") + } + running = true + started++ + }, + }, nil + } + // Register the service and start the protocol stack + if err := stack.Register("service", constructor); err != nil { + t.Fatalf("failed to register the service: %v", err) + } + if err := stack.Start(); err != nil { + t.Fatalf("failed to start protocol stack: %v", err) + } + defer stack.Stop() + + if running != true || started != 1 { + t.Fatalf("running/started mismatch: have %v/%d, want true/1", running, started) + } + // Restart the stack a few times and check successful service restarts + for i := 0; i < 3; i++ { + if err := stack.Restart(); err != nil { + t.Fatalf("iter %d: failed to restart stack: %v", i, err) + } + } + if running != true || started != 4 { + t.Fatalf("running/started mismatch: have %v/%d, want true/4", running, started) + } +} + +// Tests that if a service fails to initialize itself, none of the other services +// will be allowed to even start. +func TestServiceConstructionAbortion(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Define a batch of good services + ids := []string{"A", "B", "C", "D", "E", "F"} + + started := make(map[string]bool) + for i, id := range ids { + id := id // Closure for the constructor + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + startHook: func() { started[id] = true }, + }, nil + } + if err := stack.Register(id, constructor); err != nil { + t.Fatalf("service %d: registration failed: %v", i, err) + } + } + // Register a service that fails to construct itself + failure := errors.New("fail") + failer := func(*ServiceContext) (Service, error) { + return nil, failure + } + if err := stack.Register("failer", failer); err != nil { + t.Fatalf("failer registration failed: %v", err) + } + // Start the protocol stack and ensure none of the services get started + for i := 0; i < 100; i++ { + if err := stack.Start(); err != failure { + t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) + } + for i, id := range ids { + if started[id] { + t.Fatalf("service %d: started should not have", i) + } + delete(started, id) + } + } +} + +// Tests that if a service fails to start, all others started before it will be +// shut down. +func TestServiceStartupAbortion(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of good services + ids := []string{"A", "B", "C", "D", "E", "F"} + + started := make(map[string]bool) + stopped := make(map[string]bool) + + for i, id := range ids { + id := id // Closure for the constructor + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + startHook: func() { started[id] = true }, + stopHook: func() { stopped[id] = true }, + }, nil + } + if err := stack.Register(id, constructor); err != nil { + t.Fatalf("service %d: registration failed: %v", i, err) + } + } + // Register a service that fails to start + failure := errors.New("fail") + failer := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + start: failure, + }, nil + } + if err := stack.Register("failer", failer); err != nil { + t.Fatalf("failer registration failed: %v", err) + } + // Start the protocol stack and ensure all started services stop + for i := 0; i < 100; i++ { + if err := stack.Start(); err != failure { + t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) + } + for i, id := range ids { + if started[id] && !stopped[id] { + t.Fatalf("service %d: started but not stopped", i) + } + delete(started, id) + delete(stopped, id) + } + } +} + +// Tests that even if a registered service fails to shut down cleanly, it does +// not influece the rest of the shutdown invocations. +func TestServiceTerminationGuarantee(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of good services + ids := []string{"A", "B", "C", "D", "E", "F"} + + started := make(map[string]bool) + stopped := make(map[string]bool) + + for i, id := range ids { + id := id // Closure for the constructor + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + startHook: func() { started[id] = true }, + stopHook: func() { stopped[id] = true }, + }, nil + } + if err := stack.Register(id, constructor); err != nil { + t.Fatalf("service %d: registration failed: %v", i, err) + } + } + // Register a service that fails to shot down cleanly + failure := errors.New("fail") + failer := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + stop: failure, + }, nil + } + if err := stack.Register("failer", failer); err != nil { + t.Fatalf("failer registration failed: %v", err) + } + // Start the protocol stack, and ensure that a failing shut down terminates all + for i := 0; i < 100; i++ { + // Start the stack and make sure all is online + if err := stack.Start(); err != nil { + t.Fatalf("iter %d: failed to start protocol stack: %v", i, err) + } + for j, id := range ids { + if !started[id] { + t.Fatalf("iter %d, service %d: service not running", i, j) + } + if stopped[id] { + t.Fatalf("iter %d, service %d: service already stopped", i, j) + } + } + // Stop the stack, verify failure and check all terminations + err := stack.Stop() + if err, ok := err.(*StopError); !ok { + t.Fatalf("iter %d: termination failure mismatch: have %v, want StopError", i, err) + } else { + if err.Services["failer"] != failure { + t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services["failer"], failure) + } + if len(err.Services) != 1 { + t.Fatalf("iter %d: failure count mismatch: have %d, want %d", i, len(err.Services), 1) + } + } + for j, id := range ids { + if !stopped[id] { + t.Fatalf("iter %d, service %d: service not terminated", i, j) + } + delete(started, id) + delete(stopped, id) + } + } +} + +// Tests that all protocols defined by individual services get launched. +func TestProtocolGather(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of services with some configured number of protocols + services := map[string]int{ + "Zero Protocols": 0, + "Single Protocol": 1, + "Many Protocols": 25, + } + for id, count := range services { + protocols := make([]p2p.Protocol, count) + for i := 0; i < len(protocols); i++ { + protocols[i].Name = id + protocols[i].Version = uint(i) + } + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + protocols: protocols, + }, nil + } + if err := stack.Register(id, constructor); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) + } + } + // Start the services and ensure all protocols start successfully + if err := stack.Start(); err != nil { + t.Fatalf("failed to start protocol stack: %v", err) + } + defer stack.Stop() + + protocols := stack.Server().Protocols + if len(protocols) != 26 { + t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26) + } + for id, count := range services { + for ver := 0; ver < count; ver++ { + launched := false + for i := 0; i < len(protocols); i++ { + if protocols[i].Name == id && protocols[i].Version == uint(ver) { + launched = true + break + } + } + if !launched { + t.Errorf("configured protocol not launched: %s v%d", id, ver) + } + } + } +} diff --git a/node/service.go b/node/service.go new file mode 100644 index 0000000000..ec838dbe8e --- /dev/null +++ b/node/service.go @@ -0,0 +1,67 @@ +// Copyright 2015 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 . + +package node + +import ( + "path/filepath" + + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/p2p" +) + +// ServiceContext is a collection of service independent options inherited from +// the protocol stack, that is passed to all constructors to be optionally used; +// as well as utility methods to operate on the service environment. +type ServiceContext struct { + dataDir string // Data directory for protocol persistence + EventMux *event.TypeMux // Event multiplexer used for decoupled notifications +} + +// Database opens an existing database with the given name (or creates one if no +// previous can be found) from within the node's data directory. If the node is +// an ephemeral one, a memory database is returned. +func (ctx *ServiceContext) Database(name string, cache int) (ethdb.Database, error) { + if ctx.dataDir == "" { + return ethdb.NewMemDatabase() + } + return ethdb.NewLDBDatabase(filepath.Join(ctx.dataDir, name), cache) +} + +// ServiceConstructor is the function signature of the constructors needed to be +// registered for service instantiation. +type ServiceConstructor func(ctx *ServiceContext) (Service, error) + +// Service is an individual protocol that can be registered into a node. +// +// Notes: +// - Service life-cycle management is delegated to the node. The service is +// allowed to initialize itself upon creation, but no goroutines should be +// spun up outside of the Start method. +// - Restart logic is not required as the node will create a fresh instance +// every time a service is started. +type Service interface { + // Protocol retrieves the P2P protocols the service wishes to start. + Protocols() []p2p.Protocol + + // Start spawns any goroutines required by the service. + Start() error + + // Stop terminates all goroutines belonging to the service, blocking until they + // are all terminated. + Stop() error +} diff --git a/node/service_test.go b/node/service_test.go new file mode 100644 index 0000000000..50a4f9715b --- /dev/null +++ b/node/service_test.go @@ -0,0 +1,60 @@ +// Copyright 2015 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 . + +package node + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +// Tests that service context methods work properly. +func TestServiceContext(t *testing.T) { + // Create a temporary folder and ensure no database is contained within + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temporary data directory: %v", err) + } + defer os.RemoveAll(dir) + + if _, err := os.Stat(filepath.Join(dir, "database")); err == nil { + t.Fatalf("non-created database already exists") + } + // Request the opening/creation of a database and ensure it persists to disk + ctx := &ServiceContext{dataDir: dir} + db, err := ctx.Database("persistent", 0) + if err != nil { + t.Fatalf("failed to open persistent database: %v", err) + } + db.Close() + + if _, err := os.Stat(filepath.Join(dir, "persistent")); err != nil { + t.Fatalf("persistent database doesn't exists: %v", err) + } + // Request th opening/creation of an ephemeral database and ensure it's not persisted + ctx = &ServiceContext{dataDir: ""} + db, err = ctx.Database("ephemeral", 0) + if err != nil { + t.Fatalf("failed to open ephemeral database: %v", err) + } + db.Close() + + if _, err := os.Stat(filepath.Join(dir, "ephemeral")); err == nil { + t.Fatalf("ephemeral database exists") + } +} diff --git a/node/utils.go b/node/utils.go new file mode 100644 index 0000000000..deaa6c5fbf --- /dev/null +++ b/node/utils.go @@ -0,0 +1,33 @@ +// Copyright 2015 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 . + +package node + +import ( + "path/filepath" + + "github.com/ethereum/go-ethereum/ethdb" +) + +// openDatabase opens an existing database with the given name from within the +// specified data directory, creating one if none exists. If the data directory +// is empty, an ephemeral memory database is returned. +func openDatabase(dataDir string, name string, cache int) (ethdb.Database, error) { + if dataDir == "" { + return ethdb.NewMemDatabase() + } + return ethdb.NewLDBDatabase(filepath.Join(dataDir, name), cache) +} diff --git a/p2p/discover/table.go b/p2p/discover/table.go index c128c2ed16..298ba3fa6a 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -90,12 +90,11 @@ type transport interface { // that was most recently active is the first element in entries. type bucket struct{ entries []*Node } -func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string) *Table { +func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string) (*Table, error) { // If no node database was given, use an in-memory one db, err := newNodeDB(nodeDBPath, Version, ourID) if err != nil { - glog.V(logger.Warn).Infoln("Failed to open node database:", err) - db, _ = newNodeDB("", Version, ourID) + return nil, err } tab := &Table{ net: t, @@ -114,7 +113,7 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string tab.buckets[i] = new(bucket) } go tab.refreshLoop() - return tab + return tab, nil } // Self returns the local node. diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 84962a1a5e..13effaed62 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -34,7 +34,7 @@ import ( func TestTable_pingReplace(t *testing.T) { doit := func(newNodeIsResponding, lastInBucketIsResponding bool) { transport := newPingRecorder() - tab := newTable(transport, NodeID{}, &net.UDPAddr{}, "") + tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "") defer tab.Close() pingSender := newNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99) @@ -177,7 +177,7 @@ func TestTable_closest(t *testing.T) { test := func(test *closeTest) bool { // for any node table, Target and N - tab := newTable(nil, test.Self, &net.UDPAddr{}, "") + tab, _ := newTable(nil, test.Self, &net.UDPAddr{}, "") defer tab.Close() tab.stuff(test.All) @@ -236,7 +236,7 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) { }, } test := func(buf []*Node) bool { - tab := newTable(nil, NodeID{}, &net.UDPAddr{}, "") + tab, _ := newTable(nil, NodeID{}, &net.UDPAddr{}, "") defer tab.Close() for i := 0; i < len(buf); i++ { ld := cfg.Rand.Intn(len(tab.buckets)) @@ -279,7 +279,7 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { func TestTable_Lookup(t *testing.T) { self := nodeAtDistance(common.Hash{}, 0) - tab := newTable(lookupTestnet, self.ID, &net.UDPAddr{}, "") + tab, _ := newTable(lookupTestnet, self.ID, &net.UDPAddr{}, "") defer tab.Close() // lookup on empty table returns no nodes diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index 20f69cf084..fc7fa737c3 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -200,12 +200,15 @@ func ListenUDP(priv *ecdsa.PrivateKey, laddr string, natm nat.Interface, nodeDBP if err != nil { return nil, err } - tab, _ := newUDP(priv, conn, natm, nodeDBPath) + tab, _, err := newUDP(priv, conn, natm, nodeDBPath) + if err != nil { + return nil, err + } glog.V(logger.Info).Infoln("Listening,", tab.self) return tab, nil } -func newUDP(priv *ecdsa.PrivateKey, c conn, natm nat.Interface, nodeDBPath string) (*Table, *udp) { +func newUDP(priv *ecdsa.PrivateKey, c conn, natm nat.Interface, nodeDBPath string) (*Table, *udp, error) { udp := &udp{ conn: c, priv: priv, @@ -225,10 +228,15 @@ func newUDP(priv *ecdsa.PrivateKey, c conn, natm nat.Interface, nodeDBPath strin } // TODO: separate TCP port udp.ourEndpoint = makeEndpoint(realaddr, uint16(realaddr.Port)) - udp.Table = newTable(udp, PubkeyID(&priv.PublicKey), realaddr, nodeDBPath) + tab, err := newTable(udp, PubkeyID(&priv.PublicKey), realaddr, nodeDBPath) + if err != nil { + return nil, nil, err + } + udp.Table = tab + go udp.loop() go udp.readLoop() - return udp.Table, udp + return udp.Table, udp, nil } func (t *udp) close() { diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 913199c268..944e73d6ed 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -69,7 +69,7 @@ func newUDPTest(t *testing.T) *udpTest { remotekey: newkey(), remoteaddr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 30303}, } - test.table, test.udp = newUDP(test.localkey, test.pipe, nil, "") + test.table, test.udp, _ = newUDP(test.localkey, test.pipe, nil, "") return test } From 8a44451edfa36ea40da564a2fa7ea905d45440a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 17 Nov 2015 13:39:18 +0200 Subject: [PATCH 08/78] cmd: drop blocktest command, create gethrpctest program --- cmd/geth/blocktestcmd.go | 135 ----------------------------- cmd/gethrpctest/main.go | 182 +++++++++++++++++++++++++++++++++++++++ node/utils.go | 33 ------- 3 files changed, 182 insertions(+), 168 deletions(-) delete mode 100644 cmd/geth/blocktestcmd.go create mode 100644 cmd/gethrpctest/main.go delete mode 100644 node/utils.go diff --git a/cmd/geth/blocktestcmd.go b/cmd/geth/blocktestcmd.go deleted file mode 100644 index e4d97aa531..0000000000 --- a/cmd/geth/blocktestcmd.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2015 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 . - -package main - -import ( - "fmt" - "os" - - "github.com/codegangsta/cli" - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/tests" -) - -var blocktestCommand = cli.Command{ - Action: runBlockTest, - Name: "blocktest", - Usage: `loads a block test file`, - Description: ` -The first argument should be a block test file. -The second argument is the name of a block test from the file. - -The block test will be loaded into an in-memory database. -If loading succeeds, the RPC server is started. Clients will -be able to interact with the chain defined by the test. -`, -} - -func runBlockTest(ctx *cli.Context) { - var ( - file, testname string - rpc bool - ) - args := ctx.Args() - switch { - case len(args) == 1: - file = args[0] - case len(args) == 2: - file, testname = args[0], args[1] - case len(args) == 3: - file, testname = args[0], args[1] - rpc = true - default: - utils.Fatalf(`Usage: ethereum blocktest [ [ "rpc" ] ]`) - } - bt, err := tests.LoadBlockTests(file) - if err != nil { - utils.Fatalf("%v", err) - } - - // run all tests if no test name is specified - if testname == "" { - ecode := 0 - for name, test := range bt { - fmt.Printf("----------------- Running Block Test %q\n", name) - ethereum, err := runOneBlockTest(ctx, test) - if err != nil { - fmt.Println(err) - fmt.Println("FAIL") - ecode = 1 - } - if ethereum != nil { - ethereum.Stop() - ethereum.WaitForShutdown() - } - } - os.Exit(ecode) - return - } - // otherwise, run the given test - test, ok := bt[testname] - if !ok { - utils.Fatalf("Test file does not contain test named %q", testname) - } - ethereum, err := runOneBlockTest(ctx, test) - if err != nil { - utils.Fatalf("%v", err) - } - if rpc { - fmt.Println("Block Test post state validated, starting RPC interface.") - startEth(ctx, ethereum) - utils.StartRPC(ethereum, ctx) - ethereum.WaitForShutdown() - } -} - -func runOneBlockTest(ctx *cli.Context, test *tests.BlockTest) (*eth.Ethereum, error) { - cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx) - db, _ := ethdb.NewMemDatabase() - cfg.NewDB = func(path string) (ethdb.Database, error) { return db, nil } - cfg.MaxPeers = 0 // disable network - cfg.Shh = false // disable whisper - cfg.NAT = nil // disable port mapping - ethereum, err := eth.New(cfg) - if err != nil { - return nil, err - } - - // import the genesis block - ethereum.ResetWithGenesisBlock(test.Genesis) - // import pre accounts - _, err = test.InsertPreState(db, cfg.AccountManager) - if err != nil { - return ethereum, fmt.Errorf("InsertPreState: %v", err) - } - - cm := ethereum.BlockChain() - validBlocks, err := test.TryBlocksInsert(cm) - if err != nil { - return ethereum, fmt.Errorf("Block Test load error: %v", err) - } - newDB, err := cm.State() - if err != nil { - return ethereum, fmt.Errorf("Block Test get state error: %v", err) - } - if err := test.ValidatePostState(newDB); err != nil { - return ethereum, fmt.Errorf("post state validation failed: %v", err) - } - return ethereum, test.ValidateImportedHeaders(cm, validBlocks) -} diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go new file mode 100644 index 0000000000..5419ccc46b --- /dev/null +++ b/cmd/gethrpctest/main.go @@ -0,0 +1,182 @@ +// Copyright 2015 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 . + +// gethrpctest is a command to run the external RPC tests. +package main + +import ( + "flag" + "io/ioutil" + "log" + "os" + "os/signal" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc/api" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/ethereum/go-ethereum/tests" + "github.com/ethereum/go-ethereum/whisper" + "github.com/ethereum/go-ethereum/xeth" +) + +const defaultTestKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" + +var ( + testFile = flag.String("json", "", "Path to the .json test file to load") + testName = flag.String("test", "", "Name of the test from the .json file to run") + testKey = flag.String("key", defaultTestKey, "Private key of a test account to inject") +) + +var ( + ethereumServiceId = "ethereum" + whisperServiceId = "whisper" +) + +func main() { + flag.Parse() + + // Load the test suite to run the RPC against + tests, err := tests.LoadBlockTests(*testFile) + if err != nil { + log.Fatalf("Failed to load test suite: %v", err) + } + test, found := tests[*testName] + if !found { + log.Fatalf("Requested test (%s) not found within suite", *testName) + } + // Create the protocol stack to run the test with + keydir, err := ioutil.TempDir("", "") + if err != nil { + log.Fatalf("Failed to create temporary keystore directory: %v", err) + } + defer os.RemoveAll(keydir) + + stack, err := MakeSystemNode(keydir, *testKey, test) + if err != nil { + log.Fatalf("Failed to assemble test stack: %v", err) + } + if err := stack.Start(); err != nil { + log.Fatalf("Failed to start test node: %v", err) + } + defer stack.Stop() + + log.Println("Test node started...") + + // Make sure the tests contained within the suite pass + if err := RunTest(stack, test); err != nil { + log.Fatalf("Failed to run the pre-configured test: %v", err) + } + log.Println("Initial test suite passed...") + + // Start the RPC interface and wait until terminated + if err := StartRPC(stack); err != nil { + log.Fatalf("Failed to start RPC instarface: %v", err) + } + log.Println("RPC Interface started, accepting requests...") + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit +} + +// MakeSystemNode configures a protocol stack for the RPC tests based on a given +// keystore path and initial pre-state. +func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node.Node, error) { + // Create a networkless protocol stack + stack, err := node.New(&node.Config{NoDiscovery: true}) + if err != nil { + return nil, err + } + // Create the keystore and inject an unlocked account if requested + keystore := crypto.NewKeyStorePassphrase(keydir, crypto.StandardScryptN, crypto.StandardScryptP) + accman := accounts.NewManager(keystore) + + if len(privkey) > 0 { + key, err := crypto.HexToECDSA(privkey) + if err != nil { + return nil, err + } + if err := keystore.StoreKey(crypto.NewKeyFromECDSA(key), ""); err != nil { + return nil, err + } + if err := accman.Unlock(crypto.NewKeyFromECDSA(key).Address, ""); err != nil { + return nil, err + } + } + // Initialize and register the Ethereum protocol + db, _ := ethdb.NewMemDatabase() + if _, err := test.InsertPreState(db, accman); err != nil { + return nil, err + } + ethConf := ð.Config{ + TestGenesisState: db, + TestGenesisBlock: test.Genesis, + AccountManager: accman, + } + if err := stack.Register(ethereumServiceId, func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { + return nil, err + } + // Initialize and register the Whisper protocol + if err := stack.Register(whisperServiceId, func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { + return nil, err + } + return stack, nil +} + +// RunTest executes the specified test against an already pre-configured protocol +// stack to ensure basic checks pass before running RPC tests. +func RunTest(stack *node.Node, test *tests.BlockTest) error { + blockchain := stack.Service(ethereumServiceId).(*eth.Ethereum).BlockChain() + + // Process the blocks and verify the imported headers + blocks, err := test.TryBlocksInsert(blockchain) + if err != nil { + return err + } + if err := test.ValidateImportedHeaders(blockchain, blocks); err != nil { + return err + } + // Retrieve the assembled state and validate it + stateDb, err := blockchain.State() + if err != nil { + return err + } + if err := test.ValidatePostState(stateDb); err != nil { + return err + } + return nil +} + +// StartRPC initializes an RPC interface to the given protocol stack. +func StartRPC(stack *node.Node) error { + config := comms.HttpConfig{ + ListenAddress: "127.0.0.1", + ListenPort: 8545, + } + xeth := xeth.New(stack, nil) + codec := codec.JSON + + apis, err := api.ParseApiString(comms.DefaultHttpRpcApis, codec, xeth, stack) + if err != nil { + return err + } + return comms.StartHttp(config, codec, api.Merge(apis...)) +} diff --git a/node/utils.go b/node/utils.go deleted file mode 100644 index deaa6c5fbf..0000000000 --- a/node/utils.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015 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 . - -package node - -import ( - "path/filepath" - - "github.com/ethereum/go-ethereum/ethdb" -) - -// openDatabase opens an existing database with the given name from within the -// specified data directory, creating one if none exists. If the data directory -// is empty, an ephemeral memory database is returned. -func openDatabase(dataDir string, name string, cache int) (ethdb.Database, error) { - if dataDir == "" { - return ethdb.NewMemDatabase() - } - return ethdb.NewLDBDatabase(filepath.Join(dataDir, name), cache) -} From 1e806c4c775bd98b224eb0249007502d348e737b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 17 Nov 2015 18:33:25 +0200 Subject: [PATCH 09/78] cmd, common, core, eth, node, rpc, tests, whisper, xeth: use protocol stacks --- cmd/geth/js.go | 33 ++- cmd/geth/js_test.go | 128 +++++---- cmd/geth/main.go | 309 ++++++++------------- cmd/gethrpctest/main.go | 13 +- cmd/utils/bootnodes.go | 41 +++ cmd/utils/cmd.go | 11 +- cmd/utils/flags.go | 419 ++++++++++++++++++++--------- common/natspec/natspec_e2e_test.go | 12 +- common/types.go | 28 +- core/block_validator_test.go | 2 +- core/blockchain.go | 6 +- core/blockchain_test.go | 8 +- core/chain_makers.go | 2 +- core/default_genesis.go | 13 +- core/genesis.go | 116 +++++--- eth/backend.go | 345 ++++-------------------- node/node.go | 124 ++++++--- node/node_example_test.go | 12 +- node/node_test.go | 54 +++- node/service.go | 35 ++- node/service_test.go | 54 +++- rpc/api/admin.go | 37 +-- rpc/api/api_test.go | 2 +- rpc/api/utils.go | 12 +- tests/block_test_util.go | 25 +- whisper/peer_test.go | 2 +- whisper/whisper.go | 16 +- whisper/whisper_test.go | 2 +- xeth/state.go | 4 +- xeth/xeth.go | 135 ++++++---- 30 files changed, 1051 insertions(+), 949 deletions(-) create mode 100644 cmd/utils/bootnodes.go diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 4d5462539b..a0e8bdb21c 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/common/registrar" "github.com/ethereum/go-ethereum/eth" re "github.com/ethereum/go-ethereum/jsre" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc/api" "github.com/ethereum/go-ethereum/rpc/codec" @@ -77,7 +78,7 @@ func (r dumbterm) AppendHistory(string) {} type jsre struct { re *re.JSRE - ethereum *eth.Ethereum + stack *node.Node xeth *xeth.XEth wait chan *big.Int ps1 string @@ -176,18 +177,18 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str return js } -func newJSRE(ethereum *eth.Ethereum, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre { - js := &jsre{ethereum: ethereum, ps1: "> "} +func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre { + js := &jsre{stack: stack, ps1: "> "} // set default cors domain used by startRpc from CLI flag js.corsDomain = corsDomain if f == nil { f = js } - js.xeth = xeth.New(ethereum, f) + js.xeth = xeth.New(stack, f) js.wait = js.xeth.UpdateState() js.client = client if clt, ok := js.client.(*comms.InProcClient); ok { - if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, ethereum); err == nil { + if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, stack); err == nil { clt.Initialize(api.Merge(offeredApis...)) } } @@ -202,14 +203,14 @@ func newJSRE(ethereum *eth.Ethereum, docRoot, corsDomain string, client comms.Et js.prompter = dumbterm{bufio.NewReader(os.Stdin)} } else { lr := liner.NewLiner() - js.withHistory(ethereum.DataDir, func(hist *os.File) { lr.ReadHistory(hist) }) + js.withHistory(stack.DataDir(), func(hist *os.File) { lr.ReadHistory(hist) }) lr.SetCtrlCAborts(true) js.loadAutoCompletion() lr.SetWordCompleter(apiWordCompleter) lr.SetTabCompletionStyle(liner.TabPrints) js.prompter = lr js.atexit = func() { - js.withHistory(ethereum.DataDir, func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) }) + js.withHistory(stack.DataDir(), func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) }) lr.Close() close(js.wait) } @@ -276,7 +277,7 @@ func (js *jsre) apiBindings(f xeth.Frontend) error { apiNames = append(apiNames, a) } - apiImpl, err := api.ParseApiString(strings.Join(apiNames, ","), codec.JSON, js.xeth, js.ethereum) + apiImpl, err := api.ParseApiString(strings.Join(apiNames, ","), codec.JSON, js.xeth, js.stack) if err != nil { utils.Fatalf("Unable to determine supported api's: %v", err) } @@ -342,8 +343,14 @@ func (self *jsre) AskPassword() (string, bool) { } func (self *jsre) ConfirmTransaction(tx string) bool { - if self.ethereum.NatSpec { - notice := natspec.GetNotice(self.xeth, tx, self.ethereum.HTTPClient()) + // Retrieve the Ethereum instance from the node + var ethereum *eth.Ethereum + if _, err := self.stack.SingletonService(ðereum); err != nil { + return false + } + // If natspec is enabled, ask for permission + if ethereum.NatSpec { + notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient()) fmt.Println(notice) answer, _ := self.Prompt("Confirm Transaction [y/n]") return strings.HasPrefix(strings.Trim(answer, " "), "y") @@ -359,7 +366,11 @@ func (self *jsre) UnlockAccount(addr []byte) bool { return false } // TODO: allow retry - if err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil { + var ethereum *eth.Ethereum + if _, err := self.stack.SingletonService(ðereum); err != nil { + return false + } + if err := ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil { return false } else { fmt.Println("Account is now unlocked for this session.") diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index 4770797062..ed4d04b483 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -38,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/comms" ) @@ -66,7 +67,10 @@ type testjethre struct { } func (self *testjethre) UnlockAccount(acc []byte) bool { - err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "") + var ethereum *eth.Ethereum + self.stack.SingletonService(ðereum) + + err := ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "") if err != nil { panic("unable to unlock") } @@ -74,67 +78,74 @@ func (self *testjethre) UnlockAccount(acc []byte) bool { } func (self *testjethre) ConfirmTransaction(tx string) bool { - if self.ethereum.NatSpec { + var ethereum *eth.Ethereum + self.stack.SingletonService(ðereum) + + if ethereum.NatSpec { self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client) } return true } -func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) { +func testJEthRE(t *testing.T) (string, *testjethre, *node.Node) { return testREPL(t, nil) } -func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *eth.Ethereum) { +func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *node.Node) { tmp, err := ioutil.TempDir("", "geth-test") if err != nil { t.Fatal(err) } + // Create a networkless protocol stack + stack, err := node.New(&node.Config{PrivateKey: testNodeKey, Name: "test", NoDiscovery: true}) + if err != nil { + t.Fatalf("failed to create node: %v", err) + } + // Initialize and register the Ethereum protocol + keystore := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore")) + accman := accounts.NewManager(keystore) db, _ := ethdb.NewMemDatabase() - core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)}) - ks := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore")) - am := accounts.NewManager(ks) - conf := ð.Config{ - NodeKey: testNodeKey, - DataDir: tmp, - AccountManager: am, - MaxPeers: 0, - Name: "test", - DocRoot: "/", - SolcPath: testSolcPath, - PowTest: true, - NewDB: func(path string) (ethdb.Database, error) { return db, nil }, + + ethConf := ð.Config{ + TestGenesisState: db, + AccountManager: accman, + DocRoot: "/", + SolcPath: testSolcPath, + PowTest: true, } if config != nil { - config(conf) + config(ethConf) } - ethereum, err := eth.New(conf) - if err != nil { - t.Fatal("%v", err) + if err := stack.Register("ethereum", func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { + t.Fatalf("failed to register ethereum protocol: %v", err) } - + // Initialize all the keys for testing keyb, err := crypto.HexToECDSA(testKey) if err != nil { t.Fatal(err) } key := crypto.NewKeyFromECDSA(keyb) - err = ks.StoreKey(key, "") - if err != nil { + if err := keystore.StoreKey(key, ""); err != nil { t.Fatal(err) } - - err = am.Unlock(key.Address, "") - if err != nil { + if err := accman.Unlock(key.Address, ""); err != nil { t.Fatal(err) } + // Start the node and assemble the REPL tester + if err := stack.Start(); err != nil { + t.Fatalf("failed to start test stack: %v", err) + } + var ethereum *eth.Ethereum + stack.SingletonService(ðereum) assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") client := comms.NewInProcClient(codec.JSON) tf := &testjethre{client: ethereum.HTTPClient()} - repl := newJSRE(ethereum, assetPath, "", client, false, tf) + repl := newJSRE(stack, assetPath, "", client, false, tf) tf.jsre = repl - return tmp, tf, ethereum + return tmp, tf, stack } func TestNodeInfo(t *testing.T) { @@ -151,11 +162,8 @@ func TestNodeInfo(t *testing.T) { } func TestAccounts(t *testing.T) { - tmp, repl, ethereum := testJEthRE(t) - if err := ethereum.Start(); err != nil { - t.Fatalf("error starting ethereum: %v", err) - } - defer ethereum.Stop() + tmp, repl, node := testJEthRE(t) + defer node.Stop() defer os.RemoveAll(tmp) checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`) @@ -174,11 +182,8 @@ func TestAccounts(t *testing.T) { } func TestBlockChain(t *testing.T) { - tmp, repl, ethereum := testJEthRE(t) - if err := ethereum.Start(); err != nil { - t.Fatalf("error starting ethereum: %v", err) - } - defer ethereum.Stop() + tmp, repl, node := testJEthRE(t) + defer node.Stop() defer os.RemoveAll(tmp) // get current block dump before export/import. val, err := repl.re.Run("JSON.stringify(debug.dumpBlock(eth.blockNumber))") @@ -196,6 +201,8 @@ func TestBlockChain(t *testing.T) { tmpfile := filepath.Join(extmp, "export.chain") tmpfileq := strconv.Quote(tmpfile) + var ethereum *eth.Ethereum + node.SingletonService(ðereum) ethereum.BlockChain().Reset() checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`) @@ -209,22 +216,15 @@ func TestBlockChain(t *testing.T) { } func TestMining(t *testing.T) { - tmp, repl, ethereum := testJEthRE(t) - if err := ethereum.Start(); err != nil { - t.Fatalf("error starting ethereum: %v", err) - } - defer ethereum.Stop() + tmp, repl, node := testJEthRE(t) + defer node.Stop() defer os.RemoveAll(tmp) checkEvalJSON(t, repl, `eth.mining`, `false`) } func TestRPC(t *testing.T) { - tmp, repl, ethereum := testJEthRE(t) - if err := ethereum.Start(); err != nil { - t.Errorf("error starting ethereum: %v", err) - return - } - defer ethereum.Stop() + tmp, repl, node := testJEthRE(t) + defer node.Stop() defer os.RemoveAll(tmp) checkEvalJSON(t, repl, `admin.startRPC("127.0.0.1", 5004, "*", "web3,eth,net")`, `true`) @@ -234,12 +234,8 @@ func TestCheckTestAccountBalance(t *testing.T) { t.Skip() // i don't think it tests the correct behaviour here. it's actually testing // internals which shouldn't be tested. This now fails because of a change in the core // and i have no means to fix this, sorry - @obscuren - tmp, repl, ethereum := testJEthRE(t) - if err := ethereum.Start(); err != nil { - t.Errorf("error starting ethereum: %v", err) - return - } - defer ethereum.Stop() + tmp, repl, node := testJEthRE(t) + defer node.Stop() defer os.RemoveAll(tmp) repl.re.Run(`primary = "` + testAddress + `"`) @@ -247,12 +243,8 @@ func TestCheckTestAccountBalance(t *testing.T) { } func TestSignature(t *testing.T) { - tmp, repl, ethereum := testJEthRE(t) - if err := ethereum.Start(); err != nil { - t.Errorf("error starting ethereum: %v", err) - return - } - defer ethereum.Stop() + tmp, repl, node := testJEthRE(t) + defer node.Stop() defer os.RemoveAll(tmp) val, err := repl.re.Run(`eth.sign("` + testAddress + `", "` + testHash + `")`) @@ -443,7 +435,10 @@ multiply7 = Multiply7.at(contractaddress); } func pendingTransactions(repl *testjethre, t *testing.T) (txc int64, err error) { - txs := repl.ethereum.TxPool().GetTransactions() + var ethereum *eth.Ethereum + repl.stack.SingletonService(ðereum) + + txs := ethereum.TxPool().GetTransactions() return int64(len(txs)), nil } @@ -468,12 +463,15 @@ func processTxs(repl *testjethre, t *testing.T, expTxc int) bool { t.Errorf("incorrect number of pending transactions, expected %v, got %v", expTxc, txc) return false } - err = repl.ethereum.StartMining(runtime.NumCPU(), "") + var ethereum *eth.Ethereum + repl.stack.SingletonService(ðereum) + + err = ethereum.StartMining(runtime.NumCPU(), "") if err != nil { t.Errorf("unexpected error mining: %v", err) return false } - defer repl.ethereum.StopMining() + defer ethereum.StopMining() timer := time.NewTimer(100 * time.Second) height := new(big.Int).Add(repl.xeth.CurrentBlock().Number(), big.NewInt(1)) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 82bc21ab05..6fac4f4582 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -33,13 +33,11 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc/codec" @@ -68,22 +66,9 @@ func init() { } app = utils.NewApp(Version, "the go-ethereum command line interface") - app.Action = run + app.Action = geth app.HideVersion = true // we have a command to print the version app.Commands = []cli.Command{ - { - Action: blockRecovery, - Name: "recover", - Usage: "Attempts to recover a corrupted database by setting a new block by number or hash", - Description: ` -The recover commands will attempt to read out the last -block based on that. - -recover #number recovers by number -recover recovers by hash -`, - }, - blocktestCommand, importCommand, exportCommand, upgradedbCommand, @@ -285,7 +270,7 @@ This command allows to open a console on a running geth node. `, }, { - Action: execJSFiles, + Action: execScripts, Name: "js", Usage: `executes the given JavaScript files in the Geth JavaScript VM`, Description: ` @@ -376,14 +361,6 @@ func main() { } } -// makeExtra resolves extradata for the miner from a flag or returns a default. -func makeExtra(ctx *cli.Context) []byte { - if ctx.GlobalIsSet(utils.ExtraDataFlag.Name) { - return []byte(ctx.GlobalString(utils.ExtraDataFlag.Name)) - } - return makeDefaultExtra() -} - func makeDefaultExtra() []byte { var clientInfo = struct { Version uint @@ -404,18 +381,13 @@ func makeDefaultExtra() []byte { return extra } -func run(ctx *cli.Context) { - cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx) - cfg.ExtraData = makeExtra(ctx) - - ethereum, err := eth.New(cfg) - if err != nil { - utils.Fatalf("%v", err) - } - - startEth(ctx, ethereum) - // this blocks the thread - ethereum.WaitForShutdown() +// geth is the main entry point into the system if no special subcommand is ran. +// It creates a default node based on the command line arguments and runs it in +// blocking mode, waiting for it to be shut down. +func geth(ctx *cli.Context) { + node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx) + startNode(ctx, node) + node.Wait() } func attach(ctx *cli.Context) { @@ -449,156 +421,107 @@ func attach(ctx *cli.Context) { } } +// console starts a new geth node, attaching a JavaScript console to it at the +// same time. func console(ctx *cli.Context) { - cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx) - cfg.ExtraData = makeExtra(ctx) - - ethereum, err := eth.New(cfg) - if err != nil { - utils.Fatalf("%v", err) - } + // Create and start the node based on the CLI flags + node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx) + startNode(ctx, node) + // Attach to the newly started node, and either execute script or become interactive client := comms.NewInProcClient(codec.JSON) - - startEth(ctx, ethereum) - repl := newJSRE( - ethereum, + repl := newJSRE(node, ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name), - client, - true, - nil, - ) + client, true, nil) - if ctx.GlobalString(utils.ExecFlag.Name) != "" { - repl.batch(ctx.GlobalString(utils.ExecFlag.Name)) + if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" { + repl.batch(script) } else { repl.welcome() repl.interactive() } - - ethereum.Stop() - ethereum.WaitForShutdown() + node.Stop() } -func execJSFiles(ctx *cli.Context) { - cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx) - ethereum, err := eth.New(cfg) - if err != nil { - utils.Fatalf("%v", err) - } +// execScripts starts a new geth node based on the CLI flags, and executes each +// of the JavaScript files specified as command arguments. +func execScripts(ctx *cli.Context) { + // Create and start the node based on the CLI flags + node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx) + startNode(ctx, node) + // Attach to the newly started node and execute the given scripts client := comms.NewInProcClient(codec.JSON) - startEth(ctx, ethereum) - repl := newJSRE( - ethereum, + repl := newJSRE(node, ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name), - client, - false, - nil, - ) + client, false, nil) + for _, file := range ctx.Args() { repl.exec(file) } - - ethereum.Stop() - ethereum.WaitForShutdown() + node.Stop() } -func unlockAccount(ctx *cli.Context, am *accounts.Manager, addr string, i int, inputpassphrases []string) (addrHex, auth string, passphrases []string) { - var err error - passphrases = inputpassphrases - addrHex, err = utils.ParamToAddress(addr, am) - if err == nil { - // Attempt to unlock the account 3 times - attempts := 3 - for tries := 0; tries < attempts; tries++ { - msg := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", addr, tries+1, attempts) - auth, passphrases = getPassPhrase(ctx, msg, false, i, passphrases) - err = am.Unlock(common.HexToAddress(addrHex), auth) - if err == nil || passphrases != nil { - break - } +func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (common.Address, string) { + // Try to unlock the specified account a few times + account := utils.MakeAddress(accman, address) + + for trials := 0; trials < 3; trials++ { + prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) + password := getPassPhrase(prompt, false, i, passwords) + if err := accman.Unlock(account, password); err == nil { + return account, password } } - - if err != nil { - utils.Fatalf("Unlock account '%s' (%v) failed: %v", addr, addrHex, err) - } - fmt.Printf("Account '%s' (%v) unlocked.\n", addr, addrHex) - return + // All trials expended to unlock account, bail out + utils.Fatalf("Failed to unlock account: %s", address) + return common.Address{}, "" } -func blockRecovery(ctx *cli.Context) { - if len(ctx.Args()) < 1 { - glog.Fatal("recover requires block number or hash") +// startNode boots up the system node and all registered protocols, after which +// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the +// miner. +func startNode(ctx *cli.Context, stack *node.Node) { + // Start up the node itself + utils.StartNode(stack) + + // Unlock any account specifically requested + var ethereum *eth.Ethereum + if _, err := stack.SingletonService(ðereum); err != nil { + utils.Fatalf("ethereum service not running: %v", err) } - arg := ctx.Args().First() + accman := ethereum.AccountManager() + passwords := utils.MakePasswordList(ctx) - cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx) - blockDb, err := ethdb.NewLDBDatabase(filepath.Join(cfg.DataDir, "blockchain"), cfg.DatabaseCache) - if err != nil { - glog.Fatalln("could not open db:", err) - } - - var block *types.Block - if arg[0] == '#' { - block = core.GetBlock(blockDb, core.GetCanonicalHash(blockDb, common.String2Big(arg[1:]).Uint64())) - } else { - block = core.GetBlock(blockDb, common.HexToHash(arg)) - } - - if block == nil { - glog.Fatalln("block not found. Recovery failed") - } - - if err = core.WriteHeadBlockHash(blockDb, block.Hash()); err != nil { - glog.Fatalln("block write err", err) - } - glog.Infof("Recovery succesful. New HEAD %x\n", block.Hash()) -} - -func startEth(ctx *cli.Context, eth *eth.Ethereum) { - // Start Ethereum itself - utils.StartEthereum(eth) - - am := eth.AccountManager() - account := ctx.GlobalString(utils.UnlockedAccountFlag.Name) - accounts := strings.Split(account, " ") - var passphrases []string + accounts := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",") for i, account := range accounts { - if len(account) > 0 { - if account == "primary" { - utils.Fatalf("the 'primary' keyword is deprecated. You can use integer indexes, but the indexes are not permanent, they can change if you add external keys, export your keys or copy your keystore to another node.") - } - _, _, passphrases = unlockAccount(ctx, am, account, i, passphrases) + if trimmed := strings.TrimSpace(account); trimmed != "" { + unlockAccount(ctx, accman, trimmed, i, passwords) } } // Start auxiliary services if enabled. if !ctx.GlobalBool(utils.IPCDisabledFlag.Name) { - if err := utils.StartIPC(eth, ctx); err != nil { - utils.Fatalf("Error string IPC: %v", err) + if err := utils.StartIPC(stack, ctx); err != nil { + utils.Fatalf("Failed to start IPC: %v", err) } } if ctx.GlobalBool(utils.RPCEnabledFlag.Name) { - if err := utils.StartRPC(eth, ctx); err != nil { - utils.Fatalf("Error starting RPC: %v", err) + if err := utils.StartRPC(stack, ctx); err != nil { + utils.Fatalf("Failed to start RPC: %v", err) } } if ctx.GlobalBool(utils.MiningEnabledFlag.Name) { - err := eth.StartMining( - ctx.GlobalInt(utils.MinerThreadsFlag.Name), - ctx.GlobalString(utils.MiningGPUFlag.Name)) - if err != nil { - utils.Fatalf("%v", err) + if err := ethereum.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name), ctx.GlobalString(utils.MiningGPUFlag.Name)); err != nil { + utils.Fatalf("Failed to start mining: %v", err) } } } func accountList(ctx *cli.Context) { - am := utils.MakeAccountManager(ctx) - accts, err := am.Accounts() + accman := utils.MakeAccountManager(ctx) + accts, err := accman.Accounts() if err != nil { utils.Fatalf("Could not list accounts: %v", err) } @@ -607,67 +530,57 @@ func accountList(ctx *cli.Context) { } } -func getPassPhrase(ctx *cli.Context, desc string, confirmation bool, i int, inputpassphrases []string) (passphrase string, passphrases []string) { - passfile := ctx.GlobalString(utils.PasswordFileFlag.Name) - if len(passfile) == 0 { - fmt.Println(desc) - auth, err := utils.PromptPassword("Passphrase: ", true) +// getPassPhrase retrieves the passwor associated with an account, either fetched +// from a list of preloaded passphrases, or requested interactively from the user. +func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { + // If a list of passwords was supplied, retrieve from them + if len(passwords) > 0 { + if i < len(passwords) { + return passwords[i] + } + return passwords[len(passwords)-1] + } + // Otherwise prompt the user for the password + fmt.Println(prompt) + password, err := utils.PromptPassword("Passphrase: ", true) + if err != nil { + utils.Fatalf("Failed to read passphrase: %v", err) + } + if confirmation { + confirm, err := utils.PromptPassword("Repeat passphrase: ", false) if err != nil { - utils.Fatalf("%v", err) + utils.Fatalf("Failed to read passphrase confirmation: %v", err) } - if confirmation { - confirm, err := utils.PromptPassword("Repeat Passphrase: ", false) - if err != nil { - utils.Fatalf("%v", err) - } - if auth != confirm { - utils.Fatalf("Passphrases did not match.") - } - } - passphrase = auth - - } else { - passphrases = inputpassphrases - if passphrases == nil { - passbytes, err := ioutil.ReadFile(passfile) - if err != nil { - utils.Fatalf("Unable to read password file '%s': %v", passfile, err) - } - // this is backwards compatible if the same password unlocks several accounts - // it also has the consequence that trailing newlines will not count as part - // of the password, so --password <(echo -n 'pass') will now work without -n - passphrases = strings.Split(string(passbytes), "\n") - } - if i >= len(passphrases) { - passphrase = passphrases[len(passphrases)-1] - } else { - passphrase = passphrases[i] + if password != confirm { + utils.Fatalf("Passphrases do not match") } } - return + return password } +// accountCreate creates a new account into the keystore defined by the CLI flags. func accountCreate(ctx *cli.Context) { - am := utils.MakeAccountManager(ctx) - passphrase, _ := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, nil) - acct, err := am.NewAccount(passphrase) + accman := utils.MakeAccountManager(ctx) + password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + + account, err := accman.NewAccount(password) if err != nil { - utils.Fatalf("Could not create the account: %v", err) + utils.Fatalf("Failed to create account: %v", err) } - fmt.Printf("Address: %x\n", acct) + fmt.Printf("Address: %x\n", account) } +// accountUpdate transitions an account from a previous format to the current +// one, also providing the possibility to change the pass-phrase. func accountUpdate(ctx *cli.Context) { - am := utils.MakeAccountManager(ctx) - arg := ctx.Args().First() - if len(arg) == 0 { - utils.Fatalf("account address or index must be given as argument") + if len(ctx.Args()) == 0 { + utils.Fatalf("No accounts specified to update") } + accman := utils.MakeAccountManager(ctx) - addr, authFrom, passphrases := unlockAccount(ctx, am, arg, 0, nil) - authTo, _ := getPassPhrase(ctx, "Please give a new password. Do not forget this password.", true, 0, passphrases) - err := am.Update(common.HexToAddress(addr), authFrom, authTo) - if err != nil { + account, oldPassword := unlockAccount(ctx, accman, ctx.Args().First(), 0, nil) + newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) + if err := accman.Update(account, oldPassword, newPassword); err != nil { utils.Fatalf("Could not update the account: %v", err) } } @@ -682,10 +595,10 @@ func importWallet(ctx *cli.Context) { utils.Fatalf("Could not read wallet file: %v", err) } - am := utils.MakeAccountManager(ctx) - passphrase, _ := getPassPhrase(ctx, "", false, 0, nil) + accman := utils.MakeAccountManager(ctx) + passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) - acct, err := am.ImportPreSaleKey(keyJson, passphrase) + acct, err := accman.ImportPreSaleKey(keyJson, passphrase) if err != nil { utils.Fatalf("Could not create the account: %v", err) } @@ -697,9 +610,9 @@ func accountImport(ctx *cli.Context) { if len(keyfile) == 0 { utils.Fatalf("keyfile must be given as argument") } - am := utils.MakeAccountManager(ctx) - passphrase, _ := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, nil) - acct, err := am.Import(keyfile, passphrase) + accman := utils.MakeAccountManager(ctx) + passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + acct, err := accman.Import(keyfile, passphrase) if err != nil { utils.Fatalf("Could not create the account: %v", err) } diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index 5419ccc46b..cb4c7aece2 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -45,11 +45,6 @@ var ( testKey = flag.String("key", defaultTestKey, "Private key of a test account to inject") ) -var ( - ethereumServiceId = "ethereum" - whisperServiceId = "whisper" -) - func main() { flag.Parse() @@ -131,11 +126,11 @@ func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node TestGenesisBlock: test.Genesis, AccountManager: accman, } - if err := stack.Register(ethereumServiceId, func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { + if err := stack.Register("ethereum", func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { return nil, err } // Initialize and register the Whisper protocol - if err := stack.Register(whisperServiceId, func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { + if err := stack.Register("whisper", func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { return nil, err } return stack, nil @@ -144,7 +139,9 @@ func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node // RunTest executes the specified test against an already pre-configured protocol // stack to ensure basic checks pass before running RPC tests. func RunTest(stack *node.Node, test *tests.BlockTest) error { - blockchain := stack.Service(ethereumServiceId).(*eth.Ethereum).BlockChain() + var ethereum *eth.Ethereum + stack.SingletonService(ðereum) + blockchain := ethereum.BlockChain() // Process the blocks and verify the imported headers blocks, err := test.TryBlocksInsert(blockchain) diff --git a/cmd/utils/bootnodes.go b/cmd/utils/bootnodes.go new file mode 100644 index 0000000000..fbbaa1f227 --- /dev/null +++ b/cmd/utils/bootnodes.go @@ -0,0 +1,41 @@ +// Copyright 2015 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 . + +package utils + +import "github.com/ethereum/go-ethereum/p2p/discover" + +// FrontierBootNodes are the enode URLs of the P2P bootstrap nodes running on +// the Frontier network. +var FrontierBootNodes = []*discover.Node{ + // ETH/DEV Go Bootnodes + discover.MustParseNode("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303"), // IE + discover.MustParseNode("enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303"), // BR + discover.MustParseNode("enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303"), // SG + + // ETH/DEV Cpp Bootnodes + discover.MustParseNode("enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303"), +} + +// TestNetBootNodes are the enode URLs of the P2P bootstrap nodes running on the +// Morden test network. +var TestNetBootNodes = []*discover.Node{ + // ETH/DEV Go Bootnodes + discover.MustParseNode("enode://e4533109cc9bd7604e4ff6c095f7a1d807e15b38e9bfeb05d3b7c423ba86af0a9e89abbf40bd9dde4250fef114cd09270fa4e224cbeef8b7bf05a51e8260d6b8@94.242.229.4:40404"), + discover.MustParseNode("enode://8c336ee6f03e99613ad21274f269479bf4413fb294d697ef15ab897598afb931f56beb8e97af530aee20ce2bcba5776f4a312bc168545de4d43736992c814592@94.242.229.203:30303"), + + // ETH/DEV Cpp Bootnodes +} diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 5cbb581244..a0d60a583d 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -29,9 +29,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rlp" "github.com/peterh/liner" ) @@ -110,10 +110,9 @@ func Fatalf(format string, args ...interface{}) { os.Exit(1) } -func StartEthereum(ethereum *eth.Ethereum) { - glog.V(logger.Info).Infoln("Starting", ethereum.Name()) - if err := ethereum.Start(); err != nil { - Fatalf("Error starting Ethereum: %v", err) +func StartNode(stack *node.Node) { + if err := stack.Start(); err != nil { + Fatalf("Error starting protocol stack: %v", err) } go func() { sigc := make(chan os.Signal, 1) @@ -121,7 +120,7 @@ func StartEthereum(ethereum *eth.Ethereum) { defer signal.Stop(sigc) <-sigc glog.V(logger.Info).Infoln("Got interrupt, shutting down...") - go ethereum.Stop() + go stack.Stop() logger.Flush() for i := 10; i > 0; i-- { <-sigc diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 3792dc1e0d..30570d930b 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -19,6 +19,7 @@ package utils import ( "crypto/ecdsa" "fmt" + "io/ioutil" "log" "math" "math/big" @@ -28,12 +29,14 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "github.com/codegangsta/cli" "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -42,6 +45,8 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc/api" @@ -49,6 +54,7 @@ import ( "github.com/ethereum/go-ethereum/rpc/comms" "github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/rpc/useragent" + "github.com/ethereum/go-ethereum/whisper" "github.com/ethereum/go-ethereum/xeth" ) @@ -192,12 +198,12 @@ var ( // Account settings UnlockedAccountFlag = cli.StringFlag{ Name: "unlock", - Usage: "Unlock an account (may be creation index) until this program exits (prompts for password)", + Usage: "Comma separated list of accounts to unlock", Value: "", } PasswordFileFlag = cli.StringFlag{ Name: "password", - Usage: "Password file to use with options/subcommands needing a pass phrase", + Usage: "Password file to use for non-inteactive password input", Value: "", } @@ -316,7 +322,7 @@ var ( } BootnodesFlag = cli.StringFlag{ Name: "bootnodes", - Usage: "Space-separated enode URLs for P2P discovery bootstrap", + Usage: "Comma separated enode URLs for P2P discovery bootstrap", Value: "", } NodeKeyFileFlag = cli.StringFlag{ @@ -385,6 +391,90 @@ var ( } ) +// MustMakeDataDir retrieves the currently requested data directory, terminating +// if none (or the empty string) is specified. If the node is starting a testnet, +// the a subdirectory of the specified datadir will be used. +func MustMakeDataDir(ctx *cli.Context) string { + if path := ctx.GlobalString(DataDirFlag.Name); path != "" { + if ctx.GlobalBool(TestNetFlag.Name) { + return filepath.Join(path, "/testnet") + } + return path + } + Fatalf("Cannot determine default data directory, please set manually (--datadir)") + return "" +} + +// MakeNodeKey creates a node key from set command line flags, either loading it +// from a file or as a specified hex value. If neither flags were provided, this +// method returns nil and an emphemeral key is to be generated. +func MakeNodeKey(ctx *cli.Context) *ecdsa.PrivateKey { + var ( + hex = ctx.GlobalString(NodeKeyHexFlag.Name) + file = ctx.GlobalString(NodeKeyFileFlag.Name) + + key *ecdsa.PrivateKey + err error + ) + switch { + case file != "" && hex != "": + Fatalf("Options %q and %q are mutually exclusive", NodeKeyFileFlag.Name, NodeKeyHexFlag.Name) + + case file != "": + if key, err = crypto.LoadECDSA(file); err != nil { + Fatalf("Option %q: %v", NodeKeyFileFlag.Name, err) + } + + case hex != "": + if key, err = crypto.HexToECDSA(hex); err != nil { + Fatalf("Option %q: %v", NodeKeyHexFlag.Name, err) + } + } + return key +} + +// MakeNodeName creates a node name from a base set and the command line flags. +func MakeNodeName(client, version string, ctx *cli.Context) string { + name := common.MakeName(client, version) + if identity := ctx.GlobalString(IdentityFlag.Name); len(identity) > 0 { + name += "/" + identity + } + if ctx.GlobalBool(VMEnableJitFlag.Name) { + name += "/JIT" + } + return name +} + +// MakeBootstrapNodes creates a list of bootstrap nodes from the command line +// flags, reverting to pre-configured ones if none have been specified. +func MakeBootstrapNodes(ctx *cli.Context) []*discover.Node { + // Return pre-configured nodes if none were manually requested + if !ctx.GlobalIsSet(BootnodesFlag.Name) { + if ctx.GlobalBool(TestNetFlag.Name) { + return TestNetBootNodes + } + return FrontierBootNodes + } + // Otherwise parse and use the CLI bootstrap nodes + bootnodes := []*discover.Node{} + + for _, url := range strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",") { + node, err := discover.ParseNode(url) + if err != nil { + glog.V(logger.Error).Infof("Bootstrap URL %s: %v\n", url, err) + continue + } + bootnodes = append(bootnodes, node) + } + return bootnodes +} + +// MakeListenAddress creates a TCP listening address string from set command +// line flags. +func MakeListenAddress(ctx *cli.Context) string { + return fmt.Sprintf(":%d", ctx.GlobalInt(ListenPortFlag.Name)) +} + // MakeNAT creates a port mapper from set command line flags. func MakeNAT(ctx *cli.Context) nat.Interface { natif, err := nat.Parse(ctx.GlobalString(NATFlag.Name)) @@ -394,64 +484,145 @@ func MakeNAT(ctx *cli.Context) nat.Interface { return natif } -// MakeNodeKey creates a node key from set command line flags. -func MakeNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) { - hex, file := ctx.GlobalString(NodeKeyHexFlag.Name), ctx.GlobalString(NodeKeyFileFlag.Name) - var err error - switch { - case file != "" && hex != "": - Fatalf("Options %q and %q are mutually exclusive", NodeKeyFileFlag.Name, NodeKeyHexFlag.Name) - case file != "": - if key, err = crypto.LoadECDSA(file); err != nil { - Fatalf("Option %q: %v", NodeKeyFileFlag.Name, err) - } - case hex != "": - if key, err = crypto.HexToECDSA(hex); err != nil { - Fatalf("Option %q: %v", NodeKeyHexFlag.Name, err) - } +// MakeGenesisBlock loads up a genesis block from an input file specified in the +// command line, or returns the empty string if none set. +func MakeGenesisBlock(ctx *cli.Context) string { + genesis := ctx.GlobalString(GenesisFileFlag.Name) + if genesis == "" { + return "" } - return key + data, err := ioutil.ReadFile(genesis) + if err != nil { + Fatalf("Failed to load custom genesis file: %v", err) + } + return string(data) } -// MakeEthConfig creates ethereum options from set command line flags. -func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { - customName := ctx.GlobalString(IdentityFlag.Name) - if len(customName) > 0 { - clientID += "/" + customName +// MakeAccountManager creates an account manager from set command line flags. +func MakeAccountManager(ctx *cli.Context) *accounts.Manager { + // Create the keystore crypto primitive, light if requested + scryptN := crypto.StandardScryptN + scryptP := crypto.StandardScryptP + + if ctx.GlobalBool(LightKDFFlag.Name) { + scryptN = crypto.LightScryptN + scryptP = crypto.LightScryptP } - am := MakeAccountManager(ctx) - etherbase, err := ParamToAddress(ctx.GlobalString(EtherbaseFlag.Name), am) + // Assemble an account manager using the configured datadir + var ( + datadir = MustMakeDataDir(ctx) + keystore = crypto.NewKeyStorePassphrase(filepath.Join(datadir, "keystore"), scryptN, scryptP) + ) + return accounts.NewManager(keystore) +} + +// MakeAddress converts an account specified directly as a hex encoded string or +// a key index in the key store to an internal account representation. +func MakeAddress(accman *accounts.Manager, account string) common.Address { + // If the specified account is a valid address, return it + if common.IsHexAddress(account) { + return common.HexToAddress(account) + } + // Otherwise try to interpret the account as a keystore index + index, err := strconv.Atoi(account) if err != nil { - glog.V(logger.Error).Infoln("WARNING: No etherbase set and no accounts found as default") + Fatalf("Invalid account address or index: '%s'", account) } - // Assemble the entire eth configuration and return - cfg := ð.Config{ - Name: common.MakeName(clientID, version), - DataDir: MustDataDir(ctx), - GenesisFile: ctx.GlobalString(GenesisFileFlag.Name), + hex, err := accman.AddressByIndex(index) + if err != nil { + Fatalf("Failed to retrieve requested account #%d: %v", index, err) + } + return common.HexToAddress(hex) +} + +// MakeEtherbase retrieves the etherbase either from the directly specified +// command line flags or from the keystore if CLI indexed. +func MakeEtherbase(accman *accounts.Manager, ctx *cli.Context) common.Address { + // If the specified etherbase is a valid address, return it + etherbase := ctx.GlobalString(EtherbaseFlag.Name) + if common.IsHexAddress(etherbase) { + return common.HexToAddress(etherbase) + } + // If no etherbase was specified and no accounts are known, bail out + accounts, _ := accman.Accounts() + if etherbase == "" && len(accounts) == 0 { + glog.V(logger.Error).Infoln("WARNING: No etherbase set and no accounts found as default") + return common.Address{} + } + // Otherwise try to interpret the parameter as a keystore index + index, err := strconv.Atoi(etherbase) + if err != nil { + Fatalf("Invalid account address or index: '%s'", etherbase) + } + hex, err := accman.AddressByIndex(index) + if err != nil { + Fatalf("Failed to set requested account #%d as etherbase: %v", index, err) + } + return common.HexToAddress(hex) +} + +// MakeMinerExtra resolves extradata for the miner from the set command line flags +// or returns a default one composed on the client, runtime and OS metadata. +func MakeMinerExtra(extra []byte, ctx *cli.Context) []byte { + if ctx.GlobalIsSet(ExtraDataFlag.Name) { + return []byte(ctx.GlobalString(ExtraDataFlag.Name)) + } + return extra +} + +// MakePasswordList loads up a list of password from a file specified by the +// command line flags. +func MakePasswordList(ctx *cli.Context) []string { + if path := ctx.GlobalString(PasswordFileFlag.Name); path != "" { + blob, err := ioutil.ReadFile(path) + if err != nil { + Fatalf("Failed to read password file: %v", err) + } + return strings.Split(string(blob), "\n") + } + return nil +} + +// MakeSystemNode sets up a local node, configures the services to launch and +// assembles the P2P protocol stack. +func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.Node { + // Avoid conflicting network flags + networks, netFlags := 0, []cli.BoolFlag{DevModeFlag, TestNetFlag, OlympicFlag} + for _, flag := range netFlags { + if ctx.GlobalBool(flag.Name) { + networks++ + } + } + if networks > 1 { + Fatalf("The %v flags are mutually exclusive", netFlags) + } + // Configure the node's service container + stackConf := &node.Config{ + DataDir: MustMakeDataDir(ctx), + PrivateKey: MakeNodeKey(ctx), + Name: MakeNodeName(name, version, ctx), + NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name), + BootstrapNodes: MakeBootstrapNodes(ctx), + ListenAddr: MakeListenAddress(ctx), + NAT: MakeNAT(ctx), + MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), + MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name), + } + // Configure the Ethereum service + accman := MakeAccountManager(ctx) + + ethConf := ð.Config{ + Genesis: MakeGenesisBlock(ctx), FastSync: ctx.GlobalBool(FastSyncFlag.Name), BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name), DatabaseCache: ctx.GlobalInt(CacheFlag.Name), - SkipBcVersionCheck: false, NetworkId: ctx.GlobalInt(NetworkIdFlag.Name), - LogFile: ctx.GlobalString(LogFileFlag.Name), - Verbosity: ctx.GlobalInt(VerbosityFlag.Name), - Etherbase: common.HexToAddress(etherbase), + AccountManager: accman, + Etherbase: MakeEtherbase(accman, ctx), MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name), - AccountManager: am, - VmDebug: ctx.GlobalBool(VMDebugFlag.Name), - MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), - MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name), - Port: ctx.GlobalString(ListenPortFlag.Name), - Olympic: ctx.GlobalBool(OlympicFlag.Name), - NAT: MakeNAT(ctx), + ExtraData: MakeMinerExtra(extra, ctx), NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), DocRoot: ctx.GlobalString(DocRootFlag.Name), - Discovery: !ctx.GlobalBool(NoDiscoverFlag.Name), - NodeKey: MakeNodeKey(ctx), - Shh: ctx.GlobalBool(WhisperEnabledFlag.Name), - Dial: true, - BootNodes: ctx.GlobalString(BootnodesFlag.Name), GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)), GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)), GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)), @@ -462,46 +633,70 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { SolcPath: ctx.GlobalString(SolcPathFlag.Name), AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name), } + // Configure the Whisper service + shhEnable := ctx.GlobalBool(WhisperEnabledFlag.Name) - if ctx.GlobalBool(DevModeFlag.Name) && ctx.GlobalBool(TestNetFlag.Name) { - glog.Fatalf("%s and %s are mutually exclusive\n", DevModeFlag.Name, TestNetFlag.Name) - } + // Override any default configs in dev mode or the test net + switch { + case ctx.GlobalBool(OlympicFlag.Name): + if !ctx.GlobalIsSet(NetworkIdFlag.Name) { + ethConf.NetworkId = 1 + } + if !ctx.GlobalIsSet(GenesisFileFlag.Name) { + ethConf.Genesis = core.OlympicGenesisBlock() + } - if ctx.GlobalBool(TestNetFlag.Name) { - // testnet is always stored in the testnet folder - cfg.DataDir += "/testnet" - cfg.NetworkId = 2 - cfg.TestNet = true - } + case ctx.GlobalBool(TestNetFlag.Name): + if !ctx.GlobalIsSet(NetworkIdFlag.Name) { + ethConf.NetworkId = 2 + } + if !ctx.GlobalIsSet(GenesisFileFlag.Name) { + ethConf.Genesis = core.TestNetGenesisBlock() + } + state.StartingNonce = 1048576 // (2**20) - if ctx.GlobalBool(VMEnableJitFlag.Name) { - cfg.Name += "/JIT" - } - if ctx.GlobalBool(DevModeFlag.Name) { - if !ctx.GlobalIsSet(VMDebugFlag.Name) { - cfg.VmDebug = true + case ctx.GlobalBool(DevModeFlag.Name): + // Override the base network stack configs + if !ctx.GlobalIsSet(DataDirFlag.Name) { + stackConf.DataDir = filepath.Join(os.TempDir(), "/ethereum_dev_mode") } if !ctx.GlobalIsSet(MaxPeersFlag.Name) { - cfg.MaxPeers = 0 - } - if !ctx.GlobalIsSet(GasPriceFlag.Name) { - cfg.GasPrice = new(big.Int) + stackConf.MaxPeers = 0 } if !ctx.GlobalIsSet(ListenPortFlag.Name) { - cfg.Port = "0" // auto port + stackConf.ListenAddr = ":0" + } + // Override the Ethereum protocol configs + if !ctx.GlobalIsSet(GenesisFileFlag.Name) { + ethConf.Genesis = core.OlympicGenesisBlock() + } + if !ctx.GlobalIsSet(GasPriceFlag.Name) { + ethConf.GasPrice = new(big.Int) } if !ctx.GlobalIsSet(WhisperEnabledFlag.Name) { - cfg.Shh = true + shhEnable = true } - if !ctx.GlobalIsSet(DataDirFlag.Name) { - cfg.DataDir = os.TempDir() + "/ethereum_dev_mode" + if !ctx.GlobalIsSet(VMDebugFlag.Name) { + vm.Debug = true } - cfg.PowTest = true - cfg.DevMode = true - - glog.V(logger.Info).Infoln("dev mode enabled") + ethConf.PowTest = true } - return cfg + // Assemble and return the protocol stack + stack, err := node.New(stackConf) + if err != nil { + Fatalf("Failed to create the protocol stack: %v", err) + } + if err := stack.Register("eth", func(ctx *node.ServiceContext) (node.Service, error) { + return eth.New(ctx, ethConf) + }); err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } + if shhEnable { + if err := stack.Register("shh", func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { + Fatalf("Failed to register the Whisper service: %v", err) + } + } + return stack } // SetupLogger configures glog from the logging-related command line flags. @@ -509,7 +704,12 @@ func SetupLogger(ctx *cli.Context) { glog.SetV(ctx.GlobalInt(VerbosityFlag.Name)) glog.CopyStandardLogTo("INFO") glog.SetToStderr(true) - glog.SetLogDir(ctx.GlobalString(LogFileFlag.Name)) + if ctx.GlobalIsSet(LogFileFlag.Name) { + logger.New("", ctx.GlobalString(LogFileFlag.Name), ctx.GlobalInt(VerbosityFlag.Name)) + } + if ctx.GlobalIsSet(VMDebugFlag.Name) { + vm.Debug = ctx.GlobalBool(VMDebugFlag.Name) + } } // SetupNetwork configures the system for either the main net or some test network. @@ -535,7 +735,7 @@ func SetupVM(ctx *cli.Context) { // MakeChain creates a chain manager from set command line flags. func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database) { - datadir := MustDataDir(ctx) + datadir := MustMakeDataDir(ctx) cache := ctx.GlobalInt(CacheFlag.Name) var err error @@ -543,7 +743,7 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database Fatalf("Could not open database: %v", err) } if ctx.GlobalBool(OlympicFlag.Name) { - _, err := core.WriteTestNetGenesisBlock(chainDb, 42) + _, err := core.WriteTestNetGenesisBlock(chainDb) if err != nil { glog.Fatalln(err) } @@ -560,32 +760,6 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database return chain, chainDb } -// MakeChain creates an account manager from set command line flags. -func MakeAccountManager(ctx *cli.Context) *accounts.Manager { - dataDir := MustDataDir(ctx) - if ctx.GlobalBool(TestNetFlag.Name) { - dataDir += "/testnet" - } - scryptN := crypto.StandardScryptN - scryptP := crypto.StandardScryptP - if ctx.GlobalBool(LightKDFFlag.Name) { - scryptN = crypto.LightScryptN - scryptP = crypto.LightScryptP - } - ks := crypto.NewKeyStorePassphrase(filepath.Join(dataDir, "keystore"), scryptN, scryptP) - return accounts.NewManager(ks) -} - -// MustDataDir retrieves the currently requested data directory, terminating if -// none (or the empty string) is specified. -func MustDataDir(ctx *cli.Context) string { - if path := ctx.GlobalString(DataDirFlag.Name); path != "" { - return path - } - Fatalf("Cannot determine default data directory, please set manually (--datadir)") - return "" -} - func IpcSocketPath(ctx *cli.Context) (ipcpath string) { if runtime.GOOS == "windows" { ipcpath = common.DefaultIpcPath() @@ -605,39 +779,39 @@ func IpcSocketPath(ctx *cli.Context) (ipcpath string) { return } -func StartIPC(eth *eth.Ethereum, ctx *cli.Context) error { +// StartIPC starts a IPC JSON-RPC API server. +func StartIPC(stack *node.Node, ctx *cli.Context) error { config := comms.IpcConfig{ Endpoint: IpcSocketPath(ctx), } initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) { - fe := useragent.NewRemoteFrontend(conn, eth.AccountManager()) - xeth := xeth.New(eth, fe) - apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, eth) + fe := useragent.NewRemoteFrontend(conn, stack.Service("eth").(*eth.Ethereum).AccountManager()) + xeth := xeth.New(stack, fe) + apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, stack) if err != nil { return nil, nil, err } return xeth, api.Merge(apis...), nil } - return comms.StartIpc(config, codec.JSON, initializer) } -func StartRPC(eth *eth.Ethereum, ctx *cli.Context) error { +// StartRPC starts a HTTP JSON-RPC API server. +func StartRPC(stack *node.Node, ctx *cli.Context) error { config := comms.HttpConfig{ ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name), ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)), CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name), } - xeth := xeth.New(eth, nil) + xeth := xeth.New(stack, nil) codec := codec.JSON - apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, eth) + apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, stack) if err != nil { return err } - return comms.StartHttp(config, codec, api.Merge(apis...)) } @@ -647,20 +821,3 @@ func StartPProf(ctx *cli.Context) { log.Println(http.ListenAndServe(address, nil)) }() } - -func ParamToAddress(addr string, am *accounts.Manager) (addrHex string, err error) { - if !((len(addr) == 40) || (len(addr) == 42)) { // with or without 0x - index, err := strconv.Atoi(addr) - if err != nil { - Fatalf("Invalid account address '%s'", addr) - } - - addrHex, err = am.AddressByIndex(index) - if err != nil { - return "", err - } - } else { - addrHex = addr - } - return -} diff --git a/common/natspec/natspec_e2e_test.go b/common/natspec/natspec_e2e_test.go index 5c0d43091c..95109ec07a 100644 --- a/common/natspec/natspec_e2e_test.go +++ b/common/natspec/natspec_e2e_test.go @@ -34,6 +34,8 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/node" xe "github.com/ethereum/go-ethereum/xeth" ) @@ -146,13 +148,11 @@ func testEth(t *testing.T) (ethereum *eth.Ethereum, err error) { } // only use minimalistic stack with no networking - return eth.New(ð.Config{ - DataDir: tmp, + return eth.New(&node.ServiceContext{EventMux: new(event.TypeMux)}, ð.Config{ AccountManager: am, Etherbase: common.HexToAddress(testAddress), - MaxPeers: 0, PowTest: true, - NewDB: func(path string) (ethdb.Database, error) { return db, nil }, + TestGenesisState: db, GpoMinGasPrice: common.Big1, GpobaseCorrectionFactor: 1, GpoMaxGasPrice: common.Big1, @@ -166,7 +166,7 @@ func testInit(t *testing.T) (self *testFrontend) { t.Errorf("error creating ethereum: %v", err) return } - err = ethereum.Start() + err = ethereum.Start(nil) if err != nil { t.Errorf("error starting ethereum: %v", err) return @@ -174,7 +174,7 @@ func testInit(t *testing.T) (self *testFrontend) { // mock frontend self = &testFrontend{t: t, ethereum: ethereum} - self.xeth = xe.New(ethereum, self) + self.xeth = xe.New(nil, self) self.wait = self.xeth.UpdateState() addr, _ := self.ethereum.Etherbase() diff --git a/common/types.go b/common/types.go index 624f4b8265..ea58381886 100644 --- a/common/types.go +++ b/common/types.go @@ -24,13 +24,13 @@ import ( ) const ( - hashLength = 32 - addressLength = 20 + HashLength = 32 + AddressLength = 20 ) type ( - Hash [hashLength]byte - Address [addressLength]byte + Hash [HashLength]byte + Address [AddressLength]byte ) func BytesToHash(b []byte) Hash { @@ -53,10 +53,10 @@ func (h Hash) Hex() string { return "0x" + Bytes2Hex(h[:]) } // Sets the hash to the value of b. If b is larger than len(h) it will panic func (h *Hash) SetBytes(b []byte) { if len(b) > len(h) { - b = b[len(b)-hashLength:] + b = b[len(b)-HashLength:] } - copy(h[hashLength-len(b):], b) + copy(h[HashLength-len(b):], b) } // Set string `s` to h. If s is larger than len(h) it will panic @@ -92,6 +92,18 @@ func StringToAddress(s string) Address { return BytesToAddress([]byte(s)) } func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } +// IsHexAddress verifies whether a string can represent a valid hex-encoded +// Ethereum address or not. +func IsHexAddress(s string) bool { + if len(s) == 2+2*AddressLength && IsHex(s[2:]) { + return true + } + if len(s) == 2*AddressLength && IsHex(s) { + return true + } + return false +} + // Get the string representation of the underlying address func (a Address) Str() string { return string(a[:]) } func (a Address) Bytes() []byte { return a[:] } @@ -102,9 +114,9 @@ func (a Address) Hex() string { return "0x" + Bytes2Hex(a[:]) } // Sets the address to the value of b. If b is larger than len(a) it will panic func (a *Address) SetBytes(b []byte) { if len(b) > len(a) { - b = b[len(b)-addressLength:] + b = b[len(b)-AddressLength:] } - copy(a[addressLength-len(b):], b) + copy(a[AddressLength-len(b):], b) } // Set string `s` to a. If s is larger than len(a) it will panic diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 70953d76d0..2c4a97b457 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -34,7 +34,7 @@ func proc() (Validator, *BlockChain) { db, _ := ethdb.NewMemDatabase() var mux event.TypeMux - WriteTestNetGenesisBlock(db, 0) + WriteTestNetGenesisBlock(db) blockchain, err := NewBlockChain(db, thePow(), &mux) if err != nil { fmt.Println(err) diff --git a/core/blockchain.go b/core/blockchain.go index 5e1fc9424c..5a6795b3ec 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -149,11 +149,7 @@ func NewBlockChain(chainDb ethdb.Database, pow pow.PoW, mux *event.TypeMux) (*Bl bc.genesisBlock = bc.GetBlockByNumber(0) if bc.genesisBlock == nil { - reader, err := NewDefaultGenesisReader() - if err != nil { - return nil, err - } - bc.genesisBlock, err = WriteGenesisBlock(chainDb, reader) + bc.genesisBlock, err = WriteDefaultGenesisBlock(chainDb) if err != nil { return nil, err } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index f18b5d084a..6e1c5fdc76 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -51,7 +51,7 @@ func thePow() pow.PoW { func theBlockChain(db ethdb.Database, t *testing.T) *BlockChain { var eventMux event.TypeMux - WriteTestNetGenesisBlock(db, 0) + WriteTestNetGenesisBlock(db) blockchain, err := NewBlockChain(db, thePow(), &eventMux) if err != nil { t.Error("failed creating blockchain:", err) @@ -506,7 +506,7 @@ func testReorgShort(t *testing.T, full bool) { func testReorg(t *testing.T, first, second []int, td int64, full bool) { // Create a pristine block chain db, _ := ethdb.NewMemDatabase() - genesis, _ := WriteTestNetGenesisBlock(db, 0) + genesis, _ := WriteTestNetGenesisBlock(db) bc := chm(genesis, db) // Insert an easy and a difficult chain afterwards @@ -553,7 +553,7 @@ func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true) } func testBadHashes(t *testing.T, full bool) { // Create a pristine block chain db, _ := ethdb.NewMemDatabase() - genesis, _ := WriteTestNetGenesisBlock(db, 0) + genesis, _ := WriteTestNetGenesisBlock(db) bc := chm(genesis, db) // Create a chain, ban a hash and try to import @@ -580,7 +580,7 @@ func TestReorgBadBlockHashes(t *testing.T) { testReorgBadHashes(t, true) } func testReorgBadHashes(t *testing.T, full bool) { // Create a pristine block chain db, _ := ethdb.NewMemDatabase() - genesis, _ := WriteTestNetGenesisBlock(db, 0) + genesis, _ := WriteTestNetGenesisBlock(db) bc := chm(genesis, db) // Create a chain, import and ban aferwards diff --git a/core/chain_makers.go b/core/chain_makers.go index f1ada487f2..6d3152d977 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -220,7 +220,7 @@ func newCanonical(n int, full bool) (ethdb.Database, *BlockChain, error) { evmux := &event.TypeMux{} // Initialize a fresh chain with only a genesis block - genesis, _ := WriteTestNetGenesisBlock(db, 0) + genesis, _ := WriteTestNetGenesisBlock(db) blockchain, _ := NewBlockChain(db, FakePow{}, evmux) // Create and inject the requested chain diff --git a/core/default_genesis.go b/core/default_genesis.go index f8acda9fb6..b418bfdfe0 100644 --- a/core/default_genesis.go +++ b/core/default_genesis.go @@ -16,15 +16,6 @@ package core -import ( - "compress/gzip" - "encoding/base64" - "io" - "strings" -) - -func NewDefaultGenesisReader() (io.Reader, error) { - return gzip.NewReader(base64.NewDecoder(base64.StdEncoding, strings.NewReader(defaultGenesisBlock))) -} - +// defaultGenesisBlock is a gzip compressed dump of the official default Ethereum +// genesis block. const defaultGenesisBlock = "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" diff --git a/core/genesis.go b/core/genesis.go index 3fd8f42b0b..d8c6e9cea3 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -17,6 +17,8 @@ package core import ( + "compress/gzip" + "encoding/base64" "encoding/json" "fmt" "io" @@ -158,46 +160,80 @@ func WriteGenesisBlockForTesting(db ethdb.Database, accounts ...GenesisAccount) return block } -func WriteTestNetGenesisBlock(chainDb ethdb.Database, nonce uint64) (*types.Block, error) { - testGenesis := fmt.Sprintf(`{ - "nonce": "0x%x", - "difficulty": "0x20000", - "mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", - "coinbase": "0x0000000000000000000000000000000000000000", - "timestamp": "0x00", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "extraData": "0x", - "gasLimit": "0x2FEFD8", - "alloc": { - "0000000000000000000000000000000000000001": { "balance": "1" }, - "0000000000000000000000000000000000000002": { "balance": "1" }, - "0000000000000000000000000000000000000003": { "balance": "1" }, - "0000000000000000000000000000000000000004": { "balance": "1" }, - "102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } - } -}`, types.EncodeNonce(nonce)) - return WriteGenesisBlock(chainDb, strings.NewReader(testGenesis)) +// WriteDefaultGenesisBlock assembles the official Ethereum genesis block and +// writes it - along with all associated state - into a chain database. +func WriteDefaultGenesisBlock(chainDb ethdb.Database) (*types.Block, error) { + return WriteGenesisBlock(chainDb, strings.NewReader(DefaultGenesisBlock())) } -func WriteOlympicGenesisBlock(chainDb ethdb.Database, nonce uint64) (*types.Block, error) { - testGenesis := fmt.Sprintf(`{ - "nonce":"0x%x", - "gasLimit":"0x%x", - "difficulty":"0x%x", - "alloc": { - "0000000000000000000000000000000000000001": {"balance": "1"}, - "0000000000000000000000000000000000000002": {"balance": "1"}, - "0000000000000000000000000000000000000003": {"balance": "1"}, - "0000000000000000000000000000000000000004": {"balance": "1"}, - "dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, - "e4157b34ea9615cfbde6b4fda419828124b70c78": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, - "b9c015918bdaba24b4ff057a92a3873d6eb201be": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, - "6c386a4b26f73c802f34673f7248bb118f97424a": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, - "cd2a3d9f938e13cd947ec05abc7fe734df8dd826": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, - "2ef47100e0787b915105fd5e3f4ff6752079d5cb": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, - "e6716f9544a56c530d868e4bfbacb172315bdead": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, - "1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {"balance": "1606938044258990275541962092341162602522202993782792835301376"} - } -}`, types.EncodeNonce(nonce), params.GenesisGasLimit.Bytes(), params.GenesisDifficulty.Bytes()) - return WriteGenesisBlock(chainDb, strings.NewReader(testGenesis)) +// WriteTestNetGenesisBlock assembles the Morden test network genesis block and +// writes it - along with all associated state - into a chain database. +func WriteTestNetGenesisBlock(chainDb ethdb.Database) (*types.Block, error) { + return WriteGenesisBlock(chainDb, strings.NewReader(TestNetGenesisBlock())) +} + +// WriteOlympicGenesisBlock assembles the Olympic genesis block and writes it +// along with all associated state into a chain database. +func WriteOlympicGenesisBlock(db ethdb.Database) (*types.Block, error) { + return WriteGenesisBlock(db, strings.NewReader(OlympicGenesisBlock())) +} + +// DefaultGenesisBlock assembles a JSON string representing the default Ethereum +// genesis block. +func DefaultGenesisBlock() string { + reader, err := gzip.NewReader(base64.NewDecoder(base64.StdEncoding, strings.NewReader(defaultGenesisBlock))) + if err != nil { + panic(fmt.Sprintf("failed to access default genesis: %v", err)) + } + blob, err := ioutil.ReadAll(reader) + if err != nil { + panic(fmt.Sprintf("failed to load default genesis: %v", err)) + } + return string(blob) +} + +// OlympicGenesisBlock assembles a JSON string representing the Olympic genesis +// block. +func OlympicGenesisBlock() string { + return fmt.Sprintf(`{ + "nonce":"0x%x", + "gasLimit":"0x%x", + "difficulty":"0x%x", + "alloc": { + "0000000000000000000000000000000000000001": {"balance": "1"}, + "0000000000000000000000000000000000000002": {"balance": "1"}, + "0000000000000000000000000000000000000003": {"balance": "1"}, + "0000000000000000000000000000000000000004": {"balance": "1"}, + "dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, + "e4157b34ea9615cfbde6b4fda419828124b70c78": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, + "b9c015918bdaba24b4ff057a92a3873d6eb201be": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, + "6c386a4b26f73c802f34673f7248bb118f97424a": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, + "2ef47100e0787b915105fd5e3f4ff6752079d5cb": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, + "e6716f9544a56c530d868e4bfbacb172315bdead": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, + "1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {"balance": "1606938044258990275541962092341162602522202993782792835301376"} + } + }`, types.EncodeNonce(42), params.GenesisGasLimit.Bytes(), params.GenesisDifficulty.Bytes()) +} + +// TestNetGenesisBlock assembles a JSON string representing the Morden test net +// genenis block. +func TestNetGenesisBlock() string { + return fmt.Sprintf(`{ + "nonce": "0x%x", + "difficulty": "0x20000", + "mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", + "coinbase": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2FEFD8", + "alloc": { + "0000000000000000000000000000000000000001": { "balance": "1" }, + "0000000000000000000000000000000000000002": { "balance": "1" }, + "0000000000000000000000000000000000000003": { "balance": "1" }, + "0000000000000000000000000000000000000004": { "balance": "1" }, + "102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } + } + }`, types.EncodeNonce(0x6d6f7264656e)) } diff --git a/eth/backend.go b/eth/backend.go index 5bd6ac55da..5a8cf6a734 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -19,16 +19,12 @@ package eth import ( "bytes" - "crypto/ecdsa" - "encoding/json" "fmt" - "io/ioutil" "math/big" "os" "path/filepath" "regexp" "strings" - "syscall" "time" "github.com/ethereum/ethash" @@ -37,21 +33,16 @@ import ( "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/httpclient" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discover" - "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/whisper" ) const ( @@ -63,74 +54,29 @@ const ( ) var ( - jsonlogger = logger.NewJsonLogger() - datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} portInUseErrRE = regexp.MustCompile("address already in use") - - defaultBootNodes = []*discover.Node{ - // ETH/DEV Go Bootnodes - discover.MustParseNode("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303"), // IE - discover.MustParseNode("enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303"), // BR - discover.MustParseNode("enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303"), // SG - // ETH/DEV cpp-ethereum (poc-9.ethdev.com) - discover.MustParseNode("enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303"), - } - - defaultTestNetBootNodes = []*discover.Node{ - discover.MustParseNode("enode://e4533109cc9bd7604e4ff6c095f7a1d807e15b38e9bfeb05d3b7c423ba86af0a9e89abbf40bd9dde4250fef114cd09270fa4e224cbeef8b7bf05a51e8260d6b8@94.242.229.4:40404"), - discover.MustParseNode("enode://8c336ee6f03e99613ad21274f269479bf4413fb294d697ef15ab897598afb931f56beb8e97af530aee20ce2bcba5776f4a312bc168545de4d43736992c814592@94.242.229.203:30303"), - } - - staticNodes = "static-nodes.json" // Path within to search for the static node list - trustedNodes = "trusted-nodes.json" // Path within to search for the trusted node list ) type Config struct { - DevMode bool - TestNet bool - - Name string - NetworkId int - GenesisFile string - GenesisBlock *types.Block // used by block tests - FastSync bool - Olympic bool + NetworkId int // Network ID to use for selecting peers to connect to + Genesis string // Genesis JSON to seed the chain database with + FastSync bool // Enables the state download based fast synchronisation algorithm BlockChainVersion int SkipBcVersionCheck bool // e.g. blockchain export DatabaseCache int - DataDir string - LogFile string - Verbosity int - VmDebug bool NatSpec bool DocRoot string AutoDAG bool PowTest bool ExtraData []byte - MaxPeers int - MaxPendingPeers int - Discovery bool - Port string - - // Space-separated list of discovery node URLs - BootNodes string - - // This key is used to identify the node on the network. - // If nil, an ephemeral key is used. - NodeKey *ecdsa.PrivateKey - - NAT nat.Interface - Shh bool - Dial bool - + AccountManager *accounts.Manager Etherbase common.Address GasPrice *big.Int MinerThreads int - AccountManager *accounts.Manager SolcPath string GpoMinGasPrice *big.Int @@ -140,87 +86,8 @@ type Config struct { GpobaseStepUp int GpobaseCorrectionFactor int - // NewDB is used to create databases. - // If nil, the default is to create leveldb databases on disk. - NewDB func(path string) (ethdb.Database, error) -} - -func (cfg *Config) parseBootNodes() []*discover.Node { - if cfg.BootNodes == "" { - if cfg.TestNet { - return defaultTestNetBootNodes - } - - return defaultBootNodes - } - var ns []*discover.Node - for _, url := range strings.Split(cfg.BootNodes, " ") { - if url == "" { - continue - } - n, err := discover.ParseNode(url) - if err != nil { - glog.V(logger.Error).Infof("Bootstrap URL %s: %v\n", url, err) - continue - } - ns = append(ns, n) - } - return ns -} - -// parseNodes parses a list of discovery node URLs loaded from a .json file. -func (cfg *Config) parseNodes(file string) []*discover.Node { - // Short circuit if no node config is present - path := filepath.Join(cfg.DataDir, file) - if _, err := os.Stat(path); err != nil { - return nil - } - // Load the nodes from the config file - blob, err := ioutil.ReadFile(path) - if err != nil { - glog.V(logger.Error).Infof("Failed to access nodes: %v", err) - return nil - } - nodelist := []string{} - if err := json.Unmarshal(blob, &nodelist); err != nil { - glog.V(logger.Error).Infof("Failed to load nodes: %v", err) - return nil - } - // Interpret the list as a discovery node array - var nodes []*discover.Node - for _, url := range nodelist { - if url == "" { - continue - } - node, err := discover.ParseNode(url) - if err != nil { - glog.V(logger.Error).Infof("Node URL %s: %v\n", url, err) - continue - } - nodes = append(nodes, node) - } - return nodes -} - -func (cfg *Config) nodeKey() (*ecdsa.PrivateKey, error) { - // use explicit key from command line args if set - if cfg.NodeKey != nil { - return cfg.NodeKey, nil - } - // use persistent key if present - keyfile := filepath.Join(cfg.DataDir, "nodekey") - key, err := crypto.LoadECDSA(keyfile) - if err == nil { - return key, nil - } - // no persistent key, generate and store a new one - if key, err = crypto.GenerateKey(); err != nil { - return nil, fmt.Errorf("could not generate server key: %v", err) - } - if err := crypto.SaveECDSA(keyfile, key); err != nil { - glog.V(logger.Error).Infoln("could not persist nodekey: ", err) - } - return key, nil + TestGenesisBlock *types.Block // Genesis block to seed the chain database with (testing only!) + TestGenesisState ethdb.Database // Genesis state to seed the database with (testing only!) } type Ethereum struct { @@ -235,7 +102,6 @@ type Ethereum struct { txPool *core.TxPool blockchain *core.BlockChain accountManager *accounts.Manager - whisper *whisper.Whisper pow *ethash.Ethash protocolManager *ProtocolManager SolcPath string @@ -250,44 +116,28 @@ type Ethereum struct { httpclient *httpclient.HTTPClient - net *p2p.Server eventMux *event.TypeMux miner *miner.Miner - // logger logger.LogSystem - - Mining bool - MinerThreads int - NatSpec bool - DataDir string - AutoDAG bool - PowTest bool - autodagquit chan bool - etherbase common.Address - clientVersion string - netVersionId int - shhVersionId int + Mining bool + MinerThreads int + NatSpec bool + AutoDAG bool + PowTest bool + autodagquit chan bool + etherbase common.Address + netVersionId int } -func New(config *Config) (*Ethereum, error) { - logger.New(config.DataDir, config.LogFile, config.Verbosity) - +func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { // Let the database take 3/4 of the max open files (TODO figure out a way to get the actual limit of the open files) const dbCount = 3 ethdb.OpenFileLimit = 128 / (dbCount + 1) - newdb := config.NewDB - if newdb == nil { - newdb = func(path string) (ethdb.Database, error) { return ethdb.NewLDBDatabase(path, config.DatabaseCache) } - } - // Open the chain database and perform any upgrades needed - chainDb, err := newdb(filepath.Join(config.DataDir, "chaindata")) + chainDb, err := ctx.Database("chaindata", config.DatabaseCache) if err != nil { - if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { - err = fmt.Errorf("%v (check if another instance of geth is already running with the same data directory '%s')", err, config.DataDir) - } - return nil, fmt.Errorf("blockchain db err: %v", err) + return nil, err } if db, ok := chainDb.(*ethdb.LDBDatabase); ok { db.Meter("eth/db/chaindata/") @@ -299,56 +149,32 @@ func New(config *Config) (*Ethereum, error) { return nil, err } - dappDb, err := newdb(filepath.Join(config.DataDir, "dapp")) + dappDb, err := ctx.Database("dapp", config.DatabaseCache) if err != nil { - if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { - err = fmt.Errorf("%v (check if another instance of geth is already running with the same data directory '%s')", err, config.DataDir) - } - return nil, fmt.Errorf("dapp db err: %v", err) + return nil, err } if db, ok := dappDb.(*ethdb.LDBDatabase); ok { db.Meter("eth/db/dapp/") } - - nodeDb := filepath.Join(config.DataDir, "nodes") glog.V(logger.Info).Infof("Protocol Versions: %v, Network Id: %v", ProtocolVersions, config.NetworkId) - if len(config.GenesisFile) > 0 { - fr, err := os.Open(config.GenesisFile) + // Load up any custom genesis block if requested + if len(config.Genesis) > 0 { + block, err := core.WriteGenesisBlock(chainDb, strings.NewReader(config.Genesis)) if err != nil { return nil, err } - - block, err := core.WriteGenesisBlock(chainDb, fr) - if err != nil { - return nil, err - } - glog.V(logger.Info).Infof("Successfully wrote genesis block. New genesis hash = %x\n", block.Hash()) + glog.V(logger.Info).Infof("Successfully wrote custom genesis block: %x", block.Hash()) } - - // different modes - switch { - case config.Olympic: - glog.V(logger.Error).Infoln("Starting Olympic network") - fallthrough - case config.DevMode: - _, err := core.WriteOlympicGenesisBlock(chainDb, 42) - if err != nil { - return nil, err - } - case config.TestNet: - state.StartingNonce = 1048576 // (2**20) - _, err := core.WriteTestNetGenesisBlock(chainDb, 0x6d6f7264656e) - if err != nil { - return nil, err - } + // Load up a test setup if directly injected + if config.TestGenesisState != nil { + chainDb = config.TestGenesisState } - // This is for testing only. - if config.GenesisBlock != nil { - core.WriteTd(chainDb, config.GenesisBlock.Hash(), config.GenesisBlock.Difficulty()) - core.WriteBlock(chainDb, config.GenesisBlock) - core.WriteCanonicalHash(chainDb, config.GenesisBlock.Hash(), config.GenesisBlock.NumberU64()) - core.WriteHeadBlockHash(chainDb, config.GenesisBlock.Hash()) + if config.TestGenesisBlock != nil { + core.WriteTd(chainDb, config.TestGenesisBlock.Hash(), config.TestGenesisBlock.Difficulty()) + core.WriteBlock(chainDb, config.TestGenesisBlock) + core.WriteCanonicalHash(chainDb, config.TestGenesisBlock.Hash(), config.TestGenesisBlock.NumberU64()) + core.WriteHeadBlockHash(chainDb, config.TestGenesisBlock.Hash()) } if !config.SkipBcVersionCheck { @@ -367,9 +193,7 @@ func New(config *Config) (*Ethereum, error) { dappDb: dappDb, eventMux: &event.TypeMux{}, accountManager: config.AccountManager, - DataDir: config.DataDir, etherbase: config.Etherbase, - clientVersion: config.Name, // TODO should separate from Name netVersionId: config.NetworkId, NatSpec: config.NatSpec, MinerThreads: config.MinerThreads, @@ -412,48 +236,9 @@ func New(config *Config) (*Ethereum, error) { eth.miner.SetGasPrice(config.GasPrice) eth.miner.SetExtra(config.ExtraData) - if config.Shh { - eth.whisper = whisper.New() - eth.shhVersionId = int(eth.whisper.Version()) - } - - netprv, err := config.nodeKey() - if err != nil { - return nil, err - } - protocols := append([]p2p.Protocol{}, eth.protocolManager.SubProtocols...) - if config.Shh { - protocols = append(protocols, eth.whisper.Protocol()) - } - eth.net = &p2p.Server{ - PrivateKey: netprv, - Name: config.Name, - MaxPeers: config.MaxPeers, - MaxPendingPeers: config.MaxPendingPeers, - Discovery: config.Discovery, - Protocols: protocols, - NAT: config.NAT, - NoDial: !config.Dial, - BootstrapNodes: config.parseBootNodes(), - StaticNodes: config.parseNodes(staticNodes), - TrustedNodes: config.parseNodes(trustedNodes), - NodeDatabase: nodeDb, - } - if len(config.Port) > 0 { - eth.net.ListenAddr = ":" + config.Port - } - - vm.Debug = config.VmDebug - return eth, nil } -// Network retrieves the underlying P2P network server. This should eventually -// be moved out into a protocol independent package, but for now use an accessor. -func (s *Ethereum) Network() *p2p.Server { - return s.net -} - func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { s.blockchain.ResetWithGenesisBlock(gb) } @@ -480,86 +265,48 @@ func (s *Ethereum) StopMining() { s.miner.Stop() } func (s *Ethereum) IsMining() bool { return s.miner.Mining() } func (s *Ethereum) Miner() *miner.Miner { return s.miner } -// func (s *Ethereum) Logger() logger.LogSystem { return s.logger } -func (s *Ethereum) Name() string { return s.net.Name } func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } -func (s *Ethereum) Whisper() *whisper.Whisper { return s.whisper } func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) DappDb() ethdb.Database { return s.dappDb } func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) PeerCount() int { return s.net.PeerCount() } -func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() } -func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers } -func (s *Ethereum) ClientVersion() string { return s.clientVersion } func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } func (s *Ethereum) NetVersion() int { return s.netVersionId } -func (s *Ethereum) ShhVersion() int { return s.shhVersionId } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } -// Start the ethereum -func (s *Ethereum) Start() error { - jsonlogger.LogJson(&logger.LogStarting{ - ClientString: s.net.Name, - ProtocolVersion: s.EthVersion(), - }) - err := s.net.Start() - if err != nil { - if portInUseErrRE.MatchString(err.Error()) { - err = fmt.Errorf("%v (possibly another instance of geth is using the same port)", err) - } - return err - } +// Protocols implements node.Service, returning all the currently configured +// network protocols to start. +func (s *Ethereum) Protocols() []p2p.Protocol { + return s.protocolManager.SubProtocols +} +// Start implements node.Service, starting all internal goroutines needed by the +// Ethereum protocol implementation. +func (s *Ethereum) Start(*p2p.Server) error { if s.AutoDAG { s.StartAutoDAG() } - s.protocolManager.Start() - - if s.whisper != nil { - s.whisper.Start() - } - - glog.V(logger.Info).Infoln("Server started") return nil } -func (s *Ethereum) StartForTest() { - jsonlogger.LogJson(&logger.LogStarting{ - ClientString: s.net.Name, - ProtocolVersion: s.EthVersion(), - }) -} - -// AddPeer connects to the given node and maintains the connection until the -// server is shut down. If the connection fails for any reason, the server will -// attempt to reconnect the peer. -func (self *Ethereum) AddPeer(nodeURL string) error { - n, err := discover.ParseNode(nodeURL) - if err != nil { - return fmt.Errorf("invalid node URL: %v", err) - } - self.net.AddPeer(n) - return nil -} - -func (s *Ethereum) Stop() { - s.net.Stop() +// Stop implements node.Service, terminating all internal goroutines used by the +// Ethereum protocol. +func (s *Ethereum) Stop() error { s.blockchain.Stop() s.protocolManager.Stop() s.txPool.Stop() s.eventMux.Stop() - if s.whisper != nil { - s.whisper.Stop() - } + s.StopAutoDAG() s.chainDb.Close() s.dappDb.Close() close(s.shutdownChan) + + return nil } // This function will wait for a shutdown and resumes main thread execution diff --git a/node/node.go b/node/node.go index ad4e69414c..023f774034 100644 --- a/node/node.go +++ b/node/node.go @@ -21,6 +21,7 @@ import ( "errors" "os" "path/filepath" + "reflect" "sync" "syscall" @@ -40,14 +41,17 @@ var ( // Node represents a P2P node into which arbitrary services might be registered. type Node struct { - datadir string // Path to the currently used data directory - config *p2p.Server // Configuration of the underlying P2P networking layer - stack map[string]ServiceConstructor // Protocol stack registered into this node - emux *event.TypeMux // Event multiplexer used between the services of a stack + datadir string // Path to the currently used data directory + eventmux *event.TypeMux // Event multiplexer used between the services of a stack - running *p2p.Server // Currently running P2P networking layer - services map[string]Service // Currently running services + serverConfig *p2p.Server // Configuration of the underlying P2P networking layer + server *p2p.Server // Currently running P2P networking layer + serviceIndex map[string]ServiceConstructor // Set of services currently registered in the node + serviceOrder []string // Service construction order to handle dependencies + services map[string]Service // Currently running services + + stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex } @@ -66,7 +70,7 @@ func New(conf *Config) (*Node, error) { } return &Node{ datadir: conf.DataDir, - config: &p2p.Server{ + serverConfig: &p2p.Server{ PrivateKey: conf.NodeKey(), Name: conf.Name, Discovery: !conf.NoDiscovery, @@ -81,8 +85,9 @@ func New(conf *Config) (*Node, error) { MaxPeers: conf.MaxPeers, MaxPendingPeers: conf.MaxPendingPeers, }, - stack: make(map[string]ServiceConstructor), - emux: new(event.TypeMux), + serviceIndex: make(map[string]ServiceConstructor), + serviceOrder: []string{}, + eventmux: new(event.TypeMux), }, nil } @@ -92,14 +97,15 @@ func (n *Node) Register(id string, constructor ServiceConstructor) error { defer n.lock.Unlock() // Short circuit if the node is running or if the id is taken - if n.running != nil { + if n.server != nil { return ErrNodeRunning } - if _, ok := n.stack[id]; ok { + if _, ok := n.serviceIndex[id]; ok { return ErrServiceRegistered } // Otherwise register the service and return - n.stack[id] = constructor + n.serviceOrder = append(n.serviceOrder, id) + n.serviceIndex[id] = constructor return nil } @@ -111,14 +117,20 @@ func (n *Node) Unregister(id string) error { defer n.lock.Unlock() // Short circuit if the node is running, or if the service is unknown - if n.running != nil { + if n.server != nil { return ErrNodeRunning } - if _, ok := n.stack[id]; !ok { + if _, ok := n.serviceIndex[id]; !ok { return ErrServiceUnknown } // Otherwise drop the service and return - delete(n.stack, id) + delete(n.serviceIndex, id) + for i, service := range n.serviceOrder { + if service == id { + n.serviceOrder = append(n.serviceOrder[:i], n.serviceOrder[i+1:]...) + break + } + } return nil } @@ -128,19 +140,27 @@ func (n *Node) Start() error { defer n.lock.Unlock() // Short circuit if the node's already running - if n.running != nil { + if n.server != nil { return ErrNodeRunning } // Otherwise copy and specialize the P2P configuration running := new(p2p.Server) - *running = *n.config + *running = *n.serverConfig - ctx := &ServiceContext{ - dataDir: n.datadir, - EventMux: n.emux, - } services := make(map[string]Service) - for id, constructor := range n.stack { + for _, id := range n.serviceOrder { + constructor := n.serviceIndex[id] + + // Create a new context for the particular service + ctx := &ServiceContext{ + datadir: n.datadir, + services: make(map[string]Service), + EventMux: n.eventmux, + } + for id, s := range services { // copy needed for threaded access + ctx.services[id] = s + } + // Construct and save the service service, err := constructor(ctx) if err != nil { return err @@ -161,10 +181,12 @@ func (n *Node) Start() error { started := []string{} for id, service := range services { // Start the next service, stopping all previous upon failure - if err := service.Start(); err != nil { + if err := service.Start(running); err != nil { for _, id := range started { services[id].Stop() } + running.Stop() + return err } // Mark the service started for potential cleanup @@ -172,7 +194,8 @@ func (n *Node) Start() error { } // Finish initializing the startup n.services = services - n.running = running + n.server = running + n.stop = make(chan struct{}) return nil } @@ -184,7 +207,7 @@ func (n *Node) Stop() error { defer n.lock.Unlock() // Short circuit if the node's not running - if n.running == nil { + if n.server == nil { return ErrNodeStopped } // Otherwise terminate all the services and the P2P server too @@ -196,10 +219,11 @@ func (n *Node) Stop() error { failure.Services[id] = err } } - n.running.Stop() + n.server.Stop() n.services = nil - n.running = nil + n.server = nil + close(n.stop) if len(failure.Services) > 0 { return failure @@ -207,6 +231,19 @@ func (n *Node) Stop() error { return nil } +// Wait blocks the thread until the node is stopped. If the node is not running +// at the time of invocation, the method immediately returns. +func (n *Node) Wait() { + n.lock.RLock() + if n.server == nil { + return + } + stop := n.stop + n.lock.RUnlock() + + <-stop +} + // Restart terminates a running node and boots up a new one in its place. If the // node isn't running, an error is returned. func (n *Node) Restart() error { @@ -226,20 +263,45 @@ func (n *Node) Server() *p2p.Server { n.lock.RLock() defer n.lock.RUnlock() - return n.running + return n.server } -// Service retrieves a currently running services registered under a given id. +// Service retrieves a currently running service registered under a given id. func (n *Node) Service(id string) Service { n.lock.RLock() defer n.lock.RUnlock() - if n.services == nil { + // Short circuit if the node's not running + if n.server == nil { return nil } return n.services[id] } +// SingletonService retrieves a currently running service using a specific type +// implementing the Service interface. This is a utility function for scenarios +// where it is known that only one instance of a given service type is running, +// allowing to access services without needing to know their specific id with +// which they were registered. Note, this method uses reflection, so do not run +// in a tight loop. +func (n *Node) SingletonService(service interface{}) (string, error) { + n.lock.RLock() + defer n.lock.RUnlock() + + // Short circuit if the node's not running + if n.server == nil { + return "", ErrServiceUnknown + } + // Otherwise try to find the service to return + for id, running := range n.services { + if reflect.TypeOf(running) == reflect.ValueOf(service).Elem().Type() { + reflect.ValueOf(service).Elem().Set(reflect.ValueOf(running)) + return id, nil + } + } + return "", ErrServiceUnknown +} + // DataDir retrieves the current datadir used by the protocol stack. func (n *Node) DataDir() string { return n.datadir @@ -248,5 +310,5 @@ func (n *Node) DataDir() string { // EventMux retrieves the event multiplexer used by all the network services in // the current protocol stack. func (n *Node) EventMux() *event.TypeMux { - return n.emux + return n.eventmux } diff --git a/node/node_example_test.go b/node/node_example_test.go index f2bd014b06..898d4f5bc2 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -35,8 +35,8 @@ import ( type SampleService struct{} func (s *SampleService) Protocols() []p2p.Protocol { return nil } -func (s *SampleService) Start() error { fmt.Println("Sample service starting..."); return nil } -func (s *SampleService) Stop() error { fmt.Println("Sample service stopping..."); return nil } +func (s *SampleService) Start(*p2p.Server) error { fmt.Println("Service starting..."); return nil } +func (s *SampleService) Stop() error { fmt.Println("Service stopping..."); return nil } func ExampleUsage() { // Create a network node to run protocols with the default values. The below list @@ -80,8 +80,8 @@ func ExampleUsage() { log.Fatalf("Failed to stop the protocol stack: %v", err) } // Output: - // Sample service starting... - // Sample service stopping... - // Sample service starting... - // Sample service stopping... + // Service starting... + // Service stopping... + // Service starting... + // Service stopping... } diff --git a/node/node_test.go b/node/node_test.go index 7201276b59..1d5570e426 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -102,7 +102,7 @@ func TestNodeUsedDataDir(t *testing.T) { type NoopService struct{} func (s *NoopService) Protocols() []p2p.Protocol { return nil } -func (s *NoopService) Start() error { return nil } +func (s *NoopService) Start(*p2p.Server) error { return nil } func (s *NoopService) Stop() error { return nil } func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil } @@ -148,7 +148,7 @@ type InstrumentedService struct { stop error protocolsHook func() - startHook func() + startHook func(*p2p.Server) stopHook func() } @@ -159,9 +159,9 @@ func (s *InstrumentedService) Protocols() []p2p.Protocol { return s.protocols } -func (s *InstrumentedService) Start() error { +func (s *InstrumentedService) Start(server *p2p.Server) error { if s.startHook != nil { - s.startHook() + s.startHook(server) } return s.start } @@ -189,7 +189,7 @@ func TestServiceLifeCycle(t *testing.T) { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ - startHook: func() { started[id] = true }, + startHook: func(*p2p.Server) { started[id] = true }, stopHook: func() { stopped[id] = true }, }, nil } @@ -241,7 +241,7 @@ func TestServiceRestarts(t *testing.T) { running = false return &InstrumentedService{ - startHook: func() { + startHook: func(*p2p.Server) { if running { panic("already running") } @@ -288,7 +288,7 @@ func TestServiceConstructionAbortion(t *testing.T) { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ - startHook: func() { started[id] = true }, + startHook: func(*p2p.Server) { started[id] = true }, }, nil } if err := stack.Register(id, constructor); err != nil { @@ -334,7 +334,7 @@ func TestServiceStartupAbortion(t *testing.T) { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ - startHook: func() { started[id] = true }, + startHook: func(*p2p.Server) { started[id] = true }, stopHook: func() { stopped[id] = true }, }, nil } @@ -384,7 +384,7 @@ func TestServiceTerminationGuarantee(t *testing.T) { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ - startHook: func() { started[id] = true }, + startHook: func(*p2p.Server) { started[id] = true }, stopHook: func() { stopped[id] = true }, }, nil } @@ -438,6 +438,42 @@ func TestServiceTerminationGuarantee(t *testing.T) { } } +// TestSingletonServiceRetrieval tests that singleton services can be retrieved. +func TestSingletonServiceRetrieval(t *testing.T) { + // Create a simple stack and register two service types + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + if err := stack.Register("noop", func(*ServiceContext) (Service, error) { return new(NoopService), nil }); err != nil { + t.Fatalf("noop service registration failed: %v", err) + } + if err := stack.Register("instrumented", func(*ServiceContext) (Service, error) { return new(InstrumentedService), nil }); err != nil { + t.Fatalf("instrumented service registration failed: %v", err) + } + // Make sure none of the services can be retrieved until started + var noopServ *NoopService + if id, err := stack.SingletonService(&noopServ); id != "" || err != ErrServiceUnknown { + t.Fatalf("noop service retrieval mismatch: have %v/%v, want %v/%v", id, err, "", ErrServiceUnknown) + } + var instServ *InstrumentedService + if id, err := stack.SingletonService(&instServ); id != "" || err != ErrServiceUnknown { + t.Fatalf("instrumented service retrieval mismatch: have %v/%v, want %v/%v", id, err, "", ErrServiceUnknown) + } + // Start the stack and ensure everything is retrievable now + if err := stack.Start(); err != nil { + t.Fatalf("failed to start stack: %v", err) + } + defer stack.Stop() + + if id, err := stack.SingletonService(&noopServ); id != "noop" || err != nil { + t.Fatalf("noop service retrieval mismatch: have %v/%v, want %v/%v", id, err, "noop", nil) + } + if id, err := stack.SingletonService(&instServ); id != "instrumented" || err != nil { + t.Fatalf("instrumented service retrieval mismatch: have %v/%v, want %v/%v", id, err, "instrumented", nil) + } +} + // Tests that all protocols defined by individual services get launched. func TestProtocolGather(t *testing.T) { stack, err := New(testNodeConfig) diff --git a/node/service.go b/node/service.go index ec838dbe8e..28fc34764b 100644 --- a/node/service.go +++ b/node/service.go @@ -18,6 +18,7 @@ package node import ( "path/filepath" + "reflect" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -28,18 +29,39 @@ import ( // the protocol stack, that is passed to all constructors to be optionally used; // as well as utility methods to operate on the service environment. type ServiceContext struct { - dataDir string // Data directory for protocol persistence - EventMux *event.TypeMux // Event multiplexer used for decoupled notifications + datadir string // Data directory for protocol persistence + services map[string]Service // Index of the already constructed services + EventMux *event.TypeMux // Event multiplexer used for decoupled notifications } // Database opens an existing database with the given name (or creates one if no // previous can be found) from within the node's data directory. If the node is // an ephemeral one, a memory database is returned. func (ctx *ServiceContext) Database(name string, cache int) (ethdb.Database, error) { - if ctx.dataDir == "" { + if ctx.datadir == "" { return ethdb.NewMemDatabase() } - return ethdb.NewLDBDatabase(filepath.Join(ctx.dataDir, name), cache) + return ethdb.NewLDBDatabase(filepath.Join(ctx.datadir, name), cache) +} + +// Service retrieves an already constructed service registered under a given id. +func (ctx *ServiceContext) Service(id string) Service { + return ctx.services[id] +} + +// SingletonService retrieves an already constructed service using a specific type +// implementing the Service interface. This is a utility function for scenarios +// where it is known that only one instance of a given service type is running, +// allowing to access services without needing to know their specific id with +// which they were registered. +func (ctx *ServiceContext) SingletonService(service interface{}) (string, error) { + for id, running := range ctx.services { + if reflect.TypeOf(running) == reflect.ValueOf(service).Elem().Type() { + reflect.ValueOf(service).Elem().Set(reflect.ValueOf(running)) + return id, nil + } + } + return "", ErrServiceUnknown } // ServiceConstructor is the function signature of the constructors needed to be @@ -58,8 +80,9 @@ type Service interface { // Protocol retrieves the P2P protocols the service wishes to start. Protocols() []p2p.Protocol - // Start spawns any goroutines required by the service. - Start() error + // Start is called after all services have been constructed and the networking + // layer was also initialized to spawn any goroutines required by the service. + Start(server *p2p.Server) error // Stop terminates all goroutines belonging to the service, blocking until they // are all terminated. diff --git a/node/service_test.go b/node/service_test.go index 50a4f9715b..921a1a0126 100644 --- a/node/service_test.go +++ b/node/service_test.go @@ -17,14 +17,16 @@ package node import ( + "fmt" "io/ioutil" "os" "path/filepath" "testing" ) -// Tests that service context methods work properly. -func TestServiceContext(t *testing.T) { +// Tests that databases are correctly created persistent or ephemeral based on +// the configured service context. +func TestContextDatabases(t *testing.T) { // Create a temporary folder and ensure no database is contained within dir, err := ioutil.TempDir("", "") if err != nil { @@ -36,7 +38,7 @@ func TestServiceContext(t *testing.T) { t.Fatalf("non-created database already exists") } // Request the opening/creation of a database and ensure it persists to disk - ctx := &ServiceContext{dataDir: dir} + ctx := &ServiceContext{datadir: dir} db, err := ctx.Database("persistent", 0) if err != nil { t.Fatalf("failed to open persistent database: %v", err) @@ -47,7 +49,7 @@ func TestServiceContext(t *testing.T) { t.Fatalf("persistent database doesn't exists: %v", err) } // Request th opening/creation of an ephemeral database and ensure it's not persisted - ctx = &ServiceContext{dataDir: ""} + ctx = &ServiceContext{datadir: ""} db, err = ctx.Database("ephemeral", 0) if err != nil { t.Fatalf("failed to open ephemeral database: %v", err) @@ -58,3 +60,47 @@ func TestServiceContext(t *testing.T) { t.Fatalf("ephemeral database exists") } } + +// Tests that already constructed services can be retrieves by later ones. +func TestContextServices(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Define a set of services, constructed before/after a verifier + formers := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"} + latters := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} + + verifier := func(ctx *ServiceContext) (Service, error) { + for i, id := range formers { + if ctx.Service(id) == nil { + return nil, fmt.Errorf("former %d: service not found", i) + } + } + for i, id := range latters { + if ctx.Service(id) != nil { + return nil, fmt.Errorf("latters %d: service found", i) + } + } + return new(NoopService), nil + } + // Register the collection of services + for i, id := range formers { + if err := stack.Register(id, NewNoopService); err != nil { + t.Fatalf("former #%d: failed to register service: %v", i, err) + } + } + if err := stack.Register("verifier", verifier); err != nil { + t.Fatalf("failed to register service verifier: %v", err) + } + for i, id := range latters { + if err := stack.Register(id, NewNoopService); err != nil { + t.Fatalf("latter #%d: failed to register service: %v", i, err) + } + } + // Start the protocol stack and ensure services are constructed in order + if err := stack.Start(); err != nil { + t.Fatalf("failed to start stack: %v", err) + } + defer stack.Stop() +} diff --git a/rpc/api/admin.go b/rpc/api/admin.go index c11662577a..4682062e0a 100644 --- a/rpc/api/admin.go +++ b/rpc/api/admin.go @@ -32,6 +32,8 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/comms" @@ -80,19 +82,24 @@ type adminhandler func(*adminApi, *shared.Request) (interface{}, error) // admin api provider type adminApi struct { xeth *xeth.XEth + stack *node.Node ethereum *eth.Ethereum codec codec.Codec coder codec.ApiCoder } // create a new admin api instance -func NewAdminApi(xeth *xeth.XEth, ethereum *eth.Ethereum, codec codec.Codec) *adminApi { - return &adminApi{ - xeth: xeth, - ethereum: ethereum, - codec: codec, - coder: codec.New(nil), +func NewAdminApi(xeth *xeth.XEth, stack *node.Node, codec codec.Codec) *adminApi { + api := &adminApi{ + xeth: xeth, + stack: stack, + codec: codec, + coder: codec.New(nil), } + if stack != nil { + stack.SingletonService(&api.ethereum) + } + return api } // collection with supported methods @@ -128,24 +135,24 @@ func (self *adminApi) AddPeer(req *shared.Request) (interface{}, error) { if err := self.coder.Decode(req.Params, &args); err != nil { return nil, shared.NewDecodeParamError(err.Error()) } - - err := self.ethereum.AddPeer(args.Url) - if err == nil { - return true, nil + node, err := discover.ParseNode(args.Url) + if err != nil { + return nil, fmt.Errorf("invalid node URL: %v", err) } - return false, err + self.stack.Server().AddPeer(node) + return true, nil } func (self *adminApi) Peers(req *shared.Request) (interface{}, error) { - return self.ethereum.Network().PeersInfo(), nil + return self.stack.Server().PeersInfo(), nil } func (self *adminApi) NodeInfo(req *shared.Request) (interface{}, error) { - return self.ethereum.Network().NodeInfo(), nil + return self.stack.Server().NodeInfo(), nil } func (self *adminApi) DataDir(req *shared.Request) (interface{}, error) { - return self.ethereum.DataDir, nil + return self.stack.DataDir(), nil } func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool { @@ -253,7 +260,7 @@ func (self *adminApi) StartRPC(req *shared.Request) (interface{}, error) { CorsDomain: args.CorsDomain, } - apis, err := ParseApiString(args.Apis, self.codec, self.xeth, self.ethereum) + apis, err := ParseApiString(args.Apis, self.codec, self.xeth, self.stack) if err != nil { return false, err } diff --git a/rpc/api/api_test.go b/rpc/api/api_test.go index 131ef68f81..eb63e81510 100644 --- a/rpc/api/api_test.go +++ b/rpc/api/api_test.go @@ -93,7 +93,7 @@ func TestCompileSolidity(t *testing.T) { expSource := source eth := ð.Ethereum{} - xeth := xeth.NewTest(eth, nil) + xeth := xeth.NewTest(nil, nil) api := NewEthApi(xeth, eth, codec.JSON) var rpcRequest shared.Request diff --git a/rpc/api/utils.go b/rpc/api/utils.go index 8351e88d39..6e372c0613 100644 --- a/rpc/api/utils.go +++ b/rpc/api/utils.go @@ -22,6 +22,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/xeth" @@ -154,7 +155,7 @@ var ( ) // Parse a comma separated API string to individual api's -func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth.Ethereum) ([]shared.EthereumApi, error) { +func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, stack *node.Node) ([]shared.EthereumApi, error) { if len(strings.TrimSpace(apistr)) == 0 { return nil, fmt.Errorf("Empty apistr provided") } @@ -162,10 +163,16 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth. names := strings.Split(apistr, ",") apis := make([]shared.EthereumApi, len(names)) + var eth *eth.Ethereum + if stack != nil { + if _, err := stack.SingletonService(ð); err != nil { + return nil, err + } + } for i, name := range names { switch strings.ToLower(strings.TrimSpace(name)) { case shared.AdminApiName: - apis[i] = NewAdminApi(xeth, eth, codec) + apis[i] = NewAdminApi(xeth, stack, codec) case shared.DebugApiName: apis[i] = NewDebugApi(xeth, eth, codec) case shared.DbApiName: @@ -188,7 +195,6 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth. return nil, fmt.Errorf("Unknown API '%s'", name) } } - return apis, nil } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 6a2eb96a42..473bc3419f 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -36,7 +36,9 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rlp" ) @@ -165,15 +167,6 @@ func runBlockTest(test *BlockTest) error { ks := crypto.NewKeyStorePassphrase(filepath.Join(common.DefaultDataDir(), "keystore"), crypto.StandardScryptN, crypto.StandardScryptP) am := accounts.NewManager(ks) db, _ := ethdb.NewMemDatabase() - cfg := ð.Config{ - DataDir: common.DefaultDataDir(), - Verbosity: 5, - Etherbase: common.Address{}, - AccountManager: am, - NewDB: func(path string) (ethdb.Database, error) { return db, nil }, - } - - cfg.GenesisBlock = test.Genesis // import pre accounts & construct test genesis block & state root _, err := test.InsertPreState(db, am) @@ -181,16 +174,16 @@ func runBlockTest(test *BlockTest) error { return fmt.Errorf("InsertPreState: %v", err) } - ethereum, err := eth.New(cfg) + cfg := ð.Config{ + TestGenesisState: db, + TestGenesisBlock: test.Genesis, + Etherbase: common.Address{}, + AccountManager: am, + } + ethereum, err := eth.New(&node.ServiceContext{EventMux: new(event.TypeMux)}, cfg) if err != nil { return err } - - err = ethereum.Start() - if err != nil { - return err - } - cm := ethereum.BlockChain() validBlocks, err := test.TryBlocksInsert(cm) if err != nil { diff --git a/whisper/peer_test.go b/whisper/peer_test.go index b3d2031c19..636bd8ca1f 100644 --- a/whisper/peer_test.go +++ b/whisper/peer_test.go @@ -37,7 +37,7 @@ func startTestPeer() *testPeer { // Create a whisper client and connect with it to the tester peer client := New() - client.Start() + client.Start(nil) termed := make(chan struct{}) go func() { diff --git a/whisper/whisper.go b/whisper/whisper.go index a341f23e48..7201062b81 100644 --- a/whisper/whisper.go +++ b/whisper/whisper.go @@ -98,9 +98,9 @@ func New() *Whisper { return whisper } -// Protocol returns the whisper sub-protocol handler for this particular client. -func (self *Whisper) Protocol() p2p.Protocol { - return self.protocol +// Protocols returns the whisper sub-protocols ran by this particular client. +func (self *Whisper) Protocols() []p2p.Protocol { + return []p2p.Protocol{self.protocol} } // Version returns the whisper sub-protocols version number. @@ -156,14 +156,20 @@ func (self *Whisper) Send(envelope *Envelope) error { return self.add(envelope) } -func (self *Whisper) Start() { +// Start implements node.Service, starting the background data propagation thread +// of the Whisper protocol. +func (self *Whisper) Start(*p2p.Server) error { glog.V(logger.Info).Infoln("Whisper started") go self.update() + return nil } -func (self *Whisper) Stop() { +// Stop implements node.Service, stopping the background data propagation thread +// of the Whisper protocol. +func (self *Whisper) Stop() error { close(self.quit) glog.V(logger.Info).Infoln("Whisper stopped") + return nil } // Messages retrieves all the currently pooled messages matching a filter id. diff --git a/whisper/whisper_test.go b/whisper/whisper_test.go index b83ce0fe70..9cc235e7a5 100644 --- a/whisper/whisper_test.go +++ b/whisper/whisper_test.go @@ -33,7 +33,7 @@ func startTestCluster(n int) []*Whisper { whispers := make([]*Whisper, n) for i := 0; i < n; i++ { whispers[i] = New() - whispers[i].Start() + whispers[i].Start(nil) } // Wire all the peers to the root one for i := 1; i < n; i++ { diff --git a/xeth/state.go b/xeth/state.go index 981fe63b7e..e67dc4b5f9 100644 --- a/xeth/state.go +++ b/xeth/state.go @@ -19,6 +19,7 @@ package xeth import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/eth" ) type State struct { @@ -45,8 +46,7 @@ func (self *State) SafeGet(addr string) *Object { func (self *State) safeGet(addr string) *state.StateObject { object := self.state.GetStateObject(common.HexToAddress(addr)) if object == nil { - object = state.NewStateObject(common.HexToAddress(addr), self.xeth.backend.ChainDb()) + object = state.NewStateObject(common.HexToAddress(addr), self.xeth.backend.Service("eth").(*eth.Ethereum).ChainDb()) } - return object } diff --git a/xeth/xeth.go b/xeth/xeth.go index 19c42a9a32..6bc94273ee 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -40,7 +40,9 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/whisper" ) var ( @@ -77,7 +79,7 @@ type XEth struct { transactMu sync.Mutex // read-only fields - backend *eth.Ethereum + backend *node.Node frontend Frontend agent *miner.RemoteAgent gpo *eth.GasPriceOracle @@ -86,19 +88,26 @@ type XEth struct { filterManager *filters.FilterSystem } -func NewTest(eth *eth.Ethereum, frontend Frontend) *XEth { - return &XEth{backend: eth, frontend: frontend} +func NewTest(stack *node.Node, frontend Frontend) *XEth { + return &XEth{backend: stack, frontend: frontend} } // New creates an XEth that uses the given frontend. // If a nil Frontend is provided, a default frontend which // confirms all transactions will be used. -func New(ethereum *eth.Ethereum, frontend Frontend) *XEth { +func New(stack *node.Node, frontend Frontend) *XEth { + var ( + ethereum *eth.Ethereum + whisper *whisper.Whisper + ) + stack.SingletonService(ðereum) + stack.SingletonService(&whisper) + xeth := &XEth{ - backend: ethereum, + backend: stack, frontend: frontend, quit: make(chan struct{}), - filterManager: filters.NewFilterSystem(ethereum.EventMux()), + filterManager: filters.NewFilterSystem(stack.EventMux()), logQueue: make(map[int]*logQueue), blockQueue: make(map[int]*hashQueue), transactionQueue: make(map[int]*hashQueue), @@ -106,19 +115,35 @@ func New(ethereum *eth.Ethereum, frontend Frontend) *XEth { agent: miner.NewRemoteAgent(), gpo: eth.NewGasPriceOracle(ethereum), } - if ethereum.Whisper() != nil { - xeth.whisper = NewWhisper(ethereum.Whisper()) + if whisper != nil { + xeth.whisper = NewWhisper(whisper) } ethereum.Miner().Register(xeth.agent) if frontend == nil { xeth.frontend = dummyFrontend{} } - state, _ := xeth.backend.BlockChain().State() + state, _ := ethereum.BlockChain().State() xeth.state = NewState(xeth, state) go xeth.start() return xeth } +func (self *XEth) EthereumService() *eth.Ethereum { + var ethereum *eth.Ethereum + if _, err := self.backend.SingletonService(ðereum); err != nil { + return nil + } + return ethereum +} + +func (self *XEth) WhisperService() *whisper.Whisper { + var whisper *whisper.Whisper + if _, err := self.backend.SingletonService(&whisper); err != nil { + return nil + } + return whisper +} + func (self *XEth) start() { timer := time.NewTicker(2 * time.Second) defer timer.Stop() @@ -172,7 +197,7 @@ done: func (self *XEth) Stop() { close(self.quit) self.filterManager.Stop() - self.backend.Miner().Unregister(self.agent) + self.EthereumService().Miner().Unregister(self.agent) } func cAddress(a []string) []common.Address { @@ -207,21 +232,20 @@ func (self *XEth) AtStateNum(num int64) *XEth { var err error switch num { case -2: - st = self.backend.Miner().PendingState().Copy() + st = self.EthereumService().Miner().PendingState().Copy() default: if block := self.getBlockByHeight(num); block != nil { - st, err = state.New(block.Root(), self.backend.ChainDb()) + st, err = state.New(block.Root(), self.EthereumService().ChainDb()) if err != nil { return nil } } else { - st, err = state.New(self.backend.BlockChain().GetBlockByNumber(0).Root(), self.backend.ChainDb()) + st, err = state.New(self.EthereumService().BlockChain().GetBlockByNumber(0).Root(), self.EthereumService().ChainDb()) if err != nil { return nil } } } - return self.WithState(st) } @@ -270,7 +294,7 @@ func (self *XEth) UpdateState() (wait chan *big.Int) { wait <- n n = nil } - statedb, err := state.New(event.Block.Root(), self.backend.ChainDb()) + statedb, err := state.New(event.Block.Root(), self.EthereumService().ChainDb()) if err != nil { glog.V(logger.Error).Infoln("Could not create new state: %v", err) return @@ -294,7 +318,7 @@ func (self *XEth) getBlockByHeight(height int64) *types.Block { switch height { case -2: - return self.backend.Miner().PendingBlock() + return self.EthereumService().Miner().PendingBlock() case -1: return self.CurrentBlock() default: @@ -305,28 +329,29 @@ func (self *XEth) getBlockByHeight(height int64) *types.Block { num = uint64(height) } - return self.backend.BlockChain().GetBlockByNumber(num) + return self.EthereumService().BlockChain().GetBlockByNumber(num) } func (self *XEth) BlockByHash(strHash string) *Block { hash := common.HexToHash(strHash) - block := self.backend.BlockChain().GetBlock(hash) + block := self.EthereumService().BlockChain().GetBlock(hash) return NewBlock(block) } func (self *XEth) EthBlockByHash(strHash string) *types.Block { hash := common.HexToHash(strHash) - block := self.backend.BlockChain().GetBlock(hash) + block := self.EthereumService().BlockChain().GetBlock(hash) return block } func (self *XEth) EthTransactionByHash(hash string) (*types.Transaction, common.Hash, uint64, uint64) { - if tx, hash, number, index := core.GetTransaction(self.backend.ChainDb(), common.HexToHash(hash)); tx != nil { + ethereum := self.EthereumService() + if tx, hash, number, index := core.GetTransaction(ethereum.ChainDb(), common.HexToHash(hash)); tx != nil { return tx, hash, number, index } - return self.backend.TxPool().GetTransaction(common.HexToHash(hash)), common.Hash{}, 0, 0 + return ethereum.TxPool().GetTransaction(common.HexToHash(hash)), common.Hash{}, 0, 0 } func (self *XEth) BlockByNumber(num int64) *Block { @@ -338,23 +363,23 @@ func (self *XEth) EthBlockByNumber(num int64) *types.Block { } func (self *XEth) Td(hash common.Hash) *big.Int { - return self.backend.BlockChain().GetTd(hash) + return self.EthereumService().BlockChain().GetTd(hash) } func (self *XEth) CurrentBlock() *types.Block { - return self.backend.BlockChain().CurrentBlock() + return self.EthereumService().BlockChain().CurrentBlock() } func (self *XEth) GetBlockReceipts(bhash common.Hash) types.Receipts { - return core.GetBlockReceipts(self.backend.ChainDb(), bhash) + return core.GetBlockReceipts(self.EthereumService().ChainDb(), bhash) } func (self *XEth) GetTxReceipt(txhash common.Hash) *types.Receipt { - return core.GetReceipt(self.backend.ChainDb(), txhash) + return core.GetReceipt(self.EthereumService().ChainDb(), txhash) } func (self *XEth) GasLimit() *big.Int { - return self.backend.BlockChain().GasLimit() + return self.EthereumService().BlockChain().GasLimit() } func (self *XEth) Block(v interface{}) *Block { @@ -371,7 +396,7 @@ func (self *XEth) Block(v interface{}) *Block { func (self *XEth) Accounts() []string { // TODO: check err? - accounts, _ := self.backend.AccountManager().Accounts() + accounts, _ := self.EthereumService().AccountManager().Accounts() accountAddresses := make([]string, len(accounts)) for i, ac := range accounts { accountAddresses[i] = ac.Address.Hex() @@ -382,73 +407,73 @@ func (self *XEth) Accounts() []string { // accessor for solidity compiler. // memoized if available, retried on-demand if not func (self *XEth) Solc() (*compiler.Solidity, error) { - return self.backend.Solc() + return self.EthereumService().Solc() } // set in js console via admin interface or wrapper from cli flags func (self *XEth) SetSolc(solcPath string) (*compiler.Solidity, error) { - self.backend.SetSolc(solcPath) + self.EthereumService().SetSolc(solcPath) return self.Solc() } // store DApp value in extra database func (self *XEth) DbPut(key, val []byte) bool { - self.backend.DappDb().Put(append(dappStorePre, key...), val) + self.EthereumService().DappDb().Put(append(dappStorePre, key...), val) return true } // retrieve DApp value from extra database func (self *XEth) DbGet(key []byte) ([]byte, error) { - val, err := self.backend.DappDb().Get(append(dappStorePre, key...)) + val, err := self.EthereumService().DappDb().Get(append(dappStorePre, key...)) return val, err } func (self *XEth) PeerCount() int { - return self.backend.PeerCount() + return self.backend.Server().PeerCount() } func (self *XEth) IsMining() bool { - return self.backend.IsMining() + return self.EthereumService().IsMining() } func (self *XEth) HashRate() int64 { - return self.backend.Miner().HashRate() + return self.EthereumService().Miner().HashRate() } func (self *XEth) EthVersion() string { - return fmt.Sprintf("%d", self.backend.EthVersion()) + return fmt.Sprintf("%d", self.EthereumService().EthVersion()) } func (self *XEth) NetworkVersion() string { - return fmt.Sprintf("%d", self.backend.NetVersion()) + return fmt.Sprintf("%d", self.EthereumService().NetVersion()) } func (self *XEth) WhisperVersion() string { - return fmt.Sprintf("%d", self.backend.ShhVersion()) + return fmt.Sprintf("%d", self.WhisperService().Version()) } func (self *XEth) ClientVersion() string { - return self.backend.ClientVersion() + return self.backend.Server().Name } func (self *XEth) SetMining(shouldmine bool, threads int) bool { - ismining := self.backend.IsMining() + ismining := self.EthereumService().IsMining() if shouldmine && !ismining { - err := self.backend.StartMining(threads, "") + err := self.EthereumService().StartMining(threads, "") return err == nil } if ismining && !shouldmine { - self.backend.StopMining() + self.EthereumService().StopMining() } - return self.backend.IsMining() + return self.EthereumService().IsMining() } func (self *XEth) IsListening() bool { - return self.backend.IsListening() + return true } func (self *XEth) Coinbase() string { - eb, err := self.backend.Etherbase() + eb, err := self.EthereumService().Etherbase() if err != nil { return "0x0" } @@ -514,7 +539,7 @@ func (self *XEth) NewLogFilter(earliest, latest int64, skip, max int, address [] self.logMu.Lock() defer self.logMu.Unlock() - filter := filters.New(self.backend.ChainDb()) + filter := filters.New(self.EthereumService().ChainDb()) id := self.filterManager.Add(filter) self.logQueue[id] = &logQueue{timeout: time.Now()} @@ -538,7 +563,7 @@ func (self *XEth) NewTransactionFilter() int { self.transactionMu.Lock() defer self.transactionMu.Unlock() - filter := filters.New(self.backend.ChainDb()) + filter := filters.New(self.EthereumService().ChainDb()) id := self.filterManager.Add(filter) self.transactionQueue[id] = &hashQueue{timeout: time.Now()} @@ -557,7 +582,7 @@ func (self *XEth) NewBlockFilter() int { self.blockMu.Lock() defer self.blockMu.Unlock() - filter := filters.New(self.backend.ChainDb()) + filter := filters.New(self.EthereumService().ChainDb()) id := self.filterManager.Add(filter) self.blockQueue[id] = &hashQueue{timeout: time.Now()} @@ -624,7 +649,7 @@ func (self *XEth) Logs(id int) vm.Logs { } func (self *XEth) AllLogs(earliest, latest int64, skip, max int, address []string, topics [][]string) vm.Logs { - filter := filters.New(self.backend.ChainDb()) + filter := filters.New(self.EthereumService().ChainDb()) filter.SetBeginBlock(earliest) filter.SetEndBlock(latest) filter.SetAddresses(cAddress(address)) @@ -775,7 +800,7 @@ func (self *XEth) PushTx(encodedTx string) (string, error) { return "", err } - err = self.backend.TxPool().Add(tx) + err = self.EthereumService().TxPool().Add(tx) if err != nil { return "", err } @@ -799,7 +824,7 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st statedb := self.State().State().Copy() var from *state.StateObject if len(fromStr) == 0 { - accounts, err := self.backend.AccountManager().Accounts() + accounts, err := self.EthereumService().AccountManager().Accounts() if err != nil || len(accounts) == 0 { from = statedb.GetOrNewStateObject(common.Address{}) } else { @@ -832,7 +857,7 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st } header := self.CurrentBlock().Header() - vmenv := core.NewEnv(statedb, self.backend.BlockChain(), msg, header) + vmenv := core.NewEnv(statedb, self.EthereumService().BlockChain(), msg, header) gp := new(core.GasPool).AddGas(common.MaxBig) res, gas, err := core.ApplyMessage(vmenv, msg, gp) return common.ToHex(res), gas.String(), err @@ -843,7 +868,7 @@ func (self *XEth) ConfirmTransaction(tx string) bool { } func (self *XEth) doSign(from common.Address, hash common.Hash, didUnlock bool) ([]byte, error) { - sig, err := self.backend.AccountManager().Sign(accounts.Account{Address: from}, hash.Bytes()) + sig, err := self.EthereumService().AccountManager().Sign(accounts.Account{Address: from}, hash.Bytes()) if err == accounts.ErrLocked { if didUnlock { return nil, fmt.Errorf("signer account still locked after successful unlock") @@ -915,7 +940,7 @@ func (self *XEth) SignTransaction(fromStr, toStr, nonceStr, valueStr, gasStr, ga if len(nonceStr) != 0 { nonce = common.Big(nonceStr).Uint64() } else { - state := self.backend.TxPool().State() + state := self.EthereumService().TxPool().State() nonce = state.GetNonce(from) } var tx *types.Transaction @@ -1003,7 +1028,7 @@ func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceS if len(nonceStr) != 0 { nonce = common.Big(nonceStr).Uint64() } else { - state := self.backend.TxPool().State() + state := self.EthereumService().TxPool().State() nonce = state.GetNonce(from) } var tx *types.Transaction @@ -1017,7 +1042,7 @@ func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceS if err != nil { return "", err } - if err = self.backend.TxPool().Add(signed); err != nil { + if err = self.EthereumService().TxPool().Add(signed); err != nil { return "", err } From 3e1000fda3424d880bc43ebbb16d8a33447d4182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 26 Nov 2015 18:35:44 +0200 Subject: [PATCH 10/78] cmd, eth, node, rpc, xeth: use single-instance services --- cmd/geth/js.go | 4 +- cmd/geth/js_test.go | 14 +-- cmd/geth/main.go | 2 +- cmd/gethrpctest/main.go | 6 +- cmd/utils/flags.go | 10 +- eth/backend.go | 4 +- node/errors.go | 20 +++- node/node.go | 126 +++++++-------------- node/node_example_test.go | 2 +- node/node_test.go | 230 ++++++++++++++++---------------------- node/service.go | 38 +++---- node/service_test.go | 37 +++--- node/utils_test.go | 117 +++++++++++++++++++ rpc/api/admin.go | 2 +- rpc/api/utils.go | 2 +- xeth/state.go | 3 +- xeth/xeth.go | 8 +- 17 files changed, 330 insertions(+), 295 deletions(-) create mode 100644 node/utils_test.go diff --git a/cmd/geth/js.go b/cmd/geth/js.go index a0e8bdb21c..f1845d94f5 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -345,7 +345,7 @@ func (self *jsre) AskPassword() (string, bool) { func (self *jsre) ConfirmTransaction(tx string) bool { // Retrieve the Ethereum instance from the node var ethereum *eth.Ethereum - if _, err := self.stack.SingletonService(ðereum); err != nil { + if err := self.stack.Service(ðereum); err != nil { return false } // If natspec is enabled, ask for permission @@ -367,7 +367,7 @@ func (self *jsre) UnlockAccount(addr []byte) bool { } // TODO: allow retry var ethereum *eth.Ethereum - if _, err := self.stack.SingletonService(ðereum); err != nil { + if err := self.stack.Service(ðereum); err != nil { return false } if err := ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil { diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index ed4d04b483..a0f3f2fb76 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -68,7 +68,7 @@ type testjethre struct { func (self *testjethre) UnlockAccount(acc []byte) bool { var ethereum *eth.Ethereum - self.stack.SingletonService(ðereum) + self.stack.Service(ðereum) err := ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "") if err != nil { @@ -79,7 +79,7 @@ func (self *testjethre) UnlockAccount(acc []byte) bool { func (self *testjethre) ConfirmTransaction(tx string) bool { var ethereum *eth.Ethereum - self.stack.SingletonService(ðereum) + self.stack.Service(ðereum) if ethereum.NatSpec { self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client) @@ -118,7 +118,7 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod if config != nil { config(ethConf) } - if err := stack.Register("ethereum", func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { t.Fatalf("failed to register ethereum protocol: %v", err) } // Initialize all the keys for testing @@ -138,7 +138,7 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod t.Fatalf("failed to start test stack: %v", err) } var ethereum *eth.Ethereum - stack.SingletonService(ðereum) + stack.Service(ðereum) assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") client := comms.NewInProcClient(codec.JSON) @@ -202,7 +202,7 @@ func TestBlockChain(t *testing.T) { tmpfileq := strconv.Quote(tmpfile) var ethereum *eth.Ethereum - node.SingletonService(ðereum) + node.Service(ðereum) ethereum.BlockChain().Reset() checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`) @@ -436,7 +436,7 @@ multiply7 = Multiply7.at(contractaddress); func pendingTransactions(repl *testjethre, t *testing.T) (txc int64, err error) { var ethereum *eth.Ethereum - repl.stack.SingletonService(ðereum) + repl.stack.Service(ðereum) txs := ethereum.TxPool().GetTransactions() return int64(len(txs)), nil @@ -464,7 +464,7 @@ func processTxs(repl *testjethre, t *testing.T, expTxc int) bool { return false } var ethereum *eth.Ethereum - repl.stack.SingletonService(ðereum) + repl.stack.Service(ðereum) err = ethereum.StartMining(runtime.NumCPU(), "") if err != nil { diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 6fac4f4582..3a54718456 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -489,7 +489,7 @@ func startNode(ctx *cli.Context, stack *node.Node) { // Unlock any account specifically requested var ethereum *eth.Ethereum - if _, err := stack.SingletonService(ðereum); err != nil { + if err := stack.Service(ðereum); err != nil { utils.Fatalf("ethereum service not running: %v", err) } accman := ethereum.AccountManager() diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index cb4c7aece2..7130980ac3 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -126,11 +126,11 @@ func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node TestGenesisBlock: test.Genesis, AccountManager: accman, } - if err := stack.Register("ethereum", func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { return nil, err } // Initialize and register the Whisper protocol - if err := stack.Register("whisper", func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { + if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { return nil, err } return stack, nil @@ -140,7 +140,7 @@ func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node // stack to ensure basic checks pass before running RPC tests. func RunTest(stack *node.Node, test *tests.BlockTest) error { var ethereum *eth.Ethereum - stack.SingletonService(ðereum) + stack.Service(ðereum) blockchain := ethereum.BlockChain() // Process the blocks and verify the imported headers diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 30570d930b..53126f9e5d 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -686,13 +686,13 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. if err != nil { Fatalf("Failed to create the protocol stack: %v", err) } - if err := stack.Register("eth", func(ctx *node.ServiceContext) (node.Service, error) { + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } if shhEnable { - if err := stack.Register("shh", func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { + if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { Fatalf("Failed to register the Whisper service: %v", err) } } @@ -786,7 +786,11 @@ func StartIPC(stack *node.Node, ctx *cli.Context) error { } initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) { - fe := useragent.NewRemoteFrontend(conn, stack.Service("eth").(*eth.Ethereum).AccountManager()) + var ethereum *eth.Ethereum + if err := stack.Service(ðereum); err != nil { + return nil, nil, err + } + fe := useragent.NewRemoteFrontend(conn, ethereum.AccountManager()) xeth := xeth.New(stack, fe) apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, stack) if err != nil { diff --git a/eth/backend.go b/eth/backend.go index 5a8cf6a734..0369f6afd2 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -135,7 +135,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ethdb.OpenFileLimit = 128 / (dbCount + 1) // Open the chain database and perform any upgrades needed - chainDb, err := ctx.Database("chaindata", config.DatabaseCache) + chainDb, err := ctx.OpenDatabase("chaindata", config.DatabaseCache) if err != nil { return nil, err } @@ -149,7 +149,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { return nil, err } - dappDb, err := ctx.Database("dapp", config.DatabaseCache) + dappDb, err := ctx.OpenDatabase("dapp", config.DatabaseCache) if err != nil { return nil, err } diff --git a/node/errors.go b/node/errors.go index f8eece06d1..bd5ddeb5de 100644 --- a/node/errors.go +++ b/node/errors.go @@ -16,13 +16,27 @@ package node -import "fmt" +import ( + "fmt" + "reflect" +) -// StopError is returned if a node fails to stop either any of its registered +// DuplicateServiceError is returned during Node startup if a registered service +// constructor returns a service of the same type that was already started. +type DuplicateServiceError struct { + Kind reflect.Type +} + +// Error generates a textual representation of the duplicate service error. +func (e *DuplicateServiceError) Error() string { + return fmt.Sprintf("duplicate service: %v", e.Kind) +} + +// StopError is returned if a Node fails to stop either any of its registered // services or itself. type StopError struct { Server error - Services map[string]error + Services map[reflect.Type]error } // Error generates a textual representation of the stop error. diff --git a/node/node.go b/node/node.go index 023f774034..5566bc44bf 100644 --- a/node/node.go +++ b/node/node.go @@ -30,16 +30,16 @@ import ( ) var ( - ErrDatadirUsed = errors.New("datadir already used") - ErrNodeStopped = errors.New("node not started") - ErrNodeRunning = errors.New("node already running") - ErrServiceUnknown = errors.New("service not registered") - ErrServiceRegistered = errors.New("service already registered") + ErrDatadirUsed = errors.New("datadir already used") + ErrNodeStopped = errors.New("node not started") + ErrNodeRunning = errors.New("node already running") + ErrServiceUnknown = errors.New("unknown service") datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} ) -// Node represents a P2P node into which arbitrary services might be registered. +// Node represents a P2P node into which arbitrary (uniquely typed) services might +// be registered. type Node struct { datadir string // Path to the currently used data directory eventmux *event.TypeMux // Event multiplexer used between the services of a stack @@ -47,9 +47,8 @@ type Node struct { serverConfig *p2p.Server // Configuration of the underlying P2P networking layer server *p2p.Server // Currently running P2P networking layer - serviceIndex map[string]ServiceConstructor // Set of services currently registered in the node - serviceOrder []string // Service construction order to handle dependencies - services map[string]Service // Currently running services + serviceFuncs []ServiceConstructor // Service constructors (in dependency order) + services map[reflect.Type]Service // Currently running services stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex @@ -85,52 +84,21 @@ func New(conf *Config) (*Node, error) { MaxPeers: conf.MaxPeers, MaxPendingPeers: conf.MaxPendingPeers, }, - serviceIndex: make(map[string]ServiceConstructor), - serviceOrder: []string{}, + serviceFuncs: []ServiceConstructor{}, eventmux: new(event.TypeMux), }, nil } -// Register injects a new service into the node's stack. -func (n *Node) Register(id string, constructor ServiceConstructor) error { +// Register injects a new service into the node's stack. The service created by +// the passed constructor must be unique in its type with regard to sibling ones. +func (n *Node) Register(constructor ServiceConstructor) error { n.lock.Lock() defer n.lock.Unlock() - // Short circuit if the node is running or if the id is taken if n.server != nil { return ErrNodeRunning } - if _, ok := n.serviceIndex[id]; ok { - return ErrServiceRegistered - } - // Otherwise register the service and return - n.serviceOrder = append(n.serviceOrder, id) - n.serviceIndex[id] = constructor - - return nil -} - -// Unregister removes a service from a node's stack. If the node is currently -// running, an error will be returned. -func (n *Node) Unregister(id string) error { - n.lock.Lock() - defer n.lock.Unlock() - - // Short circuit if the node is running, or if the service is unknown - if n.server != nil { - return ErrNodeRunning - } - if _, ok := n.serviceIndex[id]; !ok { - return ErrServiceUnknown - } - // Otherwise drop the service and return - delete(n.serviceIndex, id) - for i, service := range n.serviceOrder { - if service == id { - n.serviceOrder = append(n.serviceOrder[:i], n.serviceOrder[i+1:]...) - break - } - } + n.serviceFuncs = append(n.serviceFuncs, constructor) return nil } @@ -147,25 +115,27 @@ func (n *Node) Start() error { running := new(p2p.Server) *running = *n.serverConfig - services := make(map[string]Service) - for _, id := range n.serviceOrder { - constructor := n.serviceIndex[id] - + services := make(map[reflect.Type]Service) + for _, constructor := range n.serviceFuncs { // Create a new context for the particular service ctx := &ServiceContext{ datadir: n.datadir, - services: make(map[string]Service), + services: make(map[reflect.Type]Service), EventMux: n.eventmux, } - for id, s := range services { // copy needed for threaded access - ctx.services[id] = s + for kind, s := range services { // copy needed for threaded access + ctx.services[kind] = s } // Construct and save the service service, err := constructor(ctx) if err != nil { return err } - services[id] = service + kind := reflect.TypeOf(service) + if _, exists := services[kind]; exists { + return &DuplicateServiceError{Kind: kind} + } + services[kind] = service } // Gather the protocols and start the freshly assembled P2P server for _, service := range services { @@ -178,19 +148,19 @@ func (n *Node) Start() error { return err } // Start each of the services - started := []string{} - for id, service := range services { + started := []reflect.Type{} + for kind, service := range services { // Start the next service, stopping all previous upon failure if err := service.Start(running); err != nil { - for _, id := range started { - services[id].Stop() + for _, kind := range started { + services[kind].Stop() } running.Stop() return err } // Mark the service started for potential cleanup - started = append(started, id) + started = append(started, kind) } // Finish initializing the startup n.services = services @@ -212,11 +182,11 @@ func (n *Node) Stop() error { } // Otherwise terminate all the services and the P2P server too failure := &StopError{ - Services: make(map[string]error), + Services: make(map[reflect.Type]error), } - for id, service := range n.services { + for kind, service := range n.services { if err := service.Stop(); err != nil { - failure.Services[id] = err + failure.Services[kind] = err } } n.server.Stop() @@ -266,40 +236,22 @@ func (n *Node) Server() *p2p.Server { return n.server } -// Service retrieves a currently running service registered under a given id. -func (n *Node) Service(id string) Service { +// Service retrieves a currently running service registered of a specific type. +func (n *Node) Service(service interface{}) error { n.lock.RLock() defer n.lock.RUnlock() // Short circuit if the node's not running if n.server == nil { - return nil - } - return n.services[id] -} - -// SingletonService retrieves a currently running service using a specific type -// implementing the Service interface. This is a utility function for scenarios -// where it is known that only one instance of a given service type is running, -// allowing to access services without needing to know their specific id with -// which they were registered. Note, this method uses reflection, so do not run -// in a tight loop. -func (n *Node) SingletonService(service interface{}) (string, error) { - n.lock.RLock() - defer n.lock.RUnlock() - - // Short circuit if the node's not running - if n.server == nil { - return "", ErrServiceUnknown + return ErrNodeStopped } // Otherwise try to find the service to return - for id, running := range n.services { - if reflect.TypeOf(running) == reflect.ValueOf(service).Elem().Type() { - reflect.ValueOf(service).Elem().Set(reflect.ValueOf(running)) - return id, nil - } + element := reflect.ValueOf(service).Elem() + if running, ok := n.services[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil } - return "", ErrServiceUnknown + return ErrServiceUnknown } // DataDir retrieves the current datadir used by the protocol stack. diff --git a/node/node_example_test.go b/node/node_example_test.go index 898d4f5bc2..2f9b49a56d 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -66,7 +66,7 @@ func ExampleUsage() { constructor := func(context *node.ServiceContext) (node.Service, error) { return new(SampleService), nil } - if err := stack.Register("my sample service", constructor); err != nil { + if err := stack.Register(constructor); err != nil { log.Fatalf("Failed to register service: %v", err) } // Boot up the entire protocol stack, do a restart and terminate diff --git a/node/node_test.go b/node/node_test.go index 1d5570e426..ef096fc917 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -20,6 +20,7 @@ import ( "errors" "io/ioutil" "os" + "reflect" "testing" "github.com/ethereum/go-ethereum/crypto" @@ -98,79 +99,36 @@ func TestNodeUsedDataDir(t *testing.T) { } } -// NoopService is a trivial implementation of the Service interface. -type NoopService struct{} - -func (s *NoopService) Protocols() []p2p.Protocol { return nil } -func (s *NoopService) Start(*p2p.Server) error { return nil } -func (s *NoopService) Stop() error { return nil } - -func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil } - -// Tests whether services can be registered and unregistered. +// Tests whether services can be registered and duplicates caught. func TestServiceRegistry(t *testing.T) { stack, err := New(testNodeConfig) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } - // Create a batch of dummy services and ensure they don't exist - ids := []string{"A", "B", "C"} - for i, id := range ids { - if err := stack.Unregister(id); err != ErrServiceUnknown { - t.Fatalf("service %d: pre-unregistration failure mismatch: have %v, want %v", i, err, ErrServiceUnknown) + // Register a batch of unique services and ensure they start successfully + services := []ServiceConstructor{NewNoopServiceA, NewNoopServiceB, NewNoopServiceC} + for i, constructor := range services { + if err := stack.Register(constructor); err != nil { + t.Fatalf("service #%d: registration failed: %v", i, err) } } - // Register the services, checking that the operation succeeds only once - for i, id := range ids { - if err := stack.Register(id, NewNoopService); err != nil { - t.Fatalf("service %d: registration failed: %v", i, err) - } - if err := stack.Register(id, NewNoopService); err != ErrServiceRegistered { - t.Fatalf("service %d: registration failure mismatch: have %v, want %v", i, err, ErrServiceRegistered) + if err := stack.Start(); err != nil { + t.Fatalf("failed to start original service stack: %v", err) + } + if err := stack.Stop(); err != nil { + t.Fatalf("failed to stop original service stack: %v", err) + } + // Duplicate one of the services and retry starting the node + if err := stack.Register(NewNoopServiceB); err != nil { + t.Fatalf("duplicate registration failed: %v", err) + } + if err := stack.Start(); err == nil { + t.Fatalf("duplicate service started") + } else { + if _, ok := err.(*DuplicateServiceError); !ok { + t.Fatalf("duplicate error mismatch: have %v, want %v", err, DuplicateServiceError{}) } } - // Unregister the services, checking that the operation succeeds only once - for i, id := range ids { - if err := stack.Unregister(id); err != nil { - t.Fatalf("service %d: unregistration failed: %v", i, err) - } - if err := stack.Unregister(id); err != ErrServiceUnknown { - t.Fatalf("service %d: unregistration failure mismatch: have %v, want %v", i, err, ErrServiceUnknown) - } - } -} - -// InstrumentedService is an implementation of Service for which all interface -// methods can be instrumented both return value as well as event hook wise. -type InstrumentedService struct { - protocols []p2p.Protocol - start error - stop error - - protocolsHook func() - startHook func(*p2p.Server) - stopHook func() -} - -func (s *InstrumentedService) Protocols() []p2p.Protocol { - if s.protocolsHook != nil { - s.protocolsHook() - } - return s.protocols -} - -func (s *InstrumentedService) Start(server *p2p.Server) error { - if s.startHook != nil { - s.startHook(server) - } - return s.start -} - -func (s *InstrumentedService) Stop() error { - if s.stopHook != nil { - s.stopHook() - } - return s.stop } // Tests that registered services get started and stopped correctly. @@ -180,12 +138,15 @@ func TestServiceLifeCycle(t *testing.T) { t.Fatalf("failed to create protocol stack: %v", err) } // Register a batch of life-cycle instrumented services - ids := []string{"A", "B", "C"} - + services := map[string]InstrumentingWrapper{ + "A": InstrumentedServiceMakerA, + "B": InstrumentedServiceMakerB, + "C": InstrumentedServiceMakerC, + } started := make(map[string]bool) stopped := make(map[string]bool) - for i, id := range ids { + for id, maker := range services { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ @@ -193,35 +154,29 @@ func TestServiceLifeCycle(t *testing.T) { stopHook: func() { stopped[id] = true }, }, nil } - if err := stack.Register(id, constructor); err != nil { - t.Fatalf("service %d: registration failed: %v", i, err) + if err := stack.Register(maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) } } // Start the node and check that all services are running if err := stack.Start(); err != nil { t.Fatalf("failed to start protocol stack: %v", err) } - for i, id := range ids { + for id, _ := range services { if !started[id] { - t.Fatalf("service %d: freshly started service not running", i) + t.Fatalf("service %s: freshly started service not running", id) } if stopped[id] { - t.Fatalf("service %d: freshly started service already stopped", i) - } - if stack.Service(id) == nil { - t.Fatalf("service %d: freshly started service unaccessible", i) + t.Fatalf("service %s: freshly started service already stopped", id) } } // Stop the node and check that all services have been stopped if err := stack.Stop(); err != nil { t.Fatalf("failed to stop protocol stack: %v", err) } - for i, id := range ids { + for id, _ := range services { if !stopped[id] { - t.Fatalf("service %d: freshly terminated service still running", i) - } - if service := stack.Service(id); service != nil { - t.Fatalf("service %d: freshly terminated service still accessible: %v", i, service) + t.Fatalf("service %s: freshly terminated service still running", id) } } } @@ -251,7 +206,7 @@ func TestServiceRestarts(t *testing.T) { }, nil } // Register the service and start the protocol stack - if err := stack.Register("service", constructor); err != nil { + if err := stack.Register(constructor); err != nil { t.Fatalf("failed to register the service: %v", err) } if err := stack.Start(); err != nil { @@ -281,18 +236,21 @@ func TestServiceConstructionAbortion(t *testing.T) { t.Fatalf("failed to create protocol stack: %v", err) } // Define a batch of good services - ids := []string{"A", "B", "C", "D", "E", "F"} - + services := map[string]InstrumentingWrapper{ + "A": InstrumentedServiceMakerA, + "B": InstrumentedServiceMakerB, + "C": InstrumentedServiceMakerC, + } started := make(map[string]bool) - for i, id := range ids { + for id, maker := range services { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ startHook: func(*p2p.Server) { started[id] = true }, }, nil } - if err := stack.Register(id, constructor); err != nil { - t.Fatalf("service %d: registration failed: %v", i, err) + if err := stack.Register(maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) } } // Register a service that fails to construct itself @@ -300,7 +258,7 @@ func TestServiceConstructionAbortion(t *testing.T) { failer := func(*ServiceContext) (Service, error) { return nil, failure } - if err := stack.Register("failer", failer); err != nil { + if err := stack.Register(failer); err != nil { t.Fatalf("failer registration failed: %v", err) } // Start the protocol stack and ensure none of the services get started @@ -308,9 +266,9 @@ func TestServiceConstructionAbortion(t *testing.T) { if err := stack.Start(); err != failure { t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) } - for i, id := range ids { + for id, _ := range services { if started[id] { - t.Fatalf("service %d: started should not have", i) + t.Fatalf("service %s: started should not have", id) } delete(started, id) } @@ -325,12 +283,15 @@ func TestServiceStartupAbortion(t *testing.T) { t.Fatalf("failed to create protocol stack: %v", err) } // Register a batch of good services - ids := []string{"A", "B", "C", "D", "E", "F"} - + services := map[string]InstrumentingWrapper{ + "A": InstrumentedServiceMakerA, + "B": InstrumentedServiceMakerB, + "C": InstrumentedServiceMakerC, + } started := make(map[string]bool) stopped := make(map[string]bool) - for i, id := range ids { + for id, maker := range services { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ @@ -338,8 +299,8 @@ func TestServiceStartupAbortion(t *testing.T) { stopHook: func() { stopped[id] = true }, }, nil } - if err := stack.Register(id, constructor); err != nil { - t.Fatalf("service %d: registration failed: %v", i, err) + if err := stack.Register(maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) } } // Register a service that fails to start @@ -349,7 +310,7 @@ func TestServiceStartupAbortion(t *testing.T) { start: failure, }, nil } - if err := stack.Register("failer", failer); err != nil { + if err := stack.Register(failer); err != nil { t.Fatalf("failer registration failed: %v", err) } // Start the protocol stack and ensure all started services stop @@ -357,9 +318,9 @@ func TestServiceStartupAbortion(t *testing.T) { if err := stack.Start(); err != failure { t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) } - for i, id := range ids { + for id, _ := range services { if started[id] && !stopped[id] { - t.Fatalf("service %d: started but not stopped", i) + t.Fatalf("service %s: started but not stopped", id) } delete(started, id) delete(stopped, id) @@ -375,12 +336,15 @@ func TestServiceTerminationGuarantee(t *testing.T) { t.Fatalf("failed to create protocol stack: %v", err) } // Register a batch of good services - ids := []string{"A", "B", "C", "D", "E", "F"} - + services := map[string]InstrumentingWrapper{ + "A": InstrumentedServiceMakerA, + "B": InstrumentedServiceMakerB, + "C": InstrumentedServiceMakerC, + } started := make(map[string]bool) stopped := make(map[string]bool) - for i, id := range ids { + for id, maker := range services { id := id // Closure for the constructor constructor := func(*ServiceContext) (Service, error) { return &InstrumentedService{ @@ -388,8 +352,8 @@ func TestServiceTerminationGuarantee(t *testing.T) { stopHook: func() { stopped[id] = true }, }, nil } - if err := stack.Register(id, constructor); err != nil { - t.Fatalf("service %d: registration failed: %v", i, err) + if err := stack.Register(maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) } } // Register a service that fails to shot down cleanly @@ -399,7 +363,7 @@ func TestServiceTerminationGuarantee(t *testing.T) { stop: failure, }, nil } - if err := stack.Register("failer", failer); err != nil { + if err := stack.Register(failer); err != nil { t.Fatalf("failer registration failed: %v", err) } // Start the protocol stack, and ensure that a failing shut down terminates all @@ -408,12 +372,12 @@ func TestServiceTerminationGuarantee(t *testing.T) { if err := stack.Start(); err != nil { t.Fatalf("iter %d: failed to start protocol stack: %v", i, err) } - for j, id := range ids { + for id, _ := range services { if !started[id] { - t.Fatalf("iter %d, service %d: service not running", i, j) + t.Fatalf("iter %d, service %s: service not running", i, id) } if stopped[id] { - t.Fatalf("iter %d, service %d: service already stopped", i, j) + t.Fatalf("iter %d, service %s: service already stopped", i, id) } } // Stop the stack, verify failure and check all terminations @@ -421,16 +385,17 @@ func TestServiceTerminationGuarantee(t *testing.T) { if err, ok := err.(*StopError); !ok { t.Fatalf("iter %d: termination failure mismatch: have %v, want StopError", i, err) } else { - if err.Services["failer"] != failure { - t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services["failer"], failure) + failer := reflect.TypeOf(&InstrumentedService{}) + if err.Services[failer] != failure { + t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services[failer], failure) } if len(err.Services) != 1 { t.Fatalf("iter %d: failure count mismatch: have %d, want %d", i, len(err.Services), 1) } } - for j, id := range ids { + for id, _ := range services { if !stopped[id] { - t.Fatalf("iter %d, service %d: service not terminated", i, j) + t.Fatalf("iter %d, service %s: service not terminated", i, id) } delete(started, id) delete(stopped, id) @@ -438,27 +403,27 @@ func TestServiceTerminationGuarantee(t *testing.T) { } } -// TestSingletonServiceRetrieval tests that singleton services can be retrieved. -func TestSingletonServiceRetrieval(t *testing.T) { +// TestServiceRetrieval tests that individual services can be retrieved. +func TestServiceRetrieval(t *testing.T) { // Create a simple stack and register two service types stack, err := New(testNodeConfig) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } - if err := stack.Register("noop", func(*ServiceContext) (Service, error) { return new(NoopService), nil }); err != nil { + if err := stack.Register(NewNoopService); err != nil { t.Fatalf("noop service registration failed: %v", err) } - if err := stack.Register("instrumented", func(*ServiceContext) (Service, error) { return new(InstrumentedService), nil }); err != nil { + if err := stack.Register(NewInstrumentedService); err != nil { t.Fatalf("instrumented service registration failed: %v", err) } // Make sure none of the services can be retrieved until started var noopServ *NoopService - if id, err := stack.SingletonService(&noopServ); id != "" || err != ErrServiceUnknown { - t.Fatalf("noop service retrieval mismatch: have %v/%v, want %v/%v", id, err, "", ErrServiceUnknown) + if err := stack.Service(&noopServ); err != ErrNodeStopped { + t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) } var instServ *InstrumentedService - if id, err := stack.SingletonService(&instServ); id != "" || err != ErrServiceUnknown { - t.Fatalf("instrumented service retrieval mismatch: have %v/%v, want %v/%v", id, err, "", ErrServiceUnknown) + if err := stack.Service(&instServ); err != ErrNodeStopped { + t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) } // Start the stack and ensure everything is retrievable now if err := stack.Start(); err != nil { @@ -466,11 +431,11 @@ func TestSingletonServiceRetrieval(t *testing.T) { } defer stack.Stop() - if id, err := stack.SingletonService(&noopServ); id != "noop" || err != nil { - t.Fatalf("noop service retrieval mismatch: have %v/%v, want %v/%v", id, err, "noop", nil) + if err := stack.Service(&noopServ); err != nil { + t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil) } - if id, err := stack.SingletonService(&instServ); id != "instrumented" || err != nil { - t.Fatalf("instrumented service retrieval mismatch: have %v/%v, want %v/%v", id, err, "instrumented", nil) + if err := stack.Service(&instServ); err != nil { + t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil) } } @@ -481,13 +446,16 @@ func TestProtocolGather(t *testing.T) { t.Fatalf("failed to create protocol stack: %v", err) } // Register a batch of services with some configured number of protocols - services := map[string]int{ - "Zero Protocols": 0, - "Single Protocol": 1, - "Many Protocols": 25, + services := map[string]struct { + Count int + Maker InstrumentingWrapper + }{ + "Zero Protocols": {0, InstrumentedServiceMakerA}, + "Single Protocol": {1, InstrumentedServiceMakerB}, + "Many Protocols": {25, InstrumentedServiceMakerC}, } - for id, count := range services { - protocols := make([]p2p.Protocol, count) + for id, config := range services { + protocols := make([]p2p.Protocol, config.Count) for i := 0; i < len(protocols); i++ { protocols[i].Name = id protocols[i].Version = uint(i) @@ -497,7 +465,7 @@ func TestProtocolGather(t *testing.T) { protocols: protocols, }, nil } - if err := stack.Register(id, constructor); err != nil { + if err := stack.Register(config.Maker(constructor)); err != nil { t.Fatalf("service %s: registration failed: %v", id, err) } } @@ -511,8 +479,8 @@ func TestProtocolGather(t *testing.T) { if len(protocols) != 26 { t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26) } - for id, count := range services { - for ver := 0; ver < count; ver++ { + for id, config := range services { + for ver := 0; ver < config.Count; ver++ { launched := false for i := 0; i < len(protocols); i++ { if protocols[i].Name == id && protocols[i].Version == uint(ver) { diff --git a/node/service.go b/node/service.go index 28fc34764b..bfeeb7ab98 100644 --- a/node/service.go +++ b/node/service.go @@ -29,39 +29,29 @@ import ( // the protocol stack, that is passed to all constructors to be optionally used; // as well as utility methods to operate on the service environment. type ServiceContext struct { - datadir string // Data directory for protocol persistence - services map[string]Service // Index of the already constructed services - EventMux *event.TypeMux // Event multiplexer used for decoupled notifications + datadir string // Data directory for protocol persistence + services map[reflect.Type]Service // Index of the already constructed services + EventMux *event.TypeMux // Event multiplexer used for decoupled notifications } -// Database opens an existing database with the given name (or creates one if no -// previous can be found) from within the node's data directory. If the node is -// an ephemeral one, a memory database is returned. -func (ctx *ServiceContext) Database(name string, cache int) (ethdb.Database, error) { +// OpenDatabase opens an existing database with the given name (or creates one +// if no previous can be found) from within the node's data directory. If the +// node is an ephemeral one, a memory database is returned. +func (ctx *ServiceContext) OpenDatabase(name string, cache int) (ethdb.Database, error) { if ctx.datadir == "" { return ethdb.NewMemDatabase() } return ethdb.NewLDBDatabase(filepath.Join(ctx.datadir, name), cache) } -// Service retrieves an already constructed service registered under a given id. -func (ctx *ServiceContext) Service(id string) Service { - return ctx.services[id] -} - -// SingletonService retrieves an already constructed service using a specific type -// implementing the Service interface. This is a utility function for scenarios -// where it is known that only one instance of a given service type is running, -// allowing to access services without needing to know their specific id with -// which they were registered. -func (ctx *ServiceContext) SingletonService(service interface{}) (string, error) { - for id, running := range ctx.services { - if reflect.TypeOf(running) == reflect.ValueOf(service).Elem().Type() { - reflect.ValueOf(service).Elem().Set(reflect.ValueOf(running)) - return id, nil - } +// Service retrieves a currently running service registered of a specific type. +func (ctx *ServiceContext) Service(service interface{}) error { + element := reflect.ValueOf(service).Elem() + if running, ok := ctx.services[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil } - return "", ErrServiceUnknown + return ErrServiceUnknown } // ServiceConstructor is the function signature of the constructors needed to be diff --git a/node/service_test.go b/node/service_test.go index 921a1a0126..1dfb61f73a 100644 --- a/node/service_test.go +++ b/node/service_test.go @@ -39,7 +39,7 @@ func TestContextDatabases(t *testing.T) { } // Request the opening/creation of a database and ensure it persists to disk ctx := &ServiceContext{datadir: dir} - db, err := ctx.Database("persistent", 0) + db, err := ctx.OpenDatabase("persistent", 0) if err != nil { t.Fatalf("failed to open persistent database: %v", err) } @@ -50,7 +50,7 @@ func TestContextDatabases(t *testing.T) { } // Request th opening/creation of an ephemeral database and ensure it's not persisted ctx = &ServiceContext{datadir: ""} - db, err = ctx.Database("ephemeral", 0) + db, err = ctx.OpenDatabase("ephemeral", 0) if err != nil { t.Fatalf("failed to open ephemeral database: %v", err) } @@ -67,36 +67,27 @@ func TestContextServices(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } - // Define a set of services, constructed before/after a verifier - formers := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"} - latters := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} - + // Define a verifier that ensures a NoopA is before it and NoopB after verifier := func(ctx *ServiceContext) (Service, error) { - for i, id := range formers { - if ctx.Service(id) == nil { - return nil, fmt.Errorf("former %d: service not found", i) - } + var objA *NoopServiceA + if ctx.Service(&objA) != nil { + return nil, fmt.Errorf("former service not found") } - for i, id := range latters { - if ctx.Service(id) != nil { - return nil, fmt.Errorf("latters %d: service found", i) - } + var objB *NoopServiceB + if err := ctx.Service(&objB); err != ErrServiceUnknown { + return nil, fmt.Errorf("latters lookup error mismatch: have %v, want %v", err, ErrServiceUnknown) } return new(NoopService), nil } // Register the collection of services - for i, id := range formers { - if err := stack.Register(id, NewNoopService); err != nil { - t.Fatalf("former #%d: failed to register service: %v", i, err) - } + if err := stack.Register(NewNoopServiceA); err != nil { + t.Fatalf("former failed to register service: %v", err) } - if err := stack.Register("verifier", verifier); err != nil { + if err := stack.Register(verifier); err != nil { t.Fatalf("failed to register service verifier: %v", err) } - for i, id := range latters { - if err := stack.Register(id, NewNoopService); err != nil { - t.Fatalf("latter #%d: failed to register service: %v", i, err) - } + if err := stack.Register(NewNoopServiceB); err != nil { + t.Fatalf("latter failed to register service: %v", err) } // Start the protocol stack and ensure services are constructed in order if err := stack.Start(); err != nil { diff --git a/node/utils_test.go b/node/utils_test.go new file mode 100644 index 0000000000..756622c863 --- /dev/null +++ b/node/utils_test.go @@ -0,0 +1,117 @@ +// Copyright 2015 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 . + +// Contains a batch of utility type declarations used by the tests. As the node +// operates on unique types, a lot of them are needed to check various features. + +package node + +import ( + "reflect" + + "github.com/ethereum/go-ethereum/p2p" +) + +// NoopService is a trivial implementation of the Service interface. +type NoopService struct{} + +func (s *NoopService) Protocols() []p2p.Protocol { return nil } +func (s *NoopService) Start(*p2p.Server) error { return nil } +func (s *NoopService) Stop() error { return nil } + +func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil } + +// Set of services all wrapping the base NoopService resulting in the same method +// signatures but different outer types. +type NoopServiceA struct{ NoopService } +type NoopServiceB struct{ NoopService } +type NoopServiceC struct{ NoopService } +type NoopServiceD struct{ NoopService } + +func NewNoopServiceA(*ServiceContext) (Service, error) { return new(NoopServiceA), nil } +func NewNoopServiceB(*ServiceContext) (Service, error) { return new(NoopServiceB), nil } +func NewNoopServiceC(*ServiceContext) (Service, error) { return new(NoopServiceC), nil } +func NewNoopServiceD(*ServiceContext) (Service, error) { return new(NoopServiceD), nil } + +// InstrumentedService is an implementation of Service for which all interface +// methods can be instrumented both return value as well as event hook wise. +type InstrumentedService struct { + protocols []p2p.Protocol + start error + stop error + + protocolsHook func() + startHook func(*p2p.Server) + stopHook func() +} + +func NewInstrumentedService(*ServiceContext) (Service, error) { return new(InstrumentedService), nil } + +func (s *InstrumentedService) Protocols() []p2p.Protocol { + if s.protocolsHook != nil { + s.protocolsHook() + } + return s.protocols +} + +func (s *InstrumentedService) Start(server *p2p.Server) error { + if s.startHook != nil { + s.startHook(server) + } + return s.start +} + +func (s *InstrumentedService) Stop() error { + if s.stopHook != nil { + s.stopHook() + } + return s.stop +} + +// InstrumentingWrapper is a method to specialize a service constructor returning +// a generic InstrumentedService into one returning a wrapping specific one. +type InstrumentingWrapper func(base ServiceConstructor) ServiceConstructor + +func InstrumentingWrapperMaker(base ServiceConstructor, kind reflect.Type) ServiceConstructor { + return func(ctx *ServiceContext) (Service, error) { + obj, err := base(ctx) + if err != nil { + return nil, err + } + wrapper := reflect.New(kind) + wrapper.Elem().Field(0).Set(reflect.ValueOf(obj).Elem()) + + return wrapper.Interface().(Service), nil + } +} + +// Set of services all wrapping the base InstrumentedService resulting in the +// same method signatures but different outer types. +type InstrumentedServiceA struct{ InstrumentedService } +type InstrumentedServiceB struct{ InstrumentedService } +type InstrumentedServiceC struct{ InstrumentedService } + +func InstrumentedServiceMakerA(base ServiceConstructor) ServiceConstructor { + return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceA{})) +} + +func InstrumentedServiceMakerB(base ServiceConstructor) ServiceConstructor { + return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceB{})) +} + +func InstrumentedServiceMakerC(base ServiceConstructor) ServiceConstructor { + return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceC{})) +} diff --git a/rpc/api/admin.go b/rpc/api/admin.go index 4682062e0a..1133c9bca3 100644 --- a/rpc/api/admin.go +++ b/rpc/api/admin.go @@ -97,7 +97,7 @@ func NewAdminApi(xeth *xeth.XEth, stack *node.Node, codec codec.Codec) *adminApi coder: codec.New(nil), } if stack != nil { - stack.SingletonService(&api.ethereum) + stack.Service(&api.ethereum) } return api } diff --git a/rpc/api/utils.go b/rpc/api/utils.go index 6e372c0613..d6820cd2ef 100644 --- a/rpc/api/utils.go +++ b/rpc/api/utils.go @@ -165,7 +165,7 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, stack *no var eth *eth.Ethereum if stack != nil { - if _, err := stack.SingletonService(ð); err != nil { + if err := stack.Service(ð); err != nil { return nil, err } } diff --git a/xeth/state.go b/xeth/state.go index e67dc4b5f9..7daccb5255 100644 --- a/xeth/state.go +++ b/xeth/state.go @@ -19,7 +19,6 @@ package xeth import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/eth" ) type State struct { @@ -46,7 +45,7 @@ func (self *State) SafeGet(addr string) *Object { func (self *State) safeGet(addr string) *state.StateObject { object := self.state.GetStateObject(common.HexToAddress(addr)) if object == nil { - object = state.NewStateObject(common.HexToAddress(addr), self.xeth.backend.Service("eth").(*eth.Ethereum).ChainDb()) + object = state.NewStateObject(common.HexToAddress(addr), self.xeth.EthereumService().ChainDb()) } return object } diff --git a/xeth/xeth.go b/xeth/xeth.go index 6bc94273ee..5e773d1c96 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -100,8 +100,8 @@ func New(stack *node.Node, frontend Frontend) *XEth { ethereum *eth.Ethereum whisper *whisper.Whisper ) - stack.SingletonService(ðereum) - stack.SingletonService(&whisper) + stack.Service(ðereum) + stack.Service(&whisper) xeth := &XEth{ backend: stack, @@ -130,7 +130,7 @@ func New(stack *node.Node, frontend Frontend) *XEth { func (self *XEth) EthereumService() *eth.Ethereum { var ethereum *eth.Ethereum - if _, err := self.backend.SingletonService(ðereum); err != nil { + if err := self.backend.Service(ðereum); err != nil { return nil } return ethereum @@ -138,7 +138,7 @@ func (self *XEth) EthereumService() *eth.Ethereum { func (self *XEth) WhisperService() *whisper.Whisper { var whisper *whisper.Whisper - if _, err := self.backend.SingletonService(&whisper); err != nil { + if err := self.backend.Service(&whisper); err != nil { return nil } return whisper From 18ea468cf8c2a82d3c1310c81a6389cd7c019fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 27 Nov 2015 19:32:46 +0200 Subject: [PATCH 11/78] common: fix #2008, wrong hex prefix check --- common/types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/types.go b/common/types.go index ea58381886..acbd5b28da 100644 --- a/common/types.go +++ b/common/types.go @@ -95,10 +95,10 @@ func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } // IsHexAddress verifies whether a string can represent a valid hex-encoded // Ethereum address or not. func IsHexAddress(s string) bool { - if len(s) == 2+2*AddressLength && IsHex(s[2:]) { + if len(s) == 2+2*AddressLength && IsHex(s) { return true } - if len(s) == 2*AddressLength && IsHex(s) { + if len(s) == 2*AddressLength && IsHex("0x"+s) { return true } return false From 9af4065243cecad8aaa297ee396ebbc17b280733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sun, 29 Nov 2015 17:52:52 +0200 Subject: [PATCH 12/78] Makefile: enable iOS experimental builds (fix prev omission) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5fc5e9a9de..1f9bf0db85 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ geth: @echo "Done building." @echo "Run \"$(GOBIN)/geth\" to launch geth." -geth-cross: geth-linux geth-darwin geth-windows geth-android +geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios @echo "Full cross compilation done:" @ls -l $(GOBIN)/geth-* From 1ead43c88cfacc0d30e186cc3782d31b4e9f5764 Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Mon, 30 Nov 2015 09:49:10 +0100 Subject: [PATCH 13/78] console: fix instance name printed incorrect on start --- cmd/geth/js.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 196f3af593..843c9a5b57 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -245,7 +245,7 @@ func (self *jsre) batch(statement string) { func (self *jsre) welcome() { self.re.Run(` (function () { - console.log('instance: ' + web3.version.client); + console.log('instance: ' + web3.version.node); console.log(' datadir: ' + admin.datadir); console.log("coinbase: " + eth.coinbase); var ts = 1000 * eth.getBlock(eth.blockNumber).timestamp; From 27a50c8f4bc69f98e20db361859bfbb6cf371c00 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Mon, 28 Sep 2015 11:19:23 +0200 Subject: [PATCH 14/78] core/secp256k1: update libsecp256k1 Go wrapper and tests --- crypto/secp256k1/secp256.go | 4 ++-- crypto/secp256k1/secp256_test.go | 31 +++++++++++++++---------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index 41a5608a54..7f26f307cd 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -96,7 +96,7 @@ func GenerateKeyPair() ([]byte, []byte) { var output_len C.size_t - C.secp256k1_ec_pubkey_serialize( // always returns 1 + _ = C.secp256k1_ec_pubkey_serialize( // always returns 1 context, pubkey65_ptr, &output_len, @@ -163,7 +163,7 @@ func Sign(msg []byte, seckey []byte) ([]byte, error) { sig_serialized_ptr := (*C.uchar)(unsafe.Pointer(&sig_serialized[0])) var recid C.int - C.secp256k1_ecdsa_recoverable_signature_serialize_compact( + _ = C.secp256k1_ecdsa_recoverable_signature_serialize_compact( context, sig_serialized_ptr, // 64 byte compact signature &recid, diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index cb71ea5e7d..d3ff2223d5 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -86,10 +86,7 @@ func TestSignAndRecover(t *testing.T) { func TestRandomMessagesWithSameKey(t *testing.T) { pubkey, seckey := GenerateKeyPair() keys := func() ([]byte, []byte) { - // Sign function zeroes the privkey so we need a new one in each call - newkey := make([]byte, len(seckey)) - copy(newkey, seckey) - return pubkey, newkey + return pubkey, seckey } signAndRecoverWithRandomMessages(t, keys) } @@ -209,30 +206,32 @@ func compactSigCheck(t *testing.T, sig []byte) { } } -// godep go test -v -run=XXX -bench=BenchmarkSignRandomInputEachRound +// godep go test -v -run=XXX -bench=BenchmarkSign // add -benchtime=10s to benchmark longer for more accurate average -func BenchmarkSignRandomInputEachRound(b *testing.B) { + +// to avoid compiler optimizing the benchmarked function call +var err error + +func BenchmarkSign(b *testing.B) { for i := 0; i < b.N; i++ { - b.StopTimer() _, seckey := GenerateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) b.StartTimer() - if _, err := Sign(msg, seckey); err != nil { - b.Fatal(err) - } + _, e := Sign(msg, seckey) + err = e + b.StopTimer() } } -//godep go test -v -run=XXX -bench=BenchmarkRecoverRandomInputEachRound -func BenchmarkRecoverRandomInputEachRound(b *testing.B) { +//godep go test -v -run=XXX -bench=BenchmarkECRec +func BenchmarkRecover(b *testing.B) { for i := 0; i < b.N; i++ { - b.StopTimer() _, seckey := GenerateKeyPair() msg := randentropy.GetEntropyCSPRNG(32) sig, _ := Sign(msg, seckey) b.StartTimer() - if _, err := RecoverPubkey(msg, sig); err != nil { - b.Fatal(err) - } + _, e := RecoverPubkey(msg, sig) + err = e + b.StopTimer() } } From c8ad64f33cd04fc10ac6681260ea06e464908c91 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Tue, 29 Sep 2015 19:37:44 +0200 Subject: [PATCH 15/78] crypto, crypto/ecies, crypto/secp256k1: libsecp256k1 scalar mult thanks to Felix Lange (fjl) for help with design & impl --- crypto/crypto.go | 26 ++--- crypto/crypto_test.go | 8 +- crypto/ecies/asn1.go | 7 ++ crypto/ecies/ecies.go | 1 + crypto/ecies/ecies_test.go | 123 +++++++++++++++++--- crypto/ecies/params.go | 14 +-- crypto/key.go | 5 +- crypto/{ => secp256k1}/curve.go | 166 +++++++++------------------ crypto/secp256k1/curve_test.go | 39 +++++++ crypto/secp256k1/pubkey_scalar_mul.h | 56 +++++++++ crypto/secp256k1/secp256.go | 28 ++++- crypto/secp256k1/secp256_test.go | 2 +- p2p/discover/node.go | 2 +- p2p/rlpx.go | 4 +- p2p/rlpx_test.go | 2 + whisper/message_test.go | 9 +- 16 files changed, 321 insertions(+), 171 deletions(-) rename crypto/{ => secp256k1}/curve.go (67%) create mode 100644 crypto/secp256k1/curve_test.go create mode 100644 crypto/secp256k1/pubkey_scalar_mul.h diff --git a/crypto/crypto.go b/crypto/crypto.go index 8685d62d3e..7d76237532 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -43,14 +43,6 @@ import ( "golang.org/x/crypto/ripemd160" ) -var secp256k1n *big.Int - -func init() { - // specify the params for the s256 curve - ecies.AddParamsForCurve(S256(), ecies.ECIES_AES128_SHA256) - secp256k1n = common.String2Big("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") -} - func Sha3(data ...[]byte) []byte { d := sha3.NewKeccak256() for _, b := range data { @@ -99,9 +91,9 @@ func ToECDSA(prv []byte) *ecdsa.PrivateKey { } priv := new(ecdsa.PrivateKey) - priv.PublicKey.Curve = S256() + priv.PublicKey.Curve = secp256k1.S256() priv.D = common.BigD(prv) - priv.PublicKey.X, priv.PublicKey.Y = S256().ScalarBaseMult(prv) + priv.PublicKey.X, priv.PublicKey.Y = secp256k1.S256().ScalarBaseMult(prv) return priv } @@ -116,15 +108,15 @@ func ToECDSAPub(pub []byte) *ecdsa.PublicKey { if len(pub) == 0 { return nil } - x, y := elliptic.Unmarshal(S256(), pub) - return &ecdsa.PublicKey{S256(), x, y} + x, y := elliptic.Unmarshal(secp256k1.S256(), pub) + return &ecdsa.PublicKey{secp256k1.S256(), x, y} } func FromECDSAPub(pub *ecdsa.PublicKey) []byte { if pub == nil || pub.X == nil || pub.Y == nil { return nil } - return elliptic.Marshal(S256(), pub.X, pub.Y) + return elliptic.Marshal(secp256k1.S256(), pub.X, pub.Y) } // HexToECDSA parses a secp256k1 private key. @@ -168,7 +160,7 @@ func SaveECDSA(file string, key *ecdsa.PrivateKey) error { } func GenerateKey() (*ecdsa.PrivateKey, error) { - return ecdsa.GenerateKey(S256(), rand.Reader) + return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) } func ValidateSignatureValues(v byte, r, s *big.Int) bool { @@ -176,7 +168,7 @@ func ValidateSignatureValues(v byte, r, s *big.Int) bool { return false } vint := uint32(v) - if r.Cmp(secp256k1n) < 0 && s.Cmp(secp256k1n) < 0 && (vint == 27 || vint == 28) { + if r.Cmp(secp256k1.N) < 0 && s.Cmp(secp256k1.N) < 0 && (vint == 27 || vint == 28) { return true } else { return false @@ -189,8 +181,8 @@ func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) { return nil, err } - x, y := elliptic.Unmarshal(S256(), s) - return &ecdsa.PublicKey{S256(), x, y}, nil + x, y := elliptic.Unmarshal(secp256k1.S256(), s) + return &ecdsa.PublicKey{secp256k1.S256(), x, y}, nil } func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index fdd9c1ee85..d5e19a4bb2 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -181,7 +181,7 @@ func TestValidateSignatureValues(t *testing.T) { minusOne := big.NewInt(-1) one := common.Big1 zero := common.Big0 - secp256k1nMinus1 := new(big.Int).Sub(secp256k1n, common.Big1) + secp256k1nMinus1 := new(big.Int).Sub(secp256k1.N, common.Big1) // correct v,r,s check(true, 27, one, one) @@ -208,9 +208,9 @@ func TestValidateSignatureValues(t *testing.T) { // correct sig with max r,s check(true, 27, secp256k1nMinus1, secp256k1nMinus1) // correct v, combinations of incorrect r,s at upper limit - check(false, 27, secp256k1n, secp256k1nMinus1) - check(false, 27, secp256k1nMinus1, secp256k1n) - check(false, 27, secp256k1n, secp256k1n) + check(false, 27, secp256k1.N, secp256k1nMinus1) + check(false, 27, secp256k1nMinus1, secp256k1.N) + check(false, 27, secp256k1.N, secp256k1.N) // current callers ensures r,s cannot be negative, but let's test for that too // as crypto package could be used stand-alone diff --git a/crypto/ecies/asn1.go b/crypto/ecies/asn1.go index 6eaf3d2ca3..40dabd329b 100644 --- a/crypto/ecies/asn1.go +++ b/crypto/ecies/asn1.go @@ -41,6 +41,8 @@ import ( "fmt" "hash" "math/big" + + "github.com/ethereum/go-ethereum/crypto/secp256k1" ) var ( @@ -81,6 +83,7 @@ func doScheme(base, v []int) asn1.ObjectIdentifier { type secgNamedCurve asn1.ObjectIdentifier var ( + secgNamedCurveS256 = secgNamedCurve{1, 3, 132, 0, 10} secgNamedCurveP256 = secgNamedCurve{1, 2, 840, 10045, 3, 1, 7} secgNamedCurveP384 = secgNamedCurve{1, 3, 132, 0, 34} secgNamedCurveP521 = secgNamedCurve{1, 3, 132, 0, 35} @@ -116,6 +119,8 @@ func (curve secgNamedCurve) Equal(curve2 secgNamedCurve) bool { func namedCurveFromOID(curve secgNamedCurve) elliptic.Curve { switch { + case curve.Equal(secgNamedCurveS256): + return secp256k1.S256() case curve.Equal(secgNamedCurveP256): return elliptic.P256() case curve.Equal(secgNamedCurveP384): @@ -134,6 +139,8 @@ func oidFromNamedCurve(curve elliptic.Curve) (secgNamedCurve, bool) { return secgNamedCurveP384, true case elliptic.P521(): return secgNamedCurveP521, true + case secp256k1.S256(): + return secgNamedCurveS256, true } return nil, false diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index a3b520dd5c..65dc5b38ba 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -125,6 +125,7 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b if skLen+macLen > MaxSharedKeyLength(pub) { return nil, ErrSharedKeyTooBig } + x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes()) if x == nil { return nil, ErrSharedKeyIsPointAtInfinity diff --git a/crypto/ecies/ecies_test.go b/crypto/ecies/ecies_test.go index 1c391f938d..6a0ea3f022 100644 --- a/crypto/ecies/ecies_test.go +++ b/crypto/ecies/ecies_test.go @@ -31,13 +31,18 @@ package ecies import ( "bytes" + "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" + "encoding/hex" "flag" "fmt" "io/ioutil" + "math/big" "testing" + + "github.com/ethereum/go-ethereum/crypto/secp256k1" ) var dumpEnc bool @@ -65,7 +70,6 @@ func TestKDF(t *testing.T) { } } -var skLen int var ErrBadSharedKeys = fmt.Errorf("ecies: shared keys don't match") // cmpParams compares a set of ECIES parameters. We assume, as per the @@ -117,7 +121,7 @@ func TestSharedKey(t *testing.T) { fmt.Println(err.Error()) t.FailNow() } - skLen = MaxSharedKeyLength(&prv1.PublicKey) / 2 + skLen := MaxSharedKeyLength(&prv1.PublicKey) / 2 prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) if err != nil { @@ -143,6 +147,44 @@ func TestSharedKey(t *testing.T) { } } +func TestSharedKeyPadding(t *testing.T) { + // sanity checks + prv0 := hexKey("1adf5c18167d96a1f9a0b1ef63be8aa27eaf6032c233b2b38f7850cf5b859fd9") + prv1 := hexKey("97a076fc7fcd9208240668e31c9abee952cbb6e375d1b8febc7499d6e16f1a") + x0, _ := new(big.Int).SetString("1a8ed022ff7aec59dc1b440446bdda5ff6bcb3509a8b109077282b361efffbd8", 16) + x1, _ := new(big.Int).SetString("6ab3ac374251f638d0abb3ef596d1dc67955b507c104e5f2009724812dc027b8", 16) + y0, _ := new(big.Int).SetString("e040bd480b1deccc3bc40bd5b1fdcb7bfd352500b477cb9471366dbd4493f923", 16) + y1, _ := new(big.Int).SetString("8ad915f2b503a8be6facab6588731fefeb584fd2dfa9a77a5e0bba1ec439e4fa", 16) + + if prv0.PublicKey.X.Cmp(x0) != 0 { + t.Errorf("mismatched prv0.X:\nhave: %x\nwant: %x\n", prv0.PublicKey.X.Bytes(), x0.Bytes()) + } + if prv0.PublicKey.Y.Cmp(y0) != 0 { + t.Errorf("mismatched prv0.Y:\nhave: %x\nwant: %x\n", prv0.PublicKey.Y.Bytes(), y0.Bytes()) + } + if prv1.PublicKey.X.Cmp(x1) != 0 { + t.Errorf("mismatched prv1.X:\nhave: %x\nwant: %x\n", prv1.PublicKey.X.Bytes(), x1.Bytes()) + } + if prv1.PublicKey.Y.Cmp(y1) != 0 { + t.Errorf("mismatched prv1.Y:\nhave: %x\nwant: %x\n", prv1.PublicKey.Y.Bytes(), y1.Bytes()) + } + + // test shared secret generation + sk1, err := prv0.GenerateShared(&prv1.PublicKey, 16, 16) + if err != nil { + fmt.Println(err.Error()) + } + + sk2, err := prv1.GenerateShared(&prv0.PublicKey, 16, 16) + if err != nil { + t.Fatal(err.Error()) + } + + if !bytes.Equal(sk1, sk2) { + t.Fatal(ErrBadSharedKeys.Error()) + } +} + // Verify that the key generation code fails when too much key data is // requested. func TestTooBigSharedKey(t *testing.T) { @@ -158,13 +200,13 @@ func TestTooBigSharedKey(t *testing.T) { t.FailNow() } - _, err = prv1.GenerateShared(&prv2.PublicKey, skLen*2, skLen*2) + _, err = prv1.GenerateShared(&prv2.PublicKey, 32, 32) if err != ErrSharedKeyTooBig { fmt.Println("ecdh: shared key should be too large for curve") t.FailNow() } - _, err = prv2.GenerateShared(&prv1.PublicKey, skLen*2, skLen*2) + _, err = prv2.GenerateShared(&prv1.PublicKey, 32, 32) if err != ErrSharedKeyTooBig { fmt.Println("ecdh: shared key should be too large for curve") t.FailNow() @@ -176,25 +218,21 @@ func TestTooBigSharedKey(t *testing.T) { func TestMarshalPublic(t *testing.T) { prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) if err != nil { - fmt.Println(err.Error()) - t.FailNow() + t.Fatalf("GenerateKey error: %s", err) } out, err := MarshalPublic(&prv.PublicKey) if err != nil { - fmt.Println(err.Error()) - t.FailNow() + t.Fatalf("MarshalPublic error: %s", err) } pub, err := UnmarshalPublic(out) if err != nil { - fmt.Println(err.Error()) - t.FailNow() + t.Fatalf("UnmarshalPublic error: %s", err) } if !cmpPublic(prv.PublicKey, *pub) { - fmt.Println("ecies: failed to unmarshal public key") - t.FailNow() + t.Fatal("ecies: failed to unmarshal public key") } } @@ -304,9 +342,26 @@ func BenchmarkGenSharedKeyP256(b *testing.B) { fmt.Println(err.Error()) b.FailNow() } - + b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := prv.GenerateShared(&prv.PublicKey, skLen, skLen) + _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) + if err != nil { + fmt.Println(err.Error()) + b.FailNow() + } + } +} + +// Benchmark the generation of S256 shared keys. +func BenchmarkGenSharedKeyS256(b *testing.B) { + prv, err := GenerateKey(rand.Reader, secp256k1.S256(), nil) + if err != nil { + fmt.Println(err.Error()) + b.FailNow() + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) if err != nil { fmt.Println(err.Error()) b.FailNow() @@ -511,3 +566,43 @@ func TestBasicKeyValidation(t *testing.T) { } } } + +// Verify GenerateShared against static values - useful when +// debugging changes in underlying libs +func TestSharedKeyStatic(t *testing.T) { + prv1 := hexKey("7ebbc6a8358bc76dd73ebc557056702c8cfc34e5cfcd90eb83af0347575fd2ad") + prv2 := hexKey("6a3d6396903245bba5837752b9e0348874e72db0c4e11e9c485a81b4ea4353b9") + + skLen := MaxSharedKeyLength(&prv1.PublicKey) / 2 + + sk1, err := prv1.GenerateShared(&prv2.PublicKey, skLen, skLen) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + sk2, err := prv2.GenerateShared(&prv1.PublicKey, skLen, skLen) + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + if !bytes.Equal(sk1, sk2) { + fmt.Println(ErrBadSharedKeys.Error()) + t.FailNow() + } + + sk, _ := hex.DecodeString("167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62") + if !bytes.Equal(sk1, sk) { + t.Fatalf("shared secret mismatch: want: %x have: %x", sk, sk1) + } +} + +// TODO: remove after refactoring packages crypto and crypto/ecies +func hexKey(prv string) *PrivateKey { + priv := new(ecdsa.PrivateKey) + priv.PublicKey.Curve = secp256k1.S256() + priv.D, _ = new(big.Int).SetString(prv, 16) + priv.PublicKey.X, priv.PublicKey.Y = secp256k1.S256().ScalarBaseMult(priv.D.Bytes()) + return ImportECDSA(priv) +} diff --git a/crypto/ecies/params.go b/crypto/ecies/params.go index 97ddb09730..511c53ebc0 100644 --- a/crypto/ecies/params.go +++ b/crypto/ecies/params.go @@ -41,13 +41,12 @@ import ( "crypto/sha512" "fmt" "hash" + + "github.com/ethereum/go-ethereum/crypto/secp256k1" ) -// The default curve for this package is the NIST P256 curve, which -// provides security equivalent to AES-128. -var DefaultCurve = elliptic.P256() - var ( + DefaultCurve = secp256k1.S256() ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm") ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters") ) @@ -101,9 +100,10 @@ var ( ) var paramsFromCurve = map[elliptic.Curve]*ECIESParams{ - elliptic.P256(): ECIES_AES128_SHA256, - elliptic.P384(): ECIES_AES256_SHA384, - elliptic.P521(): ECIES_AES256_SHA512, + secp256k1.S256(): ECIES_AES128_SHA256, + elliptic.P256(): ECIES_AES128_SHA256, + elliptic.P384(): ECIES_AES256_SHA384, + elliptic.P521(): ECIES_AES256_SHA512, } func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) { diff --git a/crypto/key.go b/crypto/key.go index 4ec43dfd74..ccf284ad8d 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -25,6 +25,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/pborman/uuid" ) @@ -137,7 +138,7 @@ func NewKey(rand io.Reader) *Key { panic("key generation: could not read from random source: " + err.Error()) } reader := bytes.NewReader(randBytes) - privateKeyECDSA, err := ecdsa.GenerateKey(S256(), reader) + privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), reader) if err != nil { panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) } @@ -155,7 +156,7 @@ func NewKeyForDirectICAP(rand io.Reader) *Key { panic("key generation: could not read from random source: " + err.Error()) } reader := bytes.NewReader(randBytes) - privateKeyECDSA, err := ecdsa.GenerateKey(S256(), reader) + privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), reader) if err != nil { panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) } diff --git a/crypto/curve.go b/crypto/secp256k1/curve.go similarity index 67% rename from crypto/curve.go rename to crypto/secp256k1/curve.go index 48f3f5e9ce..6e44a6771f 100644 --- a/crypto/curve.go +++ b/crypto/secp256k1/curve.go @@ -29,15 +29,22 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package crypto +package secp256k1 import ( "crypto/elliptic" "io" "math/big" "sync" + "unsafe" ) +/* +#include "libsecp256k1/include/secp256k1.h" +extern int secp256k1_pubkey_scalar_mul(const secp256k1_context* ctx, const unsigned char *point, const unsigned char *scalar); +*/ +import "C" + // This code is from https://github.com/ThePiachu/GoBit and implements // several Koblitz elliptic curves over prime fields. // @@ -211,44 +218,37 @@ func (BitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, return x3, y3, z3 } -//TODO: double check if it is okay -// ScalarMult returns k*(Bx,By) where k is a number in big-endian form. -func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) { - // We have a slight problem in that the identity of the group (the - // point at infinity) cannot be represented in (x, y) form on a finite - // machine. Thus the standard add/double algorithm has to be tweaked - // slightly: our initial state is not the identity, but x, and we - // ignore the first true bit in |k|. If we don't find any true bits in - // |k|, then we return nil, nil, because we cannot return the identity - // element. - - Bz := new(big.Int).SetInt64(1) - x := Bx - y := By - z := Bz - - seenFirstTrue := false - for _, byte := range k { - for bitNum := 0; bitNum < 8; bitNum++ { - if seenFirstTrue { - x, y, z = BitCurve.doubleJacobian(x, y, z) - } - if byte&0x80 == 0x80 { - if !seenFirstTrue { - seenFirstTrue = true - } else { - x, y, z = BitCurve.addJacobian(Bx, By, Bz, x, y, z) - } - } - byte <<= 1 - } +func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) { + // Ensure scalar is exactly 32 bytes. We pad always, even if + // scalar is 32 bytes long, to avoid a timing side channel. + if len(scalar) > 32 { + panic("can't handle scalars > 256 bits") } + padded := make([]byte, 32) + copy(padded[32-len(scalar):], scalar) + scalar = padded - if !seenFirstTrue { + // Do the multiplication in C, updating point. + point := make([]byte, 64) + readBits(point[:32], Bx) + readBits(point[32:], By) + pointPtr := (*C.uchar)(unsafe.Pointer(&point[0])) + scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0])) + res := C.secp256k1_pubkey_scalar_mul(context, pointPtr, scalarPtr) + + // Unpack the result and clear temporaries. + x := new(big.Int).SetBytes(point[:32]) + y := new(big.Int).SetBytes(point[32:]) + for i := range point { + point[i] = 0 + } + for i := range padded { + scalar[i] = 0 + } + if res != 1 { return nil, nil } - - return BitCurve.affineFromJacobian(x, y, z) + return x, y } // ScalarBaseMult returns k*G, where G is the base point of the group and k is @@ -312,86 +312,24 @@ func (BitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) { return } -//curve parameters taken from: -//http://www.secg.org/collateral/sec2_final.pdf - -var initonce sync.Once -var ecp160k1 *BitCurve -var ecp192k1 *BitCurve -var ecp224k1 *BitCurve -var ecp256k1 *BitCurve - -func initAll() { - initS160() - initS192() - initS224() - initS256() -} - -func initS160() { - // See SEC 2 section 2.4.1 - ecp160k1 = new(BitCurve) - ecp160k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73", 16) - ecp160k1.N, _ = new(big.Int).SetString("0100000000000000000001B8FA16DFAB9ACA16B6B3", 16) - ecp160k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000007", 16) - ecp160k1.Gx, _ = new(big.Int).SetString("3B4C382CE37AA192A4019E763036F4F5DD4D7EBB", 16) - ecp160k1.Gy, _ = new(big.Int).SetString("938CF935318FDCED6BC28286531733C3F03C4FEE", 16) - ecp160k1.BitSize = 160 -} - -func initS192() { - // See SEC 2 section 2.5.1 - ecp192k1 = new(BitCurve) - ecp192k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37", 16) - ecp192k1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D", 16) - ecp192k1.B, _ = new(big.Int).SetString("000000000000000000000000000000000000000000000003", 16) - ecp192k1.Gx, _ = new(big.Int).SetString("DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D", 16) - ecp192k1.Gy, _ = new(big.Int).SetString("9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D", 16) - ecp192k1.BitSize = 192 -} - -func initS224() { - // See SEC 2 section 2.6.1 - ecp224k1 = new(BitCurve) - ecp224k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D", 16) - ecp224k1.N, _ = new(big.Int).SetString("010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7", 16) - ecp224k1.B, _ = new(big.Int).SetString("00000000000000000000000000000000000000000000000000000005", 16) - ecp224k1.Gx, _ = new(big.Int).SetString("A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C", 16) - ecp224k1.Gy, _ = new(big.Int).SetString("7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5", 16) - ecp224k1.BitSize = 224 -} - -func initS256() { - // See SEC 2 section 2.7.1 - ecp256k1 = new(BitCurve) - ecp256k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) - ecp256k1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) - ecp256k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16) - ecp256k1.Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16) - ecp256k1.Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) - ecp256k1.BitSize = 256 -} - -// S160 returns a BitCurve which implements secp160k1 (see SEC 2 section 2.4.1) -func S160() *BitCurve { - initonce.Do(initAll) - return ecp160k1 -} - -// S192 returns a BitCurve which implements secp192k1 (see SEC 2 section 2.5.1) -func S192() *BitCurve { - initonce.Do(initAll) - return ecp192k1 -} - -// S224 returns a BitCurve which implements secp224k1 (see SEC 2 section 2.6.1) -func S224() *BitCurve { - initonce.Do(initAll) - return ecp224k1 -} +var ( + initonce sync.Once + theCurve *BitCurve +) // S256 returns a BitCurve which implements secp256k1 (see SEC 2 section 2.7.1) func S256() *BitCurve { - initonce.Do(initAll) - return ecp256k1 + initonce.Do(func() { + // See SEC 2 section 2.7.1 + // curve parameters taken from: + // http://www.secg.org/collateral/sec2_final.pdf + theCurve = new(BitCurve) + theCurve.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) + theCurve.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) + theCurve.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16) + theCurve.Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16) + theCurve.Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) + theCurve.BitSize = 256 + }) + return theCurve } diff --git a/crypto/secp256k1/curve_test.go b/crypto/secp256k1/curve_test.go new file mode 100644 index 0000000000..d915ee8525 --- /dev/null +++ b/crypto/secp256k1/curve_test.go @@ -0,0 +1,39 @@ +// Copyright 2015 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 . + +package secp256k1 + +import ( + "bytes" + "encoding/hex" + "math/big" + "testing" +) + +func TestReadBits(t *testing.T) { + check := func(input string) { + want, _ := hex.DecodeString(input) + int, _ := new(big.Int).SetString(input, 16) + buf := make([]byte, len(want)) + readBits(buf, int) + if !bytes.Equal(buf, want) { + t.Errorf("have: %x\nwant: %x", buf, want) + } + } + check("000000000000000000000000000000000000000000000000000000FEFCF3F8F0") + check("0000000000012345000000000000000000000000000000000000FEFCF3F8F0") + check("18F8F8F1000111000110011100222004330052300000000000000000FEFCF3F8F0") +} diff --git a/crypto/secp256k1/pubkey_scalar_mul.h b/crypto/secp256k1/pubkey_scalar_mul.h new file mode 100644 index 0000000000..0511545ec0 --- /dev/null +++ b/crypto/secp256k1/pubkey_scalar_mul.h @@ -0,0 +1,56 @@ +// Copyright 2015 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 . + +/** Multiply point by scalar in constant time. + * Returns: 1: multiplication was successful + * 0: scalar was invalid (zero or overflow) + * Args: ctx: pointer to a context object (cannot be NULL) + * Out: point: the multiplied point (usually secret) + * In: point: pointer to a 64-byte bytepublic point, + encoded as two 256bit big-endian numbers. + * scalar: a 32-byte scalar with which to multiply the point + */ +int secp256k1_pubkey_scalar_mul(const secp256k1_context* ctx, unsigned char *point, const unsigned char *scalar) { + int ret = 0; + int overflow = 0; + secp256k1_fe feX, feY; + secp256k1_gej res; + secp256k1_ge ge; + secp256k1_scalar s; + ARG_CHECK(point != NULL); + ARG_CHECK(scalar != NULL); + (void)ctx; + + secp256k1_fe_set_b32(&feX, point); + secp256k1_fe_set_b32(&feY, point+32); + secp256k1_ge_set_xy(&ge, &feX, &feY); + secp256k1_scalar_set_b32(&s, scalar, &overflow); + if (overflow || secp256k1_scalar_is_zero(&s)) { + ret = 0; + } else { + secp256k1_ecmult_const(&res, &ge, &s); + secp256k1_ge_set_gej(&ge, &res); + /* Note: can't use secp256k1_pubkey_save here because it is not constant time. */ + secp256k1_fe_normalize(&ge.x); + secp256k1_fe_normalize(&ge.y); + secp256k1_fe_get_b32(point, &ge.x); + secp256k1_fe_get_b32(point+32, &ge.y); + ret = 1; + } + secp256k1_scalar_clear(&s); + return ret; +} + diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index 7f26f307cd..8dc2481454 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -20,6 +20,7 @@ package secp256k1 /* #cgo CFLAGS: -I./libsecp256k1 +#cgo CFLAGS: -I./libsecp256k1/src/ #cgo darwin CFLAGS: -I/usr/local/include #cgo freebsd CFLAGS: -I/usr/local/include #cgo linux,arm CFLAGS: -I/usr/local/arm/include @@ -35,6 +36,7 @@ package secp256k1 #define NDEBUG #include "./libsecp256k1/src/secp256k1.c" #include "./libsecp256k1/src/modules/recovery/main_impl.h" +#include "pubkey_scalar_mul.h" typedef void (*callbackFunc) (const char* msg, void* data); extern void secp256k1GoPanicIllegal(const char* msg, void* data); @@ -44,6 +46,7 @@ import "C" import ( "errors" + "math/big" "unsafe" "github.com/ethereum/go-ethereum/crypto/randentropy" @@ -56,13 +59,16 @@ import ( > store private keys in buffer and shuffle (deters persistance on swap disc) > byte permutation (changing) > xor with chaning random block (to deter scanning memory for 0x63) (stream cipher?) - > on disk: store keys in wallets */ // holds ptr to secp256k1_context_struct (see secp256k1/include/secp256k1.h) -var context *C.secp256k1_context +var ( + context *C.secp256k1_context + N *big.Int +) func init() { + N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) // around 20 ms on a modern CPU. context = C.secp256k1_context_create(3) // SECP256K1_START_SIGN | SECP256K1_START_VERIFY C.secp256k1_context_set_illegal_callback(context, C.callbackFunc(C.secp256k1GoPanicIllegal), nil) @@ -78,7 +84,6 @@ var ( func GenerateKeyPair() ([]byte, []byte) { var seckey []byte = randentropy.GetEntropyCSPRNG(32) var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) - var pubkey64 []byte = make([]byte, 64) // secp256k1_pubkey var pubkey65 []byte = make([]byte, 65) // 65 byte uncompressed pubkey pubkey64_ptr := (*C.secp256k1_pubkey)(unsafe.Pointer(&pubkey64[0])) @@ -96,7 +101,7 @@ func GenerateKeyPair() ([]byte, []byte) { var output_len C.size_t - _ = C.secp256k1_ec_pubkey_serialize( // always returns 1 + C.secp256k1_ec_pubkey_serialize( // always returns 1 context, pubkey65_ptr, &output_len, @@ -163,7 +168,7 @@ func Sign(msg []byte, seckey []byte) ([]byte, error) { sig_serialized_ptr := (*C.uchar)(unsafe.Pointer(&sig_serialized[0])) var recid C.int - _ = C.secp256k1_ecdsa_recoverable_signature_serialize_compact( + C.secp256k1_ecdsa_recoverable_signature_serialize_compact( context, sig_serialized_ptr, // 64 byte compact signature &recid, @@ -254,3 +259,16 @@ func checkSignature(sig []byte) error { } return nil } + +// reads num into buf as big-endian bytes. +func readBits(buf []byte, num *big.Int) { + const wordLen = int(unsafe.Sizeof(big.Word(0))) + i := len(buf) + for _, d := range num.Bits() { + for j := 0; j < wordLen && i > 0; j++ { + i-- + buf[i] = byte(d) + d >>= 8 + } + } +} diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index d3ff2223d5..fc6fc9b32e 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -24,7 +24,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/randentropy" ) -const TestCount = 10000 +const TestCount = 1000 func TestPrivkeyGenerate(t *testing.T) { _, seckey := GenerateKeyPair() diff --git a/p2p/discover/node.go b/p2p/discover/node.go index a14f294249..dd19df3a2c 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -210,7 +210,7 @@ func PubkeyID(pub *ecdsa.PublicKey) NodeID { // Pubkey returns the public key represented by the node ID. // It returns an error if the ID is not a point on the curve. func (id NodeID) Pubkey() (*ecdsa.PublicKey, error) { - p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} + p := &ecdsa.PublicKey{Curve: secp256k1.S256(), X: new(big.Int), Y: new(big.Int)} half := len(id) / 2 p.X.SetBytes(id[:half]) p.Y.SetBytes(id[half:]) diff --git a/p2p/rlpx.go b/p2p/rlpx.go index aaa7338547..8f429d6ec1 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx.go @@ -277,7 +277,7 @@ func newInitiatorHandshake(remoteID discover.NodeID) (*encHandshake, error) { return nil, err } // generate random keypair to use for signing - randpriv, err := ecies.GenerateKey(rand.Reader, crypto.S256(), nil) + randpriv, err := ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil) if err != nil { return nil, err } @@ -376,7 +376,7 @@ func decodeAuthMsg(prv *ecdsa.PrivateKey, token []byte, auth []byte) (*encHandsh var err error h := new(encHandshake) // generate random keypair for session - h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) + h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil) if err != nil { return nil, err } diff --git a/p2p/rlpx_test.go b/p2p/rlpx_test.go index 900353f0ee..7cc7548e2b 100644 --- a/p2p/rlpx_test.go +++ b/p2p/rlpx_test.go @@ -93,6 +93,7 @@ func testEncHandshake(token []byte) error { go func() { r := result{side: "initiator"} defer func() { output <- r }() + defer fd0.Close() dest := &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey)} r.id, r.err = c0.doEncHandshake(prv0, dest) @@ -107,6 +108,7 @@ func testEncHandshake(token []byte) error { go func() { r := result{side: "receiver"} defer func() { output <- r }() + defer fd1.Close() r.id, r.err = c1.doEncHandshake(prv1, nil) if r.err != nil { diff --git a/whisper/message_test.go b/whisper/message_test.go index 6ff95efff5..d70da40a4a 100644 --- a/whisper/message_test.go +++ b/whisper/message_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" ) // Tests whether a message can be wrapped without any identity or encryption. @@ -72,8 +73,8 @@ func TestMessageCleartextSignRecover(t *testing.T) { if pubKey == nil { t.Fatalf("failed to recover public key") } - p1 := elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y) - p2 := elliptic.Marshal(crypto.S256(), pubKey.X, pubKey.Y) + p1 := elliptic.Marshal(secp256k1.S256(), key.PublicKey.X, key.PublicKey.Y) + p2 := elliptic.Marshal(secp256k1.S256(), pubKey.X, pubKey.Y) if !bytes.Equal(p1, p2) { t.Fatalf("public key mismatch: have 0x%x, want 0x%x", p2, p1) } @@ -150,8 +151,8 @@ func TestMessageFullCrypto(t *testing.T) { if pubKey == nil { t.Fatalf("failed to recover public key") } - p1 := elliptic.Marshal(crypto.S256(), fromKey.PublicKey.X, fromKey.PublicKey.Y) - p2 := elliptic.Marshal(crypto.S256(), pubKey.X, pubKey.Y) + p1 := elliptic.Marshal(secp256k1.S256(), fromKey.PublicKey.X, fromKey.PublicKey.Y) + p2 := elliptic.Marshal(secp256k1.S256(), pubKey.X, pubKey.Y) if !bytes.Equal(p1, p2) { t.Fatalf("public key mismatch: have 0x%x, want 0x%x", p2, p1) } From 52904ae32f0a591e7dccad7827ff1c2a73c27026 Mon Sep 17 00:00:00 2001 From: zsfelfoldi Date: Wed, 25 Nov 2015 18:28:21 +0100 Subject: [PATCH 16/78] trie: added error handling Created alternate versions of Trie and SecureTrie functions that can return a MissingNodeError (used by ODR services) --- trie/arc.go | 12 +++ trie/errors.go | 41 +++++++++ trie/iterator.go | 19 +++- trie/proof.go | 11 ++- trie/secure_trie.go | 49 +++++++++-- trie/trie.go | 210 +++++++++++++++++++++++++++++++------------- trie/trie_test.go | 75 +++++++++++++++- 7 files changed, 346 insertions(+), 71 deletions(-) create mode 100644 trie/errors.go diff --git a/trie/arc.go b/trie/arc.go index 9da012e168..fc7a3259f0 100644 --- a/trie/arc.go +++ b/trie/arc.go @@ -62,6 +62,18 @@ func newARC(c int) *arc { } } +// Clear clears the cache +func (a *arc) Clear() { + a.mutex.Lock() + defer a.mutex.Unlock() + a.p = 0 + a.t1 = list.New() + a.b1 = list.New() + a.t2 = list.New() + a.b2 = list.New() + a.cache = make(map[string]*entry, a.c) +} + // Put inserts a new key-value pair into the cache. // This optimizes future access to this entry (side effect). func (a *arc) Put(key hashNode, value node) bool { diff --git a/trie/errors.go b/trie/errors.go new file mode 100644 index 0000000000..a0f58f28fc --- /dev/null +++ b/trie/errors.go @@ -0,0 +1,41 @@ +// Copyright 2014 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 . + +package trie + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +// MissingNodeError is returned by the trie functions (TryGet, TryUpdate, TryDelete) +// in the case where a trie node is not present in the local database. Contains +// information necessary for retrieving the missing node through an ODR service. +// +// NodeHash is the hash of the missing node +// RootHash is the original root of the trie that contains the node +// KeyPrefix is the prefix that leads from the root to the missing node (hex encoded) +// KeySuffix (optional) contains the rest of the key we were looking for, gives a +// hint on which further nodes should also be retrieved (hex encoded) +type MissingNodeError struct { + RootHash, NodeHash common.Hash + KeyPrefix, KeySuffix []byte +} + +func (err *MissingNodeError) Error() string { + return fmt.Sprintf("Missing trie node %064x", err.NodeHash) +} diff --git a/trie/iterator.go b/trie/iterator.go index 38555fe08d..5f205e0817 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -16,7 +16,12 @@ package trie -import "bytes" +import ( + "bytes" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) type Iterator struct { trie *Trie @@ -100,7 +105,11 @@ func (self *Iterator) next(node interface{}, key []byte, isIterStart bool) []byt } case hashNode: - return self.next(self.trie.resolveHash(node), key, isIterStart) + rn, err := self.trie.resolveHash(node, nil, nil) + if err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled trie error: %v", err) + } + return self.next(rn, key, isIterStart) } return nil } @@ -127,7 +136,11 @@ func (self *Iterator) key(node interface{}) []byte { } } case hashNode: - return self.key(self.trie.resolveHash(node)) + rn, err := self.trie.resolveHash(node, nil, nil) + if err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled trie error: %v", err) + } + return self.key(rn) } return nil diff --git a/trie/proof.go b/trie/proof.go index a705c49db0..2e88bb50bc 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -7,6 +7,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" ) @@ -39,7 +41,14 @@ func (t *Trie) Prove(key []byte) []rlp.RawValue { case nil: return nil case hashNode: - tn = t.resolveHash(n) + var err error + tn, err = t.resolveHash(n, nil, nil) + if err != nil { + if glog.V(logger.Error) { + glog.Errorf("Unhandled trie error: %v", err) + } + return nil + } default: panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 47d1934d05..caeef3c3a3 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -21,6 +21,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" ) var secureKeyPrefix = []byte("secure-key-") @@ -46,8 +48,8 @@ type SecureTrie struct { // NewSecure creates a trie with an existing root node from db. // // If root is the zero hash or the sha3 hash of an empty string, the -// trie is initially empty. Otherwise, New will panics if db is nil -// and returns ErrMissingRoot if the root node cannpt be found. +// trie is initially empty. Otherwise, New will panic if db is nil +// and returns MissingNodeError if the root node cannot be found. // Accessing the trie loads nodes from db on demand. func NewSecure(root common.Hash, db Database) (*SecureTrie, error) { if db == nil { @@ -63,7 +65,18 @@ func NewSecure(root common.Hash, db Database) (*SecureTrie, error) { // Get returns the value for key stored in the trie. // The value bytes must not be modified by the caller. func (t *SecureTrie) Get(key []byte) []byte { - return t.Trie.Get(t.hashKey(key)) + res, err := t.TryGet(key) + if err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled trie error: %v", err) + } + return res +} + +// TryGet returns the value for key stored in the trie. +// The value bytes must not be modified by the caller. +// If a node was not found in the database, a MissingNodeError is returned. +func (t *SecureTrie) TryGet(key []byte) ([]byte, error) { + return t.Trie.TryGet(t.hashKey(key)) } // Update associates key with value in the trie. Subsequent calls to @@ -73,14 +86,40 @@ func (t *SecureTrie) Get(key []byte) []byte { // The value bytes must not be modified by the caller while they are // stored in the trie. func (t *SecureTrie) Update(key, value []byte) { + if err := t.TryUpdate(key, value); err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled trie error: %v", err) + } +} + +// TryUpdate associates key with value in the trie. Subsequent calls to +// Get will return value. If value has length zero, any existing value +// is deleted from the trie and calls to Get will return nil. +// +// The value bytes must not be modified by the caller while they are +// stored in the trie. +// +// If a node was not found in the database, a MissingNodeError is returned. +func (t *SecureTrie) TryUpdate(key, value []byte) error { hk := t.hashKey(key) - t.Trie.Update(hk, value) + err := t.Trie.TryUpdate(hk, value) + if err != nil { + return err + } t.Trie.db.Put(t.secKey(hk), key) + return nil } // Delete removes any existing value for key from the trie. func (t *SecureTrie) Delete(key []byte) { - t.Trie.Delete(t.hashKey(key)) + if err := t.TryDelete(key); err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled trie error: %v", err) + } +} + +// TryDelete removes any existing value for key from the trie. +// If a node was not found in the database, a MissingNodeError is returned. +func (t *SecureTrie) TryDelete(key []byte) error { + return t.Trie.TryDelete(t.hashKey(key)) } // GetKey returns the sha3 preimage of a hashed key that was diff --git a/trie/trie.go b/trie/trie.go index a3a383fb58..717296e27d 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -19,7 +19,6 @@ package trie import ( "bytes" - "errors" "fmt" "hash" @@ -44,7 +43,10 @@ var ( emptyState = crypto.Sha3Hash(nil) ) -var ErrMissingRoot = errors.New("missing root node") +// ClearGlobalCache clears the global trie cache +func ClearGlobalCache() { + globalCache.Clear() +} // Database must be implemented by backing stores for the trie. type Database interface { @@ -67,8 +69,9 @@ type DatabaseWriter interface { // // Trie is not safe for concurrent use. type Trie struct { - root node - db Database + root node + db Database + originalRoot common.Hash *hasher } @@ -76,16 +79,19 @@ type Trie struct { // // If root is the zero hash or the sha3 hash of an empty string, the // trie is initially empty and does not require a database. Otherwise, -// New will panics if db is nil or root does not exist in the -// database. Accessing the trie loads nodes from db on demand. +// New will panic if db is nil and returns a MissingNodeError if root does +// not exist in the database. Accessing the trie loads nodes from db on demand. func New(root common.Hash, db Database) (*Trie, error) { - trie := &Trie{db: db} + trie := &Trie{db: db, originalRoot: root} if (root != common.Hash{}) && root != emptyRoot { if db == nil { panic("trie.New: cannot use existing root without a database") } if v, _ := trie.db.Get(root[:]); len(v) == 0 { - return nil, ErrMissingRoot + return nil, &MissingNodeError{ + RootHash: root, + NodeHash: root, + } } trie.root = hashNode(root.Bytes()) } @@ -100,28 +106,44 @@ func (t *Trie) Iterator() *Iterator { // Get returns the value for key stored in the trie. // The value bytes must not be modified by the caller. func (t *Trie) Get(key []byte) []byte { + res, err := t.TryGet(key) + if err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled trie error: %v", err) + } + return res +} + +// TryGet returns the value for key stored in the trie. +// The value bytes must not be modified by the caller. +// If a node was not found in the database, a MissingNodeError is returned. +func (t *Trie) TryGet(key []byte) ([]byte, error) { key = compactHexDecode(key) + pos := 0 tn := t.root - for len(key) > 0 { + for pos < len(key) { switch n := tn.(type) { case shortNode: - if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { - return nil + if len(key)-pos < len(n.Key) || !bytes.Equal(n.Key, key[pos:pos+len(n.Key)]) { + return nil, nil } tn = n.Val - key = key[len(n.Key):] + pos += len(n.Key) case fullNode: - tn = n[key[0]] - key = key[1:] + tn = n[key[pos]] + pos++ case nil: - return nil + return nil, nil case hashNode: - tn = t.resolveHash(n) + var err error + tn, err = t.resolveHash(n, key[:pos], key[pos:]) + if err != nil { + return nil, err + } default: panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) } } - return tn.(valueNode) + return tn.(valueNode), nil } // Update associates key with value in the trie. Subsequent calls to @@ -131,17 +153,40 @@ func (t *Trie) Get(key []byte) []byte { // The value bytes must not be modified by the caller while they are // stored in the trie. func (t *Trie) Update(key, value []byte) { - k := compactHexDecode(key) - if len(value) != 0 { - t.root = t.insert(t.root, k, valueNode(value)) - } else { - t.root = t.delete(t.root, k) + if err := t.TryUpdate(key, value); err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled trie error: %v", err) } } -func (t *Trie) insert(n node, key []byte, value node) node { +// TryUpdate associates key with value in the trie. Subsequent calls to +// Get will return value. If value has length zero, any existing value +// is deleted from the trie and calls to Get will return nil. +// +// The value bytes must not be modified by the caller while they are +// stored in the trie. +// +// If a node was not found in the database, a MissingNodeError is returned. +func (t *Trie) TryUpdate(key, value []byte) error { + k := compactHexDecode(key) + if len(value) != 0 { + n, err := t.insert(t.root, nil, k, valueNode(value)) + if err != nil { + return err + } + t.root = n + } else { + n, err := t.delete(t.root, nil, k) + if err != nil { + return err + } + t.root = n + } + return nil +} + +func (t *Trie) insert(n node, prefix, key []byte, value node) (node, error) { if len(key) == 0 { - return value + return value, nil } switch n := n.(type) { case shortNode: @@ -149,25 +194,40 @@ func (t *Trie) insert(n node, key []byte, value node) node { // If the whole key matches, keep this short node as is // and only update the value. if matchlen == len(n.Key) { - return shortNode{n.Key, t.insert(n.Val, key[matchlen:], value)} + nn, err := t.insert(n.Val, append(prefix, key[:matchlen]...), key[matchlen:], value) + if err != nil { + return nil, err + } + return shortNode{n.Key, nn}, nil } // Otherwise branch out at the index where they differ. var branch fullNode - branch[n.Key[matchlen]] = t.insert(nil, n.Key[matchlen+1:], n.Val) - branch[key[matchlen]] = t.insert(nil, key[matchlen+1:], value) + var err error + branch[n.Key[matchlen]], err = t.insert(nil, append(prefix, n.Key[:matchlen+1]...), n.Key[matchlen+1:], n.Val) + if err != nil { + return nil, err + } + branch[key[matchlen]], err = t.insert(nil, append(prefix, key[:matchlen+1]...), key[matchlen+1:], value) + if err != nil { + return nil, err + } // Replace this shortNode with the branch if it occurs at index 0. if matchlen == 0 { - return branch + return branch, nil } // Otherwise, replace it with a short node leading up to the branch. - return shortNode{key[:matchlen], branch} + return shortNode{key[:matchlen], branch}, nil case fullNode: - n[key[0]] = t.insert(n[key[0]], key[1:], value) - return n + nn, err := t.insert(n[key[0]], append(prefix, key[0]), key[1:], value) + if err != nil { + return nil, err + } + n[key[0]] = nn + return n, nil case nil: - return shortNode{key, value} + return shortNode{key, value}, nil case hashNode: // We've hit a part of the trie that isn't loaded yet. Load @@ -176,7 +236,11 @@ func (t *Trie) insert(n node, key []byte, value node) node { // // TODO: track whether insertion changed the value and keep // n as a hash node if it didn't. - return t.insert(t.resolveHash(n), key, value) + rn, err := t.resolveHash(n, prefix, key) + if err != nil { + return nil, err + } + return t.insert(rn, prefix, key, value) default: panic(fmt.Sprintf("%T: invalid node: %v", n, n)) @@ -185,28 +249,44 @@ func (t *Trie) insert(n node, key []byte, value node) node { // Delete removes any existing value for key from the trie. func (t *Trie) Delete(key []byte) { + if err := t.TryDelete(key); err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled trie error: %v", err) + } +} + +// TryDelete removes any existing value for key from the trie. +// If a node was not found in the database, a MissingNodeError is returned. +func (t *Trie) TryDelete(key []byte) error { k := compactHexDecode(key) - t.root = t.delete(t.root, k) + n, err := t.delete(t.root, nil, k) + if err != nil { + return err + } + t.root = n + return nil } // delete returns the new root of the trie with key deleted. // It reduces the trie to minimal form by simplifying // nodes on the way up after deleting recursively. -func (t *Trie) delete(n node, key []byte) node { +func (t *Trie) delete(n node, prefix, key []byte) (node, error) { switch n := n.(type) { case shortNode: matchlen := prefixLen(key, n.Key) if matchlen < len(n.Key) { - return n // don't replace n on mismatch + return n, nil // don't replace n on mismatch } if matchlen == len(key) { - return nil // remove n entirely for whole matches + return nil, nil // remove n entirely for whole matches } // The key is longer than n.Key. Remove the remaining suffix // from the subtrie. Child can never be nil here since the // subtrie must contain at least two other values with keys // longer than n.Key. - child := t.delete(n.Val, key[len(n.Key):]) + child, err := t.delete(n.Val, append(prefix, key[:len(n.Key)]...), key[len(n.Key):]) + if err != nil { + return nil, err + } switch child := child.(type) { case shortNode: // Deleting from the subtrie reduced it to another @@ -215,13 +295,17 @@ func (t *Trie) delete(n node, key []byte) node { // always creates a new slice) instead of append to // avoid modifying n.Key since it might be shared with // other nodes. - return shortNode{concat(n.Key, child.Key...), child.Val} + return shortNode{concat(n.Key, child.Key...), child.Val}, nil default: - return shortNode{n.Key, child} + return shortNode{n.Key, child}, nil } case fullNode: - n[key[0]] = t.delete(n[key[0]], key[1:]) + nn, err := t.delete(n[key[0]], append(prefix, key[0]), key[1:]) + if err != nil { + return nil, err + } + n[key[0]] = nn // Check how many non-nil entries are left after deleting and // reduce the full node to a short node if only one entry is // left. Since n must've contained at least two children @@ -250,21 +334,24 @@ func (t *Trie) delete(n node, key []byte) node { // shortNode{..., shortNode{...}}. Since the entry // might not be loaded yet, resolve it just for this // check. - cnode := t.resolve(n[pos]) + cnode, err := t.resolve(n[pos], prefix, []byte{byte(pos)}) + if err != nil { + return nil, err + } if cnode, ok := cnode.(shortNode); ok { k := append([]byte{byte(pos)}, cnode.Key...) - return shortNode{k, cnode.Val} + return shortNode{k, cnode.Val}, nil } } // Otherwise, n is replaced by a one-nibble short node // containing the child. - return shortNode{[]byte{byte(pos)}, n[pos]} + return shortNode{[]byte{byte(pos)}, n[pos]}, nil } // n still contains at least two values and cannot be reduced. - return n + return n, nil case nil: - return nil + return nil, nil case hashNode: // We've hit a part of the trie that isn't loaded yet. Load @@ -273,7 +360,11 @@ func (t *Trie) delete(n node, key []byte) node { // // TODO: track whether deletion actually hit a key and keep // n as a hash node if it didn't. - return t.delete(t.resolveHash(n), key) + rn, err := t.resolveHash(n, prefix, key) + if err != nil { + return nil, err + } + return t.delete(rn, prefix, key) default: panic(fmt.Sprintf("%T: invalid node: %v (%v)", n, n, key)) @@ -287,34 +378,31 @@ func concat(s1 []byte, s2 ...byte) []byte { return r } -func (t *Trie) resolve(n node) node { +func (t *Trie) resolve(n node, prefix, suffix []byte) (node, error) { if n, ok := n.(hashNode); ok { - return t.resolveHash(n) + return t.resolveHash(n, prefix, suffix) } - return n + return n, nil } -func (t *Trie) resolveHash(n hashNode) node { +func (t *Trie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) { if v, ok := globalCache.Get(n); ok { - return v + return v, nil } enc, err := t.db.Get(n) if err != nil || enc == nil { - // TODO: This needs to be improved to properly distinguish errors. - // Disk I/O errors shouldn't produce nil (and cause a - // consensus failure or weird crash), but it is unclear how - // they could be handled because the entire stack above the trie isn't - // prepared to cope with missing state nodes. - if glog.V(logger.Error) { - glog.Errorf("Dangling hash node ref %x: %v", n, err) + return nil, &MissingNodeError{ + RootHash: t.originalRoot, + NodeHash: common.BytesToHash(n), + KeyPrefix: prefix, + KeySuffix: suffix, } - return nil } dec := mustDecodeNode(n, enc) if dec != nil { globalCache.Put(n, dec) } - return dec + return dec, nil } // Root returns the root hash of the trie. diff --git a/trie/trie_test.go b/trie/trie_test.go index c96861bedb..35d043cdf1 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -64,11 +64,84 @@ func TestMissingRoot(t *testing.T) { if trie != nil { t.Error("New returned non-nil trie for invalid root") } - if err != ErrMissingRoot { + if _, ok := err.(*MissingNodeError); !ok { t.Error("New returned wrong error: %v", err) } } +func TestMissingNode(t *testing.T) { + db, _ := ethdb.NewMemDatabase() + trie, _ := New(common.Hash{}, db) + updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer") + updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") + root, _ := trie.Commit() + + ClearGlobalCache() + + trie, _ = New(root, db) + _, err := trie.TryGet([]byte("120000")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + trie, _ = New(root, db) + _, err = trie.TryGet([]byte("120099")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + trie, _ = New(root, db) + _, err = trie.TryGet([]byte("123456")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + trie, _ = New(root, db) + err = trie.TryUpdate([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + trie, _ = New(root, db) + err = trie.TryDelete([]byte("123456")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + db.Delete(common.FromHex("e1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9")) + ClearGlobalCache() + + trie, _ = New(root, db) + _, err = trie.TryGet([]byte("120000")) + if _, ok := err.(*MissingNodeError); !ok { + t.Errorf("Wrong error: %v", err) + } + + trie, _ = New(root, db) + _, err = trie.TryGet([]byte("120099")) + if _, ok := err.(*MissingNodeError); !ok { + t.Errorf("Wrong error: %v", err) + } + + trie, _ = New(root, db) + _, err = trie.TryGet([]byte("123456")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + trie, _ = New(root, db) + err = trie.TryUpdate([]byte("120099"), []byte("zxcv")) + if _, ok := err.(*MissingNodeError); !ok { + t.Errorf("Wrong error: %v", err) + } + + trie, _ = New(root, db) + err = trie.TryDelete([]byte("123456")) + if _, ok := err.(*MissingNodeError); !ok { + t.Errorf("Wrong error: %v", err) + } +} + func TestInsert(t *testing.T) { trie := newEmpty() From 61ca14bc44dce04a125c9f633b047b8bbe7b3a83 Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Tue, 1 Dec 2015 11:01:40 +0100 Subject: [PATCH 17/78] eth: use global event mux instead --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/backend.go b/eth/backend.go index 0369f6afd2..91f02db72a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -191,7 +191,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { shutdownChan: make(chan bool), chainDb: chainDb, dappDb: dappDb, - eventMux: &event.TypeMux{}, + eventMux: ctx.EventMux, accountManager: config.AccountManager, etherbase: config.Etherbase, netVersionId: config.NetworkId, From 391ca61678418a15f14909b611458fdc0894bbe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 1 Dec 2015 12:40:55 +0200 Subject: [PATCH 18/78] Makefile: build ARM v5/v6/v7, drop high API mobile clients --- Makefile | 61 ++++++++++++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 1f9bf0db85..12802647e3 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,10 @@ # don't need to bother with make. .PHONY: geth geth-cross evm all test travis-test-with-coverage xgo clean -.PHONY: geth-linux geth-linux-arm geth-linux-386 geth-linux-amd64 +.PHONY: geth-linux geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-386 geth-linux-amd64 .PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64 .PHONY: geth-windows geth-windows-386 geth-windows-amd64 -.PHONY: geth-android geth-android-16 geth-android-21 -.PHONY: geth-ios geth-ios-5.0 geth-ios-8.1 +.PHONY: geth-android geth-ios GOBIN = build/bin @@ -24,15 +23,10 @@ geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios @echo "Full cross compilation done:" @ls -l $(GOBIN)/geth-* -geth-linux: xgo geth-linux-arm geth-linux-386 geth-linux-amd64 +geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm @echo "Linux cross compilation done:" @ls -l $(GOBIN)/geth-linux-* -geth-linux-arm: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm -v $(shell build/flags.sh) ./cmd/geth - @echo "Linux ARM cross compilation done:" - @ls -l $(GOBIN)/geth-linux-* | grep arm - geth-linux-386: xgo build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux 386 cross compilation done:" @@ -43,7 +37,26 @@ geth-linux-amd64: xgo @echo "Linux amd64 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep amd64 -geth-darwin: xgo geth-darwin-386 geth-darwin-amd64 +geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 + @echo "Linux ARM cross compilation done:" + @ls -l $(GOBIN)/geth-linux-* | grep arm + +geth-linux-arm-5: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/geth + @echo "Linux ARMv5 cross compilation done:" + @ls -l $(GOBIN)/geth-linux-* | grep arm-5 + +geth-linux-arm-6: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/geth + @echo "Linux ARMv6 cross compilation done:" + @ls -l $(GOBIN)/geth-linux-* | grep arm-6 + +geth-linux-arm-7: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/geth + @echo "Linux ARMv7 cross compilation done:" + @ls -l $(GOBIN)/geth-linux-* | grep arm-7 + +geth-darwin: geth-darwin-386 geth-darwin-amd64 @echo "Darwin cross compilation done:" @ls -l $(GOBIN)/geth-darwin-* @@ -57,7 +70,7 @@ geth-darwin-amd64: xgo @echo "Darwin amd64 cross compilation done:" @ls -l $(GOBIN)/geth-darwin-* | grep amd64 -geth-windows: xgo geth-windows-386 geth-windows-amd64 +geth-windows: geth-windows-386 geth-windows-amd64 @echo "Windows cross compilation done:" @ls -l $(GOBIN)/geth-windows-* @@ -71,34 +84,16 @@ geth-windows-amd64: xgo @echo "Windows amd64 cross compilation done:" @ls -l $(GOBIN)/geth-windows-* | grep amd64 -geth-android: xgo geth-android-16 geth-android-21 +geth-android: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=android/* -v $(shell build/flags.sh) ./cmd/geth @echo "Android cross compilation done:" @ls -l $(GOBIN)/geth-android-* -geth-android-16: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=android-16/* -v $(shell build/flags.sh) ./cmd/geth - @echo "Android 16 cross compilation done:" - @ls -l $(GOBIN)/geth-android-16-* - -geth-android-21: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=android-21/* -v $(shell build/flags.sh) ./cmd/geth - @echo "Android 21 cross compilation done:" - @ls -l $(GOBIN)/geth-android-21-* - -geth-ios: xgo geth-ios-5.0 geth-ios-8.1 +geth-ios: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios/* -v $(shell build/flags.sh) ./cmd/geth @echo "iOS cross compilation done:" @ls -l $(GOBIN)/geth-ios-* -geth-ios-5.0: - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-5.0/* -v $(shell build/flags.sh) ./cmd/geth - @echo "iOS 5.0 cross compilation done:" - @ls -l $(GOBIN)/geth-ios-5.0-* - -geth-ios-8.1: - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-8.1/* -v $(shell build/flags.sh) ./cmd/geth - @echo "iOS 8.1 cross compilation done:" - @ls -l $(GOBIN)/geth-ios-8.1-* - evm: build/env.sh $(GOROOT)/bin/go install -v $(shell build/flags.sh) ./cmd/evm @echo "Done building." From d648e96b3d2c52074b6085ceb26e5f2755257791 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 1 Dec 2015 11:20:49 +0100 Subject: [PATCH 19/78] cmd/utils: restore starting geth without any accounts and etherbase Also remove some duplication around address/index parsing. --- cmd/geth/main.go | 7 +++++-- cmd/utils/flags.go | 32 +++++++++++++------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 3a54718456..6ec30cebcf 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -464,9 +464,12 @@ func execScripts(ctx *cli.Context) { node.Stop() } +// tries unlocking the specified account a few times. func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (common.Address, string) { - // Try to unlock the specified account a few times - account := utils.MakeAddress(accman, address) + account, err := utils.MakeAddress(accman, address) + if err != nil { + utils.Fatalf("Unlock error: %v", err) + } for trials := 0; trials < 3; trials++ { prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 53126f9e5d..839ec3f027 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -518,47 +518,41 @@ func MakeAccountManager(ctx *cli.Context) *accounts.Manager { // MakeAddress converts an account specified directly as a hex encoded string or // a key index in the key store to an internal account representation. -func MakeAddress(accman *accounts.Manager, account string) common.Address { +func MakeAddress(accman *accounts.Manager, account string) (a common.Address, err error) { // If the specified account is a valid address, return it if common.IsHexAddress(account) { - return common.HexToAddress(account) + return common.HexToAddress(account), nil } // Otherwise try to interpret the account as a keystore index index, err := strconv.Atoi(account) if err != nil { - Fatalf("Invalid account address or index: '%s'", account) + return a, fmt.Errorf("invalid account address or index %q", account) } hex, err := accman.AddressByIndex(index) if err != nil { - Fatalf("Failed to retrieve requested account #%d: %v", index, err) + return a, fmt.Errorf("can't get account #%d (%v)", index, err) } - return common.HexToAddress(hex) + return common.HexToAddress(hex), nil } // MakeEtherbase retrieves the etherbase either from the directly specified // command line flags or from the keystore if CLI indexed. func MakeEtherbase(accman *accounts.Manager, ctx *cli.Context) common.Address { - // If the specified etherbase is a valid address, return it - etherbase := ctx.GlobalString(EtherbaseFlag.Name) - if common.IsHexAddress(etherbase) { - return common.HexToAddress(etherbase) - } - // If no etherbase was specified and no accounts are known, bail out accounts, _ := accman.Accounts() - if etherbase == "" && len(accounts) == 0 { + if !ctx.GlobalIsSet(EtherbaseFlag.Name) && len(accounts) == 0 { glog.V(logger.Error).Infoln("WARNING: No etherbase set and no accounts found as default") return common.Address{} } - // Otherwise try to interpret the parameter as a keystore index - index, err := strconv.Atoi(etherbase) - if err != nil { - Fatalf("Invalid account address or index: '%s'", etherbase) + etherbase := ctx.GlobalString(EtherbaseFlag.Name) + if etherbase == "" { + return common.Address{} } - hex, err := accman.AddressByIndex(index) + // If the specified etherbase is a valid address, return it + addr, err := MakeAddress(accman, etherbase) if err != nil { - Fatalf("Failed to set requested account #%d as etherbase: %v", index, err) + Fatalf("Option %q: %v", EtherbaseFlag.Name, err) } - return common.HexToAddress(hex) + return addr } // MakeMinerExtra resolves extradata for the miner from the set command line flags From 9901a40f047f55d1a756805bdeed3997d071c3d1 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 1 Dec 2015 00:11:24 +0100 Subject: [PATCH 20/78] core: added a new RemovedLogEvent When a chain reorganisation occurs we collect the logs that were deleted during the chain reorganisation. The removed logs are posted to the event mux indicating that those were deleted during the reorg. --- core/blockchain.go | 22 +++++++++++++++++++-- core/blockchain_test.go | 43 +++++++++++++++++++++++++++++++++++++++++ core/chain_makers.go | 4 ++-- core/events.go | 3 +++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 5a6795b3ec..dc6e0835c5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1240,6 +1240,17 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error { oldStart = oldBlock newStart = newBlock deletedTxs types.Transactions + deletedLogs vm.Logs + // collectLogs collects the logs that were generated during the + // processing of the block that corresponds with the given hash. + // These logs are later announced as deleted. + collectLogs = func(h common.Hash) { + // Coalesce logs + receipts := GetBlockReceipts(self.chainDb, h) + for _, receipt := range receipts { + deletedLogs = append(deletedLogs, receipt.Logs...) + } + } ) // first reduce whoever is higher bound @@ -1247,6 +1258,8 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error { // reduce old chain for oldBlock = oldBlock; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = self.GetBlock(oldBlock.ParentHash()) { deletedTxs = append(deletedTxs, oldBlock.Transactions()...) + + collectLogs(oldBlock.Hash()) } } else { // reduce new chain and append new chain blocks for inserting later on @@ -1269,6 +1282,7 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error { } newChain = append(newChain, newBlock) deletedTxs = append(deletedTxs, oldBlock.Transactions()...) + collectLogs(oldBlock.Hash()) oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash()) if oldBlock == nil { @@ -1302,7 +1316,6 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error { if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil { return err } - addedTxs = append(addedTxs, block.Transactions()...) } @@ -1316,7 +1329,12 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error { } // Must be posted in a goroutine because of the transaction pool trying // to acquire the chain manager lock - go self.eventMux.Post(RemovedTransactionEvent{diff}) + if len(diff) > 0 { + go self.eventMux.Post(RemovedTransactionEvent{diff}) + } + if len(deletedLogs) > 0 { + go self.eventMux.Post(RemovedLogEvent{deletedLogs}) + } return nil } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 6e1c5fdc76..b4ac1696a3 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -963,3 +963,46 @@ func TestChainTxReorgs(t *testing.T) { } } } + +func TestLogReorgs(t *testing.T) { + params.MinGasLimit = big.NewInt(125000) // Minimum the gas limit may ever be. + params.GenesisGasLimit = big.NewInt(3141592) // Gas limit of the Genesis block. + + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + db, _ = ethdb.NewMemDatabase() + // this code generates a log + code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") + ) + genesis := WriteGenesisBlockForTesting(db, + GenesisAccount{addr1, big.NewInt(10000000000000)}, + ) + + evmux := &event.TypeMux{} + blockchain, _ := NewBlockChain(db, FakePow{}, evmux) + + subs := evmux.Subscribe(RemovedLogEvent{}) + chain, _ := GenerateChain(genesis, db, 2, func(i int, gen *BlockGen) { + if i == 1 { + tx, err := types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code).SignECDSA(key1) + if err != nil { + t.Fatalf("failed to create tx: %v", err) + } + gen.AddTx(tx) + } + }) + if _, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + chain, _ = GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) {}) + if _, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("failed to insert forked chain: %v", err) + } + + ev := <-subs.Chan() + if len(ev.Data.(RemovedLogEvent).Logs) == 0 { + t.Error("expected logs") + } +} diff --git a/core/chain_makers.go b/core/chain_makers.go index 6d3152d977..4f6fa39896 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -90,6 +90,7 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { if b.gasPool == nil { b.SetCoinbase(common.Address{}) } + b.statedb.StartRecord(tx.Hash(), common.Hash{}, len(b.txs)) _, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.gasPool) if err != nil { panic(err) @@ -97,8 +98,7 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { root := b.statedb.IntermediateRoot() b.header.GasUsed.Add(b.header.GasUsed, gas) receipt := types.NewReceipt(root.Bytes(), b.header.GasUsed) - logs := b.statedb.GetLogs(tx.Hash()) - receipt.Logs = logs + receipt.Logs = b.statedb.GetLogs(tx.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) b.txs = append(b.txs, tx) b.receipts = append(b.receipts, receipt) diff --git a/core/events.go b/core/events.go index 8cf230dda4..1a760c71cf 100644 --- a/core/events.go +++ b/core/events.go @@ -39,6 +39,9 @@ type NewMinedBlockEvent struct{ Block *types.Block } // RemovedTransactionEvent is posted when a reorg happens type RemovedTransactionEvent struct{ Txs types.Transactions } +// RemovedLogEvent is posted when a reorg happens +type RemovedLogEvent struct{ Logs vm.Logs } + // ChainSplit is posted when a new head is detected type ChainSplitEvent struct { Block *types.Block From 2e43414c7931322f9eac114b253e6fbdcdc5b678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 1 Dec 2015 13:20:43 +0200 Subject: [PATCH 21/78] accounts: increase re-lock timeout to account for slow CI servers --- accounts/accounts_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index d7a8a2b85f..55ddecdea3 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -68,7 +68,7 @@ func TestTimedUnlock(t *testing.T) { } // Signing fails again after automatic locking - time.Sleep(150 * time.Millisecond) + time.Sleep(350 * time.Millisecond) _, err = am.Sign(a1, testSigData) if err != ErrLocked { t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) From 98d68cfa91abbfe97cd0544291e39f7376960210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 2 Dec 2015 16:57:15 +0200 Subject: [PATCH 22/78] Makefile: enable 64-bit ARM builds on Linux --- Makefile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 12802647e3..297f3ac006 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,8 @@ # don't need to bother with make. .PHONY: geth geth-cross evm all test travis-test-with-coverage xgo clean -.PHONY: geth-linux geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-386 geth-linux-amd64 +.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 .PHONY: geth-windows geth-windows-386 geth-windows-amd64 .PHONY: geth-android geth-ios @@ -37,7 +38,7 @@ geth-linux-amd64: xgo @echo "Linux amd64 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep amd64 -geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 +geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64 @echo "Linux ARM cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm @@ -56,6 +57,11 @@ geth-linux-arm-7: xgo @echo "Linux ARMv7 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm-7 +geth-linux-arm64: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/geth + @echo "Linux ARM64 cross compilation done:" + @ls -l $(GOBIN)/geth-linux-* | grep arm64 + geth-darwin: geth-darwin-386 geth-darwin-amd64 @echo "Darwin cross compilation done:" @ls -l $(GOBIN)/geth-darwin-* From abb53644c63593ad816f2c5b5181568769234323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 3 Dec 2015 11:45:35 +0200 Subject: [PATCH 23/78] p2p: always allow dynamic dials if network not disabled --- p2p/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/server.go b/p2p/server.go index ee670b10ed..7991585f17 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -337,7 +337,7 @@ func (srv *Server) Start() (err error) { srv.ntab = ntab } - dynPeers := srv.MaxPeers / 2 + dynPeers := (srv.MaxPeers + 1) / 2 if !srv.Discovery { dynPeers = 0 } From 9acec62cf8f26eb6560e6fb35c695725a0d67bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 3 Dec 2015 20:33:24 +0200 Subject: [PATCH 24/78] Makefile: support 64bit ARM iOS cross builds --- Makefile | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 297f3ac006..76cb819b6a 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,8 @@ .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 .PHONY: geth-windows geth-windows-386 geth-windows-amd64 -.PHONY: geth-android geth-ios +.PHONY: geth-android +.PHONY: geth-ios geth-ios-arm-7 geth-ios-arm64 GOBIN = build/bin @@ -95,11 +96,20 @@ geth-android: xgo @echo "Android cross compilation done:" @ls -l $(GOBIN)/geth-android-* -geth-ios: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios/* -v $(shell build/flags.sh) ./cmd/geth +geth-ios: geth-ios-arm-7 geth-ios-arm64 @echo "iOS cross compilation done:" @ls -l $(GOBIN)/geth-ios-* +geth-ios-arm-7: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios/arm-7 -v $(shell build/flags.sh) ./cmd/geth + @echo "iOS ARMv7 cross compilation done:" + @ls -l $(GOBIN)/geth-ios-* | grep arm-7 + +geth-ios-arm64: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-7.0/arm64 -v $(shell build/flags.sh) ./cmd/geth + @echo "iOS ARM64 cross compilation done:" + @ls -l $(GOBIN)/geth-ios-* | grep arm64 + evm: build/env.sh $(GOROOT)/bin/go install -v $(shell build/flags.sh) ./cmd/evm @echo "Done building." From 02356b36d618d5633e2f097505c95bf45dd7f4ab Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 3 Dec 2015 20:04:39 +0100 Subject: [PATCH 25/78] crypto/secp256k1: remove dependency on libgmp Turns out we actually don't need it, USE_NUM_NONE works because we also set USE_FIELD_INV_BUILTIN. --- .travis.yml | 4 ---- Makefile | 27 +++++++++++++-------------- README.md | 2 +- crypto/secp256k1/README.md | 25 ------------------------- crypto/secp256k1/secp256.go | 9 +-------- 5 files changed, 15 insertions(+), 52 deletions(-) delete mode 100644 crypto/secp256k1/README.md diff --git a/.travis.yml b/.travis.yml index 13211f7363..c1d545c548 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,10 +20,6 @@ env: global: - secure: "U2U1AmkU4NJBgKR/uUAebQY87cNL0+1JHjnLOmmXwxYYyj5ralWb1aSuSH3qSXiT93qLBmtaUkuv9fberHVqrbAeVlztVdUsKAq7JMQH+M99iFkC9UiRMqHmtjWJ0ok4COD1sRYixxi21wb/JrMe3M1iL4QJVS61iltjHhVdM64=" sudo: false -addons: - apt: - packages: - - libgmp3-dev notifications: webhooks: urls: diff --git a/Makefile b/Makefile index 76cb819b6a..a2332e53fa 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,6 @@ GOBIN = build/bin -CROSSDEPS = https://gmplib.org/download/gmp/gmp-6.1.0.tar.bz2 MODE ?= default GO ?= latest @@ -30,12 +29,12 @@ geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm @ls -l $(GOBIN)/geth-linux-* geth-linux-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux 386 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep 386 geth-linux-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux amd64 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep amd64 @@ -44,22 +43,22 @@ geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-ar @ls -l $(GOBIN)/geth-linux-* | grep arm geth-linux-arm-5: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux ARMv5 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm-5 geth-linux-arm-6: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux ARMv6 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm-6 geth-linux-arm-7: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux ARMv7 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm-7 geth-linux-arm64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux ARM64 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm64 @@ -68,12 +67,12 @@ geth-darwin: geth-darwin-386 geth-darwin-amd64 @ls -l $(GOBIN)/geth-darwin-* geth-darwin-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Darwin 386 cross compilation done:" @ls -l $(GOBIN)/geth-darwin-* | grep 386 geth-darwin-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/geth @echo "Darwin amd64 cross compilation done:" @ls -l $(GOBIN)/geth-darwin-* | grep amd64 @@ -82,17 +81,17 @@ geth-windows: geth-windows-386 geth-windows-amd64 @ls -l $(GOBIN)/geth-windows-* geth-windows-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Windows 386 cross compilation done:" @ls -l $(GOBIN)/geth-windows-* | grep 386 geth-windows-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/geth @echo "Windows amd64 cross compilation done:" @ls -l $(GOBIN)/geth-windows-* | grep amd64 geth-android: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --targets=android/* -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=android/* -v $(shell build/flags.sh) ./cmd/geth @echo "Android cross compilation done:" @ls -l $(GOBIN)/geth-android-* @@ -101,12 +100,12 @@ geth-ios: geth-ios-arm-7 geth-ios-arm64 @ls -l $(GOBIN)/geth-ios-* geth-ios-arm-7: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios/arm-7 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=ios/arm-7 -v $(shell build/flags.sh) ./cmd/geth @echo "iOS ARMv7 cross compilation done:" @ls -l $(GOBIN)/geth-ios-* | grep arm-7 geth-ios-arm64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --deps=$(CROSSDEPS) --depsargs=--disable-assembly --targets=ios-7.0/arm64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=ios-7.0/arm64 -v $(shell build/flags.sh) ./cmd/geth @echo "iOS ARM64 cross compilation done:" @ls -l $(GOBIN)/geth-ios-* | grep arm64 diff --git a/README.md b/README.md index 717e726e34..2d60e0564d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ For prerequisites and detailed build instructions please read the [Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) on the wiki. -Building geth requires two external dependencies, Go and GMP. +Building geth requires both a Go and a C compiler. You can install them using your favourite package manager. Once the dependencies are installed, run diff --git a/crypto/secp256k1/README.md b/crypto/secp256k1/README.md deleted file mode 100644 index 5a86147d48..0000000000 --- a/crypto/secp256k1/README.md +++ /dev/null @@ -1,25 +0,0 @@ -secp256k1-go -======= - -golang secp256k1 library - -Implements cryptographic operations for the secp256k1 ECDSA curve used by Bitcoin. - -Installing -=== - -GMP library headers are required to build. On Debian-based systems, the package is called `libgmp-dev`. - -``` -sudo apt-get install libgmp-dev -``` - -Now compiles with cgo! - -Test -=== - -To run tests do -``` -go tests -``` \ No newline at end of file diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index 8dc2481454..97b4bd8daa 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -21,14 +21,7 @@ package secp256k1 /* #cgo CFLAGS: -I./libsecp256k1 #cgo CFLAGS: -I./libsecp256k1/src/ -#cgo darwin CFLAGS: -I/usr/local/include -#cgo freebsd CFLAGS: -I/usr/local/include -#cgo linux,arm CFLAGS: -I/usr/local/arm/include -#cgo LDFLAGS: -lgmp -#cgo darwin LDFLAGS: -L/usr/local/lib -#cgo freebsd LDFLAGS: -L/usr/local/lib -#cgo linux,arm LDFLAGS: -L/usr/local/arm/lib -#define USE_NUM_GMP +#define USE_NUM_NONE #define USE_FIELD_10X26 #define USE_FIELD_INV_BUILTIN #define USE_SCALAR_8X32 From 1cecda8333feee3d705fd43b9b7a44b08ce583ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 4 Dec 2015 15:30:59 +0200 Subject: [PATCH 26/78] core: write individual transactions and receipts too on fast sync --- core/blockchain.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/blockchain.go b/core/blockchain.go index dc6e0835c5..9d526e352e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -980,6 +980,18 @@ func (self *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain glog.Fatal(errs[index]) return } + if err := WriteTransactions(self.chainDb, block); err != nil { + errs[index] = fmt.Errorf("failed to write individual transactions: %v", err) + atomic.AddInt32(&failed, 1) + glog.Fatal(errs[index]) + return + } + if err := WriteReceipts(self.chainDb, receipts); err != nil { + errs[index] = fmt.Errorf("failed to write individual receipts: %v", err) + atomic.AddInt32(&failed, 1) + glog.Fatal(errs[index]) + return + } atomic.AddInt32(&stats.processed, 1) } } From 032453b3e7252ebce88bef82399e1ef7dd198199 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 7 Dec 2015 17:01:29 +0100 Subject: [PATCH 27/78] crypto: "Crypto" -> "crypto" in web3 key format --- crypto/key.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crypto/key.go b/crypto/key.go index ccf284ad8d..8e2d8553bb 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -50,17 +50,17 @@ type plainKeyJSON struct { } type encryptedKeyJSONV3 struct { - Address string `json:"address"` - Crypto cryptoJSON - Id string `json:"id"` - Version int `json:"version"` + Address string `json:"address"` + Crypto cryptoJSON `json:"crypto"` + Id string `json:"id"` + Version int `json:"version"` } type encryptedKeyJSONV1 struct { - Address string `json:"address"` - Crypto cryptoJSON - Id string `json:"id"` - Version string `json:"version"` + Address string `json:"address"` + Crypto cryptoJSON `json:"crypto"` + Id string `json:"id"` + Version string `json:"version"` } type cryptoJSON struct { From 0e4deeb6e7d150c50d3ef07bb7af622b94911967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 8 Dec 2015 18:05:43 +0200 Subject: [PATCH 28/78] Makefile, cmd/geth: assemble ios xcode frameworks --- Makefile | 45 +++++++++++++++++++------------------------- cmd/geth/library.c | 24 +++++++++++++++++++++++ cmd/geth/library.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ cmd/geth/main.go | 3 ++- 4 files changed, 91 insertions(+), 27 deletions(-) create mode 100644 cmd/geth/library.c create mode 100644 cmd/geth/library.go diff --git a/Makefile b/Makefile index a2332e53fa..d5d3825d8b 100644 --- a/Makefile +++ b/Makefile @@ -7,12 +7,9 @@ .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 .PHONY: geth-windows geth-windows-386 geth-windows-amd64 -.PHONY: geth-android -.PHONY: geth-ios geth-ios-arm-7 geth-ios-arm64 +.PHONY: geth-android geth-ios geth-ios-sim GOBIN = build/bin - -MODE ?= default GO ?= latest geth: @@ -29,12 +26,12 @@ geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm @ls -l $(GOBIN)/geth-linux-* geth-linux-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux 386 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep 386 geth-linux-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux amd64 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep amd64 @@ -43,22 +40,22 @@ geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-ar @ls -l $(GOBIN)/geth-linux-* | grep arm geth-linux-arm-5: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux ARMv5 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm-5 geth-linux-arm-6: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux ARMv6 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm-6 geth-linux-arm-7: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux ARMv7 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm-7 geth-linux-arm64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/geth @echo "Linux ARM64 cross compilation done:" @ls -l $(GOBIN)/geth-linux-* | grep arm64 @@ -67,12 +64,12 @@ geth-darwin: geth-darwin-386 geth-darwin-amd64 @ls -l $(GOBIN)/geth-darwin-* geth-darwin-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Darwin 386 cross compilation done:" @ls -l $(GOBIN)/geth-darwin-* | grep 386 geth-darwin-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/geth @echo "Darwin amd64 cross compilation done:" @ls -l $(GOBIN)/geth-darwin-* | grep amd64 @@ -81,33 +78,29 @@ geth-windows: geth-windows-386 geth-windows-amd64 @ls -l $(GOBIN)/geth-windows-* geth-windows-386: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/geth @echo "Windows 386 cross compilation done:" @ls -l $(GOBIN)/geth-windows-* | grep 386 geth-windows-amd64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/geth @echo "Windows amd64 cross compilation done:" @ls -l $(GOBIN)/geth-windows-* | grep amd64 geth-android: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=android/* -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=android/* -v $(shell build/flags.sh) ./cmd/geth @echo "Android cross compilation done:" @ls -l $(GOBIN)/geth-android-* -geth-ios: geth-ios-arm-7 geth-ios-arm64 - @echo "iOS cross compilation done:" +geth-ios: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/* -v $(shell build/flags.sh) ./cmd/geth + @echo "iOS framework cross compilation done:" @ls -l $(GOBIN)/geth-ios-* -geth-ios-arm-7: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=ios/arm-7 -v $(shell build/flags.sh) ./cmd/geth - @echo "iOS ARMv7 cross compilation done:" - @ls -l $(GOBIN)/geth-ios-* | grep arm-7 - -geth-ios-arm64: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --buildmode=$(MODE) --dest=$(GOBIN) --targets=ios-7.0/arm64 -v $(shell build/flags.sh) ./cmd/geth - @echo "iOS ARM64 cross compilation done:" - @ls -l $(GOBIN)/geth-ios-* | grep arm64 +geth-ios-sim: xgo + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/amd64 -v $(shell build/flags.sh) ./cmd/geth + @echo "iOS framework simulator-only cross compilation done:" + @ls -l $(GOBIN)/geth-ios-* evm: build/env.sh $(GOROOT)/bin/go install -v $(shell build/flags.sh) ./cmd/evm diff --git a/cmd/geth/library.c b/cmd/geth/library.c new file mode 100644 index 0000000000..f738621a82 --- /dev/null +++ b/cmd/geth/library.c @@ -0,0 +1,24 @@ +// Copyright 2015 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 . + +// Simple wrapper to translate the API exposed methods and types to inthernal +// Go versions of the same types. + +#include "_cgo_export.h" + +int run(const char* args) { + return doRun((char*)args); +} diff --git a/cmd/geth/library.go b/cmd/geth/library.go new file mode 100644 index 0000000000..b8ffaaed77 --- /dev/null +++ b/cmd/geth/library.go @@ -0,0 +1,46 @@ +// Copyright 2015 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 . + +// Contains a simple library definition to allow creating a Geth instance from +// straight C code. + +package main + +// #ifdef __cplusplus +// extern "C" { +// #endif +// +// extern int run(const char*); +// +// #ifdef __cplusplus +// } +// #endif +import "C" +import ( + "fmt" + "os" + "strings" +) + +//export doRun +func doRun(args *C.char) C.int { + // This is equivalent to geth.main, just modified to handle the function arg passing + if err := app.Run(strings.Split("geth "+C.GoString(args), " ")); err != nil { + fmt.Fprintln(os.Stderr, err) + return -1 + } + return 0 +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 6ec30cebcf..8798bbb273 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -340,6 +340,8 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.ExtraDataFlag, } app.Before = func(ctx *cli.Context) error { + runtime.GOMAXPROCS(runtime.NumCPU()) + utils.SetupLogger(ctx) utils.SetupNetwork(ctx) utils.SetupVM(ctx) @@ -353,7 +355,6 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso } func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) defer logger.Flush() if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) From 03f090b30bdee27bfd8e45b0532b6674d808c8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 14 Dec 2015 14:28:14 +0200 Subject: [PATCH 29/78] Makefile, cmd/geth: support building Android archives --- Makefile | 45 +++++++++++++---------------- cmd/geth/library_android.go | 56 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 cmd/geth/library_android.go diff --git a/Makefile b/Makefile index d5d3825d8b..abf99945a8 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ .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 .PHONY: geth-windows geth-windows-386 geth-windows-amd64 -.PHONY: geth-android geth-ios geth-ios-sim +.PHONY: geth-android geth-ios GOBIN = build/bin GO ?= latest @@ -19,88 +19,83 @@ geth: geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios @echo "Full cross compilation done:" - @ls -l $(GOBIN)/geth-* + @ls -ld $(GOBIN)/geth-* geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm @echo "Linux cross compilation done:" - @ls -l $(GOBIN)/geth-linux-* + @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 @echo "Linux 386 cross compilation done:" - @ls -l $(GOBIN)/geth-linux-* | grep 386 + @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 @echo "Linux amd64 cross compilation done:" - @ls -l $(GOBIN)/geth-linux-* | grep amd64 + @ls -ld $(GOBIN)/geth-linux-* | grep amd64 geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64 @echo "Linux ARM cross compilation done:" - @ls -l $(GOBIN)/geth-linux-* | grep arm + @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 @echo "Linux ARMv5 cross compilation done:" - @ls -l $(GOBIN)/geth-linux-* | grep arm-5 + @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 @echo "Linux ARMv6 cross compilation done:" - @ls -l $(GOBIN)/geth-linux-* | grep arm-6 + @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 @echo "Linux ARMv7 cross compilation done:" - @ls -l $(GOBIN)/geth-linux-* | grep arm-7 + @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 @echo "Linux ARM64 cross compilation done:" - @ls -l $(GOBIN)/geth-linux-* | grep arm64 + @ls -ld $(GOBIN)/geth-linux-* | grep arm64 geth-darwin: geth-darwin-386 geth-darwin-amd64 @echo "Darwin cross compilation done:" - @ls -l $(GOBIN)/geth-darwin-* + @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 @echo "Darwin 386 cross compilation done:" - @ls -l $(GOBIN)/geth-darwin-* | grep 386 + @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 @echo "Darwin amd64 cross compilation done:" - @ls -l $(GOBIN)/geth-darwin-* | grep amd64 + @ls -ld $(GOBIN)/geth-darwin-* | grep amd64 geth-windows: geth-windows-386 geth-windows-amd64 @echo "Windows cross compilation done:" - @ls -l $(GOBIN)/geth-windows-* + @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 @echo "Windows 386 cross compilation done:" - @ls -l $(GOBIN)/geth-windows-* | grep 386 + @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 @echo "Windows amd64 cross compilation done:" - @ls -l $(GOBIN)/geth-windows-* | grep amd64 + @ls -ld $(GOBIN)/geth-windows-* | grep amd64 geth-android: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=android/* -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v $(shell build/flags.sh) ./cmd/geth @echo "Android cross compilation done:" - @ls -l $(GOBIN)/geth-android-* + @ls -ld $(GOBIN)/geth-android-* geth-ios: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/* -v $(shell build/flags.sh) ./cmd/geth + build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/framework -v $(shell build/flags.sh) ./cmd/geth @echo "iOS framework cross compilation done:" - @ls -l $(GOBIN)/geth-ios-* - -geth-ios-sim: xgo - build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/amd64 -v $(shell build/flags.sh) ./cmd/geth - @echo "iOS framework simulator-only cross compilation done:" - @ls -l $(GOBIN)/geth-ios-* + @ls -ld $(GOBIN)/geth-ios-* evm: build/env.sh $(GOROOT)/bin/go install -v $(shell build/flags.sh) ./cmd/evm diff --git a/cmd/geth/library_android.go b/cmd/geth/library_android.go new file mode 100644 index 0000000000..fb021bfe00 --- /dev/null +++ b/cmd/geth/library_android.go @@ -0,0 +1,56 @@ +// Copyright 2015 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 . + +// Contains specialized code for running Geth on Android. + +package main + +// #include +// #cgo LDFLAGS: -llog +import "C" +import ( + "bufio" + "os" +) + +func init() { + // Redirect the standard output and error to logcat + oldStdout, oldStderr := os.Stdout, os.Stderr + + outRead, outWrite, _ := os.Pipe() + errRead, errWrite, _ := os.Pipe() + + os.Stdout = outWrite + os.Stderr = errWrite + + go func() { + scanner := bufio.NewScanner(outRead) + for scanner.Scan() { + line := scanner.Text() + C.__android_log_write(C.ANDROID_LOG_INFO, C.CString("Stdout"), C.CString(line)) + oldStdout.WriteString(line + "\n") + } + }() + + go func() { + scanner := bufio.NewScanner(errRead) + for scanner.Scan() { + line := scanner.Text() + C.__android_log_write(C.ANDROID_LOG_INFO, C.CString("Stderr"), C.CString(line)) + oldStderr.WriteString(line + "\n") + } + }() +} From eae81465c1c815c317cd30e4de6bdf4d59df2340 Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Thu, 15 Oct 2015 16:07:19 +0200 Subject: [PATCH 30/78] rpc: new RPC implementation with pub/sub support --- accounts/account_manager.go | 15 + cmd/geth/js.go | 22 +- cmd/geth/js_test.go | 2 +- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/gethrpctest/main.go | 65 +- cmd/utils/api.go | 74 +++ cmd/utils/flags.go | 52 +- common/types.go | 49 ++ core/types/block.go | 4 + core/types/bloom9.go | 4 + core/vm/log.go | 16 + eth/api.go | 1216 +++++++++++++++++++++++++++++++++++ eth/backend.go | 60 ++ eth/downloader/api.go | 64 ++ eth/filters/api.go | 575 +++++++++++++++++ miner/api.go | 72 +++ miner/worker.go | 1 + node/node.go | 10 + node/node_example_test.go | 2 + node/service.go | 4 + node/utils_test.go | 6 + rpc/api/mergedapi.go | 8 +- rpc/api/personal_js.go | 5 + rpc/api/utils.go | 2 + rpc/comms/ipc.go | 22 +- rpc/jeth.go | 72 +++ rpc/v2/doc.go | 102 +++ rpc/v2/errors.go | 85 +++ rpc/v2/json.go | 343 ++++++++++ rpc/v2/json_test.go | 73 +++ rpc/v2/server.go | 378 +++++++++++ rpc/v2/server_test.go | 219 +++++++ rpc/v2/types.go | 352 ++++++++++ rpc/v2/types_test.go | 57 ++ rpc/v2/utils.go | 205 ++++++ whisper/api.go | 413 ++++++++++++ whisper/whisper.go | 14 + 38 files changed, 4651 insertions(+), 14 deletions(-) create mode 100644 cmd/utils/api.go create mode 100644 eth/api.go create mode 100644 eth/downloader/api.go create mode 100644 eth/filters/api.go create mode 100644 miner/api.go create mode 100644 rpc/v2/doc.go create mode 100644 rpc/v2/errors.go create mode 100644 rpc/v2/json.go create mode 100644 rpc/v2/json_test.go create mode 100644 rpc/v2/server.go create mode 100644 rpc/v2/server_test.go create mode 100644 rpc/v2/types.go create mode 100644 rpc/v2/types_test.go create mode 100644 rpc/v2/utils.go create mode 100644 whisper/api.go diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 2781be656f..74006395cd 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -44,6 +44,10 @@ type Account struct { Address common.Address } +func (acc *Account) MarshalJSON() ([]byte, error) { + return []byte(`"` + acc.Address.Hex() + `"`), nil +} + type Manager struct { keyStore crypto.KeyStore unlocked map[common.Address]*unlocked @@ -92,6 +96,17 @@ func (am *Manager) Unlock(addr common.Address, keyAuth string) error { return am.TimedUnlock(addr, keyAuth, 0) } +func (am *Manager) Lock(addr common.Address) error { + am.mutex.Lock() + if unl, found := am.unlocked[addr]; found { + am.mutex.Unlock() + am.expire(addr, unl, time.Duration(0) * time.Nanosecond) + } else { + am.mutex.Unlock() + } + return nil +} + // TimedUnlock unlocks the account with the given address. The account // stays unlocked for the duration of timeout. A timeout of 0 unlocks the account // until the program exits. diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 843c9a5b57..56b7a8b001 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -246,10 +246,10 @@ func (self *jsre) welcome() { self.re.Run(` (function () { console.log('instance: ' + web3.version.node); - console.log(' datadir: ' + admin.datadir); console.log("coinbase: " + eth.coinbase); var ts = 1000 * eth.getBlock(eth.blockNumber).timestamp; console.log("at block: " + eth.blockNumber + " (" + new Date(ts) + ")"); + console.log(' datadir: ' + admin.datadir); })(); `) if modules, err := self.supportedApis(); err == nil { @@ -258,7 +258,7 @@ func (self *jsre) welcome() { loadedModules = append(loadedModules, fmt.Sprintf("%s:%s", api, version)) } sort.Strings(loadedModules) - fmt.Println("modules:", strings.Join(loadedModules, " ")) + } } @@ -325,12 +325,28 @@ func (js *jsre) apiBindings(f xeth.Frontend) error { } _, err = js.re.Run(shortcuts) - if err != nil { utils.Fatalf("Error setting namespaces: %v", err) } js.re.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`) + + // overrule some of the methods that require password as input and ask for it interactively + p, err := js.re.Get("personal") + if err != nil { + fmt.Println("Unable to overrule sensitive methods in personal module") + return nil + } + + // Override the unlockAccount and newAccount methods on the personal object since these require user interaction. + // Assign the jeth.unlockAccount and jeth.newAccount in the jsre the original web3 callbacks. These will be called + // by the jeth.* methods after they got the password from the user and send the original web3 request to the backend. + persObj := p.Object() + js.re.Run(`jeth.unlockAccount = personal.unlockAccount;`) + persObj.Set("unlockAccount", jeth.UnlockAccount) + js.re.Run(`jeth.newAccount = personal.newAccount;`) + persObj.Set("newAccount", jeth.NewAccount) + return nil } diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index a0f3f2fb76..ca636188f4 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -168,7 +168,7 @@ func TestAccounts(t *testing.T) { checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`) checkEvalJSON(t, repl, `eth.coinbase`, `"`+testAddress+`"`) - val, err := repl.re.Run(`personal.newAccount("password")`) + val, err := repl.re.Run(`jeth.newAccount("password")`) if err != nil { t.Errorf("expected no error, got %v", err) } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 6ec30cebcf..bb291ccde2 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -313,6 +313,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.IPCDisabledFlag, utils.IPCApiFlag, utils.IPCPathFlag, + utils.IPCExperimental, utils.ExecFlag, utils.WhisperEnabledFlag, utils.DevModeFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 5c09e29ce4..7a6ff704c4 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -158,6 +158,7 @@ var AppHelpFlagGroups = []flagGroup{ Flags: []cli.Flag{ utils.WhisperEnabledFlag, utils.NatspecEnabledFlag, + utils.IPCExperimental, }, }, { diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index 7130980ac3..636d329e48 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -23,15 +23,22 @@ import ( "log" "os" "os/signal" + "path/filepath" + "runtime" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc/api" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/comms" + rpc "github.com/ethereum/go-ethereum/rpc/v2" "github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/whisper" "github.com/ethereum/go-ethereum/xeth" @@ -81,9 +88,14 @@ func main() { } log.Println("Initial test suite passed...") + if err := StartIPC(stack); err != nil { + log.Fatalf("Failed to start IPC interface: %v\n", err) + } + log.Println("IPC Interface started, accepting requests...") + // Start the RPC interface and wait until terminated if err := StartRPC(stack); err != nil { - log.Fatalf("Failed to start RPC instarface: %v", err) + log.Fatalf("Failed to start RPC interface: %v", err) } log.Println("RPC Interface started, accepting requests...") @@ -177,3 +189,54 @@ func StartRPC(stack *node.Node) error { } return comms.StartHttp(config, codec, api.Merge(apis...)) } + +// StartRPC initializes an IPC interface to the given protocol stack. +func StartIPC(stack *node.Node) error { + var ethereum *eth.Ethereum + if err := stack.Service(ðereum); err != nil { + return err + } + + endpoint := `\\.\pipe\geth.ipc` + if runtime.GOOS != "windows" { + endpoint = filepath.Join(common.DefaultDataDir(), "geth.ipc") + } + + config := comms.IpcConfig{ + Endpoint: endpoint, + } + + listener, err := comms.CreateListener(config) + if err != nil { + return err + } + + server := rpc.NewServer() + + // register package API's this node provides + offered := stack.RPCAPIs() + for _, api := range offered { + server.RegisterName(api.Namespace, api.Service) + glog.V(logger.Debug).Infof("Register %T@%s for IPC service\n", api.Service, api.Namespace) + } + + web3 := utils.NewPublicWeb3API(stack) + server.RegisterName("web3", web3) + net := utils.NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) + server.RegisterName("net", net) + + go func() { + glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint) + for { + conn, err := listener.Accept() + if err != nil { + glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err) + } + + codec := rpc.NewJSONCodec(conn) + go server.ServeCodec(codec) + } + }() + + return nil +} diff --git a/cmd/utils/api.go b/cmd/utils/api.go new file mode 100644 index 0000000000..59f0dab740 --- /dev/null +++ b/cmd/utils/api.go @@ -0,0 +1,74 @@ +// Copyright 2015 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 . + +package utils + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + rpc "github.com/ethereum/go-ethereum/rpc/v2" +) + +// PublicWeb3API offers helper utils +type PublicWeb3API struct { + stack *node.Node +} + +// NewPublicWeb3API creates a new Web3Service instance +func NewPublicWeb3API(stack *node.Node) *PublicWeb3API { + return &PublicWeb3API{stack} +} + +// ClientVersion returns the node name +func (s *PublicWeb3API) ClientVersion() string { + return s.stack.Server().Name +} + +// Sha3 applies the ethereum sha3 implementation on the input. +// It assumes the input is hex encoded. +func (s *PublicWeb3API) Sha3(input string) string { + return common.ToHex(crypto.Sha3(common.FromHex(input))) +} + +// PublicNetAPI offers network related RPC methods +type PublicNetAPI struct { + net *p2p.Server + networkVersion int +} + +// NewPublicNetAPI creates a new net api instance. +func NewPublicNetAPI(net *p2p.Server, networkVersion int) *PublicNetAPI { + return &PublicNetAPI{net, networkVersion} +} + +// Listening returns an indication if the node is listening for network connections. +func (s *PublicNetAPI) Listening() bool { + return true // always listening +} + +// Peercount returns the number of connected peers +func (s *PublicNetAPI) PeerCount() *rpc.HexNumber { + return rpc.NewHexNumber(s.net.PeerCount()) +} + +// ProtocolVersion returns the current ethereum protocol version. +func (s *PublicNetAPI) Version() string { + return fmt.Sprintf("%d", s.networkVersion) +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 839ec3f027..1aa8f4e894 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -54,6 +54,7 @@ import ( "github.com/ethereum/go-ethereum/rpc/comms" "github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/rpc/useragent" + rpc "github.com/ethereum/go-ethereum/rpc/v2" "github.com/ethereum/go-ethereum/whisper" "github.com/ethereum/go-ethereum/xeth" ) @@ -300,6 +301,10 @@ var ( Usage: "Filename for IPC socket/pipe", Value: DirectoryString{common.DefaultIpcPath()}, } + IPCExperimental = cli.BoolFlag{ + Name: "ipcexp", + Usage: "Enable the new RPC implementation", + } ExecFlag = cli.StringFlag{ Name: "exec", Usage: "Execute JavaScript statement (only in combination with console/attach)", @@ -690,6 +695,7 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. Fatalf("Failed to register the Whisper service: %v", err) } } + return stack } @@ -773,17 +779,53 @@ func IpcSocketPath(ctx *cli.Context) (ipcpath string) { return } -// StartIPC starts a IPC JSON-RPC API server. func StartIPC(stack *node.Node, ctx *cli.Context) error { config := comms.IpcConfig{ Endpoint: IpcSocketPath(ctx), } - initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) { - var ethereum *eth.Ethereum - if err := stack.Service(ðereum); err != nil { - return nil, nil, err + var ethereum *eth.Ethereum + if err := stack.Service(ðereum); err != nil { + return err + } + + if ctx.GlobalIsSet(IPCExperimental.Name) { + listener, err := comms.CreateListener(config) + if err != nil { + return err } + + server := rpc.NewServer() + + // register package API's this node provides + offered := stack.RPCAPIs() + for _, api := range offered { + server.RegisterName(api.Namespace, api.Service) + glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) + } + + web3 := NewPublicWeb3API(stack) + server.RegisterName("web3", web3) + net := NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) + server.RegisterName("net", net) + + go func() { + glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint) + for { + conn, err := listener.Accept() + if err != nil { + glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err) + } + + codec := rpc.NewJSONCodec(conn) + go server.ServeCodec(codec) + } + }() + + return nil + } + + initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) { fe := useragent.NewRemoteFrontend(conn, ethereum.AccountManager()) xeth := xeth.New(stack, fe) apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, stack) diff --git a/common/types.go b/common/types.go index acbd5b28da..b1666d7338 100644 --- a/common/types.go +++ b/common/types.go @@ -17,6 +17,8 @@ package common import ( + "encoding/hex" + "encoding/json" "fmt" "math/big" "math/rand" @@ -50,6 +52,21 @@ func (h Hash) Bytes() []byte { return h[:] } func (h Hash) Big() *big.Int { return Bytes2Big(h[:]) } func (h Hash) Hex() string { return "0x" + Bytes2Hex(h[:]) } +// UnmarshalJSON parses a hash in its hex from to a hash. +func (h *Hash) UnmarshalJSON(input []byte) error { + length := len(input) + if length >= 2 && input[0] == '"' && input[length-1] == '"' { + input = input[1 : length-1] + } + h.SetBytes(FromHex(string(input))) + return nil +} + +// Serialize given hash to JSON +func (h Hash) MarshalJSON() ([]byte, error) { + return json.Marshal(h.Hex()) +} + // Sets the hash to the value of b. If b is larger than len(h) it will panic func (h *Hash) SetBytes(b []byte) { if len(b) > len(h) { @@ -129,6 +146,38 @@ func (a *Address) Set(other Address) { } } +// Serialize given address to JSON +func (a Address) MarshalJSON() ([]byte, error) { + return json.Marshal(a.Hex()) +} + +// Parse address from raw json data +func (a *Address) UnmarshalJSON(data []byte) error { + if len(data) > 2 && data[0] == '"' && data[len(data)-1] == '"' { + data = data[:len(data)-1][1:] + } + + if len(data) > 2 && data[0] == '0' && data[1] == 'x' { + data = data[2:] + } + + if len(data) != 2*AddressLength { + return fmt.Errorf("Invalid address length, expected %d got %d bytes", 2*AddressLength, len(data)) + } + + n, err := hex.Decode(a[:], data) + if err != nil { + return err + } + + if n != AddressLength { + return fmt.Errorf("Invalid address") + } + + a.Set(HexToAddress(string(data))) + return nil +} + // PP Pretty Prints a byte slice in the following format: // hex(value[:4])...(hex[len(value)-4:]) func PP(value []byte) string { diff --git a/core/types/block.go b/core/types/block.go index 1d1cfa5151..5536e0ea86 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -48,6 +48,10 @@ func (n BlockNonce) Uint64() uint64 { return binary.BigEndian.Uint64(n[:]) } +func (n BlockNonce) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"0x%x"`, n)), nil +} + type Header struct { ParentHash common.Hash // Hash to the previous block UncleHash common.Hash // Uncles of this block diff --git a/core/types/bloom9.go b/core/types/bloom9.go index cd90fd9716..372045ab21 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -69,6 +69,10 @@ func (b Bloom) TestBytes(test []byte) bool { return b.Test(common.BytesToBig(test)) } +func (b Bloom) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%#x"`, b.Bytes())), nil +} + func CreateBloom(receipts Receipts) Bloom { bin := new(big.Int) for _, receipt := range receipts { diff --git a/core/vm/log.go b/core/vm/log.go index 191e3a2539..e4cc6021bd 100644 --- a/core/vm/log.go +++ b/core/vm/log.go @@ -17,6 +17,7 @@ package vm import ( + "encoding/json" "fmt" "io" @@ -63,6 +64,21 @@ func (l *Log) String() string { return fmt.Sprintf(`log: %x %x %x %x %d %x %d`, l.Address, l.Topics, l.Data, l.TxHash, l.TxIndex, l.BlockHash, l.Index) } +func (r *Log) MarshalJSON() ([]byte, error) { + fields := map[string]interface{}{ + "address": r.Address, + "data": fmt.Sprintf("%#x", r.Data), + "blockNumber": fmt.Sprintf("%#x", r.BlockNumber), + "logIndex": fmt.Sprintf("%#x", r.Index), + "blockHash": r.BlockHash, + "transactionHash": r.TxHash, + "transactionIndex": fmt.Sprintf("%#x", r.TxIndex), + "topics": r.Topics, + } + + return json.Marshal(fields) +} + type Logs []*Log // LogForStorage is a wrapper around a Log that flattens and parses the entire diff --git a/eth/api.go b/eth/api.go new file mode 100644 index 0000000000..06fc2deb1d --- /dev/null +++ b/eth/api.go @@ -0,0 +1,1216 @@ +// Copyright 2015 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 . + +package eth + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math/big" + "sync" + "time" + + "gopkg.in/fatih/set.v0" + + "github.com/ethereum/ethash" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/compiler" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rlp" + rpc "github.com/ethereum/go-ethereum/rpc/v2" +) + +const ( + defaultGasPrice = uint64(10000000000000) + defaultGas = uint64(90000) +) + +// PublicEthereumAPI provides an API to access Ethereum related information. +// It offers only methods that operate on public data that is freely available to anyone. +type PublicEthereumAPI struct { + e *Ethereum + gpo *GasPriceOracle +} + +// NewPublicEthereumAPI creates a new Etheruem protocol API. +func NewPublicEthereumAPI(e *Ethereum) *PublicEthereumAPI { + return &PublicEthereumAPI{e, NewGasPriceOracle(e)} +} + +// GasPrice returns a suggestion for a gas price. +func (s *PublicEthereumAPI) GasPrice() *big.Int { + return s.gpo.SuggestPrice() +} + +// GetCompilers returns the collection of available smart contract compilers +func (s *PublicEthereumAPI) GetCompilers() ([]string, error) { + solc, err := s.e.Solc() + if err != nil { + return nil, err + } + + if solc != nil { + return []string{"Solidity"}, nil + } + + return []string{}, nil +} + +// CompileSolidity compiles the given solidity source +func (s *PublicEthereumAPI) CompileSolidity(source string) (map[string]*compiler.Contract, error) { + solc, err := s.e.Solc() + if err != nil { + return nil, err + } + + if solc == nil { + return nil, errors.New("solc (solidity compiler) not found") + } + + return solc.Compile(source) +} + +// Etherbase is the address that mining rewards will be send to +func (s *PublicEthereumAPI) Etherbase() (common.Address, error) { + return s.e.Etherbase() +} + +// see Etherbase +func (s *PublicEthereumAPI) Coinbase() (common.Address, error) { + return s.Etherbase() +} + +// ProtocolVersion returns the current Ethereum protocol version this node supports +func (s *PublicEthereumAPI) ProtocolVersion() *rpc.HexNumber { + return rpc.NewHexNumber(s.e.EthVersion()) +} + +// Hashrate returns the POW hashrate +func (s *PublicEthereumAPI) Hashrate() *rpc.HexNumber { + return rpc.NewHexNumber(s.e.Miner().HashRate()) +} + +// Syncing returns false in case the node is currently not synching with the network. It can be up to date or has not +// yet received the latest block headers from its pears. In case it is synchronizing an object with 3 properties is +// returned: +// - startingBlock: block number this node started to synchronise from +// - currentBlock: block number this node is currently importing +// - highestBlock: block number of the highest block header this node has received from peers +func (s *PublicEthereumAPI) Syncing() (interface{}, error) { + origin, current, height := s.e.Downloader().Progress() + if current < height { + return map[string]interface{}{ + "startingBlock": rpc.NewHexNumber(origin), + "currentBlock": rpc.NewHexNumber(current), + "highestBlock": rpc.NewHexNumber(height), + }, nil + } + return false, nil +} + +// PrivateMinerAPI provides private RPC methods to control the miner. +// These methods can be abused by external users and must be considered insecure for use by untrusted users. +type PrivateMinerAPI struct { + e *Ethereum +} + +// NewPrivateMinerAPI create a new RPC service which controls the miner of this node. +func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI { + return &PrivateMinerAPI{e: e} +} + +// Start the miner with the given number of threads +func (s *PrivateMinerAPI) Start(threads rpc.HexNumber) (bool, error) { + s.e.StartAutoDAG() + err := s.e.StartMining(threads.Int(), "") + if err == nil { + return true, nil + } + return false, err +} + +// Stop the miner +func (s *PrivateMinerAPI) Stop() bool { + s.e.StopMining() + return true +} + +// SetExtra sets the extra data string that is included when this miner mines a block. +func (s *PrivateMinerAPI) SetExtra(extra string) (bool, error) { + if err := s.e.Miner().SetExtra([]byte(extra)); err != nil { + return false, err + } + return true, nil +} + +// SetGasPrice sets the minimum accepted gas price for the miner. +func (s *PrivateMinerAPI) SetGasPrice(gasPrice rpc.Number) bool { + s.e.Miner().SetGasPrice(gasPrice.BigInt()) + return true +} + +// SetEtherbase sets the etherbase of the miner +func (s *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool { + s.e.SetEtherbase(etherbase) + return true +} + +// StartAutoDAG starts auto DAG generation. This will prevent the DAG generating on epoch change +// which will cause the node to stop mining during the generation process. +func (s *PrivateMinerAPI) StartAutoDAG() bool { + s.e.StartAutoDAG() + return true +} + +// StopAutoDAG stops auto DAG generation +func (s *PrivateMinerAPI) StopAutoDAG() bool { + s.e.StopAutoDAG() + return true +} + +// MakeDAG creates the new DAG for the given block number +func (s *PrivateMinerAPI) MakeDAG(blockNr rpc.BlockNumber) (bool, error) { + if err := ethash.MakeDAG(uint64(blockNr.Int64()), ""); err != nil { + return false, err + } + return true, nil +} + +// PublicTxPoolAPI offers and API for the transaction pool. It only operates on data that is non confidential. +type PublicTxPoolAPI struct { + e *Ethereum +} + +// NewPublicTxPoolAPI creates a new tx pool service that gives information about the transaction pool. +func NewPublicTxPoolAPI(e *Ethereum) *PublicTxPoolAPI { + return &PublicTxPoolAPI{e} +} + +// Status returns the number of pending and queued transaction in the pool. +func (s *PublicTxPoolAPI) Status() map[string]*rpc.HexNumber { + pending, queue := s.e.TxPool().Stats() + return map[string]*rpc.HexNumber{ + "pending": rpc.NewHexNumber(pending), + "queued": rpc.NewHexNumber(queue), + } +} + +// PublicAccountAPI provides an API to access accounts managed by this node. +// It offers only methods that can retrieve accounts. +type PublicAccountAPI struct { + am *accounts.Manager +} + +// NewPublicAccountAPI creates a new PublicAccountAPI. +func NewPublicAccountAPI(am *accounts.Manager) *PublicAccountAPI { + return &PublicAccountAPI{am: am} +} + +// Accounts returns the collection of accounts this node manages +func (s *PublicAccountAPI) Accounts() ([]accounts.Account, error) { + return s.am.Accounts() +} + +// PrivateAccountAPI provides an API to access accounts managed by this node. +// It offers methods to create, (un)lock en list accounts. +type PrivateAccountAPI struct { + am *accounts.Manager +} + +// NewPrivateAccountAPI create a new PrivateAccountAPI. +func NewPrivateAccountAPI(am *accounts.Manager) *PrivateAccountAPI { + return &PrivateAccountAPI{am} +} + +// ListAccounts will return a list of addresses for accounts this node manages. +func (s *PrivateAccountAPI) ListAccounts() ([]common.Address, error) { + accounts, err := s.am.Accounts() + if err != nil { + return nil, err + } + + addresses := make([]common.Address, len(accounts)) + for i, acc := range accounts { + addresses[i] = acc.Address + } + return addresses, nil +} + +// NewAccount will create a new account and returns the address for the new account. +func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) { + acc, err := s.am.NewAccount(password) + if err == nil { + return acc.Address, nil + } + return common.Address{}, err +} + +// UnlockAccount will unlock the account associated with the given address with the given password for duration seconds. +// It returns an indication if the action was successful. +func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration int) bool { + if err := s.am.TimedUnlock(addr, password, time.Duration(duration)*time.Second); err != nil { + glog.V(logger.Info).Infof("%v\n", err) + return false + } + return true +} + +// LockAccount will lock the account associated with the given address when it's unlocked. +func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { + return s.am.Lock(addr) == nil +} + +// PublicBlockChainAPI provides an API to access the Ethereum blockchain. +// It offers only methods that operate on public data that is freely available to anyone. +type PublicBlockChainAPI struct { + bc *core.BlockChain + chainDb ethdb.Database + eventMux *event.TypeMux + am *accounts.Manager +} + +// NewPublicBlockChainAPI creates a new Etheruem blockchain API. +func NewPublicBlockChainAPI(bc *core.BlockChain, chainDb ethdb.Database, eventMux *event.TypeMux, am *accounts.Manager) *PublicBlockChainAPI { + return &PublicBlockChainAPI{bc: bc, chainDb: chainDb, eventMux: eventMux, am: am} +} + +// BlockNumber returns the block number of the chain head. +func (s *PublicBlockChainAPI) BlockNumber() *big.Int { + return s.bc.CurrentHeader().Number +} + +// GetBalance returns the amount of wei for the given address in the state of the given block number. +// When block number equals rpc.LatestBlockNumber the current block is used. +func (s *PublicBlockChainAPI) GetBalance(address common.Address, blockNr rpc.BlockNumber) (*big.Int, error) { + block := blockByNumber(s.bc, blockNr) + if block == nil { + return nil, nil + } + + state, err := state.New(block.Root(), s.chainDb) + if err != nil { + return nil, err + } + return state.GetBalance(address), nil +} + +// blockByNumber is a commonly used helper function which retrieves and returns the block for the given block number. It +// returns nil when no block could be found. +func blockByNumber(bc *core.BlockChain, blockNr rpc.BlockNumber) *types.Block { + if blockNr == rpc.LatestBlockNumber { + return bc.CurrentBlock() + } + + return bc.GetBlockByNumber(uint64(blockNr)) +} + +// GetBlockByNumber returns the requested block. When blockNr is -1 the chain head is returned. When fullTx is true all +// transactions in the block are returned in full detail, otherwise only the transaction hash is returned. +func (s *PublicBlockChainAPI) GetBlockByNumber(blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { + if block := blockByNumber(s.bc, blockNr); block != nil { + return s.rpcOutputBlock(block, true, fullTx) + } + return nil, nil +} + +// GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full +// detail, otherwise only the transaction hash is returned. +func (s *PublicBlockChainAPI) GetBlockByHash(blockHash common.Hash, fullTx bool) (map[string]interface{}, error) { + if block := s.bc.GetBlock(blockHash); block != nil { + return s.rpcOutputBlock(block, true, fullTx) + } + return nil, nil +} + +// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true +// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned. +func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(blockNr rpc.BlockNumber, index rpc.HexNumber) (map[string]interface{}, error) { + if blockNr == rpc.PendingBlockNumber { + return nil, nil + } + + if block := blockByNumber(s.bc, blockNr); block != nil { + uncles := block.Uncles() + if index.Int() < 0 || index.Int() >= len(uncles) { + glog.V(logger.Debug).Infof("uncle block on index %d not found for block #%d", index.Int(), blockNr) + return nil, nil + } + block = types.NewBlockWithHeader(uncles[index.Int()]) + return s.rpcOutputBlock(block, false, false) + } + return nil, nil +} + +// GetUncleByBlockHashAndIndex returns the uncle block for the given block hash and index. When fullTx is true +// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned. +func (s *PublicBlockChainAPI) GetUncleByBlockHashAndIndex(blockHash common.Hash, index rpc.HexNumber) (map[string]interface{}, error) { + if block := s.bc.GetBlock(blockHash); block != nil { + uncles := block.Uncles() + if index.Int() < 0 || index.Int() >= len(uncles) { + glog.V(logger.Debug).Infof("uncle block on index %d not found for block %s", index.Int(), blockHash.Hex()) + return nil, nil + } + block = types.NewBlockWithHeader(uncles[index.Int()]) + return s.rpcOutputBlock(block, false, false) + } + return nil, nil +} + +// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number +func (s *PublicBlockChainAPI) GetUncleCountByBlockNumber(blockNr rpc.BlockNumber) *rpc.HexNumber { + if blockNr == rpc.PendingBlockNumber { + return rpc.NewHexNumber(0) + } + + if block := blockByNumber(s.bc, blockNr); block != nil { + return rpc.NewHexNumber(len(block.Uncles())) + } + return nil +} + +// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash +func (s *PublicBlockChainAPI) GetUncleCountByBlockHash(blockHash common.Hash) *rpc.HexNumber { + if block := s.bc.GetBlock(blockHash); block != nil { + return rpc.NewHexNumber(len(block.Uncles())) + } + return nil +} + +// NewBlocksArgs allows the user to specify if the returned block should include transactions and in which format. +type NewBlocksArgs struct { + IncludeTransactions bool `json:"includeTransactions"` + TransactionDetails bool `json:"transactionDetails"` +} + +// NewBlocks triggers a new block event each time a block is appended to the chain. It accepts an argument which allows +// the caller to specify whether the output should contain transactions and in what format. +func (s *PublicBlockChainAPI) NewBlocks(args NewBlocksArgs) (rpc.Subscription, error) { + sub := s.eventMux.Subscribe(core.ChainEvent{}) + + output := func(rawBlock interface{}) interface{} { + if event, ok := rawBlock.(core.ChainEvent); ok { + notification, err := s.rpcOutputBlock(event.Block, args.IncludeTransactions, args.TransactionDetails) + if err == nil { + return notification + } + } + return rawBlock + } + + return rpc.NewSubscriptionWithOutputFormat(sub, output), nil +} + +// GetCode returns the code stored at the given address in the state for the given block number. +func (s *PublicBlockChainAPI) GetCode(address common.Address, blockNr rpc.BlockNumber) (string, error) { + return s.GetData(address, blockNr) +} + +// GetData returns the data stored at the given address in the state for the given block number. +func (s *PublicBlockChainAPI) GetData(address common.Address, blockNr rpc.BlockNumber) (string, error) { + if block := blockByNumber(s.bc, blockNr); block != nil { + state, err := state.New(block.Root(), s.chainDb) + if err != nil { + return "", err + } + res := state.GetCode(address) + if len(res) == 0 { // backwards compatibility + return "0x", nil + } + return common.ToHex(res), nil + } + + return "0x", nil +} + +// GetStorageAt returns the storage from the state at the given address, key and block number. +func (s *PublicBlockChainAPI) GetStorageAt(address common.Address, key string, blockNr rpc.BlockNumber) (string, error) { + if block := blockByNumber(s.bc, blockNr); block != nil { + state, err := state.New(block.Root(), s.chainDb) + if err != nil { + return "", err + } + + return state.GetState(address, common.HexToHash(key)).Hex(), nil + } + + return "0x", nil +} + +// callmsg is the message type used for call transations. +type callmsg struct { + from *state.StateObject + to *common.Address + gas, gasPrice *big.Int + value *big.Int + data []byte +} + +// accessor boilerplate to implement core.Message +func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } +func (m callmsg) Nonce() uint64 { return m.from.Nonce() } +func (m callmsg) To() *common.Address { return m.to } +func (m callmsg) GasPrice() *big.Int { return m.gasPrice } +func (m callmsg) Gas() *big.Int { return m.gas } +func (m callmsg) Value() *big.Int { return m.value } +func (m callmsg) Data() []byte { return m.data } + +type CallArgs struct { + From common.Address `json:"from"` + To common.Address `json:"to"` + Gas rpc.HexNumber `json:"gas"` + GasPrice rpc.HexNumber `json:"gasPrice"` + Value rpc.HexNumber `json:"value"` + Data string `json:"data"` +} + +func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (string, *big.Int, error) { + if block := blockByNumber(s.bc, blockNr); block != nil { + stateDb, err := state.New(block.Root(), s.chainDb) + if err != nil { + return "0x", nil, err + } + + stateDb = stateDb.Copy() + var from *state.StateObject + if args.From == (common.Address{}) { + accounts, err := s.am.Accounts() + if err != nil || len(accounts) == 0 { + from = stateDb.GetOrNewStateObject(common.Address{}) + } else { + from = stateDb.GetOrNewStateObject(accounts[0].Address) + } + } else { + from = stateDb.GetOrNewStateObject(args.From) + } + + from.SetBalance(common.MaxBig) + + msg := callmsg{ + from: from, + to: &args.To, + gas: args.Gas.BigInt(), + gasPrice: args.GasPrice.BigInt(), + value: args.Value.BigInt(), + data: common.FromHex(args.Data), + } + + if msg.gas.Cmp(common.Big0) == 0 { + msg.gas = big.NewInt(50000000) + } + + if msg.gasPrice.Cmp(common.Big0) == 0 { + msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + } + + header := s.bc.CurrentBlock().Header() + vmenv := core.NewEnv(stateDb, s.bc, msg, header) + gp := new(core.GasPool).AddGas(common.MaxBig) + res, gas, err := core.ApplyMessage(vmenv, msg, gp) + if len(res) == 0 { // backwards compatability + return "0x", gas, err + } + return common.ToHex(res), gas, err + } + + return "0x", common.Big0, nil +} + +// 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 usefull to execute and retrieve values. +func (s *PublicBlockChainAPI) Call(args CallArgs, blockNr rpc.BlockNumber) (string, error) { + result, _, err := s.doCall(args, blockNr) + return result, err +} + +// EstimateGas returns an estimate of the amount of gas needed to execute the given transaction. +func (s *PublicBlockChainAPI) EstimateGas(args CallArgs) (*rpc.HexNumber, error) { + _, gas, err := s.doCall(args, rpc.LatestBlockNumber) + return rpc.NewHexNumber(gas), err +} + +// rpcOutputBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are +// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain +// transaction hashes. +func (s *PublicBlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { + fields := map[string]interface{}{ + "number": rpc.NewHexNumber(b.Number()), + "hash": b.Hash(), + "parentHash": b.ParentHash(), + "nonce": b.Header().Nonce, + "sha3Uncles": b.UncleHash(), + "logsBloom": b.Bloom(), + "stateRoot": b.Root(), + "miner": b.Coinbase(), + "difficulty": rpc.NewHexNumber(b.Difficulty()), + "totalDifficulty": rpc.NewHexNumber(s.bc.GetTd(b.Hash())), + "extraData": fmt.Sprintf("0x%x", b.Extra()), + "size": rpc.NewHexNumber(b.Size().Int64()), + "gasLimit": rpc.NewHexNumber(b.GasLimit()), + "gasUsed": rpc.NewHexNumber(b.GasUsed()), + "timestamp": rpc.NewHexNumber(b.Time()), + "transactionsRoot": b.TxHash(), + "receiptRoot": b.ReceiptHash(), + } + + if inclTx { + formatTx := func(tx *types.Transaction) (interface{}, error) { + return tx.Hash(), nil + } + + if fullTx { + formatTx = func(tx *types.Transaction) (interface{}, error) { + return newRPCTransaction(b, tx.Hash()) + } + } + + txs := b.Transactions() + transactions := make([]interface{}, len(txs)) + var err error + for i, tx := range b.Transactions() { + if transactions[i], err = formatTx(tx); err != nil { + return nil, err + } + } + fields["transactions"] = transactions + } + + uncles := b.Uncles() + uncleHashes := make([]common.Hash, len(uncles)) + for i, uncle := range uncles { + uncleHashes[i] = uncle.Hash() + } + fields["uncles"] = uncleHashes + + return fields, nil +} + +// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction +type RPCTransaction struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber *rpc.HexNumber `json:"blockNumber"` + From common.Address `json:"from"` + Gas *rpc.HexNumber `json:"gas"` + GasPrice *rpc.HexNumber `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Input string `json:"input"` + Nonce *rpc.HexNumber `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *rpc.HexNumber `json:"transactionIndex"` + Value *rpc.HexNumber `json:"value"` +} + +// newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation +func newRPCPendingTransaction(tx *types.Transaction) *RPCTransaction { + from, _ := tx.From() + + return &RPCTransaction{ + From: from, + Gas: rpc.NewHexNumber(tx.Gas()), + GasPrice: rpc.NewHexNumber(tx.GasPrice()), + Hash: tx.Hash(), + Input: fmt.Sprintf("0x%x", tx.Data()), + Nonce: rpc.NewHexNumber(tx.Nonce()), + To: tx.To(), + Value: rpc.NewHexNumber(tx.Value()), + } +} + +// newRPCTransaction returns a transaction that will serialize to the RPC representation. +func newRPCTransactionFromBlockIndex(b *types.Block, txIndex int) (*RPCTransaction, error) { + if txIndex >= 0 && txIndex < len(b.Transactions()) { + tx := b.Transactions()[txIndex] + from, err := tx.From() + if err != nil { + return nil, err + } + + return &RPCTransaction{ + BlockHash: b.Hash(), + BlockNumber: rpc.NewHexNumber(b.Number()), + From: from, + Gas: rpc.NewHexNumber(tx.Gas()), + GasPrice: rpc.NewHexNumber(tx.GasPrice()), + Hash: tx.Hash(), + Input: fmt.Sprintf("0x%x", tx.Data()), + Nonce: rpc.NewHexNumber(tx.Nonce()), + To: tx.To(), + TransactionIndex: rpc.NewHexNumber(txIndex), + Value: rpc.NewHexNumber(tx.Value()), + }, nil + } + + return nil, nil +} + +// newRPCTransaction returns a transaction that will serialize to the RPC representation. +func newRPCTransaction(b *types.Block, txHash common.Hash) (*RPCTransaction, error) { + for idx, tx := range b.Transactions() { + if tx.Hash() == txHash { + return newRPCTransactionFromBlockIndex(b, idx) + } + } + + return nil, nil +} + +// PublicTransactionPoolAPI exposes methods for the RPC interface +type PublicTransactionPoolAPI struct { + eventMux *event.TypeMux + chainDb ethdb.Database + bc *core.BlockChain + am *accounts.Manager + txPool *core.TxPool + txMu sync.Mutex +} + +// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. +func NewPublicTransactionPoolAPI(txPool *core.TxPool, chainDb ethdb.Database, eventMux *event.TypeMux, bc *core.BlockChain, am *accounts.Manager) *PublicTransactionPoolAPI { + return &PublicTransactionPoolAPI{ + eventMux: eventMux, + chainDb: chainDb, + bc: bc, + am: am, + txPool: txPool, + } +} + +func getTransaction(chainDb ethdb.Database, txPool *core.TxPool, txHash common.Hash) (*types.Transaction, bool, error) { + txData, err := chainDb.Get(txHash.Bytes()) + isPending := false + tx := new(types.Transaction) + + if err == nil && len(txData) > 0 { + if err := rlp.DecodeBytes(txData, tx); err != nil { + return nil, isPending, err + } + } else { + // pending transaction? + tx = txPool.GetTransaction(txHash) + isPending = true + } + + return tx, isPending, nil +} + +// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. +func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByNumber(blockNr rpc.BlockNumber) *rpc.HexNumber { + if blockNr == rpc.PendingBlockNumber { + return rpc.NewHexNumber(0) + } + + if block := blockByNumber(s.bc, blockNr); block != nil { + return rpc.NewHexNumber(len(block.Transactions())) + } + + return nil +} + +// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash. +func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByHash(blockHash common.Hash) *rpc.HexNumber { + if block := s.bc.GetBlock(blockHash); block != nil { + return rpc.NewHexNumber(len(block.Transactions())) + } + return nil +} + +// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. +func (s *PublicTransactionPoolAPI) GetTransactionByBlockNumberAndIndex(blockNr rpc.BlockNumber, index rpc.HexNumber) (*RPCTransaction, error) { + if block := blockByNumber(s.bc, blockNr); block != nil { + return newRPCTransactionFromBlockIndex(block, index.Int()) + } + return nil, nil +} + +// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. +func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(blockHash common.Hash, index rpc.HexNumber) (*RPCTransaction, error) { + if block := s.bc.GetBlock(blockHash); block != nil { + return newRPCTransactionFromBlockIndex(block, index.Int()) + } + return nil, nil +} + +// GetTransactionCount returns the number of transactions the given address has sent for the given block number +func (s *PublicTransactionPoolAPI) GetTransactionCount(address common.Address, blockNr rpc.BlockNumber) (*rpc.HexNumber, error) { + block := blockByNumber(s.bc, blockNr) + if block == nil { + return nil, nil + } + + state, err := state.New(block.Root(), s.chainDb) + if err != nil { + return nil, err + } + return rpc.NewHexNumber(state.GetNonce(address)), nil +} + +// getTransactionBlockData fetches the meta data for the given transaction from the chain database. This is useful to +// retrieve block information for a hash. It returns the block hash, block index and transaction index. +func getTransactionBlockData(chainDb ethdb.Database, txHash common.Hash) (common.Hash, uint64, uint64, error) { + var txBlock struct { + BlockHash common.Hash + BlockIndex uint64 + Index uint64 + } + + blockData, err := chainDb.Get(append(txHash.Bytes(), 0x0001)) + if err != nil { + return common.Hash{}, uint64(0), uint64(0), err + } + + reader := bytes.NewReader(blockData) + if err = rlp.Decode(reader, &txBlock); err != nil { + return common.Hash{}, uint64(0), uint64(0), err + } + + return txBlock.BlockHash, txBlock.BlockIndex, txBlock.Index, nil +} + +// GetTransactionByHash returns the transaction for the given hash +func (s *PublicTransactionPoolAPI) GetTransactionByHash(txHash common.Hash) (*RPCTransaction, error) { + var tx *types.Transaction + var isPending bool + var err error + + if tx, isPending, err = getTransaction(s.chainDb, s.txPool, txHash); err != nil { + glog.V(logger.Debug).Infof("%v\n", err) + return nil, nil + } else if tx == nil { + return nil, nil + } + + if isPending { + return newRPCPendingTransaction(tx), nil + } + + blockHash, _, _, err := getTransactionBlockData(s.chainDb, txHash) + if err != nil { + glog.V(logger.Debug).Infof("%v\n", err) + return nil, nil + } + + if block := s.bc.GetBlock(blockHash); block != nil { + return newRPCTransaction(block, txHash) + } + + return nil, nil +} + +// GetTransactionReceipt returns the transaction receipt for the given transaction hash. +func (s *PublicTransactionPoolAPI) GetTransactionReceipt(txHash common.Hash) (map[string]interface{}, error) { + receipt := core.GetReceipt(s.chainDb, txHash) + if receipt == nil { + glog.V(logger.Debug).Infof("receipt not found for transaction %s", txHash.Hex()) + return nil, nil + } + + tx, _, err := getTransaction(s.chainDb, s.txPool, txHash) + if err != nil { + glog.V(logger.Debug).Infof("%v\n", err) + return nil, nil + } + + txBlock, blockIndex, index, err := getTransactionBlockData(s.chainDb, txHash) + if err != nil { + glog.V(logger.Debug).Infof("%v\n", err) + return nil, nil + } + + from, err := tx.From() + if err != nil { + glog.V(logger.Debug).Infof("%v\n", err) + return nil, nil + } + + fields := map[string]interface{}{ + "blockHash": txBlock, + "blockNumber": rpc.NewHexNumber(blockIndex), + "transactionHash": txHash, + "transactionIndex": rpc.NewHexNumber(index), + "from": from, + "to": tx.To(), + "gasUsed": rpc.NewHexNumber(receipt.GasUsed), + "cumulativeGasUsed": rpc.NewHexNumber(receipt.CumulativeGasUsed), + "contractAddress": nil, + "logs": receipt.Logs, + } + + if receipt.Logs == nil { + fields["logs"] = []vm.Logs{} + } + + // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation + if bytes.Compare(receipt.ContractAddress.Bytes(), bytes.Repeat([]byte{0}, 20)) != 0 { + fields["contractAddress"] = receipt.ContractAddress + } + + return fields, nil +} + +// sign is a helper function that signs a transaction with the private key of the given address. +func (s *PublicTransactionPoolAPI) sign(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + acc := accounts.Account{address} + signature, err := s.am.Sign(acc, tx.SigHash().Bytes()) + if err != nil { + return nil, err + } + return tx.WithSignature(signature) +} + +type SendTxArgs struct { + From common.Address `json:"from"` + To common.Address `json:"to"` + Gas *rpc.HexNumber `json:"gas"` + GasPrice *rpc.HexNumber `json:"gasPrice"` + Value *rpc.HexNumber `json:"value"` + Data string `json:"data"` + Nonce *rpc.HexNumber `json:"nonce"` +} + +// SendTransaction will create a transaction for the given transaction argument, sign it and submit it to the +// transaction pool. +func (s *PublicTransactionPoolAPI) SendTransaction(args SendTxArgs) (common.Hash, error) { + if args.Gas == nil { + args.Gas = rpc.NewHexNumber(defaultGas) + } + if args.GasPrice == nil { + args.GasPrice = rpc.NewHexNumber(defaultGasPrice) + } + if args.Value == nil { + args.Value = rpc.NewHexNumber(0) + } + + s.txMu.Lock() + defer s.txMu.Unlock() + + if args.Nonce == nil { + args.Nonce = rpc.NewHexNumber(s.txPool.State().GetNonce(args.From)) + } + + var tx *types.Transaction + contractCreation := (args.To == common.Address{}) + + if contractCreation { + tx = types.NewContractCreation(args.Nonce.Uint64(), args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) + } else { + tx = types.NewTransaction(args.Nonce.Uint64(), args.To, args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) + } + + signedTx, err := s.sign(args.From, tx) + if err != nil { + return common.Hash{}, err + } + + if err := s.txPool.Add(signedTx); err != nil { + return common.Hash{}, nil + } + + if contractCreation { + addr := crypto.CreateAddress(args.From, args.Nonce.Uint64()) + glog.V(logger.Info).Infof("Tx(%s) created: %s\n", signedTx.Hash().Hex(), addr.Hex()) + } else { + glog.V(logger.Info).Infof("Tx(%s) to: %s\n", signedTx.Hash().Hex(), tx.To().Hex()) + } + + return signedTx.Hash(), nil +} + +// SendRawTransaction will add the signed transaction to the transaction pool. +// The sender is responsible for signing the transaction and using the correct nonce. +func (s *PublicTransactionPoolAPI) SendRawTransaction(encodedTx string) (string, error) { + tx := new(types.Transaction) + if err := rlp.DecodeBytes(common.FromHex(encodedTx), tx); err != nil { + return "", err + } + + if err := s.txPool.Add(tx); err != nil { + return "", err + } + + if tx.To() == nil { + from, err := tx.From() + if err != nil { + return "", err + } + addr := crypto.CreateAddress(from, tx.Nonce()) + glog.V(logger.Info).Infof("Tx(%x) created: %x\n", tx.Hash(), addr) + } else { + glog.V(logger.Info).Infof("Tx(%x) to: %x\n", tx.Hash(), tx.To()) + } + + return tx.Hash().Hex(), nil +} + +// Sign will sign the given data string with the given address. The account corresponding with the address needs to +// be unlocked. +func (s *PublicTransactionPoolAPI) Sign(address common.Address, data string) (string, error) { + signature, error := s.am.Sign(accounts.Account{Address: address}, common.HexToHash(data).Bytes()) + return common.ToHex(signature), error +} + +type SignTransactionArgs struct { + From common.Address + To common.Address + Nonce *rpc.HexNumber + Value *rpc.HexNumber + Gas *rpc.HexNumber + GasPrice *rpc.HexNumber + Data string + + BlockNumber int64 +} + +// Tx is a helper object for argument and return values +type Tx struct { + tx *types.Transaction + + To *common.Address `json:"to"` + From common.Address `json:"from"` + Nonce *rpc.HexNumber `json:"nonce"` + Value *rpc.HexNumber `json:"value"` + Data string `json:"data"` + GasLimit *rpc.HexNumber `json:"gas"` + GasPrice *rpc.HexNumber `json:"gasPrice"` + Hash common.Hash `json:"hash"` +} + +func (tx *Tx) UnmarshalJSON(b []byte) (err error) { + req := struct { + To common.Address `json:"to"` + From common.Address `json:"from"` + Nonce *rpc.HexNumber `json:"nonce"` + Value *rpc.HexNumber `json:"value"` + Data string `json:"data"` + GasLimit *rpc.HexNumber `json:"gas"` + GasPrice *rpc.HexNumber `json:"gasPrice"` + Hash common.Hash `json:"hash"` + }{} + + if err := json.Unmarshal(b, &req); err != nil { + return err + } + + contractCreation := (req.To == (common.Address{})) + + tx.To = &req.To + tx.From = req.From + tx.Nonce = req.Nonce + tx.Value = req.Value + tx.Data = req.Data + tx.GasLimit = req.GasLimit + tx.GasPrice = req.GasPrice + tx.Hash = req.Hash + + data := common.Hex2Bytes(tx.Data) + + if tx.Nonce == nil { + return fmt.Errorf("need nonce") + } + if tx.Value == nil { + tx.Value = rpc.NewHexNumber(0) + } + if tx.GasLimit == nil { + tx.GasLimit = rpc.NewHexNumber(0) + } + if tx.GasPrice == nil { + tx.GasPrice = rpc.NewHexNumber(defaultGasPrice) + } + + if contractCreation { + tx.tx = types.NewContractCreation(tx.Nonce.Uint64(), tx.Value.BigInt(), tx.GasLimit.BigInt(), tx.GasPrice.BigInt(), data) + } else { + if tx.To == nil { + return fmt.Errorf("need to address") + } + tx.tx = types.NewTransaction(tx.Nonce.Uint64(), *tx.To, tx.Value.BigInt(), tx.GasLimit.BigInt(), tx.GasPrice.BigInt(), data) + } + + return nil +} + +type SignTransactionResult struct { + Raw string `json:"raw"` + Tx *Tx `json:"tx"` +} + +func newTx(t *types.Transaction) *Tx { + from, _ := t.From() + return &Tx{ + tx: t, + To: t.To(), + From: from, + Value: rpc.NewHexNumber(t.Value()), + Nonce: rpc.NewHexNumber(t.Nonce()), + Data: "0x" + common.Bytes2Hex(t.Data()), + GasLimit: rpc.NewHexNumber(t.Gas()), + GasPrice: rpc.NewHexNumber(t.GasPrice()), + Hash: t.Hash(), + } +} + +// SignTransaction will sign the given transaction with the from account. +// The node needs to have the private key of the account corresponding with +// the given from address and it needs to be unlocked. +func (s *PublicTransactionPoolAPI) SignTransaction(args *SignTransactionArgs) (*SignTransactionResult, error) { + if args.Gas == nil { + args.Gas = rpc.NewHexNumber(defaultGas) + } + if args.GasPrice == nil { + args.GasPrice = rpc.NewHexNumber(defaultGasPrice) + } + if args.Value == nil { + args.Value = rpc.NewHexNumber(0) + } + + s.txMu.Lock() + defer s.txMu.Unlock() + + if args.Nonce == nil { + args.Nonce = rpc.NewHexNumber(s.txPool.State().GetNonce(args.From)) + } + + var tx *types.Transaction + contractCreation := (args.To == common.Address{}) + + if contractCreation { + tx = types.NewContractCreation(args.Nonce.Uint64(), args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) + } else { + tx = types.NewTransaction(args.Nonce.Uint64(), args.To, args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) + } + + signedTx, err := s.sign(args.From, tx) + if err != nil { + return nil, err + } + + data, err := rlp.EncodeToBytes(signedTx) + if err != nil { + return nil, err + } + + return &SignTransactionResult{"0x" + common.Bytes2Hex(data), newTx(tx)}, nil +} + +// PendingTransactions returns the transactions that are in the transaction pool and have a from address that is one of +// the accounts this node manages. +func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) { + accounts, err := s.am.Accounts() + if err != nil { + return nil, err + } + + accountSet := set.New() + for _, account := range accounts { + accountSet.Add(account.Address) + } + + pending := s.txPool.GetTransactions() + transactions := make([]*RPCTransaction, 0) + for _, tx := range pending { + if from, _ := tx.From(); accountSet.Has(from) { + transactions = append(transactions, newRPCPendingTransaction(tx)) + } + } + + return transactions, nil +} + +// NewPendingTransaction creates a subscription that is triggered each time a transaction enters the transaction pool +// and is send from one of the transactions this nodes manages. +func (s *PublicTransactionPoolAPI) NewPendingTransactions() (rpc.Subscription, error) { + sub := s.eventMux.Subscribe(core.TxPreEvent{}) + + accounts, err := s.am.Accounts() + if err != nil { + return rpc.Subscription{}, err + } + accountSet := set.New() + for _, account := range accounts { + accountSet.Add(account.Address) + } + accountSetLastUpdates := time.Now() + + output := func(transaction interface{}) interface{} { + if time.Since(accountSetLastUpdates) > (time.Duration(2) * time.Second) { + if accounts, err = s.am.Accounts(); err != nil { + accountSet.Clear() + for _, account := range accounts { + accountSet.Add(account.Address) + } + accountSetLastUpdates = time.Now() + } + } + + tx := transaction.(core.TxPreEvent) + if from, err := tx.Tx.From(); err == nil { + if accountSet.Has(from) { + return tx.Tx.Hash() + } + } + return nil + } + + return rpc.NewSubscriptionWithOutputFormat(sub, output), nil +} + +// Resend accepts an existing transaction and a new gas price and limit. It will remove the given transaction from the +// pool and reinsert it with the new gas price and limit. +func (s *PublicTransactionPoolAPI) Resend(tx *Tx, gasPrice, gasLimit *rpc.HexNumber) (common.Hash, error) { + + pending := s.txPool.GetTransactions() + for _, p := range pending { + if pFrom, err := p.From(); err == nil && pFrom == tx.From && p.SigHash() == tx.tx.SigHash() { + if gasPrice == nil { + gasPrice = rpc.NewHexNumber(tx.tx.GasPrice()) + } + if gasLimit == nil { + gasLimit = rpc.NewHexNumber(tx.tx.Gas()) + } + + var newTx *types.Transaction + contractCreation := (*tx.tx.To() == common.Address{}) + if contractCreation { + newTx = types.NewContractCreation(tx.tx.Nonce(), tx.tx.Value(), gasPrice.BigInt(), gasLimit.BigInt(), tx.tx.Data()) + } else { + newTx = types.NewTransaction(tx.tx.Nonce(), *tx.tx.To(), tx.tx.Value(), gasPrice.BigInt(), gasLimit.BigInt(), tx.tx.Data()) + } + + signedTx, err := s.sign(tx.From, newTx) + if err != nil { + return common.Hash{}, err + } + + s.txPool.RemoveTx(tx.Hash) + if err = s.txPool.Add(signedTx); err != nil { + return common.Hash{}, err + } + + return signedTx.Hash(), nil + } + } + + return common.Hash{}, fmt.Errorf("Transaction %#x not found", tx.Hash) +} diff --git a/eth/backend.go b/eth/backend.go index 91f02db72a..ad98635a5e 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" @@ -43,6 +44,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" + rpc "github.com/ethereum/go-ethereum/rpc/v2" ) const ( @@ -239,6 +241,64 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { return eth, nil } +// APIs returns the collection of RPC services the ethereum package offers. +// NOTE, some of these services probably need to be moved to somewhere else. +func (s *Ethereum) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "eth", + Version: "1.0", + Service: NewPublicEthereumAPI(s), + Public: true, + }, { + Namespace: "eth", + Version: "1.0", + Service: NewPublicAccountAPI(s.AccountManager()), + Public: true, + }, { + Namespace: "personal", + Version: "1.0", + Service: NewPrivateAccountAPI(s.AccountManager()), + Public: false, + }, { + Namespace: "eth", + Version: "1.0", + Service: NewPublicBlockChainAPI(s.BlockChain(), s.ChainDb(), s.EventMux(), s.AccountManager()), + Public: true, + }, { + Namespace: "eth", + Version: "1.0", + Service: NewPublicTransactionPoolAPI(s.TxPool(), s.ChainDb(), s.EventMux(), s.BlockChain(), s.AccountManager()), + Public: true, + }, { + Namespace: "eth", + Version: "1.0", + Service: miner.NewPublicMinerAPI(s.Miner()), + Public: true, + }, { + Namespace: "eth", + Version: "1.0", + Service: downloader.NewPublicDownloaderAPI(s.Downloader()), + Public: true, + }, { + Namespace: "miner", + Version: "1.0", + Service: NewPrivateMinerAPI(s), + Public: false, + }, { + Namespace: "txpool", + Version: "1.0", + Service: NewPublicTxPoolAPI(s), + Public: true, + }, { + Namespace: "eth", + Version: "1.0", + Service: filters.NewPublicFilterAPI(s.ChainDb(), s.EventMux()), + Public: true, + }, + } +} + func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { s.blockchain.ResetWithGenesisBlock(gb) } diff --git a/eth/downloader/api.go b/eth/downloader/api.go new file mode 100644 index 0000000000..9deff22a19 --- /dev/null +++ b/eth/downloader/api.go @@ -0,0 +1,64 @@ +// Copyright 2015 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 . + +package downloader + +import ( + rpc "github.com/ethereum/go-ethereum/rpc/v2" +) + +// PublicDownloaderAPI provides an API which gives informatoin about the current synchronisation status. +// It offers only methods that operates on data that can be available to anyone without security risks. +type PublicDownloaderAPI struct { + d *Downloader +} + +// NewPublicDownloaderAPI create a new PublicDownloaderAPI. +func NewPublicDownloaderAPI(d *Downloader) *PublicDownloaderAPI { + return &PublicDownloaderAPI{d} +} + +// Progress gives progress indications when the node is synchronising with the Ethereum network. +type Progress struct { + Origin uint64 `json:"startingBlock"` + Current uint64 `json:"currentBlock"` + Height uint64 `json:"highestBlock"` +} + +// SyncingResult provides information about the current synchronisation status for this node. +type SyncingResult struct { + Syncing bool `json:"syncing"` + Status Progress `json:"status"` +} + +// Syncing provides information when this nodes starts synchronising with the Ethereumn network and when it's finished. +func (s *PublicDownloaderAPI) Syncing() (rpc.Subscription, error) { + sub := s.d.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{}) + + output := func(event interface{}) interface{} { + switch event.(type) { + case StartEvent: + result := &SyncingResult{Syncing: true} + result.Status.Origin, result.Status.Current, result.Status.Height = s.d.Progress() + return result + case DoneEvent, FailedEvent: + return false + } + return nil + } + + return rpc.NewSubscriptionWithOutputFormat(sub, output), nil +} diff --git a/eth/filters/api.go b/eth/filters/api.go new file mode 100644 index 0000000000..411d8e5a3d --- /dev/null +++ b/eth/filters/api.go @@ -0,0 +1,575 @@ +// Copyright 2015 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 . + +package filters + +import ( + "sync" + "time" + + "crypto/rand" + "encoding/hex" + "errors" + + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + rpc "github.com/ethereum/go-ethereum/rpc/v2" +) + +var ( + filterTickerTime = 5 * time.Minute +) + +// byte will be inferred +const ( + unknownFilterTy = iota + blockFilterTy + transactionFilterTy + logFilterTy +) + +// PublicFilterAPI offers support to create and manage filters. This will allow externa clients to retrieve various +// information related to the Ethereum protocol such als blocks, transactions and logs. +type PublicFilterAPI struct { + mux *event.TypeMux + + quit chan struct{} + chainDb ethdb.Database + + filterManager *FilterSystem + + filterMapMu sync.RWMutex + filterMapping map[string]int // maps between filter internal filter identifiers and external filter identifiers + + logMu sync.RWMutex + logQueue map[int]*logQueue + + blockMu sync.RWMutex + blockQueue map[int]*hashQueue + + transactionMu sync.RWMutex + transactionQueue map[int]*hashQueue + + transactMu sync.Mutex +} + +// NewPublicFilterAPI returns a new PublicFilterAPI instance. +func NewPublicFilterAPI(chainDb ethdb.Database, mux *event.TypeMux) *PublicFilterAPI { + svc := &PublicFilterAPI{ + mux: mux, + chainDb: chainDb, + filterManager: NewFilterSystem(mux), + filterMapping: make(map[string]int), + logQueue: make(map[int]*logQueue), + blockQueue: make(map[int]*hashQueue), + transactionQueue: make(map[int]*hashQueue), + } + go svc.start() + return svc +} + +// Stop quits the work loop. +func (s *PublicFilterAPI) Stop() { + close(s.quit) +} + +// start the work loop, wait and process events. +func (s *PublicFilterAPI) start() { + timer := time.NewTicker(2 * time.Second) + defer timer.Stop() +done: + for { + select { + case <-timer.C: + s.logMu.Lock() + for id, filter := range s.logQueue { + if time.Since(filter.timeout) > filterTickerTime { + s.filterManager.Remove(id) + delete(s.logQueue, id) + } + } + s.logMu.Unlock() + + s.blockMu.Lock() + for id, filter := range s.blockQueue { + if time.Since(filter.timeout) > filterTickerTime { + s.filterManager.Remove(id) + delete(s.blockQueue, id) + } + } + s.blockMu.Unlock() + + s.transactionMu.Lock() + for id, filter := range s.transactionQueue { + if time.Since(filter.timeout) > filterTickerTime { + s.filterManager.Remove(id) + delete(s.transactionQueue, id) + } + } + s.transactionMu.Unlock() + case <-s.quit: + break done + } + } + +} + +// NewBlockFilter create a new filter that returns blocks that are included into the canonical chain. +func (s *PublicFilterAPI) NewBlockFilter() (string, error) { + externalId, err := newFilterId() + if err != nil { + return "", err + } + + s.blockMu.Lock() + filter := New(s.chainDb) + id := s.filterManager.Add(filter) + s.blockQueue[id] = &hashQueue{timeout: time.Now()} + + filter.BlockCallback = func(block *types.Block, logs vm.Logs) { + s.blockMu.Lock() + defer s.blockMu.Unlock() + + if queue := s.blockQueue[id]; queue != nil { + queue.add(block.Hash()) + } + } + + defer s.blockMu.Unlock() + + s.filterMapMu.Lock() + s.filterMapping[externalId] = id + s.filterMapMu.Unlock() + + return externalId, nil +} + +// NewPendingTransactionFilter creates a filter that returns new pending transactions. +func (s *PublicFilterAPI) NewPendingTransactionFilter() (string, error) { + externalId, err := newFilterId() + if err != nil { + return "", err + } + + s.transactionMu.Lock() + defer s.transactionMu.Unlock() + + filter := New(s.chainDb) + id := s.filterManager.Add(filter) + s.transactionQueue[id] = &hashQueue{timeout: time.Now()} + + filter.TransactionCallback = func(tx *types.Transaction) { + s.transactionMu.Lock() + defer s.transactionMu.Unlock() + + if queue := s.transactionQueue[id]; queue != nil { + queue.add(tx.Hash()) + } + } + + s.filterMapMu.Lock() + s.filterMapping[externalId] = id + s.filterMapMu.Unlock() + + return externalId, nil +} + +// newLogFilter creates a new log filter. +func (s *PublicFilterAPI) newLogFilter(earliest, latest int64, addresses []common.Address, topics [][]common.Hash) int { + s.logMu.Lock() + defer s.logMu.Unlock() + + filter := New(s.chainDb) + id := s.filterManager.Add(filter) + s.logQueue[id] = &logQueue{timeout: time.Now()} + + filter.SetBeginBlock(earliest) + filter.SetEndBlock(latest) + filter.SetAddresses(addresses) + filter.SetTopics(topics) + filter.LogsCallback = func(logs vm.Logs) { + s.logMu.Lock() + defer s.logMu.Unlock() + + if queue := s.logQueue[id]; queue != nil { + queue.add(logs...) + } + } + + return id +} + +// NewFilterArgs represents a request to create a new filter. +type NewFilterArgs struct { + FromBlock rpc.BlockNumber + ToBlock rpc.BlockNumber + Addresses []common.Address + Topics [][]common.Hash +} + +func (args *NewFilterArgs) UnmarshalJSON(data []byte) error { + type input struct { + From *rpc.BlockNumber `json:"fromBlock"` + ToBlock *rpc.BlockNumber `json:"toBlock"` + Addresses interface{} `json:"address"` + Topics interface{} `json:"topics"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if raw.From == nil { + args.FromBlock = rpc.LatestBlockNumber + } else { + args.FromBlock = *raw.From + } + + if raw.ToBlock == nil { + args.ToBlock = rpc.LatestBlockNumber + } else { + args.ToBlock = *raw.ToBlock + } + + args.Addresses = []common.Address{} + + if raw.Addresses != nil { + // raw.Address can contain a single address or an array of addresses + var addresses []common.Address + + if strAddrs, ok := raw.Addresses.([]interface{}); ok { + for i, addr := range strAddrs { + if strAddr, ok := addr.(string); ok { + if len(strAddr) >= 2 && strAddr[0] == '0' && (strAddr[1] == 'x' || strAddr[1] == 'X') { + strAddr = strAddr[2:] + } + if decAddr, err := hex.DecodeString(strAddr); err == nil { + addresses = append(addresses, common.BytesToAddress(decAddr)) + } else { + fmt.Errorf("invalid address given") + } + } else { + return fmt.Errorf("invalid address on index %d", i) + } + } + } else if singleAddr, ok := raw.Addresses.(string); ok { + if len(singleAddr) >= 2 && singleAddr[0] == '0' && (singleAddr[1] == 'x' || singleAddr[1] == 'X') { + singleAddr = singleAddr[2:] + } + if decAddr, err := hex.DecodeString(singleAddr); err == nil { + addresses = append(addresses, common.BytesToAddress(decAddr)) + } else { + fmt.Errorf("invalid address given") + } + } else { + errors.New("invalid address(es) given") + } + args.Addresses = addresses + } + + topicConverter := func(raw string) (common.Hash, error) { + if len(raw) == 0 { + return common.Hash{}, nil + } + + if len(raw) >= 2 && raw[0] == '0' && (raw[1] == 'x' || raw[1] == 'X') { + raw = raw[2:] + } + + if decAddr, err := hex.DecodeString(raw); err == nil { + return common.BytesToHash(decAddr), nil + } + + return common.Hash{}, errors.New("invalid topic given") + } + + // topics is an array consisting of strings or arrays of strings + if raw.Topics != nil { + topics, ok := raw.Topics.([]interface{}) + if ok { + parsedTopics := make([][]common.Hash, len(topics)) + for i, topic := range topics { + if topic == nil { + parsedTopics[i] = []common.Hash{common.StringToHash("")} + } else if strTopic, ok := topic.(string); ok { + if t, err := topicConverter(strTopic); err != nil { + return fmt.Errorf("invalid topic on index %d", i) + } else { + parsedTopics[i] = []common.Hash{t} + } + } else if arrTopic, ok := topic.([]interface{}); ok { + parsedTopics[i] = make([]common.Hash, len(arrTopic)) + for j := 0; j < len(parsedTopics[i]); i++ { + if arrTopic[j] == nil { + parsedTopics[i][j] = common.StringToHash("") + } else if str, ok := arrTopic[j].(string); ok { + if t, err := topicConverter(str); err != nil { + return fmt.Errorf("invalid topic on index %d", i) + } else { + parsedTopics[i] = []common.Hash{t} + } + } else { + fmt.Errorf("topic[%d][%d] not a string", i, j) + } + } + } else { + return fmt.Errorf("topic[%d] invalid", i) + } + } + args.Topics = parsedTopics + } + } + + return nil +} + +// NewFilter creates a new filter and returns the filter id. It can be uses to retrieve logs. +func (s *PublicFilterAPI) NewFilter(args NewFilterArgs) (string, error) { + externalId, err := newFilterId() + if err != nil { + return "", err + } + + var id int + if len(args.Addresses) > 0 { + id = s.newLogFilter(args.FromBlock.Int64(), args.ToBlock.Int64(), args.Addresses, args.Topics) + } else { + id = s.newLogFilter(args.FromBlock.Int64(), args.ToBlock.Int64(), nil, args.Topics) + } + + s.filterMapMu.Lock() + s.filterMapping[externalId] = id + s.filterMapMu.Unlock() + + return externalId, nil +} + +// GetLogs returns the logs matching the given argument. +func (s *PublicFilterAPI) GetLogs(args NewFilterArgs) vm.Logs { + filter := New(s.chainDb) + filter.SetBeginBlock(args.FromBlock.Int64()) + filter.SetEndBlock(args.ToBlock.Int64()) + filter.SetAddresses(args.Addresses) + filter.SetTopics(args.Topics) + + return returnLogs(filter.Find()) +} + +// UninstallFilter removes the filter with the given filter id. +func (s *PublicFilterAPI) UninstallFilter(filterId string) bool { + s.filterMapMu.Lock() + defer s.filterMapMu.Unlock() + + id, ok := s.filterMapping[filterId] + if !ok { + return false + } + + defer s.filterManager.Remove(id) + delete(s.filterMapping, filterId) + + if _, ok := s.logQueue[id]; ok { + s.logMu.Lock() + defer s.logMu.Unlock() + delete(s.logQueue, id) + return true + } + if _, ok := s.blockQueue[id]; ok { + s.blockMu.Lock() + defer s.blockMu.Unlock() + delete(s.blockQueue, id) + return true + } + if _, ok := s.transactionQueue[id]; ok { + s.transactionMu.Lock() + defer s.transactionMu.Unlock() + delete(s.transactionQueue, id) + return true + } + + return false +} + +// getFilterType is a helper utility that determine the type of filter for the given filter id. +func (s *PublicFilterAPI) getFilterType(id int) byte { + if _, ok := s.blockQueue[id]; ok { + return blockFilterTy + } else if _, ok := s.transactionQueue[id]; ok { + return transactionFilterTy + } else if _, ok := s.logQueue[id]; ok { + return logFilterTy + } + + return unknownFilterTy +} + +// blockFilterChanged returns a collection of block hashes for the block filter with the given id. +func (s *PublicFilterAPI) blockFilterChanged(id int) []common.Hash { + s.blockMu.Lock() + defer s.blockMu.Unlock() + + if s.blockQueue[id] != nil { + return s.blockQueue[id].get() + } + return nil +} + +// transactionFilterChanged returns a collection of transaction hashes for the pending +// transaction filter with the given id. +func (s *PublicFilterAPI) transactionFilterChanged(id int) []common.Hash { + s.blockMu.Lock() + defer s.blockMu.Unlock() + + if s.transactionQueue[id] != nil { + return s.transactionQueue[id].get() + } + return nil +} + +// logFilterChanged returns a collection of logs for the log filter with the given id. +func (s *PublicFilterAPI) logFilterChanged(id int) vm.Logs { + s.logMu.Lock() + defer s.logMu.Unlock() + + if s.logQueue[id] != nil { + return s.logQueue[id].get() + } + return nil +} + +// GetFilterLogs returns the logs for the filter with the given id. +func (s *PublicFilterAPI) GetFilterLogs(filterId string) vm.Logs { + id, ok := s.filterMapping[filterId] + if !ok { + return returnLogs(nil) + } + + if filter := s.filterManager.Get(id); filter != nil { + return returnLogs(filter.Find()) + } + + return returnLogs(nil) +} + +// GetFilterChanges returns the logs for the filter with the given id since last time is was called. +// This can be used for polling. +func (s *PublicFilterAPI) GetFilterChanges(filterId string) interface{} { + s.filterMapMu.Lock() + id, ok := s.filterMapping[filterId] + s.filterMapMu.Unlock() + + if !ok { // filter not found + return []interface{}{} + } + + switch s.getFilterType(id) { + case blockFilterTy: + return returnHashes(s.blockFilterChanged(id)) + case transactionFilterTy: + return returnHashes(s.transactionFilterChanged(id)) + case logFilterTy: + return returnLogs(s.logFilterChanged(id)) + } + + return []interface{}{} +} + +type logQueue struct { + mu sync.Mutex + + logs vm.Logs + timeout time.Time + id int +} + +func (l *logQueue) add(logs ...*vm.Log) { + l.mu.Lock() + defer l.mu.Unlock() + + l.logs = append(l.logs, logs...) +} + +func (l *logQueue) get() vm.Logs { + l.mu.Lock() + defer l.mu.Unlock() + + l.timeout = time.Now() + tmp := l.logs + l.logs = nil + return tmp +} + +type hashQueue struct { + mu sync.Mutex + + hashes []common.Hash + timeout time.Time + id int +} + +func (l *hashQueue) add(hashes ...common.Hash) { + l.mu.Lock() + defer l.mu.Unlock() + + l.hashes = append(l.hashes, hashes...) +} + +func (l *hashQueue) get() []common.Hash { + l.mu.Lock() + defer l.mu.Unlock() + + l.timeout = time.Now() + tmp := l.hashes + l.hashes = nil + return tmp +} + +// newFilterId generates a new random filter identifier that can be exposed to the outer world. By publishing random +// identifiers it is not feasible for DApp's to guess filter id's for other DApp's and uninstall or poll for them +// causing the affected DApp to miss data. +func newFilterId() (string, error) { + var subid [16]byte + n, _ := rand.Read(subid[:]) + if n != 16 { + return "", errors.New("Unable to generate filter id") + } + return "0x" + hex.EncodeToString(subid[:]), nil +} + +// returnLogs is a helper that will return an empty logs array case the given logs is nil, otherwise is will return the +// given logs. The RPC interfaces defines that always an array is returned. +func returnLogs(logs vm.Logs) vm.Logs { + if logs == nil { + return vm.Logs{} + } + return logs +} + +// returnHashes is a helper that will return an empty hash array case the given hash array is nil, otherwise is will +// return the given hashes. The RPC interfaces defines that always an array is returned. +func returnHashes(hashes []common.Hash) []common.Hash { + if hashes == nil { + return []common.Hash{} + } + return hashes +} diff --git a/miner/api.go b/miner/api.go new file mode 100644 index 0000000000..65d106afd8 --- /dev/null +++ b/miner/api.go @@ -0,0 +1,72 @@ +// Copyright 2015 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 . + +package miner + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger/glog" + rpc "github.com/ethereum/go-ethereum/rpc/v2" +) + +// PublicMinerAPI provides an API to control the miner. +// It offers only methods that operate on data that pose no security risk when it is publicly accessible. +type PublicMinerAPI struct { + miner *Miner + agent *RemoteAgent +} + +// NewPublicMinerAPI create a new PublicMinerAPI instance. +func NewPublicMinerAPI(miner *Miner) *PublicMinerAPI { + return &PublicMinerAPI{miner, NewRemoteAgent()} +} + +// Mining returns an indication if this node is currently mining. +func (s *PublicMinerAPI) Mining() bool { + return s.miner.Mining() +} + +// SubmitWork can be used by external miner to submit their POW solution. It returns an indication if the work was +// accepted. Note, this is not an indication if the provided work was valid! +func (s *PublicMinerAPI) SubmitWork(nonce rpc.HexNumber, solution, digest common.Hash) bool { + return s.agent.SubmitWork(nonce.Uint64(), digest, solution) +} + +// GetWork returns a work package for external miner. The work package consists of 3 strings +// result[0], 32 bytes hex encoded current block header pow-hash +// result[1], 32 bytes hex encoded seed hash used for DAG +// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty +func (s *PublicMinerAPI) GetWork() ([]string, error) { + if !s.Mining() { + s.miner.Start(s.miner.coinbase, 0) + } + if work, err := s.agent.GetWork(); err == nil { + return work[:], nil + } else { + glog.Infof("%v\n", err) + } + return nil, fmt.Errorf("mining not ready") +} + +// SubmitHashrate can be used for remote miners to submit their hash rate. This enables the node to report the combined +// hash rate of all miners which submit work through this node. It accepts the miner hash rate and an identifier which +// must be unique between nodes. +func (s *PublicMinerAPI) SubmitHashrate(hashrate rpc.HexNumber, id common.Hash) bool { + s.agent.SubmitHashrate(id, hashrate.Uint64()) + return true +} diff --git a/miner/worker.go b/miner/worker.go index 754a6fc483..1a411ae20b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -327,6 +327,7 @@ func (self *worker) wait() { go func(block *types.Block, logs vm.Logs, receipts []*types.Receipt) { self.mux.Post(core.NewMinedBlockEvent{block}) self.mux.Post(core.ChainEvent{block, block.Hash(), logs}) + if stat == core.CanonStatTy { self.mux.Post(core.ChainHeadEvent{block}) self.mux.Post(logs) diff --git a/node/node.go b/node/node.go index 5566bc44bf..d6debe1237 100644 --- a/node/node.go +++ b/node/node.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" + rpc "github.com/ethereum/go-ethereum/rpc/v2" ) var ( @@ -264,3 +265,12 @@ func (n *Node) DataDir() string { func (n *Node) EventMux() *event.TypeMux { return n.eventmux } + +// RPCAPIs returns the collection of RPC descriptor this node offers +func (n *Node) RPCAPIs() []rpc.API { + var apis []rpc.API + for _, api := range n.services { + apis = append(apis, api.APIs()...) + } + return apis +} diff --git a/node/node_example_test.go b/node/node_example_test.go index 2f9b49a56d..ef41dddaf4 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" + rpc "github.com/ethereum/go-ethereum/rpc/v2" ) // SampleService is a trivial network service that can be attached to a node for @@ -35,6 +36,7 @@ import ( type SampleService struct{} func (s *SampleService) Protocols() []p2p.Protocol { return nil } +func (s *SampleService) APIs() []rpc.API { return nil } func (s *SampleService) Start(*p2p.Server) error { fmt.Println("Service starting..."); return nil } func (s *SampleService) Stop() error { fmt.Println("Service stopping..."); return nil } diff --git a/node/service.go b/node/service.go index bfeeb7ab98..b83d4c80d9 100644 --- a/node/service.go +++ b/node/service.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" + rpc "github.com/ethereum/go-ethereum/rpc/v2" ) // ServiceContext is a collection of service independent options inherited from @@ -70,6 +71,9 @@ type Service interface { // Protocol retrieves the P2P protocols the service wishes to start. Protocols() []p2p.Protocol + // APIs retrieves the list of RPC descriptors the service provides + APIs() []rpc.API + // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. Start(server *p2p.Server) error diff --git a/node/utils_test.go b/node/utils_test.go index 756622c863..2b7bfadbea 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -23,12 +23,14 @@ import ( "reflect" "github.com/ethereum/go-ethereum/p2p" + rpc "github.com/ethereum/go-ethereum/rpc/v2" ) // NoopService is a trivial implementation of the Service interface. type NoopService struct{} func (s *NoopService) Protocols() []p2p.Protocol { return nil } +func (s *NoopService) APIs() []rpc.API { return nil } func (s *NoopService) Start(*p2p.Server) error { return nil } func (s *NoopService) Stop() error { return nil } @@ -67,6 +69,10 @@ func (s *InstrumentedService) Protocols() []p2p.Protocol { return s.protocols } +func (s *InstrumentedService) APIs() []rpc.API { + return nil +} + func (s *InstrumentedService) Start(server *p2p.Server) error { if s.startHook != nil { s.startHook(server) diff --git a/rpc/api/mergedapi.go b/rpc/api/mergedapi.go index 8f4ef8e603..92e1e2bb77 100644 --- a/rpc/api/mergedapi.go +++ b/rpc/api/mergedapi.go @@ -39,9 +39,11 @@ func newMergedApi(apis ...shared.EthereumApi) *MergedApi { mergedApi.methods = make(map[string]shared.EthereumApi) for _, api := range apis { - mergedApi.apis[api.Name()] = api.ApiVersion() - for _, method := range api.Methods() { - mergedApi.methods[method] = api + if api != nil { + mergedApi.apis[api.Name()] = api.ApiVersion() + for _, method := range api.Methods() { + mergedApi.methods[method] = api + } } } return mergedApi diff --git a/rpc/api/personal_js.go b/rpc/api/personal_js.go index 81c5d4a366..84c669af7b 100644 --- a/rpc/api/personal_js.go +++ b/rpc/api/personal_js.go @@ -33,6 +33,11 @@ web3._extend({ call: 'personal_unlockAccount', params: 3, inputFormatter: [null, null, null] + }), + new web3._extend.Method({ + name: 'lockAccount', + call: 'personal_lockAccount', + params: 1 }) ], properties: diff --git a/rpc/api/utils.go b/rpc/api/utils.go index d6820cd2ef..794b6abeea 100644 --- a/rpc/api/utils.go +++ b/rpc/api/utils.go @@ -191,6 +191,8 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, stack *no apis[i] = NewPersonalApi(xeth, eth, codec) case shared.Web3ApiName: apis[i] = NewWeb3Api(xeth, codec) + case "rpc": // gives information about the RPC interface + continue default: return nil, fmt.Errorf("Unknown API '%s'", name) } diff --git a/rpc/comms/ipc.go b/rpc/comms/ipc.go index 882d62ab49..3ba747b1de 100644 --- a/rpc/comms/ipc.go +++ b/rpc/comms/ipc.go @@ -69,13 +69,28 @@ func (self *ipcClient) SupportedModules() (map[string]string, error) { req := shared.Request{ Id: 1, Jsonrpc: "2.0", - Method: "modules", + Method: "rpc_modules", } if err := self.coder.WriteResponse(req); err != nil { return nil, err } + res, _ := self.coder.ReadResponse() + if sucRes, ok := res.(*shared.SuccessResponse); ok { + data, _ := json.Marshal(sucRes.Result) + modules := make(map[string]string) + if err := json.Unmarshal(data, &modules); err == nil { + return modules, nil + } + } + + // old version uses modules instead of rpc_modules, this can be removed after full migration + req.Method = "modules" + if err := self.coder.WriteResponse(req); err != nil { + return nil, err + } + res, err := self.coder.ReadResponse() if err != nil { return nil, err @@ -108,6 +123,11 @@ func StartIpc(cfg IpcConfig, codec codec.Codec, initializer InitFunc) error { return nil } +// CreateListener creates an listener, on Unix platforms this is a unix socket, on Windows this is a named pipe +func CreateListener(cfg IpcConfig) (net.Listener, error) { + return ipcListen(cfg) +} + func ipcLoop(cfg IpcConfig, codec codec.Codec, initializer InitFunc, l net.Listener) { glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint) defer os.Remove(cfg.Endpoint) diff --git a/rpc/jeth.go b/rpc/jeth.go index 1260a34042..de7dd1e762 100644 --- a/rpc/jeth.go +++ b/rpc/jeth.go @@ -54,6 +54,78 @@ func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface return res } +// UnlockAccount asks the user for the password and than executes the jeth.UnlockAccount callback in the jsre +func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) { + var cmd, account, passwd string + timeout := int64(300) + var ok bool + + if len(call.ArgumentList) == 0 { + fmt.Println("expected address of account to unlock") + return otto.FalseValue() + } + + if len(call.ArgumentList) >= 1 { + if accountExport, err := call.Argument(0).Export(); err == nil { + if account, ok = accountExport.(string); ok { + if len(call.ArgumentList) == 1 { + fmt.Printf("Unlock account %s\n", account) + passwd, err = utils.PromptPassword("Passphrase: ", true) + if err != nil { + return otto.FalseValue() + } + } + } + } + } + if len(call.ArgumentList) >= 2 { + if passwdExport, err := call.Argument(1).Export(); err == nil { + passwd, _ = passwdExport.(string) + } + } + + if len(call.ArgumentList) >= 3 { + if timeoutExport, err := call.Argument(2).Export(); err == nil { + timeout, _ = timeoutExport.(int64) + } + } + + cmd = fmt.Sprintf("jeth.unlockAccount('%s', '%s', %d)", account, passwd, timeout) + if val, err := call.Otto.Run(cmd); err == nil { + return val + } + + return otto.FalseValue() +} + +// NewAccount asks the user for the password and than executes the jeth.newAccount callback in the jsre +func (self *Jeth) NewAccount(call otto.FunctionCall) (response otto.Value) { + if len(call.ArgumentList) == 0 { + passwd, err := utils.PromptPassword("Passphrase: ", true) + if err != nil { + return otto.FalseValue() + } + passwd2, err := utils.PromptPassword("Repeat passphrase: ", true) + if err != nil { + return otto.FalseValue() + } + + if passwd != passwd2 { + fmt.Println("Passphrases don't match") + return otto.FalseValue() + } + + cmd := fmt.Sprintf("jeth.newAccount('%s')", passwd) + if val, err := call.Otto.Run(cmd); err == nil { + return val + } + } else { + fmt.Println("New account doesn't expect argument(s), you will be prompted for a password") + } + + return otto.FalseValue() +} + func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { reqif, err := call.Argument(0).Export() if err != nil { diff --git a/rpc/v2/doc.go b/rpc/v2/doc.go new file mode 100644 index 0000000000..e51494adb0 --- /dev/null +++ b/rpc/v2/doc.go @@ -0,0 +1,102 @@ +// Copyright 2015 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 . + +/* +Package rpc provides access to the exported methods of an object across a network +or other I/O connection. After creating a server instance objects can be registered, +making it visible from the outside. Exported methods that follow specific +conventions can be called remotely. It also has support for the publish/subscribe +pattern. + +Methods that satisfy the following criteria are made available for remote access: + - object must be exported + - method must be exported + - method returns 0, 1 (response or error) or 2 (response and error) values + - method argument(s) must be exported or builtin types + - method returned value(s) must be exported or builtin types + +An example method: + func (s *CalcService) Div(a, b int) (int, error) + +When the returned error isn't nil the returned integer is ignored and the error is +send back to the client. Otherwise the returned integer is send back to the client. + +The server offers the ServeCodec method which accepts a ServerCodec instance. It will +read requests from the codec, process the request and sends the response back to the +client using the codec. The server can execute requests concurrently. Responses +can be send back to the client out of order. + +An example server which uses the JSON codec: + type CalculatorService struct {} + + func (s *CalculatorService) Add(a, b int) int { + return a + b + } + + func (s *CalculatorService Div(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("divide by zero") + } + return a/b, nil + } + + calculator := new(CalculatorService) + server := NewServer() + server.RegisterName("calculator", calculator") + + l, _ := net.ListenUnix("unix", &net.UnixAddr{Net: "unix", Name: "/tmp/calculator.sock"}) + for { + c, _ := l.AcceptUnix() + codec := v2.NewJSONCodec(c) + go server.ServeCodec(codec) + } + +The package also supports the publish subscribe pattern through the use of subscriptions. +A method that is considered eligible for notifications must satisfy the following criteria: + - object must be exported + - method must be exported + - method argument(s) must be exported or builtin types + - method must return the tuple Subscription, error + + +An example method: + func (s *BlockChainService) Head() (Subscription, error) { + sub := s.bc.eventMux.Subscribe(ChainHeadEvent{}) + return v2.NewSubscription(sub), nil + } + +This method will push all raised ChainHeadEvents to subscribed clients. If the client is only +interested in every N'th block it is possible to add a criteria. + + func (s *BlockChainService) HeadFiltered(nth uint64) (Subscription, error) { + sub := s.bc.eventMux.Subscribe(ChainHeadEvent{}) + + criteria := func(event interface{}) bool { + chainHeadEvent := event.(ChainHeadEvent) + if chainHeadEvent.Block.NumberU64() % nth == 0 { + return true + } + return false + } + + return v2.NewSubscriptionFiltered(sub, criteria), nil + } + +Subscriptions are deleted when: + - the user sends an unsubscribe request + - the connection which was used to create the subscription is closed +*/ +package v2 diff --git a/rpc/v2/errors.go b/rpc/v2/errors.go new file mode 100644 index 0000000000..a06d19d84c --- /dev/null +++ b/rpc/v2/errors.go @@ -0,0 +1,85 @@ +// Copyright 2015 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 . + +package v2 + +import "fmt" + +// request is for an unknown service +type methodNotFoundError struct { + service string + method string +} + +func (e *methodNotFoundError) Code() int { + return -32601 +} + +func (e *methodNotFoundError) Error() string { + return fmt.Sprintf("The method %s%s%s does not exist/is not available", e.service, serviceMethodSeparator, e.method) +} + +// received message isn't a valid request +type invalidRequestError struct { + message string +} + +func (e *invalidRequestError) Code() int { + return -32600 +} + +func (e *invalidRequestError) Error() string { + return e.message +} + +// received message is invalid +type invalidMessageError struct { + message string +} + +func (e *invalidMessageError) Code() int { + return -32700 +} + +func (e *invalidMessageError) Error() string { + return e.message +} + +// unable to decode supplied params, or an invalid number of parameters +type invalidParamsError struct { + message string +} + +func (e *invalidParamsError) Code() int { + return -32602 +} + +func (e *invalidParamsError) Error() string { + return e.message +} + +// logic error, callback returned an error +type callbackError struct { + message string +} + +func (e *callbackError) Code() int { + return -32000 +} + +func (e *callbackError) Error() string { + return e.message +} diff --git a/rpc/v2/json.go b/rpc/v2/json.go new file mode 100644 index 0000000000..9208e2d370 --- /dev/null +++ b/rpc/v2/json.go @@ -0,0 +1,343 @@ +// Copyright 2015 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 . + +package v2 + +import ( + "encoding/json" + "fmt" + "io" + "reflect" + "strings" + "sync/atomic" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +const ( + jsonRPCVersion = "2.0" + serviceMethodSeparator = "_" + subscribeMethod = "eth_subscribe" + unsubscribeMethod = "eth_unsubscribe" + notificationMethod = "eth_subscription" +) + +// JSON-RPC request +type jsonRequest struct { + Method string `json:"method"` + Version string `json:"jsonrpc"` + Id *int64 `json:"id,omitempty"` + Payload json.RawMessage `json:"params"` +} + +// JSON-RPC response +type jsonSuccessResponse struct { + Version string `json:"jsonrpc"` + Id int64 `json:"id"` + Result interface{} `json:"result,omitempty"` +} + +// JSON-RPC error object +type jsonError struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +// JSON-RPC error response +type jsonErrResponse struct { + Version string `json:"jsonrpc"` + Id *int64 `json:"id,omitempty"` + Error jsonError `json:"error"` +} + +// JSON-RPC notification payload +type jsonSubscription struct { + Subscription string `json:"subscription"` + Result interface{} `json:"result,omitempty"` +} + +// JSON-RPC notification +type jsonNotification struct { + Version string `json:"jsonrpc"` + Method string `json:"method"` + Params jsonSubscription `json:"params"` +} + +// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It also has support for parsing arguments +// and serializing (result) objects. +type jsonCodec struct { + closed chan interface{} + isClosed int32 + d *json.Decoder + e *json.Encoder + req jsonRequest + rw io.ReadWriteCloser +} + +// NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0 +func NewJSONCodec(rwc io.ReadWriteCloser) ServerCodec { + d := json.NewDecoder(rwc) + d.UseNumber() + return &jsonCodec{closed: make(chan interface{}), d: d, e: json.NewEncoder(rwc), rw: rwc, isClosed: 0} +} + +// isBatch returns true when the first non-whitespace characters is '[' +func isBatch(msg json.RawMessage) bool { + for _, c := range msg { + // skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt) + if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d { + continue + } + return c == '[' + } + return false +} + +// ReadRequestHeaders will read new requests without parsing the arguments. It will return a collection of requests, an +// indication if these requests are in batch form or an error when the incoming message could not be read/parsed. +func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) { + var incomingMsg json.RawMessage + if err := c.d.Decode(&incomingMsg); err != nil { + return nil, false, &invalidRequestError{err.Error()} + } + + if isBatch(incomingMsg) { + return parseBatchRequest(incomingMsg) + } + + return parseRequest(incomingMsg) +} + +// parseRequest will parse a single request from the given RawMessage. It will return the parsed request, an indication +// if the request was a batch or an error when the request could not be parsed. +func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { + var in jsonRequest + if err := json.Unmarshal(incomingMsg, &in); err != nil { + return nil, false, &invalidMessageError{err.Error()} + } + + if in.Id == nil { + return nil, false, &invalidMessageError{"Server cannot handle notifications"} + } + + // subscribe are special, they will always use `subscribeMethod` as service method + if in.Method == subscribeMethod { + reqs := []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true}} + if len(in.Payload) > 0 { + // first param must be subscription name + var subscribeMethod [1]string + if err := json.Unmarshal(in.Payload, &subscribeMethod); err != nil { + glog.V(logger.Debug).Infof("Unable to parse subscription method: %v\n", err) + return nil, false, &invalidRequestError{"Unable to parse subscription request"} + } + + // all subscriptions are made on the eth service + reqs[0].service, reqs[0].method = "eth", subscribeMethod[0] + reqs[0].params = in.Payload + return reqs, false, nil + } + return nil, false, &invalidRequestError{"Unable to parse subscription request"} + } + + if in.Method == unsubscribeMethod { + return []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true, + method: unsubscribeMethod, params: in.Payload}}, false, nil + } + + // regular RPC call + elems := strings.Split(in.Method, serviceMethodSeparator) + if len(elems) != 2 { + return nil, false, &methodNotFoundError{in.Method, ""} + } + + if len(in.Payload) == 0 { + return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id}}, false, nil + } + + return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id, params: in.Payload}}, false, nil +} + +// parseBatchRequest will parse a batch request into a collection of requests from the given RawMessage, an indication +// if the request was a batch or an error when the request could not be read. +func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { + var in []jsonRequest + if err := json.Unmarshal(incomingMsg, &in); err != nil { + return nil, false, &invalidMessageError{err.Error()} + } + + requests := make([]rpcRequest, len(in)) + for i, r := range in { + if r.Id == nil { + return nil, true, &invalidMessageError{"Server cannot handle notifications"} + } + + // (un)subscribe are special, they will always use the same service.method + if r.Method == subscribeMethod { + requests[i] = rpcRequest{id: *r.Id, isPubSub: true} + if len(r.Payload) > 0 { + var subscribeMethod [1]string + if err := json.Unmarshal(r.Payload, &subscribeMethod); err != nil { + glog.V(logger.Debug).Infof("Unable to parse subscription method: %v\n", err) + return nil, false, &invalidRequestError{"Unable to parse subscription request"} + } + + // all subscriptions are made on the eth service + requests[i].service, requests[i].method = "eth", subscribeMethod[0] + requests[i].params = r.Payload + continue + } + + return nil, true, &invalidRequestError{"Unable to parse (un)subscribe request arguments"} + } + + if r.Method == unsubscribeMethod { + requests[i] = rpcRequest{id: *r.Id, isPubSub: true, method: unsubscribeMethod, params: r.Payload} + continue + } + + elems := strings.Split(r.Method, serviceMethodSeparator) + if len(elems) != 2 { + return nil, true, &methodNotFoundError{r.Method, ""} + } + + if len(r.Payload) == 0 { + requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: nil} + } else { + requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: r.Payload} + } + } + + return requests, true, nil +} + +// ParseRequestArguments tries to parse the given params (json.RawMessage) with the given types. It returns the parsed +// values or an error when the parsing failed. +func (c *jsonCodec) ParseRequestArguments(argTypes []reflect.Type, params interface{}) ([]reflect.Value, RPCError) { + if args, ok := params.(json.RawMessage); !ok { + return nil, &invalidParamsError{"Invalid params supplied"} + } else { + return parsePositionalArguments(args, argTypes) + } +} + +func countArguments(args json.RawMessage) (int, error) { + var cnt []interface{} + if err := json.Unmarshal(args, &cnt); err != nil { + return -1, nil + } + return len(cnt), nil +} + +// parsePositionalArguments tries to parse the given args to an array of values with the given types. It returns the +// parsed values or an error when the args could not be parsed. +func parsePositionalArguments(args json.RawMessage, argTypes []reflect.Type) ([]reflect.Value, RPCError) { + argValues := make([]reflect.Value, len(argTypes)) + params := make([]interface{}, len(argTypes)) + + n, err := countArguments(args) + if err != nil { + return nil, &invalidParamsError{err.Error()} + } + if n != len(argTypes) { + return nil, &invalidParamsError{fmt.Sprintf("insufficient params, want %d have %d", len(argTypes), n)} + } + + for i, t := range argTypes { + if t.Kind() == reflect.Ptr { + // values must be pointers for the Unmarshal method, reflect. + // Dereference otherwise reflect.New would create **SomeType + argValues[i] = reflect.New(t.Elem()) + params[i] = argValues[i].Interface() + + // when not specified blockNumbers are by default latest (-1) + if blockNumber, ok := params[i].(*BlockNumber); ok { + *blockNumber = BlockNumber(-1) + } + } else { + argValues[i] = reflect.New(t) + params[i] = argValues[i].Interface() + + // when not specified blockNumbers are by default latest (-1) + if blockNumber, ok := params[i].(*BlockNumber); ok { + *blockNumber = BlockNumber(-1) + } + } + } + + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, &invalidParamsError{err.Error()} + } + + // Convert pointers back to values where necessary + for i, a := range argValues { + if a.Kind() != argTypes[i].Kind() { + argValues[i] = reflect.Indirect(argValues[i]) + } + } + + return argValues, nil +} + +// CreateResponse will create a JSON-RPC success response with the given id and reply as result. +func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} { + if isHexNum(reflect.TypeOf(reply)) { + return &jsonSuccessResponse{Version: jsonRPCVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)} + } + return &jsonSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply} +} + +// CreateErrorResponse will create a JSON-RPC error response with the given id and error. +func (c *jsonCodec) CreateErrorResponse(id *int64, err RPCError) interface{} { + return &jsonErrResponse{Version: jsonRPCVersion, Id: id, Error: jsonError{Code: err.Code(), Message: err.Error()}} +} + +// CreateErrorResponseWithInfo will create a JSON-RPC error response with the given id and error. +// info is optional and contains additional information about the error. When an empty string is passed it is ignored. +func (c *jsonCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} { + return &jsonErrResponse{Version: jsonRPCVersion, Id: id, + Error: jsonError{Code: err.Code(), Message: err.Error(), Data: info}} +} + +// CreateNotification will create a JSON-RPC notification with the given subscription id and event as params. +func (c *jsonCodec) CreateNotification(subid string, event interface{}) interface{} { + if isHexNum(reflect.TypeOf(event)) { + return &jsonNotification{Version: jsonRPCVersion, Method: notificationMethod, + Params: jsonSubscription{Subscription: subid, Result: fmt.Sprintf(`%#x`, event)}} + } + + return &jsonNotification{Version: jsonRPCVersion, Method: notificationMethod, + Params: jsonSubscription{Subscription: subid, Result: event}} +} + +// Write message to client +func (c *jsonCodec) Write(res interface{}) error { + return c.e.Encode(res) +} + +// Close the underlying connection +func (c *jsonCodec) Close() { + if atomic.CompareAndSwapInt32(&c.isClosed, 0, 1) { + close(c.closed) + c.rw.Close() + } +} + +// Closed returns a channel which will be closed when Close is called +func (c *jsonCodec) Closed() <-chan interface{} { + return c.closed +} diff --git a/rpc/v2/json_test.go b/rpc/v2/json_test.go new file mode 100644 index 0000000000..dc8a345d7f --- /dev/null +++ b/rpc/v2/json_test.go @@ -0,0 +1,73 @@ +package v2 + +import ( + "bufio" + "bytes" + "reflect" + "testing" +) + +type RWC struct { + *bufio.ReadWriter +} + +func (rwc *RWC) Close() error { + return nil +} + +func TestJSONRequestParsing(t *testing.T) { + server := NewServer() + service := new(Service) + + if err := server.RegisterName("calc", service); err != nil { + t.Fatalf("%v", err) + } + + req := bytes.NewBufferString(`{"id": 1234, "jsonrpc": "2.0", "method": "calc_add", "params": [11, 22]}`) + var str string + reply := bytes.NewBufferString(str) + rw := &RWC{bufio.NewReadWriter(bufio.NewReader(req), bufio.NewWriter(reply))} + + codec := NewJSONCodec(rw) + + requests, batch, err := codec.ReadRequestHeaders() + if err != nil { + t.Fatalf("%v", err) + } + + if batch { + t.Fatalf("Request isn't a batch") + } + + if len(requests) != 1 { + t.Fatalf("Expected 1 request but got %d requests - %v", len(requests), requests) + } + + if requests[0].service != "calc" { + t.Fatalf("Expected service 'calc' but got '%s'", requests[0].service) + } + + if requests[0].method != "add" { + t.Fatalf("Expected method 'Add' but got '%s'", requests[0].method) + } + + if requests[0].id != 1234 { + t.Fatalf("Expected id 1234 but got %d", requests[0].id) + } + + var arg int + args := []reflect.Type{reflect.TypeOf(arg), reflect.TypeOf(arg)} + + v, err := codec.ParseRequestArguments(args, requests[0].params) + if err != nil { + t.Fatalf("%v", err) + } + + if len(v) != 2 { + t.Fatalf("Expected 2 argument values, got %d", len(v)) + } + + if v[0].Int() != 11 || v[1].Int() != 22 { + t.Fatalf("expected %d == 11 && %d == 22", v[0].Int(), v[1].Int()) + } +} diff --git a/rpc/v2/server.go b/rpc/v2/server.go new file mode 100644 index 0000000000..ff6b69015a --- /dev/null +++ b/rpc/v2/server.go @@ -0,0 +1,378 @@ +// Copyright 2015 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 . + +package v2 + +import ( + "fmt" + "reflect" + + "runtime" + + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +// NewServer will create a new server instance with no registered handlers. +func NewServer() *Server { + server := &Server{services: make(serviceRegistry), subscriptions: make(subscriptionRegistry)} + + // register a default service which will provide meta information about the RPC service such as the services and + // methods it offers. + rpcService := &RPCService{server} + server.RegisterName("rpc", rpcService) + + return server +} + +// RPCService gives meta information about the server. +// e.g. gives information about the loaded modules. +type RPCService struct { + server *Server +} + +// Modules returns the list of RPC services with their version number +func (s *RPCService) Modules() map[string]string { + modules := make(map[string]string) + for name, _ := range s.server.services { + modules[name] = "1.0" + } + return modules +} + +// RegisterName will create an service for the given rcvr type under the given name. When no methods on the given rcvr +// match the criteria to be either a RPC method or a subscription an error is returned. Otherwise a new service is +// created and added to the service collection this server instance serves. +func (s *Server) RegisterName(name string, rcvr interface{}) error { + if s.services == nil { + s.services = make(serviceRegistry) + } + + svc := new(service) + svc.typ = reflect.TypeOf(rcvr) + rcvrVal := reflect.ValueOf(rcvr) + + if name == "" { + return fmt.Errorf("no service name for type %s", svc.typ.String()) + } + if !isExported(reflect.Indirect(rcvrVal).Type().Name()) { + return fmt.Errorf("%s is not exported", reflect.Indirect(rcvrVal).Type().Name()) + } + + // already a previous service register under given sname, merge methods/subscriptions + if regsvc, present := s.services[name]; present { + methods, subscriptions := suitableCallbacks(rcvrVal, svc.typ) + if len(methods) == 0 && len(subscriptions) == 0 { + return fmt.Errorf("Service doesn't have any suitable methods/subscriptions to expose") + } + + for _, m := range methods { + regsvc.callbacks[formatName(m.method.Name)] = m + } + for _, s := range subscriptions { + regsvc.subscriptions[formatName(s.method.Name)] = s + } + + return nil + } + + svc.name = name + svc.callbacks, svc.subscriptions = suitableCallbacks(rcvrVal, svc.typ) + + if len(svc.callbacks) == 0 && len(svc.subscriptions) == 0 { + return fmt.Errorf("Service doesn't have any suitable methods/subscriptions to expose") + } + + s.services[svc.name] = svc + + return nil +} + +// ServeCodec reads incoming requests from codec, calls the appropriate callback and writes the +// response back using the given codec. It will block until the codec is closed. +// +// This server will: +// 1. allow for asynchronous and parallel request execution +// 2. supports notifications (pub/sub) +// 3. supports request batches +func (s *Server) ServeCodec(codec ServerCodec) { + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + glog.Errorln(string(buf)) + } + codec.Close() + }() + + for { + reqs, batch, err := s.readRequest(codec) + if err != nil { + glog.V(logger.Debug).Infof("%v\n", err) + codec.Write(codec.CreateErrorResponse(nil, err)) + break + } + + if batch { + go s.execBatch(codec, reqs) + } else { + go s.exec(codec, reqs[0]) + } + } +} + +// sendNotification will create a notification from the given event by serializing member fields of the event. +// It will then send the notification to the client, when it fails the codec is closed. When the event has multiple +// fields an array of values is returned. +func sendNotification(codec ServerCodec, subid string, event interface{}) { + notification := codec.CreateNotification(subid, event) + + if err := codec.Write(notification); err != nil { + codec.Close() + } +} + +// createSubscription will register a new subscription and waits for raised events. When an event is raised it will: +// 1. test if the event is raised matches the criteria the user has (optionally) specified +// 2. create a notification of the event and send it the client when it matches the criteria +// It will unsubscribe the subscription when the socket is closed or the subscription is unsubscribed by the user. +func (s *Server) createSubscription(c ServerCodec, req *serverRequest) (string, error) { + args := []reflect.Value{req.callb.rcvr} + if len(req.args) > 0 { + args = append(args, req.args...) + } + + subid, err := newSubscriptionId() + if err != nil { + return "", err + } + + reply := req.callb.method.Func.Call(args) + + if reply[1].IsNil() { // no error + if subscription, ok := reply[0].Interface().(Subscription); ok { + s.muSubcriptions.Lock() + s.subscriptions[subid] = subscription + s.muSubcriptions.Unlock() + go func() { + cases := []reflect.SelectCase{ + reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(subscription.Chan())}, // new event + reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(c.Closed())}, // connection closed + } + + for { + idx, notification, recvOk := reflect.Select(cases) + switch idx { + case 0: // new event, or channel closed + if recvOk { // send notification + if event, ok := notification.Interface().(*event.Event); ok { + if subscription.match == nil || subscription.match(event.Data) { + sendNotification(c, subid, subscription.format(event.Data)) + } + } + } else { // user send an eth_unsubscribe request + return + } + case 1: // connection closed + s.unsubscribe(subid) + return + } + } + }() + } else { // unable to create subscription + s.muSubcriptions.Lock() + delete(s.subscriptions, subid) + s.muSubcriptions.Unlock() + } + } else { + return "", fmt.Errorf("Unable to create subscription") + } + + return subid, nil +} + +// unsubscribe calls the Unsubscribe method on the subscription and removes a subscription from the subscription +// registry. +func (s *Server) unsubscribe(subid string) bool { + s.muSubcriptions.Lock() + defer s.muSubcriptions.Unlock() + if sub, ok := s.subscriptions[subid]; ok { + sub.Unsubscribe() + delete(s.subscriptions, subid) + return true + } + return false +} + +// handle executes a request and returns the response from the callback. +func (s *Server) handle(codec ServerCodec, req *serverRequest) interface{} { + if req.err != nil { + return codec.CreateErrorResponse(&req.id, req.err) + } + + if req.isUnsubscribe { // first param must be the subscription id + if len(req.args) >= 1 && req.args[0].Kind() == reflect.String { + subid := req.args[0].String() + if s.unsubscribe(subid) { + return codec.CreateResponse(req.id, true) + } else { + return codec.CreateErrorResponse(&req.id, + &callbackError{fmt.Sprintf("subscription '%s' not found", subid)}) + } + } + return codec.CreateErrorResponse(&req.id, &invalidParamsError{"Expected subscription id as argument"}) + } + + if req.callb.isSubscribe { + subid, err := s.createSubscription(codec, req) + if err != nil { + return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}) + } + return codec.CreateResponse(req.id, subid) + } + + // regular RPC call + if len(req.args) != len(req.callb.argTypes) { + rpcErr := &invalidParamsError{fmt.Sprintf("%s%s%s expects %d parameters, got %d", + req.svcname, serviceMethodSeparator, req.callb.method.Name, + len(req.callb.argTypes), len(req.args))} + return codec.CreateErrorResponse(&req.id, rpcErr) + } + + arguments := []reflect.Value{req.callb.rcvr} + if len(req.args) > 0 { + arguments = append(arguments, req.args...) + } + + reply := req.callb.method.Func.Call(arguments) + + if len(reply) == 0 { + return codec.CreateResponse(req.id, nil) + } + + if req.callb.errPos >= 0 { // test if method returned an error + if !reply[req.callb.errPos].IsNil() { + e := reply[req.callb.errPos].Interface().(error) + res := codec.CreateErrorResponse(&req.id, &callbackError{e.Error()}) + return res + } + } + + return codec.CreateResponse(req.id, reply[0].Interface()) +} + +// exec executes the given request and writes the result back using the codec. +func (s *Server) exec(codec ServerCodec, req *serverRequest) { + var response interface{} + if req.err != nil { + response = codec.CreateErrorResponse(&req.id, req.err) + } else { + response = s.handle(codec, req) + } + + if err := codec.Write(response); err != nil { + glog.V(logger.Error).Infof("%v\n", err) + codec.Close() + } +} + +// execBatch executes the given requests and writes the result back using the codec. It will only write the response +// back when the last request is processed. +func (s *Server) execBatch(codec ServerCodec, requests []*serverRequest) { + responses := make([]interface{}, len(requests)) + for i, req := range requests { + if req.err != nil { + responses[i] = codec.CreateErrorResponse(&req.id, req.err) + } else { + responses[i] = s.handle(codec, req) + } + } + + if err := codec.Write(responses); err != nil { + glog.V(logger.Error).Infof("%v\n", err) + codec.Close() + } +} + +// readRequest requests the next (batch) request from the codec. It will return the collection of requests, an +// indication if the request was a batch, the invalid request identifier and an error when the request could not be +// read/parsed. +func (s *Server) readRequest(codec ServerCodec) ([]*serverRequest, bool, RPCError) { + reqs, batch, err := codec.ReadRequestHeaders() + if err != nil { + return nil, batch, err + } + + requests := make([]*serverRequest, len(reqs)) + + // verify requests + for i, r := range reqs { + var ok bool + var svc *service + + if r.isPubSub && r.method == unsubscribeMethod { + requests[i] = &serverRequest{id: r.id, isUnsubscribe: true} + argTypes := []reflect.Type{reflect.TypeOf("")} + if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil { + requests[i].args = args + } else { + requests[i].err = &invalidParamsError{err.Error()} + } + continue + } + + if svc, ok = s.services[r.service]; !ok { + requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}} + continue + } + + if r.isPubSub { // eth_subscribe + if callb, ok := svc.subscriptions[r.method]; ok { + requests[i] = &serverRequest{id: r.id, svcname: svc.name, callb: callb} + if r.params != nil && len(callb.argTypes) > 0 { + argTypes := []reflect.Type{reflect.TypeOf("")} + argTypes = append(argTypes, callb.argTypes...) + if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil { + requests[i].args = args[1:] // first one is service.method name which isn't an actual argument + } else { + requests[i].err = &invalidParamsError{err.Error()} + } + } + } else { + requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{subscribeMethod, r.method}} + } + continue + } + + if callb, ok := svc.callbacks[r.method]; ok { + requests[i] = &serverRequest{id: r.id, svcname: svc.name, callb: callb} + if r.params != nil && len(callb.argTypes) > 0 { + if args, err := codec.ParseRequestArguments(callb.argTypes, r.params); err == nil { + requests[i].args = args + } else { + requests[i].err = &invalidParamsError{err.Error()} + } + } + continue + } + + requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}} + } + + return requests, batch, nil +} diff --git a/rpc/v2/server_test.go b/rpc/v2/server_test.go new file mode 100644 index 0000000000..f4f77672fb --- /dev/null +++ b/rpc/v2/server_test.go @@ -0,0 +1,219 @@ +package v2 + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + "time" +) + +type Service struct{} + +type Args struct { + S string +} + +func (s *Service) NoArgsRets() { +} + +type Result struct { + String string + Int int + Args *Args +} + +func (s *Service) Echo(str string, i int, args *Args) Result { + return Result{str, i, args} +} + +func (s *Service) Rets() (string, error) { + return "", nil +} + +func (s *Service) InvalidRets1() (error, string) { + return nil, "" +} + +func (s *Service) InvalidRets2() (string, string) { + return "", "" +} + +func (s *Service) InvalidRets3() (string, string, error) { + return "", "", nil +} + +func (s *Service) Subscription() (Subscription, error) { + return NewSubscription(nil), nil +} + +func TestServerRegisterName(t *testing.T) { + server := NewServer() + service := new(Service) + + if err := server.RegisterName("calc", service); err != nil { + t.Fatalf("%v", err) + } + + if len(server.services) != 2 { + t.Fatalf("Expected 2 service entries, got %d", len(server.services)) + } + + svc, ok := server.services["calc"] + if !ok { + t.Fatalf("Expected service calc to be registered") + } + + if len(svc.callbacks) != 3 { + t.Errorf("Expected 3 callbacks for service 'calc', got %d", len(svc.callbacks)) + } + + if len(svc.subscriptions) != 1 { + t.Errorf("Expected 1 subscription for service 'calc', got %d", len(svc.subscriptions)) + } +} + +// dummy codec used for testing RPC method execution +type ServerTestCodec struct { + counter int + input []byte + output string + closer chan interface{} +} + +func (c *ServerTestCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) { + c.counter += 1 + + if c.counter == 1 { + var req jsonRequest + json.Unmarshal(c.input, &req) + return []rpcRequest{rpcRequest{id: *req.Id, isPubSub: false, service: "test", method: req.Method, params: req.Payload}}, false, nil + } + + // requests are executes in parallel, wait a bit before returning an error so that the previous request has time to + // be executed + timer := time.NewTimer(time.Duration(2) * time.Second) + <-timer.C + + return nil, false, &invalidRequestError{"connection closed"} +} + +func (c *ServerTestCodec) ParseRequestArguments(argTypes []reflect.Type, payload interface{}) ([]reflect.Value, RPCError) { + + args, _ := payload.(json.RawMessage) + + argValues := make([]reflect.Value, len(argTypes)) + params := make([]interface{}, len(argTypes)) + + n, err := countArguments(args) + if err != nil { + return nil, &invalidParamsError{err.Error()} + } + if n != len(argTypes) { + return nil, &invalidParamsError{fmt.Sprintf("insufficient params, want %d have %d", len(argTypes), n)} + + } + + for i, t := range argTypes { + if t.Kind() == reflect.Ptr { + // values must be pointers for the Unmarshal method, reflect. + // Dereference otherwise reflect.New would create **SomeType + argValues[i] = reflect.New(t.Elem()) + params[i] = argValues[i].Interface() + + // when not specified blockNumbers are by default latest (-1) + if blockNumber, ok := params[i].(*BlockNumber); ok { + *blockNumber = BlockNumber(-1) + } + } else { + argValues[i] = reflect.New(t) + params[i] = argValues[i].Interface() + + // when not specified blockNumbers are by default latest (-1) + if blockNumber, ok := params[i].(*BlockNumber); ok { + *blockNumber = BlockNumber(-1) + } + } + } + + if err := json.Unmarshal(args, ¶ms); err != nil { + return nil, &invalidParamsError{err.Error()} + } + + // Convert pointers back to values where necessary + for i, a := range argValues { + if a.Kind() != argTypes[i].Kind() { + argValues[i] = reflect.Indirect(argValues[i]) + } + } + + return argValues, nil +} + +func (c *ServerTestCodec) CreateResponse(id int64, reply interface{}) interface{} { + return &jsonSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply} +} + +func (c *ServerTestCodec) CreateErrorResponse(id *int64, err RPCError) interface{} { + return &jsonErrResponse{Version: jsonRPCVersion, Id: id, Error: jsonError{Code: err.Code(), Message: err.Error()}} +} + +func (c *ServerTestCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} { + return &jsonErrResponse{Version: jsonRPCVersion, Id: id, + Error: jsonError{Code: err.Code(), Message: err.Error(), Data: info}} +} + +func (c *ServerTestCodec) CreateNotification(subid string, event interface{}) interface{} { + return &jsonNotification{Version: jsonRPCVersion, Method: notificationMethod, + Params: jsonSubscription{Subscription: subid, Result: event}} +} + +func (c *ServerTestCodec) Write(msg interface{}) error { + if len(c.output) == 0 { // only capture first response + if o, err := json.Marshal(msg); err != nil { + return err + } else { + c.output = string(o) + } + } + + return nil +} + +func (c *ServerTestCodec) Close() { + close(c.closer) +} + +func (c *ServerTestCodec) Closed() <-chan interface{} { + return c.closer +} + +func TestServerMethodExecution(t *testing.T) { + server := NewServer() + service := new(Service) + + if err := server.RegisterName("test", service); err != nil { + t.Fatalf("%v", err) + } + + id := int64(12345) + req := jsonRequest{ + Method: "echo", + Version: "2.0", + Id: &id, + } + args := []interface{}{"string arg", 1122, &Args{"qwerty"}} + req.Payload, _ = json.Marshal(&args) + + input, _ := json.Marshal(&req) + codec := &ServerTestCodec{input: input, closer: make(chan interface{})} + go server.ServeCodec(codec) + + <-codec.closer + + expected := `{"jsonrpc":"2.0","id":12345,"result":{"String":"string arg","Int":1122,"Args":{"S":"qwerty"}}}` + + if expected != codec.output { + t.Fatalf("expected %s, got %s\n", expected, codec.output) + } +} diff --git a/rpc/v2/types.go b/rpc/v2/types.go new file mode 100644 index 0000000000..d538e0a3f7 --- /dev/null +++ b/rpc/v2/types.go @@ -0,0 +1,352 @@ +// Copyright 2015 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 . + +package v2 + +import ( + "fmt" + "math" + "math/big" + "reflect" + "strings" + + "sync" + + "github.com/ethereum/go-ethereum/event" +) + +// API describes the set of methods offered over the RPC interface +type API struct { + Namespace string // namespace under which the rpc methods of Service are exposed + Version string // api version for DApp's + Service interface{} // receiver instance which holds the methods + Public bool // indication if the methods must be considered safe for public use +} + +// callback is a method callback which was registered in the server +type callback struct { + rcvr reflect.Value // receiver of method + method reflect.Method // callback + argTypes []reflect.Type // input argument types + errPos int // err return idx, of -1 when method cannot return error + isSubscribe bool // indication if the callback is a subscription +} + +// service represents a registered object +type service struct { + name string // name for service + rcvr reflect.Value // receiver of methods for the service + typ reflect.Type // receiver type + callbacks callbacks // registered handlers + subscriptions subscriptions // available subscriptions/notifications +} + +// serverRequest is an incoming request +type serverRequest struct { + id int64 + svcname string + rcvr reflect.Value + callb *callback + args []reflect.Value + isUnsubscribe bool + err RPCError +} + +type serviceRegistry map[string]*service // collection of services +type callbacks map[string]*callback // collection of RPC callbacks +type subscriptions map[string]*callback // collection of subscription callbacks +type subscriptionRegistry map[string]Subscription // collection of subscriptions + +// Server represents a RPC server +type Server struct { + services serviceRegistry + muSubcriptions sync.Mutex // protects subscriptions + subscriptions subscriptionRegistry +} + +// rpcRequest represents a raw incoming RPC request +type rpcRequest struct { + service string + method string + id int64 + isPubSub bool + params interface{} +} + +// RPCError implements RPC error, is add support for error codec over regular go errors +type RPCError interface { + // RPC error code + Code() int + // Error message + Error() string +} + +// ServerCodec implements reading, parsing and writing RPC messages for the server side of +// a RPC session. Implementations must be go-routine safe since the codec can be called in +// multiple go-routines concurrently. +type ServerCodec interface { + // Read next request + ReadRequestHeaders() ([]rpcRequest, bool, RPCError) + // Parse request argument to the given types + ParseRequestArguments([]reflect.Type, interface{}) ([]reflect.Value, RPCError) + // Assemble success response + CreateResponse(int64, interface{}) interface{} + // Assemble error response + CreateErrorResponse(*int64, RPCError) interface{} + // Assemble error response with extra information about the error through info + CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} + // Create notification response + CreateNotification(string, interface{}) interface{} + // Write msg to client. + Write(interface{}) error + // Close underlying data stream + Close() + // Closed when underlying connection is closed + Closed() <-chan interface{} +} + +// SubscriptionMatcher returns true if the given value matches the criteria specified by the user +type SubscriptionMatcher func(interface{}) bool + +// SubscriptionOutputFormat accepts event data and has the ability to format the data before it is send to the client +type SubscriptionOutputFormat func(interface{}) interface{} + +// defaultSubscriptionOutputFormatter returns data and is used as default output format for notifications +func defaultSubscriptionOutputFormatter(data interface{}) interface{} { + return data +} + +// Subscription is used by the server to send notifications to the client +type Subscription struct { + sub event.Subscription + match SubscriptionMatcher + format SubscriptionOutputFormat +} + +// NewSubscription create a new RPC subscription +func NewSubscription(sub event.Subscription) Subscription { + return Subscription{sub, nil, defaultSubscriptionOutputFormatter} +} + +// NewSubscriptionWithOutputFormat create a new RPC subscription which a custom notification output format +func NewSubscriptionWithOutputFormat(sub event.Subscription, formatter SubscriptionOutputFormat) Subscription { + return Subscription{sub, nil, formatter} +} + +// NewSubscriptionFiltered will create a new subscription. For each raised event the given matcher is +// called. If it returns true the event is send as notification to the client, otherwise it is ignored. +func NewSubscriptionFiltered(sub event.Subscription, match SubscriptionMatcher) Subscription { + return Subscription{sub, match, defaultSubscriptionOutputFormatter} +} + +// Chan returns the channel where new events will be published. It's up the user to call the matcher to +// determine if the events are interesting for the client. +func (s *Subscription) Chan() <-chan *event.Event { + return s.sub.Chan() +} + +// Unsubscribe will end the subscription and closes the event channel +func (s *Subscription) Unsubscribe() { + s.sub.Unsubscribe() +} + +// HexNumber serializes a number to hex format using the "%#x" format +type HexNumber big.Int + +// NewHexNumber creates a new hex number instance which will serialize the given val with `%#x` on marshal. +func NewHexNumber(val interface{}) *HexNumber { + if val == nil { + return nil + } + + if v, ok := val.(*big.Int); ok && v != nil { + hn := new(big.Int).Set(v) + return (*HexNumber)(hn) + } + + rval := reflect.ValueOf(val) + + var unsigned uint64 + utype := reflect.TypeOf(unsigned) + if t := rval.Type(); t.ConvertibleTo(utype) { + hn := new(big.Int).SetUint64(rval.Convert(utype).Uint()) + return (*HexNumber)(hn) + } + + var signed int64 + stype := reflect.TypeOf(signed) + if t := rval.Type(); t.ConvertibleTo(stype) { + hn := new(big.Int).SetInt64(rval.Convert(stype).Int()) + return (*HexNumber)(hn) + } + + return nil +} + +func (h *HexNumber) UnmarshalJSON(input []byte) error { + length := len(input) + if length >= 2 && input[0] == '"' && input[length-1] == '"' { + input = input[1 : length-1] + } + + hn := (*big.Int)(h) + if _, ok := hn.SetString(string(input), 0); ok { + return nil + } + + return fmt.Errorf("Unable to parse number") +} + +// MarshalJSON serialize the hex number instance to a hex representation. +func (h *HexNumber) MarshalJSON() ([]byte, error) { + if h != nil { + hn := (*big.Int)(h) + if hn.BitLen() == 0 { + return []byte(`"0x0"`), nil + } + return []byte(fmt.Sprintf(`"0x%x"`, hn)), nil + } + return nil, nil +} + +func (h *HexNumber) Int() int { + hn := (*big.Int)(h) + return int(hn.Int64()) +} + +func (h *HexNumber) Int64() int64 { + hn := (*big.Int)(h) + return hn.Int64() +} + +func (h *HexNumber) Uint() uint { + hn := (*big.Int)(h) + return uint(hn.Uint64()) +} + +func (h *HexNumber) Uint64() uint64 { + hn := (*big.Int)(h) + return hn.Uint64() +} + +func (h *HexNumber) BigInt() *big.Int { + return (*big.Int)(h) +} + +type Number int64 + +func (n *Number) UnmarshalJSON(data []byte) error { + input := strings.TrimSpace(string(data)) + + if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] + } + + if len(input) == 0 { + *n = Number(latestBlockNumber.Int64()) + return nil + } + + in := new(big.Int) + _, ok := in.SetString(input, 0) + + if !ok { // test if user supplied string tag + return fmt.Errorf(`invalid number %s`, data) + } + + if in.Cmp(earliestBlockNumber) >= 0 && in.Cmp(maxBlockNumber) <= 0 { + *n = Number(in.Int64()) + return nil + } + + return fmt.Errorf("blocknumber not in range [%d, %d]", earliestBlockNumber, maxBlockNumber) +} + +func (n *Number) Int64() int64 { + return *(*int64)(n) +} + +func (n *Number) BigInt() *big.Int { + return big.NewInt(n.Int64()) +} + +var ( + pendingBlockNumber = big.NewInt(-2) + latestBlockNumber = big.NewInt(-1) + earliestBlockNumber = big.NewInt(0) + maxBlockNumber = big.NewInt(math.MaxInt64) +) + +type BlockNumber int64 + +const ( + PendingBlockNumber = BlockNumber(-2) + LatestBlockNumber = BlockNumber(-1) +) + +// UnmarshalJSON parses the given JSON fragement into a BlockNumber. It supports: +// - "latest" or "earliest" as string arguments +// - the block number +// Returned errors: +// - an unsupported error when "pending" is specified (not yet implemented) +// - an invalid block number error when the given argument isn't a known strings +// - an out of range error when the given block number is either too little or too large +func (bn *BlockNumber) UnmarshalJSON(data []byte) error { + input := strings.TrimSpace(string(data)) + + if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] + } + + if len(input) == 0 { + *bn = BlockNumber(latestBlockNumber.Int64()) + return nil + } + + in := new(big.Int) + _, ok := in.SetString(input, 0) + + if !ok { // test if user supplied string tag + strBlockNumber := input + if strBlockNumber == "latest" { + *bn = BlockNumber(latestBlockNumber.Int64()) + return nil + } + + if strBlockNumber == "earliest" { + *bn = BlockNumber(earliestBlockNumber.Int64()) + return nil + } + + if strBlockNumber == "pending" { + *bn = BlockNumber(pendingBlockNumber.Int64()) + return nil + } + + return fmt.Errorf(`invalid blocknumber %s`, data) + } + + if in.Cmp(earliestBlockNumber) >= 0 && in.Cmp(maxBlockNumber) <= 0 { + *bn = BlockNumber(in.Int64()) + return nil + } + + return fmt.Errorf("blocknumber not in range [%d, %d]", earliestBlockNumber, maxBlockNumber) +} + +func (bn *BlockNumber) Int64() int64 { + return (int64)(*bn) +} diff --git a/rpc/v2/types_test.go b/rpc/v2/types_test.go new file mode 100644 index 0000000000..f73a2369ee --- /dev/null +++ b/rpc/v2/types_test.go @@ -0,0 +1,57 @@ +package v2 + +import ( + "bytes" + "encoding/json" + "math/big" + "testing" +) + +func TestNewHexNumber(t *testing.T) { + tests := []interface{}{big.NewInt(123), int64(123), uint64(123), int8(123), uint8(123)} + + for i, v := range tests { + hn := NewHexNumber(v) + if hn == nil { + t.Fatalf("Unable to create hex number instance for tests[%d]", i) + } + if hn.Int64() != 123 { + t.Fatalf("expected %d, got %d on value tests[%d]", 123, hn.Int64(), i) + } + } + + failures := []interface{}{"", nil, []byte{1, 2, 3, 4}} + for i, v := range failures { + hn := NewHexNumber(v) + if hn != nil { + t.Fatalf("Creating a nex number instance of %T should fail (failures[%d])", failures[i], i) + } + } +} + +func TestHexNumberUnmarshalJSON(t *testing.T) { + tests := []string{`"0x4d2"`, "1234", `"1234"`} + for i, v := range tests { + var hn HexNumber + if err := json.Unmarshal([]byte(v), &hn); err != nil { + t.Fatalf("Test %d failed - %s", i, err) + } + + if hn.Int64() != 1234 { + t.Fatalf("Expected %d, got %d for test[%d]", 1234, hn.Int64(), i) + } + } +} + +func TestHexNumberMarshalJSON(t *testing.T) { + hn := NewHexNumber(1234567890) + got, err := json.Marshal(hn) + if err != nil { + t.Fatalf("Unable to marshal hex number - %s", err) + } + + exp := []byte(`"0x499602d2"`) + if bytes.Compare(exp, got) != 0 { + t.Fatalf("Invalid json.Marshal, expected '%s', got '%s'", exp, got) + } +} diff --git a/rpc/v2/utils.go b/rpc/v2/utils.go new file mode 100644 index 0000000000..a564b24733 --- /dev/null +++ b/rpc/v2/utils.go @@ -0,0 +1,205 @@ +// Copyright 2015 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 . + +package v2 + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "math/big" + "reflect" + "unicode" + "unicode/utf8" +) + +// Is this an exported - upper case - name? +func isExported(name string) bool { + rune, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(rune) +} + +// Is this type exported or a builtin? +func isExportedOrBuiltinType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + // PkgPath will be non-empty even for an exported type, + // so we need to check the type name as well. + return isExported(t.Name()) || t.PkgPath() == "" +} + +var errorType = reflect.TypeOf((*error)(nil)).Elem() + +// Implements this type the error interface +func isErrorType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t.Implements(errorType) +} + +var subscriptionType = reflect.TypeOf((*Subscription)(nil)).Elem() + +func isSubscriptionType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t == subscriptionType +} + +// isPubSub tests whether the given method return the pair (v2.Subscription, error) +func isPubSub(methodType reflect.Type) bool { + if methodType.NumOut() != 2 { + return false + } + return isSubscriptionType(methodType.Out(0)) && isErrorType(methodType.Out(1)) +} + +// formatName will convert to first character to lower case +func formatName(name string) string { + ret := []rune(name) + if len(ret) > 0 { + ret[0] = unicode.ToLower(ret[0]) + } + return string(ret) +} + +var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem() + +// Indication if this type should be serialized in hex +func isHexNum(t reflect.Type) bool { + if t == nil { + return false + } + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + + return t == bigIntType +} + +var blockNumberType = reflect.TypeOf((*BlockNumber)(nil)).Elem() + +// Indication if the given block is a BlockNumber +func isBlockNumber(t reflect.Type) bool { + if t == nil { + return false + } + + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + + return t == blockNumberType +} + +// suitableCallbacks iterates over the methods of the given type. It will determine if a method satisfies the criteria +// for a RPC callback or a subscription callback and adds it to the collection of callbacks or subscriptions. See server +// documentation for a summary of these criteria. +func suitableCallbacks(rcvr reflect.Value, typ reflect.Type) (callbacks, subscriptions) { + callbacks := make(callbacks) + subscriptions := make(subscriptions) + +METHODS: + for m := 0; m < typ.NumMethod(); m++ { + method := typ.Method(m) + mtype := method.Type + mname := formatName(method.Name) + if method.PkgPath != "" { // method must be exported + continue + } + + var h callback + h.isSubscribe = isPubSub(mtype) + h.rcvr = rcvr + h.method = method + h.errPos = -1 + + if h.isSubscribe { + h.argTypes = make([]reflect.Type, mtype.NumIn()-1) // skip rcvr type + for i := 1; i < mtype.NumIn(); i++ { + argType := mtype.In(i) + if isExportedOrBuiltinType(argType) { + h.argTypes[i-1] = argType + } else { + continue METHODS + } + } + + subscriptions[mname] = &h + continue METHODS + } + + numIn := mtype.NumIn() + + // determine method arguments, ignore first arg since it's the receiver type + // Arguments must be exported or builtin types + h.argTypes = make([]reflect.Type, numIn-1) + for i := 1; i < numIn; i++ { + argType := mtype.In(i) + if !isExportedOrBuiltinType(argType) { + continue METHODS + } + h.argTypes[i-1] = argType + } + + // check that all returned values are exported or builtin types + for i := 0; i < mtype.NumOut(); i++ { + if !isExportedOrBuiltinType(mtype.Out(i)) { + continue METHODS + } + } + + // when a method returns an error it must be the last returned value + h.errPos = -1 + for i := 0; i < mtype.NumOut(); i++ { + if isErrorType(mtype.Out(i)) { + h.errPos = i + break + } + } + + if h.errPos >= 0 && h.errPos != mtype.NumOut()-1 { + continue METHODS + } + + switch mtype.NumOut() { + case 0, 1: + break + case 2: + if h.errPos == -1 { // method must one return value and 1 error + continue METHODS + } + break + default: + continue METHODS + } + + callbacks[mname] = &h + } + + return callbacks, subscriptions +} + +func newSubscriptionId() (string, error) { + var subid [16]byte + n, _ := rand.Read(subid[:]) + if n != 16 { + return "", errors.New("Unable to generate subscription id") + } + return "0x" + hex.EncodeToString(subid[:]), nil +} diff --git a/whisper/api.go b/whisper/api.go new file mode 100644 index 0000000000..16f8bd3291 --- /dev/null +++ b/whisper/api.go @@ -0,0 +1,413 @@ +// Copyright 2015 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 . + +package whisper + +import ( + "encoding/json" + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + rpc "github.com/ethereum/go-ethereum/rpc/v2" +) + +// PublicWhisperAPI provides the whisper RPC service. +type PublicWhisperAPI struct { + w *Whisper + + messagesMu sync.RWMutex + messages map[int]*whisperFilter +} + +type whisperOfflineError struct{} + +func (e *whisperOfflineError) Error() string { + return "whisper is offline" +} + +// whisperOffLineErr is returned when the node doesn't offer the shh service. +var whisperOffLineErr = new(whisperOfflineError) + +// NewPublicWhisperAPI create a new RPC whisper service. +func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI { + return &PublicWhisperAPI{w: w, messages: make(map[int]*whisperFilter)} +} + +// Version returns the Whisper version this node offers. +func (s *PublicWhisperAPI) Version() (*rpc.HexNumber, error) { + if s.w == nil { + return rpc.NewHexNumber(0), whisperOffLineErr + } + return rpc.NewHexNumber(s.w.Version()), nil +} + +// HasIdentity checks if the the whisper node is configured with the private key +// of the specified public pair. +func (s *PublicWhisperAPI) HasIdentity(identity string) (bool, error) { + if s.w == nil { + return false, whisperOffLineErr + } + return s.w.HasIdentity(crypto.ToECDSAPub(common.FromHex(identity))), nil +} + +// NewIdentity generates a new cryptographic identity for the client, and injects +// it into the known identities for message decryption. +func (s *PublicWhisperAPI) NewIdentity() (string, error) { + if s.w == nil { + return "", whisperOffLineErr + } + + identity := s.w.NewIdentity() + return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil +} + +type NewFilterArgs struct { + To string + From string + Topics [][][]byte +} + +// NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages. +func (s *PublicWhisperAPI) NewFilter(args *NewFilterArgs) (*rpc.HexNumber, error) { + if s.w == nil { + return nil, whisperOffLineErr + } + + var id int + filter := Filter{ + To: crypto.ToECDSAPub(common.FromHex(args.To)), + From: crypto.ToECDSAPub(common.FromHex(args.From)), + Topics: NewFilterTopics(args.Topics...), + Fn: func(message *Message) { + wmsg := NewWhisperMessage(message) + s.messagesMu.RLock() // Only read lock to the filter pool + defer s.messagesMu.RUnlock() + if s.messages[id] != nil { + s.messages[id].insert(wmsg) + } + }, + } + + id = s.w.Watch(filter) + + s.messagesMu.Lock() + s.messages[id] = newWhisperFilter(id, s.w) + s.messagesMu.Unlock() + + return rpc.NewHexNumber(id), nil +} + +// GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval. +func (s *PublicWhisperAPI) GetFilterChanges(filterId rpc.HexNumber) []WhisperMessage { + s.messagesMu.RLock() + defer s.messagesMu.RUnlock() + + if s.messages[filterId.Int()] != nil { + if changes := s.messages[filterId.Int()].retrieve(); changes != nil { + return changes + } + } + return returnWhisperMessages(nil) +} + +// UninstallFilter disables and removes an existing filter. +func (s *PublicWhisperAPI) UninstallFilter(filterId rpc.HexNumber) bool { + s.messagesMu.Lock() + defer s.messagesMu.Unlock() + + if _, ok := s.messages[filterId.Int()]; ok { + delete(s.messages, filterId.Int()) + return true + } + return false +} + +// GetMessages retrieves all the known messages that match a specific filter. +func (s *PublicWhisperAPI) GetMessages(filterId rpc.HexNumber) []WhisperMessage { + // Retrieve all the cached messages matching a specific, existing filter + s.messagesMu.RLock() + defer s.messagesMu.RUnlock() + + var messages []*Message + if s.messages[filterId.Int()] != nil { + messages = s.messages[filterId.Int()].messages() + } + + return returnWhisperMessages(messages) +} + +// returnWhisperMessages converts aNhisper message to a RPC whisper message. +func returnWhisperMessages(messages []*Message) []WhisperMessage { + msgs := make([]WhisperMessage, len(messages)) + for i, msg := range messages { + msgs[i] = NewWhisperMessage(msg) + } + return msgs +} + +type PostArgs struct { + From string `json:"from"` + To string `json:"to"` + Topics [][]byte `json:"topics"` + Payload string `json:"payload"` + Priority int64 `json:"priority"` + TTL int64 `json:"ttl"` +} + +// Post injects a message into the whisper network for distribution. +func (s *PublicWhisperAPI) Post(args *PostArgs) (bool, error) { + if s.w == nil { + return false, whisperOffLineErr + } + + // construct whisper message with transmission options + message := NewMessage(common.FromHex(args.Payload)) + options := Options{ + To: crypto.ToECDSAPub(common.FromHex(args.To)), + TTL: time.Duration(args.TTL) * time.Second, + Topics: NewTopics(args.Topics...), + } + + // set sender identity + if len(args.From) > 0 { + if key := s.w.GetIdentity(crypto.ToECDSAPub(common.FromHex(args.From))); key != nil { + options.From = key + } else { + return false, fmt.Errorf("unknown identity to send from: %s", args.From) + } + } + + // Wrap and send the message + pow := time.Duration(args.Priority) * time.Millisecond + envelope, err := message.Wrap(pow, options) + if err != nil { + return false, err + } + + return true, s.w.Send(envelope) +} + +// WhisperMessage is the RPC representation of a whisper message. +type WhisperMessage struct { + ref *Message + + Payload string `json:"payload"` + To string `json:"to"` + From string `json:"from"` + Sent int64 `json:"sent"` + TTL int64 `json:"ttl"` + Hash string `json:"hash"` +} + +func (args *PostArgs) UnmarshalJSON(data []byte) (err error) { + var obj struct { + From string `json:"from"` + To string `json:"to"` + Topics []string `json:"topics"` + Payload string `json:"payload"` + Priority rpc.HexNumber `json:"priority"` + TTL rpc.HexNumber `json:"ttl"` + } + + if err := json.Unmarshal(data, &obj); err != nil { + return err + } + + args.From = obj.From + args.To = obj.To + args.Payload = obj.Payload + args.Priority = obj.Priority.Int64() + args.TTL = obj.TTL.Int64() + + // decode topic strings + args.Topics = make([][]byte, len(obj.Topics)) + for i, topic := range obj.Topics { + args.Topics[i] = common.FromHex(topic) + } + + return nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a +// JSON message blob into a WhisperFilterArgs structure. +func (args *NewFilterArgs) UnmarshalJSON(b []byte) (err error) { + // Unmarshal the JSON message and sanity check + var obj struct { + To interface{} `json:"to"` + From interface{} `json:"from"` + Topics interface{} `json:"topics"` + } + if err := json.Unmarshal(b, &obj); err != nil { + return err + } + + // Retrieve the simple data contents of the filter arguments + if obj.To == nil { + args.To = "" + } else { + argstr, ok := obj.To.(string) + if !ok { + return fmt.Errorf("to is not a string") + } + args.To = argstr + } + if obj.From == nil { + args.From = "" + } else { + argstr, ok := obj.From.(string) + if !ok { + return fmt.Errorf("from is not a string") + } + args.From = argstr + } + // Construct the nested topic array + if obj.Topics != nil { + // Make sure we have an actual topic array + list, ok := obj.Topics.([]interface{}) + if !ok { + return fmt.Errorf("topics is not an array") + } + // Iterate over each topic and handle nil, string or array + topics := make([][]string, len(list)) + for idx, field := range list { + switch value := field.(type) { + case nil: + topics[idx] = []string{} + + case string: + topics[idx] = []string{value} + + case []interface{}: + topics[idx] = make([]string, len(value)) + for i, nested := range value { + switch value := nested.(type) { + case nil: + topics[idx][i] = "" + + case string: + topics[idx][i] = value + + default: + return fmt.Errorf("topic[%d][%d] is not a string", idx, i) + } + } + default: + return fmt.Errorf("topic[%d] not a string or array", idx) + } + } + + topicsDecoded := make([][][]byte, len(topics)) + for i, condition := range topics { + topicsDecoded[i] = make([][]byte, len(condition)) + for j, topic := range condition { + topicsDecoded[i][j] = common.FromHex(topic) + } + } + + args.Topics = topicsDecoded + } + return nil +} + +// whisperFilter is the message cache matching a specific filter, accumulating +// inbound messages until the are requested by the client. +type whisperFilter struct { + id int // Filter identifier for old message retrieval + ref *Whisper // Whisper reference for old message retrieval + + cache []WhisperMessage // Cache of messages not yet polled + skip map[common.Hash]struct{} // List of retrieved messages to avoid duplication + update time.Time // Time of the last message query + + lock sync.RWMutex // Lock protecting the filter internals +} + +// messages retrieves all the cached messages from the entire pool matching the +// filter, resetting the filter's change buffer. +func (w *whisperFilter) messages() []*Message { + w.lock.Lock() + defer w.lock.Unlock() + + w.cache = nil + w.update = time.Now() + + w.skip = make(map[common.Hash]struct{}) + messages := w.ref.Messages(w.id) + for _, message := range messages { + w.skip[message.Hash] = struct{}{} + } + return messages +} + +// insert injects a new batch of messages into the filter cache. +func (w *whisperFilter) insert(messages ...WhisperMessage) { + w.lock.Lock() + defer w.lock.Unlock() + + for _, message := range messages { + if _, ok := w.skip[message.ref.Hash]; !ok { + w.cache = append(w.cache, messages...) + } + } +} + +// retrieve fetches all the cached messages from the filter. +func (w *whisperFilter) retrieve() (messages []WhisperMessage) { + w.lock.Lock() + defer w.lock.Unlock() + + messages, w.cache = w.cache, nil + w.update = time.Now() + + return +} + +// activity returns the last time instance when client requests were executed on +// the filter. +func (w *whisperFilter) activity() time.Time { + w.lock.RLock() + defer w.lock.RUnlock() + + return w.update +} + +// newWhisperFilter creates a new serialized, poll based whisper topic filter. +func newWhisperFilter(id int, ref *Whisper) *whisperFilter { + return &whisperFilter{ + id: id, + ref: ref, + + update: time.Now(), + skip: make(map[common.Hash]struct{}), + } +} + +// NewWhisperMessage converts an internal message into an API version. +func NewWhisperMessage(message *Message) WhisperMessage { + return WhisperMessage{ + ref: message, + + Payload: common.ToHex(message.Payload), + From: common.ToHex(crypto.FromECDSAPub(message.Recover())), + To: common.ToHex(crypto.FromECDSAPub(message.To)), + Sent: message.Sent.Unix(), + TTL: int64(message.TTL / time.Second), + Hash: common.ToHex(message.Hash.Bytes()), + } +} diff --git a/whisper/whisper.go b/whisper/whisper.go index 7201062b81..e99950b760 100644 --- a/whisper/whisper.go +++ b/whisper/whisper.go @@ -28,6 +28,8 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" + rpc "github.com/ethereum/go-ethereum/rpc/v2" + "gopkg.in/fatih/set.v0" ) @@ -98,6 +100,18 @@ func New() *Whisper { return whisper } +// APIs returns the RPC descriptors the Whisper implementation offers +func (s *Whisper) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "shh", + Version: "1.0", + Service: NewPublicWhisperAPI(s), + Public: true, + }, + } +} + // Protocols returns the whisper sub-protocols ran by this particular client. func (self *Whisper) Protocols() []p2p.Protocol { return []p2p.Protocol{self.protocol} From d8370a4e15f00afeb783f7f3be8b47e93c4338d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 4 Dec 2015 20:56:11 +0200 Subject: [PATCH 31/78] core, eth, node, rpc: port the admin and debug API --- cmd/gethrpctest/main.go | 2 +- cmd/utils/flags.go | 2 +- eth/api.go | 217 ++++++++++++++++++++++++++++++++--- eth/backend.go | 13 +++ node/api.go | 244 ++++++++++++++++++++++++++++++++++++++++ node/node.go | 30 ++++- rpc/api/debug_js.go | 12 +- 7 files changed, 494 insertions(+), 26 deletions(-) create mode 100644 node/api.go diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index 636d329e48..ae815c4a66 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -214,7 +214,7 @@ func StartIPC(stack *node.Node) error { server := rpc.NewServer() // register package API's this node provides - offered := stack.RPCAPIs() + offered := stack.APIs() for _, api := range offered { server.RegisterName(api.Namespace, api.Service) glog.V(logger.Debug).Infof("Register %T@%s for IPC service\n", api.Service, api.Namespace) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1aa8f4e894..63efa08ee3 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -798,7 +798,7 @@ func StartIPC(stack *node.Node, ctx *cli.Context) error { server := rpc.NewServer() // register package API's this node provides - offered := stack.RPCAPIs() + offered := stack.APIs() for _, api := range offered { server.RegisterName(api.Namespace, api.Service) glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) diff --git a/eth/api.go b/eth/api.go index 06fc2deb1d..068b350dbe 100644 --- a/eth/api.go +++ b/eth/api.go @@ -21,7 +21,9 @@ import ( "encoding/json" "errors" "fmt" + "io" "math/big" + "os" "sync" "time" @@ -46,7 +48,7 @@ import ( const ( defaultGasPrice = uint64(10000000000000) - defaultGas = uint64(90000) + defaultGas = uint64(90000) ) // PublicEthereumAPI provides an API to access Ethereum related information. @@ -471,12 +473,12 @@ type callmsg struct { // accessor boilerplate to implement core.Message func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } -func (m callmsg) Nonce() uint64 { return m.from.Nonce() } -func (m callmsg) To() *common.Address { return m.to } -func (m callmsg) GasPrice() *big.Int { return m.gasPrice } -func (m callmsg) Gas() *big.Int { return m.gas } -func (m callmsg) Value() *big.Int { return m.value } -func (m callmsg) Data() []byte { return m.data } +func (m callmsg) Nonce() uint64 { return m.from.Nonce() } +func (m callmsg) To() *common.Address { return m.to } +func (m callmsg) GasPrice() *big.Int { return m.gasPrice } +func (m callmsg) Gas() *big.Int { return m.gas } +func (m callmsg) Value() *big.Int { return m.value } +func (m callmsg) Data() []byte { return m.data } type CallArgs struct { From common.Address `json:"from"` @@ -972,20 +974,20 @@ func (s *PublicTransactionPoolAPI) Sign(address common.Address, data string) (st } type SignTransactionArgs struct { - From common.Address - To common.Address - Nonce *rpc.HexNumber - Value *rpc.HexNumber - Gas *rpc.HexNumber - GasPrice *rpc.HexNumber - Data string + From common.Address + To common.Address + Nonce *rpc.HexNumber + Value *rpc.HexNumber + Gas *rpc.HexNumber + GasPrice *rpc.HexNumber + Data string BlockNumber int64 } // Tx is a helper object for argument and return values type Tx struct { - tx *types.Transaction + tx *types.Transaction To *common.Address `json:"to"` From common.Address `json:"from"` @@ -1214,3 +1216,188 @@ func (s *PublicTransactionPoolAPI) Resend(tx *Tx, gasPrice, gasLimit *rpc.HexNum return common.Hash{}, fmt.Errorf("Transaction %#x not found", tx.Hash) } + +// PrivateAdminAPI is the collection of Etheruem APIs exposed over the private +// admin endpoint. +type PrivateAdminAPI struct { + eth *Ethereum +} + +// NewPrivateAdminAPI creates a new API definition for the private admin methods +// of the Ethereum service. +func NewPrivateAdminAPI(eth *Ethereum) *PrivateAdminAPI { + return &PrivateAdminAPI{eth: eth} +} + +// SetSolc sets the Solidity compiler path to be used by the node. +func (api *PrivateAdminAPI) SetSolc(path string) (string, error) { + solc, err := api.eth.SetSolc(path) + if err != nil { + return "", err + } + return solc.Info(), nil +} + +// ExportChain exports the current blockchain into a local file. +func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) { + // Make sure we can create the file to export into + out, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + if err != nil { + return false, err + } + defer out.Close() + + // Export the blockchain + if err := api.eth.BlockChain().Export(out); err != nil { + return false, err + } + return true, nil +} + +// ImportChain imports a blockchain from a local file. +func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) { + // Make sure the can access the file to import + in, err := os.Open(file) + if err != nil { + return false, err + } + defer in.Close() + + // Run actual the import in pre-configured batches + stream := rlp.NewStream(in, 0) + + blocks, index := make([]*types.Block, 0, 2500), 0 + for batch := 0; ; batch++ { + // Load a batch of blocks from the input file + for len(blocks) < cap(blocks) { + block := new(types.Block) + if err := stream.Decode(block); err == io.EOF { + break + } else if err != nil { + return false, fmt.Errorf("block %d: failed to parse: %v", index, err) + } + blocks = append(blocks, block) + index++ + } + if len(blocks) == 0 { + break + } + // Import the batch and reset the buffer + if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil { + return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err) + } + blocks = blocks[:0] + } + return true, nil +} + +// PublicDebugAPI is the collection of Etheruem APIs exposed over the public +// debugging endpoint. +type PublicDebugAPI struct { + eth *Ethereum +} + +// NewPublicDebugAPI creates a new API definition for the public debug methods +// of the Ethereum service. +func NewPublicDebugAPI(eth *Ethereum) *PublicDebugAPI { + return &PublicDebugAPI{eth: eth} +} + +// DumpBlock retrieves the entire state of the database at a given block. +func (api *PublicDebugAPI) DumpBlock(number uint64) (state.World, error) { + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return state.World{}, fmt.Errorf("block #%d not found", number) + } + stateDb, err := state.New(block.Root(), api.eth.ChainDb()) + if err != nil { + return state.World{}, err + } + return stateDb.RawDump(), nil +} + +// GetBlockRlp retrieves the RLP encoded for of a single block. +func (api *PublicDebugAPI) GetBlockRlp(number uint64) (string, error) { + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return "", fmt.Errorf("block #%d not found", number) + } + encoded, err := rlp.EncodeToBytes(block) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", encoded), nil +} + +// PrintBlock retrieves a block and returns its pretty printed form. +func (api *PublicDebugAPI) PrintBlock(number uint64) (string, error) { + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return "", fmt.Errorf("block #%d not found", number) + } + return fmt.Sprintf("%s", block), nil +} + +// SeedHash retrieves the seed hash of a block. +func (api *PublicDebugAPI) SeedHash(number uint64) (string, error) { + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return "", fmt.Errorf("block #%d not found", number) + } + hash, err := ethash.GetSeedHash(number) + if err != nil { + return "", err + } + return fmt.Sprintf("0x%x", hash), nil +} + +// PrivateDebugAPI is the collection of Etheruem APIs exposed over the private +// debugging endpoint. +type PrivateDebugAPI struct { + eth *Ethereum +} + +// NewPrivateDebugAPI creates a new API definition for the private debug methods +// of the Ethereum service. +func NewPrivateDebugAPI(eth *Ethereum) *PrivateDebugAPI { + return &PrivateDebugAPI{eth: eth} +} + +// ProcessBlock reprocesses an already owned block. +func (api *PrivateDebugAPI) ProcessBlock(number uint64) (bool, error) { + // Fetch the block that we aim to reprocess + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return false, fmt.Errorf("block #%d not found", number) + } + // Temporarily enable debugging + defer func(old bool) { vm.Debug = old }(vm.Debug) + vm.Debug = true + + // Validate and reprocess the block + var ( + blockchain = api.eth.BlockChain() + validator = blockchain.Validator() + processor = blockchain.Processor() + ) + if err := core.ValidateHeader(blockchain.AuxValidator(), block.Header(), blockchain.GetHeader(block.ParentHash()), true, false); err != nil { + return false, err + } + statedb, err := state.New(blockchain.GetBlock(block.ParentHash()).Root(), api.eth.ChainDb()) + if err != nil { + return false, err + } + receipts, _, usedGas, err := processor.Process(block, statedb) + if err != nil { + return false, err + } + if err := validator.ValidateState(block, blockchain.GetBlock(block.ParentHash()), statedb, receipts, usedGas); err != nil { + return false, err + } + return true, nil +} + +// SetHead rewinds the head of the blockchain to a previous block. +func (api *PrivateDebugAPI) SetHead(number uint64) { + api.eth.BlockChain().SetHead(number) +} diff --git a/eth/backend.go b/eth/backend.go index ad98635a5e..d51446d51a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -295,6 +295,19 @@ func (s *Ethereum) APIs() []rpc.API { Version: "1.0", Service: filters.NewPublicFilterAPI(s.ChainDb(), s.EventMux()), Public: true, + }, { + Namespace: "admin", + Version: "1.0", + Service: NewPrivateAdminAPI(s), + }, { + Namespace: "debug", + Version: "1.0", + Service: NewPublicDebugAPI(s), + Public: true, + }, { + Namespace: "debug", + Version: "1.0", + Service: NewPrivateDebugAPI(s), }, } } diff --git a/node/api.go b/node/api.go new file mode 100644 index 0000000000..a44ee16c07 --- /dev/null +++ b/node/api.go @@ -0,0 +1,244 @@ +// Copyright 2015 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 . + +package node + +import ( + "fmt" + "strings" + "time" + + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/rcrowley/go-metrics" +) + +// PrivateAdminAPI is the collection of administrative API methods exposed only +// over a secure RPC channel. +type PrivateAdminAPI struct { + node *Node // Node interfaced by this API +} + +// NewPrivateAdminAPI creates a new API definition for the private admin methods +// of the node itself. +func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI { + return &PrivateAdminAPI{node: node} +} + +// AddPeer requests connecting to a remote node, and also maintaining the new +// connection at all times, even reconnecting if it is lost. +func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { + // Make sure the server is running, fail otherwise + server := api.node.Server() + if server == nil { + return false, ErrNodeStopped + } + // Try to add 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.AddPeer(node) + return true, nil +} + +// StartRPC starts the HTTP RPC API server. +func (api *PrivateAdminAPI) StartRPC(address string, port int, cors string, apis string) (bool, error) { + /*// Parse the list of API modules to make available + apis, err := api.ParseApiString(apis, codec.JSON, xeth.New(api.node, nil), api.node) + if err != nil { + return false, err + } + // Configure and start the HTTP RPC server + config := comms.HttpConfig{ + ListenAddress: address, + ListenPort: port, + CorsDomain: cors, + } + if err := comms.StartHttp(config, self.codec, api.Merge(apis...)); err != nil { + return false, err + } + return true, nil*/ + return false, fmt.Errorf("needs new RPC implementation to resolve circular dependency") +} + +// StopRPC terminates an already running HTTP RPC API endpoint. +func (api *PrivateAdminAPI) StopRPC() { + comms.StopHttp() +} + +// PublicAdminAPI is the collection of administrative API methods exposed over +// both secure and unsecure RPC channels. +type PublicAdminAPI struct { + node *Node // Node interfaced by this API +} + +// NewPublicAdminAPI creates a new API definition for the public admin methods +// of the node itself. +func NewPublicAdminAPI(node *Node) *PublicAdminAPI { + return &PublicAdminAPI{node: node} +} + +// Peers retrieves all the information we know about each individual peer at the +// protocol granularity. +func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { + server := api.node.Server() + if server == nil { + return nil, ErrNodeStopped + } + return server.PeersInfo(), nil +} + +// NodeInfo retrieves all the information we know about the host node at the +// protocol granularity. +func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { + server := api.node.Server() + if server == nil { + return nil, ErrNodeStopped + } + return server.NodeInfo(), nil +} + +// Datadir retrieves the current data directory the node is using. +func (api *PublicAdminAPI) Datadir() string { + return api.node.DataDir() +} + +// PrivateDebugAPI is the collection of debugging related API methods exposed +// only over a secure RPC channel. +type PrivateDebugAPI struct { + node *Node // Node interfaced by this API +} + +// NewPrivateDebugAPI creates a new API definition for the private debug methods +// of the node itself. +func NewPrivateDebugAPI(node *Node) *PrivateDebugAPI { + return &PrivateDebugAPI{node: node} +} + +// Verbosity updates the node's logging verbosity. Note, due to the lack of fine +// grained contextual loggers, this will update the verbosity level for the entire +// process, not just this node instance. +func (api *PrivateDebugAPI) Verbosity(level int) { + glog.SetV(level) +} + +// PublicDebugAPI is the collection of debugging related API methods exposed over +// both secure and unsecure RPC channels. +type PublicDebugAPI struct { + node *Node // Node interfaced by this API +} + +// NewPublicDebugAPI creates a new API definition for the public debug methods +// of the node itself. +func NewPublicDebugAPI(node *Node) *PublicDebugAPI { + return &PublicDebugAPI{node: node} +} + +// Metrics retrieves all the known system metric collected by the node. +func (api *PublicDebugAPI) Metrics(raw bool) (map[string]interface{}, error) { + // Create a rate formatter + units := []string{"", "K", "M", "G", "T", "E", "P"} + round := func(value float64, prec int) string { + unit := 0 + for value >= 1000 { + unit, value, prec = unit+1, value/1000, 2 + } + return fmt.Sprintf(fmt.Sprintf("%%.%df%s", prec, units[unit]), value) + } + format := func(total float64, rate float64) string { + return fmt.Sprintf("%s (%s/s)", round(total, 0), round(rate, 2)) + } + // Iterate over all the metrics, and just dump for now + counters := make(map[string]interface{}) + metrics.DefaultRegistry.Each(func(name string, metric interface{}) { + // Create or retrieve the counter hierarchy for this metric + root, parts := counters, strings.Split(name, "/") + for _, part := range parts[:len(parts)-1] { + if _, ok := root[part]; !ok { + root[part] = make(map[string]interface{}) + } + root = root[part].(map[string]interface{}) + } + name = parts[len(parts)-1] + + // Fill the counter with the metric details, formatting if requested + if raw { + switch metric := metric.(type) { + case metrics.Meter: + root[name] = map[string]interface{}{ + "AvgRate01Min": metric.Rate1(), + "AvgRate05Min": metric.Rate5(), + "AvgRate15Min": metric.Rate15(), + "MeanRate": metric.RateMean(), + "Overall": float64(metric.Count()), + } + + case metrics.Timer: + root[name] = map[string]interface{}{ + "AvgRate01Min": metric.Rate1(), + "AvgRate05Min": metric.Rate5(), + "AvgRate15Min": metric.Rate15(), + "MeanRate": metric.RateMean(), + "Overall": float64(metric.Count()), + "Percentiles": map[string]interface{}{ + "5": metric.Percentile(0.05), + "20": metric.Percentile(0.2), + "50": metric.Percentile(0.5), + "80": metric.Percentile(0.8), + "95": metric.Percentile(0.95), + }, + } + + default: + root[name] = "Unknown metric type" + } + } else { + switch metric := metric.(type) { + case metrics.Meter: + root[name] = map[string]interface{}{ + "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), + "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), + "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), + "Overall": format(float64(metric.Count()), metric.RateMean()), + } + + case metrics.Timer: + root[name] = map[string]interface{}{ + "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), + "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), + "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), + "Overall": format(float64(metric.Count()), metric.RateMean()), + "Maximum": time.Duration(metric.Max()).String(), + "Minimum": time.Duration(metric.Min()).String(), + "Percentiles": map[string]interface{}{ + "5": time.Duration(metric.Percentile(0.05)).String(), + "20": time.Duration(metric.Percentile(0.2)).String(), + "50": time.Duration(metric.Percentile(0.5)).String(), + "80": time.Duration(metric.Percentile(0.8)).String(), + "95": time.Duration(metric.Percentile(0.95)).String(), + }, + } + + default: + root[name] = "Unknown metric type" + } + } + }) + return counters, nil +} diff --git a/node/node.go b/node/node.go index d6debe1237..5d7b5869c5 100644 --- a/node/node.go +++ b/node/node.go @@ -266,9 +266,33 @@ func (n *Node) EventMux() *event.TypeMux { return n.eventmux } -// RPCAPIs returns the collection of RPC descriptor this node offers -func (n *Node) RPCAPIs() []rpc.API { - var apis []rpc.API +// APIs returns the collection of RPC descriptor this node offers. This method +// is just a quick placeholder passthrough for the RPC update, which in the next +// step will be fully integrated into the node itself. +func (n *Node) APIs() []rpc.API { + // Define all the APIs owned by the node itself + apis := []rpc.API{ + { + Namespace: "admin", + Version: "1.0", + Service: NewPrivateAdminAPI(n), + }, { + Namespace: "admin", + Version: "1.0", + Service: NewPublicAdminAPI(n), + Public: true, + }, { + Namespace: "debug", + Version: "1.0", + Service: NewPrivateDebugAPI(n), + }, { + Namespace: "debug", + Version: "1.0", + Service: NewPublicDebugAPI(n), + Public: true, + }, + } + // Inject all the APIs owned by various services for _, api := range n.services { apis = append(apis, api.APIs()...) } diff --git a/rpc/api/debug_js.go b/rpc/api/debug_js.go index 0eb9f97f15..03755ada0d 100644 --- a/rpc/api/debug_js.go +++ b/rpc/api/debug_js.go @@ -25,37 +25,37 @@ web3._extend({ name: 'printBlock', call: 'debug_printBlock', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'getBlockRlp', call: 'debug_getBlockRlp', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'setHead', call: 'debug_setHead', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'processBlock', call: 'debug_processBlock', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'seedHash', call: 'debug_seedHash', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'dumpBlock', call: 'debug_dumpBlock', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'metrics', From 975c5912610013d5486336ab825ede86c8369b9a Mon Sep 17 00:00:00 2001 From: zsfelfoldi Date: Sat, 21 Nov 2015 00:40:36 +0100 Subject: [PATCH 32/78] core: tx pool skip price validation for "owned" transactions --- core/transaction_pool.go | 67 ++++++++++++++++++++++++++++++++--- core/transaction_pool_test.go | 11 ++++++ eth/api.go | 2 ++ xeth/xeth.go | 1 + 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/core/transaction_pool.go b/core/transaction_pool.go index 16f66efdcf..7dcc2aac26 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -22,6 +22,7 @@ import ( "math/big" "sort" "sync" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" @@ -65,10 +66,10 @@ type TxPool struct { minGasPrice *big.Int eventMux *event.TypeMux events event.Subscription - - mu sync.RWMutex - pending map[common.Hash]*types.Transaction // processable transactions - queue map[common.Address]map[common.Hash]*types.Transaction + localTx *txSet + mu sync.RWMutex + pending map[common.Hash]*types.Transaction // processable transactions + queue map[common.Address]map[common.Hash]*types.Transaction } func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn, gasLimitFn func() *big.Int) *TxPool { @@ -81,6 +82,7 @@ func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn, gasLimitFn func( gasLimit: gasLimitFn, minGasPrice: new(big.Int), pendingState: nil, + localTx: newTxSet(), events: eventMux.Subscribe(ChainHeadEvent{}, GasPriceChanged{}, RemovedTransactionEvent{}), } go pool.eventLoop() @@ -168,6 +170,14 @@ func (pool *TxPool) Stats() (pending int, queued int) { return } +// SetLocal marks a transaction as local, skipping gas price +// check against local miner minimum in the future +func (pool *TxPool) SetLocal(tx *types.Transaction) { + pool.mu.Lock() + defer pool.mu.Unlock() + pool.localTx.add(tx.Hash()) +} + // validateTx checks whether a transaction is valid according // to the consensus rules. func (pool *TxPool) validateTx(tx *types.Transaction) error { @@ -177,8 +187,9 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error { err error ) + local := pool.localTx.contains(tx.Hash()) // Drop transactions under our own minimal accepted gas price - if pool.minGasPrice.Cmp(tx.GasPrice()) > 0 { + if !local && pool.minGasPrice.Cmp(tx.GasPrice()) > 0 { return ErrCheap } @@ -489,3 +500,49 @@ type txQueueEntry struct { func (q txQueue) Len() int { return len(q) } func (q txQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] } func (q txQueue) Less(i, j int) bool { return q[i].Nonce() < q[j].Nonce() } + +// txSet represents a set of transaction hashes in which entries +// are automatically dropped after txSetDuration time +type txSet struct { + txMap map[common.Hash]struct{} + txOrd map[uint64]txOrdType + addPtr, delPtr uint64 +} + +const txSetDuration = time.Hour * 2 + +// txOrdType represents an entry in the time-ordered list of transaction hashes +type txOrdType struct { + hash common.Hash + time time.Time +} + +// newTxSet creates a new transaction set +func newTxSet() *txSet { + return &txSet{ + txMap: make(map[common.Hash]struct{}), + txOrd: make(map[uint64]txOrdType), + } +} + +// contains returns true if the set contains the given transaction hash +// (not thread safe, should be called from a locked environment) +func (self *txSet) contains(hash common.Hash) bool { + _, ok := self.txMap[hash] + return ok +} + +// add adds a transaction hash to the set, then removes entries older than txSetDuration +// (not thread safe, should be called from a locked environment) +func (self *txSet) add(hash common.Hash) { + self.txMap[hash] = struct{}{} + now := time.Now() + self.txOrd[self.addPtr] = txOrdType{hash: hash, time: now} + self.addPtr++ + delBefore := now.Add(-txSetDuration) + for self.delPtr < self.addPtr && self.txOrd[self.delPtr].time.Before(delBefore) { + delete(self.txMap, self.txOrd[self.delPtr].hash) + delete(self.txOrd, self.delPtr) + self.delPtr++ + } +} diff --git a/core/transaction_pool_test.go b/core/transaction_pool_test.go index 229dcacf37..a311bdd660 100644 --- a/core/transaction_pool_test.go +++ b/core/transaction_pool_test.go @@ -72,6 +72,17 @@ func TestInvalidTransactions(t *testing.T) { if err := pool.Add(tx); err != ErrNonce { t.Error("expected", ErrNonce) } + + tx = transaction(1, big.NewInt(100000), key) + pool.minGasPrice = big.NewInt(1000) + if err := pool.Add(tx); err != ErrCheap { + t.Error("expected", ErrCheap, "got", err) + } + + pool.SetLocal(tx) + if err := pool.Add(tx); err != nil { + t.Error("expected", nil, "got", err) + } } func TestTransactionQueue(t *testing.T) { diff --git a/eth/api.go b/eth/api.go index 068b350dbe..a1630e2d10 100644 --- a/eth/api.go +++ b/eth/api.go @@ -926,6 +926,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(args SendTxArgs) (common.Hash return common.Hash{}, err } + s.txPool.SetLocal(signedTx) if err := s.txPool.Add(signedTx); err != nil { return common.Hash{}, nil } @@ -948,6 +949,7 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(encodedTx string) (string, return "", err } + s.txPool.SetLocal(tx) if err := s.txPool.Add(tx); err != nil { return "", err } diff --git a/xeth/xeth.go b/xeth/xeth.go index 85bf41a824..5a5399a3e7 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -1044,6 +1044,7 @@ func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceS if err != nil { return "", err } + self.EthereumService().TxPool().SetLocal(signed) if err = self.EthereumService().TxPool().Add(signed); err != nil { return "", err } From f3aac71fad041dedd239f0a86f7c7c43614cbf4f Mon Sep 17 00:00:00 2001 From: zsfelfoldi Date: Wed, 9 Dec 2015 18:28:07 +0100 Subject: [PATCH 33/78] rpc/v2: optionally passing context argument to rpc v2 api methods --- Godeps/Godeps.json | 4 + .../src/golang.org/x/net/context/context.go | 447 ++++++++++++++ .../golang.org/x/net/context/context_test.go | 575 ++++++++++++++++++ .../x/net/context/withtimeout_test.go | 26 + rpc/v2/server.go | 21 +- rpc/v2/server_test.go | 40 +- rpc/v2/types.go | 2 +- rpc/v2/utils.go | 25 +- 8 files changed, 1122 insertions(+), 18 deletions(-) create mode 100644 Godeps/_workspace/src/golang.org/x/net/context/context.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/context/context_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b467215d1e..06f4b3b2da 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -101,6 +101,10 @@ "ImportPath": "golang.org/x/crypto/scrypt", "Rev": "4ed45ec682102c643324fae5dff8dab085b6c300" }, + { + "ImportPath": "golang.org/x/net/context", + "Rev": "e0403b4e005737430c05a57aac078479844f919c" + }, { "ImportPath": "golang.org/x/net/html", "Rev": "e0403b4e005737430c05a57aac078479844f919c" diff --git a/Godeps/_workspace/src/golang.org/x/net/context/context.go b/Godeps/_workspace/src/golang.org/x/net/context/context.go new file mode 100644 index 0000000000..e7ee376c47 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/context/context.go @@ -0,0 +1,447 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package context defines the Context type, which carries deadlines, +// cancelation signals, and other request-scoped values across API boundaries +// and between processes. +// +// Incoming requests to a server should create a Context, and outgoing calls to +// servers should accept a Context. The chain of function calls between must +// propagate the Context, optionally replacing it with a modified copy created +// using WithDeadline, WithTimeout, WithCancel, or WithValue. +// +// Programs that use Contexts should follow these rules to keep interfaces +// consistent across packages and enable static analysis tools to check context +// propagation: +// +// Do not store Contexts inside a struct type; instead, pass a Context +// explicitly to each function that needs it. The Context should be the first +// parameter, typically named ctx: +// +// func DoSomething(ctx context.Context, arg Arg) error { +// // ... use ctx ... +// } +// +// Do not pass a nil Context, even if a function permits it. Pass context.TODO +// if you are unsure about which Context to use. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The same Context may be passed to functions running in different goroutines; +// Contexts are safe for simultaneous use by multiple goroutines. +// +// See http://blog.golang.org/context for example code for a server that uses +// Contexts. +package context // import "golang.org/x/net/context" + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context interface { + // Deadline returns the time when work done on behalf of this context + // should be canceled. Deadline returns ok==false when no deadline is + // set. Successive calls to Deadline return the same results. + Deadline() (deadline time.Time, ok bool) + + // Done returns a channel that's closed when work done on behalf of this + // context should be canceled. Done may return nil if this context can + // never be canceled. Successive calls to Done return the same value. + // + // WithCancel arranges for Done to be closed when cancel is called; + // WithDeadline arranges for Done to be closed when the deadline + // expires; WithTimeout arranges for Done to be closed when the timeout + // elapses. + // + // Done is provided for use in select statements: + // + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out <-chan Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } + // + // See http://blog.golang.org/pipelines for more examples of how to use + // a Done channel for cancelation. + Done() <-chan struct{} + + // Err returns a non-nil error value after Done is closed. Err returns + // Canceled if the context was canceled or DeadlineExceeded if the + // context's deadline passed. No other values for Err are defined. + // After Done is closed, successive calls to Err return the same value. + Err() error + + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.WithValue and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + // + // Packages that define a Context key should provide type-safe accessors + // for the values stores using that key: + // + // // Package user defines a User type that's stored in Contexts. + // package user + // + // import "golang.org/x/net/context" + // + // // User is the type of value stored in the Contexts. + // type User struct {...} + // + // // key is an unexported type for keys defined in this package. + // // This prevents collisions with keys defined in other packages. + // type key int + // + // // userKey is the key for user.User values in Contexts. It is + // // unexported; clients use user.NewContext and user.FromContext + // // instead of using this key directly. + // var userKey key = 0 + // + // // NewContext returns a new Context that carries value u. + // func NewContext(ctx context.Context, u *User) context.Context { + // return context.WithValue(ctx, userKey, u) + // } + // + // // FromContext returns the User value stored in ctx, if any. + // func FromContext(ctx context.Context) (*User, bool) { + // u, ok := ctx.Value(userKey).(*User) + // return u, ok + // } + Value(key interface{}) interface{} +} + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = errors.New("context canceled") + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = errors.New("context deadline exceeded") + +// An emptyCtx is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +type emptyCtx int + +func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { + return +} + +func (*emptyCtx) Done() <-chan struct{} { + return nil +} + +func (*emptyCtx) Err() error { + return nil +} + +func (*emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (e *emptyCtx) String() string { + switch e { + case background: + return "context.Background" + case todo: + return "context.TODO" + } + return "unknown empty Context" +} + +var ( + background = new(emptyCtx) + todo = new(emptyCtx) +) + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return background +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it's is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return todo +} + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc func() + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + c := newCancelCtx(parent) + propagateCancel(parent, &c) + return &c, func() { c.cancel(true, Canceled) } +} + +// newCancelCtx returns an initialized cancelCtx. +func newCancelCtx(parent Context) cancelCtx { + return cancelCtx{ + Context: parent, + done: make(chan struct{}), + } +} + +// propagateCancel arranges for child to be canceled when parent is. +func propagateCancel(parent Context, child canceler) { + if parent.Done() == nil { + return // parent is never canceled + } + if p, ok := parentCancelCtx(parent); ok { + p.mu.Lock() + if p.err != nil { + // parent has already been canceled + child.cancel(false, p.err) + } else { + if p.children == nil { + p.children = make(map[canceler]bool) + } + p.children[child] = true + } + p.mu.Unlock() + } else { + go func() { + select { + case <-parent.Done(): + child.cancel(false, parent.Err()) + case <-child.Done(): + } + }() + } +} + +// parentCancelCtx follows a chain of parent references until it finds a +// *cancelCtx. This function understands how each of the concrete types in this +// package represents its parent. +func parentCancelCtx(parent Context) (*cancelCtx, bool) { + for { + switch c := parent.(type) { + case *cancelCtx: + return c, true + case *timerCtx: + return &c.cancelCtx, true + case *valueCtx: + parent = c.Context + default: + return nil, false + } + } +} + +// removeChild removes a context from its parent. +func removeChild(parent Context, child canceler) { + p, ok := parentCancelCtx(parent) + if !ok { + return + } + p.mu.Lock() + if p.children != nil { + delete(p.children, child) + } + p.mu.Unlock() +} + +// A canceler is a context type that can be canceled directly. The +// implementations are *cancelCtx and *timerCtx. +type canceler interface { + cancel(removeFromParent bool, err error) + Done() <-chan struct{} +} + +// A cancelCtx can be canceled. When canceled, it also cancels any children +// that implement canceler. +type cancelCtx struct { + Context + + done chan struct{} // closed by the first cancel call. + + mu sync.Mutex + children map[canceler]bool // set to nil by the first cancel call + err error // set to non-nil by the first cancel call +} + +func (c *cancelCtx) Done() <-chan struct{} { + return c.done +} + +func (c *cancelCtx) Err() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.err +} + +func (c *cancelCtx) String() string { + return fmt.Sprintf("%v.WithCancel", c.Context) +} + +// cancel closes c.done, cancels each of c's children, and, if +// removeFromParent is true, removes c from its parent's children. +func (c *cancelCtx) cancel(removeFromParent bool, err error) { + if err == nil { + panic("context: internal error: missing cancel error") + } + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return // already canceled + } + c.err = err + close(c.done) + for child := range c.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + c.children = nil + c.mu.Unlock() + + if removeFromParent { + removeChild(c.Context, c) + } +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { + // The current deadline is already sooner than the new one. + return WithCancel(parent) + } + c := &timerCtx{ + cancelCtx: newCancelCtx(parent), + deadline: deadline, + } + propagateCancel(parent, c) + d := deadline.Sub(time.Now()) + if d <= 0 { + c.cancel(true, DeadlineExceeded) // deadline has already passed + return c, func() { c.cancel(true, Canceled) } + } + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.timer = time.AfterFunc(d, func() { + c.cancel(true, DeadlineExceeded) + }) + } + return c, func() { c.cancel(true, Canceled) } +} + +// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to +// implement Done and Err. It implements cancel by stopping its timer then +// delegating to cancelCtx.cancel. +type timerCtx struct { + cancelCtx + timer *time.Timer // Under cancelCtx.mu. + + deadline time.Time +} + +func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { + return c.deadline, true +} + +func (c *timerCtx) String() string { + return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) +} + +func (c *timerCtx) cancel(removeFromParent bool, err error) { + c.cancelCtx.cancel(false, err) + if removeFromParent { + // Remove this timerCtx from its parent cancelCtx's children. + removeChild(c.cancelCtx.Context, c) + } + c.mu.Lock() + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } + c.mu.Unlock() +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return &valueCtx{parent, key, val} +} + +// A valueCtx carries a key-value pair. It implements Value for that key and +// delegates all other calls to the embedded Context. +type valueCtx struct { + Context + key, val interface{} +} + +func (c *valueCtx) String() string { + return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) +} + +func (c *valueCtx) Value(key interface{}) interface{} { + if c.key == key { + return c.val + } + return c.Context.Value(key) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/context/context_test.go b/Godeps/_workspace/src/golang.org/x/net/context/context_test.go new file mode 100644 index 0000000000..faf67722a0 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/context/context_test.go @@ -0,0 +1,575 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context + +import ( + "fmt" + "math/rand" + "runtime" + "strings" + "sync" + "testing" + "time" +) + +// otherContext is a Context that's not one of the types defined in context.go. +// This lets us test code paths that differ based on the underlying type of the +// Context. +type otherContext struct { + Context +} + +func TestBackground(t *testing.T) { + c := Background() + if c == nil { + t.Fatalf("Background returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.Background"; got != want { + t.Errorf("Background().String() = %q want %q", got, want) + } +} + +func TestTODO(t *testing.T) { + c := TODO() + if c == nil { + t.Fatalf("TODO returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.TODO"; got != want { + t.Errorf("TODO().String() = %q want %q", got, want) + } +} + +func TestWithCancel(t *testing.T) { + c1, cancel := WithCancel(Background()) + + if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want { + t.Errorf("c1.String() = %q want %q", got, want) + } + + o := otherContext{c1} + c2, _ := WithCancel(o) + contexts := []Context{c1, o, c2} + + for i, c := range contexts { + if d := c.Done(); d == nil { + t.Errorf("c[%d].Done() == %v want non-nil", i, d) + } + if e := c.Err(); e != nil { + t.Errorf("c[%d].Err() == %v want nil", i, e) + } + + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + } + + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + + for i, c := range contexts { + select { + case <-c.Done(): + default: + t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i) + } + if e := c.Err(); e != Canceled { + t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled) + } + } +} + +func TestParentFinishesChild(t *testing.T) { + // Context tree: + // parent -> cancelChild + // parent -> valueChild -> timerChild + parent, cancel := WithCancel(Background()) + cancelChild, stop := WithCancel(parent) + defer stop() + valueChild := WithValue(parent, "key", "value") + timerChild, stop := WithTimeout(valueChild, 10000*time.Hour) + defer stop() + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-cancelChild.Done(): + t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x) + case x := <-timerChild.Done(): + t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x) + case x := <-valueChild.Done(): + t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x) + default: + } + + // The parent's children should contain the two cancelable children. + pc := parent.(*cancelCtx) + cc := cancelChild.(*cancelCtx) + tc := timerChild.(*timerCtx) + pc.mu.Lock() + if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] { + t.Errorf("bad linkage: pc.children = %v, want %v and %v", + pc.children, cc, tc) + } + pc.mu.Unlock() + + if p, ok := parentCancelCtx(cc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc) + } + if p, ok := parentCancelCtx(tc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc) + } + + cancel() + + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children) + } + pc.mu.Unlock() + + // parent and children should all be finished. + check := func(ctx Context, name string) { + select { + case <-ctx.Done(): + default: + t.Errorf("<-%s.Done() blocked, but shouldn't have", name) + } + if e := ctx.Err(); e != Canceled { + t.Errorf("%s.Err() == %v want %v", name, e, Canceled) + } + } + check(parent, "parent") + check(cancelChild, "cancelChild") + check(valueChild, "valueChild") + check(timerChild, "timerChild") + + // WithCancel should return a canceled context on a canceled parent. + precanceledChild := WithValue(parent, "key", "value") + select { + case <-precanceledChild.Done(): + default: + t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have") + } + if e := precanceledChild.Err(); e != Canceled { + t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled) + } +} + +func TestChildFinishesFirst(t *testing.T) { + cancelable, stop := WithCancel(Background()) + defer stop() + for _, parent := range []Context{Background(), cancelable} { + child, cancel := WithCancel(parent) + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-child.Done(): + t.Errorf("<-child.Done() == %v want nothing (it should block)", x) + default: + } + + cc := child.(*cancelCtx) + pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background() + if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) { + t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok) + } + + if pcok { + pc.mu.Lock() + if len(pc.children) != 1 || !pc.children[cc] { + t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc) + } + pc.mu.Unlock() + } + + cancel() + + if pcok { + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children) + } + pc.mu.Unlock() + } + + // child should be finished. + select { + case <-child.Done(): + default: + t.Errorf("<-child.Done() blocked, but shouldn't have") + } + if e := child.Err(); e != Canceled { + t.Errorf("child.Err() == %v want %v", e, Canceled) + } + + // parent should not be finished. + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + default: + } + if e := parent.Err(); e != nil { + t.Errorf("parent.Err() == %v want nil", e) + } + } +} + +func testDeadline(c Context, wait time.Duration, t *testing.T) { + select { + case <-time.After(wait): + t.Fatalf("context should have timed out") + case <-c.Done(): + } + if e := c.Err(); e != DeadlineExceeded { + t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded) + } +} + +func TestDeadline(t *testing.T) { + c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o = otherContext{c} + c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond)) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 100*time.Millisecond) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o = otherContext{c} + c, _ = WithTimeout(o, 300*time.Millisecond) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestCanceledTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 200*time.Millisecond) + o := otherContext{c} + c, cancel := WithTimeout(o, 400*time.Millisecond) + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + select { + case <-c.Done(): + default: + t.Errorf("<-c.Done() blocked, but shouldn't have") + } + if e := c.Err(); e != Canceled { + t.Errorf("c.Err() == %v want %v", e, Canceled) + } +} + +type key1 int +type key2 int + +var k1 = key1(1) +var k2 = key2(1) // same int as k1, different type +var k3 = key2(3) // same type as k2, different int + +func TestValues(t *testing.T) { + check := func(c Context, nm, v1, v2, v3 string) { + if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 { + t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0) + } + if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 { + t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0) + } + if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 { + t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0) + } + } + + c0 := Background() + check(c0, "c0", "", "", "") + + c1 := WithValue(Background(), k1, "c1k1") + check(c1, "c1", "c1k1", "", "") + + if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want { + t.Errorf("c.String() = %q want %q", got, want) + } + + c2 := WithValue(c1, k2, "c2k2") + check(c2, "c2", "c1k1", "c2k2", "") + + c3 := WithValue(c2, k3, "c3k3") + check(c3, "c2", "c1k1", "c2k2", "c3k3") + + c4 := WithValue(c3, k1, nil) + check(c4, "c4", "", "c2k2", "c3k3") + + o0 := otherContext{Background()} + check(o0, "o0", "", "", "") + + o1 := otherContext{WithValue(Background(), k1, "c1k1")} + check(o1, "o1", "c1k1", "", "") + + o2 := WithValue(o1, k2, "o2k2") + check(o2, "o2", "c1k1", "o2k2", "") + + o3 := otherContext{c4} + check(o3, "o3", "", "c2k2", "c3k3") + + o4 := WithValue(o3, k3, nil) + check(o4, "o4", "", "c2k2", "") +} + +func TestAllocs(t *testing.T) { + bg := Background() + for _, test := range []struct { + desc string + f func() + limit float64 + gccgoLimit float64 + }{ + { + desc: "Background()", + f: func() { Background() }, + limit: 0, + gccgoLimit: 0, + }, + { + desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1), + f: func() { + c := WithValue(bg, k1, nil) + c.Value(k1) + }, + limit: 3, + gccgoLimit: 3, + }, + { + desc: "WithTimeout(bg, 15*time.Millisecond)", + f: func() { + c, _ := WithTimeout(bg, 15*time.Millisecond) + <-c.Done() + }, + limit: 8, + gccgoLimit: 13, + }, + { + desc: "WithCancel(bg)", + f: func() { + c, cancel := WithCancel(bg) + cancel() + <-c.Done() + }, + limit: 5, + gccgoLimit: 8, + }, + { + desc: "WithTimeout(bg, 100*time.Millisecond)", + f: func() { + c, cancel := WithTimeout(bg, 100*time.Millisecond) + cancel() + <-c.Done() + }, + limit: 8, + gccgoLimit: 25, + }, + } { + limit := test.limit + if runtime.Compiler == "gccgo" { + // gccgo does not yet do escape analysis. + // TOOD(iant): Remove this when gccgo does do escape analysis. + limit = test.gccgoLimit + } + if n := testing.AllocsPerRun(100, test.f); n > limit { + t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit)) + } + } +} + +func TestSimultaneousCancels(t *testing.T) { + root, cancel := WithCancel(Background()) + m := map[Context]CancelFunc{root: cancel} + q := []Context{root} + // Create a tree of contexts. + for len(q) != 0 && len(m) < 100 { + parent := q[0] + q = q[1:] + for i := 0; i < 4; i++ { + ctx, cancel := WithCancel(parent) + m[ctx] = cancel + q = append(q, ctx) + } + } + // Start all the cancels in a random order. + var wg sync.WaitGroup + wg.Add(len(m)) + for _, cancel := range m { + go func(cancel CancelFunc) { + cancel() + wg.Done() + }(cancel) + } + // Wait on all the contexts in a random order. + for ctx := range m { + select { + case <-ctx.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n]) + } + } + // Wait for all the cancel functions to return. + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n]) + } +} + +func TestInterlockedCancels(t *testing.T) { + parent, cancelParent := WithCancel(Background()) + child, cancelChild := WithCancel(parent) + go func() { + parent.Done() + cancelChild() + }() + cancelParent() + select { + case <-child.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n]) + } +} + +func TestLayersCancel(t *testing.T) { + testLayers(t, time.Now().UnixNano(), false) +} + +func TestLayersTimeout(t *testing.T) { + testLayers(t, time.Now().UnixNano(), true) +} + +func testLayers(t *testing.T, seed int64, testTimeout bool) { + rand.Seed(seed) + errorf := func(format string, a ...interface{}) { + t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...) + } + const ( + timeout = 200 * time.Millisecond + minLayers = 30 + ) + type value int + var ( + vals []*value + cancels []CancelFunc + numTimers int + ctx = Background() + ) + for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ { + switch rand.Intn(3) { + case 0: + v := new(value) + ctx = WithValue(ctx, v, v) + vals = append(vals, v) + case 1: + var cancel CancelFunc + ctx, cancel = WithCancel(ctx) + cancels = append(cancels, cancel) + case 2: + var cancel CancelFunc + ctx, cancel = WithTimeout(ctx, timeout) + cancels = append(cancels, cancel) + numTimers++ + } + } + checkValues := func(when string) { + for _, key := range vals { + if val := ctx.Value(key).(*value); key != val { + errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key) + } + } + } + select { + case <-ctx.Done(): + errorf("ctx should not be canceled yet") + default: + } + if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) { + t.Errorf("ctx.String() = %q want prefix %q", s, prefix) + } + t.Log(ctx) + checkValues("before cancel") + if testTimeout { + select { + case <-ctx.Done(): + case <-time.After(timeout + timeout/10): + errorf("ctx should have timed out") + } + checkValues("after timeout") + } else { + cancel := cancels[rand.Intn(len(cancels))] + cancel() + select { + case <-ctx.Done(): + default: + errorf("ctx should be canceled") + } + checkValues("after cancel") + } +} + +func TestCancelRemoves(t *testing.T) { + checkChildren := func(when string, ctx Context, want int) { + if got := len(ctx.(*cancelCtx).children); got != want { + t.Errorf("%s: context has %d children, want %d", when, got, want) + } + } + + ctx, _ := WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel := WithCancel(ctx) + checkChildren("with WithCancel child ", ctx, 1) + cancel() + checkChildren("after cancelling WithCancel child", ctx, 0) + + ctx, _ = WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel = WithTimeout(ctx, 60*time.Minute) + checkChildren("with WithTimeout child ", ctx, 1) + cancel() + checkChildren("after cancelling WithTimeout child", ctx, 0) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go b/Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go new file mode 100644 index 0000000000..a6754dc368 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go @@ -0,0 +1,26 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context_test + +import ( + "fmt" + "time" + + "golang.org/x/net/context" +) + +func ExampleWithTimeout() { + // Pass a context with a timeout to tell a blocking function that it + // should abandon its work after the timeout elapses. + ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond) + select { + case <-time.After(200 * time.Millisecond): + fmt.Println("overslept") + case <-ctx.Done(): + fmt.Println(ctx.Err()) // prints "context deadline exceeded" + } + // Output: + // context deadline exceeded +} diff --git a/rpc/v2/server.go b/rpc/v2/server.go index ff6b69015a..4c04f04d24 100644 --- a/rpc/v2/server.go +++ b/rpc/v2/server.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "golang.org/x/net/context" ) // NewServer will create a new server instance with no registered handlers. @@ -120,6 +121,9 @@ func (s *Server) ServeCodec(codec ServerCodec) { codec.Close() }() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + for { reqs, batch, err := s.readRequest(codec) if err != nil { @@ -129,9 +133,9 @@ func (s *Server) ServeCodec(codec ServerCodec) { } if batch { - go s.execBatch(codec, reqs) + go s.execBatch(ctx, codec, reqs) } else { - go s.exec(codec, reqs[0]) + go s.exec(ctx, codec, reqs[0]) } } } @@ -220,7 +224,7 @@ func (s *Server) unsubscribe(subid string) bool { } // handle executes a request and returns the response from the callback. -func (s *Server) handle(codec ServerCodec, req *serverRequest) interface{} { +func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverRequest) interface{} { if req.err != nil { return codec.CreateErrorResponse(&req.id, req.err) } @@ -255,6 +259,9 @@ func (s *Server) handle(codec ServerCodec, req *serverRequest) interface{} { } arguments := []reflect.Value{req.callb.rcvr} + if req.callb.hasCtx { + arguments = append(arguments, reflect.ValueOf(ctx)) + } if len(req.args) > 0 { arguments = append(arguments, req.args...) } @@ -277,12 +284,12 @@ func (s *Server) handle(codec ServerCodec, req *serverRequest) interface{} { } // exec executes the given request and writes the result back using the codec. -func (s *Server) exec(codec ServerCodec, req *serverRequest) { +func (s *Server) exec(ctx context.Context, codec ServerCodec, req *serverRequest) { var response interface{} if req.err != nil { response = codec.CreateErrorResponse(&req.id, req.err) } else { - response = s.handle(codec, req) + response = s.handle(ctx, codec, req) } if err := codec.Write(response); err != nil { @@ -293,13 +300,13 @@ func (s *Server) exec(codec ServerCodec, req *serverRequest) { // execBatch executes the given requests and writes the result back using the codec. It will only write the response // back when the last request is processed. -func (s *Server) execBatch(codec ServerCodec, requests []*serverRequest) { +func (s *Server) execBatch(ctx context.Context, codec ServerCodec, requests []*serverRequest) { responses := make([]interface{}, len(requests)) for i, req := range requests { if req.err != nil { responses[i] = codec.CreateErrorResponse(&req.id, req.err) } else { - responses[i] = s.handle(codec, req) + responses[i] = s.handle(ctx, codec, req) } } diff --git a/rpc/v2/server_test.go b/rpc/v2/server_test.go index f4f77672fb..f250c184f9 100644 --- a/rpc/v2/server_test.go +++ b/rpc/v2/server_test.go @@ -6,6 +6,8 @@ import ( "reflect" "testing" "time" + + "golang.org/x/net/context" ) type Service struct{} @@ -27,6 +29,10 @@ func (s *Service) Echo(str string, i int, args *Args) Result { return Result{str, i, args} } +func (s *Service) EchoWithCtx(ctx context.Context, str string, i int, args *Args) Result { + return Result{str, i, args} +} + func (s *Service) Rets() (string, error) { return "", nil } @@ -64,8 +70,8 @@ func TestServerRegisterName(t *testing.T) { t.Fatalf("Expected service calc to be registered") } - if len(svc.callbacks) != 3 { - t.Errorf("Expected 3 callbacks for service 'calc', got %d", len(svc.callbacks)) + if len(svc.callbacks) != 4 { + t.Errorf("Expected 4 callbacks for service 'calc', got %d", len(svc.callbacks)) } if len(svc.subscriptions) != 1 { @@ -217,3 +223,33 @@ func TestServerMethodExecution(t *testing.T) { t.Fatalf("expected %s, got %s\n", expected, codec.output) } } + +func TestServerMethodWithCtx(t *testing.T) { + server := NewServer() + service := new(Service) + + if err := server.RegisterName("test", service); err != nil { + t.Fatalf("%v", err) + } + + id := int64(12345) + req := jsonRequest{ + Method: "echoWithCtx", + Version: "2.0", + Id: &id, + } + args := []interface{}{"string arg", 1122, &Args{"qwerty"}} + req.Payload, _ = json.Marshal(&args) + + input, _ := json.Marshal(&req) + codec := &ServerTestCodec{input: input, closer: make(chan interface{})} + go server.ServeCodec(codec) + + <-codec.closer + + expected := `{"jsonrpc":"2.0","id":12345,"result":{"String":"string arg","Int":1122,"Args":{"S":"qwerty"}}}` + + if expected != codec.output { + t.Fatalf("expected %s, got %s\n", expected, codec.output) + } +} diff --git a/rpc/v2/types.go b/rpc/v2/types.go index d538e0a3f7..8e638726fe 100644 --- a/rpc/v2/types.go +++ b/rpc/v2/types.go @@ -22,7 +22,6 @@ import ( "math/big" "reflect" "strings" - "sync" "github.com/ethereum/go-ethereum/event" @@ -41,6 +40,7 @@ type callback struct { rcvr reflect.Value // receiver of method method reflect.Method // callback argTypes []reflect.Type // input argument types + hasCtx bool // method's first argument is a context (not included in argTypes) errPos int // err return idx, of -1 when method cannot return error isSubscribe bool // indication if the callback is a subscription } diff --git a/rpc/v2/utils.go b/rpc/v2/utils.go index a564b24733..ca37924a3c 100644 --- a/rpc/v2/utils.go +++ b/rpc/v2/utils.go @@ -24,6 +24,8 @@ import ( "reflect" "unicode" "unicode/utf8" + + "golang.org/x/net/context" ) // Is this an exported - upper case - name? @@ -107,6 +109,8 @@ func isBlockNumber(t reflect.Type) bool { return t == blockNumberType } +var contextType = reflect.TypeOf(new(context.Context)).Elem() + // suitableCallbacks iterates over the methods of the given type. It will determine if a method satisfies the criteria // for a RPC callback or a subscription callback and adds it to the collection of callbacks or subscriptions. See server // documentation for a summary of these criteria. @@ -129,12 +133,19 @@ METHODS: h.method = method h.errPos = -1 + firstArg := 1 + numIn := mtype.NumIn() + if numIn >= 2 && mtype.In(1) == contextType { + h.hasCtx = true + firstArg = 2 + } + if h.isSubscribe { - h.argTypes = make([]reflect.Type, mtype.NumIn()-1) // skip rcvr type - for i := 1; i < mtype.NumIn(); i++ { + h.argTypes = make([]reflect.Type, numIn-firstArg) // skip rcvr type + for i := firstArg; i < numIn; i++ { argType := mtype.In(i) if isExportedOrBuiltinType(argType) { - h.argTypes[i-1] = argType + h.argTypes[i-firstArg] = argType } else { continue METHODS } @@ -144,17 +155,15 @@ METHODS: continue METHODS } - numIn := mtype.NumIn() - // determine method arguments, ignore first arg since it's the receiver type // Arguments must be exported or builtin types - h.argTypes = make([]reflect.Type, numIn-1) - for i := 1; i < numIn; i++ { + h.argTypes = make([]reflect.Type, numIn-firstArg) + for i := firstArg; i < numIn; i++ { argType := mtype.In(i) if !isExportedOrBuiltinType(argType) { continue METHODS } - h.argTypes[i-1] = argType + h.argTypes[i-firstArg] = argType } // check that all returned values are exported or builtin types From e51eeb81044c5cfc9416da4edce6da25b90acdb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 15 Dec 2015 19:22:48 +0200 Subject: [PATCH 34/78] eth: fix #2076, where end of hash query was interpreted number query --- eth/handler.go | 4 +++- eth/handler_test.go | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/eth/handler.go b/eth/handler.go index d8c5b4b648..d04f79105f 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -372,6 +372,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { if err := msg.Decode(&query); err != nil { return errResp(ErrDecode, "%v: %v", msg, err) } + hashMode := query.Origin.Hash != (common.Hash{}) + // Gather headers until the fetch or network limits is reached var ( bytes common.StorageSize @@ -381,7 +383,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && len(headers) < downloader.MaxHeaderFetch { // Retrieve the next header satisfying the query var origin *types.Header - if query.Origin.Hash != (common.Hash{}) { + if hashMode { origin = pm.blockchain.GetHeader(query.Origin.Hash) } else { origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number) diff --git a/eth/handler_test.go b/eth/handler_test.go index ab2ce54b13..148d56cc60 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -301,6 +301,15 @@ func testGetBlockHeaders(t *testing.T, protocol int) { pm.blockchain.GetBlockByNumber(1).Hash(), }, }, + // Check a corner case where requesting more can iterate past the endpoints + { + &getBlockHeadersData{Origin: hashOrNumber{Number: 2}, Amount: 5, Reverse: true}, + []common.Hash{ + pm.blockchain.GetBlockByNumber(2).Hash(), + pm.blockchain.GetBlockByNumber(1).Hash(), + pm.blockchain.GetBlockByNumber(0).Hash(), + }, + }, // Check that non existing headers aren't returned { &getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, @@ -322,6 +331,17 @@ func testGetBlockHeaders(t *testing.T, protocol int) { if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { t.Errorf("test %d: headers mismatch: %v", i, err) } + // If the test used number origins, repeat with hashes as the too + if tt.query.Origin.Hash == (common.Hash{}) { + if origin := pm.blockchain.GetBlockByNumber(tt.query.Origin.Number); origin != nil { + tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0 + + p2p.Send(peer.app, 0x03, tt.query) + if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } + } + } } } From ef422ee1e1eef831c681aaec31ce7da23b12ae6d Mon Sep 17 00:00:00 2001 From: zsfelfoldi Date: Mon, 30 Nov 2015 13:34:19 +0100 Subject: [PATCH 35/78] light: implemented odr-capable trie and state structures --- light/odr.go | 98 +++++++++++++++ light/state.go | 275 ++++++++++++++++++++++++++++++++++++++++++ light/state_object.go | 267 ++++++++++++++++++++++++++++++++++++++++ light/state_test.go | 269 +++++++++++++++++++++++++++++++++++++++++ light/trie.go | 123 +++++++++++++++++++ trie/encoding.go | 21 ++++ trie/encoding_test.go | 6 + trie/errors.go | 18 ++- trie/proof.go | 24 ++-- trie/trie.go | 5 +- 10 files changed, 1091 insertions(+), 15 deletions(-) create mode 100644 light/odr.go create mode 100644 light/state.go create mode 100644 light/state_object.go create mode 100644 light/state_test.go create mode 100644 light/trie.go diff --git a/light/odr.go b/light/odr.go new file mode 100644 index 0000000000..5ed4f2325e --- /dev/null +++ b/light/odr.go @@ -0,0 +1,98 @@ +// Copyright 2015 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 . + +// Package light implements on-demand retrieval capable state and chain objects +// for the Ethereum Light Client. +package light + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/net/context" +) + +// OdrBackend is an interface to a backend service that handles odr retrievals +type OdrBackend interface { + Database() ethdb.Database + Retrieve(ctx context.Context, req OdrRequest) error +} + +// OdrRequest is an interface for retrieval requests +type OdrRequest interface { + StoreResult(db ethdb.Database) +} + +// TrieRequest is the ODR request type for state/storage trie entries +type TrieRequest struct { + OdrRequest + root common.Hash + key []byte + proof []rlp.RawValue +} + +// StoreResult stores the retrieved data in local database +func (req *TrieRequest) StoreResult(db ethdb.Database) { + storeProof(db, req.proof) +} + +// storeProof stores the new trie nodes obtained from a merkle proof in the database +func storeProof(db ethdb.Database, proof []rlp.RawValue) { + for _, buf := range proof { + hash := crypto.Sha3(buf) + val, _ := db.Get(hash) + if val == nil { + db.Put(hash, buf) + } + } +} + +// NodeDataRequest is the ODR request type for node data (used for retrieving contract code) +type NodeDataRequest struct { + OdrRequest + hash common.Hash + data []byte +} + +// GetData returns the retrieved node data after a successful request +func (req *NodeDataRequest) GetData() []byte { + return req.data +} + +// StoreResult stores the retrieved data in local database +func (req *NodeDataRequest) StoreResult(db ethdb.Database) { + db.Put(req.hash[:], req.GetData()) +} + +var sha3_nil = crypto.Sha3Hash(nil) + +// retrieveNodeData tries to retrieve node data with the given hash from the network +func retrieveNodeData(ctx context.Context, odr OdrBackend, hash common.Hash) ([]byte, error) { + if hash == sha3_nil { + return nil, nil + } + res, _ := odr.Database().Get(hash[:]) + if res != nil { + return res, nil + } + r := &NodeDataRequest{hash: hash} + if err := odr.Retrieve(ctx, r); err != nil { + return nil, err + } else { + return r.GetData(), nil + } +} diff --git a/light/state.go b/light/state.go new file mode 100644 index 0000000000..e18f9cdc5d --- /dev/null +++ b/light/state.go @@ -0,0 +1,275 @@ +// Copyright 2015 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 . + +package light + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "golang.org/x/net/context" +) + +// StartingNonce determines the default nonce when new accounts are being created. +var StartingNonce uint64 + +// LightState is a memory representation of a state. +// This version is ODR capable, caching only the already accessed part of the +// state, retrieving unknown parts on-demand from the ODR backend. Changes are +// never stored in the local database, only in the memory objects. +type LightState struct { + odr OdrBackend + trie *LightTrie + + stateObjects map[string]*StateObject +} + +// NewLightState creates a new LightState with the specified root. +// Note that the creation of a light state is always successful, even if the +// root is non-existent. In that case, ODR retrieval will always be unsuccessful +// and every operation will return with an error or wait for the context to be +// cancelled. +func NewLightState(root common.Hash, odr OdrBackend) *LightState { + tr := NewLightTrie(root, odr, true) + return &LightState{ + odr: odr, + trie: tr, + stateObjects: make(map[string]*StateObject), + } +} + +// HasAccount returns true if an account exists at the given address +func (self *LightState) HasAccount(ctx context.Context, addr common.Address) (bool, error) { + so, err := self.GetStateObject(ctx, addr) + return so != nil, err +} + +// GetBalance retrieves the balance from the given address or 0 if the account does +// not exist +func (self *LightState) GetBalance(ctx context.Context, addr common.Address) (*big.Int, error) { + stateObject, err := self.GetStateObject(ctx, addr) + if err != nil { + return common.Big0, err + } + if stateObject != nil { + return stateObject.balance, nil + } + + return common.Big0, nil +} + +// GetNonce returns the nonce at the given address or 0 if the account does +// not exist +func (self *LightState) GetNonce(ctx context.Context, addr common.Address) (uint64, error) { + stateObject, err := self.GetStateObject(ctx, addr) + if err != nil { + return 0, err + } + if stateObject != nil { + return stateObject.nonce, nil + } + return 0, nil +} + +// GetCode returns the contract code at the given address or nil if the account +// does not exist +func (self *LightState) GetCode(ctx context.Context, addr common.Address) ([]byte, error) { + stateObject, err := self.GetStateObject(ctx, addr) + if err != nil { + return nil, err + } + if stateObject != nil { + return stateObject.code, nil + } + return nil, nil +} + +// GetState returns the contract storage value at storage address b from the +// contract address a or common.Hash{} if the account does not exist +func (self *LightState) GetState(ctx context.Context, a common.Address, b common.Hash) (common.Hash, error) { + stateObject, err := self.GetStateObject(ctx, a) + if err == nil && stateObject != nil { + return stateObject.GetState(ctx, b) + } + return common.Hash{}, err +} + +// IsDeleted returns true if the given account has been marked for deletion +// or false if the account does not exist +func (self *LightState) IsDeleted(ctx context.Context, addr common.Address) (bool, error) { + stateObject, err := self.GetStateObject(ctx, addr) + if err == nil && stateObject != nil { + return stateObject.remove, nil + } + return false, err +} + +/* + * SETTERS + */ + +// AddBalance adds the given amount to the balance of the specified account +func (self *LightState) AddBalance(ctx context.Context, addr common.Address, amount *big.Int) error { + stateObject, err := self.GetOrNewStateObject(ctx, addr) + if err == nil && stateObject != nil { + stateObject.AddBalance(amount) + } + return err +} + +// SetNonce sets the nonce of the specified account +func (self *LightState) SetNonce(ctx context.Context, addr common.Address, nonce uint64) error { + stateObject, err := self.GetOrNewStateObject(ctx, addr) + if err == nil && stateObject != nil { + stateObject.SetNonce(nonce) + } + return err +} + +// SetCode sets the contract code at the specified account +func (self *LightState) SetCode(ctx context.Context, addr common.Address, code []byte) error { + stateObject, err := self.GetOrNewStateObject(ctx, addr) + if err == nil && stateObject != nil { + stateObject.SetCode(code) + } + return err +} + +// SetState sets the storage value at storage address key of the account addr +func (self *LightState) SetState(ctx context.Context, addr common.Address, key common.Hash, value common.Hash) error { + stateObject, err := self.GetOrNewStateObject(ctx, addr) + if err == nil && stateObject != nil { + stateObject.SetState(key, value) + } + return err +} + +// Delete marks an account to be removed and clears its balance +func (self *LightState) Delete(ctx context.Context, addr common.Address) (bool, error) { + stateObject, err := self.GetOrNewStateObject(ctx, addr) + if err == nil && stateObject != nil { + stateObject.MarkForDeletion() + stateObject.balance = new(big.Int) + + return true, nil + } + + return false, err +} + +// +// Get, set, new state object methods +// + +// GetStateObject returns the state object of the given account or nil if the +// account does not exist +func (self *LightState) GetStateObject(ctx context.Context, addr common.Address) (stateObject *StateObject, err error) { + stateObject = self.stateObjects[addr.Str()] + if stateObject != nil { + if stateObject.deleted { + stateObject = nil + } + return stateObject, nil + } + data, err := self.trie.Get(ctx, addr[:]) + if err != nil { + return nil, err + } + if len(data) == 0 { + return nil, nil + } + + stateObject, err = DecodeObject(ctx, addr, self.odr, []byte(data)) + if err != nil { + return nil, err + } + + self.SetStateObject(stateObject) + + return stateObject, nil +} + +// SetStateObject sets the state object of the given account +func (self *LightState) SetStateObject(object *StateObject) { + self.stateObjects[object.Address().Str()] = object +} + +// GetOrNewStateObject returns the state object of the given account or creates a +// new one if the account does not exist +func (self *LightState) GetOrNewStateObject(ctx context.Context, addr common.Address) (*StateObject, error) { + stateObject, err := self.GetStateObject(ctx, addr) + if err == nil && (stateObject == nil || stateObject.deleted) { + stateObject, err = self.CreateStateObject(ctx, addr) + } + return stateObject, err +} + +// newStateObject creates a state object whether it exists in the state or not +func (self *LightState) newStateObject(addr common.Address) *StateObject { + if glog.V(logger.Core) { + glog.Infof("(+) %x\n", addr) + } + + stateObject := NewStateObject(addr, self.odr) + stateObject.SetNonce(StartingNonce) + self.stateObjects[addr.Str()] = stateObject + + return stateObject +} + +// CreateStateObject creates creates a new state object and takes ownership. +// This is different from "NewStateObject" +func (self *LightState) CreateStateObject(ctx context.Context, addr common.Address) (*StateObject, error) { + // Get previous (if any) + so, err := self.GetStateObject(ctx, addr) + if err != nil { + return nil, err + } + // Create a new one + newSo := self.newStateObject(addr) + + // If it existed set the balance to the new account + if so != nil { + newSo.balance = so.balance + } + + return newSo, nil +} + +// +// Setting, copying of the state methods +// + +// Copy creates a copy of the state +func (self *LightState) Copy() *LightState { + // ignore error - we assume state-to-be-copied always exists + state := NewLightState(common.Hash{}, self.odr) + state.trie = self.trie + for k, stateObject := range self.stateObjects { + state.stateObjects[k] = stateObject.Copy() + } + + return state +} + +// Set copies the contents of the given state onto this state, overwriting +// its contents +func (self *LightState) Set(state *LightState) { + self.trie = state.trie + self.stateObjects = state.stateObjects +} diff --git a/light/state_object.go b/light/state_object.go new file mode 100644 index 0000000000..7660c38831 --- /dev/null +++ b/light/state_object.go @@ -0,0 +1,267 @@ +// Copyright 2014 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 . + +package light + +import ( + "bytes" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/net/context" +) + +var emptyCodeHash = crypto.Sha3(nil) + +// Code represents a contract code in binary form +type Code []byte + +// String returns a string representation of the code +func (self Code) String() string { + return string(self) //strings.Join(Disassemble(self), " ") +} + +// Storage is a memory map cache of a contract storage +type Storage map[string]common.Hash + +// String returns a string representation of the storage cache +func (self Storage) String() (str string) { + for key, value := range self { + str += fmt.Sprintf("%X : %X\n", key, value) + } + + return +} + +// Copy copies the contents of a storage cache +func (self Storage) Copy() Storage { + cpy := make(Storage) + for key, value := range self { + cpy[key] = value + } + + return cpy +} + +// StateObject is a memory representation of an account or contract and its storage. +// This version is ODR capable, caching only the already accessed part of the +// storage, retrieving unknown parts on-demand from the ODR backend. Changes are +// never stored in the local database, only in the memory objects. +type StateObject struct { + odr OdrBackend + trie *LightTrie + + // Address belonging to this account + address common.Address + // The balance of the account + balance *big.Int + // The nonce of the account + nonce uint64 + // The code hash if code is present (i.e. a contract) + codeHash []byte + // The code for this account + code Code + // Temporarily initialisation code + initCode Code + // Cached storage (flushed when updated) + storage Storage + + // Mark for deletion + // When an object is marked for deletion it will be delete from the trie + // during the "update" phase of the state transition + remove bool + deleted bool + dirty bool +} + +// NewStateObject creates a new StateObject of the specified account address +func NewStateObject(address common.Address, odr OdrBackend) *StateObject { + object := &StateObject{ + odr: odr, + address: address, + balance: new(big.Int), + dirty: true, + codeHash: emptyCodeHash, + storage: make(Storage), + } + object.trie = NewLightTrie(common.Hash{}, odr, true) + return object +} + +// MarkForDeletion marks an account to be removed +func (self *StateObject) MarkForDeletion() { + self.remove = true + self.dirty = true + + if glog.V(logger.Core) { + glog.Infof("%x: #%d %v X\n", self.Address(), self.nonce, self.balance) + } +} + +// getAddr gets the storage value at the given address from the trie +func (c *StateObject) getAddr(ctx context.Context, addr common.Hash) (common.Hash, error) { + var ret []byte + val, err := c.trie.Get(ctx, addr[:]) + if err != nil { + return common.Hash{}, err + } + rlp.DecodeBytes(val, &ret) + return common.BytesToHash(ret), nil +} + +// Storage returns the storage cache object of the account +func (self *StateObject) Storage() Storage { + return self.storage +} + +// GetState returns the storage value at the given address from either the cache +// or the trie +func (self *StateObject) GetState(ctx context.Context, key common.Hash) (common.Hash, error) { + strkey := key.Str() + value, exists := self.storage[strkey] + if !exists { + var err error + value, err = self.getAddr(ctx, key) + if err != nil { + return common.Hash{}, err + } + if (value != common.Hash{}) { + self.storage[strkey] = value + } + } + + return value, nil +} + +// SetState sets the storage value at the given address +func (self *StateObject) SetState(k, value common.Hash) { + self.storage[k.Str()] = value + self.dirty = true +} + +// AddBalance adds the given amount to the account balance +func (c *StateObject) AddBalance(amount *big.Int) { + c.SetBalance(new(big.Int).Add(c.balance, amount)) + + if glog.V(logger.Core) { + glog.Infof("%x: #%d %v (+ %v)\n", c.Address(), c.nonce, c.balance, amount) + } +} + +// SubBalance subtracts the given amount from the account balance +func (c *StateObject) SubBalance(amount *big.Int) { + c.SetBalance(new(big.Int).Sub(c.balance, amount)) + + if glog.V(logger.Core) { + glog.Infof("%x: #%d %v (- %v)\n", c.Address(), c.nonce, c.balance, amount) + } +} + +// SetBalance sets the account balance to the given amount +func (c *StateObject) SetBalance(amount *big.Int) { + c.balance = amount + c.dirty = true +} + +// Copy creates a copy of the state object +func (self *StateObject) Copy() *StateObject { + stateObject := NewStateObject(self.Address(), self.odr) + stateObject.balance.Set(self.balance) + stateObject.codeHash = common.CopyBytes(self.codeHash) + stateObject.nonce = self.nonce + stateObject.trie = self.trie + stateObject.code = common.CopyBytes(self.code) + stateObject.initCode = common.CopyBytes(self.initCode) + stateObject.storage = self.storage.Copy() + stateObject.remove = self.remove + stateObject.dirty = self.dirty + stateObject.deleted = self.deleted + + return stateObject +} + +// +// Attribute accessors +// + +// Balance returns the account balance +func (self *StateObject) Balance() *big.Int { + return self.balance +} + +// Address returns the address of the contract/account +func (c *StateObject) Address() common.Address { + return c.address +} + +// Code returns the contract code +func (self *StateObject) Code() []byte { + return self.code +} + +// SetCode sets the contract code +func (self *StateObject) SetCode(code []byte) { + self.code = code + self.codeHash = crypto.Sha3(code) + self.dirty = true +} + +// SetNonce sets the account nonce +func (self *StateObject) SetNonce(nonce uint64) { + self.nonce = nonce + self.dirty = true +} + +// Nonce returns the account nonce +func (self *StateObject) Nonce() uint64 { + return self.nonce +} + +// Encoding + +type extStateObject struct { + Nonce uint64 + Balance *big.Int + Root common.Hash + CodeHash []byte +} + +// DecodeObject decodes an RLP-encoded state object. +func DecodeObject(ctx context.Context, address common.Address, odr OdrBackend, data []byte) (*StateObject, error) { + var ( + obj = &StateObject{address: address, odr: odr, storage: make(Storage)} + ext extStateObject + err error + ) + if err = rlp.DecodeBytes(data, &ext); err != nil { + return nil, err + } + obj.trie = NewLightTrie(ext.Root, odr, true) + if !bytes.Equal(ext.CodeHash, emptyCodeHash) { + if obj.code, err = retrieveNodeData(ctx, obj.odr, common.BytesToHash(ext.CodeHash)); err != nil { + return nil, fmt.Errorf("can't find code for hash %x: %v", ext.CodeHash, err) + } + } + obj.nonce = ext.Nonce + obj.balance = ext.Balance + obj.codeHash = ext.CodeHash + return obj, nil +} \ No newline at end of file diff --git a/light/state_test.go b/light/state_test.go new file mode 100644 index 0000000000..2c2e6daea1 --- /dev/null +++ b/light/state_test.go @@ -0,0 +1,269 @@ +// Copyright 2015 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 . + +package light + +import ( + "bytes" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie" + "golang.org/x/net/context" +) + +type testOdr struct { + OdrBackend + sdb, ldb ethdb.Database +} + +func (odr *testOdr) Database() ethdb.Database { + return odr.ldb +} + +func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { + switch req := req.(type) { + case *TrieRequest: + t, _ := trie.New(req.root, odr.sdb) + req.proof = t.Prove(req.key) + trie.ClearGlobalCache() + case *NodeDataRequest: + req.data, _ = odr.sdb.Get(req.hash[:]) + } + req.StoreResult(odr.ldb) + return nil +} + +func makeTestState() (common.Hash, ethdb.Database) { + sdb, _ := ethdb.NewMemDatabase() + st, _ := state.New(common.Hash{}, sdb) + for i := byte(0); i < 100; i++ { + so := st.GetOrNewStateObject(common.Address{i}) + for j := byte(0); j < 100; j++ { + val := common.Hash{i, j} + so.SetState(common.Hash{j}, val) + so.SetNonce(100) + } + so.AddBalance(big.NewInt(int64(i))) + so.SetCode([]byte{i, i, i}) + so.Update() + st.UpdateStateObject(so) + } + root, _ := st.Commit() + return root, sdb +} + +func TestLightStateOdr(t *testing.T) { + root, sdb := makeTestState() + ldb, _ := ethdb.NewMemDatabase() + odr := &testOdr{sdb: sdb, ldb: ldb} + ls := NewLightState(root, odr) + ctx := context.Background() + trie.ClearGlobalCache() + + for i := byte(0); i < 100; i++ { + addr := common.Address{i} + err := ls.AddBalance(ctx, addr, big.NewInt(1000)) + if err != nil { + t.Fatalf("Error adding balance to acc[%d]: %v", i, err) + } + err = ls.SetState(ctx, addr, common.Hash{100}, common.Hash{i, 100}) + if err != nil { + t.Fatalf("Error setting storage of acc[%d]: %v", i, err) + } + } + + addr := common.Address{100} + _, err := ls.CreateStateObject(ctx, addr) + if err != nil { + t.Fatalf("Error creating state object: %v", err) + } + err = ls.SetCode(ctx, addr, []byte{100, 100, 100}) + if err != nil { + t.Fatalf("Error setting code: %v", err) + } + err = ls.AddBalance(ctx, addr, big.NewInt(1100)) + if err != nil { + t.Fatalf("Error adding balance to acc[100]: %v", err) + } + for j := byte(0); j < 101; j++ { + err = ls.SetState(ctx, addr, common.Hash{j}, common.Hash{100, j}) + if err != nil { + t.Fatalf("Error setting storage of acc[100]: %v", err) + } + } + err = ls.SetNonce(ctx, addr, 100) + if err != nil { + t.Fatalf("Error setting nonce for acc[100]: %v", err) + } + + for i := byte(0); i < 101; i++ { + addr := common.Address{i} + + bal, err := ls.GetBalance(ctx, addr) + if err != nil { + t.Fatalf("Error getting balance of acc[%d]: %v", i, err) + } + if bal.Int64() != int64(i)+1000 { + t.Fatalf("Incorrect balance at acc[%d]: expected %v, got %v", i, int64(i)+1000, bal.Int64()) + } + + nonce, err := ls.GetNonce(ctx, addr) + if err != nil { + t.Fatalf("Error getting nonce of acc[%d]: %v", i, err) + } + if nonce != 100 { + t.Fatalf("Incorrect nonce at acc[%d]: expected %v, got %v", i, 100, nonce) + } + + code, err := ls.GetCode(ctx, addr) + exp := []byte{i, i, i} + if err != nil { + t.Fatalf("Error getting code of acc[%d]: %v", i, err) + } + if !bytes.Equal(code, exp) { + t.Fatalf("Incorrect code at acc[%d]: expected %v, got %v", i, exp, code) + } + + for j := byte(0); j < 101; j++ { + exp := common.Hash{i, j} + val, err := ls.GetState(ctx, addr, common.Hash{j}) + if err != nil { + t.Fatalf("Error retrieving acc[%d].storage[%d]: %v", i, j, err) + } + if val != exp { + t.Fatalf("Retrieved wrong value from acc[%d].storage[%d]: expected %04x, got %04x", i, j, exp, val) + } + } + } +} + +func TestLightStateSetCopy(t *testing.T) { + root, sdb := makeTestState() + ldb, _ := ethdb.NewMemDatabase() + odr := &testOdr{sdb: sdb, ldb: ldb} + ls := NewLightState(root, odr) + ctx := context.Background() + trie.ClearGlobalCache() + + for i := byte(0); i < 100; i++ { + addr := common.Address{i} + err := ls.AddBalance(ctx, addr, big.NewInt(1000)) + if err != nil { + t.Fatalf("Error adding balance to acc[%d]: %v", i, err) + } + err = ls.SetState(ctx, addr, common.Hash{100}, common.Hash{i, 100}) + if err != nil { + t.Fatalf("Error setting storage of acc[%d]: %v", i, err) + } + } + + ls2 := ls.Copy() + + for i := byte(0); i < 100; i++ { + addr := common.Address{i} + err := ls2.AddBalance(ctx, addr, big.NewInt(1000)) + if err != nil { + t.Fatalf("Error adding balance to acc[%d]: %v", i, err) + } + err = ls2.SetState(ctx, addr, common.Hash{100}, common.Hash{i, 200}) + if err != nil { + t.Fatalf("Error setting storage of acc[%d]: %v", i, err) + } + } + + lsx := ls.Copy() + ls.Set(ls2) + ls2.Set(lsx) + + for i := byte(0); i < 100; i++ { + addr := common.Address{i} + // check balance in ls + bal, err := ls.GetBalance(ctx, addr) + if err != nil { + t.Fatalf("Error getting balance to acc[%d]: %v", i, err) + } + if bal.Int64() != int64(i)+2000 { + t.Fatalf("Incorrect balance at ls.acc[%d]: expected %v, got %v", i, int64(i)+1000, bal.Int64()) + } + // check balance in ls2 + bal, err = ls2.GetBalance(ctx, addr) + if err != nil { + t.Fatalf("Error getting balance to acc[%d]: %v", i, err) + } + if bal.Int64() != int64(i)+1000 { + t.Fatalf("Incorrect balance at ls.acc[%d]: expected %v, got %v", i, int64(i)+1000, bal.Int64()) + } + // check storage in ls + exp := common.Hash{i, 200} + val, err := ls.GetState(ctx, addr, common.Hash{100}) + if err != nil { + t.Fatalf("Error retrieving acc[%d].storage[100]: %v", i, err) + } + if val != exp { + t.Fatalf("Retrieved wrong value from acc[%d].storage[100]: expected %04x, got %04x", i, exp, val) + } + // check storage in ls2 + exp = common.Hash{i, 100} + val, err = ls2.GetState(ctx, addr, common.Hash{100}) + if err != nil { + t.Fatalf("Error retrieving acc[%d].storage[100]: %v", i, err) + } + if val != exp { + t.Fatalf("Retrieved wrong value from acc[%d].storage[100]: expected %04x, got %04x", i, exp, val) + } + } +} + +func TestLightStateDelete(t *testing.T) { + root, sdb := makeTestState() + ldb, _ := ethdb.NewMemDatabase() + odr := &testOdr{sdb: sdb, ldb: ldb} + ls := NewLightState(root, odr) + ctx := context.Background() + trie.ClearGlobalCache() + + addr := common.Address{42} + + b, err := ls.HasAccount(ctx, addr) + if err != nil { + t.Fatalf("HasAccount error: %v", err) + } + if !b { + t.Fatalf("HasAccount returned false, expected true") + } + + b, err = ls.IsDeleted(ctx, addr) + if err != nil { + t.Fatalf("IsDeleted error: %v", err) + } + if b { + t.Fatalf("IsDeleted returned true, expected false") + } + + ls.Delete(ctx, addr) + + b, err = ls.IsDeleted(ctx, addr) + if err != nil { + t.Fatalf("IsDeleted error: %v", err) + } + if !b { + t.Fatalf("IsDeleted returned false, expected true") + } +} diff --git a/light/trie.go b/light/trie.go new file mode 100644 index 0000000000..e9c96ea48e --- /dev/null +++ b/light/trie.go @@ -0,0 +1,123 @@ +// Copyright 2015 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 . + +package light + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie" + "golang.org/x/net/context" +) + +// LightTrie is an ODR-capable wrapper around trie.SecureTrie +type LightTrie struct { + trie *trie.SecureTrie + originalRoot common.Hash + odr OdrBackend + db ethdb.Database +} + +// NewLightTrie creates a new LightTrie instance. It doesn't instantly try to +// access the db or network and retrieve the root node, it only initializes its +// encapsulated SecureTrie at the first actual operation. +func NewLightTrie(root common.Hash, odr OdrBackend, useFakeMap bool) *LightTrie { + return &LightTrie{ + // SecureTrie is initialized before first request + originalRoot: root, + odr: odr, + db: odr.Database(), + } +} + +// retrieveKey retrieves a single key, returns true and stores nodes in local +// database if successful +func (t *LightTrie) retrieveKey(ctx context.Context, key []byte) bool { + r := &TrieRequest{root: t.originalRoot, key: key} + return t.odr.Retrieve(ctx, r) == nil +} + +// do tries and retries to execute a function until it returns with no error or +// an error type other than MissingNodeError +func (t *LightTrie) do(ctx context.Context, fallbackKey []byte, fn func() error) error { + err := fn() + for err != nil { + mn, ok := err.(*trie.MissingNodeError) + if !ok { + return err + } + + var key []byte + if mn.PrefixLen+mn.SuffixLen > 0 { + key = mn.Key + } else { + key = fallbackKey + } + if !t.retrieveKey(ctx, key) { + break + } + err = fn() + } + return err +} + +// Get returns the value for key stored in the trie. +// The value bytes must not be modified by the caller. +func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) { + err = t.do(ctx, key, func() (err error) { + if t.trie == nil { + t.trie, err = trie.NewSecure(t.originalRoot, t.db) + } + if err == nil { + res, err = t.trie.TryGet(key) + } + return + }) + return +} + +// Update associates key with value in the trie. Subsequent calls to +// Get will return value. If value has length zero, any existing value +// is deleted from the trie and calls to Get will return nil. +// +// The value bytes must not be modified by the caller while they are +// stored in the trie. +func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) { + err = t.do(ctx, key, func() (err error) { + if t.trie == nil { + t.trie, err = trie.NewSecure(t.originalRoot, t.db) + } + if err == nil { + err = t.trie.TryUpdate(key, value) + } + return + }) + return +} + +// Delete removes any existing value for key from the trie. +func (t *LightTrie) Delete(ctx context.Context, key []byte) (err error) { + err = t.do(ctx, key, func() (err error) { + if t.trie == nil { + t.trie, err = trie.NewSecure(t.originalRoot, t.db) + } + if err == nil { + err = t.trie.TryDelete(key) + } + return + }) + return +} diff --git a/trie/encoding.go b/trie/encoding.go index 3c172b8438..761bad1889 100644 --- a/trie/encoding.go +++ b/trie/encoding.go @@ -69,6 +69,27 @@ func compactHexDecode(str []byte) []byte { return nibbles } +// compactHexEncode encodes a series of nibbles into a byte array +func compactHexEncode(nibbles []byte) []byte { + nl := len(nibbles) + if nl == 0 { + return nil + } + if nibbles[nl-1] == 16 { + nl-- + } + l := (nl + 1) / 2 + var str = make([]byte, l) + for i, _ := range str { + b := nibbles[i*2] * 16 + if nl > i*2 { + b += nibbles[i*2+1] + } + str[i] = b + } + return str +} + func decodeCompact(key []byte) []byte { l := len(key) / 2 var res = make([]byte, l) diff --git a/trie/encoding_test.go b/trie/encoding_test.go index 061d48d58b..2f125ef2f8 100644 --- a/trie/encoding_test.go +++ b/trie/encoding_test.go @@ -57,6 +57,12 @@ func (s *TrieEncodingSuite) TestCompactHexDecode(c *checker.C) { c.Assert(res, checker.DeepEquals, exp) } +func (s *TrieEncodingSuite) TestCompactHexEncode(c *checker.C) { + exp := []byte("verb") + res := compactHexEncode([]byte{7, 6, 6, 5, 7, 2, 6, 2, 16}) + c.Assert(res, checker.DeepEquals, exp) +} + func (s *TrieEncodingSuite) TestCompactDecode(c *checker.C) { // odd compact decode exp := []byte{1, 2, 3, 4, 5} diff --git a/trie/errors.go b/trie/errors.go index a0f58f28fc..fd0e6f0a97 100644 --- a/trie/errors.go +++ b/trie/errors.go @@ -27,13 +27,23 @@ import ( // information necessary for retrieving the missing node through an ODR service. // // NodeHash is the hash of the missing node +// // RootHash is the original root of the trie that contains the node -// KeyPrefix is the prefix that leads from the root to the missing node (hex encoded) -// KeySuffix (optional) contains the rest of the key we were looking for, gives a -// hint on which further nodes should also be retrieved (hex encoded) +// +// Key is a binary-encoded key that contains the prefix that leads to the first +// missing node and optionally a suffix that hints on which further nodes should +// also be retrieved +// +// PrefixLen is the nibble length of the key prefix that leads from the root to +// the missing node +// +// SuffixLen is the nibble length of the remaining part of the key that hints on +// which further nodes should also be retrieved (can be zero when there are no +// such hints in the error message) type MissingNodeError struct { RootHash, NodeHash common.Hash - KeyPrefix, KeySuffix []byte + Key []byte + PrefixLen, SuffixLen int } func (err *MissingNodeError) Error() string { diff --git a/trie/proof.go b/trie/proof.go index 2e88bb50bc..0f92649424 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -17,29 +17,30 @@ import ( // also included in the last node and can be retrieved by verifying // the proof. // -// The returned proof is nil if the trie does not contain a value for key. -// For existing keys, the proof will have at least one element. +// If the trie does not contain a value for key, the returned proof +// contains all nodes of the longest existing prefix of the key +// (at least the root node), ending with the node that proves the +// absence of the key. func (t *Trie) Prove(key []byte) []rlp.RawValue { // Collect all nodes on the path to key. key = compactHexDecode(key) nodes := []node{} tn := t.root - for len(key) > 0 { + for len(key) > 0 && tn != nil { switch n := tn.(type) { case shortNode: if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { // The trie doesn't contain the key. - return nil + tn = nil + } else { + tn = n.Val + key = key[len(n.Key):] } - tn = n.Val - key = key[len(n.Key):] nodes = append(nodes, n) case fullNode: tn = n[key[0]] key = key[1:] nodes = append(nodes, n) - case nil: - return nil case hashNode: var err error tn, err = t.resolveHash(n, nil, nil) @@ -93,7 +94,12 @@ func VerifyProof(rootHash common.Hash, key []byte, proof []rlp.RawValue) (value keyrest, cld := get(n, key) switch cld := cld.(type) { case nil: - return nil, fmt.Errorf("key mismatch at proof node %d", i) + if i != len(proof)-1 { + return nil, fmt.Errorf("key mismatch at proof node %d", i) + } else { + // The trie doesn't contain the key. + return nil, nil + } case hashNode: key = keyrest wantHash = cld diff --git a/trie/trie.go b/trie/trie.go index 717296e27d..9dfde45294 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -394,8 +394,9 @@ func (t *Trie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) { return nil, &MissingNodeError{ RootHash: t.originalRoot, NodeHash: common.BytesToHash(n), - KeyPrefix: prefix, - KeySuffix: suffix, + Key: compactHexEncode(append(prefix, suffix...)), + PrefixLen: len(prefix), + SuffixLen: len(suffix), } } dec := mustDecodeNode(n, enc) From d1f507b7f16e359dc2773195edb72a22357e5424 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 22 Oct 2015 23:46:01 +0200 Subject: [PATCH 36/78] p2p/discover: support incomplete node URLs, add Resolve --- p2p/discover/database_test.go | 16 +++---- p2p/discover/node.go | 72 ++++++++++++++++++++-------- p2p/discover/node_test.go | 88 +++++++++++++++++++++++++++-------- p2p/discover/table.go | 26 ++++++++++- p2p/discover/table_test.go | 6 +-- p2p/discover/udp.go | 2 +- p2p/discover/udp_test.go | 2 +- 7 files changed, 158 insertions(+), 54 deletions(-) diff --git a/p2p/discover/database_test.go b/p2p/discover/database_test.go index 80c1a6ff29..5a729f02b9 100644 --- a/p2p/discover/database_test.go +++ b/p2p/discover/database_test.go @@ -102,7 +102,7 @@ func TestNodeDBInt64(t *testing.T) { } func TestNodeDBFetchStore(t *testing.T) { - node := newNode( + node := NewNode( MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{192, 168, 0, 1}, 30303, @@ -165,7 +165,7 @@ var nodeDBSeedQueryNodes = []struct { // This one should not be in the result set because its last // pong time is too far in the past. { - node: newNode( + node: NewNode( MustHexID("0x84d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{127, 0, 0, 3}, 30303, @@ -176,7 +176,7 @@ var nodeDBSeedQueryNodes = []struct { // This one shouldn't be in in the result set because its // nodeID is the local node's ID. { - node: newNode( + node: NewNode( MustHexID("0x57d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{127, 0, 0, 3}, 30303, @@ -187,7 +187,7 @@ var nodeDBSeedQueryNodes = []struct { // These should be in the result set. { - node: newNode( + node: NewNode( MustHexID("0x22d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{127, 0, 0, 1}, 30303, @@ -196,7 +196,7 @@ var nodeDBSeedQueryNodes = []struct { pong: time.Now().Add(-2 * time.Second), }, { - node: newNode( + node: NewNode( MustHexID("0x44d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{127, 0, 0, 2}, 30303, @@ -205,7 +205,7 @@ var nodeDBSeedQueryNodes = []struct { pong: time.Now().Add(-3 * time.Second), }, { - node: newNode( + node: NewNode( MustHexID("0xe2d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{127, 0, 0, 3}, 30303, @@ -303,7 +303,7 @@ var nodeDBExpirationNodes = []struct { exp bool }{ { - node: newNode( + node: NewNode( MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{127, 0, 0, 1}, 30303, @@ -312,7 +312,7 @@ var nodeDBExpirationNodes = []struct { pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute), exp: false, }, { - node: newNode( + node: NewNode( MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{127, 0, 0, 2}, 30303, diff --git a/p2p/discover/node.go b/p2p/discover/node.go index dd19df3a2c..fac493f010 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -26,6 +26,7 @@ import ( "math/rand" "net" "net/url" + "regexp" "strconv" "strings" @@ -37,6 +38,7 @@ import ( const nodeIDBits = 512 // Node represents a host on the network. +// The fields of Node may not be modified. type Node struct { IP net.IP // len 4 for IPv4 or 16 for IPv6 UDP, TCP uint16 // port numbers @@ -54,7 +56,9 @@ type Node struct { contested bool } -func newNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { +// NewNode creates a new node. It is mostly meant to be used for +// testing purposes. +func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { if ipv4 := ip.To4(); ipv4 != nil { ip = ipv4 } @@ -71,31 +75,47 @@ func (n *Node) addr() *net.UDPAddr { return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)} } +// Incomplete returns true for nodes with no IP address. +func (n *Node) Incomplete() bool { + return n.IP == nil +} + // The string representation of a Node is a URL. // Please see ParseNode for a description of the format. func (n *Node) String() string { - addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)} - u := url.URL{ - Scheme: "enode", - User: url.User(fmt.Sprintf("%x", n.ID[:])), - Host: addr.String(), - } - if n.UDP != n.TCP { - u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP)) + u := url.URL{Scheme: "enode"} + if n.Incomplete() { + u.Host = fmt.Sprintf("%x", n.ID[:]) + } else { + addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)} + u.User = url.User(fmt.Sprintf("%x", n.ID[:])) + u.Host = addr.String() + if n.UDP != n.TCP { + u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP)) + } } return u.String() } -// ParseNode parses a node URL. +var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$") + +// ParseNode parses a node designator. // -// A node URL has scheme "enode". +// There are two basic forms of node designators +// - incomplete nodes, which only have the public key (node ID) +// - complete nodes, which contain the public key and IP/Port information // -// The hexadecimal node ID is encoded in the username portion of the -// URL, separated from the host by an @ sign. The hostname can only be -// given as an IP address, DNS domain names are not allowed. The port -// in the host name section is the TCP listening port. If the TCP and -// UDP (discovery) ports differ, the UDP port is specified as query -// parameter "discport". +// For incomplete nodes, the designator must look like one of these +// +// enode:// +// +// +// For complete nodes, the node ID is encoded in the username portion +// of the URL, separated from the host by an @ sign. The hostname can +// only be given as an IP address, DNS domain names are not allowed. +// The port in the host name section is the TCP listening port. If the +// TCP and UDP (discovery) ports differ, the UDP port is specified as +// query parameter "discport". // // In the following example, the node URL describes // a node with IP address 10.3.58.6, TCP listening port 30303 @@ -103,12 +123,26 @@ func (n *Node) String() string { // // enode://@10.3.58.6:30303?discport=30301 func ParseNode(rawurl string) (*Node, error) { + if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil { + id, err := HexID(m[1]) + if err != nil { + return nil, fmt.Errorf("invalid node ID (%v)", err) + } + return NewNode(id, nil, 0, 0), nil + } + return parseComplete(rawurl) +} + +func parseComplete(rawurl string) (*Node, error) { var ( id NodeID ip net.IP tcpPort, udpPort uint64 ) u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } if u.Scheme != "enode" { return nil, errors.New("invalid URL scheme, want \"enode\"") } @@ -143,7 +177,7 @@ func ParseNode(rawurl string) (*Node, error) { return nil, errors.New("invalid discport in query") } } - return newNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil + return NewNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil } // MustParseNode parses a node URL. It panics if the URL is not valid. @@ -180,7 +214,7 @@ func HexID(in string) (NodeID, error) { if err != nil { return id, err } else if len(b) != len(id) { - return id, fmt.Errorf("wrong length, need %d hex bytes", len(id)) + return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2) } copy(id[:], b) return id, nil diff --git a/p2p/discover/node_test.go b/p2p/discover/node_test.go index e523e12d2f..3d1662d0b9 100644 --- a/p2p/discover/node_test.go +++ b/p2p/discover/node_test.go @@ -17,10 +17,12 @@ package discover import ( + "fmt" "math/big" "math/rand" "net" "reflect" + "strings" "testing" "testing/quick" "time" @@ -29,6 +31,27 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) +func ExampleNewNode() { + id := MustHexID("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439") + + // Complete nodes contain UDP and TCP endpoints: + n1 := NewNode(id, net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 52150, 30303) + fmt.Println("n1:", n1) + fmt.Println("n1.Incomplete() ->", n1.Incomplete()) + + // An incomplete node can be created by passing zero values + // for all parameters except id. + n2 := NewNode(id, nil, 0, 0) + fmt.Println("n2:", n2) + fmt.Println("n2.Incomplete() ->", n2.Incomplete()) + + // Output: + // n1: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:30303?discport=52150 + // n1.Incomplete() -> false + // n2: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439 + // n2.Incomplete() -> true +} + var parseNodeTests = []struct { rawurl string wantError string @@ -38,14 +61,11 @@ var parseNodeTests = []struct { rawurl: "http://foobar", wantError: `invalid URL scheme, want "enode"`, }, - { - rawurl: "enode://foobar", - wantError: `does not contain node ID`, - }, { rawurl: "enode://01010101@123.124.125.126:3", - wantError: `invalid node ID (wrong length, need 64 hex bytes)`, + wantError: `invalid node ID (wrong length, want 128 hex chars)`, }, + // Complete nodes with IP address. { rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3", wantError: `invalid IP address`, @@ -60,7 +80,7 @@ var parseNodeTests = []struct { }, { rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", - wantResult: newNode( + wantResult: NewNode( MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{0x7f, 0x0, 0x0, 0x1}, 52150, @@ -69,7 +89,7 @@ var parseNodeTests = []struct { }, { rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", - wantResult: newNode( + wantResult: NewNode( MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.ParseIP("::"), 52150, @@ -78,7 +98,7 @@ var parseNodeTests = []struct { }, { rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", - wantResult: newNode( + wantResult: NewNode( MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 52150, @@ -87,33 +107,62 @@ var parseNodeTests = []struct { }, { rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", - wantResult: newNode( + wantResult: NewNode( MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{0x7f, 0x0, 0x0, 0x1}, 22334, 52150, ), }, + // Incomplete nodes with no address. + { + rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", + wantResult: NewNode( + MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + nil, 0, 0, + ), + }, + { + rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", + wantResult: NewNode( + MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + nil, 0, 0, + ), + }, + // Invalid URLs + { + rawurl: "01010101", + wantError: `invalid node ID (wrong length, want 128 hex chars)`, + }, + { + rawurl: "enode://01010101", + wantError: `invalid node ID (wrong length, want 128 hex chars)`, + }, + { + // This test checks that errors from url.Parse are handled. + rawurl: "://foo", + wantError: `parse ://foo: missing protocol scheme`, + }, } func TestParseNode(t *testing.T) { - for i, test := range parseNodeTests { + for _, test := range parseNodeTests { n, err := ParseNode(test.rawurl) if test.wantError != "" { if err == nil { - t.Errorf("test %d: got nil error, expected %#q", i, test.wantError) + t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError) continue } else if err.Error() != test.wantError { - t.Errorf("test %d: got error %#q, expected %#q", i, err.Error(), test.wantError) + t.Errorf("test %q:\n got error %#q, expected %#q", test.rawurl, err.Error(), test.wantError) continue } } else { if err != nil { - t.Errorf("test %d: unexpected error: %v", i, err) + t.Errorf("test %q:\n unexpected error: %v", test.rawurl, err) continue } if !reflect.DeepEqual(n, test.wantResult) { - t.Errorf("test %d: result mismatch:\ngot: %#v, want: %#v", i, n, test.wantResult) + t.Errorf("test %q:\n result mismatch:\ngot: %#v, want: %#v", test.rawurl, n, test.wantResult) } } } @@ -121,12 +170,11 @@ func TestParseNode(t *testing.T) { func TestNodeString(t *testing.T) { for i, test := range parseNodeTests { - if test.wantError != "" { - continue - } - str := test.wantResult.String() - if str != test.rawurl { - t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl) + if test.wantError == "" && strings.HasPrefix(test.rawurl, "enode://") { + str := test.wantResult.String() + if str != test.rawurl { + t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl) + } } } } diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 298ba3fa6a..efa6e8eea4 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -99,7 +99,7 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string tab := &Table{ net: t, db: db, - self: newNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)), + self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)), bonding: make(map[NodeID]*bondproc), bondslots: make(chan struct{}, maxBondingPingPongs), refreshReq: make(chan struct{}), @@ -196,6 +196,28 @@ func (tab *Table) Bootstrap(nodes []*Node) { tab.requestRefresh() } +// Resolve searches for a specific node with the given ID. +// It returns nil if the node could not be found. +func (tab *Table) Resolve(targetID NodeID) *Node { + // If the node is present in the local table, no + // network interaction is required. + hash := crypto.Sha3Hash(targetID[:]) + tab.mutex.Lock() + cl := tab.closest(hash, 1) + tab.mutex.Unlock() + if len(cl.entries) > 0 && cl.entries[0].ID == targetID { + return cl.entries[0] + } + // Otherwise, do a network lookup. + result := tab.Lookup(targetID) + for _, n := range result { + if n.ID == targetID { + return n + } + } + return nil +} + // Lookup performs a network search for nodes close // to the given target. It approaches the target by querying // nodes that are closer to it on each iteration. @@ -466,7 +488,7 @@ func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAdd tab.net.waitping(id) } // Bonding succeeded, update the node database. - w.n = newNode(id, addr.IP, uint16(addr.Port), tcpPort) + w.n = NewNode(id, addr.IP, uint16(addr.Port), tcpPort) tab.db.updateNode(w.n) close(w.done) } diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 13effaed62..30a418f441 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -36,7 +36,7 @@ func TestTable_pingReplace(t *testing.T) { transport := newPingRecorder() tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "") defer tab.Close() - pingSender := newNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99) + pingSender := NewNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99) // fill up the sender's bucket. last := fillBucket(tab, 253) @@ -287,7 +287,7 @@ func TestTable_Lookup(t *testing.T) { t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) } // seed table with initial node (otherwise lookup will terminate immediately) - seed := newNode(lookupTestnet.dists[256][0], net.IP{}, 256, 0) + seed := NewNode(lookupTestnet.dists[256][0], net.IP{}, 256, 0) tab.stuff([]*Node{seed}) results := tab.Lookup(lookupTestnet.target) @@ -517,7 +517,7 @@ func (tn *preminedTestnet) findnode(toid NodeID, toaddr *net.UDPAddr, target Nod next := uint16(toaddr.Port) - 1 var result []*Node for i, id := range tn.dists[toaddr.Port] { - result = append(result, newNode(id, net.ParseIP("127.0.0.1"), next, uint16(i))) + result = append(result, NewNode(id, net.ParseIP("127.0.0.1"), next, uint16(i))) } return result, nil } diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index fc7fa737c3..e93949c56a 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -120,7 +120,7 @@ func nodeFromRPC(rn rpcNode) (n *Node, valid bool) { if rn.IP.IsMulticast() || rn.IP.IsUnspecified() || rn.UDP == 0 { return nil, false } - return newNode(rn.ID, rn.IP, rn.UDP, rn.TCP), true + return NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP), true } func nodeToRPC(n *Node) rpcNode { diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 944e73d6ed..55d6d564a3 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -243,7 +243,7 @@ func TestUDP_findnode(t *testing.T) { // ensure there's a bond with the test node, // findnode won't be accepted otherwise. - test.table.db.updateNode(newNode( + test.table.db.updateNode(NewNode( PubkeyID(&test.remotekey.PublicKey), test.remoteaddr.IP, uint16(test.remoteaddr.Port), From 04c6369a09baa5267a01713663f7c1cbb08896c9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 7 Dec 2015 12:06:49 +0100 Subject: [PATCH 37/78] p2p, p2p/discover: track bootstrap state in p2p/discover This change simplifies the dial scheduling logic because it no longer needs to track whether the discovery table has been bootstrapped. --- p2p/dial.go | 33 +++---------- p2p/dial_test.go | 28 ++++------- p2p/discover/node.go | 20 +++++++- p2p/discover/table.go | 105 +++++++++++++++++++++++++++--------------- p2p/discover/udp.go | 12 ++--- p2p/server.go | 3 ++ 6 files changed, 110 insertions(+), 91 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index 0fd3a4cf52..bdc9f852c2 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -46,7 +46,6 @@ type dialstate struct { ntab discoverTable lookupRunning bool - bootstrapped bool dialing map[discover.NodeID]connFlag lookupBuf []*discover.Node // current discovery lookup results @@ -58,7 +57,6 @@ type dialstate struct { type discoverTable interface { Self() *discover.Node Close() - Bootstrap([]*discover.Node) Lookup(target discover.NodeID) []*discover.Node ReadRandomNodes([]*discover.Node) int } @@ -84,13 +82,9 @@ type dialTask struct { // discoverTask runs discovery table operations. // Only one discoverTask is active at any time. -// -// If bootstrap is true, the task runs Table.Bootstrap, -// otherwise it performs a random lookup and leaves the -// results in the task. +// discoverTask.Do performs a random lookup. type discoverTask struct { - bootstrap bool - results []*discover.Node + results []*discover.Node } // A waitExpireTask is generated if there are no other tasks @@ -154,7 +148,7 @@ func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now // Use random nodes from the table for half of the necessary // dynamic dials. randomCandidates := needDynDials / 2 - if randomCandidates > 0 && s.bootstrapped { + if randomCandidates > 0 { n := s.ntab.ReadRandomNodes(s.randomNodes) for i := 0; i < randomCandidates && i < n; i++ { if addDial(dynDialedConn, s.randomNodes[i]) { @@ -171,12 +165,10 @@ func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now } } s.lookupBuf = s.lookupBuf[:copy(s.lookupBuf, s.lookupBuf[i:])] - // Launch a discovery lookup if more candidates are needed. The - // first discoverTask bootstraps the table and won't return any - // results. + // Launch a discovery lookup if more candidates are needed. if len(s.lookupBuf) < needDynDials && !s.lookupRunning { s.lookupRunning = true - newtasks = append(newtasks, &discoverTask{bootstrap: !s.bootstrapped}) + newtasks = append(newtasks, &discoverTask{}) } // Launch a timer to wait for the next node to expire if all @@ -196,9 +188,6 @@ func (s *dialstate) taskDone(t task, now time.Time) { s.hist.add(t.dest.ID, now.Add(dialHistoryExpiration)) delete(s.dialing, t.dest.ID) case *discoverTask: - if t.bootstrap { - s.bootstrapped = true - } s.lookupRunning = false s.lookupBuf = append(s.lookupBuf, t.results...) } @@ -221,10 +210,6 @@ func (t *dialTask) String() string { } func (t *discoverTask) Do(srv *Server) { - if t.bootstrap { - srv.ntab.Bootstrap(srv.BootstrapNodes) - return - } // newTasks generates a lookup task whenever dynamic dials are // necessary. Lookups need to take some time, otherwise the // event loop spins too fast. @@ -238,12 +223,8 @@ func (t *discoverTask) Do(srv *Server) { t.results = srv.ntab.Lookup(target) } -func (t *discoverTask) String() (s string) { - if t.bootstrap { - s = "discovery bootstrap" - } else { - s = "discovery lookup" - } +func (t *discoverTask) String() string { + s := "discovery lookup" if len(t.results) > 0 { s += fmt.Sprintf(" (%d results)", len(t.results)) } diff --git a/p2p/dial_test.go b/p2p/dial_test.go index d24e03e292..0127b2d870 100644 --- a/p2p/dial_test.go +++ b/p2p/dial_test.go @@ -76,15 +76,10 @@ func runDialTest(t *testing.T, test dialtest) { type fakeTable []*discover.Node -func (t fakeTable) Self() *discover.Node { return new(discover.Node) } -func (t fakeTable) Close() {} -func (t fakeTable) Bootstrap([]*discover.Node) {} -func (t fakeTable) Lookup(target discover.NodeID) []*discover.Node { - return nil -} -func (t fakeTable) ReadRandomNodes(buf []*discover.Node) int { - return copy(buf, t) -} +func (t fakeTable) Self() *discover.Node { return new(discover.Node) } +func (t fakeTable) Close() {} +func (t fakeTable) Lookup(discover.NodeID) []*discover.Node { return nil } +func (t fakeTable) ReadRandomNodes(buf []*discover.Node) int { return copy(buf, t) } // This test checks that dynamic dials are launched from discovery results. func TestDialStateDynDial(t *testing.T) { @@ -98,7 +93,7 @@ func TestDialStateDynDial(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, }, - new: []task{&discoverTask{bootstrap: true}}, + new: []task{&discoverTask{}}, }, // Dynamic dials are launched when it completes. { @@ -108,7 +103,7 @@ func TestDialStateDynDial(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, }, done: []task{ - &discoverTask{bootstrap: true, results: []*discover.Node{ + &discoverTask{results: []*discover.Node{ {ID: uintID(2)}, // this one is already connected and not dialed. {ID: uintID(3)}, {ID: uintID(4)}, @@ -238,22 +233,15 @@ func TestDialStateDynDialFromTable(t *testing.T) { runDialTest(t, dialtest{ init: newDialState(nil, table, 10), rounds: []round{ - // Discovery bootstrap is launched. - { - new: []task{&discoverTask{bootstrap: true}}, - }, // 5 out of 8 of the nodes returned by ReadRandomNodes are dialed. { - done: []task{ - &discoverTask{bootstrap: true}, - }, new: []task{ &dialTask{dynDialedConn, &discover.Node{ID: uintID(1)}}, &dialTask{dynDialedConn, &discover.Node{ID: uintID(2)}}, &dialTask{dynDialedConn, &discover.Node{ID: uintID(3)}}, &dialTask{dynDialedConn, &discover.Node{ID: uintID(4)}}, &dialTask{dynDialedConn, &discover.Node{ID: uintID(5)}}, - &discoverTask{bootstrap: false}, + &discoverTask{}, }, }, // Dialing nodes 1,2 succeeds. Dials from the lookup are launched. @@ -275,7 +263,7 @@ func TestDialStateDynDialFromTable(t *testing.T) { &dialTask{dynDialedConn, &discover.Node{ID: uintID(10)}}, &dialTask{dynDialedConn, &discover.Node{ID: uintID(11)}}, &dialTask{dynDialedConn, &discover.Node{ID: uintID(12)}}, - &discoverTask{bootstrap: false}, + &discoverTask{}, }, }, // Dialing nodes 3,4,5 fails. The dials from the lookup succeed. diff --git a/p2p/discover/node.go b/p2p/discover/node.go index fac493f010..c4a3b50117 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -80,6 +80,24 @@ func (n *Node) Incomplete() bool { return n.IP == nil } +// checks whether n is a valid complete node. +func (n *Node) validateComplete() error { + if n.Incomplete() { + return errors.New("incomplete node") + } + if n.UDP == 0 { + return errors.New("missing UDP port") + } + if n.TCP == 0 { + return errors.New("missing TCP port") + } + if n.IP.IsMulticast() || n.IP.IsUnspecified() { + return errors.New("invalid IP (multicast/unspecified)") + } + _, err := n.ID.Pubkey() // validate the key (on curve, etc.) + return err +} + // The string representation of a Node is a URL. // Please see ParseNode for a description of the format. func (n *Node) String() string { @@ -249,7 +267,7 @@ func (id NodeID) Pubkey() (*ecdsa.PublicKey, error) { p.X.SetBytes(id[:half]) p.Y.SetBytes(id[half:]) if !p.Curve.IsOnCurve(p.X, p.Y) { - return nil, errors.New("not a point on the S256 curve") + return nil, errors.New("id is invalid secp256k1 curve point") } return p, nil } diff --git a/p2p/discover/table.go b/p2p/discover/table.go index efa6e8eea4..abb7980f85 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -25,6 +25,7 @@ package discover import ( "crypto/rand" "encoding/binary" + "fmt" "net" "sort" "sync" @@ -56,7 +57,7 @@ type Table struct { nursery []*Node // bootstrap nodes db *nodeDB // database of known nodes - refreshReq chan struct{} + refreshReq chan chan struct{} closeReq chan struct{} closed chan struct{} @@ -102,7 +103,7 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)), bonding: make(map[NodeID]*bondproc), bondslots: make(chan struct{}, maxBondingPingPongs), - refreshReq: make(chan struct{}), + refreshReq: make(chan chan struct{}), closeReq: make(chan struct{}), closed: make(chan struct{}), } @@ -179,21 +180,27 @@ func (tab *Table) Close() { } } -// Bootstrap sets the bootstrap nodes. These nodes are used to connect -// to the network if the table is empty. Bootstrap will also attempt to -// fill the table by performing random lookup operations on the -// network. -func (tab *Table) Bootstrap(nodes []*Node) { +// SetFallbackNodes sets the initial points of contact. These nodes +// are used to connect to the network if the table is empty and there +// are no known nodes in the database. +func (tab *Table) SetFallbackNodes(nodes []*Node) error { + for _, n := range nodes { + if err := n.validateComplete(); err != nil { + return fmt.Errorf("bad bootstrap/fallback node %q (%v)", n, err) + } + } tab.mutex.Lock() - // TODO: maybe filter nodes with bad fields (nil, etc.) to avoid strange crashes tab.nursery = make([]*Node, 0, len(nodes)) for _, n := range nodes { cpy := *n + // Recompute cpy.sha because the node might not have been + // created by NewNode or ParseNode. cpy.sha = crypto.Sha3Hash(n.ID[:]) tab.nursery = append(tab.nursery, &cpy) } tab.mutex.Unlock() - tab.requestRefresh() + tab.refresh() + return nil } // Resolve searches for a specific node with the given ID. @@ -224,26 +231,36 @@ func (tab *Table) Resolve(targetID NodeID) *Node { // The given target does not need to be an actual node // identifier. func (tab *Table) Lookup(targetID NodeID) []*Node { + return tab.lookup(targetID, true) +} + +func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node { var ( target = crypto.Sha3Hash(targetID[:]) asked = make(map[NodeID]bool) seen = make(map[NodeID]bool) reply = make(chan []*Node, alpha) pendingQueries = 0 + result *nodesByDistance ) // don't query further if we hit ourself. // unlikely to happen often in practice. asked[tab.self.ID] = true - tab.mutex.Lock() - // generate initial result set - result := tab.closest(target, bucketSize) - tab.mutex.Unlock() - - // If the result set is empty, all nodes were dropped, refresh. - if len(result.entries) == 0 { - tab.requestRefresh() - return nil + for { + tab.mutex.Lock() + // generate initial result set + result = tab.closest(target, bucketSize) + tab.mutex.Unlock() + if len(result.entries) > 0 || !refreshIfEmpty { + break + } + // The result set is empty, all nodes were dropped, refresh. + // We actually wait for the refresh to complete here. The very + // first query will hit this case and run the bootstrapping + // logic. + <-tab.refresh() + refreshIfEmpty = false } for { @@ -287,24 +304,24 @@ func (tab *Table) Lookup(targetID NodeID) []*Node { return result.entries } -func (tab *Table) requestRefresh() { +func (tab *Table) refresh() <-chan struct{} { + done := make(chan struct{}) select { - case tab.refreshReq <- struct{}{}: + case tab.refreshReq <- done: case <-tab.closed: + close(done) } + return done } +// refreshLoop schedules doRefresh runs and coordinates shutdown. func (tab *Table) refreshLoop() { - defer func() { - tab.db.close() - if tab.net != nil { - tab.net.close() - } - close(tab.closed) - }() - - timer := time.NewTicker(autoRefreshInterval) - var done chan struct{} + var ( + timer = time.NewTicker(autoRefreshInterval) + waiting []chan struct{} // accumulates waiting callers while doRefresh runs + done chan struct{} // where doRefresh reports completion + ) +loop: for { select { case <-timer.C: @@ -312,20 +329,34 @@ func (tab *Table) refreshLoop() { done = make(chan struct{}) go tab.doRefresh(done) } - case <-tab.refreshReq: + case req := <-tab.refreshReq: + waiting = append(waiting, req) if done == nil { done = make(chan struct{}) go tab.doRefresh(done) } case <-done: + for _, ch := range waiting { + close(ch) + } + waiting = nil done = nil case <-tab.closeReq: - if done != nil { - <-done - } - return + break loop } } + + if tab.net != nil { + tab.net.close() + } + if done != nil { + <-done + } + for _, ch := range waiting { + close(ch) + } + tab.db.close() + close(tab.closed) } // doRefresh performs a lookup for a random target to keep buckets @@ -342,7 +373,7 @@ func (tab *Table) doRefresh(done chan struct{}) { // We perform a lookup with a random target instead. var target NodeID rand.Read(target[:]) - result := tab.Lookup(target) + result := tab.lookup(target, false) if len(result) > 0 { return } @@ -366,7 +397,7 @@ func (tab *Table) doRefresh(done chan struct{}) { tab.mutex.Unlock() // Finally, do a self lookup to fill up the buckets. - tab.Lookup(tab.self.ID) + tab.lookup(tab.self.ID, false) } // closest returns the n nodes in the table that are closest to the diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index e93949c56a..72b2a45e58 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -114,13 +114,11 @@ func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} } -func nodeFromRPC(rn rpcNode) (n *Node, valid bool) { +func nodeFromRPC(rn rpcNode) (*Node, error) { // TODO: don't accept localhost, LAN addresses from internet hosts - // TODO: check public key is on secp256k1 curve - if rn.IP.IsMulticast() || rn.IP.IsUnspecified() || rn.UDP == 0 { - return nil, false - } - return NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP), true + n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP) + err := n.validateComplete() + return n, err } func nodeToRPC(n *Node) rpcNode { @@ -271,7 +269,7 @@ func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node reply := r.(*neighbors) for _, rn := range reply.Nodes { nreceived++ - if n, valid := nodeFromRPC(rn); valid { + if n, err := nodeFromRPC(rn); err == nil { nodes = append(nodes, n) } } diff --git a/p2p/server.go b/p2p/server.go index 7991585f17..52d1be6775 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -334,6 +334,9 @@ func (srv *Server) Start() (err error) { if err != nil { return err } + if err := ntab.SetFallbackNodes(srv.BootstrapNodes); err != nil { + return err + } srv.ntab = ntab } From 6c41e675ec6fc720a1e8429fa4ef035a476e26d8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 22 Oct 2015 23:46:28 +0200 Subject: [PATCH 38/78] p2p: resolve incomplete dial targets This change makes it possible to add peers without providing their IP address. The endpoint of the target node is resolved using the discovery protocol. --- p2p/dial.go | 110 ++++++++++++++++++++++++++++++++-------- p2p/dial_test.go | 128 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 175 insertions(+), 63 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index bdc9f852c2..c0e703d7d0 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -36,6 +36,10 @@ const ( // Discovery lookups are throttled and can only run // once every few seconds. lookupInterval = 4 * time.Second + + // Endpoint resolution is throttled with bounded backoff. + initialResolveDelay = 60 * time.Second + maxResolveDelay = time.Hour ) // dialstate schedules dials and discovery lookups. @@ -46,17 +50,17 @@ type dialstate struct { ntab discoverTable lookupRunning bool - - dialing map[discover.NodeID]connFlag - lookupBuf []*discover.Node // current discovery lookup results - randomNodes []*discover.Node // filled from Table - static map[discover.NodeID]*discover.Node - hist *dialHistory + dialing map[discover.NodeID]connFlag + lookupBuf []*discover.Node // current discovery lookup results + randomNodes []*discover.Node // filled from Table + static map[discover.NodeID]*dialTask + hist *dialHistory } type discoverTable interface { Self() *discover.Node Close() + Resolve(target discover.NodeID) *discover.Node Lookup(target discover.NodeID) []*discover.Node ReadRandomNodes([]*discover.Node) int } @@ -74,10 +78,13 @@ type task interface { Do(*Server) } -// A dialTask is generated for each node that is dialed. +// A dialTask is generated for each node that is dialed. Its +// fields cannot be accessed while the task is running. type dialTask struct { - flags connFlag - dest *discover.Node + flags connFlag + dest *discover.Node + lastResolved time.Time + resolveDelay time.Duration } // discoverTask runs discovery table operations. @@ -97,26 +104,31 @@ func newDialState(static []*discover.Node, ntab discoverTable, maxdyn int) *dial s := &dialstate{ maxDynDials: maxdyn, ntab: ntab, - static: make(map[discover.NodeID]*discover.Node), + static: make(map[discover.NodeID]*dialTask), dialing: make(map[discover.NodeID]connFlag), randomNodes: make([]*discover.Node, maxdyn/2), hist: new(dialHistory), } for _, n := range static { - s.static[n.ID] = n + s.addStatic(n) } return s } func (s *dialstate) addStatic(n *discover.Node) { - s.static[n.ID] = n + // This overwites the task instead of updating an existing + // entry, giving users the opportunity to force a resolve operation. + s.static[n.ID] = &dialTask{flags: staticDialedConn, dest: n} } func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now time.Time) []task { var newtasks []task + isDialing := func(id discover.NodeID) bool { + _, found := s.dialing[id] + return found || peers[id] != nil || s.hist.contains(id) + } addDial := func(flag connFlag, n *discover.Node) bool { - _, dialing := s.dialing[n.ID] - if dialing || peers[n.ID] != nil || s.hist.contains(n.ID) { + if isDialing(n.ID) { return false } s.dialing[n.ID] = flag @@ -141,8 +153,11 @@ func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now s.hist.expire(now) // Create dials for static nodes if they are not connected. - for _, n := range s.static { - addDial(staticDialedConn, n) + for id, t := range s.static { + if !isDialing(id) { + s.dialing[id] = t.flags + newtasks = append(newtasks, t) + } } // Use random nodes from the table for half of the necessary @@ -194,17 +209,68 @@ func (s *dialstate) taskDone(t task, now time.Time) { } func (t *dialTask) Do(srv *Server) { - addr := &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)} - glog.V(logger.Debug).Infof("dialing %v\n", t.dest) + if t.dest.Incomplete() { + if !t.resolve(srv) { + return + } + } + success := t.dial(srv, t.dest) + // Try resolving the ID of static nodes if dialing failed. + if !success && t.flags&staticDialedConn != 0 { + if t.resolve(srv) { + t.dial(srv, t.dest) + } + } +} + +// resolve attempts to find the current endpoint for the destination +// using discovery. +// +// Resolve operations are throttled with backoff to avoid flooding the +// discovery network with useless queries for nodes that don't exist. +// The backoff delay resets when the node is found. +func (t *dialTask) resolve(srv *Server) bool { + if srv.ntab == nil { + glog.V(logger.Debug).Infof("can't resolve node %x: discovery is disabled", t.dest.ID[:6]) + return false + } + if t.resolveDelay == 0 { + t.resolveDelay = initialResolveDelay + } + if time.Since(t.lastResolved) < t.resolveDelay { + return false + } + resolved := srv.ntab.Resolve(t.dest.ID) + t.lastResolved = time.Now() + if resolved == nil { + t.resolveDelay *= 2 + if t.resolveDelay > maxResolveDelay { + t.resolveDelay = maxResolveDelay + } + glog.V(logger.Debug).Infof("resolving node %x failed (new delay: %v)", t.dest.ID[:6], t.resolveDelay) + return false + } + // The node was found. + t.resolveDelay = initialResolveDelay + t.dest = resolved + glog.V(logger.Debug).Infof("resolved node %x: %v:%d", t.dest.ID[:6], t.dest.IP, t.dest.TCP) + return true +} + +// dial performs the actual connection attempt. +func (t *dialTask) dial(srv *Server, dest *discover.Node) bool { + addr := &net.TCPAddr{IP: dest.IP, Port: int(dest.TCP)} + glog.V(logger.Debug).Infof("dial tcp %v (%x)\n", addr, dest.ID[:6]) fd, err := srv.Dialer.Dial("tcp", addr.String()) if err != nil { - glog.V(logger.Detail).Infof("dial error: %v", err) - return + glog.V(logger.Detail).Infof("%v", err) + return false } mfd := newMeteredConn(fd, false) - - srv.setupConn(mfd, t.flags, t.dest) + srv.setupConn(mfd, t.flags, dest) + return true } + func (t *dialTask) String() string { return fmt.Sprintf("%v %x %v:%d", t.flags, t.dest.ID[:8], t.dest.IP, t.dest.TCP) } diff --git a/p2p/dial_test.go b/p2p/dial_test.go index 0127b2d870..3447660a3f 100644 --- a/p2p/dial_test.go +++ b/p2p/dial_test.go @@ -18,6 +18,7 @@ package p2p import ( "encoding/binary" + "net" "reflect" "testing" "time" @@ -79,6 +80,7 @@ type fakeTable []*discover.Node func (t fakeTable) Self() *discover.Node { return new(discover.Node) } func (t fakeTable) Close() {} func (t fakeTable) Lookup(discover.NodeID) []*discover.Node { return nil } +func (t fakeTable) Resolve(discover.NodeID) *discover.Node { return nil } func (t fakeTable) ReadRandomNodes(buf []*discover.Node) int { return copy(buf, t) } // This test checks that dynamic dials are launched from discovery results. @@ -113,9 +115,9 @@ func TestDialStateDynDial(t *testing.T) { }}, }, new: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(3)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(4)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, }, }, // Some of the dials complete but no new ones are launched yet because @@ -129,8 +131,8 @@ func TestDialStateDynDial(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(4)}}, }, done: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(3)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(4)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, }, }, // No new dial tasks are launched in the this round because @@ -145,7 +147,7 @@ func TestDialStateDynDial(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(5)}}, }, done: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, }, new: []task{ &waitExpireTask{Duration: 14 * time.Second}, @@ -162,7 +164,7 @@ func TestDialStateDynDial(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(5)}}, }, new: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(6)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(6)}}, }, }, // More peers (3,4) drop off and dial for ID 6 completes. @@ -175,10 +177,10 @@ func TestDialStateDynDial(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(5)}}, }, done: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(6)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(6)}}, }, new: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(7)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}}, &discoverTask{}, }, }, @@ -193,7 +195,7 @@ func TestDialStateDynDial(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(7)}}, }, done: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(7)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}}, }, }, // Finish the running node discovery with an empty set. A new lookup @@ -236,11 +238,11 @@ func TestDialStateDynDialFromTable(t *testing.T) { // 5 out of 8 of the nodes returned by ReadRandomNodes are dialed. { new: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(1)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(2)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(3)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(4)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, &discoverTask{}, }, }, @@ -251,8 +253,8 @@ func TestDialStateDynDialFromTable(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, }, done: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(1)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(2)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}}, &discoverTask{results: []*discover.Node{ {ID: uintID(10)}, {ID: uintID(11)}, @@ -260,9 +262,9 @@ func TestDialStateDynDialFromTable(t *testing.T) { }}, }, new: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(10)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(11)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(12)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}}, &discoverTask{}, }, }, @@ -276,12 +278,12 @@ func TestDialStateDynDialFromTable(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(12)}}, }, done: []task{ - &dialTask{dynDialedConn, &discover.Node{ID: uintID(3)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(4)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(5)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(10)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(11)}}, - &dialTask{dynDialedConn, &discover.Node{ID: uintID(12)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}}, + &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}}, }, }, // Waiting for expiry. No waitExpireTask is launched because the @@ -332,9 +334,9 @@ func TestDialStateStaticDial(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, }, new: []task{ - &dialTask{staticDialedConn, &discover.Node{ID: uintID(3)}}, - &dialTask{staticDialedConn, &discover.Node{ID: uintID(4)}}, - &dialTask{staticDialedConn, &discover.Node{ID: uintID(5)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}}, }, }, // No new tasks are launched in this round because all static @@ -346,7 +348,7 @@ func TestDialStateStaticDial(t *testing.T) { {rw: &conn{flags: staticDialedConn, id: uintID(3)}}, }, done: []task{ - &dialTask{staticDialedConn, &discover.Node{ID: uintID(3)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}}, }, }, // No new dial tasks are launched because all static @@ -360,8 +362,8 @@ func TestDialStateStaticDial(t *testing.T) { {rw: &conn{flags: staticDialedConn, id: uintID(5)}}, }, done: []task{ - &dialTask{staticDialedConn, &discover.Node{ID: uintID(4)}}, - &dialTask{staticDialedConn, &discover.Node{ID: uintID(5)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}}, }, new: []task{ &waitExpireTask{Duration: 14 * time.Second}, @@ -386,8 +388,8 @@ func TestDialStateStaticDial(t *testing.T) { {rw: &conn{flags: staticDialedConn, id: uintID(5)}}, }, new: []task{ - &dialTask{staticDialedConn, &discover.Node{ID: uintID(2)}}, - &dialTask{staticDialedConn, &discover.Node{ID: uintID(4)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}}, }, }, }, @@ -410,9 +412,9 @@ func TestDialStateCache(t *testing.T) { { peers: nil, new: []task{ - &dialTask{staticDialedConn, &discover.Node{ID: uintID(1)}}, - &dialTask{staticDialedConn, &discover.Node{ID: uintID(2)}}, - &dialTask{staticDialedConn, &discover.Node{ID: uintID(3)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}}, }, }, // No new tasks are launched in this round because all static @@ -423,8 +425,8 @@ func TestDialStateCache(t *testing.T) { {rw: &conn{flags: staticDialedConn, id: uintID(2)}}, }, done: []task{ - &dialTask{staticDialedConn, &discover.Node{ID: uintID(1)}}, - &dialTask{staticDialedConn, &discover.Node{ID: uintID(2)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}}, }, }, // A salvage task is launched to wait for node 3's history @@ -435,7 +437,7 @@ func TestDialStateCache(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, }, done: []task{ - &dialTask{staticDialedConn, &discover.Node{ID: uintID(3)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}}, }, new: []task{ &waitExpireTask{Duration: 14 * time.Second}, @@ -455,13 +457,40 @@ func TestDialStateCache(t *testing.T) { {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, }, new: []task{ - &dialTask{staticDialedConn, &discover.Node{ID: uintID(3)}}, + &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}}, }, }, }, }) } +func TestDialResolve(t *testing.T) { + resolved := discover.NewNode(uintID(1), net.IP{127, 0, 55, 234}, 3333, 4444) + table := &resolveMock{answer: resolved} + state := newDialState(nil, table, 0) + + // Check that the task is generated with an incomplete ID. + dest := discover.NewNode(uintID(1), nil, 0, 0) + state.addStatic(dest) + tasks := state.newTasks(0, nil, time.Time{}) + if !reflect.DeepEqual(tasks, []task{&dialTask{flags: staticDialedConn, dest: dest}}) { + t.Fatalf("expected dial task, got %#v", tasks) + } + + // Now run the task, it should resolve the ID once. + srv := &Server{ntab: table, Dialer: &net.Dialer{Deadline: time.Now().Add(-5 * time.Minute)}} + tasks[0].Do(srv) + if !reflect.DeepEqual(table.resolveCalls, []discover.NodeID{dest.ID}) { + t.Fatalf("wrong resolve calls, got %v", table.resolveCalls) + } + + // Report it as done to the dialer, which should update the static node record. + state.taskDone(tasks[0], time.Now()) + if state.static[uintID(1)].dest != resolved { + t.Fatalf("state.dest not updated") + } +} + // compares task lists but doesn't care about the order. func sametasks(a, b []task) bool { if len(a) != len(b) { @@ -484,3 +513,20 @@ func uintID(i uint32) discover.NodeID { binary.BigEndian.PutUint32(id[:], i) return id } + +// implements discoverTable for TestDialResolve +type resolveMock struct { + resolveCalls []discover.NodeID + answer *discover.Node +} + +func (t *resolveMock) Resolve(id discover.NodeID) *discover.Node { + t.resolveCalls = append(t.resolveCalls, id) + return t.answer +} + +func (t *resolveMock) Self() *discover.Node { return new(discover.Node) } +func (t *resolveMock) Close() {} +func (t *resolveMock) Bootstrap([]*discover.Node) {} +func (t *resolveMock) Lookup(discover.NodeID) []*discover.Node { return nil } +func (t *resolveMock) ReadRandomNodes(buf []*discover.Node) int { return 0 } From b3f1f4c673271258f18c61d8b6e5cda9849da27a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 17 Dec 2015 23:28:27 +0100 Subject: [PATCH 39/78] p2p/discover: attempt to deflake TestUDP_responseTimeouts The test expected the timeout to fire after a matcher for the response was added, but the timeout is random and fired sooner sometimes. --- p2p/discover/udp_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 55d6d564a3..8ed12b8eca 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -167,16 +167,17 @@ func TestUDP_responseTimeouts(t *testing.T) { binary.BigEndian.PutUint64(p.from[:], uint64(i)) if p.ptype <= 128 { p.errc = timeoutErr + test.udp.addpending <- p nTimeouts++ } else { p.errc = nilErr + test.udp.addpending <- p time.AfterFunc(randomDuration(60*time.Millisecond), func() { if !test.udp.handleReply(p.from, p.ptype, nil) { t.Logf("not matched: %v", p) } }) } - test.udp.addpending <- p time.Sleep(randomDuration(30 * time.Millisecond)) } From 9be5d5cd90517244b239c6af4e602d898fafeaf7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 17 Dec 2015 14:13:30 +0100 Subject: [PATCH 40/78] eth/downloader: fix negative balance issue in tests The test chain generated by makeChainFork included invalid uncle headers, crashing the generator during the state commit. The headers were invalid because they used the iteration counter as the block number, even though makeChainFork uses a block with number > 0 as the parent. Fix this by introducing BlockGen.Number, which allows accessing the actual number of the block being generated. --- core/chain_makers.go | 5 +++++ eth/downloader/downloader_test.go | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/chain_makers.go b/core/chain_makers.go index 4f6fa39896..5a8f380a36 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -104,6 +104,11 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { b.receipts = append(b.receipts, receipt) } +// Number returns the block number of the block being generated. +func (b *BlockGen) Number() *big.Int { + return new(big.Int).Set(b.header.Number) +} + // AddUncheckedReceipts forcefully adds a receipts to the block without a // backing transaction. // diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index cfcc8a2ef9..f02418a2f3 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -61,8 +61,11 @@ func makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Recei block.AddTx(tx) } // If the block number is a multiple of 5, add a bonus uncle to the block - if i%5 == 0 { - block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 1).Hash(), Number: big.NewInt(int64(i - 1))}) + if i > 0 && i%5 == 0 { + block.AddUncle(&types.Header{ + ParentHash: block.PrevBlock(i - 1).Hash(), + Number: big.NewInt(block.Number().Int64() - 1), + }) } }) // Convert the block-chain into a hash-chain and header/block maps From 1b89bd5d269d2d85a7c72067e18212135d8757f9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 11 Dec 2015 01:29:41 +0100 Subject: [PATCH 41/78] core/state, core/types use package rlp for state, receipt serialisation --- core/state/dump.go | 2 +- core/state/state_object.go | 93 ++++++++++++++++++-------------------- core/state/state_test.go | 6 +-- core/state/statedb.go | 22 +++++---- core/types/receipt.go | 15 +++--- 5 files changed, 67 insertions(+), 71 deletions(-) diff --git a/core/state/dump.go b/core/state/dump.go index 9acb8a0244..cff9c50aa3 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -45,7 +45,7 @@ func (self *StateDB) RawDump() World { it := self.trie.Iterator() for it.Next() { addr := self.trie.GetKey(it.Key) - stateObject := NewStateObjectFromBytes(common.BytesToAddress(addr), it.Value, self.db) + stateObject, _ := DecodeObject(common.BytesToAddress(addr), self.db, it.Value) account := Account{Balance: stateObject.balance.String(), Nonce: stateObject.nonce, Root: common.Bytes2Hex(stateObject.Root()), CodeHash: common.Bytes2Hex(stateObject.codeHash)} account.Storage = make(map[string]string) diff --git a/core/state/state_object.go b/core/state/state_object.go index c06e3d2276..47546112f4 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -19,17 +19,19 @@ package state import ( "bytes" "fmt" + "io" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) +var emptyCodeHash = crypto.Sha3(nil) + type Code []byte func (self Code) String() string { @@ -56,8 +58,7 @@ func (self Storage) Copy() Storage { } type StateObject struct { - // State database for storing state changes - db ethdb.Database + db trie.Database // State database for storing state changes trie *trie.SecureTrie // Address belonging to this account @@ -83,39 +84,16 @@ type StateObject struct { dirty bool } -func NewStateObject(address common.Address, db ethdb.Database) *StateObject { - object := &StateObject{db: db, address: address, balance: new(big.Int), dirty: true} +func NewStateObject(address common.Address, db trie.Database) *StateObject { + object := &StateObject{ + db: db, + address: address, + balance: new(big.Int), + dirty: true, + codeHash: emptyCodeHash, + storage: make(Storage), + } object.trie, _ = trie.NewSecure(common.Hash{}, db) - object.storage = make(Storage) - return object -} - -func NewStateObjectFromBytes(address common.Address, data []byte, db ethdb.Database) *StateObject { - var extobject struct { - Nonce uint64 - Balance *big.Int - Root common.Hash - CodeHash []byte - } - err := rlp.Decode(bytes.NewReader(data), &extobject) - if err != nil { - glog.Errorf("can't decode state object %x: %v", address, err) - return nil - } - trie, err := trie.NewSecure(extobject.Root, db) - if err != nil { - // TODO: bubble this up or panic - glog.Errorf("can't create account trie with root %x: %v", extobject.Root[:], err) - return nil - } - - object := &StateObject{address: address, db: db} - object.nonce = extobject.Nonce - object.balance = extobject.Balance - object.codeHash = extobject.CodeHash - object.trie = trie - object.storage = make(map[string]common.Hash) - object.code, _ = db.Get(extobject.CodeHash) return object } @@ -172,7 +150,6 @@ func (self *StateObject) Update() { self.trie.Delete([]byte(key)) continue } - self.setAddr([]byte(key), value) } } @@ -248,6 +225,7 @@ func (self *StateObject) Code() []byte { func (self *StateObject) SetCode(code []byte) { self.code = code + self.codeHash = crypto.Sha3(code) self.dirty = true } @@ -276,23 +254,40 @@ func (self *StateObject) EachStorage(cb func(key, value []byte)) { } } -// // Encoding -// -// State object encoding methods -func (c *StateObject) RlpEncode() []byte { - return common.Encode([]interface{}{c.nonce, c.balance, c.Root(), c.CodeHash()}) +type extStateObject struct { + Nonce uint64 + Balance *big.Int + Root common.Hash + CodeHash []byte } -func (c *StateObject) CodeHash() common.Bytes { - return crypto.Sha3(c.code) +// EncodeRLP implements rlp.Encoder. +func (c *StateObject) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{c.nonce, c.balance, c.Root(), c.codeHash}) } -// Storage change object. Used by the manifest for notifying changes to -// the sub channels. -type StorageState struct { - StateAddress []byte - Address []byte - Value *big.Int +// DecodeObject decodes an RLP-encoded state object. +func DecodeObject(address common.Address, db trie.Database, data []byte) (*StateObject, error) { + var ( + obj = &StateObject{address: address, db: db, storage: make(Storage)} + ext extStateObject + err error + ) + if err = rlp.DecodeBytes(data, &ext); err != nil { + return nil, err + } + if obj.trie, err = trie.NewSecure(ext.Root, db); err != nil { + return nil, err + } + if !bytes.Equal(ext.CodeHash, emptyCodeHash) { + if obj.code, err = db.Get(ext.CodeHash); err != nil { + return nil, fmt.Errorf("can't find code for hash %x: %v", ext.CodeHash, err) + } + } + obj.nonce = ext.Nonce + obj.balance = ext.Balance + obj.codeHash = ext.CodeHash + return obj, nil } diff --git a/core/state/state_test.go b/core/state/state_test.go index 7ddbe11a13..7ce341c361 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -138,8 +138,7 @@ func TestSnapshot2(t *testing.T) { so0 := state.GetStateObject(stateobjaddr0) so0.balance = big.NewInt(42) so0.nonce = 43 - so0.code = []byte{'c', 'a', 'f', 'e'} - so0.codeHash = so0.CodeHash() + so0.SetCode([]byte{'c', 'a', 'f', 'e'}) so0.remove = true so0.deleted = false so0.dirty = false @@ -149,8 +148,7 @@ func TestSnapshot2(t *testing.T) { so1 := state.GetStateObject(stateobjaddr1) so1.balance = big.NewInt(52) so1.nonce = 53 - so1.code = []byte{'c', 'a', 'f', 'e', '2'} - so1.codeHash = so1.CodeHash() + so1.SetCode([]byte{'c', 'a', 'f', 'e', '2'}) so1.remove = true so1.deleted = true so1.dirty = true diff --git a/core/state/statedb.go b/core/state/statedb.go index a9de71409c..ab93870bf6 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,6 +18,7 @@ package state import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -25,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -205,13 +207,15 @@ func (self *StateDB) Delete(addr common.Address) bool { // Update the given state object and apply it to state trie func (self *StateDB) UpdateStateObject(stateObject *StateObject) { - //addr := stateObject.Address() - - if len(stateObject.CodeHash()) > 0 { - self.db.Put(stateObject.CodeHash(), stateObject.code) + if len(stateObject.code) > 0 { + self.db.Put(stateObject.codeHash, stateObject.code) } addr := stateObject.Address() - self.trie.Update(addr[:], stateObject.RlpEncode()) + data, err := rlp.EncodeToBytes(stateObject) + if err != nil { + panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err)) + } + self.trie.Update(addr[:], data) } // Delete the given state object and delete it from the state trie @@ -238,10 +242,12 @@ func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObje if len(data) == 0 { return nil } - - stateObject = NewStateObjectFromBytes(addr, []byte(data), self.db) + stateObject, err := DecodeObject(addr, self.db, data) + if err != nil { + glog.Errorf("can't decode object at %x: %v", addr[:], err) + return nil + } self.SetStateObject(stateObject) - return stateObject } diff --git a/core/types/receipt.go b/core/types/receipt.go index e7d5203a3e..5f847fc5c3 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -125,17 +125,14 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { // Receipts is a wrapper around a Receipt array to implement types.DerivableList. type Receipts []*Receipt -// RlpEncode implements common.RlpEncode required for SHA3 derivation. -func (r Receipts) RlpEncode() []byte { - bytes, err := rlp.EncodeToBytes(r) +// Len returns the number of receipts in this list. +func (r Receipts) Len() int { return len(r) } + +// GetRlp returns the RLP encoding of one receipt from the list. +func (r Receipts) GetRlp(i int) []byte { + bytes, err := rlp.EncodeToBytes(r[i]) if err != nil { panic(err) } return bytes } - -// Len returns the number of receipts in this list. -func (r Receipts) Len() int { return len(r) } - -// GetRlp returns the RLP encoding of one receipt from the list. -func (r Receipts) GetRlp(i int) []byte { return common.Rlp(r[i]) } From e6fb69296e647ff305e5d9df059e5aa956303538 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 11 Dec 2015 01:33:45 +0100 Subject: [PATCH 42/78] common: remove old RLP implementation, Value and ExtPackage In order to make this happen, kill all remaining trivial uses of common/{rlp,value}.go. The non-trivial ones have been updated earlier. --- cmd/geth/chaincmd.go | 5 +- common/package.go | 139 ------------- common/rlp.go | 292 -------------------------- common/rlp_test.go | 176 ---------------- common/value.go | 428 --------------------------------------- common/value_test.go | 86 -------- core/database_util.go | 14 ++ eth/backend.go | 14 +- ethdb/memory_database.go | 23 +-- xeth/types.go | 4 - 10 files changed, 19 insertions(+), 1162 deletions(-) delete mode 100644 common/package.go delete mode 100644 common/rlp.go delete mode 100644 common/rlp_test.go delete mode 100644 common/value.go delete mode 100644 common/value_test.go diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 80f3777d6d..868ee7db16 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -137,8 +137,7 @@ func upgradeDB(ctx *cli.Context) { glog.Infoln("Upgrading blockchain database") chain, chainDb := utils.MakeChain(ctx) - v, _ := chainDb.Get([]byte("BlockchainVersion")) - bcVersion := int(common.NewValue(v).Uint()) + bcVersion := core.GetBlockChainVersion(chainDb) if bcVersion == 0 { bcVersion = core.BlockChainVersion } @@ -154,7 +153,7 @@ func upgradeDB(ctx *cli.Context) { // Import the chain file. chain, chainDb = utils.MakeChain(ctx) - chainDb.Put([]byte("BlockchainVersion"), common.NewValue(core.BlockChainVersion).Bytes()) + core.WriteBlockChainVersion(chainDb, core.BlockChainVersion) err := utils.ImportChain(chain, exportFile) chainDb.Close() if err != nil { diff --git a/common/package.go b/common/package.go deleted file mode 100644 index 4e8780c08a..0000000000 --- a/common/package.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2014 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 . - -package common - -import ( - "archive/zip" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "strings" -) - -// Manifest object -// -// The manifest object holds all the relevant information supplied with the -// the manifest specified in the package -type Manifest struct { - Entry string - Height, Width int -} - -// External package -// -// External package contains the main html file and manifest -type ExtPackage struct { - EntryHtml string - Manifest *Manifest -} - -// Read file -// -// Read a given compressed file and returns the read bytes. -// Returns an error otherwise -func ReadFile(f *zip.File) ([]byte, error) { - rc, err := f.Open() - if err != nil { - return nil, err - } - defer rc.Close() - - content, err := ioutil.ReadAll(rc) - if err != nil { - return nil, err - } - - return content, nil -} - -// Reads manifest -// -// Reads and returns a manifest object. Returns error otherwise -func ReadManifest(m []byte) (*Manifest, error) { - var manifest Manifest - - dec := json.NewDecoder(strings.NewReader(string(m))) - if err := dec.Decode(&manifest); err == io.EOF { - } else if err != nil { - return nil, err - } - - return &manifest, nil -} - -// Find file in archive -// -// Returns the index of the given file name if it exists. -1 if file not found -func FindFileInArchive(fn string, files []*zip.File) (index int) { - index = -1 - // Find the manifest first - for i, f := range files { - if f.Name == fn { - index = i - } - } - - return -} - -// Open package -// -// Opens a prepared ethereum package -// Reads the manifest file and determines file contents and returns and -// the external package. -func OpenPackage(fn string) (*ExtPackage, error) { - r, err := zip.OpenReader(fn) - if err != nil { - return nil, err - } - defer r.Close() - - manifestIndex := FindFileInArchive("manifest.json", r.File) - - if manifestIndex < 0 { - return nil, fmt.Errorf("No manifest file found in archive") - } - - f, err := ReadFile(r.File[manifestIndex]) - if err != nil { - return nil, err - } - - manifest, err := ReadManifest(f) - if err != nil { - return nil, err - } - - if manifest.Entry == "" { - return nil, fmt.Errorf("Entry file specified but appears to be empty: %s", manifest.Entry) - } - - entryIndex := FindFileInArchive(manifest.Entry, r.File) - if entryIndex < 0 { - return nil, fmt.Errorf("Entry file not found: '%s'", manifest.Entry) - } - - f, err = ReadFile(r.File[entryIndex]) - if err != nil { - return nil, err - } - - extPackage := &ExtPackage{string(f), manifest} - - return extPackage, nil -} diff --git a/common/rlp.go b/common/rlp.go deleted file mode 100644 index 481b451b1b..0000000000 --- a/common/rlp.go +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2014 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 . - -package common - -import ( - "bytes" - "fmt" - "math/big" - "reflect" -) - -type RlpEncode interface { - RlpEncode() []byte -} - -type RlpEncodeDecode interface { - RlpEncode - RlpValue() []interface{} -} - -type RlpEncodable interface { - RlpData() interface{} -} - -func Rlp(encoder RlpEncode) []byte { - return encoder.RlpEncode() -} - -type RlpEncoder struct { - rlpData []byte -} - -func NewRlpEncoder() *RlpEncoder { - encoder := &RlpEncoder{} - - return encoder -} -func (coder *RlpEncoder) EncodeData(rlpData interface{}) []byte { - return Encode(rlpData) -} - -const ( - RlpEmptyList = 0x80 - RlpEmptyStr = 0x40 -) - -const rlpEof = -1 - -func Char(c []byte) int { - if len(c) > 0 { - return int(c[0]) - } - - return rlpEof -} - -func DecodeWithReader(reader *bytes.Buffer) interface{} { - var slice []interface{} - - // Read the next byte - char := Char(reader.Next(1)) - switch { - case char <= 0x7f: - return char - - case char <= 0xb7: - return reader.Next(int(char - 0x80)) - - case char <= 0xbf: - length := ReadVarInt(reader.Next(int(char - 0xb7))) - - return reader.Next(int(length)) - - case char <= 0xf7: - length := int(char - 0xc0) - for i := 0; i < length; i++ { - obj := DecodeWithReader(reader) - slice = append(slice, obj) - } - - return slice - case char <= 0xff: - length := ReadVarInt(reader.Next(int(char - 0xf7))) - for i := uint64(0); i < length; i++ { - obj := DecodeWithReader(reader) - slice = append(slice, obj) - } - - return slice - default: - panic(fmt.Sprintf("byte not supported: %q", char)) - } - - return slice -} - -var ( - directRlp = big.NewInt(0x7f) - numberRlp = big.NewInt(0xb7) - zeroRlp = big.NewInt(0x0) -) - -func intlen(i int64) (length int) { - for i > 0 { - i = i >> 8 - length++ - } - return -} - -func Encode(object interface{}) []byte { - var buff bytes.Buffer - - if object != nil { - switch t := object.(type) { - case *Value: - buff.Write(Encode(t.Val)) - case RlpEncodable: - buff.Write(Encode(t.RlpData())) - // Code dup :-/ - case int: - buff.Write(Encode(big.NewInt(int64(t)))) - case uint: - buff.Write(Encode(big.NewInt(int64(t)))) - case int8: - buff.Write(Encode(big.NewInt(int64(t)))) - case int16: - buff.Write(Encode(big.NewInt(int64(t)))) - case int32: - buff.Write(Encode(big.NewInt(int64(t)))) - case int64: - buff.Write(Encode(big.NewInt(t))) - case uint16: - buff.Write(Encode(big.NewInt(int64(t)))) - case uint32: - buff.Write(Encode(big.NewInt(int64(t)))) - case uint64: - buff.Write(Encode(big.NewInt(int64(t)))) - case byte: - buff.Write(Encode(big.NewInt(int64(t)))) - case *big.Int: - // Not sure how this is possible while we check for nil - if t == nil { - buff.WriteByte(0xc0) - } else { - buff.Write(Encode(t.Bytes())) - } - case Bytes: - buff.Write(Encode([]byte(t))) - case []byte: - if len(t) == 1 && t[0] <= 0x7f { - buff.Write(t) - } else if len(t) < 56 { - buff.WriteByte(byte(len(t) + 0x80)) - buff.Write(t) - } else { - b := big.NewInt(int64(len(t))) - buff.WriteByte(byte(len(b.Bytes()) + 0xb7)) - buff.Write(b.Bytes()) - buff.Write(t) - } - case string: - buff.Write(Encode([]byte(t))) - case []interface{}: - // Inline function for writing the slice header - WriteSliceHeader := func(length int) { - if length < 56 { - buff.WriteByte(byte(length + 0xc0)) - } else { - b := big.NewInt(int64(length)) - buff.WriteByte(byte(len(b.Bytes()) + 0xf7)) - buff.Write(b.Bytes()) - } - } - - var b bytes.Buffer - for _, val := range t { - b.Write(Encode(val)) - } - WriteSliceHeader(len(b.Bytes())) - buff.Write(b.Bytes()) - default: - // This is how it should have been from the start - // needs refactoring (@fjl) - v := reflect.ValueOf(t) - switch v.Kind() { - case reflect.Slice: - var b bytes.Buffer - for i := 0; i < v.Len(); i++ { - b.Write(Encode(v.Index(i).Interface())) - } - - blen := b.Len() - if blen < 56 { - buff.WriteByte(byte(blen) + 0xc0) - } else { - ilen := byte(intlen(int64(blen))) - buff.WriteByte(ilen + 0xf7) - t := make([]byte, ilen) - for i := byte(0); i < ilen; i++ { - t[ilen-i-1] = byte(blen >> (i * 8)) - } - buff.Write(t) - } - buff.ReadFrom(&b) - } - } - } else { - // Empty list for nil - buff.WriteByte(0xc0) - } - - return buff.Bytes() -} - -// TODO Use a bytes.Buffer instead of a raw byte slice. -// Cleaner code, and use draining instead of seeking the next bytes to read -func Decode(data []byte, pos uint64) (interface{}, uint64) { - var slice []interface{} - char := int(data[pos]) - switch { - case char <= 0x7f: - return data[pos], pos + 1 - - case char <= 0xb7: - b := uint64(data[pos]) - 0x80 - - return data[pos+1 : pos+1+b], pos + 1 + b - - case char <= 0xbf: - b := uint64(data[pos]) - 0xb7 - - b2 := ReadVarInt(data[pos+1 : pos+1+b]) - - return data[pos+1+b : pos+1+b+b2], pos + 1 + b + b2 - - case char <= 0xf7: - b := uint64(data[pos]) - 0xc0 - prevPos := pos - pos++ - for i := uint64(0); i < b; { - var obj interface{} - - // Get the next item in the data list and append it - obj, prevPos = Decode(data, pos) - slice = append(slice, obj) - - // Increment i by the amount bytes read in the previous - // read - i += (prevPos - pos) - pos = prevPos - } - return slice, pos - - case char <= 0xff: - l := uint64(data[pos]) - 0xf7 - b := ReadVarInt(data[pos+1 : pos+1+l]) - - pos = pos + l + 1 - - prevPos := b - for i := uint64(0); i < uint64(b); { - var obj interface{} - - obj, prevPos = Decode(data, pos) - slice = append(slice, obj) - - i += (prevPos - pos) - pos = prevPos - } - return slice, pos - - default: - panic(fmt.Sprintf("byte not supported: %q", char)) - } - - return slice, 0 -} diff --git a/common/rlp_test.go b/common/rlp_test.go deleted file mode 100644 index 2320ffe3c2..0000000000 --- a/common/rlp_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2014 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 . - -package common - -import ( - "bytes" - "math/big" - "reflect" - "testing" - - "github.com/ethereum/go-ethereum/rlp" -) - -func TestNonInterfaceSlice(t *testing.T) { - vala := []string{"value1", "value2", "value3"} - valb := []interface{}{"value1", "value2", "value3"} - resa := Encode(vala) - resb := Encode(valb) - if !bytes.Equal(resa, resb) { - t.Errorf("expected []string & []interface{} to be equal") - } -} - -func TestRlpValueEncoding(t *testing.T) { - val := EmptyValue() - val.AppendList().Append(byte(1)).Append(byte(2)).Append(byte(3)) - val.Append("4").AppendList().Append(byte(5)) - - res, err := rlp.EncodeToBytes(val) - if err != nil { - t.Fatalf("encode error: %v", err) - } - exp := Encode([]interface{}{[]interface{}{1, 2, 3}, "4", []interface{}{5}}) - if bytes.Compare(res, exp) != 0 { - t.Errorf("expected %x, got %x", exp, res) - } -} - -func TestValueSlice(t *testing.T) { - val := []interface{}{ - "value1", - "valeu2", - "value3", - } - - value := NewValue(val) - splitVal := value.SliceFrom(1) - - if splitVal.Len() != 2 { - t.Error("SliceFrom: Expected len", 2, "got", splitVal.Len()) - } - - splitVal = value.SliceTo(2) - if splitVal.Len() != 2 { - t.Error("SliceTo: Expected len", 2, "got", splitVal.Len()) - } - - splitVal = value.SliceFromTo(1, 3) - if splitVal.Len() != 2 { - t.Error("SliceFromTo: Expected len", 2, "got", splitVal.Len()) - } -} - -func TestLargeData(t *testing.T) { - data := make([]byte, 100000) - enc := Encode(data) - value := NewValueFromBytes(enc) - if value.Len() != len(data) { - t.Error("Expected data to be", len(data), "got", value.Len()) - } -} - -func TestValue(t *testing.T) { - value := NewValueFromBytes([]byte("\xcd\x83dog\x83god\x83cat\x01")) - if value.Get(0).Str() != "dog" { - t.Errorf("expected '%v', got '%v'", value.Get(0).Str(), "dog") - } - - if value.Get(3).Uint() != 1 { - t.Errorf("expected '%v', got '%v'", value.Get(3).Uint(), 1) - } -} - -func TestEncode(t *testing.T) { - strRes := "\x83dog" - bytes := Encode("dog") - - str := string(bytes) - if str != strRes { - t.Errorf("Expected %q, got %q", strRes, str) - } - - sliceRes := "\xcc\x83dog\x83god\x83cat" - strs := []interface{}{"dog", "god", "cat"} - bytes = Encode(strs) - slice := string(bytes) - if slice != sliceRes { - t.Error("Expected %q, got %q", sliceRes, slice) - } - - intRes := "\x82\x04\x00" - bytes = Encode(1024) - if string(bytes) != intRes { - t.Errorf("Expected %q, got %q", intRes, bytes) - } -} - -func TestDecode(t *testing.T) { - single := []byte("\x01") - b, _ := Decode(single, 0) - - if b.(uint8) != 1 { - t.Errorf("Expected 1, got %q", b) - } - - str := []byte("\x83dog") - b, _ = Decode(str, 0) - if bytes.Compare(b.([]byte), []byte("dog")) != 0 { - t.Errorf("Expected dog, got %q", b) - } - - slice := []byte("\xcc\x83dog\x83god\x83cat") - res := []interface{}{"dog", "god", "cat"} - b, _ = Decode(slice, 0) - if reflect.DeepEqual(b, res) { - t.Errorf("Expected %q, got %q", res, b) - } -} - -func TestEncodeDecodeBigInt(t *testing.T) { - bigInt := big.NewInt(1391787038) - encoded := Encode(bigInt) - - value := NewValueFromBytes(encoded) - if value.BigInt().Cmp(bigInt) != 0 { - t.Errorf("Expected %v, got %v", bigInt, value.BigInt()) - } -} - -func TestEncodeDecodeBytes(t *testing.T) { - bv := NewValue([]interface{}{[]byte{1, 2, 3, 4, 5}, []byte{6}}) - b, _ := rlp.EncodeToBytes(bv) - val := NewValueFromBytes(b) - if !bv.Cmp(val) { - t.Errorf("Expected %#v, got %#v", bv, val) - } -} - -func TestEncodeZero(t *testing.T) { - b, _ := rlp.EncodeToBytes(NewValue(0)) - exp := []byte{0xc0} - if bytes.Compare(b, exp) == 0 { - t.Error("Expected", exp, "got", b) - } -} - -func BenchmarkEncodeDecode(b *testing.B) { - for i := 0; i < b.N; i++ { - bytes := Encode([]interface{}{"dog", "god", "cat"}) - Decode(bytes, 0) - } -} diff --git a/common/value.go b/common/value.go deleted file mode 100644 index 7abbf67b15..0000000000 --- a/common/value.go +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2014 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 . - -package common - -import ( - "bytes" - "fmt" - "io" - "math/big" - "reflect" - "strconv" - - "github.com/ethereum/go-ethereum/rlp" -) - -// Value can hold values of certain basic types and provides ways to -// convert between types without bothering to check whether the -// conversion is actually meaningful. -// -// It currently supports the following types: -// -// - int{,8,16,32,64} -// - uint{,8,16,32,64} -// - *big.Int -// - []byte, string -// - []interface{} -// -// Value is useful whenever you feel that Go's types limit your -// ability to express yourself. In these situations, use Value and -// forget about this strong typing nonsense. -type Value struct{ Val interface{} } - -func (val *Value) String() string { - return fmt.Sprintf("%x", val.Val) -} - -func NewValue(val interface{}) *Value { - t := val - if v, ok := val.(*Value); ok { - t = v.Val - } - - return &Value{Val: t} -} - -func (val *Value) Type() reflect.Kind { - return reflect.TypeOf(val.Val).Kind() -} - -func (val *Value) IsNil() bool { - return val.Val == nil -} - -func (val *Value) Len() int { - if data, ok := val.Val.([]interface{}); ok { - return len(data) - } - - return len(val.Bytes()) -} - -func (val *Value) Uint() uint64 { - if Val, ok := val.Val.(uint8); ok { - return uint64(Val) - } else if Val, ok := val.Val.(uint16); ok { - return uint64(Val) - } else if Val, ok := val.Val.(uint32); ok { - return uint64(Val) - } else if Val, ok := val.Val.(uint64); ok { - return Val - } else if Val, ok := val.Val.(float32); ok { - return uint64(Val) - } else if Val, ok := val.Val.(float64); ok { - return uint64(Val) - } else if Val, ok := val.Val.(int); ok { - return uint64(Val) - } else if Val, ok := val.Val.(uint); ok { - return uint64(Val) - } else if Val, ok := val.Val.([]byte); ok { - return new(big.Int).SetBytes(Val).Uint64() - } else if Val, ok := val.Val.(*big.Int); ok { - return Val.Uint64() - } - - return 0 -} - -func (val *Value) Int() int64 { - if Val, ok := val.Val.(int8); ok { - return int64(Val) - } else if Val, ok := val.Val.(int16); ok { - return int64(Val) - } else if Val, ok := val.Val.(int32); ok { - return int64(Val) - } else if Val, ok := val.Val.(int64); ok { - return Val - } else if Val, ok := val.Val.(int); ok { - return int64(Val) - } else if Val, ok := val.Val.(float32); ok { - return int64(Val) - } else if Val, ok := val.Val.(float64); ok { - return int64(Val) - } else if Val, ok := val.Val.([]byte); ok { - return new(big.Int).SetBytes(Val).Int64() - } else if Val, ok := val.Val.(*big.Int); ok { - return Val.Int64() - } else if Val, ok := val.Val.(string); ok { - n, _ := strconv.Atoi(Val) - return int64(n) - } - - return 0 -} - -func (val *Value) Byte() byte { - if Val, ok := val.Val.(byte); ok { - return Val - } - - return 0x0 -} - -func (val *Value) BigInt() *big.Int { - if a, ok := val.Val.([]byte); ok { - b := new(big.Int).SetBytes(a) - - return b - } else if a, ok := val.Val.(*big.Int); ok { - return a - } else if a, ok := val.Val.(string); ok { - return Big(a) - } else { - return big.NewInt(int64(val.Uint())) - } - - return big.NewInt(0) -} - -func (val *Value) Str() string { - if a, ok := val.Val.([]byte); ok { - return string(a) - } else if a, ok := val.Val.(string); ok { - return a - } else if a, ok := val.Val.(byte); ok { - return string(a) - } - - return "" -} - -func (val *Value) Bytes() []byte { - if a, ok := val.Val.([]byte); ok { - return a - } else if s, ok := val.Val.(byte); ok { - return []byte{s} - } else if s, ok := val.Val.(string); ok { - return []byte(s) - } else if s, ok := val.Val.(*big.Int); ok { - return s.Bytes() - } else { - return big.NewInt(val.Int()).Bytes() - } - - return []byte{} -} - -func (val *Value) Err() error { - if err, ok := val.Val.(error); ok { - return err - } - - return nil -} - -func (val *Value) Slice() []interface{} { - if d, ok := val.Val.([]interface{}); ok { - return d - } - - return []interface{}{} -} - -func (val *Value) SliceFrom(from int) *Value { - slice := val.Slice() - - return NewValue(slice[from:]) -} - -func (val *Value) SliceTo(to int) *Value { - slice := val.Slice() - - return NewValue(slice[:to]) -} - -func (val *Value) SliceFromTo(from, to int) *Value { - slice := val.Slice() - - return NewValue(slice[from:to]) -} - -// TODO More type checking methods -func (val *Value) IsSlice() bool { - return val.Type() == reflect.Slice -} - -func (val *Value) IsStr() bool { - return val.Type() == reflect.String -} - -func (self *Value) IsErr() bool { - _, ok := self.Val.(error) - return ok -} - -// Special list checking function. Something is considered -// a list if it's of type []interface{}. The list is usually -// used in conjunction with rlp decoded streams. -func (val *Value) IsList() bool { - _, ok := val.Val.([]interface{}) - - return ok -} - -func (val *Value) IsEmpty() bool { - return val.Val == nil || ((val.IsSlice() || val.IsStr()) && val.Len() == 0) -} - -// Threat the value as a slice -func (val *Value) Get(idx int) *Value { - if d, ok := val.Val.([]interface{}); ok { - // Guard for oob - if len(d) <= idx { - return NewValue(nil) - } - - if idx < 0 { - return NewValue(nil) - } - - return NewValue(d[idx]) - } - - // If this wasn't a slice you probably shouldn't be using this function - return NewValue(nil) -} - -func (self *Value) Copy() *Value { - switch val := self.Val.(type) { - case *big.Int: - return NewValue(new(big.Int).Set(val)) - case []byte: - return NewValue(CopyBytes(val)) - default: - return NewValue(self.Val) - } - - return nil -} - -func (val *Value) Cmp(o *Value) bool { - return reflect.DeepEqual(val.Val, o.Val) -} - -func (self *Value) DeepCmp(o *Value) bool { - return bytes.Compare(self.Bytes(), o.Bytes()) == 0 -} - -func (self *Value) DecodeRLP(s *rlp.Stream) error { - var v interface{} - if err := s.Decode(&v); err != nil { - return err - } - self.Val = v - return nil -} - -func (self *Value) EncodeRLP(w io.Writer) error { - if self == nil { - w.Write(rlp.EmptyList) - return nil - } else { - return rlp.Encode(w, self.Val) - } -} - -// NewValueFromBytes decodes RLP data. -// The contained value will be nil if data contains invalid RLP. -func NewValueFromBytes(data []byte) *Value { - v := new(Value) - if len(data) != 0 { - if err := rlp.DecodeBytes(data, v); err != nil { - v.Val = nil - } - } - return v -} - -// Value setters -func NewSliceValue(s interface{}) *Value { - list := EmptyValue() - - if s != nil { - if slice, ok := s.([]interface{}); ok { - for _, val := range slice { - list.Append(val) - } - } else if slice, ok := s.([]string); ok { - for _, val := range slice { - list.Append(val) - } - } - } - - return list -} - -func EmptyValue() *Value { - return NewValue([]interface{}{}) -} - -func (val *Value) AppendList() *Value { - list := EmptyValue() - val.Val = append(val.Slice(), list) - - return list -} - -func (val *Value) Append(v interface{}) *Value { - val.Val = append(val.Slice(), v) - - return val -} - -const ( - valOpAdd = iota - valOpDiv - valOpMul - valOpPow - valOpSub -) - -// Math stuff -func (self *Value) doOp(op int, other interface{}) *Value { - left := self.BigInt() - right := NewValue(other).BigInt() - - switch op { - case valOpAdd: - self.Val = left.Add(left, right) - case valOpDiv: - self.Val = left.Div(left, right) - case valOpMul: - self.Val = left.Mul(left, right) - case valOpPow: - self.Val = left.Exp(left, right, Big0) - case valOpSub: - self.Val = left.Sub(left, right) - } - - return self -} - -func (self *Value) Add(other interface{}) *Value { - return self.doOp(valOpAdd, other) -} - -func (self *Value) Sub(other interface{}) *Value { - return self.doOp(valOpSub, other) -} - -func (self *Value) Div(other interface{}) *Value { - return self.doOp(valOpDiv, other) -} - -func (self *Value) Mul(other interface{}) *Value { - return self.doOp(valOpMul, other) -} - -func (self *Value) Pow(other interface{}) *Value { - return self.doOp(valOpPow, other) -} - -type ValueIterator struct { - value *Value - currentValue *Value - idx int -} - -func (val *Value) NewIterator() *ValueIterator { - return &ValueIterator{value: val} -} - -func (it *ValueIterator) Len() int { - return it.value.Len() -} - -func (it *ValueIterator) Next() bool { - if it.idx >= it.value.Len() { - return false - } - - it.currentValue = it.value.Get(it.idx) - it.idx++ - - return true -} - -func (it *ValueIterator) Value() *Value { - return it.currentValue -} - -func (it *ValueIterator) Idx() int { - return it.idx - 1 -} diff --git a/common/value_test.go b/common/value_test.go deleted file mode 100644 index ac2ef02a70..0000000000 --- a/common/value_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2014 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 . - -package common - -import ( - "math/big" - - checker "gopkg.in/check.v1" -) - -type ValueSuite struct{} - -var _ = checker.Suite(&ValueSuite{}) - -func (s *ValueSuite) TestValueCmp(c *checker.C) { - val1 := NewValue("hello") - val2 := NewValue("world") - c.Assert(val1.Cmp(val2), checker.Equals, false) - - val3 := NewValue("hello") - val4 := NewValue("hello") - c.Assert(val3.Cmp(val4), checker.Equals, true) -} - -func (s *ValueSuite) TestValueTypes(c *checker.C) { - str := NewValue("str") - num := NewValue(1) - inter := NewValue([]interface{}{1}) - byt := NewValue([]byte{1, 2, 3, 4}) - bigInt := NewValue(big.NewInt(10)) - - strExp := "str" - numExp := uint64(1) - interExp := []interface{}{1} - bytExp := []byte{1, 2, 3, 4} - bigExp := big.NewInt(10) - - c.Assert(str.Str(), checker.Equals, strExp) - c.Assert(num.Uint(), checker.Equals, numExp) - c.Assert(NewValue(inter.Val).Cmp(NewValue(interExp)), checker.Equals, true) - c.Assert(byt.Bytes(), checker.DeepEquals, bytExp) - c.Assert(bigInt.BigInt(), checker.DeepEquals, bigExp) -} - -func (s *ValueSuite) TestIterator(c *checker.C) { - value := NewValue([]interface{}{1, 2, 3}) - iter := value.NewIterator() - values := []uint64{1, 2, 3} - i := 0 - for iter.Next() { - c.Assert(values[i], checker.Equals, iter.Value().Uint()) - i++ - } -} - -func (s *ValueSuite) TestMath(c *checker.C) { - data1 := NewValue(1) - data1.Add(1).Add(1) - exp1 := NewValue(3) - data2 := NewValue(2) - data2.Sub(1).Sub(1) - exp2 := NewValue(0) - - c.Assert(data1.DeepCmp(exp1), checker.Equals, true) - c.Assert(data2.DeepCmp(exp2), checker.Equals, true) -} - -func (s *ValueSuite) TestString(c *checker.C) { - data := "10" - exp := int64(10) - c.Assert(NewValue(data).Int(), checker.DeepEquals, exp) -} diff --git a/core/database_util.go b/core/database_util.go index fbcce3e8cc..2dc113e291 100644 --- a/core/database_util.go +++ b/core/database_util.go @@ -582,3 +582,17 @@ func GetMipmapBloom(db ethdb.Database, number, level uint64) types.Bloom { bloomDat, _ := db.Get(mipmapKey(number, level)) return types.BytesToBloom(bloomDat) } + +// GetBlockChainVersion reads the version number from db. +func GetBlockChainVersion(db ethdb.Database) int { + var vsn uint + enc, _ := db.Get([]byte("BlockchainVersion")) + rlp.DecodeBytes(enc, &vsn) + return int(vsn) +} + +// WriteBlockChainVersion writes vsn as the version number to db. +func WriteBlockChainVersion(db ethdb.Database, vsn int) { + enc, _ := rlp.EncodeToBytes(uint(vsn)) + db.Put([]byte("BlockchainVersion"), enc) +} diff --git a/eth/backend.go b/eth/backend.go index d51446d51a..abd1214caa 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -180,12 +180,11 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { } if !config.SkipBcVersionCheck { - b, _ := chainDb.Get([]byte("BlockchainVersion")) - bcVersion := int(common.NewValue(b).Uint()) + bcVersion := core.GetBlockChainVersion(chainDb) if bcVersion != config.BlockChainVersion && bcVersion != 0 { return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d). Run geth upgradedb.\n", bcVersion, config.BlockChainVersion) } - saveBlockchainVersion(chainDb, config.BlockChainVersion) + core.WriteBlockChainVersion(chainDb, config.BlockChainVersion) } glog.V(logger.Info).Infof("Blockchain DB Version: %d", config.BlockChainVersion) @@ -479,15 +478,6 @@ func dagFiles(epoch uint64) (string, string) { return dag, "full-R" + dag } -func saveBlockchainVersion(db ethdb.Database, bcVersion int) { - d, _ := db.Get([]byte("BlockchainVersion")) - blockchainVersion := common.NewValue(d).Uint() - - if blockchainVersion == 0 { - db.Put([]byte("BlockchainVersion"), common.NewValue(bcVersion).Bytes()) - } -} - // upgradeChainDatabase ensures that the chain database stores block split into // separate header and body entries. func upgradeChainDatabase(db ethdb.Database) error { diff --git a/ethdb/memory_database.go b/ethdb/memory_database.go index 01273b9db3..45423ed73b 100644 --- a/ethdb/memory_database.go +++ b/ethdb/memory_database.go @@ -18,7 +18,6 @@ package ethdb import ( "errors" - "fmt" "sync" "github.com/ethereum/go-ethereum/common" @@ -90,27 +89,7 @@ func (db *MemDatabase) Delete(key []byte) error { return nil } -func (db *MemDatabase) Print() { - db.lock.RLock() - defer db.lock.RUnlock() - - for key, val := range db.db { - fmt.Printf("%x(%d): ", key, len(key)) - node := common.NewValueFromBytes(val) - fmt.Printf("%q\n", node.Val) - } -} - -func (db *MemDatabase) Close() { -} - -func (db *MemDatabase) LastKnownTD() []byte { - data, _ := db.Get([]byte("LastKnownTotalDifficulty")) - if len(data) == 0 || data == nil { - data = []byte{0x0} - } - return data -} +func (db *MemDatabase) Close() {} func (db *MemDatabase) NewBatch() Batch { return &memBatch{db: db} diff --git a/xeth/types.go b/xeth/types.go index 218c8dc7cd..090115b7ed 100644 --- a/xeth/types.go +++ b/xeth/types.go @@ -47,10 +47,6 @@ func (self *Object) StorageString(str string) []byte { } } -func (self *Object) StorageValue(addr *common.Value) []byte { - return self.storage(addr.Bytes()) -} - func (self *Object) storage(addr []byte) []byte { return self.StateObject.GetState(common.BytesToHash(addr)).Bytes() } From 3c9a2c752fcc1d85d1e026ac498b2e092725db61 Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Fri, 18 Dec 2015 15:23:43 +0100 Subject: [PATCH 43/78] console: bugfix that causes the console to crash when connection to an endpoint without the personal api enabled --- cmd/geth/js.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 56b7a8b001..9329eaa0e7 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -341,11 +341,12 @@ func (js *jsre) apiBindings(f xeth.Frontend) error { // Override the unlockAccount and newAccount methods on the personal object since these require user interaction. // Assign the jeth.unlockAccount and jeth.newAccount in the jsre the original web3 callbacks. These will be called // by the jeth.* methods after they got the password from the user and send the original web3 request to the backend. - persObj := p.Object() - js.re.Run(`jeth.unlockAccount = personal.unlockAccount;`) - persObj.Set("unlockAccount", jeth.UnlockAccount) - js.re.Run(`jeth.newAccount = personal.newAccount;`) - persObj.Set("newAccount", jeth.NewAccount) + if persObj := p.Object(); persObj != nil { // make sure the personal api is enabled over the interface + js.re.Run(`jeth.unlockAccount = personal.unlockAccount;`) + persObj.Set("unlockAccount", jeth.UnlockAccount) + js.re.Run(`jeth.newAccount = personal.newAccount;`) + persObj.Set("newAccount", jeth.NewAccount) + } return nil } From 1f39746886ce96436aec255347e19fc57edf74b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 30 Dec 2015 14:06:09 +0200 Subject: [PATCH 44/78] eth/downloader: throttling tests are time-sensitive, don't run parallel --- eth/downloader/downloader_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index f02418a2f3..347a0e3f0c 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -720,8 +720,6 @@ func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) } func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) } func testThrottling(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - // Create a long block chain to download and the tester targetBlocks := 8 * blockCacheLimit hashes, headers, blocks, receipts := makeChain(targetBlocks, 0, genesis, nil) @@ -751,7 +749,7 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) { } // Wait a bit for sync to throttle itself var cached, frozen int - for start := time.Now(); time.Since(start) < time.Second; { + for start := time.Now(); time.Since(start) < 3*time.Second; { time.Sleep(25 * time.Millisecond) tester.lock.Lock() From 9e011ff1cd7fe060e106608d0823e48431ef12e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 29 Dec 2015 14:01:08 +0200 Subject: [PATCH 45/78] core, eth/downloader: ensure state presence in ancestor lookup --- core/blockchain.go | 13 ++++ core/state/statedb.go | 1 - eth/downloader/downloader.go | 104 +++++++++++++++--------------- eth/downloader/downloader_test.go | 17 ++++- eth/downloader/types.go | 4 +- eth/handler.go | 7 +- 6 files changed, 85 insertions(+), 61 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 9d526e352e..95ed06d8d6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -587,6 +587,19 @@ func (bc *BlockChain) HasBlock(hash common.Hash) bool { return bc.GetBlock(hash) != nil } +// HasBlockAndState checks if a block and associated state trie is fully present +// in the database or not, caching it if present. +func (bc *BlockChain) HasBlockAndState(hash common.Hash) bool { + // Check first that the block itself is known + block := bc.GetBlock(hash) + if block == nil { + return false + } + // Ensure the associated state is also present + _, err := state.New(block.Root(), bc.chainDb) + return err == nil +} + // GetBlock retrieves a block from the database by hash, caching it if found. func (self *BlockChain) GetBlock(hash common.Hash) *types.Block { // Short circuit if the block's already in the cache, retrieve otherwise diff --git a/core/state/statedb.go b/core/state/statedb.go index ab93870bf6..4133210577 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -57,7 +57,6 @@ type StateDB struct { func New(root common.Hash, db ethdb.Database) (*StateDB, error) { tr, err := trie.NewSecure(root, db) if err != nil { - glog.Errorf("can't create state trie with root %x: %v", root[:], err) return nil, err } return &StateDB{ diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index c272d05afa..1639947304 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -112,20 +112,20 @@ type Downloader struct { syncStatsLock sync.RWMutex // Lock protecting the sync stats fields // Callbacks - hasHeader headerCheckFn // Checks if a header is present in the chain - hasBlock blockCheckFn // Checks if a block is present in the chain - getHeader headerRetrievalFn // Retrieves a header from the chain - getBlock blockRetrievalFn // Retrieves a block from the chain - headHeader headHeaderRetrievalFn // Retrieves the head header from the chain - headBlock headBlockRetrievalFn // Retrieves the head block from the chain - headFastBlock headFastBlockRetrievalFn // Retrieves the head fast-sync block from the chain - commitHeadBlock headBlockCommitterFn // Commits a manually assembled block as the chain head - getTd tdRetrievalFn // Retrieves the TD of a block from the chain - insertHeaders headerChainInsertFn // Injects a batch of headers into the chain - insertBlocks blockChainInsertFn // Injects a batch of blocks into the chain - insertReceipts receiptChainInsertFn // Injects a batch of blocks and their receipts into the chain - rollback chainRollbackFn // Removes a batch of recently added chain links - dropPeer peerDropFn // Drops a peer for misbehaving + hasHeader headerCheckFn // Checks if a header is present in the chain + hasBlockAndState blockAndStateCheckFn // Checks if a block and associated state is present in the chain + getHeader headerRetrievalFn // Retrieves a header from the chain + getBlock blockRetrievalFn // Retrieves a block from the chain + headHeader headHeaderRetrievalFn // Retrieves the head header from the chain + headBlock headBlockRetrievalFn // Retrieves the head block from the chain + headFastBlock headFastBlockRetrievalFn // Retrieves the head fast-sync block from the chain + commitHeadBlock headBlockCommitterFn // Commits a manually assembled block as the chain head + getTd tdRetrievalFn // Retrieves the TD of a block from the chain + insertHeaders headerChainInsertFn // Injects a batch of headers into the chain + insertBlocks blockChainInsertFn // Injects a batch of blocks into the chain + insertReceipts receiptChainInsertFn // Injects a batch of blocks and their receipts into the chain + rollback chainRollbackFn // Removes a batch of recently added chain links + dropPeer peerDropFn // Drops a peer for misbehaving // Status synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing @@ -156,41 +156,41 @@ type Downloader struct { } // New creates a new downloader to fetch hashes and blocks from remote peers. -func New(stateDb ethdb.Database, mux *event.TypeMux, hasHeader headerCheckFn, hasBlock blockCheckFn, getHeader headerRetrievalFn, - getBlock blockRetrievalFn, headHeader headHeaderRetrievalFn, headBlock headBlockRetrievalFn, headFastBlock headFastBlockRetrievalFn, - commitHeadBlock headBlockCommitterFn, getTd tdRetrievalFn, insertHeaders headerChainInsertFn, insertBlocks blockChainInsertFn, - insertReceipts receiptChainInsertFn, rollback chainRollbackFn, dropPeer peerDropFn) *Downloader { +func New(stateDb ethdb.Database, mux *event.TypeMux, hasHeader headerCheckFn, hasBlockAndState blockAndStateCheckFn, + getHeader headerRetrievalFn, getBlock blockRetrievalFn, headHeader headHeaderRetrievalFn, headBlock headBlockRetrievalFn, + headFastBlock headFastBlockRetrievalFn, commitHeadBlock headBlockCommitterFn, getTd tdRetrievalFn, insertHeaders headerChainInsertFn, + insertBlocks blockChainInsertFn, insertReceipts receiptChainInsertFn, rollback chainRollbackFn, dropPeer peerDropFn) *Downloader { return &Downloader{ - mode: FullSync, - mux: mux, - queue: newQueue(stateDb), - peers: newPeerSet(), - hasHeader: hasHeader, - hasBlock: hasBlock, - getHeader: getHeader, - getBlock: getBlock, - headHeader: headHeader, - headBlock: headBlock, - headFastBlock: headFastBlock, - commitHeadBlock: commitHeadBlock, - getTd: getTd, - insertHeaders: insertHeaders, - insertBlocks: insertBlocks, - insertReceipts: insertReceipts, - rollback: rollback, - dropPeer: dropPeer, - newPeerCh: make(chan *peer, 1), - hashCh: make(chan dataPack, 1), - blockCh: make(chan dataPack, 1), - headerCh: make(chan dataPack, 1), - bodyCh: make(chan dataPack, 1), - receiptCh: make(chan dataPack, 1), - stateCh: make(chan dataPack, 1), - blockWakeCh: make(chan bool, 1), - bodyWakeCh: make(chan bool, 1), - receiptWakeCh: make(chan bool, 1), - stateWakeCh: make(chan bool, 1), + mode: FullSync, + mux: mux, + queue: newQueue(stateDb), + peers: newPeerSet(), + hasHeader: hasHeader, + hasBlockAndState: hasBlockAndState, + getHeader: getHeader, + getBlock: getBlock, + headHeader: headHeader, + headBlock: headBlock, + headFastBlock: headFastBlock, + commitHeadBlock: commitHeadBlock, + getTd: getTd, + insertHeaders: insertHeaders, + insertBlocks: insertBlocks, + insertReceipts: insertReceipts, + rollback: rollback, + dropPeer: dropPeer, + newPeerCh: make(chan *peer, 1), + hashCh: make(chan dataPack, 1), + blockCh: make(chan dataPack, 1), + headerCh: make(chan dataPack, 1), + bodyCh: make(chan dataPack, 1), + receiptCh: make(chan dataPack, 1), + stateCh: make(chan dataPack, 1), + blockWakeCh: make(chan bool, 1), + bodyWakeCh: make(chan bool, 1), + receiptWakeCh: make(chan bool, 1), + stateWakeCh: make(chan bool, 1), } } @@ -564,7 +564,7 @@ func (d *Downloader) findAncestor61(p *peer) (uint64, error) { // Check if a common ancestor was found finished = true for i := len(hashes) - 1; i >= 0; i-- { - if d.hasBlock(hashes[i]) { + if d.hasBlockAndState(hashes[i]) { number, hash = uint64(from)+uint64(i), hashes[i] break } @@ -620,11 +620,11 @@ func (d *Downloader) findAncestor61(p *peer) (uint64, error) { arrived = true // Modify the search interval based on the response - block := d.getBlock(hashes[0]) - if block == nil { + if !d.hasBlockAndState(hashes[0]) { end = check break } + block := d.getBlock(hashes[0]) // this doesn't check state, hence the above explicit check if block.NumberU64() != check { glog.V(logger.Debug).Infof("%v: non requested hash #%d [%x…], instead of #%d", p, block.NumberU64(), block.Hash().Bytes()[:4], check) return 0, errBadPeer @@ -989,7 +989,7 @@ func (d *Downloader) findAncestor(p *peer) (uint64, error) { // Check if a common ancestor was found finished = true for i := len(headers) - 1; i >= 0; i-- { - if (d.mode != LightSync && d.hasBlock(headers[i].Hash())) || (d.mode == LightSync && d.hasHeader(headers[i].Hash())) { + if (d.mode != LightSync && d.hasBlockAndState(headers[i].Hash())) || (d.mode == LightSync && d.hasHeader(headers[i].Hash())) { number, hash = headers[i].Number.Uint64(), headers[i].Hash() break } @@ -1045,7 +1045,7 @@ func (d *Downloader) findAncestor(p *peer) (uint64, error) { arrived = true // Modify the search interval based on the response - if (d.mode == FullSync && !d.hasBlock(headers[0].Hash())) || (d.mode != FullSync && !d.hasHeader(headers[0].Hash())) { + if (d.mode == FullSync && !d.hasBlockAndState(headers[0].Hash())) || (d.mode != FullSync && !d.hasHeader(headers[0].Hash())) { end = check break } diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 347a0e3f0c..418243b208 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -153,6 +153,8 @@ func newTester() *downloadTester { peerChainTds: make(map[string]map[common.Hash]*big.Int), } tester.stateDb, _ = ethdb.NewMemDatabase() + tester.stateDb.Put(genesis.Root().Bytes(), []byte{0x00}) + tester.downloader = New(tester.stateDb, new(event.TypeMux), tester.hasHeader, tester.hasBlock, tester.getHeader, tester.getBlock, tester.headHeader, tester.headBlock, tester.headFastBlock, tester.commitHeadBlock, tester.getTd, tester.insertHeaders, tester.insertBlocks, tester.insertReceipts, tester.rollback, tester.dropPeer) @@ -180,9 +182,14 @@ func (dl *downloadTester) hasHeader(hash common.Hash) bool { return dl.getHeader(hash) != nil } -// hasBlock checks if a block is present in the testers canonical chain. +// hasBlock checks if a block and associated state is present in the testers canonical chain. func (dl *downloadTester) hasBlock(hash common.Hash) bool { - return dl.getBlock(hash) != nil + block := dl.getBlock(hash) + if block == nil { + return false + } + _, err := dl.stateDb.Get(block.Root().Bytes()) + return err == nil } // getHeader retrieves a header from the testers canonical chain. @@ -295,8 +302,10 @@ func (dl *downloadTester) insertBlocks(blocks types.Blocks) (int, error) { defer dl.lock.Unlock() for i, block := range blocks { - if _, ok := dl.ownBlocks[block.ParentHash()]; !ok { + if parent, ok := dl.ownBlocks[block.ParentHash()]; !ok { return i, errors.New("unknown parent") + } else if _, err := dl.stateDb.Get(parent.Root().Bytes()); err != nil { + return i, fmt.Errorf("unknown parent state %x: %v", parent.Root(), err) } if _, ok := dl.ownHeaders[block.Hash()]; !ok { dl.ownHashes = append(dl.ownHashes, block.Hash()) @@ -1103,6 +1112,8 @@ func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) { } // Tests that upon detecting an invalid header, the recent ones are rolled back +// for various failure scenarios. Afterwards a full sync is attempted to make +// sure no state was corrupted. func TestInvalidHeaderRollback63Fast(t *testing.T) { testInvalidHeaderRollback(t, 63, FastSync) } func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } func TestInvalidHeaderRollback64Light(t *testing.T) { testInvalidHeaderRollback(t, 64, LightSync) } diff --git a/eth/downloader/types.go b/eth/downloader/types.go index 5937be606b..b67fff1f8f 100644 --- a/eth/downloader/types.go +++ b/eth/downloader/types.go @@ -27,8 +27,8 @@ import ( // headerCheckFn is a callback type for verifying a header's presence in the local chain. type headerCheckFn func(common.Hash) bool -// blockCheckFn is a callback type for verifying a block's presence in the local chain. -type blockCheckFn func(common.Hash) bool +// blockAndStateCheckFn is a callback type for verifying block and associated states' presence in the local chain. +type blockAndStateCheckFn func(common.Hash) bool // headerRetrievalFn is a callback type for retrieving a header from the local chain. type headerRetrievalFn func(common.Hash) *types.Header diff --git a/eth/handler.go b/eth/handler.go index d04f79105f..108a6679c1 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -138,9 +138,10 @@ func NewProtocolManager(fastSync bool, networkId int, mux *event.TypeMux, txpool return nil, errIncompatibleConfig } // Construct the different synchronisation mechanisms - manager.downloader = downloader.New(chaindb, manager.eventMux, blockchain.HasHeader, blockchain.HasBlock, blockchain.GetHeader, blockchain.GetBlock, - blockchain.CurrentHeader, blockchain.CurrentBlock, blockchain.CurrentFastBlock, blockchain.FastSyncCommitHead, blockchain.GetTd, - blockchain.InsertHeaderChain, blockchain.InsertChain, blockchain.InsertReceiptChain, blockchain.Rollback, manager.removePeer) + manager.downloader = downloader.New(chaindb, manager.eventMux, blockchain.HasHeader, blockchain.HasBlockAndState, blockchain.GetHeader, + blockchain.GetBlock, blockchain.CurrentHeader, blockchain.CurrentBlock, blockchain.CurrentFastBlock, blockchain.FastSyncCommitHead, + blockchain.GetTd, blockchain.InsertHeaderChain, blockchain.InsertChain, blockchain.InsertReceiptChain, blockchain.Rollback, + manager.removePeer) validator := func(block *types.Block, parent *types.Block) error { return core.ValidateHeader(pow, block.Header(), parent.Header(), true, false) From 649787a9bf76fbf4b28336d4c1f06922e44e6d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 30 Dec 2015 18:31:37 +0200 Subject: [PATCH 46/78] core: fix transaction reorg issues within the tx pool --- core/transaction_pool.go | 119 +++++++++------ core/transaction_pool_test.go | 269 +++++++++++++++++++++++++++++++++- 2 files changed, 344 insertions(+), 44 deletions(-) diff --git a/core/transaction_pool.go b/core/transaction_pool.go index 7dcc2aac26..abd5bb7b79 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -140,7 +140,6 @@ func (pool *TxPool) resetState() { } } } - // Check the queue and move transactions over to the pending if possible // or remove those that have become invalid pool.checkQueue() @@ -301,17 +300,15 @@ func (pool *TxPool) addTx(hash common.Hash, addr common.Address, tx *types.Trans } // Add queues a single transaction in the pool if it is valid. -func (self *TxPool) Add(tx *types.Transaction) (err error) { +func (self *TxPool) Add(tx *types.Transaction) error { self.mu.Lock() defer self.mu.Unlock() - err = self.add(tx) - if err == nil { - // check and validate the queueue - self.checkQueue() + if err := self.add(tx); err != nil { + return err } - - return + self.checkQueue() + return nil } // AddTransactions attempts to queue all valid transactions in txs. @@ -417,51 +414,55 @@ func (pool *TxPool) checkQueue() { pool.resetState() } - var addq txQueue + var promote txQueue for address, txs := range pool.queue { - // guessed nonce is the nonce currently kept by the tx pool (pending state) - guessedNonce := pool.pendingState.GetNonce(address) - // true nonce is the nonce known by the last state currentState, err := pool.currentState() if err != nil { glog.Errorf("could not get current state: %v", err) return } - trueNonce := currentState.GetNonce(address) - addq := addq[:0] + balance := currentState.GetBalance(address) + + var ( + guessedNonce = pool.pendingState.GetNonce(address) // nonce currently kept by the tx pool (pending state) + trueNonce = currentState.GetNonce(address) // nonce known by the last state + ) + promote = promote[:0] for hash, tx := range txs { - if tx.Nonce() < trueNonce { - // Drop queued transactions whose nonce is lower than - // the account nonce because they have been processed. + // Drop processed or out of fund transactions + if tx.Nonce() < trueNonce || balance.Cmp(tx.Cost()) < 0 { + if glog.V(logger.Core) { + glog.Infof("removed tx (%v) from pool queue: low tx nonce or out of funds\n", tx) + } delete(txs, hash) - } else { - // Collect the remaining transactions for the next pass. - addq = append(addq, txQueueEntry{hash, address, tx}) - } - } - // Find the next consecutive nonce range starting at the - // current account nonce. - sort.Sort(addq) - for i, e := range addq { - // start deleting the transactions from the queue if they exceed the limit - if i > maxQueued { - delete(pool.queue[address], e.hash) continue } - - if e.Nonce() > guessedNonce { - if len(addq)-i > maxQueued { + // Collect the remaining transactions for the next pass. + promote = append(promote, txQueueEntry{hash, address, tx}) + } + // Find the next consecutive nonce range starting at the current account nonce, + // pushing the guessed nonce forward if we add consecutive transactions. + sort.Sort(promote) + for i, entry := range promote { + // If we reached a gap in the nonces, enforce transaction limit and stop + if entry.Nonce() > guessedNonce { + if len(promote)-i > maxQueued { if glog.V(logger.Debug) { - glog.Infof("Queued tx limit exceeded for %s. Tx %s removed\n", common.PP(address[:]), common.PP(e.hash[:])) + glog.Infof("Queued tx limit exceeded for %s. Tx %s removed\n", common.PP(address[:]), common.PP(entry.hash[:])) } - for j := i + maxQueued; j < len(addq); j++ { - delete(txs, addq[j].hash) + for _, drop := range promote[i+maxQueued:] { + delete(txs, drop.hash) } } break } - delete(txs, e.hash) - pool.addTx(e.hash, address, e.Transaction) + // Otherwise promote the transaction and move the guess nonce if needed + pool.addTx(entry.hash, address, entry.Transaction) + delete(txs, entry.hash) + + if entry.Nonce() == guessedNonce { + guessedNonce++ + } } // Delete the entire queue entry if it became empty. if len(txs) == 0 { @@ -471,20 +472,56 @@ func (pool *TxPool) checkQueue() { } // validatePool removes invalid and processed transactions from the main pool. +// If a transaction is removed for being invalid (e.g. out of funds), all sub- +// sequent (Still valid) transactions are moved back into the future queue. This +// is important to prevent a drained account from DOSing the network with non +// executable transactions. func (pool *TxPool) validatePool() { state, err := pool.currentState() if err != nil { glog.V(logger.Info).Infoln("failed to get current state: %v", err) return } + balanceCache := make(map[common.Address]*big.Int) + + // Clean up the pending pool, accumulating invalid nonces + gaps := make(map[common.Address]uint64) + for hash, tx := range pool.pending { - from, _ := tx.From() // err already checked - // perform light nonce validation - if state.GetNonce(from) > tx.Nonce() { + sender, _ := tx.From() // err already checked + + // Perform light nonce and balance validation + balance := balanceCache[sender] + if balance == nil { + balance = state.GetBalance(sender) + balanceCache[sender] = balance + } + if past := state.GetNonce(sender) > tx.Nonce(); past || balance.Cmp(tx.Cost()) < 0 { + // Remove an already past it invalidated transaction if glog.V(logger.Core) { - glog.Infof("removed tx (%x) from pool: low tx nonce\n", hash[:4]) + glog.Infof("removed tx (%v) from pool: low tx nonce or out of funds\n", tx) } delete(pool.pending, hash) + + // Track the smallest invalid nonce to postpone subsequent transactions + if !past { + if prev, ok := gaps[sender]; !ok || tx.Nonce() < prev { + gaps[sender] = tx.Nonce() + } + } + } + } + // Move all transactions after a gap back to the future queue + if len(gaps) > 0 { + for hash, tx := range pool.pending { + sender, _ := tx.From() + if gap, ok := gaps[sender]; ok && tx.Nonce() >= gap { + if glog.V(logger.Core) { + glog.Infof("postponed tx (%v) due to introduced gap\n", tx) + } + pool.queueTx(hash, tx) + delete(pool.pending, hash) + } } } } diff --git a/core/transaction_pool_test.go b/core/transaction_pool_test.go index a311bdd660..811e401119 100644 --- a/core/transaction_pool_test.go +++ b/core/transaction_pool_test.go @@ -90,7 +90,7 @@ func TestTransactionQueue(t *testing.T) { tx := transaction(0, big.NewInt(100), key) from, _ := tx.From() currentState, _ := pool.currentState() - currentState.AddBalance(from, big.NewInt(1)) + currentState.AddBalance(from, big.NewInt(1000)) pool.queueTx(tx.Hash(), tx) pool.checkQueue() @@ -115,15 +115,17 @@ func TestTransactionQueue(t *testing.T) { tx1 := transaction(0, big.NewInt(100), key) tx2 := transaction(10, big.NewInt(100), key) tx3 := transaction(11, big.NewInt(100), key) + from, _ = tx1.From() + currentState, _ = pool.currentState() + currentState.AddBalance(from, big.NewInt(1000)) pool.queueTx(tx1.Hash(), tx1) pool.queueTx(tx2.Hash(), tx2) pool.queueTx(tx3.Hash(), tx3) - from, _ = tx1.From() pool.checkQueue() if len(pool.pending) != 1 { - t.Error("expected tx pool to be 1 =") + t.Error("expected tx pool to be 1, got", len(pool.pending)) } if len(pool.queue[from]) != 2 { t.Error("expected len(queue) == 2, got", len(pool.queue[from])) @@ -272,3 +274,264 @@ func TestRemovedTxEvent(t *testing.T) { t.Error("expected 1 pending tx, got", len(pool.pending)) } } + +// Tests that if an account runs out of funds, any pending and queued transactions +// are dropped. +func TestTransactionDropping(t *testing.T) { + // Create a test account and fund it + pool, key := setupTxPool() + account, _ := transaction(0, big.NewInt(0), key).From() + + state, _ := pool.currentState() + state.AddBalance(account, big.NewInt(1000)) + + // Add some pending and some queued transactions + var ( + tx0 = transaction(0, big.NewInt(100), key) + tx1 = transaction(1, big.NewInt(200), key) + tx10 = transaction(10, big.NewInt(100), key) + tx11 = transaction(11, big.NewInt(200), key) + ) + pool.addTx(tx0.Hash(), account, tx0) + pool.addTx(tx1.Hash(), account, tx1) + pool.queueTx(tx10.Hash(), tx10) + pool.queueTx(tx11.Hash(), tx11) + + // Check that pre and post validations leave the pool as is + if len(pool.pending) != 2 { + t.Errorf("pending transaction mismatch: have %d, want %d", len(pool.pending), 2) + } + if len(pool.queue[account]) != 2 { + t.Errorf("queued transaction mismatch: have %d, want %d", len(pool.queue), 2) + } + pool.resetState() + if len(pool.pending) != 2 { + t.Errorf("pending transaction mismatch: have %d, want %d", len(pool.pending), 2) + } + if len(pool.queue[account]) != 2 { + t.Errorf("queued transaction mismatch: have %d, want %d", len(pool.queue), 2) + } + // Reduce the balance of the account, and check that invalidated transactions are dropped + state.AddBalance(account, big.NewInt(-750)) + pool.resetState() + + if _, ok := pool.pending[tx0.Hash()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx0) + } + if _, ok := pool.pending[tx1.Hash()]; ok { + t.Errorf("out-of-fund pending transaction present: %v", tx1) + } + if _, ok := pool.queue[account][tx10.Hash()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx10) + } + if _, ok := pool.queue[account][tx11.Hash()]; ok { + t.Errorf("out-of-fund queued transaction present: %v", tx11) + } +} + +// Tests that if a transaction is dropped from the current pending pool (e.g. out +// of fund), all consecutive (still valid, but not executable) transactions are +// postponed back into the future queue to prevent broadcating them. +func TestTransactionPostponing(t *testing.T) { + // Create a test account and fund it + pool, key := setupTxPool() + account, _ := transaction(0, big.NewInt(0), key).From() + + state, _ := pool.currentState() + state.AddBalance(account, big.NewInt(1000)) + + // Add a batch consecutive pending transactions for validation + txns := []*types.Transaction{} + for i := 0; i < 100; i++ { + var tx *types.Transaction + if i%2 == 0 { + tx = transaction(uint64(i), big.NewInt(100), key) + } else { + tx = transaction(uint64(i), big.NewInt(500), key) + } + pool.addTx(tx.Hash(), account, tx) + txns = append(txns, tx) + } + // Check that pre and post validations leave the pool as is + if len(pool.pending) != len(txns) { + t.Errorf("pending transaction mismatch: have %d, want %d", len(pool.pending), len(txns)) + } + if len(pool.queue[account]) != 0 { + t.Errorf("queued transaction mismatch: have %d, want %d", len(pool.queue), 0) + } + pool.resetState() + if len(pool.pending) != len(txns) { + t.Errorf("pending transaction mismatch: have %d, want %d", len(pool.pending), len(txns)) + } + if len(pool.queue[account]) != 0 { + t.Errorf("queued transaction mismatch: have %d, want %d", len(pool.queue), 0) + } + // Reduce the balance of the account, and check that transactions are reorganized + state.AddBalance(account, big.NewInt(-750)) + pool.resetState() + + if _, ok := pool.pending[txns[0].Hash()]; !ok { + t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txns[0]) + } + if _, ok := pool.queue[account][txns[0].Hash()]; ok { + t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txns[0]) + } + for i, tx := range txns[1:] { + if i%2 == 1 { + if _, ok := pool.pending[tx.Hash()]; ok { + t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx) + } + if _, ok := pool.queue[account][tx.Hash()]; !ok { + t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx) + } + } else { + if _, ok := pool.pending[tx.Hash()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx) + } + if _, ok := pool.queue[account][tx.Hash()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx) + } + } + } +} + +// Tests that if the transaction count belonging to a single account goes above +// some threshold, the higher transactions are dropped to prevent DOS attacks. +func TestTransactionQueueLimiting(t *testing.T) { + // Create a test account and fund it + pool, key := setupTxPool() + account, _ := transaction(0, big.NewInt(0), key).From() + + state, _ := pool.currentState() + state.AddBalance(account, big.NewInt(1000000)) + + // Keep queuing up transactions and make sure all above a limit are dropped + for i := uint64(1); i <= maxQueued+5; i++ { + if err := pool.Add(transaction(i, big.NewInt(100000), key)); err != nil { + t.Fatalf("tx %d: failed to add transaction: %v", i, err) + } + if len(pool.pending) != 0 { + t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0) + } + if i <= maxQueued { + if len(pool.queue[account]) != int(i) { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, len(pool.queue[account]), i) + } + } else { + if len(pool.queue[account]) != maxQueued { + t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, len(pool.queue[account]), maxQueued) + } + } + } +} + +// Tests that even if the transaction count belonging to a single account goes +// above some threshold, as long as the transactions are executable, they are +// accepted. +func TestTransactionPendingLimiting(t *testing.T) { + // Create a test account and fund it + pool, key := setupTxPool() + account, _ := transaction(0, big.NewInt(0), key).From() + + state, _ := pool.currentState() + state.AddBalance(account, big.NewInt(1000000)) + + // Keep queuing up transactions and make sure all above a limit are dropped + for i := uint64(0); i < maxQueued+5; i++ { + if err := pool.Add(transaction(i, big.NewInt(100000), key)); err != nil { + t.Fatalf("tx %d: failed to add transaction: %v", i, err) + } + if len(pool.pending) != int(i)+1 { + t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), i+1) + } + if len(pool.queue[account]) != 0 { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, len(pool.queue[account]), 0) + } + } +} + +// Tests that the transaction limits are enforced the same way irrelevant whether +// the transactions are added one by one or in batches. +func TestTransactionQueueLimitingEquivalency(t *testing.T) { testTransactionLimitingEquivalency(t, 1) } +func TestTransactionPendingLimitingEquivalency(t *testing.T) { testTransactionLimitingEquivalency(t, 0) } + +func testTransactionLimitingEquivalency(t *testing.T, origin uint64) { + // Add a batch of transactions to a pool one by one + pool1, key1 := setupTxPool() + account1, _ := transaction(0, big.NewInt(0), key1).From() + state1, _ := pool1.currentState() + state1.AddBalance(account1, big.NewInt(1000000)) + + for i := uint64(0); i < maxQueued+5; i++ { + if err := pool1.Add(transaction(origin+i, big.NewInt(100000), key1)); err != nil { + t.Fatalf("tx %d: failed to add transaction: %v", i, err) + } + } + // Add a batch of transactions to a pool in one bit batch + pool2, key2 := setupTxPool() + account2, _ := transaction(0, big.NewInt(0), key2).From() + state2, _ := pool2.currentState() + state2.AddBalance(account2, big.NewInt(1000000)) + + txns := []*types.Transaction{} + for i := uint64(0); i < maxQueued+5; i++ { + txns = append(txns, transaction(origin+i, big.NewInt(100000), key2)) + } + pool2.AddTransactions(txns) + + // Ensure the batch optimization honors the same pool mechanics + if len(pool1.pending) != len(pool2.pending) { + t.Errorf("pending transaction count mismatch: one-by-one algo: %d, batch algo: %d", len(pool1.pending), len(pool2.pending)) + } + if len(pool1.queue[account1]) != len(pool2.queue[account2]) { + t.Errorf("queued transaction count mismatch: one-by-one algo: %d, batch algo: %d", len(pool1.queue[account1]), len(pool2.queue[account2])) + } +} + +// Benchmarks the speed of validating the contents of the pending queue of the +// transaction pool. +func BenchmarkValidatePool100(b *testing.B) { benchmarkValidatePool(b, 100) } +func BenchmarkValidatePool1000(b *testing.B) { benchmarkValidatePool(b, 1000) } +func BenchmarkValidatePool10000(b *testing.B) { benchmarkValidatePool(b, 10000) } + +func benchmarkValidatePool(b *testing.B, size int) { + // Add a batch of transactions to a pool one by one + pool, key := setupTxPool() + account, _ := transaction(0, big.NewInt(0), key).From() + state, _ := pool.currentState() + state.AddBalance(account, big.NewInt(1000000)) + + for i := 0; i < size; i++ { + tx := transaction(uint64(i), big.NewInt(100000), key) + pool.addTx(tx.Hash(), account, tx) + } + // Benchmark the speed of pool validation + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.validatePool() + } +} + +// Benchmarks the speed of scheduling the contents of the future queue of the +// transaction pool. +func BenchmarkCheckQueue100(b *testing.B) { benchmarkCheckQueue(b, 100) } +func BenchmarkCheckQueue1000(b *testing.B) { benchmarkCheckQueue(b, 1000) } +func BenchmarkCheckQueue10000(b *testing.B) { benchmarkCheckQueue(b, 10000) } + +func benchmarkCheckQueue(b *testing.B, size int) { + // Add a batch of transactions to a pool one by one + pool, key := setupTxPool() + account, _ := transaction(0, big.NewInt(0), key).From() + state, _ := pool.currentState() + state.AddBalance(account, big.NewInt(1000000)) + + for i := 0; i < size; i++ { + tx := transaction(uint64(1+i), big.NewInt(100000), key) + pool.queueTx(tx.Hash(), tx) + } + // Benchmark the speed of pool validation + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.checkQueue() + } +} From e2fdd335415d39083ef9ff5950dd4050f25b193a Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Fri, 8 Jan 2016 12:35:57 +0100 Subject: [PATCH 47/78] common: Fix HomeDir detection I am working on porting geth to [Ubuntu Core](https://developer.ubuntu.com/en/snappy/https://developer.ubuntu.com/en/snappy/). I am testing geth on a Raspberry PI and for Ubuntu Core the $HOME directory is unique for each application. See [here](https://developer.ubuntu.com/en/snappy/guides/filesystem-layout) for more information of their filesystem layout. For some reason in Go `usr.HomeDir` returns a different value than `$HOME` in Ubuntu Core. Adding this at the end of `HomeDir()` ```go fmt.Printf("at HomeDir, user.HomeDir = %s and $HOME is %s\n", usr.HomeDir, os.Getenv("HOME")) ``` gives the following output ``` at HomeDir, user.HomeDir = /home/ubuntu and $HOME is /home/ubuntu/apps/geth.sideload/IJcODREBYbHO ``` With this commit, I propose giving precedence to the `$HOME` environment variable as is also suggested by the [homedir](https://github.com/mitchellh/go-homedir/blob/master/homedir.go) project. --- common/path.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/common/path.go b/common/path.go index 39eacaceeb..9ba2f93c0c 100644 --- a/common/path.go +++ b/common/path.go @@ -63,13 +63,14 @@ func AbsolutePath(Datadir string, filename string) string { return filepath.Join(Datadir, filename) } -func HomeDir() (home string) { - if usr, err := user.Current(); err == nil { - home = usr.HomeDir - } else { - home = os.Getenv("HOME") +func HomeDir() string { + if home := os.Getenv("HOME"); home != "" { + return home } - return + if usr, err := user.Current(); err == nil { + return usr.HomeDir + } + return "" } func DefaultDataDir() string { From e5b480b6387231e0aa1866a9a1c118b88eae619c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 11 Jan 2016 16:53:06 +0200 Subject: [PATCH 48/78] core: fix invalid state reuse in chain maker based tests --- core/chain_makers.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/core/chain_makers.go b/core/chain_makers.go index 5a8f380a36..c62618e6cb 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -91,15 +91,10 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { b.SetCoinbase(common.Address{}) } b.statedb.StartRecord(tx.Hash(), common.Hash{}, len(b.txs)) - _, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.gasPool) + receipt, _, _, err := ApplyTransaction(nil, b.gasPool, b.statedb, b.header, tx, b.header.GasUsed) if err != nil { panic(err) } - root := b.statedb.IntermediateRoot() - b.header.GasUsed.Add(b.header.GasUsed, gas) - receipt := types.NewReceipt(root.Bytes(), b.header.GasUsed) - receipt.Logs = b.statedb.GetLogs(tx.Hash()) - receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) b.txs = append(b.txs, tx) b.receipts = append(b.receipts, receipt) } @@ -169,12 +164,8 @@ func (b *BlockGen) OffsetTime(seconds int64) { // values. Inserting them into BlockChain requires use of FakePow or // a similar non-validating proof of work implementation. func GenerateChain(parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) { - statedb, err := state.New(parent.Root(), db) - if err != nil { - panic(err) - } blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n) - genblock := func(i int, h *types.Header) (*types.Block, types.Receipts) { + genblock := func(i int, h *types.Header, statedb *state.StateDB) (*types.Block, types.Receipts) { b := &BlockGen{parent: parent, i: i, chain: blocks, header: h, statedb: statedb} if gen != nil { gen(i, b) @@ -188,8 +179,12 @@ func GenerateChain(parent *types.Block, db ethdb.Database, n int, gen func(int, return types.NewBlock(h, b.txs, b.uncles, b.receipts), b.receipts } for i := 0; i < n; i++ { + statedb, err := state.New(parent.Root(), db) + if err != nil { + panic(err) + } header := makeHeader(parent, statedb) - block, receipt := genblock(i, header) + block, receipt := genblock(i, header, statedb) blocks[i] = block receipts[i] = receipt parent = block From 752c75fb21ad151204f2aadeee1fff597e013aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 11 Jan 2016 18:20:31 +0200 Subject: [PATCH 49/78] core/vm: resolve circular dependency to debug vm storage --- core/vm/contract.go | 7 +++++++ core/vm/environment.go | 1 + core/vm/jit_test.go | 17 +++++++++-------- core/vm/vm.go | 9 +++------ 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/core/vm/contract.go b/core/vm/contract.go index 95417e7471..5981dcca0d 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -27,6 +27,7 @@ type ContractRef interface { ReturnGas(*big.Int, *big.Int) Address() common.Address SetCode([]byte) + EachStorage(cb func(key, value []byte)) } // Contract represents an ethereum contract in the state database. It contains @@ -124,3 +125,9 @@ func (self *Contract) SetCallCode(addr *common.Address, code []byte) { self.Code = code self.CodeAddr = addr } + +// EachStorage iterates the contract's storage and calls a method for every key +// value pair. +func (self *Contract) EachStorage(cb func(key, value []byte)) { + self.caller.EachStorage(cb) +} diff --git a/core/vm/environment.go b/core/vm/environment.go index 299d126747..4fee583bfb 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -121,4 +121,5 @@ type Account interface { Address() common.Address ReturnGas(*big.Int, *big.Int) SetCode([]byte) + EachStorage(cb func(key, value []byte)) } diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go index aa97e51844..8c50ed0f5c 100644 --- a/core/vm/jit_test.go +++ b/core/vm/jit_test.go @@ -125,14 +125,15 @@ type vmBench struct { type account struct{} -func (account) SubBalance(amount *big.Int) {} -func (account) AddBalance(amount *big.Int) {} -func (account) SetBalance(*big.Int) {} -func (account) SetNonce(uint64) {} -func (account) Balance() *big.Int { return nil } -func (account) Address() common.Address { return common.Address{} } -func (account) ReturnGas(*big.Int, *big.Int) {} -func (account) SetCode([]byte) {} +func (account) SubBalance(amount *big.Int) {} +func (account) AddBalance(amount *big.Int) {} +func (account) SetBalance(*big.Int) {} +func (account) SetNonce(uint64) {} +func (account) Balance() *big.Int { return nil } +func (account) Address() common.Address { return common.Address{} } +func (account) ReturnGas(*big.Int, *big.Int) {} +func (account) SetCode([]byte) {} +func (account) EachStorage(cb func(key, value []byte)) {} func runVmBench(test vmBench, b *testing.B) { var sender account diff --git a/core/vm/vm.go b/core/vm/vm.go index 4b03e55f04..8e07aaa899 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -376,12 +376,9 @@ func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, st stck[i] = new(big.Int).Set(item) } storage := make(map[common.Hash][]byte) - /* - object := contract.self.(*state.StateObject) - object.EachStorage(func(k, v []byte) { - storage[common.BytesToHash(k)] = v - }) - */ + contract.self.EachStorage(func(k, v []byte) { + storage[common.BytesToHash(k)] = v + }) self.env.AddStructLog(StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, err}) } } From 2d5d6d9d0e4dbcd03d563bdc67fa31514bdafdac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 13 Jan 2016 12:04:03 +0200 Subject: [PATCH 50/78] core/state: always commit in batches, just finish if not needed later --- core/state/statedb.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 4133210577..8093472b59 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -353,7 +353,8 @@ func (s *StateDB) IntermediateRoot() common.Hash { // Commit commits all state changes to the database. func (s *StateDB) Commit() (root common.Hash, err error) { - return s.commit(s.db) + root, batch := s.CommitBatch() + return root, batch.Write() } // CommitBatch commits all state changes to a write batch but does not From 6e7620afe0e2b75e917c0b7c1bccd7d31a8341b9 Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Mon, 18 Jan 2016 13:28:03 +0100 Subject: [PATCH 51/78] eth: changed NodeInfo Genesis and Head types --- eth/handler.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index 108a6679c1..e8bac23c32 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -750,10 +750,10 @@ func (self *ProtocolManager) txBroadcastLoop() { // EthNodeInfo represents a short summary of the Ethereum sub-protocol metadata known // about the host peer. type EthNodeInfo struct { - Network int `json:"network"` // Ethereum network ID (0=Olympic, 1=Frontier, 2=Morden) - Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain - Genesis string `json:"genesis"` // SHA3 hash of the host's genesis block - Head string `json:"head"` // SHA3 hash of the host's best owned block + Network int `json:"network"` // Ethereum network ID (0=Olympic, 1=Frontier, 2=Morden) + Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain + Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block + Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block } // NodeInfo retrieves some protocol metadata about the running host node. @@ -761,7 +761,7 @@ func (self *ProtocolManager) NodeInfo() *EthNodeInfo { return &EthNodeInfo{ Network: self.networkId, Difficulty: self.blockchain.GetTd(self.blockchain.CurrentBlock().Hash()), - Genesis: fmt.Sprintf("%x", self.blockchain.Genesis().Hash()), - Head: fmt.Sprintf("%x", self.blockchain.CurrentBlock().Hash()), + Genesis: self.blockchain.Genesis().Hash(), + Head: self.blockchain.CurrentBlock().Hash(), } } From cdd34fcb166cdb90ee411d029e6851325e80c8d3 Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Tue, 19 Jan 2016 09:26:17 +0100 Subject: [PATCH 52/78] console: add admin.sleep and admin.sleepBlocks --- cmd/geth/js.go | 9 ++++++ rpc/jeth.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 9329eaa0e7..cdafab7fa1 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -348,6 +348,15 @@ func (js *jsre) apiBindings(f xeth.Frontend) error { persObj.Set("newAccount", jeth.NewAccount) } + // The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer. + // Bind these if the admin module is available. + if a, err := js.re.Get("admin"); err == nil { + if adminObj := a.Object(); adminObj != nil { + adminObj.Set("sleepBlocks", jeth.SleepBlocks) + adminObj.Set("sleep", jeth.Sleep) + } + } + return nil } diff --git a/rpc/jeth.go b/rpc/jeth.go index de7dd1e762..b195a49653 100644 --- a/rpc/jeth.go +++ b/rpc/jeth.go @@ -19,6 +19,7 @@ package rpc import ( "encoding/json" "fmt" + "time" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/jsre" @@ -247,3 +248,77 @@ func (self *Jeth) confirmTransaction(id interface{}, jsonrpc string, args []inte // Accept all tx which are send from this console return self.client.Send(shared.NewRpcResponse(id, jsonrpc, true, nil)) == nil } + +// throwJSExeception panics on an otto value, the Otto VM will then throw msg as a javascript error. +func throwJSExeception(msg interface{}) otto.Value { + p, _ := otto.ToValue(msg) + panic(p) + return p +} + +// Sleep will halt the console for arg[0] seconds. +func (self *Jeth) Sleep(call otto.FunctionCall) (response otto.Value) { + if len(call.ArgumentList) >= 1 { + if call.Argument(0).IsNumber() { + sleep, _ := call.Argument(0).ToInteger() + time.Sleep(time.Duration(sleep) * time.Second) + return otto.TrueValue() + } + } + return throwJSExeception("usage: sleep()") +} + +// SleepBlocks will wait for a specified number of new blocks or max for a +// given of seconds. sleepBlocks(nBlocks[, maxSleep]). +func (self *Jeth) SleepBlocks(call otto.FunctionCall) (response otto.Value) { + nBlocks := int64(0) + maxSleep := int64(9999999999999999) // indefinitely + + nArgs := len(call.ArgumentList) + + if nArgs == 0 { + throwJSExeception("usage: sleepBlocks([, max sleep in seconds])") + } + + if nArgs >= 1 { + if call.Argument(0).IsNumber() { + nBlocks, _ = call.Argument(0).ToInteger() + } else { + throwJSExeception("expected number as first argument") + } + } + + if nArgs >= 2 { + if call.Argument(1).IsNumber() { + maxSleep, _ = call.Argument(1).ToInteger() + } else { + throwJSExeception("expected number as second argument") + } + } + + // go through the console, this will allow web3 to call the appropriate + // callbacks if a delayed response or notification is received. + currentBlockNr := func() int64 { + result, err := call.Otto.Run("eth.blockNumber") + if err != nil { + throwJSExeception(err.Error()) + } + blockNr, err := result.ToInteger() + if err != nil { + throwJSExeception(err.Error()) + } + return blockNr + } + + targetBlockNr := currentBlockNr() + nBlocks + deadline := time.Now().Add(time.Duration(maxSleep) * time.Second) + + for time.Now().Before(deadline) { + if currentBlockNr() >= targetBlockNr { + return otto.TrueValue() + } + time.Sleep(time.Second) + } + + return otto.FalseValue() +} From 886478b18b73bbe8421531f1a71664a2bc0f5eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 20 Jan 2016 12:09:24 +0200 Subject: [PATCH 53/78] core/state, trie: don't leak database writes before commit --- core/state/statedb.go | 10 +++++--- core/state/statedb_test.go | 52 ++++++++++++++++++++++++++++++++++++++ trie/secure_trie.go | 49 +++++++++++++++++++++++++++++++---- 3 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 core/state/statedb_test.go diff --git a/core/state/statedb.go b/core/state/statedb.go index 8093472b59..22ffa36a06 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -206,9 +206,6 @@ func (self *StateDB) Delete(addr common.Address) bool { // Update the given state object and apply it to state trie func (self *StateDB) UpdateStateObject(stateObject *StateObject) { - if len(stateObject.code) > 0 { - self.db.Put(stateObject.codeHash, stateObject.code) - } addr := stateObject.Address() data, err := rlp.EncodeToBytes(stateObject) if err != nil { @@ -375,8 +372,15 @@ func (s *StateDB) commit(db trie.DatabaseWriter) (common.Hash, error) { // and just mark it for deletion in the trie. s.DeleteStateObject(stateObject) } else { + // Write any contract code associated with the state object + if len(stateObject.code) > 0 { + if err := db.Put(stateObject.codeHash, stateObject.code); err != nil { + return common.Hash{}, err + } + } // Write any storage changes in the state object to its trie. stateObject.Update() + // Commit the trie of the object to the batch. // This updates the trie root internally, so // getting the root hash of the storage trie diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go new file mode 100644 index 0000000000..fd4d02fdd3 --- /dev/null +++ b/core/state/statedb_test.go @@ -0,0 +1,52 @@ +// Copyright 2015 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 . + +package state + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +// Tests that updating a state trie does not leak any database writes prior to +// actually committing the state. +func TestUpdateLeaks(t *testing.T) { + // Create an empty state database + db, _ := ethdb.NewMemDatabase() + state, _ := New(common.Hash{}, db) + + // Update it with some accounts + for i := byte(0); i < 255; i++ { + obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.AddBalance(big.NewInt(int64(11 * i))) + obj.SetNonce(uint64(42 * i)) + if i%2 == 0 { + obj.SetState(common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) + } + if i%3 == 0 { + obj.SetCode([]byte{i, i, i, i, i}) + } + state.UpdateStateObject(obj) + } + // Ensure that no data was leaked into the database + for _, key := range db.Keys() { + value, _ := db.Get(key) + t.Errorf("State leaked into database: %x -> %x", key, value) + } +} diff --git a/trie/secure_trie.go b/trie/secure_trie.go index caeef3c3a3..ca515aacbb 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -40,9 +40,10 @@ var secureKeyPrefix = []byte("secure-key-") type SecureTrie struct { *Trie - hash hash.Hash - secKeyBuf []byte - hashKeyBuf []byte + hash hash.Hash + hashKeyBuf []byte + secKeyBuf []byte + secKeyCache map[string][]byte } // NewSecure creates a trie with an existing root node from db. @@ -59,7 +60,10 @@ func NewSecure(root common.Hash, db Database) (*SecureTrie, error) { if err != nil { return nil, err } - return &SecureTrie{Trie: trie}, nil + return &SecureTrie{ + Trie: trie, + secKeyCache: make(map[string][]byte), + }, nil } // Get returns the value for key stored in the trie. @@ -105,7 +109,7 @@ func (t *SecureTrie) TryUpdate(key, value []byte) error { if err != nil { return err } - t.Trie.db.Put(t.secKey(hk), key) + t.secKeyCache[string(hk)] = key return nil } @@ -125,10 +129,45 @@ func (t *SecureTrie) TryDelete(key []byte) error { // GetKey returns the sha3 preimage of a hashed key that was // previously used to store a value. func (t *SecureTrie) GetKey(shaKey []byte) []byte { + if key, ok := t.secKeyCache[string(shaKey)]; ok { + return key + } key, _ := t.Trie.db.Get(t.secKey(shaKey)) return key } +// Commit writes all nodes and the secure hash pre-images to the trie's database. +// Nodes are stored with their sha3 hash as the key. +// +// Committing flushes nodes from memory. Subsequent Get calls will load nodes +// from the database. +func (t *SecureTrie) Commit() (root common.Hash, err error) { + return t.CommitTo(t.db) +} + +// CommitTo writes all nodes and the secure hash pre-images to the given database. +// Nodes are stored with their sha3 hash as the key. +// +// Committing flushes nodes from memory. Subsequent Get calls will load nodes from +// the trie's database. Calling code must ensure that the changes made to db are +// written back to the trie's attached database before using the trie. +func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { + if len(t.secKeyCache) > 0 { + for hk, key := range t.secKeyCache { + if err := db.Put(t.secKey([]byte(hk)), key); err != nil { + return common.Hash{}, err + } + } + t.secKeyCache = make(map[string][]byte) + } + n, err := t.hashRoot(db) + if err != nil { + return (common.Hash{}), err + } + t.root = n + return common.BytesToHash(n.(hashNode)), nil +} + func (t *SecureTrie) secKey(key []byte) []byte { t.secKeyBuf = append(t.secKeyBuf[:0], secureKeyPrefix...) t.secKeyBuf = append(t.secKeyBuf, key...) From f3d4ce0d164f7b17a143304e2b94421573d596a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 20 Jan 2016 16:06:28 +0200 Subject: [PATCH 54/78] core/state, ethdb, trie: test intermediate secure key leak, fix memdb bug --- core/state/statedb_test.go | 68 ++++++++++++++++++++++++++++++++++++++ ethdb/memory_database.go | 2 +- trie/secure_trie.go | 6 ++-- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index fd4d02fdd3..8138f8d783 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -50,3 +50,71 @@ func TestUpdateLeaks(t *testing.T) { t.Errorf("State leaked into database: %x -> %x", key, value) } } + +// Tests that no intermediate state of an object is stored into the database, +// only the one right before the commit. +func TestIntermediateLeaks(t *testing.T) { + // Create two state databases, one transitioning to the final state, the other final from the beginning + transDb, _ := ethdb.NewMemDatabase() + finalDb, _ := ethdb.NewMemDatabase() + transState, _ := New(common.Hash{}, transDb) + finalState, _ := New(common.Hash{}, finalDb) + + // Update the states with some objects + for i := byte(0); i < 255; i++ { + // Create a new state object with some data into the transition database + obj := transState.GetOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.SetBalance(big.NewInt(int64(11 * i))) + obj.SetNonce(uint64(42 * i)) + if i%2 == 0 { + obj.SetState(common.BytesToHash([]byte{i, i, i, 0}), common.BytesToHash([]byte{i, i, i, i, 0})) + } + if i%3 == 0 { + obj.SetCode([]byte{i, i, i, i, i, 0}) + } + transState.UpdateStateObject(obj) + + // Overwrite all the data with new values in the transition database + obj.SetBalance(big.NewInt(int64(11*i + 1))) + obj.SetNonce(uint64(42*i + 1)) + if i%2 == 0 { + obj.SetState(common.BytesToHash([]byte{i, i, i, 0}), common.Hash{}) + obj.SetState(common.BytesToHash([]byte{i, i, i, 1}), common.BytesToHash([]byte{i, i, i, i, 1})) + } + if i%3 == 0 { + obj.SetCode([]byte{i, i, i, i, i, 1}) + } + transState.UpdateStateObject(obj) + + // Create the final state object directly in the final database + obj = finalState.GetOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.SetBalance(big.NewInt(int64(11*i + 1))) + obj.SetNonce(uint64(42*i + 1)) + if i%2 == 0 { + obj.SetState(common.BytesToHash([]byte{i, i, i, 1}), common.BytesToHash([]byte{i, i, i, i, 1})) + } + if i%3 == 0 { + obj.SetCode([]byte{i, i, i, i, i, 1}) + } + finalState.UpdateStateObject(obj) + } + if _, err := transState.Commit(); err != nil { + t.Fatalf("failed to commit transition state: %v", err) + } + if _, err := finalState.Commit(); err != nil { + t.Fatalf("failed to commit final state: %v", err) + } + // Cross check the databases to ensure they are the same + for _, key := range finalDb.Keys() { + if _, err := transDb.Get(key); err != nil { + val, _ := finalDb.Get(key) + t.Errorf("entry missing from the transition database: %x -> %x", key, val) + } + } + for _, key := range transDb.Keys() { + if _, err := finalDb.Get(key); err != nil { + val, _ := transDb.Get(key) + t.Errorf("extra entry in the transition database: %x -> %x", key, val) + } + } +} diff --git a/ethdb/memory_database.go b/ethdb/memory_database.go index 45423ed73b..a729f52332 100644 --- a/ethdb/memory_database.go +++ b/ethdb/memory_database.go @@ -107,7 +107,7 @@ func (b *memBatch) Put(key, value []byte) error { b.lock.Lock() defer b.lock.Unlock() - b.writes = append(b.writes, kv{key, common.CopyBytes(value)}) + b.writes = append(b.writes, kv{common.CopyBytes(key), common.CopyBytes(value)}) return nil } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index ca515aacbb..be7defe83b 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -109,7 +109,7 @@ func (t *SecureTrie) TryUpdate(key, value []byte) error { if err != nil { return err } - t.secKeyCache[string(hk)] = key + t.secKeyCache[string(hk)] = common.CopyBytes(key) return nil } @@ -123,7 +123,9 @@ func (t *SecureTrie) Delete(key []byte) { // TryDelete removes any existing value for key from the trie. // If a node was not found in the database, a MissingNodeError is returned. func (t *SecureTrie) TryDelete(key []byte) error { - return t.Trie.TryDelete(t.hashKey(key)) + hk := t.hashKey(key) + delete(t.secKeyCache, string(hk)) + return t.Trie.TryDelete(hk) } // GetKey returns the sha3 preimage of a hashed key that was From a15b02320eca04325c998d69a77ebf1e53e22808 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 20 Jan 2016 14:13:10 +0100 Subject: [PATCH 55/78] logger/glog: add directory context to output and vmodule matching This change allows setting the verbosity for directory prefixes using the syntax: --vmodule=eth/=6 --- logger/glog/glog.go | 89 ++++++++++++++++++++++++---------------- logger/glog/glog_test.go | 51 ++++++++++++++++------- 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/logger/glog/glog.go b/logger/glog/glog.go index 008c0e036f..ef0c3068c5 100644 --- a/logger/glog/glog.go +++ b/logger/glog/glog.go @@ -63,11 +63,17 @@ // Enable V-leveled logging at the specified level. // -vmodule="" // The syntax of the argument is a comma-separated list of pattern=N, -// where pattern is a literal file name (minus the ".go" suffix) or -// "glob" pattern and N is a V level. For instance, -// -vmodule=gopher*=3 -// sets the V level to 3 in all Go files whose names begin "gopher". +// where pattern is a literal file name or "glob" pattern matching +// and N is a V level. For instance, // +// -vmodule=gopher.go=3 +// sets the V level to 3 in all Go files named "gopher.go". +// +// -vmodule=foo=3 +// sets V to 3 in all files of any packages whose import path ends in "foo". +// +// -vmodule=foo/*=3 +// sets V to 3 in all files of any packages whose import path contains "foo". package glog import ( @@ -78,7 +84,7 @@ import ( "io" stdLog "log" "os" - "path/filepath" + "regexp" "runtime" "strconv" "strings" @@ -113,6 +119,20 @@ var severityName = []string{ fatalLog: "FATAL", } +// these path prefixes are trimmed for display, but not when +// matching vmodule filters. +var trimPrefixes = []string{ + "/github.com/ethereum/go-ethereum", + "/github.com/ethereum/ethash", +} + +func trimToImportPath(file string) string { + if root := strings.LastIndex(file, "src/"); root != 0 { + file = file[root+3:] + } + return file +} + // SetV sets the global verbosity level func SetV(v int) { logging.verbosity.set(Level(v)) @@ -261,21 +281,10 @@ type moduleSpec struct { // modulePat contains a filter for the -vmodule flag. // It holds a verbosity level and a file pattern to match. type modulePat struct { - pattern string - literal bool // The pattern is a literal string + pattern *regexp.Regexp level Level } -// match reports whether the file matches the pattern. It uses a string -// comparison if the pattern contains no metacharacters. -func (m *modulePat) match(file string) bool { - if m.literal { - return file == m.pattern - } - match, _ := filepath.Match(m.pattern, file) - return match -} - func (m *moduleSpec) String() string { // Lock because the type is not atomic. TODO: clean this up. logging.mu.Lock() @@ -322,7 +331,8 @@ func (m *moduleSpec) Set(value string) error { continue // Ignore. It's harmless but no point in paying the overhead. } // TODO: check syntax of filter? - filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) + re, _ := compileModulePattern(pattern) + filter = append(filter, modulePat{re, Level(v)}) } logging.mu.Lock() defer logging.mu.Unlock() @@ -330,10 +340,21 @@ func (m *moduleSpec) Set(value string) error { return nil } -// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters -// that require filepath.Match to be called to match the pattern. -func isLiteral(pattern string) bool { - return !strings.ContainsAny(pattern, `\*?[]`) +// compiles a vmodule pattern to a regular expression. +func compileModulePattern(pat string) (*regexp.Regexp, error) { + re := ".*" + for _, comp := range strings.Split(pat, "/") { + if comp == "*" { + re += "(/.*)?" + } else if comp != "" { + // TODO: maybe return error if comp contains * + re += "/" + regexp.QuoteMeta(comp) + } + } + if !strings.HasSuffix(pat, ".go") { + re += "/[^/]+\\.go" + } + return regexp.Compile(re + "$") } // traceLocation represents the setting of the -log_backtrace_at flag. @@ -556,10 +577,14 @@ func (l *loggingT) header(s severity, depth int) (*buffer, string, int) { file = "???" line = 1 } else { - slash := strings.LastIndex(file, "/") - if slash >= 0 { - file = file[slash+1:] + file = trimToImportPath(file) + for _, p := range trimPrefixes { + if strings.HasPrefix(file, p) { + file = file[len(p):] + break + } } + file = file[1:] // drop '/' } return l.formatHeader(s, file, line), file, line } @@ -592,9 +617,7 @@ func (l *loggingT) formatHeader(s severity, file string, line int) *buffer { buf.tmp[14] = '.' buf.nDigits(6, 15, now.Nanosecond()/1000, '0') buf.tmp[21] = ' ' - buf.nDigits(7, 22, pid, ' ') // TODO: should be TID - buf.tmp[29] = ' ' - buf.Write(buf.tmp[:30]) + buf.Write(buf.tmp[:22]) buf.WriteString(file) buf.tmp[0] = ':' n := buf.someDigits(1, line) @@ -976,15 +999,9 @@ func (lb logBridge) Write(b []byte) (n int, err error) { func (l *loggingT) setV(pc uintptr) Level { fn := runtime.FuncForPC(pc) file, _ := fn.FileLine(pc) - // The file is something like /a/b/c/d.go. We want just the d. - if strings.HasSuffix(file, ".go") { - file = file[:len(file)-3] - } - if slash := strings.LastIndex(file, "/"); slash >= 0 { - file = file[slash+1:] - } + file = trimToImportPath(file) for _, filter := range l.vmodule.filter { - if filter.match(file) { + if filter.pattern.MatchString(file) { l.vmap[pc] = filter.level return filter.level } diff --git a/logger/glog/glog_test.go b/logger/glog/glog_test.go index 0fb376e1fd..30861a48d9 100644 --- a/logger/glog/glog_test.go +++ b/logger/glog/glog_test.go @@ -180,7 +180,7 @@ func TestHeader(t *testing.T) { pid = 1234 Info("test") var line int - format := "I0102 15:04:05.067890 1234 glog_test.go:%d] test\n" + format := "I0102 15:04:05.067890 logger/glog/glog_test.go:%d] test\n" n, err := fmt.Sscanf(contents(infoLog), format, &line) if n != 1 || err != nil { t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog)) @@ -253,7 +253,7 @@ func TestV(t *testing.T) { func TestVmoduleOn(t *testing.T) { setFlags() defer logging.swap(logging.newBuffers()) - logging.vmodule.Set("glog_test=2") + logging.vmodule.Set("glog_test.go=2") defer logging.vmodule.Set("") if !V(1) { t.Error("V not enabled for 1") @@ -290,22 +290,43 @@ func TestVmoduleOff(t *testing.T) { } } +var patternTests = []struct{ input, want string }{ + {"foo/bar/x.go", ".*/foo/bar/x\\.go$"}, + {"foo/*/x.go", ".*/foo(/.*)?/x\\.go$"}, + {"foo/*", ".*/foo(/.*)?/[^/]+\\.go$"}, +} + +func TestCompileModulePattern(t *testing.T) { + for _, test := range patternTests { + re, err := compileModulePattern(test.input) + if err != nil { + t.Fatalf("%s: %v", err) + } + if re.String() != test.want { + t.Errorf("mismatch for %q: got %q, want %q", test.input, re.String(), test.want) + } + } +} + // vGlobs are patterns that match/don't match this file at V=2. var vGlobs = map[string]bool{ // Easy to test the numeric match here. - "glog_test=1": false, // If -vmodule sets V to 1, V(2) will fail. - "glog_test=2": true, - "glog_test=3": true, // If -vmodule sets V to 1, V(3) will succeed. - // These all use 2 and check the patterns. All are true. - "*=2": true, - "?l*=2": true, - "????_*=2": true, - "??[mno]?_*t=2": true, - // These all use 2 and check the patterns. All are false. - "*x=2": false, - "m*=2": false, - "??_*=2": false, - "?[abc]?_*t=2": false, + "glog_test.go=1": false, // If -vmodule sets V to 1, V(2) will fail. + "glog_test.go=2": true, + "glog_test.go=3": true, // If -vmodule sets V to 1, V(3) will succeed. + + // Import path prefix matching + "logger/glog=1": false, + "logger/glog=2": true, + "logger/glog=3": true, + + // Import path glob matching + "logger/*=1": false, + "logger/*=2": true, + "logger/*=3": true, + + // These all use 2 and check the patterns. + "*=2": true, } // Test that vmodule globbing works as advertised. From 0edcbc797f9ecf3d757b4ffcf1844a2ba8ec72ef Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 21 Jan 2016 13:36:33 +0100 Subject: [PATCH 56/78] logger/glog: add SetVmodule --- logger/glog/glog.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/logger/glog/glog.go b/logger/glog/glog.go index ef0c3068c5..b8eaf9359e 100644 --- a/logger/glog/glog.go +++ b/logger/glog/glog.go @@ -138,6 +138,11 @@ func SetV(v int) { logging.verbosity.set(Level(v)) } +// SetVmodule sets the global verbosity patterns. +func SetVmodule(pat string) error { + return logging.vmodule.Set(pat) +} + // SetToStderr sets the global output style func SetToStderr(toStderr bool) { logging.toStderr = toStderr From e4d794851b713b5a22147c570963ff76ec26c7d0 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 21 Jan 2016 13:36:50 +0100 Subject: [PATCH 57/78] node, rpc/api: add debug_vmodule, move admin_verbosity to debug_verbosity --- node/api.go | 5 +++++ rpc/api/admin.go | 12 ------------ rpc/api/admin_args.go | 22 ---------------------- rpc/api/admin_js.go | 6 ------ rpc/api/debug_js.go | 14 +++++++++++++- 5 files changed, 18 insertions(+), 41 deletions(-) diff --git a/node/api.go b/node/api.go index a44ee16c07..909329e573 100644 --- a/node/api.go +++ b/node/api.go @@ -138,6 +138,11 @@ func (api *PrivateDebugAPI) Verbosity(level int) { glog.SetV(level) } +// Vmodule updates the node's logging verbosity pattern. +func (api *PrivateDebugAPI) Vmodule(pattern string) error { + return glog.SetVmodule(pattern) +} + // PublicDebugAPI is the collection of debugging related API methods exposed over // both secure and unsecure RPC channels. type PublicDebugAPI struct { diff --git a/rpc/api/admin.go b/rpc/api/admin.go index 1133c9bca3..daf2f31b45 100644 --- a/rpc/api/admin.go +++ b/rpc/api/admin.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/rlp" @@ -55,7 +54,6 @@ var ( "admin_nodeInfo": (*adminApi).NodeInfo, "admin_exportChain": (*adminApi).ExportChain, "admin_importChain": (*adminApi).ImportChain, - "admin_verbosity": (*adminApi).Verbosity, "admin_setSolc": (*adminApi).SetSolc, "admin_datadir": (*adminApi).DataDir, "admin_startRPC": (*adminApi).StartRPC, @@ -225,16 +223,6 @@ func (self *adminApi) ExportChain(req *shared.Request) (interface{}, error) { return true, nil } -func (self *adminApi) Verbosity(req *shared.Request) (interface{}, error) { - args := new(VerbosityArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - glog.SetV(args.Level) - return true, nil -} - func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) { args := new(SetSolcArgs) if err := self.coder.Decode(req.Params, &args); err != nil { diff --git a/rpc/api/admin_args.go b/rpc/api/admin_args.go index e3a2f72bf9..e09597ad49 100644 --- a/rpc/api/admin_args.go +++ b/rpc/api/admin_args.go @@ -69,28 +69,6 @@ func (args *ImportExportChainArgs) UnmarshalJSON(b []byte) (err error) { return nil } -type VerbosityArgs struct { - Level int -} - -func (args *VerbosityArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) != 1 { - return shared.NewDecodeParamError("Expected enode as argument") - } - - level, err := numString(obj[0]) - if err == nil { - args.Level = int(level.Int64()) - } - - return nil -} - type SetSolcArgs struct { Path string } diff --git a/rpc/api/admin_js.go b/rpc/api/admin_js.go index 413ea8d476..e6171cc74a 100644 --- a/rpc/api/admin_js.go +++ b/rpc/api/admin_js.go @@ -45,12 +45,6 @@ web3._extend({ params: 2, inputFormatter: [null, null] }), - new web3._extend.Method({ - name: 'verbosity', - call: 'admin_verbosity', - params: 1, - inputFormatter: [web3._extend.utils.fromDecimal] - }), new web3._extend.Method({ name: 'setSolc', call: 'admin_setSolc', diff --git a/rpc/api/debug_js.go b/rpc/api/debug_js.go index 03755ada0d..030511add3 100644 --- a/rpc/api/debug_js.go +++ b/rpc/api/debug_js.go @@ -62,7 +62,19 @@ web3._extend({ call: 'debug_metrics', params: 1, inputFormatter: [null] - }) + }), + new web3._extend.Method({ + name: 'verbosity', + call: 'debug_verbosity', + params: 1, + inputFormatter: [web3._extend.utils.fromDecimal] + }), + new web3._extend.Method({ + name: 'vmodule', + call: 'debug_vmodule', + params: 1, + inputFormatter: [null] + }), ], properties: [ From 896c134d309051bfc5dfd8ac99aca0bbacb946dd Mon Sep 17 00:00:00 2001 From: caktux Date: Fri, 22 Jan 2016 04:03:41 -0500 Subject: [PATCH 58/78] separate and optimize Dockerfile for master and develop --- docker/Dockerfile | 21 --------------------- docker/develop/Dockerfile | 17 +++++++++++++++++ docker/master/Dockerfile | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 21 deletions(-) delete mode 100644 docker/Dockerfile create mode 100644 docker/develop/Dockerfile create mode 100644 docker/master/Dockerfile diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index ba5b05d14c..0000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM ubuntu:wily -MAINTAINER caktux - -ENV DEBIAN_FRONTEND noninteractive - -# Usual update / upgrade -RUN apt-get update -RUN apt-get upgrade -q -y -RUN apt-get dist-upgrade -q -y - -# Install Ethereum -RUN apt-get install -q -y software-properties-common -RUN add-apt-repository ppa:ethereum/ethereum -RUN add-apt-repository ppa:ethereum/ethereum-dev -RUN apt-get update -RUN apt-get install -q -y geth - -EXPOSE 8545 -EXPOSE 30303 - -ENTRYPOINT ["/usr/bin/geth"] diff --git a/docker/develop/Dockerfile b/docker/develop/Dockerfile new file mode 100644 index 0000000000..98b4aadf8f --- /dev/null +++ b/docker/develop/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:wily +MAINTAINER caktux + +ENV DEBIAN_FRONTEND noninteractive + +RUN apt-get update && \ + apt-get upgrade -q -y && \ + apt-get dist-upgrade -q -y && \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 923F6CA9 && \ + echo "deb http://ppa.launchpad.net/ethereum/ethereum-dev/ubuntu wily main" | tee -a /etc/apt/sources.list.d/ethereum.list && \ + apt-get update && \ + apt-get install -q -y geth + +EXPOSE 8545 +EXPOSE 30303 + +ENTRYPOINT ["/usr/bin/geth"] diff --git a/docker/master/Dockerfile b/docker/master/Dockerfile new file mode 100644 index 0000000000..2c6de28c92 --- /dev/null +++ b/docker/master/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:wily +MAINTAINER caktux + +ENV DEBIAN_FRONTEND noninteractive + +RUN apt-get update && \ + apt-get upgrade -q -y && \ + apt-get dist-upgrade -q -y && \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 923F6CA9 && \ + echo "deb http://ppa.launchpad.net/ethereum/ethereum/ubuntu wily main" | tee -a /etc/apt/sources.list.d/ethereum.list && \ + apt-get update && \ + apt-get install -q -y geth + +EXPOSE 8545 +EXPOSE 30303 + +ENTRYPOINT ["/usr/bin/geth"] From a2dc074b1ddffaf4e35758835bafe14cca5f0ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 22 Jan 2016 12:05:17 +0200 Subject: [PATCH 59/78] core, core/types, miner: fix transaction nonce-price combo sort --- core/transaction_pool.go | 2 +- core/types/transaction.go | 87 +++++++++++++++++++++++++++------- core/types/transaction_test.go | 57 ++++++++++++++++++++++ miner/worker.go | 9 ++-- 4 files changed, 132 insertions(+), 23 deletions(-) diff --git a/core/transaction_pool.go b/core/transaction_pool.go index abd5bb7b79..54c0ddebb0 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -376,7 +376,7 @@ func (self *TxPool) GetQueuedTransactions() types.Transactions { ret = append(ret, tx) } } - sort.Sort(types.TxByNonce{ret}) + sort.Sort(types.TxByNonce(ret)) return ret } diff --git a/core/types/transaction.go b/core/types/transaction.go index 7a6c5e088d..4049ae888d 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -17,11 +17,13 @@ package types import ( + "container/heap" "crypto/ecdsa" "errors" "fmt" "io" "math/big" + "sort" "sync/atomic" "github.com/ethereum/go-ethereum/common" @@ -302,27 +304,78 @@ func TxDifference(a, b Transactions) (keep Transactions) { return keep } -type TxByNonce struct{ Transactions } +// TxByNonce implements the sort interface to allow sorting a list of transactions +// by their nonces. This is usually only useful for sorting transactions from a +// single account, otherwise a nonce comparison doesn't make much sense. +type TxByNonce Transactions -func (s TxByNonce) Less(i, j int) bool { - return s.Transactions[i].data.AccountNonce < s.Transactions[j].data.AccountNonce +func (s TxByNonce) Len() int { return len(s) } +func (s TxByNonce) Less(i, j int) bool { return s[i].data.AccountNonce < s[j].data.AccountNonce } +func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// TxByPrice implements both the sort and the heap interface, making it useful +// for all at once sorting as well as individually adding and removing elements. +type TxByPrice Transactions + +func (s TxByPrice) Len() int { return len(s) } +func (s TxByPrice) Less(i, j int) bool { return s[i].data.Price.Cmp(s[j].data.Price) > 0 } +func (s TxByPrice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s *TxByPrice) Push(x interface{}) { + *s = append(*s, x.(*Transaction)) } -type TxByPrice struct{ Transactions } - -func (s TxByPrice) Less(i, j int) bool { - return s.Transactions[i].data.Price.Cmp(s.Transactions[j].data.Price) > 0 +func (s *TxByPrice) Pop() interface{} { + old := *s + n := len(old) + x := old[n-1] + *s = old[0 : n-1] + return x } -type TxByPriceAndNonce struct{ Transactions } - -func (s TxByPriceAndNonce) Less(i, j int) bool { - // we can ignore the error here. Sorting shouldn't care about validness - ifrom, _ := s.Transactions[i].From() - jfrom, _ := s.Transactions[j].From() - // favour nonce if they are from the same recipient - if ifrom == jfrom { - return s.Transactions[i].data.AccountNonce < s.Transactions[j].data.AccountNonce +// SortByPriceAndNonce sorts the transactions by price in such a way that the +// nonce orderings within a single account are maintained. +// +// Note, this is not as trivial as it seems from the first look as there are three +// different criteria that need to be taken into account (price, nonce, account +// match), which cannot be done with any plain sorting method, as certain items +// cannot be compared without context. +// +// This method first sorts the separates the list of transactions into individual +// sender accounts and sorts them by nonce. After the account nonce ordering is +// satisfied, the results are merged back together by price, always comparing only +// the head transaction from each account. This is done via a heap to keep it fast. +func SortByPriceAndNonce(txs []*Transaction) { + // Separate the transactions by account and sort by nonce + byNonce := make(map[common.Address][]*Transaction) + for _, tx := range txs { + acc, _ := tx.From() // we only sort valid txs so this cannot fail + byNonce[acc] = append(byNonce[acc], tx) + } + for _, accTxs := range byNonce { + sort.Sort(TxByNonce(accTxs)) + } + // Initialize a price based heap with the head transactions + byPrice := make(TxByPrice, 0, len(byNonce)) + for acc, accTxs := range byNonce { + byPrice = append(byPrice, accTxs[0]) + byNonce[acc] = accTxs[1:] + } + heap.Init(&byPrice) + + // Merge by replacing the best with the next from the same account + txs = txs[:0] + for len(byPrice) > 0 { + // Retrieve the next best transaction by price + best := heap.Pop(&byPrice).(*Transaction) + + // Push in its place the next transaction from the same account + acc, _ := best.From() // we only sort valid txs so this cannot fail + if accTxs, ok := byNonce[acc]; ok && len(accTxs) > 0 { + heap.Push(&byPrice, accTxs[0]) + byNonce[acc] = accTxs[1:] + } + // Accumulate the best priced transaction + txs = append(txs, best) } - return s.Transactions[i].data.Price.Cmp(s.Transactions[j].data.Price) > 0 } diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 1c0e27d9a4..62420e71f8 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -117,3 +117,60 @@ func TestRecipientNormal(t *testing.T) { t.Error("derived address doesn't match") } } + +// Tests that transactions can be correctly sorted according to their price in +// decreasing order, but at the same time with increasing nonces when issued by +// the same account. +func TestTransactionPriceNonceSort(t *testing.T) { + // Generate a batch of accounts to start with + keys := make([]*ecdsa.PrivateKey, 25) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + } + // Generate a batch of transactions with overlapping values, but shifted nonces + txs := []*Transaction{} + for start, key := range keys { + for i := 0; i < 25; i++ { + tx, _ := NewTransaction(uint64(start+i), common.Address{}, big.NewInt(100), big.NewInt(100), big.NewInt(int64(start+i)), nil).SignECDSA(key) + txs = append(txs, tx) + } + } + // Sort the transactions and cross check the nonce ordering + SortByPriceAndNonce(txs) + for i, txi := range txs { + fromi, _ := txi.From() + + // Make sure the nonce order is valid + for j, txj := range txs[i+1:] { + fromj, _ := txj.From() + + if fromi == fromj && txi.Nonce() > txj.Nonce() { + t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce()) + } + } + // Find the previous and next nonce of this account + prev, next := i-1, i+1 + for j := i - 1; j >= 0; j-- { + if fromj, _ := txs[j].From(); fromi == fromj { + prev = j + break + } + } + for j := i + 1; j < len(txs); j++ { + if fromj, _ := txs[j].From(); fromi == fromj { + next = j + break + } + } + // Make sure that in between the neighbor nonces, the transaction is correctly positioned price wise + for j := prev + 1; j < next; j++ { + fromj, _ := txs[j].From() + if j < i && txs[j].GasPrice().Cmp(txi.GasPrice()) < 0 { + t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", j, fromj[:4], txs[j].GasPrice(), i, fromi[:4], txi.GasPrice()) + } + if j > i && txs[j].GasPrice().Cmp(txi.GasPrice()) > 0 { + t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) > tx #%d (A=%x P=%v)", j, fromj[:4], txs[j].GasPrice(), i, fromi[:4], txi.GasPrice()) + } + } + } +} diff --git a/miner/worker.go b/miner/worker.go index 1a411ae20b..9c29d22504 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -19,7 +19,6 @@ package miner import ( "fmt" "math/big" - "sort" "sync" "sync/atomic" "time" @@ -496,12 +495,12 @@ func (self *worker) commitNewWork() { /* //approach 1 transactions := self.eth.TxPool().GetTransactions() - sort.Sort(types.TxByNonce{transactions}) + sort.Sort(types.TxByNonce(transactions)) */ //approach 2 transactions := self.eth.TxPool().GetTransactions() - sort.Sort(types.TxByPriceAndNonce{transactions}) + types.SortByPriceAndNonce(transactions) /* // approach 3 // commit transactions for this run. @@ -525,8 +524,8 @@ func (self *worker) commitNewWork() { multiTxOwner = append(multiTxOwner, txs...) } } - sort.Sort(types.TxByPrice{singleTxOwner}) - sort.Sort(types.TxByNonce{multiTxOwner}) + sort.Sort(types.TxByPrice(singleTxOwner)) + sort.Sort(types.TxByNonce(multiTxOwner)) transactions := append(singleTxOwner, multiTxOwner...) */ From 2871781f64958c144a15ccb4e64c005a10be7cdd Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 22 Jan 2016 16:08:23 +0100 Subject: [PATCH 60/78] p2p/discover: fix Windows-specific issue for larger-than-buffer packets On Windows, UDPConn.ReadFrom returns an error for packets larger than the receive buffer. The error is not marked temporary, causing our loop to exit when the first oversized packet arrived. The fix is to treat this particular error as temporary. Fixes: #1579, #2087 Updates: #2082 --- p2p/discover/udp.go | 9 ++++-- p2p/discover/udp_notwindows.go | 26 ++++++++++++++++ p2p/discover/udp_test.go | 56 +++++++++++++++++++++++++++++++--- p2p/discover/udp_windows.go | 40 ++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 p2p/discover/udp_notwindows.go create mode 100644 p2p/discover/udp_windows.go diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index 72b2a45e58..cec9046a3b 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -453,8 +453,11 @@ func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) ([]byte, return packet, nil } -type tempError interface { - Temporary() bool +func isTemporaryError(err error) bool { + tempErr, ok := err.(interface { + Temporary() bool + }) + return ok && tempErr.Temporary() || isPacketTooBig(err) } // readLoop runs in its own goroutine. it handles incoming UDP packets. @@ -466,7 +469,7 @@ func (t *udp) readLoop() { buf := make([]byte, 1280) for { nbytes, from, err := t.conn.ReadFromUDP(buf) - if tempErr, ok := err.(tempError); ok && tempErr.Temporary() { + if isTemporaryError(err) { // Ignore temporary read errors. glog.V(logger.Debug).Infof("Temporary read error: %v", err) continue diff --git a/p2p/discover/udp_notwindows.go b/p2p/discover/udp_notwindows.go new file mode 100644 index 0000000000..e9de83aa91 --- /dev/null +++ b/p2p/discover/udp_notwindows.go @@ -0,0 +1,26 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// 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 . + +//+build !windows + +package discover + +// reports whether err indicates that a UDP packet didn't +// fit the receive buffer. There is no such error on +// non-Windows platforms. +func isPacketTooBig(err error) bool { + return false +} diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 8ed12b8eca..ec28867cca 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -23,10 +23,8 @@ import ( "errors" "fmt" "io" - logpkg "log" "math/rand" "net" - "os" "path/filepath" "reflect" "runtime" @@ -34,12 +32,62 @@ import ( "testing" "time" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger" ) func init() { - logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, logpkg.LstdFlags, logger.ErrorLevel)) + spew.Config.DisableMethods = true +} + +// This test checks that isPacketTooBig correctly identifies +// errors that result from receiving a UDP packet larger +// than the supplied receive buffer. +func TestIsPacketTooBig(t *testing.T) { + listener, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer listener.Close() + sender, err := net.Dial("udp", listener.LocalAddr().String()) + if err != nil { + t.Fatal(err) + } + defer sender.Close() + + sendN := 1800 + recvN := 300 + for i := 0; i < 20; i++ { + go func() { + buf := make([]byte, sendN) + for i := range buf { + buf[i] = byte(i) + } + sender.Write(buf) + }() + + buf := make([]byte, recvN) + listener.SetDeadline(time.Now().Add(1 * time.Second)) + n, _, err := listener.ReadFrom(buf) + if err != nil { + if nerr, ok := err.(net.Error); ok && nerr.Timeout() { + continue + } + if !isPacketTooBig(err) { + t.Fatal("unexpected read error:", spew.Sdump(err)) + } + continue + } + if n != recvN { + t.Fatalf("short read: %d, want %d", n, recvN) + } + for i := range buf { + if buf[i] != byte(i) { + t.Fatalf("error in pattern") + break + } + } + } } // shared test variables diff --git a/p2p/discover/udp_windows.go b/p2p/discover/udp_windows.go new file mode 100644 index 0000000000..66bbf9597d --- /dev/null +++ b/p2p/discover/udp_windows.go @@ -0,0 +1,40 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// 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 . + +//+build windows + +package discover + +import ( + "net" + "os" + "syscall" +) + +const _WSAEMSGSIZE = syscall.Errno(10040) + +// reports whether err indicates that a UDP packet didn't +// fit the receive buffer. On Windows, WSARecvFrom returns +// code WSAEMSGSIZE and no data if this happens. +func isPacketTooBig(err error) bool { + if opErr, ok := err.(*net.OpError); ok { + if scErr, ok := opErr.Err.(*os.SyscallError); ok { + return scErr.Err == _WSAEMSGSIZE + } + return opErr.Err == _WSAEMSGSIZE + } + return false +} From 19b2640e89465c1c57f1bbea0274d52d97151f60 Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Wed, 16 Dec 2015 10:58:01 +0100 Subject: [PATCH 61/78] rpc: migrated the RPC insterface to a new reflection based RPC layer --- Godeps/Godeps.json | 14 +- .../src/github.com/kardianos/osext/LICENSE | 27 - .../src/github.com/kardianos/osext/README.md | 14 - .../src/github.com/kardianos/osext/osext.go | 27 - .../github.com/kardianos/osext/osext_plan9.go | 20 - .../kardianos/osext/osext_procfs.go | 28 - .../kardianos/osext/osext_sysctl.go | 79 - .../github.com/kardianos/osext/osext_test.go | 79 - .../kardianos/osext/osext_windows.go | 34 - .../src/github.com/rs/cors/.travis.yml | 4 - .../_workspace/src/github.com/rs/cors/LICENSE | 19 - .../src/github.com/rs/cors/README.md | 84 - .../src/github.com/rs/cors/bench_test.go | 37 - .../_workspace/src/github.com/rs/cors/cors.go | 308 -- .../src/github.com/rs/cors/cors_test.go | 288 -- .../rs/cors/examples/alice/server.go | 24 - .../rs/cors/examples/default/server.go | 18 - .../rs/cors/examples/goji/server.go | 22 - .../rs/cors/examples/martini/server.go | 23 - .../rs/cors/examples/negroni/server.go | 26 - .../rs/cors/examples/nethttp/server.go | 20 - .../rs/cors/examples/openbar/server.go | 22 - .../src/github.com/rs/cors/utils.go | 27 - .../src/github.com/rs/cors/utils_test.go | 28 - .../src/golang.org/x/net/websocket/client.go | 113 + .../x/net/websocket/exampledial_test.go | 31 + .../x/net/websocket/examplehandler_test.go | 26 + .../src/golang.org/x/net/websocket/hybi.go | 564 ++++ .../golang.org/x/net/websocket/hybi_test.go | 590 ++++ .../src/golang.org/x/net/websocket/server.go | 114 + .../golang.org/x/net/websocket/websocket.go | 411 +++ .../x/net/websocket/websocket_test.go | 414 +++ accounts/account_manager.go | 2 +- cmd/geth/js.go | 174 +- cmd/geth/js_test.go | 101 +- cmd/geth/main.go | 51 +- cmd/geth/monitorcmd.go | 47 +- cmd/geth/usage.go | 8 +- cmd/gethrpctest/main.go | 53 +- cmd/utils/api.go | 74 - cmd/utils/client.go | 176 ++ cmd/utils/flags.go | 157 +- {rpc => cmd/utils}/jeth.go | 81 +- common/natspec/natspec.go | 2 + common/natspec/natspec_e2e_test.go | 2 + common/natspec/natspec_test.go | 2 + common/registrar/ethreg/api.go | 265 ++ common/registrar/ethreg/ethreg.go | 48 - eth/api.go | 97 +- eth/backend.go | 36 +- eth/downloader/api.go | 2 +- eth/filters/api.go | 2 +- jsre/ethereum_js.go | 4 +- light/state_object.go | 2 +- miner/api.go | 2 +- node/api.go | 113 +- node/node.go | 7 +- node/node_example_test.go | 2 +- node/service.go | 2 +- node/utils_test.go | 2 +- rpc/api/admin.go | 465 --- rpc/api/admin_args.go | 468 --- rpc/api/admin_js.go | 143 - rpc/api/api.go | 26 - rpc/api/api_test.go | 170 -- rpc/api/args.go | 74 - rpc/api/args_test.go | 2649 ----------------- rpc/api/db.go | 144 - rpc/api/db_args.go | 126 - rpc/api/db_js.go | 29 - rpc/api/debug.go | 303 -- rpc/api/debug_args.go | 87 - rpc/api/debug_js.go | 83 - rpc/api/eth.go | 721 ----- rpc/api/eth_args.go | 1104 ------- rpc/api/eth_js.go | 66 - rpc/api/mergedapi.go | 88 - rpc/api/miner.go | 177 -- rpc/api/miner_args.go | 142 - rpc/api/miner_js.go | 83 - rpc/api/net.go | 99 - rpc/api/net_js.go | 39 - rpc/api/parsing.go | 522 ---- rpc/api/personal.go | 139 - rpc/api/personal_args.go | 85 - rpc/api/personal_js.go | 51 - rpc/api/shh.go | 196 -- rpc/api/shh_args.go | 174 -- rpc/api/shh_js.go | 34 - rpc/api/txpool.go | 92 - rpc/api/txpool_js.go | 33 - rpc/api/utils.go | 226 -- rpc/api/web3.go | 99 - rpc/codec/codec.go | 65 - rpc/codec/json.go | 149 - rpc/codec/json_test.go | 157 - rpc/comms/comms.go | 150 - rpc/comms/http.go | 345 --- rpc/comms/inproc.go | 82 - rpc/comms/ipc.go | 158 - rpc/comms/ipc_unix.go | 82 - rpc/{v2 => }/doc.go | 128 +- rpc/{v2 => }/errors.go | 14 +- rpc/http.go | 368 +++ rpc/ipc.go | 84 + rpc/{api/web3_args.go => ipc_unix.go} | 44 +- rpc/{comms => }/ipc_windows.go | 56 +- rpc/javascript.go | 414 +++ rpc/{v2 => }/json.go | 28 +- rpc/{v2 => }/json_test.go | 2 +- rpc/{v2 => }/server.go | 61 +- rpc/{v2 => }/server_test.go | 32 +- rpc/shared/errors.go | 126 - rpc/shared/types.go | 108 - rpc/shared/utils.go | 43 - rpc/{v2 => }/types.go | 18 +- rpc/{v2 => }/types_test.go | 18 +- rpc/useragent/agent.go | 24 - rpc/useragent/remote_frontend.go | 166 -- rpc/{v2 => }/utils.go | 34 +- rpc/websocket.go | 235 ++ rpc/xeth.go | 77 - whisper/api.go | 2 +- whisper/whisper.go | 2 +- xeth/frontend.go | 48 - xeth/state.go | 51 - xeth/types.go | 237 -- xeth/whisper.go | 121 - xeth/whisper_filter.go | 100 - xeth/whisper_message.go | 53 - xeth/xeth.go | 1137 ------- xeth/xeth_test.go | 26 - 132 files changed, 4711 insertions(+), 14320 deletions(-) delete mode 100644 Godeps/_workspace/src/github.com/kardianos/osext/LICENSE delete mode 100644 Godeps/_workspace/src/github.com/kardianos/osext/README.md delete mode 100644 Godeps/_workspace/src/github.com/kardianos/osext/osext.go delete mode 100644 Godeps/_workspace/src/github.com/kardianos/osext/osext_plan9.go delete mode 100644 Godeps/_workspace/src/github.com/kardianos/osext/osext_procfs.go delete mode 100644 Godeps/_workspace/src/github.com/kardianos/osext/osext_sysctl.go delete mode 100644 Godeps/_workspace/src/github.com/kardianos/osext/osext_test.go delete mode 100644 Godeps/_workspace/src/github.com/kardianos/osext/osext_windows.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/.travis.yml delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/LICENSE delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/README.md delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/bench_test.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/cors.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/cors_test.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/examples/alice/server.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/examples/default/server.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/examples/goji/server.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/examples/martini/server.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/examples/negroni/server.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/examples/nethttp/server.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/examples/openbar/server.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/utils.go delete mode 100644 Godeps/_workspace/src/github.com/rs/cors/utils_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/websocket/client.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/websocket/exampledial_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/websocket/examplehandler_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/websocket/hybi.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/websocket/hybi_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/websocket/server.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/websocket/websocket.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/websocket/websocket_test.go delete mode 100644 cmd/utils/api.go create mode 100644 cmd/utils/client.go rename {rpc => cmd/utils}/jeth.go (81%) create mode 100644 common/registrar/ethreg/api.go delete mode 100644 common/registrar/ethreg/ethreg.go delete mode 100644 rpc/api/admin.go delete mode 100644 rpc/api/admin_args.go delete mode 100644 rpc/api/admin_js.go delete mode 100644 rpc/api/api.go delete mode 100644 rpc/api/api_test.go delete mode 100644 rpc/api/args.go delete mode 100644 rpc/api/args_test.go delete mode 100644 rpc/api/db.go delete mode 100644 rpc/api/db_args.go delete mode 100644 rpc/api/db_js.go delete mode 100644 rpc/api/debug.go delete mode 100644 rpc/api/debug_args.go delete mode 100644 rpc/api/debug_js.go delete mode 100644 rpc/api/eth.go delete mode 100644 rpc/api/eth_args.go delete mode 100644 rpc/api/eth_js.go delete mode 100644 rpc/api/mergedapi.go delete mode 100644 rpc/api/miner.go delete mode 100644 rpc/api/miner_args.go delete mode 100644 rpc/api/miner_js.go delete mode 100644 rpc/api/net.go delete mode 100644 rpc/api/net_js.go delete mode 100644 rpc/api/parsing.go delete mode 100644 rpc/api/personal.go delete mode 100644 rpc/api/personal_args.go delete mode 100644 rpc/api/personal_js.go delete mode 100644 rpc/api/shh.go delete mode 100644 rpc/api/shh_args.go delete mode 100644 rpc/api/shh_js.go delete mode 100644 rpc/api/txpool.go delete mode 100644 rpc/api/txpool_js.go delete mode 100644 rpc/api/utils.go delete mode 100644 rpc/api/web3.go delete mode 100644 rpc/codec/codec.go delete mode 100644 rpc/codec/json.go delete mode 100644 rpc/codec/json_test.go delete mode 100644 rpc/comms/comms.go delete mode 100644 rpc/comms/http.go delete mode 100644 rpc/comms/inproc.go delete mode 100644 rpc/comms/ipc.go delete mode 100644 rpc/comms/ipc_unix.go rename rpc/{v2 => }/doc.go (64%) rename rpc/{v2 => }/errors.go (89%) create mode 100644 rpc/http.go create mode 100644 rpc/ipc.go rename rpc/{api/web3_args.go => ipc_unix.go} (53%) rename rpc/{comms => }/ipc_windows.go (94%) create mode 100644 rpc/javascript.go rename rpc/{v2 => }/json.go (95%) rename rpc/{v2 => }/json_test.go (99%) rename rpc/{v2 => }/server.go (89%) rename rpc/{v2 => }/server_test.go (83%) delete mode 100644 rpc/shared/errors.go delete mode 100644 rpc/shared/types.go delete mode 100644 rpc/shared/utils.go rename rpc/{v2 => }/types.go (96%) rename rpc/{v2 => }/types_test.go (62%) delete mode 100644 rpc/useragent/agent.go delete mode 100644 rpc/useragent/remote_frontend.go rename rpc/{v2 => }/utils.go (87%) create mode 100644 rpc/websocket.go delete mode 100644 rpc/xeth.go delete mode 100644 xeth/frontend.go delete mode 100644 xeth/state.go delete mode 100644 xeth/types.go delete mode 100644 xeth/whisper.go delete mode 100644 xeth/whisper_filter.go delete mode 100644 xeth/whisper_message.go delete mode 100644 xeth/xeth.go delete mode 100644 xeth/xeth_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 06f4b3b2da..b5703f7f2f 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/ethereum/go-ethereum", - "GoVersion": "go1.4", + "GoVersion": "go1.5.2", "Packages": [ "./..." ], @@ -40,10 +40,6 @@ "ImportPath": "github.com/jackpal/go-nat-pmp", "Rev": "a45aa3d54aef73b504e15eb71bea0e5565b5e6e1" }, - { - "ImportPath": "github.com/kardianos/osext", - "Rev": "ccfcd0245381f0c94c68f50626665eed3c6b726a" - }, { "ImportPath": "github.com/mattn/go-isatty", "Rev": "7fcbc72f853b92b5720db4a6b8482be612daef24" @@ -73,10 +69,6 @@ "ImportPath": "github.com/robertkrimen/otto", "Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba" }, - { - "ImportPath": "github.com/rs/cors", - "Rev": "6e0c3cb65fc0fdb064c743d176a620e3ca446dfb" - }, { "ImportPath": "github.com/shiena/ansicolor", "Rev": "a5e2b567a4dd6cc74545b8a4f27c9d63b9e7735b" @@ -109,6 +101,10 @@ "ImportPath": "golang.org/x/net/html", "Rev": "e0403b4e005737430c05a57aac078479844f919c" }, + { + "ImportPath": "golang.org/x/net/websocket", + "Rev": "e0403b4e005737430c05a57aac078479844f919c" + }, { "ImportPath": "golang.org/x/text/encoding", "Rev": "c93e7c9fff19fb9139b5ab04ce041833add0134e" diff --git a/Godeps/_workspace/src/github.com/kardianos/osext/LICENSE b/Godeps/_workspace/src/github.com/kardianos/osext/LICENSE deleted file mode 100644 index 7448756763..0000000000 --- a/Godeps/_workspace/src/github.com/kardianos/osext/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/kardianos/osext/README.md b/Godeps/_workspace/src/github.com/kardianos/osext/README.md deleted file mode 100644 index 820e1ecb54..0000000000 --- a/Godeps/_workspace/src/github.com/kardianos/osext/README.md +++ /dev/null @@ -1,14 +0,0 @@ -### Extensions to the "os" package. - -## Find the current Executable and ExecutableFolder. - -There is sometimes utility in finding the current executable file -that is running. This can be used for upgrading the current executable -or finding resources located relative to the executable file. - -Multi-platform and supports: - * Linux - * OS X - * Windows - * Plan 9 - * BSDs. diff --git a/Godeps/_workspace/src/github.com/kardianos/osext/osext.go b/Godeps/_workspace/src/github.com/kardianos/osext/osext.go deleted file mode 100644 index 4ed4b9aa33..0000000000 --- a/Godeps/_workspace/src/github.com/kardianos/osext/osext.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Extensions to the standard "os" package. -package osext - -import "path/filepath" - -// Executable returns an absolute path that can be used to -// re-invoke the current program. -// It may not be valid after the current program exits. -func Executable() (string, error) { - p, err := executable() - return filepath.Clean(p), err -} - -// Returns same path as Executable, returns just the folder -// path. Excludes the executable name. -func ExecutableFolder() (string, error) { - p, err := Executable() - if err != nil { - return "", err - } - folder, _ := filepath.Split(p) - return folder, nil -} diff --git a/Godeps/_workspace/src/github.com/kardianos/osext/osext_plan9.go b/Godeps/_workspace/src/github.com/kardianos/osext/osext_plan9.go deleted file mode 100644 index 655750c542..0000000000 --- a/Godeps/_workspace/src/github.com/kardianos/osext/osext_plan9.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package osext - -import ( - "os" - "strconv" - "syscall" -) - -func executable() (string, error) { - f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") - if err != nil { - return "", err - } - defer f.Close() - return syscall.Fd2path(int(f.Fd())) -} diff --git a/Godeps/_workspace/src/github.com/kardianos/osext/osext_procfs.go b/Godeps/_workspace/src/github.com/kardianos/osext/osext_procfs.go deleted file mode 100644 index a50021ad53..0000000000 --- a/Godeps/_workspace/src/github.com/kardianos/osext/osext_procfs.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux netbsd openbsd solaris dragonfly - -package osext - -import ( - "errors" - "fmt" - "os" - "runtime" -) - -func executable() (string, error) { - switch runtime.GOOS { - case "linux": - return os.Readlink("/proc/self/exe") - case "netbsd": - return os.Readlink("/proc/curproc/exe") - case "openbsd", "dragonfly": - return os.Readlink("/proc/curproc/file") - case "solaris": - return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid())) - } - return "", errors.New("ExecPath not implemented for " + runtime.GOOS) -} diff --git a/Godeps/_workspace/src/github.com/kardianos/osext/osext_sysctl.go b/Godeps/_workspace/src/github.com/kardianos/osext/osext_sysctl.go deleted file mode 100644 index b66cac878c..0000000000 --- a/Godeps/_workspace/src/github.com/kardianos/osext/osext_sysctl.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin freebsd - -package osext - -import ( - "os" - "path/filepath" - "runtime" - "syscall" - "unsafe" -) - -var initCwd, initCwdErr = os.Getwd() - -func executable() (string, error) { - var mib [4]int32 - switch runtime.GOOS { - case "freebsd": - mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} - case "darwin": - mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} - } - - n := uintptr(0) - // Get length. - _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) - if errNum != 0 { - return "", errNum - } - if n == 0 { // This shouldn't happen. - return "", nil - } - buf := make([]byte, n) - _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) - if errNum != 0 { - return "", errNum - } - if n == 0 { // This shouldn't happen. - return "", nil - } - for i, v := range buf { - if v == 0 { - buf = buf[:i] - break - } - } - var err error - execPath := string(buf) - // execPath will not be empty due to above checks. - // Try to get the absolute path if the execPath is not rooted. - if execPath[0] != '/' { - execPath, err = getAbs(execPath) - if err != nil { - return execPath, err - } - } - // For darwin KERN_PROCARGS may return the path to a symlink rather than the - // actual executable. - if runtime.GOOS == "darwin" { - if execPath, err = filepath.EvalSymlinks(execPath); err != nil { - return execPath, err - } - } - return execPath, nil -} - -func getAbs(execPath string) (string, error) { - if initCwdErr != nil { - return execPath, initCwdErr - } - // The execPath may begin with a "../" or a "./" so clean it first. - // Join the two paths, trailing and starting slashes undetermined, so use - // the generic Join function. - return filepath.Join(initCwd, filepath.Clean(execPath)), nil -} diff --git a/Godeps/_workspace/src/github.com/kardianos/osext/osext_test.go b/Godeps/_workspace/src/github.com/kardianos/osext/osext_test.go deleted file mode 100644 index dc661dbc21..0000000000 --- a/Godeps/_workspace/src/github.com/kardianos/osext/osext_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin linux freebsd netbsd windows - -package osext - -import ( - "fmt" - "os" - oexec "os/exec" - "path/filepath" - "runtime" - "testing" -) - -const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH" - -func TestExecPath(t *testing.T) { - ep, err := Executable() - if err != nil { - t.Fatalf("ExecPath failed: %v", err) - } - // we want fn to be of the form "dir/prog" - dir := filepath.Dir(filepath.Dir(ep)) - fn, err := filepath.Rel(dir, ep) - if err != nil { - t.Fatalf("filepath.Rel: %v", err) - } - cmd := &oexec.Cmd{} - // make child start with a relative program path - cmd.Dir = dir - cmd.Path = fn - // forge argv[0] for child, so that we can verify we could correctly - // get real path of the executable without influenced by argv[0]. - cmd.Args = []string{"-", "-test.run=XXXX"} - cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)} - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("exec(self) failed: %v", err) - } - outs := string(out) - if !filepath.IsAbs(outs) { - t.Fatalf("Child returned %q, want an absolute path", out) - } - if !sameFile(outs, ep) { - t.Fatalf("Child returned %q, not the same file as %q", out, ep) - } -} - -func sameFile(fn1, fn2 string) bool { - fi1, err := os.Stat(fn1) - if err != nil { - return false - } - fi2, err := os.Stat(fn2) - if err != nil { - return false - } - return os.SameFile(fi1, fi2) -} - -func init() { - if e := os.Getenv(execPath_EnvVar); e != "" { - // first chdir to another path - dir := "/" - if runtime.GOOS == "windows" { - dir = filepath.VolumeName(".") - } - os.Chdir(dir) - if ep, err := Executable(); err != nil { - fmt.Fprint(os.Stderr, "ERROR: ", err) - } else { - fmt.Fprint(os.Stderr, ep) - } - os.Exit(0) - } -} diff --git a/Godeps/_workspace/src/github.com/kardianos/osext/osext_windows.go b/Godeps/_workspace/src/github.com/kardianos/osext/osext_windows.go deleted file mode 100644 index 72d282cf8c..0000000000 --- a/Godeps/_workspace/src/github.com/kardianos/osext/osext_windows.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package osext - -import ( - "syscall" - "unicode/utf16" - "unsafe" -) - -var ( - kernel = syscall.MustLoadDLL("kernel32.dll") - getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") -) - -// GetModuleFileName() with hModule = NULL -func executable() (exePath string, err error) { - return getModuleFileName() -} - -func getModuleFileName() (string, error) { - var n uint32 - b := make([]uint16, syscall.MAX_PATH) - size := uint32(len(b)) - - r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) - n = uint32(r0) - if n == 0 { - return "", e1 - } - return string(utf16.Decode(b[0:n])), nil -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/.travis.yml b/Godeps/_workspace/src/github.com/rs/cors/.travis.yml deleted file mode 100644 index bbb5185a2e..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: go -go: -- 1.3 -- 1.4 diff --git a/Godeps/_workspace/src/github.com/rs/cors/LICENSE b/Godeps/_workspace/src/github.com/rs/cors/LICENSE deleted file mode 100644 index d8e2df5a47..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2014 Olivier Poitrey - -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. diff --git a/Godeps/_workspace/src/github.com/rs/cors/README.md b/Godeps/_workspace/src/github.com/rs/cors/README.md deleted file mode 100644 index 6f70c30aca..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![build](https://img.shields.io/travis/rs/cors.svg?style=flat)](https://travis-ci.org/rs/cors) - -CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang. - -## Getting Started - -After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`. - -```go -package main - -import ( - "net/http" - - "github.com/rs/cors" -) - -func main() { - h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{\"hello\": \"world\"}")) - }) - - // cors.Default() setup the middleware with default options being - // all origins accepted with simple methods (GET, POST). See - // documentation below for more options. - handler = cors.Default().Handler(h) - http.ListenAndServe(":8080", handler) -} -``` - -Install `cors`: - - go get github.com/rs/cors - -Then run your server: - - go run server.go - -The server now runs on `localhost:8080`: - - $ curl -D - -H 'Origin: http://foo.com' http://localhost:8080/ - HTTP/1.1 200 OK - Access-Control-Allow-Origin: foo.com - Content-Type: application/json - Date: Sat, 25 Oct 2014 03:43:57 GMT - Content-Length: 18 - - {"hello": "world"} - -### More Examples - -* `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go) -* [Goji](https://goji.io): [examples/goji/server.go](https://github.com/rs/cors/blob/master/examples/goji/server.go) -* [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go) -* [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go) -* [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go) - -## Parameters - -Parameters are passed to the middleware thru the `cors.New` method as follow: - -```go -c := cors.New(cors.Options{ - AllowedOrigins: []string{"http://foo.com"}, - AllowCredentials: true, -}) - -// Insert the middleware -handler = c.Handler(handler) -``` - -* **AllowedOrigins** `[]string`: A list of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. The default value is `*`. -* **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests. -* **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`) -* **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification -* **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`. -* **MaxAge** `int`: Indicates how long (in seconds) the results of a preflight request can be cached. The default is `0` which stands for no max age. - -See [API documentation](http://godoc.org/github.com/rs/cors) for more info. - -## Licenses - -All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE). diff --git a/Godeps/_workspace/src/github.com/rs/cors/bench_test.go b/Godeps/_workspace/src/github.com/rs/cors/bench_test.go deleted file mode 100644 index 454375d2cc..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/bench_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package cors - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -func BenchmarkWithout(b *testing.B) { - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - - for i := 0; i < b.N; i++ { - testHandler.ServeHTTP(res, req) - } -} - -func BenchmarkDefault(b *testing.B) { - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - handler := Default() - - for i := 0; i < b.N; i++ { - handler.Handler(testHandler).ServeHTTP(res, req) - } -} - -func BenchmarkPreflight(b *testing.B) { - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Access-Control-Request-Method", "GET") - handler := Default() - - for i := 0; i < b.N; i++ { - handler.Handler(testHandler).ServeHTTP(res, req) - } -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/cors.go b/Godeps/_workspace/src/github.com/rs/cors/cors.go deleted file mode 100644 index 276bc40bb2..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/cors.go +++ /dev/null @@ -1,308 +0,0 @@ -/* -Package cors is net/http handler to handle CORS related requests -as defined by http://www.w3.org/TR/cors/ - -You can configure it by passing an option struct to cors.New: - - c := cors.New(cors.Options{ - AllowedOrigins: []string{"foo.com"}, - AllowedMethods: []string{"GET", "POST", "DELETE"}, - AllowCredentials: true, - }) - -Then insert the handler in the chain: - - handler = c.Handler(handler) - -See Options documentation for more options. - -The resulting handler is a standard net/http handler. -*/ -package cors - -import ( - "log" - "net/http" - "os" - "strconv" - "strings" -) - -// Options is a configuration container to setup the CORS middleware. -type Options struct { - // AllowedOrigins is a list of origins a cross-domain request can be executed from. - // If the special "*" value is present in the list, all origins will be allowed. - // Default value is ["*"] - AllowedOrigins []string - // AllowedMethods is a list of methods the client is allowed to use with - // cross-domain requests. Default value is simple methods (GET and POST) - AllowedMethods []string - // AllowedHeaders is list of non simple headers the client is allowed to use with - // cross-domain requests. - // If the special "*" value is present in the list, all headers will be allowed. - // Default value is [] but "Origin" is always appended to the list. - AllowedHeaders []string - // ExposedHeaders indicates which headers are safe to expose to the API of a CORS - // API specification - ExposedHeaders []string - // AllowCredentials indicates whether the request can include user credentials like - // cookies, HTTP authentication or client side SSL certificates. - AllowCredentials bool - // MaxAge indicates how long (in seconds) the results of a preflight request - // can be cached - MaxAge int - // Debugging flag adds additional output to debug server side CORS issues - Debug bool - // log object to use when debugging - log *log.Logger -} - -type Cors struct { - // The CORS Options - options Options -} - -// New creates a new Cors handler with the provided options. -func New(options Options) *Cors { - // Normalize options - // Note: for origins and methods matching, the spec requires a case-sensitive matching. - // As it may error prone, we chose to ignore the spec here. - normOptions := Options{ - AllowedOrigins: convert(options.AllowedOrigins, strings.ToLower), - AllowedMethods: convert(options.AllowedMethods, strings.ToUpper), - // Origin is always appended as some browsers will always request - // for this header at preflight - AllowedHeaders: convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey), - ExposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey), - AllowCredentials: options.AllowCredentials, - MaxAge: options.MaxAge, - Debug: options.Debug, - log: log.New(os.Stdout, "[cors] ", log.LstdFlags), - } - if len(normOptions.AllowedOrigins) == 0 { - // Default is all origins - normOptions.AllowedOrigins = []string{"*"} - } - if len(normOptions.AllowedHeaders) == 1 { - // Add some sensible defaults - normOptions.AllowedHeaders = []string{"Origin", "Accept", "Content-Type"} - } - if len(normOptions.AllowedMethods) == 0 { - // Default is simple methods - normOptions.AllowedMethods = []string{"GET", "POST"} - } - - if normOptions.Debug { - normOptions.log.Printf("Options: %v", normOptions) - } - return &Cors{ - options: normOptions, - } -} - -// Default creates a new Cors handler with default options -func Default() *Cors { - return New(Options{}) -} - -// Handler apply the CORS specification on the request, and add relevant CORS headers -// as necessary. -func (cors *Cors) Handler(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "OPTIONS" { - cors.logf("Handler: Preflight request") - cors.handlePreflight(w, r) - // Preflight requests are standalone and should stop the chain as some other - // middleware may not handle OPTIONS requests correctly. One typical example - // is authentication middleware ; OPTIONS requests won't carry authentication - // headers (see #1) - } else { - cors.logf("Handler: Actual request") - cors.handleActualRequest(w, r) - h.ServeHTTP(w, r) - } - }) -} - -// Martini compatible handler -func (cors *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) { - if r.Method == "OPTIONS" { - cors.logf("HandlerFunc: Preflight request") - cors.handlePreflight(w, r) - } else { - cors.logf("HandlerFunc: Actual request") - cors.handleActualRequest(w, r) - } -} - -// Negroni compatible interface -func (cors *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - if r.Method == "OPTIONS" { - cors.logf("ServeHTTP: Preflight request") - cors.handlePreflight(w, r) - // Preflight requests are standalone and should stop the chain as some other - // middleware may not handle OPTIONS requests correctly. One typical example - // is authentication middleware ; OPTIONS requests won't carry authentication - // headers (see #1) - } else { - cors.logf("ServeHTTP: Actual request") - cors.handleActualRequest(w, r) - next(w, r) - } -} - -// handlePreflight handles pre-flight CORS requests -func (cors *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { - options := cors.options - headers := w.Header() - origin := r.Header.Get("Origin") - - if r.Method != "OPTIONS" { - cors.logf(" Preflight aborted: %s!=OPTIONS", r.Method) - return - } - if origin == "" { - cors.logf(" Preflight aborted: empty origin") - return - } - if !cors.isOriginAllowed(origin) { - cors.logf(" Preflight aborted: origin '%s' not allowed", origin) - return - } - - reqMethod := r.Header.Get("Access-Control-Request-Method") - if !cors.isMethodAllowed(reqMethod) { - cors.logf(" Preflight aborted: method '%s' not allowed", reqMethod) - return - } - reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers")) - if !cors.areHeadersAllowed(reqHeaders) { - cors.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders) - return - } - headers.Set("Access-Control-Allow-Origin", origin) - headers.Add("Vary", "Origin") - // Spec says: Since the list of methods can be unbounded, simply returning the method indicated - // by Access-Control-Request-Method (if supported) can be enough - headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod)) - if len(reqHeaders) > 0 { - - // Spec says: Since the list of headers can be unbounded, simply returning supported headers - // from Access-Control-Request-Headers can be enough - headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", ")) - } - if options.AllowCredentials { - headers.Set("Access-Control-Allow-Credentials", "true") - } - if options.MaxAge > 0 { - headers.Set("Access-Control-Max-Age", strconv.Itoa(options.MaxAge)) - } - cors.logf(" Preflight response headers: %v", headers) -} - -// handleActualRequest handles simple cross-origin requests, actual request or redirects -func (cors *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { - options := cors.options - headers := w.Header() - origin := r.Header.Get("Origin") - - if r.Method == "OPTIONS" { - cors.logf(" Actual request no headers added: method == %s", r.Method) - return - } - if origin == "" { - cors.logf(" Actual request no headers added: missing origin") - return - } - if !cors.isOriginAllowed(origin) { - cors.logf(" Actual request no headers added: origin '%s' not allowed", origin) - return - } - - // Note that spec does define a way to specifically disallow a simple method like GET or - // POST. Access-Control-Allow-Methods is only used for pre-flight requests and the - // spec doesn't instruct to check the allowed methods for simple cross-origin requests. - // We think it's a nice feature to be able to have control on those methods though. - if !cors.isMethodAllowed(r.Method) { - if cors.options.Debug { - cors.logf(" Actual request no headers added: method '%s' not allowed", - r.Method) - } - - return - } - headers.Set("Access-Control-Allow-Origin", origin) - headers.Add("Vary", "Origin") - if len(options.ExposedHeaders) > 0 { - headers.Set("Access-Control-Expose-Headers", strings.Join(options.ExposedHeaders, ", ")) - } - if options.AllowCredentials { - headers.Set("Access-Control-Allow-Credentials", "true") - } - cors.logf(" Actual response added headers: %v", headers) -} - -// convenience method. checks if debugging is turned on before printing -func (cors *Cors) logf(format string, a ...interface{}) { - if cors.options.Debug { - cors.options.log.Printf(format, a...) - } -} - -// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests -// on the endpoint -func (cors *Cors) isOriginAllowed(origin string) bool { - allowedOrigins := cors.options.AllowedOrigins - origin = strings.ToLower(origin) - for _, allowedOrigin := range allowedOrigins { - switch allowedOrigin { - case "*": - return true - case origin: - return true - } - } - return false -} - -// isMethodAllowed checks if a given method can be used as part of a cross-domain request -// on the endpoing -func (cors *Cors) isMethodAllowed(method string) bool { - allowedMethods := cors.options.AllowedMethods - if len(allowedMethods) == 0 { - // If no method allowed, always return false, even for preflight request - return false - } - method = strings.ToUpper(method) - if method == "OPTIONS" { - // Always allow preflight requests - return true - } - for _, allowedMethod := range allowedMethods { - if allowedMethod == method { - return true - } - } - return false -} - -// areHeadersAllowed checks if a given list of headers are allowed to used within -// a cross-domain request. -func (cors *Cors) areHeadersAllowed(requestedHeaders []string) bool { - if len(requestedHeaders) == 0 { - return true - } - for _, header := range requestedHeaders { - found := false - for _, allowedHeader := range cors.options.AllowedHeaders { - if allowedHeader == "*" || allowedHeader == header { - found = true - break - } - } - if !found { - return false - } - } - return true -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/cors_test.go b/Godeps/_workspace/src/github.com/rs/cors/cors_test.go deleted file mode 100644 index f215018c96..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/cors_test.go +++ /dev/null @@ -1,288 +0,0 @@ -package cors - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -var testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("bar")) -}) - -func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) { - for name, value := range reqHeaders { - if resHeaders.Get(name) != value { - t.Errorf("Invalid header `%s', wanted `%s', got `%s'", name, value, resHeaders.Get(name)) - } - } -} - -func TestNoConfig(t *testing.T) { - s := New(Options{ - // Intentionally left blank. - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestWildcardOrigin(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"*"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestAllowedOrigin(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestDisallowedOrigin(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://barbaz.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestAllowedMethod(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedMethods: []string{"PUT", "DELETE"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "PUT") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "PUT", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestDisallowedMethod(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedMethods: []string{"PUT", "DELETE"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "PATCH") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestAllowedHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedHeaders: []string{"X-Header-1", "x-header-2"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "X-Header-2, X-Header-1", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestAllowedWildcardHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedHeaders: []string{"*"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "X-Header-2, X-Header-1", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestDisallowedHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedHeaders: []string{"X-Header-1", "x-header-2"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - req.Header.Add("Access-Control-Request-Headers", "X-Header-3, X-Header-1") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestOriginHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - req.Header.Add("Access-Control-Request-Headers", "origin") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Origin", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestExposedHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - ExposedHeaders: []string{"X-Header-1", "x-header-2"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "X-Header-1, X-Header-2", - }) -} - -func TestAllowedCredentials(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowCredentials: true, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "true", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/alice/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/alice/server.go deleted file mode 100644 index 0a3e15cb8a..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/examples/alice/server.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/justinas/alice" - "github.com/rs/cors" -) - -func main() { - c := cors.New(cors.Options{ - AllowedOrigins: []string{"http://foo.com"}, - }) - - mux := http.NewServeMux() - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{\"hello\": \"world\"}")) - }) - - chain := alice.New(c.Handler).Then(mux) - http.ListenAndServe(":8080", chain) -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/default/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/default/server.go deleted file mode 100644 index 851ac41d01..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/examples/default/server.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/rs/cors" -) - -func main() { - h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{\"hello\": \"world\"}")) - }) - - // Use default options - handler := cors.Default().Handler(h) - http.ListenAndServe(":8080", handler) -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/goji/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/goji/server.go deleted file mode 100644 index 1fb4073aad..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/examples/goji/server.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/rs/cors" - "github.com/zenazn/goji" -) - -func main() { - c := cors.New(cors.Options{ - AllowedOrigins: []string{"http://foo.com"}, - }) - goji.Use(c.Handler) - - goji.Get("/", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{\"hello\": \"world\"}")) - }) - - goji.Serve() -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/martini/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/martini/server.go deleted file mode 100644 index 081af32f92..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/examples/martini/server.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "github.com/go-martini/martini" - "github.com/martini-contrib/render" - "github.com/rs/cors" -) - -func main() { - c := cors.New(cors.Options{ - AllowedOrigins: []string{"http://foo.com"}, - }) - - m := martini.Classic() - m.Use(render.Renderer()) - m.Use(c.HandlerFunc) - - m.Get("/", func(r render.Render) { - r.JSON(200, map[string]interface{}{"hello": "world"}) - }) - - m.Run() -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/negroni/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/negroni/server.go deleted file mode 100644 index 3cb33bff6f..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/examples/negroni/server.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/codegangsta/negroni" - "github.com/rs/cors" -) - -func main() { - c := cors.New(cors.Options{ - AllowedOrigins: []string{"http://foo.com"}, - }) - - mux := http.NewServeMux() - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{\"hello\": \"world\"}")) - }) - - n := negroni.Classic() - n.Use(c) - n.UseHandler(mux) - n.Run(":3000") -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/nethttp/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/nethttp/server.go deleted file mode 100644 index eaa775e443..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/examples/nethttp/server.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/rs/cors" -) - -func main() { - c := cors.New(cors.Options{ - AllowedOrigins: []string{"http://foo.com"}, - }) - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{\"hello\": \"world\"}")) - }) - - http.ListenAndServe(":8080", c.Handler(handler)) -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/examples/openbar/server.go b/Godeps/_workspace/src/github.com/rs/cors/examples/openbar/server.go deleted file mode 100644 index 0940423006..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/examples/openbar/server.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/rs/cors" -) - -func main() { - c := cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, - AllowCredentials: true, - }) - - h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{\"hello\": \"world\"}")) - }) - - http.ListenAndServe(":8080", c.Handler(h)) -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/utils.go b/Godeps/_workspace/src/github.com/rs/cors/utils.go deleted file mode 100644 index 429ab1114b..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/utils.go +++ /dev/null @@ -1,27 +0,0 @@ -package cors - -import ( - "net/http" - "strings" -) - -type converter func(string) string - -// convert converts a list of string using the passed converter function -func convert(s []string, c converter) []string { - out := []string{} - for _, i := range s { - out = append(out, c(i)) - } - return out -} - -func parseHeaderList(headerList string) (headers []string) { - for _, header := range strings.Split(headerList, ",") { - header = http.CanonicalHeaderKey(strings.TrimSpace(header)) - if header != "" { - headers = append(headers, header) - } - } - return headers -} diff --git a/Godeps/_workspace/src/github.com/rs/cors/utils_test.go b/Godeps/_workspace/src/github.com/rs/cors/utils_test.go deleted file mode 100644 index 3fc77fc1e0..0000000000 --- a/Godeps/_workspace/src/github.com/rs/cors/utils_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package cors - -import ( - "strings" - "testing" -) - -func TestConvert(t *testing.T) { - s := convert([]string{"A", "b", "C"}, strings.ToLower) - e := []string{"a", "b", "c"} - if s[0] != e[0] || s[1] != e[1] || s[2] != e[2] { - t.Errorf("%v != %v", s, e) - } -} - -func TestParseHeaderList(t *testing.T) { - h := parseHeaderList("header, second-header, THIRD-HEADER") - e := []string{"Header", "Second-Header", "Third-Header"} - if h[0] != e[0] || h[1] != e[1] || h[2] != e[2] { - t.Errorf("%v != %v", h, e) - } -} - -func TestParseHeaderListEmpty(t *testing.T) { - if len(parseHeaderList("")) != 0 { - t.Error("should be empty sclice") - } -} diff --git a/Godeps/_workspace/src/golang.org/x/net/websocket/client.go b/Godeps/_workspace/src/golang.org/x/net/websocket/client.go new file mode 100644 index 0000000000..20d1e1e38e --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/websocket/client.go @@ -0,0 +1,113 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "crypto/tls" + "io" + "net" + "net/http" + "net/url" +) + +// DialError is an error that occurs while dialling a websocket server. +type DialError struct { + *Config + Err error +} + +func (e *DialError) Error() string { + return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error() +} + +// NewConfig creates a new WebSocket config for client connection. +func NewConfig(server, origin string) (config *Config, err error) { + config = new(Config) + config.Version = ProtocolVersionHybi13 + config.Location, err = url.ParseRequestURI(server) + if err != nil { + return + } + config.Origin, err = url.ParseRequestURI(origin) + if err != nil { + return + } + config.Header = http.Header(make(map[string][]string)) + return +} + +// NewClient creates a new WebSocket client connection over rwc. +func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) { + br := bufio.NewReader(rwc) + bw := bufio.NewWriter(rwc) + err = hybiClientHandshake(config, br, bw) + if err != nil { + return + } + buf := bufio.NewReadWriter(br, bw) + ws = newHybiClientConn(config, buf, rwc) + return +} + +// Dial opens a new client connection to a WebSocket. +func Dial(url_, protocol, origin string) (ws *Conn, err error) { + config, err := NewConfig(url_, origin) + if err != nil { + return nil, err + } + if protocol != "" { + config.Protocol = []string{protocol} + } + return DialConfig(config) +} + +var portMap = map[string]string{ + "ws": "80", + "wss": "443", +} + +func parseAuthority(location *url.URL) string { + if _, ok := portMap[location.Scheme]; ok { + if _, _, err := net.SplitHostPort(location.Host); err != nil { + return net.JoinHostPort(location.Host, portMap[location.Scheme]) + } + } + return location.Host +} + +// DialConfig opens a new client connection to a WebSocket with a config. +func DialConfig(config *Config) (ws *Conn, err error) { + var client net.Conn + if config.Location == nil { + return nil, &DialError{config, ErrBadWebSocketLocation} + } + if config.Origin == nil { + return nil, &DialError{config, ErrBadWebSocketOrigin} + } + switch config.Location.Scheme { + case "ws": + client, err = net.Dial("tcp", parseAuthority(config.Location)) + + case "wss": + client, err = tls.Dial("tcp", parseAuthority(config.Location), config.TlsConfig) + + default: + err = ErrBadScheme + } + if err != nil { + goto Error + } + + ws, err = NewClient(config, client) + if err != nil { + client.Close() + goto Error + } + return + +Error: + return nil, &DialError{config, err} +} diff --git a/Godeps/_workspace/src/golang.org/x/net/websocket/exampledial_test.go b/Godeps/_workspace/src/golang.org/x/net/websocket/exampledial_test.go new file mode 100644 index 0000000000..72bb9d48eb --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/websocket/exampledial_test.go @@ -0,0 +1,31 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket_test + +import ( + "fmt" + "log" + + "golang.org/x/net/websocket" +) + +// This example demonstrates a trivial client. +func ExampleDial() { + origin := "http://localhost/" + url := "ws://localhost:12345/ws" + ws, err := websocket.Dial(url, "", origin) + if err != nil { + log.Fatal(err) + } + if _, err := ws.Write([]byte("hello, world!\n")); err != nil { + log.Fatal(err) + } + var msg = make([]byte, 512) + var n int + if n, err = ws.Read(msg); err != nil { + log.Fatal(err) + } + fmt.Printf("Received: %s.\n", msg[:n]) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/websocket/examplehandler_test.go b/Godeps/_workspace/src/golang.org/x/net/websocket/examplehandler_test.go new file mode 100644 index 0000000000..f22a98fcd4 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/websocket/examplehandler_test.go @@ -0,0 +1,26 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket_test + +import ( + "io" + "net/http" + + "golang.org/x/net/websocket" +) + +// Echo the data received on the WebSocket. +func EchoServer(ws *websocket.Conn) { + io.Copy(ws, ws) +} + +// This example demonstrates a trivial echo server. +func ExampleHandler() { + http.Handle("/echo", websocket.Handler(EchoServer)) + err := http.ListenAndServe(":12345", nil) + if err != nil { + panic("ListenAndServe: " + err.Error()) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/net/websocket/hybi.go b/Godeps/_workspace/src/golang.org/x/net/websocket/hybi.go new file mode 100644 index 0000000000..f8c0b2e299 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/websocket/hybi.go @@ -0,0 +1,564 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +// This file implements a protocol of hybi draft. +// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 + +import ( + "bufio" + "bytes" + "crypto/rand" + "crypto/sha1" + "encoding/base64" + "encoding/binary" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +const ( + websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + closeStatusNormal = 1000 + closeStatusGoingAway = 1001 + closeStatusProtocolError = 1002 + closeStatusUnsupportedData = 1003 + closeStatusFrameTooLarge = 1004 + closeStatusNoStatusRcvd = 1005 + closeStatusAbnormalClosure = 1006 + closeStatusBadMessageData = 1007 + closeStatusPolicyViolation = 1008 + closeStatusTooBigData = 1009 + closeStatusExtensionMismatch = 1010 + + maxControlFramePayloadLength = 125 +) + +var ( + ErrBadMaskingKey = &ProtocolError{"bad masking key"} + ErrBadPongMessage = &ProtocolError{"bad pong message"} + ErrBadClosingStatus = &ProtocolError{"bad closing status"} + ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"} + ErrNotImplemented = &ProtocolError{"not implemented"} + + handshakeHeader = map[string]bool{ + "Host": true, + "Upgrade": true, + "Connection": true, + "Sec-Websocket-Key": true, + "Sec-Websocket-Origin": true, + "Sec-Websocket-Version": true, + "Sec-Websocket-Protocol": true, + "Sec-Websocket-Accept": true, + } +) + +// A hybiFrameHeader is a frame header as defined in hybi draft. +type hybiFrameHeader struct { + Fin bool + Rsv [3]bool + OpCode byte + Length int64 + MaskingKey []byte + + data *bytes.Buffer +} + +// A hybiFrameReader is a reader for hybi frame. +type hybiFrameReader struct { + reader io.Reader + + header hybiFrameHeader + pos int64 + length int +} + +func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) { + n, err = frame.reader.Read(msg) + if err != nil { + return 0, err + } + if frame.header.MaskingKey != nil { + for i := 0; i < n; i++ { + msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4] + frame.pos++ + } + } + return n, err +} + +func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode } + +func (frame *hybiFrameReader) HeaderReader() io.Reader { + if frame.header.data == nil { + return nil + } + if frame.header.data.Len() == 0 { + return nil + } + return frame.header.data +} + +func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil } + +func (frame *hybiFrameReader) Len() (n int) { return frame.length } + +// A hybiFrameReaderFactory creates new frame reader based on its frame type. +type hybiFrameReaderFactory struct { + *bufio.Reader +} + +// NewFrameReader reads a frame header from the connection, and creates new reader for the frame. +// See Section 5.2 Base Framing protocol for detail. +// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2 +func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) { + hybiFrame := new(hybiFrameReader) + frame = hybiFrame + var header []byte + var b byte + // First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits) + b, err = buf.ReadByte() + if err != nil { + return + } + header = append(header, b) + hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0 + for i := 0; i < 3; i++ { + j := uint(6 - i) + hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0 + } + hybiFrame.header.OpCode = header[0] & 0x0f + + // Second byte. Mask/Payload len(7bits) + b, err = buf.ReadByte() + if err != nil { + return + } + header = append(header, b) + mask := (b & 0x80) != 0 + b &= 0x7f + lengthFields := 0 + switch { + case b <= 125: // Payload length 7bits. + hybiFrame.header.Length = int64(b) + case b == 126: // Payload length 7+16bits + lengthFields = 2 + case b == 127: // Payload length 7+64bits + lengthFields = 8 + } + for i := 0; i < lengthFields; i++ { + b, err = buf.ReadByte() + if err != nil { + return + } + header = append(header, b) + hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b) + } + if mask { + // Masking key. 4 bytes. + for i := 0; i < 4; i++ { + b, err = buf.ReadByte() + if err != nil { + return + } + header = append(header, b) + hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b) + } + } + hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length) + hybiFrame.header.data = bytes.NewBuffer(header) + hybiFrame.length = len(header) + int(hybiFrame.header.Length) + return +} + +// A HybiFrameWriter is a writer for hybi frame. +type hybiFrameWriter struct { + writer *bufio.Writer + + header *hybiFrameHeader +} + +func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) { + var header []byte + var b byte + if frame.header.Fin { + b |= 0x80 + } + for i := 0; i < 3; i++ { + if frame.header.Rsv[i] { + j := uint(6 - i) + b |= 1 << j + } + } + b |= frame.header.OpCode + header = append(header, b) + if frame.header.MaskingKey != nil { + b = 0x80 + } else { + b = 0 + } + lengthFields := 0 + length := len(msg) + switch { + case length <= 125: + b |= byte(length) + case length < 65536: + b |= 126 + lengthFields = 2 + default: + b |= 127 + lengthFields = 8 + } + header = append(header, b) + for i := 0; i < lengthFields; i++ { + j := uint((lengthFields - i - 1) * 8) + b = byte((length >> j) & 0xff) + header = append(header, b) + } + if frame.header.MaskingKey != nil { + if len(frame.header.MaskingKey) != 4 { + return 0, ErrBadMaskingKey + } + header = append(header, frame.header.MaskingKey...) + frame.writer.Write(header) + data := make([]byte, length) + for i := range data { + data[i] = msg[i] ^ frame.header.MaskingKey[i%4] + } + frame.writer.Write(data) + err = frame.writer.Flush() + return length, err + } + frame.writer.Write(header) + frame.writer.Write(msg) + err = frame.writer.Flush() + return length, err +} + +func (frame *hybiFrameWriter) Close() error { return nil } + +type hybiFrameWriterFactory struct { + *bufio.Writer + needMaskingKey bool +} + +func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) { + frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType} + if buf.needMaskingKey { + frameHeader.MaskingKey, err = generateMaskingKey() + if err != nil { + return nil, err + } + } + return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil +} + +type hybiFrameHandler struct { + conn *Conn + payloadType byte +} + +func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (r frameReader, err error) { + if handler.conn.IsServerConn() { + // The client MUST mask all frames sent to the server. + if frame.(*hybiFrameReader).header.MaskingKey == nil { + handler.WriteClose(closeStatusProtocolError) + return nil, io.EOF + } + } else { + // The server MUST NOT mask all frames. + if frame.(*hybiFrameReader).header.MaskingKey != nil { + handler.WriteClose(closeStatusProtocolError) + return nil, io.EOF + } + } + if header := frame.HeaderReader(); header != nil { + io.Copy(ioutil.Discard, header) + } + switch frame.PayloadType() { + case ContinuationFrame: + frame.(*hybiFrameReader).header.OpCode = handler.payloadType + case TextFrame, BinaryFrame: + handler.payloadType = frame.PayloadType() + case CloseFrame: + return nil, io.EOF + case PingFrame: + pingMsg := make([]byte, maxControlFramePayloadLength) + n, err := io.ReadFull(frame, pingMsg) + if err != nil && err != io.ErrUnexpectedEOF { + return nil, err + } + io.Copy(ioutil.Discard, frame) + n, err = handler.WritePong(pingMsg[:n]) + if err != nil { + return nil, err + } + return nil, nil + case PongFrame: + return nil, ErrNotImplemented + } + return frame, nil +} + +func (handler *hybiFrameHandler) WriteClose(status int) (err error) { + handler.conn.wio.Lock() + defer handler.conn.wio.Unlock() + w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame) + if err != nil { + return err + } + msg := make([]byte, 2) + binary.BigEndian.PutUint16(msg, uint16(status)) + _, err = w.Write(msg) + w.Close() + return err +} + +func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) { + handler.conn.wio.Lock() + defer handler.conn.wio.Unlock() + w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame) + if err != nil { + return 0, err + } + n, err = w.Write(msg) + w.Close() + return n, err +} + +// newHybiConn creates a new WebSocket connection speaking hybi draft protocol. +func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { + if buf == nil { + br := bufio.NewReader(rwc) + bw := bufio.NewWriter(rwc) + buf = bufio.NewReadWriter(br, bw) + } + ws := &Conn{config: config, request: request, buf: buf, rwc: rwc, + frameReaderFactory: hybiFrameReaderFactory{buf.Reader}, + frameWriterFactory: hybiFrameWriterFactory{ + buf.Writer, request == nil}, + PayloadType: TextFrame, + defaultCloseStatus: closeStatusNormal} + ws.frameHandler = &hybiFrameHandler{conn: ws} + return ws +} + +// generateMaskingKey generates a masking key for a frame. +func generateMaskingKey() (maskingKey []byte, err error) { + maskingKey = make([]byte, 4) + if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil { + return + } + return +} + +// generateNonce generates a nonce consisting of a randomly selected 16-byte +// value that has been base64-encoded. +func generateNonce() (nonce []byte) { + key := make([]byte, 16) + if _, err := io.ReadFull(rand.Reader, key); err != nil { + panic(err) + } + nonce = make([]byte, 24) + base64.StdEncoding.Encode(nonce, key) + return +} + +// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of +// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string. +func getNonceAccept(nonce []byte) (expected []byte, err error) { + h := sha1.New() + if _, err = h.Write(nonce); err != nil { + return + } + if _, err = h.Write([]byte(websocketGUID)); err != nil { + return + } + expected = make([]byte, 28) + base64.StdEncoding.Encode(expected, h.Sum(nil)) + return +} + +// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17 +func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) { + bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n") + + bw.WriteString("Host: " + config.Location.Host + "\r\n") + bw.WriteString("Upgrade: websocket\r\n") + bw.WriteString("Connection: Upgrade\r\n") + nonce := generateNonce() + if config.handshakeData != nil { + nonce = []byte(config.handshakeData["key"]) + } + bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n") + bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n") + + if config.Version != ProtocolVersionHybi13 { + return ErrBadProtocolVersion + } + + bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n") + if len(config.Protocol) > 0 { + bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n") + } + // TODO(ukai): send Sec-WebSocket-Extensions. + err = config.Header.WriteSubset(bw, handshakeHeader) + if err != nil { + return err + } + + bw.WriteString("\r\n") + if err = bw.Flush(); err != nil { + return err + } + + resp, err := http.ReadResponse(br, &http.Request{Method: "GET"}) + if err != nil { + return err + } + if resp.StatusCode != 101 { + return ErrBadStatus + } + if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" || + strings.ToLower(resp.Header.Get("Connection")) != "upgrade" { + return ErrBadUpgrade + } + expectedAccept, err := getNonceAccept(nonce) + if err != nil { + return err + } + if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) { + return ErrChallengeResponse + } + if resp.Header.Get("Sec-WebSocket-Extensions") != "" { + return ErrUnsupportedExtensions + } + offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol") + if offeredProtocol != "" { + protocolMatched := false + for i := 0; i < len(config.Protocol); i++ { + if config.Protocol[i] == offeredProtocol { + protocolMatched = true + break + } + } + if !protocolMatched { + return ErrBadWebSocketProtocol + } + config.Protocol = []string{offeredProtocol} + } + + return nil +} + +// newHybiClientConn creates a client WebSocket connection after handshake. +func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn { + return newHybiConn(config, buf, rwc, nil) +} + +// A HybiServerHandshaker performs a server handshake using hybi draft protocol. +type hybiServerHandshaker struct { + *Config + accept []byte +} + +func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) { + c.Version = ProtocolVersionHybi13 + if req.Method != "GET" { + return http.StatusMethodNotAllowed, ErrBadRequestMethod + } + // HTTP version can be safely ignored. + + if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" || + !strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") { + return http.StatusBadRequest, ErrNotWebSocket + } + + key := req.Header.Get("Sec-Websocket-Key") + if key == "" { + return http.StatusBadRequest, ErrChallengeResponse + } + version := req.Header.Get("Sec-Websocket-Version") + switch version { + case "13": + c.Version = ProtocolVersionHybi13 + default: + return http.StatusBadRequest, ErrBadWebSocketVersion + } + var scheme string + if req.TLS != nil { + scheme = "wss" + } else { + scheme = "ws" + } + c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI()) + if err != nil { + return http.StatusBadRequest, err + } + protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol")) + if protocol != "" { + protocols := strings.Split(protocol, ",") + for i := 0; i < len(protocols); i++ { + c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i])) + } + } + c.accept, err = getNonceAccept([]byte(key)) + if err != nil { + return http.StatusInternalServerError, err + } + return http.StatusSwitchingProtocols, nil +} + +// Origin parses Origin header in "req". +// If origin is "null", returns (nil, nil). +func Origin(config *Config, req *http.Request) (*url.URL, error) { + var origin string + switch config.Version { + case ProtocolVersionHybi13: + origin = req.Header.Get("Origin") + } + if origin == "null" { + return nil, nil + } + return url.ParseRequestURI(origin) +} + +func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) { + if len(c.Protocol) > 0 { + if len(c.Protocol) != 1 { + // You need choose a Protocol in Handshake func in Server. + return ErrBadWebSocketProtocol + } + } + buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n") + buf.WriteString("Upgrade: websocket\r\n") + buf.WriteString("Connection: Upgrade\r\n") + buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n") + if len(c.Protocol) > 0 { + buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n") + } + // TODO(ukai): send Sec-WebSocket-Extensions. + if c.Header != nil { + err := c.Header.WriteSubset(buf, handshakeHeader) + if err != nil { + return err + } + } + buf.WriteString("\r\n") + return buf.Flush() +} + +func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { + return newHybiServerConn(c.Config, buf, rwc, request) +} + +// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol. +func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { + return newHybiConn(config, buf, rwc, request) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/websocket/hybi_test.go b/Godeps/_workspace/src/golang.org/x/net/websocket/hybi_test.go new file mode 100644 index 0000000000..d6a19108a6 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/websocket/hybi_test.go @@ -0,0 +1,590 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "testing" +) + +// Test the getNonceAccept function with values in +// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 +func TestSecWebSocketAccept(t *testing.T) { + nonce := []byte("dGhlIHNhbXBsZSBub25jZQ==") + expected := []byte("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") + accept, err := getNonceAccept(nonce) + if err != nil { + t.Errorf("getNonceAccept: returned error %v", err) + return + } + if !bytes.Equal(expected, accept) { + t.Errorf("getNonceAccept: expected %q got %q", expected, accept) + } +} + +func TestHybiClientHandshake(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + bw := bufio.NewWriter(b) + br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= +Sec-WebSocket-Protocol: chat + +`)) + var err error + config := new(Config) + config.Location, err = url.ParseRequestURI("ws://server.example.com/chat") + if err != nil { + t.Fatal("location url", err) + } + config.Origin, err = url.ParseRequestURI("http://example.com") + if err != nil { + t.Fatal("origin url", err) + } + config.Protocol = append(config.Protocol, "chat") + config.Protocol = append(config.Protocol, "superchat") + config.Version = ProtocolVersionHybi13 + + config.handshakeData = map[string]string{ + "key": "dGhlIHNhbXBsZSBub25jZQ==", + } + err = hybiClientHandshake(config, br, bw) + if err != nil { + t.Errorf("handshake failed: %v", err) + } + req, err := http.ReadRequest(bufio.NewReader(b)) + if err != nil { + t.Fatalf("read request: %v", err) + } + if req.Method != "GET" { + t.Errorf("request method expected GET, but got %q", req.Method) + } + if req.URL.Path != "/chat" { + t.Errorf("request path expected /chat, but got %q", req.URL.Path) + } + if req.Proto != "HTTP/1.1" { + t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto) + } + if req.Host != "server.example.com" { + t.Errorf("request Host expected server.example.com, but got %v", req.Host) + } + var expectedHeader = map[string]string{ + "Connection": "Upgrade", + "Upgrade": "websocket", + "Sec-Websocket-Key": config.handshakeData["key"], + "Origin": config.Origin.String(), + "Sec-Websocket-Protocol": "chat, superchat", + "Sec-Websocket-Version": fmt.Sprintf("%d", ProtocolVersionHybi13), + } + for k, v := range expectedHeader { + if req.Header.Get(k) != v { + t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k))) + } + } +} + +func TestHybiClientHandshakeWithHeader(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + bw := bufio.NewWriter(b) + br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= +Sec-WebSocket-Protocol: chat + +`)) + var err error + config := new(Config) + config.Location, err = url.ParseRequestURI("ws://server.example.com/chat") + if err != nil { + t.Fatal("location url", err) + } + config.Origin, err = url.ParseRequestURI("http://example.com") + if err != nil { + t.Fatal("origin url", err) + } + config.Protocol = append(config.Protocol, "chat") + config.Protocol = append(config.Protocol, "superchat") + config.Version = ProtocolVersionHybi13 + config.Header = http.Header(make(map[string][]string)) + config.Header.Add("User-Agent", "test") + + config.handshakeData = map[string]string{ + "key": "dGhlIHNhbXBsZSBub25jZQ==", + } + err = hybiClientHandshake(config, br, bw) + if err != nil { + t.Errorf("handshake failed: %v", err) + } + req, err := http.ReadRequest(bufio.NewReader(b)) + if err != nil { + t.Fatalf("read request: %v", err) + } + if req.Method != "GET" { + t.Errorf("request method expected GET, but got %q", req.Method) + } + if req.URL.Path != "/chat" { + t.Errorf("request path expected /chat, but got %q", req.URL.Path) + } + if req.Proto != "HTTP/1.1" { + t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto) + } + if req.Host != "server.example.com" { + t.Errorf("request Host expected server.example.com, but got %v", req.Host) + } + var expectedHeader = map[string]string{ + "Connection": "Upgrade", + "Upgrade": "websocket", + "Sec-Websocket-Key": config.handshakeData["key"], + "Origin": config.Origin.String(), + "Sec-Websocket-Protocol": "chat, superchat", + "Sec-Websocket-Version": fmt.Sprintf("%d", ProtocolVersionHybi13), + "User-Agent": "test", + } + for k, v := range expectedHeader { + if req.Header.Get(k) != v { + t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k))) + } + } +} + +func TestHybiServerHandshake(t *testing.T) { + config := new(Config) + handshaker := &hybiServerHandshaker{Config: config} + br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 +Host: server.example.com +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== +Origin: http://example.com +Sec-WebSocket-Protocol: chat, superchat +Sec-WebSocket-Version: 13 + +`)) + req, err := http.ReadRequest(br) + if err != nil { + t.Fatal("request", err) + } + code, err := handshaker.ReadHandshake(br, req) + if err != nil { + t.Errorf("handshake failed: %v", err) + } + if code != http.StatusSwitchingProtocols { + t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code) + } + expectedProtocols := []string{"chat", "superchat"} + if fmt.Sprintf("%v", config.Protocol) != fmt.Sprintf("%v", expectedProtocols) { + t.Errorf("protocol expected %q but got %q", expectedProtocols, config.Protocol) + } + b := bytes.NewBuffer([]byte{}) + bw := bufio.NewWriter(b) + + config.Protocol = config.Protocol[:1] + + err = handshaker.AcceptHandshake(bw) + if err != nil { + t.Errorf("handshake response failed: %v", err) + } + expectedResponse := strings.Join([]string{ + "HTTP/1.1 101 Switching Protocols", + "Upgrade: websocket", + "Connection: Upgrade", + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", + "Sec-WebSocket-Protocol: chat", + "", ""}, "\r\n") + + if b.String() != expectedResponse { + t.Errorf("handshake expected %q but got %q", expectedResponse, b.String()) + } +} + +func TestHybiServerHandshakeNoSubProtocol(t *testing.T) { + config := new(Config) + handshaker := &hybiServerHandshaker{Config: config} + br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 +Host: server.example.com +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== +Origin: http://example.com +Sec-WebSocket-Version: 13 + +`)) + req, err := http.ReadRequest(br) + if err != nil { + t.Fatal("request", err) + } + code, err := handshaker.ReadHandshake(br, req) + if err != nil { + t.Errorf("handshake failed: %v", err) + } + if code != http.StatusSwitchingProtocols { + t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code) + } + if len(config.Protocol) != 0 { + t.Errorf("len(config.Protocol) expected 0, but got %q", len(config.Protocol)) + } + b := bytes.NewBuffer([]byte{}) + bw := bufio.NewWriter(b) + + err = handshaker.AcceptHandshake(bw) + if err != nil { + t.Errorf("handshake response failed: %v", err) + } + expectedResponse := strings.Join([]string{ + "HTTP/1.1 101 Switching Protocols", + "Upgrade: websocket", + "Connection: Upgrade", + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", + "", ""}, "\r\n") + + if b.String() != expectedResponse { + t.Errorf("handshake expected %q but got %q", expectedResponse, b.String()) + } +} + +func TestHybiServerHandshakeHybiBadVersion(t *testing.T) { + config := new(Config) + handshaker := &hybiServerHandshaker{Config: config} + br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 +Host: server.example.com +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== +Sec-WebSocket-Origin: http://example.com +Sec-WebSocket-Protocol: chat, superchat +Sec-WebSocket-Version: 9 + +`)) + req, err := http.ReadRequest(br) + if err != nil { + t.Fatal("request", err) + } + code, err := handshaker.ReadHandshake(br, req) + if err != ErrBadWebSocketVersion { + t.Errorf("handshake expected err %q but got %q", ErrBadWebSocketVersion, err) + } + if code != http.StatusBadRequest { + t.Errorf("status expected %q but got %q", http.StatusBadRequest, code) + } +} + +func testHybiFrame(t *testing.T, testHeader, testPayload, testMaskedPayload []byte, frameHeader *hybiFrameHeader) { + b := bytes.NewBuffer([]byte{}) + frameWriterFactory := &hybiFrameWriterFactory{bufio.NewWriter(b), false} + w, _ := frameWriterFactory.NewFrameWriter(TextFrame) + w.(*hybiFrameWriter).header = frameHeader + _, err := w.Write(testPayload) + w.Close() + if err != nil { + t.Errorf("Write error %q", err) + } + var expectedFrame []byte + expectedFrame = append(expectedFrame, testHeader...) + expectedFrame = append(expectedFrame, testMaskedPayload...) + if !bytes.Equal(expectedFrame, b.Bytes()) { + t.Errorf("frame expected %q got %q", expectedFrame, b.Bytes()) + } + frameReaderFactory := &hybiFrameReaderFactory{bufio.NewReader(b)} + r, err := frameReaderFactory.NewFrameReader() + if err != nil { + t.Errorf("Read error %q", err) + } + if header := r.HeaderReader(); header == nil { + t.Errorf("no header") + } else { + actualHeader := make([]byte, r.Len()) + n, err := header.Read(actualHeader) + if err != nil { + t.Errorf("Read header error %q", err) + } else { + if n < len(testHeader) { + t.Errorf("header too short %q got %q", testHeader, actualHeader[:n]) + } + if !bytes.Equal(testHeader, actualHeader[:n]) { + t.Errorf("header expected %q got %q", testHeader, actualHeader[:n]) + } + } + } + if trailer := r.TrailerReader(); trailer != nil { + t.Errorf("unexpected trailer %q", trailer) + } + frame := r.(*hybiFrameReader) + if frameHeader.Fin != frame.header.Fin || + frameHeader.OpCode != frame.header.OpCode || + len(testPayload) != int(frame.header.Length) { + t.Errorf("mismatch %v (%d) vs %v", frameHeader, len(testPayload), frame) + } + payload := make([]byte, len(testPayload)) + _, err = r.Read(payload) + if err != nil { + t.Errorf("read %v", err) + } + if !bytes.Equal(testPayload, payload) { + t.Errorf("payload %q vs %q", testPayload, payload) + } +} + +func TestHybiShortTextFrame(t *testing.T) { + frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame} + payload := []byte("hello") + testHybiFrame(t, []byte{0x81, 0x05}, payload, payload, frameHeader) + + payload = make([]byte, 125) + testHybiFrame(t, []byte{0x81, 125}, payload, payload, frameHeader) +} + +func TestHybiShortMaskedTextFrame(t *testing.T) { + frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame, + MaskingKey: []byte{0xcc, 0x55, 0x80, 0x20}} + payload := []byte("hello") + maskedPayload := []byte{0xa4, 0x30, 0xec, 0x4c, 0xa3} + header := []byte{0x81, 0x85} + header = append(header, frameHeader.MaskingKey...) + testHybiFrame(t, header, payload, maskedPayload, frameHeader) +} + +func TestHybiShortBinaryFrame(t *testing.T) { + frameHeader := &hybiFrameHeader{Fin: true, OpCode: BinaryFrame} + payload := []byte("hello") + testHybiFrame(t, []byte{0x82, 0x05}, payload, payload, frameHeader) + + payload = make([]byte, 125) + testHybiFrame(t, []byte{0x82, 125}, payload, payload, frameHeader) +} + +func TestHybiControlFrame(t *testing.T) { + frameHeader := &hybiFrameHeader{Fin: true, OpCode: PingFrame} + payload := []byte("hello") + testHybiFrame(t, []byte{0x89, 0x05}, payload, payload, frameHeader) + + frameHeader = &hybiFrameHeader{Fin: true, OpCode: PongFrame} + testHybiFrame(t, []byte{0x8A, 0x05}, payload, payload, frameHeader) + + frameHeader = &hybiFrameHeader{Fin: true, OpCode: CloseFrame} + payload = []byte{0x03, 0xe8} // 1000 + testHybiFrame(t, []byte{0x88, 0x02}, payload, payload, frameHeader) +} + +func TestHybiLongFrame(t *testing.T) { + frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame} + payload := make([]byte, 126) + testHybiFrame(t, []byte{0x81, 126, 0x00, 126}, payload, payload, frameHeader) + + payload = make([]byte, 65535) + testHybiFrame(t, []byte{0x81, 126, 0xff, 0xff}, payload, payload, frameHeader) + + payload = make([]byte, 65536) + testHybiFrame(t, []byte{0x81, 127, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, payload, payload, frameHeader) +} + +func TestHybiClientRead(t *testing.T) { + wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o', + 0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping + 0x81, 0x05, 'w', 'o', 'r', 'l', 'd'} + br := bufio.NewReader(bytes.NewBuffer(wireData)) + bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) + conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil) + + msg := make([]byte, 512) + n, err := conn.Read(msg) + if err != nil { + t.Errorf("read 1st frame, error %q", err) + } + if n != 5 { + t.Errorf("read 1st frame, expect 5, got %d", n) + } + if !bytes.Equal(wireData[2:7], msg[:n]) { + t.Errorf("read 1st frame %v, got %v", wireData[2:7], msg[:n]) + } + n, err = conn.Read(msg) + if err != nil { + t.Errorf("read 2nd frame, error %q", err) + } + if n != 5 { + t.Errorf("read 2nd frame, expect 5, got %d", n) + } + if !bytes.Equal(wireData[16:21], msg[:n]) { + t.Errorf("read 2nd frame %v, got %v", wireData[16:21], msg[:n]) + } + n, err = conn.Read(msg) + if err == nil { + t.Errorf("read not EOF") + } + if n != 0 { + t.Errorf("expect read 0, got %d", n) + } +} + +func TestHybiShortRead(t *testing.T) { + wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o', + 0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping + 0x81, 0x05, 'w', 'o', 'r', 'l', 'd'} + br := bufio.NewReader(bytes.NewBuffer(wireData)) + bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) + conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil) + + step := 0 + pos := 0 + expectedPos := []int{2, 5, 16, 19} + expectedLen := []int{3, 2, 3, 2} + for { + msg := make([]byte, 3) + n, err := conn.Read(msg) + if step >= len(expectedPos) { + if err == nil { + t.Errorf("read not EOF") + } + if n != 0 { + t.Errorf("expect read 0, got %d", n) + } + return + } + pos = expectedPos[step] + endPos := pos + expectedLen[step] + if err != nil { + t.Errorf("read from %d, got error %q", pos, err) + return + } + if n != endPos-pos { + t.Errorf("read from %d, expect %d, got %d", pos, endPos-pos, n) + } + if !bytes.Equal(wireData[pos:endPos], msg[:n]) { + t.Errorf("read from %d, frame %v, got %v", pos, wireData[pos:endPos], msg[:n]) + } + step++ + } +} + +func TestHybiServerRead(t *testing.T) { + wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20, + 0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello + 0x89, 0x85, 0xcc, 0x55, 0x80, 0x20, + 0xa4, 0x30, 0xec, 0x4c, 0xa3, // ping: hello + 0x81, 0x85, 0xed, 0x83, 0xb4, 0x24, + 0x9a, 0xec, 0xc6, 0x48, 0x89, // world + } + br := bufio.NewReader(bytes.NewBuffer(wireData)) + bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) + conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request)) + + expected := [][]byte{[]byte("hello"), []byte("world")} + + msg := make([]byte, 512) + n, err := conn.Read(msg) + if err != nil { + t.Errorf("read 1st frame, error %q", err) + } + if n != 5 { + t.Errorf("read 1st frame, expect 5, got %d", n) + } + if !bytes.Equal(expected[0], msg[:n]) { + t.Errorf("read 1st frame %q, got %q", expected[0], msg[:n]) + } + + n, err = conn.Read(msg) + if err != nil { + t.Errorf("read 2nd frame, error %q", err) + } + if n != 5 { + t.Errorf("read 2nd frame, expect 5, got %d", n) + } + if !bytes.Equal(expected[1], msg[:n]) { + t.Errorf("read 2nd frame %q, got %q", expected[1], msg[:n]) + } + + n, err = conn.Read(msg) + if err == nil { + t.Errorf("read not EOF") + } + if n != 0 { + t.Errorf("expect read 0, got %d", n) + } +} + +func TestHybiServerReadWithoutMasking(t *testing.T) { + wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o'} + br := bufio.NewReader(bytes.NewBuffer(wireData)) + bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) + conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request)) + // server MUST close the connection upon receiving a non-masked frame. + msg := make([]byte, 512) + _, err := conn.Read(msg) + if err != io.EOF { + t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err) + } +} + +func TestHybiClientReadWithMasking(t *testing.T) { + wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20, + 0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello + } + br := bufio.NewReader(bytes.NewBuffer(wireData)) + bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) + conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil) + + // client MUST close the connection upon receiving a masked frame. + msg := make([]byte, 512) + _, err := conn.Read(msg) + if err != io.EOF { + t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err) + } +} + +// Test the hybiServerHandshaker supports firefox implementation and +// checks Connection request header include (but it's not necessary +// equal to) "upgrade" +func TestHybiServerFirefoxHandshake(t *testing.T) { + config := new(Config) + handshaker := &hybiServerHandshaker{Config: config} + br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 +Host: server.example.com +Upgrade: websocket +Connection: keep-alive, upgrade +Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== +Origin: http://example.com +Sec-WebSocket-Protocol: chat, superchat +Sec-WebSocket-Version: 13 + +`)) + req, err := http.ReadRequest(br) + if err != nil { + t.Fatal("request", err) + } + code, err := handshaker.ReadHandshake(br, req) + if err != nil { + t.Errorf("handshake failed: %v", err) + } + if code != http.StatusSwitchingProtocols { + t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code) + } + b := bytes.NewBuffer([]byte{}) + bw := bufio.NewWriter(b) + + config.Protocol = []string{"chat"} + + err = handshaker.AcceptHandshake(bw) + if err != nil { + t.Errorf("handshake response failed: %v", err) + } + expectedResponse := strings.Join([]string{ + "HTTP/1.1 101 Switching Protocols", + "Upgrade: websocket", + "Connection: Upgrade", + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", + "Sec-WebSocket-Protocol: chat", + "", ""}, "\r\n") + + if b.String() != expectedResponse { + t.Errorf("handshake expected %q but got %q", expectedResponse, b.String()) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/net/websocket/server.go b/Godeps/_workspace/src/golang.org/x/net/websocket/server.go new file mode 100644 index 0000000000..70322133c4 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/websocket/server.go @@ -0,0 +1,114 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "fmt" + "io" + "net/http" +) + +func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) { + var hs serverHandshaker = &hybiServerHandshaker{Config: config} + code, err := hs.ReadHandshake(buf.Reader, req) + if err == ErrBadWebSocketVersion { + fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) + fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion) + buf.WriteString("\r\n") + buf.WriteString(err.Error()) + buf.Flush() + return + } + if err != nil { + fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) + buf.WriteString("\r\n") + buf.WriteString(err.Error()) + buf.Flush() + return + } + if handshake != nil { + err = handshake(config, req) + if err != nil { + code = http.StatusForbidden + fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) + buf.WriteString("\r\n") + buf.Flush() + return + } + } + err = hs.AcceptHandshake(buf.Writer) + if err != nil { + code = http.StatusBadRequest + fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) + buf.WriteString("\r\n") + buf.Flush() + return + } + conn = hs.NewServerConn(buf, rwc, req) + return +} + +// Server represents a server of a WebSocket. +type Server struct { + // Config is a WebSocket configuration for new WebSocket connection. + Config + + // Handshake is an optional function in WebSocket handshake. + // For example, you can check, or don't check Origin header. + // Another example, you can select config.Protocol. + Handshake func(*Config, *http.Request) error + + // Handler handles a WebSocket connection. + Handler +} + +// ServeHTTP implements the http.Handler interface for a WebSocket +func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { + s.serveWebSocket(w, req) +} + +func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) { + rwc, buf, err := w.(http.Hijacker).Hijack() + if err != nil { + panic("Hijack failed: " + err.Error()) + return + } + // The server should abort the WebSocket connection if it finds + // the client did not send a handshake that matches with protocol + // specification. + defer rwc.Close() + conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake) + if err != nil { + return + } + if conn == nil { + panic("unexpected nil conn") + } + s.Handler(conn) +} + +// Handler is a simple interface to a WebSocket browser client. +// It checks if Origin header is valid URL by default. +// You might want to verify websocket.Conn.Config().Origin in the func. +// If you use Server instead of Handler, you could call websocket.Origin and +// check the origin in your Handshake func. So, if you want to accept +// non-browser client, which doesn't send Origin header, you could use Server +//. that doesn't check origin in its Handshake. +type Handler func(*Conn) + +func checkOrigin(config *Config, req *http.Request) (err error) { + config.Origin, err = Origin(config, req) + if err == nil && config.Origin == nil { + return fmt.Errorf("null origin") + } + return err +} + +// ServeHTTP implements the http.Handler interface for a WebSocket +func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + s := Server{Handler: h, Handshake: checkOrigin} + s.serveWebSocket(w, req) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/websocket/websocket.go b/Godeps/_workspace/src/golang.org/x/net/websocket/websocket.go new file mode 100644 index 0000000000..0f4917bf7e --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/websocket/websocket.go @@ -0,0 +1,411 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package websocket implements a client and server for the WebSocket protocol +// as specified in RFC 6455. +package websocket + +import ( + "bufio" + "crypto/tls" + "encoding/json" + "errors" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "sync" + "time" +) + +const ( + ProtocolVersionHybi13 = 13 + ProtocolVersionHybi = ProtocolVersionHybi13 + SupportedProtocolVersion = "13" + + ContinuationFrame = 0 + TextFrame = 1 + BinaryFrame = 2 + CloseFrame = 8 + PingFrame = 9 + PongFrame = 10 + UnknownFrame = 255 +) + +// ProtocolError represents WebSocket protocol errors. +type ProtocolError struct { + ErrorString string +} + +func (err *ProtocolError) Error() string { return err.ErrorString } + +var ( + ErrBadProtocolVersion = &ProtocolError{"bad protocol version"} + ErrBadScheme = &ProtocolError{"bad scheme"} + ErrBadStatus = &ProtocolError{"bad status"} + ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"} + ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"} + ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"} + ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"} + ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"} + ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"} + ErrBadFrame = &ProtocolError{"bad frame"} + ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"} + ErrNotWebSocket = &ProtocolError{"not websocket protocol"} + ErrBadRequestMethod = &ProtocolError{"bad method"} + ErrNotSupported = &ProtocolError{"not supported"} +) + +// Addr is an implementation of net.Addr for WebSocket. +type Addr struct { + *url.URL +} + +// Network returns the network type for a WebSocket, "websocket". +func (addr *Addr) Network() string { return "websocket" } + +// Config is a WebSocket configuration +type Config struct { + // A WebSocket server address. + Location *url.URL + + // A Websocket client origin. + Origin *url.URL + + // WebSocket subprotocols. + Protocol []string + + // WebSocket protocol version. + Version int + + // TLS config for secure WebSocket (wss). + TlsConfig *tls.Config + + // Additional header fields to be sent in WebSocket opening handshake. + Header http.Header + + handshakeData map[string]string +} + +// serverHandshaker is an interface to handle WebSocket server side handshake. +type serverHandshaker interface { + // ReadHandshake reads handshake request message from client. + // Returns http response code and error if any. + ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) + + // AcceptHandshake accepts the client handshake request and sends + // handshake response back to client. + AcceptHandshake(buf *bufio.Writer) (err error) + + // NewServerConn creates a new WebSocket connection. + NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) +} + +// frameReader is an interface to read a WebSocket frame. +type frameReader interface { + // Reader is to read payload of the frame. + io.Reader + + // PayloadType returns payload type. + PayloadType() byte + + // HeaderReader returns a reader to read header of the frame. + HeaderReader() io.Reader + + // TrailerReader returns a reader to read trailer of the frame. + // If it returns nil, there is no trailer in the frame. + TrailerReader() io.Reader + + // Len returns total length of the frame, including header and trailer. + Len() int +} + +// frameReaderFactory is an interface to creates new frame reader. +type frameReaderFactory interface { + NewFrameReader() (r frameReader, err error) +} + +// frameWriter is an interface to write a WebSocket frame. +type frameWriter interface { + // Writer is to write payload of the frame. + io.WriteCloser +} + +// frameWriterFactory is an interface to create new frame writer. +type frameWriterFactory interface { + NewFrameWriter(payloadType byte) (w frameWriter, err error) +} + +type frameHandler interface { + HandleFrame(frame frameReader) (r frameReader, err error) + WriteClose(status int) (err error) +} + +// Conn represents a WebSocket connection. +type Conn struct { + config *Config + request *http.Request + + buf *bufio.ReadWriter + rwc io.ReadWriteCloser + + rio sync.Mutex + frameReaderFactory + frameReader + + wio sync.Mutex + frameWriterFactory + + frameHandler + PayloadType byte + defaultCloseStatus int +} + +// Read implements the io.Reader interface: +// it reads data of a frame from the WebSocket connection. +// if msg is not large enough for the frame data, it fills the msg and next Read +// will read the rest of the frame data. +// it reads Text frame or Binary frame. +func (ws *Conn) Read(msg []byte) (n int, err error) { + ws.rio.Lock() + defer ws.rio.Unlock() +again: + if ws.frameReader == nil { + frame, err := ws.frameReaderFactory.NewFrameReader() + if err != nil { + return 0, err + } + ws.frameReader, err = ws.frameHandler.HandleFrame(frame) + if err != nil { + return 0, err + } + if ws.frameReader == nil { + goto again + } + } + n, err = ws.frameReader.Read(msg) + if err == io.EOF { + if trailer := ws.frameReader.TrailerReader(); trailer != nil { + io.Copy(ioutil.Discard, trailer) + } + ws.frameReader = nil + goto again + } + return n, err +} + +// Write implements the io.Writer interface: +// it writes data as a frame to the WebSocket connection. +func (ws *Conn) Write(msg []byte) (n int, err error) { + ws.wio.Lock() + defer ws.wio.Unlock() + w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType) + if err != nil { + return 0, err + } + n, err = w.Write(msg) + w.Close() + if err != nil { + return n, err + } + return n, err +} + +// Close implements the io.Closer interface. +func (ws *Conn) Close() error { + err := ws.frameHandler.WriteClose(ws.defaultCloseStatus) + if err != nil { + return err + } + return ws.rwc.Close() +} + +func (ws *Conn) IsClientConn() bool { return ws.request == nil } +func (ws *Conn) IsServerConn() bool { return ws.request != nil } + +// LocalAddr returns the WebSocket Origin for the connection for client, or +// the WebSocket location for server. +func (ws *Conn) LocalAddr() net.Addr { + if ws.IsClientConn() { + return &Addr{ws.config.Origin} + } + return &Addr{ws.config.Location} +} + +// RemoteAddr returns the WebSocket location for the connection for client, or +// the Websocket Origin for server. +func (ws *Conn) RemoteAddr() net.Addr { + if ws.IsClientConn() { + return &Addr{ws.config.Location} + } + return &Addr{ws.config.Origin} +} + +var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn") + +// SetDeadline sets the connection's network read & write deadlines. +func (ws *Conn) SetDeadline(t time.Time) error { + if conn, ok := ws.rwc.(net.Conn); ok { + return conn.SetDeadline(t) + } + return errSetDeadline +} + +// SetReadDeadline sets the connection's network read deadline. +func (ws *Conn) SetReadDeadline(t time.Time) error { + if conn, ok := ws.rwc.(net.Conn); ok { + return conn.SetReadDeadline(t) + } + return errSetDeadline +} + +// SetWriteDeadline sets the connection's network write deadline. +func (ws *Conn) SetWriteDeadline(t time.Time) error { + if conn, ok := ws.rwc.(net.Conn); ok { + return conn.SetWriteDeadline(t) + } + return errSetDeadline +} + +// Config returns the WebSocket config. +func (ws *Conn) Config() *Config { return ws.config } + +// Request returns the http request upgraded to the WebSocket. +// It is nil for client side. +func (ws *Conn) Request() *http.Request { return ws.request } + +// Codec represents a symmetric pair of functions that implement a codec. +type Codec struct { + Marshal func(v interface{}) (data []byte, payloadType byte, err error) + Unmarshal func(data []byte, payloadType byte, v interface{}) (err error) +} + +// Send sends v marshaled by cd.Marshal as single frame to ws. +func (cd Codec) Send(ws *Conn, v interface{}) (err error) { + data, payloadType, err := cd.Marshal(v) + if err != nil { + return err + } + ws.wio.Lock() + defer ws.wio.Unlock() + w, err := ws.frameWriterFactory.NewFrameWriter(payloadType) + if err != nil { + return err + } + _, err = w.Write(data) + w.Close() + return err +} + +// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores in v. +func (cd Codec) Receive(ws *Conn, v interface{}) (err error) { + ws.rio.Lock() + defer ws.rio.Unlock() + if ws.frameReader != nil { + _, err = io.Copy(ioutil.Discard, ws.frameReader) + if err != nil { + return err + } + ws.frameReader = nil + } +again: + frame, err := ws.frameReaderFactory.NewFrameReader() + if err != nil { + return err + } + frame, err = ws.frameHandler.HandleFrame(frame) + if err != nil { + return err + } + if frame == nil { + goto again + } + payloadType := frame.PayloadType() + data, err := ioutil.ReadAll(frame) + if err != nil { + return err + } + return cd.Unmarshal(data, payloadType, v) +} + +func marshal(v interface{}) (msg []byte, payloadType byte, err error) { + switch data := v.(type) { + case string: + return []byte(data), TextFrame, nil + case []byte: + return data, BinaryFrame, nil + } + return nil, UnknownFrame, ErrNotSupported +} + +func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) { + switch data := v.(type) { + case *string: + *data = string(msg) + return nil + case *[]byte: + *data = msg + return nil + } + return ErrNotSupported +} + +/* +Message is a codec to send/receive text/binary data in a frame on WebSocket connection. +To send/receive text frame, use string type. +To send/receive binary frame, use []byte type. + +Trivial usage: + + import "websocket" + + // receive text frame + var message string + websocket.Message.Receive(ws, &message) + + // send text frame + message = "hello" + websocket.Message.Send(ws, message) + + // receive binary frame + var data []byte + websocket.Message.Receive(ws, &data) + + // send binary frame + data = []byte{0, 1, 2} + websocket.Message.Send(ws, data) + +*/ +var Message = Codec{marshal, unmarshal} + +func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) { + msg, err = json.Marshal(v) + return msg, TextFrame, err +} + +func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) { + return json.Unmarshal(msg, v) +} + +/* +JSON is a codec to send/receive JSON data in a frame from a WebSocket connection. + +Trivial usage: + + import "websocket" + + type T struct { + Msg string + Count int + } + + // receive JSON type T + var data T + websocket.JSON.Receive(ws, &data) + + // send JSON type T + websocket.JSON.Send(ws, data) +*/ +var JSON = Codec{jsonMarshal, jsonUnmarshal} diff --git a/Godeps/_workspace/src/golang.org/x/net/websocket/websocket_test.go b/Godeps/_workspace/src/golang.org/x/net/websocket/websocket_test.go new file mode 100644 index 0000000000..a376abacf1 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/websocket/websocket_test.go @@ -0,0 +1,414 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bytes" + "fmt" + "io" + "log" + "net" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync" + "testing" +) + +var serverAddr string +var once sync.Once + +func echoServer(ws *Conn) { io.Copy(ws, ws) } + +type Count struct { + S string + N int +} + +func countServer(ws *Conn) { + for { + var count Count + err := JSON.Receive(ws, &count) + if err != nil { + return + } + count.N++ + count.S = strings.Repeat(count.S, count.N) + err = JSON.Send(ws, count) + if err != nil { + return + } + } +} + +func subProtocolHandshake(config *Config, req *http.Request) error { + for _, proto := range config.Protocol { + if proto == "chat" { + config.Protocol = []string{proto} + return nil + } + } + return ErrBadWebSocketProtocol +} + +func subProtoServer(ws *Conn) { + for _, proto := range ws.Config().Protocol { + io.WriteString(ws, proto) + } +} + +func startServer() { + http.Handle("/echo", Handler(echoServer)) + http.Handle("/count", Handler(countServer)) + subproto := Server{ + Handshake: subProtocolHandshake, + Handler: Handler(subProtoServer), + } + http.Handle("/subproto", subproto) + server := httptest.NewServer(nil) + serverAddr = server.Listener.Addr().String() + log.Print("Test WebSocket server listening on ", serverAddr) +} + +func newConfig(t *testing.T, path string) *Config { + config, _ := NewConfig(fmt.Sprintf("ws://%s%s", serverAddr, path), "http://localhost") + return config +} + +func TestEcho(t *testing.T) { + once.Do(startServer) + + // websocket.Dial() + client, err := net.Dial("tcp", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + conn, err := NewClient(newConfig(t, "/echo"), client) + if err != nil { + t.Errorf("WebSocket handshake error: %v", err) + return + } + + msg := []byte("hello, world\n") + if _, err := conn.Write(msg); err != nil { + t.Errorf("Write: %v", err) + } + var actual_msg = make([]byte, 512) + n, err := conn.Read(actual_msg) + if err != nil { + t.Errorf("Read: %v", err) + } + actual_msg = actual_msg[0:n] + if !bytes.Equal(msg, actual_msg) { + t.Errorf("Echo: expected %q got %q", msg, actual_msg) + } + conn.Close() +} + +func TestAddr(t *testing.T) { + once.Do(startServer) + + // websocket.Dial() + client, err := net.Dial("tcp", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + conn, err := NewClient(newConfig(t, "/echo"), client) + if err != nil { + t.Errorf("WebSocket handshake error: %v", err) + return + } + + ra := conn.RemoteAddr().String() + if !strings.HasPrefix(ra, "ws://") || !strings.HasSuffix(ra, "/echo") { + t.Errorf("Bad remote addr: %v", ra) + } + la := conn.LocalAddr().String() + if !strings.HasPrefix(la, "http://") { + t.Errorf("Bad local addr: %v", la) + } + conn.Close() +} + +func TestCount(t *testing.T) { + once.Do(startServer) + + // websocket.Dial() + client, err := net.Dial("tcp", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + conn, err := NewClient(newConfig(t, "/count"), client) + if err != nil { + t.Errorf("WebSocket handshake error: %v", err) + return + } + + var count Count + count.S = "hello" + if err := JSON.Send(conn, count); err != nil { + t.Errorf("Write: %v", err) + } + if err := JSON.Receive(conn, &count); err != nil { + t.Errorf("Read: %v", err) + } + if count.N != 1 { + t.Errorf("count: expected %d got %d", 1, count.N) + } + if count.S != "hello" { + t.Errorf("count: expected %q got %q", "hello", count.S) + } + if err := JSON.Send(conn, count); err != nil { + t.Errorf("Write: %v", err) + } + if err := JSON.Receive(conn, &count); err != nil { + t.Errorf("Read: %v", err) + } + if count.N != 2 { + t.Errorf("count: expected %d got %d", 2, count.N) + } + if count.S != "hellohello" { + t.Errorf("count: expected %q got %q", "hellohello", count.S) + } + conn.Close() +} + +func TestWithQuery(t *testing.T) { + once.Do(startServer) + + client, err := net.Dial("tcp", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + + config := newConfig(t, "/echo") + config.Location, err = url.ParseRequestURI(fmt.Sprintf("ws://%s/echo?q=v", serverAddr)) + if err != nil { + t.Fatal("location url", err) + } + + ws, err := NewClient(config, client) + if err != nil { + t.Errorf("WebSocket handshake: %v", err) + return + } + ws.Close() +} + +func testWithProtocol(t *testing.T, subproto []string) (string, error) { + once.Do(startServer) + + client, err := net.Dial("tcp", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + + config := newConfig(t, "/subproto") + config.Protocol = subproto + + ws, err := NewClient(config, client) + if err != nil { + return "", err + } + msg := make([]byte, 16) + n, err := ws.Read(msg) + if err != nil { + return "", err + } + ws.Close() + return string(msg[:n]), nil +} + +func TestWithProtocol(t *testing.T) { + proto, err := testWithProtocol(t, []string{"chat"}) + if err != nil { + t.Errorf("SubProto: unexpected error: %v", err) + } + if proto != "chat" { + t.Errorf("SubProto: expected %q, got %q", "chat", proto) + } +} + +func TestWithTwoProtocol(t *testing.T) { + proto, err := testWithProtocol(t, []string{"test", "chat"}) + if err != nil { + t.Errorf("SubProto: unexpected error: %v", err) + } + if proto != "chat" { + t.Errorf("SubProto: expected %q, got %q", "chat", proto) + } +} + +func TestWithBadProtocol(t *testing.T) { + _, err := testWithProtocol(t, []string{"test"}) + if err != ErrBadStatus { + t.Errorf("SubProto: expected %v, got %v", ErrBadStatus, err) + } +} + +func TestHTTP(t *testing.T) { + once.Do(startServer) + + // If the client did not send a handshake that matches the protocol + // specification, the server MUST return an HTTP response with an + // appropriate error code (such as 400 Bad Request) + resp, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr)) + if err != nil { + t.Errorf("Get: error %#v", err) + return + } + if resp == nil { + t.Error("Get: resp is null") + return + } + if resp.StatusCode != http.StatusBadRequest { + t.Errorf("Get: expected %q got %q", http.StatusBadRequest, resp.StatusCode) + } +} + +func TestTrailingSpaces(t *testing.T) { + // http://code.google.com/p/go/issues/detail?id=955 + // The last runs of this create keys with trailing spaces that should not be + // generated by the client. + once.Do(startServer) + config := newConfig(t, "/echo") + for i := 0; i < 30; i++ { + // body + ws, err := DialConfig(config) + if err != nil { + t.Errorf("Dial #%d failed: %v", i, err) + break + } + ws.Close() + } +} + +func TestDialConfigBadVersion(t *testing.T) { + once.Do(startServer) + config := newConfig(t, "/echo") + config.Version = 1234 + + _, err := DialConfig(config) + + if dialerr, ok := err.(*DialError); ok { + if dialerr.Err != ErrBadProtocolVersion { + t.Errorf("dial expected err %q but got %q", ErrBadProtocolVersion, dialerr.Err) + } + } +} + +func TestSmallBuffer(t *testing.T) { + // http://code.google.com/p/go/issues/detail?id=1145 + // Read should be able to handle reading a fragment of a frame. + once.Do(startServer) + + // websocket.Dial() + client, err := net.Dial("tcp", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + conn, err := NewClient(newConfig(t, "/echo"), client) + if err != nil { + t.Errorf("WebSocket handshake error: %v", err) + return + } + + msg := []byte("hello, world\n") + if _, err := conn.Write(msg); err != nil { + t.Errorf("Write: %v", err) + } + var small_msg = make([]byte, 8) + n, err := conn.Read(small_msg) + if err != nil { + t.Errorf("Read: %v", err) + } + if !bytes.Equal(msg[:len(small_msg)], small_msg) { + t.Errorf("Echo: expected %q got %q", msg[:len(small_msg)], small_msg) + } + var second_msg = make([]byte, len(msg)) + n, err = conn.Read(second_msg) + if err != nil { + t.Errorf("Read: %v", err) + } + second_msg = second_msg[0:n] + if !bytes.Equal(msg[len(small_msg):], second_msg) { + t.Errorf("Echo: expected %q got %q", msg[len(small_msg):], second_msg) + } + conn.Close() +} + +var parseAuthorityTests = []struct { + in *url.URL + out string +}{ + { + &url.URL{ + Scheme: "ws", + Host: "www.google.com", + }, + "www.google.com:80", + }, + { + &url.URL{ + Scheme: "wss", + Host: "www.google.com", + }, + "www.google.com:443", + }, + { + &url.URL{ + Scheme: "ws", + Host: "www.google.com:80", + }, + "www.google.com:80", + }, + { + &url.URL{ + Scheme: "wss", + Host: "www.google.com:443", + }, + "www.google.com:443", + }, + // some invalid ones for parseAuthority. parseAuthority doesn't + // concern itself with the scheme unless it actually knows about it + { + &url.URL{ + Scheme: "http", + Host: "www.google.com", + }, + "www.google.com", + }, + { + &url.URL{ + Scheme: "http", + Host: "www.google.com:80", + }, + "www.google.com:80", + }, + { + &url.URL{ + Scheme: "asdf", + Host: "127.0.0.1", + }, + "127.0.0.1", + }, + { + &url.URL{ + Scheme: "asdf", + Host: "www.google.com", + }, + "www.google.com", + }, +} + +func TestParseAuthority(t *testing.T) { + for _, tt := range parseAuthorityTests { + out := parseAuthority(tt.in) + if out != tt.out { + t.Errorf("got %v; want %v", out, tt.out) + } + } +} diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 74006395cd..34cf0fa537 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -100,7 +100,7 @@ func (am *Manager) Lock(addr common.Address) error { am.mutex.Lock() if unl, found := am.unlocked[addr]; found { am.mutex.Unlock() - am.expire(addr, unl, time.Duration(0) * time.Nanosecond) + am.expire(addr, unl, time.Duration(0)*time.Nanosecond) } else { am.mutex.Unlock() } diff --git a/cmd/geth/js.go b/cmd/geth/js.go index cdafab7fa1..3d0251f089 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -24,23 +24,16 @@ import ( "os/signal" "path/filepath" "regexp" - "strings" - "sort" + "strings" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/natspec" "github.com/ethereum/go-ethereum/common/registrar" "github.com/ethereum/go-ethereum/eth" re "github.com/ethereum/go-ethereum/jsre" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/rpc/api" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" "github.com/peterh/liner" "github.com/robertkrimen/otto" ) @@ -79,82 +72,90 @@ func (r dumbterm) AppendHistory(string) {} type jsre struct { re *re.JSRE stack *node.Node - xeth *xeth.XEth wait chan *big.Int ps1 string atexit func() corsDomain string - client comms.EthereumClient + client rpc.Client prompter } var ( - loadedModulesMethods map[string][]string + loadedModulesMethods map[string][]string + autoCompleteStatement = "function _autocomplete(obj) {var results = []; for (var e in obj) { results.push(e); }; return results; }; _autocomplete(%s)" ) -func keywordCompleter(line string) []string { - results := make([]string, 0) +func keywordCompleter(jsre *jsre, line string) []string { + var results []string + parts := strings.Split(line, ".") + objRef := "this" + prefix := line + if len(parts) > 1 { + objRef = strings.Join(parts[0:len(parts) - 1], ".") + prefix = parts[len(parts) - 1] + } - if strings.Contains(line, ".") { - elements := strings.Split(line, ".") - if len(elements) == 2 { - module := elements[0] - partialMethod := elements[1] - if methods, found := loadedModulesMethods[module]; found { - for _, method := range methods { - if strings.HasPrefix(method, partialMethod) { // e.g. debug.se - results = append(results, module+"."+method) - } + result, _ := jsre.re.Run(fmt.Sprintf(autoCompleteStatement, objRef)) + raw, _ := result.Export() + if keys, ok := raw.([]interface{}); ok { + for _, k := range keys { + if strings.HasPrefix(fmt.Sprintf("%s", k), prefix) { + if objRef == "this" { + results = append(results, fmt.Sprintf("%s", k)) + } else { + results = append(results, fmt.Sprintf("%s.%s", strings.Join(parts[:len(parts) - 1], "."), k)) } } } - } else { - for module, methods := range loadedModulesMethods { - if line == module { // user typed in full module name, show all methods - for _, method := range methods { - results = append(results, module+"."+method) - } - } else if strings.HasPrefix(module, line) { // partial method name, e.g. admi - results = append(results, module) - } - } } + + // e.g. web3 append dot since its an object + isObj, _ := jsre.re.Run(fmt.Sprintf("typeof(%s) === 'object'", line)) + if isObject, _ := isObj.ToBoolean(); isObject { + results = append(results, line + ".") + } + + sort.Strings(results) return results } -func apiWordCompleter(line string, pos int) (head string, completions []string, tail string) { - if len(line) == 0 || pos == 0 { - return "", nil, "" +func apiWordCompleterWithContext(jsre *jsre) liner.WordCompleter { + completer := func(line string, pos int) (head string, completions []string, tail string) { + if len(line) == 0 || pos == 0 { + return "", nil, "" + } + + // chuck data to relevant part for autocompletion, e.g. in case of nested lines eth.getBalance(eth.coinb + i := 0 + for i = pos - 1; i > 0; i-- { + if line[i] == '.' || (line[i] >= 'a' && line[i] <= 'z') || (line[i] >= 'A' && line[i] <= 'Z') { + continue + } + if i >= 3 && line[i] == '3' && line[i - 3] == 'w' && line[i - 2] == 'e' && line[i - 1] == 'b' { + continue + } + i += 1 + break + } + + begin := line[:i] + keyword := line[i:pos] + end := line[pos:] + + completionWords := keywordCompleter(jsre, keyword) + return begin, completionWords, end } - i := 0 - for i = pos - 1; i > 0; i-- { - if line[i] == '.' || (line[i] >= 'a' && line[i] <= 'z') || (line[i] >= 'A' && line[i] <= 'Z') { - continue - } - if i >= 3 && line[i] == '3' && line[i-3] == 'w' && line[i-2] == 'e' && line[i-1] == 'b' { - continue - } - i += 1 - break - } - - begin := line[:i] - keyword := line[i:pos] - end := line[pos:] - - completionWords := keywordCompleter(keyword) - return begin, completionWords, end + return completer } -func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir string, interactive bool) *jsre { +func newLightweightJSRE(docRoot string, client rpc.Client, datadir string, interactive bool) *jsre { js := &jsre{ps1: "> "} js.wait = make(chan *big.Int) js.client = client - // update state in separare forever blocks js.re = re.New(docRoot) - if err := js.apiBindings(js); err != nil { + if err := js.apiBindings(); err != nil { utils.Fatalf("Unable to initialize console - %v", err) } @@ -165,7 +166,7 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str js.withHistory(datadir, func(hist *os.File) { lr.ReadHistory(hist) }) lr.SetCtrlCAborts(true) js.loadAutoCompletion() - lr.SetWordCompleter(apiWordCompleter) + lr.SetWordCompleter(apiWordCompleterWithContext(js)) lr.SetTabCompletionStyle(liner.TabPrints) js.prompter = lr js.atexit = func() { @@ -177,25 +178,15 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str return js } -func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre { +func newJSRE(stack *node.Node, docRoot, corsDomain string, client rpc.Client, interactive bool) *jsre { js := &jsre{stack: stack, ps1: "> "} // set default cors domain used by startRpc from CLI flag js.corsDomain = corsDomain - if f == nil { - f = js - } - js.xeth = xeth.New(stack, f) - js.wait = js.xeth.UpdateState() + js.wait = make(chan *big.Int) js.client = client - if clt, ok := js.client.(*comms.InProcClient); ok { - if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, stack); err == nil { - clt.Initialize(api.Merge(offeredApis...)) - } - } - // update state in separare forever blocks js.re = re.New(docRoot) - if err := js.apiBindings(f); err != nil { + if err := js.apiBindings(); err != nil { utils.Fatalf("Unable to connect - %v", err) } @@ -206,7 +197,7 @@ func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.Ethereum js.withHistory(stack.DataDir(), func(hist *os.File) { lr.ReadHistory(hist) }) lr.SetCtrlCAborts(true) js.loadAutoCompletion() - lr.SetWordCompleter(apiWordCompleter) + lr.SetWordCompleter(apiWordCompleterWithContext(js)) lr.SetTabCompletionStyle(liner.TabPrints) js.prompter = lr js.atexit = func() { @@ -222,7 +213,7 @@ func (self *jsre) loadAutoCompletion() { if modules, err := self.supportedApis(); err == nil { loadedModulesMethods = make(map[string][]string) for module, _ := range modules { - loadedModulesMethods[module] = api.AutoCompletion[module] + loadedModulesMethods[module] = rpc.AutoCompletion[module] } } } @@ -258,7 +249,6 @@ func (self *jsre) welcome() { loadedModules = append(loadedModules, fmt.Sprintf("%s:%s", api, version)) } sort.Strings(loadedModules) - } } @@ -266,7 +256,7 @@ func (self *jsre) supportedApis() (map[string]string, error) { return self.client.SupportedModules() } -func (js *jsre) apiBindings(f xeth.Frontend) error { +func (js *jsre) apiBindings() error { apis, err := js.supportedApis() if err != nil { return err @@ -277,12 +267,7 @@ func (js *jsre) apiBindings(f xeth.Frontend) error { apiNames = append(apiNames, a) } - apiImpl, err := api.ParseApiString(strings.Join(apiNames, ","), codec.JSON, js.xeth, js.stack) - if err != nil { - utils.Fatalf("Unable to determine supported api's: %v", err) - } - - jeth := rpc.NewJeth(api.Merge(apiImpl...), js.re, js.client, f) + jeth := utils.NewJeth(js.re, js.client) js.re.Set("jeth", struct{}{}) t, _ := js.re.Get("jeth") jethObj := t.Object() @@ -313,14 +298,16 @@ func (js *jsre) apiBindings(f xeth.Frontend) error { // load only supported API's in javascript runtime shortcuts := "var eth = web3.eth; " for _, apiName := range apiNames { - if apiName == shared.Web3ApiName { - continue // manually mapped + if apiName == "web3" || apiName == "rpc" { + continue // manually mapped or ignore } - if err = js.re.Compile(fmt.Sprintf("%s.js", apiName), api.Javascript(apiName)); err == nil { - shortcuts += fmt.Sprintf("var %s = web3.%s; ", apiName, apiName) - } else { - utils.Fatalf("Error loading %s.js: %v", apiName, err) + if jsFile, ok := rpc.WEB3Extensions[apiName]; ok { + if err = js.re.Compile(fmt.Sprintf("%s.js", apiName), jsFile); err == nil { + shortcuts += fmt.Sprintf("var %s = web3.%s; ", apiName, apiName) + } else { + utils.Fatalf("Error loading %s.js: %v", apiName, err) + } } } @@ -375,14 +362,13 @@ func (self *jsre) ConfirmTransaction(tx string) bool { return false } // If natspec is enabled, ask for permission - if ethereum.NatSpec { - notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient()) - fmt.Println(notice) - answer, _ := self.Prompt("Confirm Transaction [y/n]") - return strings.HasPrefix(strings.Trim(answer, " "), "y") - } else { - return true + if ethereum.NatSpec && false /* disabled for now */ { + // notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient()) + // fmt.Println(notice) + // answer, _ := self.Prompt("Confirm Transaction [y/n]") + // return strings.HasPrefix(strings.Trim(answer, " "), "y") } + return true } func (self *jsre) UnlockAccount(addr []byte) bool { diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index ca636188f4..19583c5efe 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -32,30 +32,27 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/httpclient" - "github.com/ethereum/go-ethereum/common/natspec" - "github.com/ethereum/go-ethereum/common/registrar" "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/node" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/ethereum/go-ethereum/cmd/utils" ) const ( testSolcPath = "" - solcVersion = "0.9.23" + solcVersion = "0.9.23" - testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674" + testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674" testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" testBalance = "10000000000000000000" - // of empty string +// of empty string testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" ) var ( - versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) + versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f")) testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}` ) @@ -77,15 +74,16 @@ func (self *testjethre) UnlockAccount(acc []byte) bool { return true } -func (self *testjethre) ConfirmTransaction(tx string) bool { - var ethereum *eth.Ethereum - self.stack.Service(ðereum) - - if ethereum.NatSpec { - self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client) - } - return true -} +// Temporary disabled while natspec hasn't been migrated +//func (self *testjethre) ConfirmTransaction(tx string) bool { +// var ethereum *eth.Ethereum +// self.stack.Service(ðereum) +// +// if ethereum.NatSpec { +// self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client) +// } +// return true +//} func testJEthRE(t *testing.T) (string, *testjethre, *node.Node) { return testREPL(t, nil) @@ -118,7 +116,9 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod if config != nil { config(ethConf) } - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + return eth.New(ctx, ethConf) + }); err != nil { t.Fatalf("failed to register ethereum protocol: %v", err) } // Initialize all the keys for testing @@ -141,9 +141,10 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod stack.Service(ðereum) assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") - client := comms.NewInProcClient(codec.JSON) + //client := comms.NewInProcClient(codec.JSON) + client := utils.NewInProcRPCClient(stack) tf := &testjethre{client: ethereum.HTTPClient()} - repl := newJSRE(stack, assetPath, "", client, false, tf) + repl := newJSRE(stack, assetPath, "", client, false) tf.jsre = repl return tmp, tf, stack } @@ -166,8 +167,8 @@ func TestAccounts(t *testing.T) { defer node.Stop() defer os.RemoveAll(tmp) - checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`) - checkEvalJSON(t, repl, `eth.coinbase`, `"`+testAddress+`"`) + checkEvalJSON(t, repl, `eth.accounts`, `["` + testAddress + `"]`) + checkEvalJSON(t, repl, `eth.coinbase`, `"` + testAddress + `"`) val, err := repl.re.Run(`jeth.newAccount("password")`) if err != nil { t.Errorf("expected no error, got %v", err) @@ -177,7 +178,7 @@ func TestAccounts(t *testing.T) { t.Errorf("address not hex: %q", addr) } - checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`","`+addr+`"]`) + checkEvalJSON(t, repl, `eth.accounts`, `["` + testAddress + `","` + addr + `"]`) } @@ -205,13 +206,13 @@ func TestBlockChain(t *testing.T) { node.Service(ðereum) ethereum.BlockChain().Reset() - checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`) + checkEvalJSON(t, repl, `admin.exportChain(` + tmpfileq + `)`, `true`) if _, err := os.Stat(tmpfile); err != nil { t.Fatal(err) } // check import, verify that dumpBlock gives the same result. - checkEvalJSON(t, repl, `admin.importChain(`+tmpfileq+`)`, `true`) + checkEvalJSON(t, repl, `admin.importChain(` + tmpfileq + `)`, `true`) checkEvalJSON(t, repl, `debug.dumpBlock(eth.blockNumber)`, beforeExport) } @@ -239,7 +240,7 @@ func TestCheckTestAccountBalance(t *testing.T) { defer os.RemoveAll(tmp) repl.re.Run(`primary = "` + testAddress + `"`) - checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"`+testBalance+`"`) + checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"` + testBalance + `"`) } func TestSignature(t *testing.T) { @@ -278,19 +279,20 @@ func TestContract(t *testing.T) { defer ethereum.Stop() defer os.RemoveAll(tmp) - reg := registrar.New(repl.xeth) - _, err := reg.SetGlobalRegistrar("", coinbase) - if err != nil { - t.Errorf("error setting HashReg: %v", err) - } - _, err = reg.SetHashReg("", coinbase) - if err != nil { - t.Errorf("error setting HashReg: %v", err) - } - _, err = reg.SetUrlHint("", coinbase) - if err != nil { - t.Errorf("error setting HashReg: %v", err) - } + // Temporary disabled while registrar isn't migrated + //reg := registrar.New(repl.xeth) + //_, err := reg.SetGlobalRegistrar("", coinbase) + //if err != nil { + // t.Errorf("error setting HashReg: %v", err) + //} + //_, err = reg.SetHashReg("", coinbase) + //if err != nil { + // t.Errorf("error setting HashReg: %v", err) + //} + //_, err = reg.SetUrlHint("", coinbase) + //if err != nil { + // t.Errorf("error setting HashReg: %v", err) + //} /* TODO: * lookup receipt and contract addresses by tx hash * name registration for HashReg and UrlHint addresses @@ -299,11 +301,11 @@ func TestContract(t *testing.T) { */ source := `contract test {\n` + - " /// @notice Will multiply `a` by 7." + `\n` + - ` function multiply(uint a) returns(uint d) {\n` + - ` return a * 7;\n` + - ` }\n` + - `}\n` + " /// @notice Will multiply `a` by 7." + `\n` + + ` function multiply(uint a) returns(uint d) {\n` + + ` return a * 7;\n` + + ` }\n` + + `}\n` if checkEvalJSON(t, repl, `admin.stopNatSpec()`, `true`) != nil { return @@ -313,10 +315,10 @@ func TestContract(t *testing.T) { if err != nil { t.Fatalf("%v", err) } - if checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"`+testAddress+`"`) != nil { + if checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"` + testAddress + `"`) != nil { return } - if checkEvalJSON(t, repl, `source = "`+source+`"`, `"`+source+`"`) != nil { + if checkEvalJSON(t, repl, `source = "` + source + `"`, `"` + source + `"`) != nil { return } @@ -394,7 +396,7 @@ multiply7 = Multiply7.at(contractaddress); var contentHash = `"0x86d2b7cf1e72e9a7a3f8d96601f0151742a2f780f1526414304fbe413dc7f9bd"` if sol != nil && solcVersion != sol.Version() { - modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"`+sol.Version()+`"`)) + modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"` + sol.Version() + `"`)) fmt.Printf("modified contractinfo:\n%s\n", modContractInfo) contentHash = `"` + common.ToHex(crypto.Sha3([]byte(modContractInfo))) + `"` } @@ -474,11 +476,12 @@ func processTxs(repl *testjethre, t *testing.T, expTxc int) bool { defer ethereum.StopMining() timer := time.NewTimer(100 * time.Second) - height := new(big.Int).Add(repl.xeth.CurrentBlock().Number(), big.NewInt(1)) + blockNr := ethereum.BlockChain().CurrentBlock().Number() + height := new(big.Int).Add(blockNr, big.NewInt(1)) repl.wait <- height select { case <-timer.C: - // if times out make sure the xeth loop does not block + // if times out make sure the xeth loop does not block go func() { select { case repl.wait <- nil: diff --git a/cmd/geth/main.go b/cmd/geth/main.go index f2bb275520..e6d1909143 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -40,8 +40,6 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" ) const ( @@ -263,11 +261,11 @@ See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console Name: "attach", Usage: `Geth Console: interactive JavaScript environment (connect to node)`, Description: ` -The Geth console is an interactive shell for the JavaScript runtime environment -which exposes a node admin interface as well as the Ðapp JavaScript API. -See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console. -This command allows to open a console on a running geth node. -`, + The Geth console is an interactive shell for the JavaScript runtime environment + which exposes a node admin interface as well as the Ðapp JavaScript API. + See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console. + This command allows to open a console on a running geth node. + `, }, { Action: execScripts, @@ -309,11 +307,15 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.RPCEnabledFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, - utils.RpcApiFlag, + utils.RPCApiFlag, + utils.WSEnabledFlag, + utils.WSListenAddrFlag, + utils.WSPortFlag, + utils.WSApiFlag, + utils.WSAllowedDomainsFlag, utils.IPCDisabledFlag, utils.IPCApiFlag, utils.IPCPathFlag, - utils.IPCExperimental, utils.ExecFlag, utils.WhisperEnabledFlag, utils.DevModeFlag, @@ -392,20 +394,12 @@ func geth(ctx *cli.Context) { node.Wait() } +// attach will connect to a running geth instance attaching a JavaScript console and to it. func attach(ctx *cli.Context) { - var client comms.EthereumClient - var err error - if ctx.Args().Present() { - client, err = comms.ClientFromEndpoint(ctx.Args().First(), codec.JSON) - } else { - cfg := comms.IpcConfig{ - Endpoint: utils.IpcSocketPath(ctx), - } - client, err = comms.NewIpcClient(cfg, codec.JSON) - } - + // attach to a running geth instance + client, err := utils.NewRemoteRPCClient(ctx) if err != nil { - utils.Fatalf("Unable to attach to geth node - %v", err) + utils.Fatalf("Unable to attach to geth - %v", err) } repl := newLightweightJSRE( @@ -431,11 +425,12 @@ func console(ctx *cli.Context) { startNode(ctx, node) // Attach to the newly started node, and either execute script or become interactive - client := comms.NewInProcClient(codec.JSON) + client := utils.NewInProcRPCClient(node) + repl := newJSRE(node, ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name), - client, true, nil) + client, true) if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" { repl.batch(script) @@ -454,11 +449,12 @@ func execScripts(ctx *cli.Context) { startNode(ctx, node) // Attach to the newly started node and execute the given scripts - client := comms.NewInProcClient(codec.JSON) + client := utils.NewInProcRPCClient(node) + repl := newJSRE(node, ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name), - client, false, nil) + client, false) for _, file := range ctx.Args() { repl.exec(file) @@ -517,6 +513,11 @@ func startNode(ctx *cli.Context, stack *node.Node) { utils.Fatalf("Failed to start RPC: %v", err) } } + if ctx.GlobalBool(utils.WSEnabledFlag.Name) { + if err := utils.StartWS(stack, ctx); err != nil { + utils.Fatalf("Failed to start WS: %v", err) + } + } if ctx.GlobalBool(utils.MiningEnabledFlag.Name) { if err := ethereum.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name), ctx.GlobalString(utils.MiningGPUFlag.Name)); err != nil { utils.Fatalf("Failed to start mining: %v", err) diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index a45d29b8f2..1d7bf3f6ac 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -21,16 +21,15 @@ import ( "math" "reflect" "runtime" - "sort" "strings" "time" + "sort" + "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" "github.com/gizak/termui" ) @@ -70,20 +69,18 @@ to display multiple metrics simultaneously. // monitor starts a terminal UI based monitoring tool for the requested metrics. func monitor(ctx *cli.Context) { var ( - client comms.EthereumClient + client rpc.Client err error ) // Attach to an Ethereum node over IPC or RPC endpoint := ctx.String(monitorCommandAttachFlag.Name) - if client, err = comms.ClientFromEndpoint(endpoint, codec.JSON); err != nil { + if client, err = utils.NewRemoteRPCClientFromString(endpoint); err != nil { utils.Fatalf("Unable to attach to geth node: %v", err) } defer client.Close() - xeth := rpc.NewXeth(client) - // Retrieve all the available metrics and resolve the user pattens - metrics, err := retrieveMetrics(xeth) + metrics, err := retrieveMetrics(client) if err != nil { utils.Fatalf("Failed to retrieve system metrics: %v", err) } @@ -133,7 +130,7 @@ func monitor(ctx *cli.Context) { } termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, footer))) - refreshCharts(xeth, monitored, data, units, charts, ctx, footer) + refreshCharts(client, monitored, data, units, charts, ctx, footer) termui.Body.Align() termui.Render(termui.Body) @@ -154,7 +151,7 @@ func monitor(ctx *cli.Context) { termui.Render(termui.Body) } case <-refresh: - if refreshCharts(xeth, monitored, data, units, charts, ctx, footer) { + if refreshCharts(client, monitored, data, units, charts, ctx, footer) { termui.Body.Align() } termui.Render(termui.Body) @@ -164,8 +161,30 @@ func monitor(ctx *cli.Context) { // retrieveMetrics contacts the attached geth node and retrieves the entire set // of collected system metrics. -func retrieveMetrics(xeth *rpc.Xeth) (map[string]interface{}, error) { - return xeth.Call("debug_metrics", []interface{}{true}) +func retrieveMetrics(client rpc.Client) (map[string]interface{}, error) { + req := map[string]interface{}{ + "id": new(int64), + "method": "debug_metrics", + "jsonrpc": "2.0", + "params": []interface{}{true}, + } + + if err := client.Send(req); err != nil { + return nil, err + } + + var res rpc.JSONSuccessResponse + if err := client.Recv(&res); err != nil { + return nil, err + } + + if res.Result != nil { + if mets, ok := res.Result.(map[string]interface{}); ok { + return mets, nil + } + } + + return nil, fmt.Errorf("unable to retrieve metrics") } // resolveMetrics takes a list of input metric patterns, and resolves each to one @@ -253,8 +272,8 @@ func fetchMetric(metrics map[string]interface{}, metric string) float64 { // refreshCharts retrieves a next batch of metrics, and inserts all the new // values into the active datasets and charts -func refreshCharts(xeth *rpc.Xeth, metrics []string, data [][]float64, units []int, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) (realign bool) { - values, err := retrieveMetrics(xeth) +func refreshCharts(client rpc.Client, metrics []string, data [][]float64, units []int, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) (realign bool) { + values, err := retrieveMetrics(client) for i, metric := range metrics { if len(data) < 512 { data[i] = append([]float64{fetchMetric(values, metric)}, data[i]...) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 7a6ff704c4..a9fce6418a 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -87,7 +87,12 @@ var AppHelpFlagGroups = []flagGroup{ utils.RPCEnabledFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, - utils.RpcApiFlag, + utils.RPCApiFlag, + utils.WSEnabledFlag, + utils.WSListenAddrFlag, + utils.WSPortFlag, + utils.WSApiFlag, + utils.WSAllowedDomainsFlag, utils.IPCDisabledFlag, utils.IPCApiFlag, utils.IPCPathFlag, @@ -158,7 +163,6 @@ var AppHelpFlagGroups = []flagGroup{ Flags: []cli.Flag{ utils.WhisperEnabledFlag, utils.NatspecEnabledFlag, - utils.IPCExperimental, }, }, { diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index ae815c4a66..b4530ca51f 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -26,8 +26,9 @@ import ( "path/filepath" "runtime" + "errors" + "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -35,13 +36,9 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc/api" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/whisper" - "github.com/ethereum/go-ethereum/xeth" ) const defaultTestKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" @@ -176,21 +173,25 @@ func RunTest(stack *node.Node, test *tests.BlockTest) error { // StartRPC initializes an RPC interface to the given protocol stack. func StartRPC(stack *node.Node) error { - config := comms.HttpConfig{ - ListenAddress: "127.0.0.1", - ListenPort: 8545, - } - xeth := xeth.New(stack, nil) - codec := codec.JSON + /* + web3 := NewPublicWeb3API(stack) + server.RegisterName("web3", web3) + net := NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) + server.RegisterName("net", net) + */ - apis, err := api.ParseApiString(comms.DefaultHttpRpcApis, codec, xeth, stack) - if err != nil { - return err + for _, api := range stack.APIs() { + if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok { + _, err := adminApi.StartRPC("127.0.0.1", 8545, "", "admin,db,eth,debug,miner,net,shh,txpool,personal,web3") + return err + } } - return comms.StartHttp(config, codec, api.Merge(apis...)) + + glog.V(logger.Error).Infof("Unable to start RPC-HTTP interface, could not find admin API") + return errors.New("Unable to start RPC-HTTP interface") } -// StartRPC initializes an IPC interface to the given protocol stack. +// StartIPC initializes an IPC interface to the given protocol stack. func StartIPC(stack *node.Node) error { var ethereum *eth.Ethereum if err := stack.Service(ðereum); err != nil { @@ -202,11 +203,7 @@ func StartIPC(stack *node.Node) error { endpoint = filepath.Join(common.DefaultDataDir(), "geth.ipc") } - config := comms.IpcConfig{ - Endpoint: endpoint, - } - - listener, err := comms.CreateListener(config) + listener, err := rpc.CreateIPCListener(endpoint) if err != nil { return err } @@ -217,16 +214,16 @@ func StartIPC(stack *node.Node) error { offered := stack.APIs() for _, api := range offered { server.RegisterName(api.Namespace, api.Service) - glog.V(logger.Debug).Infof("Register %T@%s for IPC service\n", api.Service, api.Namespace) + glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) } - web3 := utils.NewPublicWeb3API(stack) - server.RegisterName("web3", web3) - net := utils.NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) - server.RegisterName("net", net) + //var ethereum *eth.Ethereum + //if err := stack.Service(ðereum); err != nil { + // return err + //} go func() { - glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint) + glog.V(logger.Info).Infof("Start IPC server on %s\n", endpoint) for { conn, err := listener.Accept() if err != nil { diff --git a/cmd/utils/api.go b/cmd/utils/api.go deleted file mode 100644 index 59f0dab740..0000000000 --- a/cmd/utils/api.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2015 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 . - -package utils - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - rpc "github.com/ethereum/go-ethereum/rpc/v2" -) - -// PublicWeb3API offers helper utils -type PublicWeb3API struct { - stack *node.Node -} - -// NewPublicWeb3API creates a new Web3Service instance -func NewPublicWeb3API(stack *node.Node) *PublicWeb3API { - return &PublicWeb3API{stack} -} - -// ClientVersion returns the node name -func (s *PublicWeb3API) ClientVersion() string { - return s.stack.Server().Name -} - -// Sha3 applies the ethereum sha3 implementation on the input. -// It assumes the input is hex encoded. -func (s *PublicWeb3API) Sha3(input string) string { - return common.ToHex(crypto.Sha3(common.FromHex(input))) -} - -// PublicNetAPI offers network related RPC methods -type PublicNetAPI struct { - net *p2p.Server - networkVersion int -} - -// NewPublicNetAPI creates a new net api instance. -func NewPublicNetAPI(net *p2p.Server, networkVersion int) *PublicNetAPI { - return &PublicNetAPI{net, networkVersion} -} - -// Listening returns an indication if the node is listening for network connections. -func (s *PublicNetAPI) Listening() bool { - return true // always listening -} - -// Peercount returns the number of connected peers -func (s *PublicNetAPI) PeerCount() *rpc.HexNumber { - return rpc.NewHexNumber(s.net.PeerCount()) -} - -// ProtocolVersion returns the current ethereum protocol version. -func (s *PublicNetAPI) Version() string { - return fmt.Sprintf("%d", s.networkVersion) -} diff --git a/cmd/utils/client.go b/cmd/utils/client.go new file mode 100644 index 0000000000..bac4564911 --- /dev/null +++ b/cmd/utils/client.go @@ -0,0 +1,176 @@ +// Copyright 2015 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 . + +package utils + +import ( + "encoding/json" + "fmt" + + "strings" + + "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" +) + +// NewInProcRPCClient will start a new RPC server for the given node and returns a client to interact with it. +func NewInProcRPCClient(stack *node.Node) *inProcClient { + server := rpc.NewServer() + + offered := stack.APIs() + for _, api := range offered { + server.RegisterName(api.Namespace, api.Service) + } + + web3 := node.NewPublicWeb3API(stack) + server.RegisterName("web3", web3) + + var ethereum *eth.Ethereum + if err := stack.Service(ðereum); err == nil { + net := eth.NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) + server.RegisterName("net", net) + } else { + glog.V(logger.Warn).Infof("%v\n", err) + } + + buf := &buf{ + requests: make(chan []byte), + responses: make(chan []byte), + } + client := &inProcClient{ + server: server, + buf: buf, + } + + go func() { + server.ServeCodec(rpc.NewJSONCodec(client.buf)) + }() + + return client +} + +// buf represents the connection between the RPC server and console +type buf struct { + readBuf []byte // store remaining request bytes after a partial read + requests chan []byte // list with raw serialized requests + responses chan []byte // list with raw serialized responses +} + +// will read the next request in json format +func (b *buf) Read(p []byte) (int, error) { + // last read didn't read entire request, return remaining bytes + if len(b.readBuf) > 0 { + n := copy(p, b.readBuf) + if n < len(b.readBuf) { + b.readBuf = b.readBuf[:n] + } else { + b.readBuf = b.readBuf[:0] + } + return n, nil + } + + // read next request + req := <-b.requests + n := copy(p, req) + if n < len(req) { + // buf too small, store remaining chunk for next read + b.readBuf = req[n:] + } + + return n, nil +} + +// Write send the given buffer to the backend +func (b *buf) Write(p []byte) (n int, err error) { + b.responses <- p + return len(p), nil +} + +// Close cleans up obtained resources. +func (b *buf) Close() error { + close(b.requests) + close(b.responses) + + return nil +} + +// inProcClient starts a RPC server and uses buf to communicate with it. +type inProcClient struct { + server *rpc.Server + buf *buf +} + +// Close will stop the RPC server +func (c *inProcClient) Close() { + c.server.Stop() +} + +// Send a msg to the endpoint +func (c *inProcClient) Send(msg interface{}) error { + d, err := json.Marshal(msg) + if err != nil { + return err + } + c.buf.requests <- d + return nil +} + +// Recv reads a message and tries to parse it into the given msg +func (c *inProcClient) Recv(msg interface{}) error { + data := <-c.buf.responses + return json.Unmarshal(data, &msg) +} + +// Returns the collection of modules the RPC server offers. +func (c *inProcClient) SupportedModules() (map[string]string, error) { + return rpc.SupportedModules(c) +} + +// NewRemoteRPCClient returns a RPC client which connects to a running geth instance. +// Depending on the given context this can either be a IPC or a HTTP client. +func NewRemoteRPCClient(ctx *cli.Context) (rpc.Client, error) { + if ctx.Args().Present() { + endpoint := ctx.Args().First() + return NewRemoteRPCClientFromString(endpoint) + } + + // use IPC by default + endpoint := IPCSocketPath(ctx) + return rpc.NewIPCClient(endpoint) +} + +// NewRemoteRPCClientFromString returns a RPC client which connects to the given +// endpoint. It must start with either `ipc:` or `rpc:` (HTTP). +func NewRemoteRPCClientFromString(endpoint string) (rpc.Client, error) { + if strings.HasPrefix(endpoint, "ipc:") { + return rpc.NewIPCClient(endpoint[4:]) + } + if strings.HasPrefix(endpoint, "rpc:") { + return rpc.NewHTTPClient(endpoint[4:]) + } + if strings.HasPrefix(endpoint, "http://") { + return rpc.NewHTTPClient(endpoint) + } + if strings.HasPrefix(endpoint, "ws:") { + return rpc.NewWSClient(endpoint) + } + + return nil, fmt.Errorf("invalid endpoint") +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 63efa08ee3..9199432d87 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -23,7 +23,6 @@ import ( "log" "math" "math/big" - "net" "net/http" "os" "path/filepath" @@ -31,6 +30,8 @@ import ( "strconv" "strings" + "errors" + "github.com/codegangsta/cli" "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" @@ -49,14 +50,8 @@ import ( "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc/api" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/rpc/useragent" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/whisper" - "github.com/ethereum/go-ethereum/xeth" ) func init() { @@ -282,10 +277,10 @@ var ( Usage: "Domains from which to accept cross origin requests (browser enforced)", Value: "", } - RpcApiFlag = cli.StringFlag{ + RPCApiFlag = cli.StringFlag{ Name: "rpcapi", Usage: "API's offered over the HTTP-RPC interface", - Value: comms.DefaultHttpRpcApis, + Value: rpc.DefaultHttpRpcApis, } IPCDisabledFlag = cli.BoolFlag{ Name: "ipcdisable", @@ -294,16 +289,36 @@ var ( IPCApiFlag = cli.StringFlag{ Name: "ipcapi", Usage: "API's offered over the IPC-RPC interface", - Value: comms.DefaultIpcApis, + Value: rpc.DefaultIpcApis, } IPCPathFlag = DirectoryFlag{ Name: "ipcpath", Usage: "Filename for IPC socket/pipe", Value: DirectoryString{common.DefaultIpcPath()}, } - IPCExperimental = cli.BoolFlag{ - Name: "ipcexp", - Usage: "Enable the new RPC implementation", + WSEnabledFlag = cli.BoolFlag{ + Name: "ws", + Usage: "Enable the WS-RPC server", + } + WSListenAddrFlag = cli.StringFlag{ + Name: "wsaddr", + Usage: "WS-RPC server listening interface", + Value: "127.0.0.1", + } + WSPortFlag = cli.IntFlag{ + Name: "wsport", + Usage: "WS-RPC server listening port", + Value: 8546, + } + WSApiFlag = cli.StringFlag{ + Name: "wsapi", + Usage: "API's offered over the WS-RPC interface", + Value: rpc.DefaultHttpRpcApis, + } + WSAllowedDomainsFlag = cli.StringFlag{ + Name: "wsdomains", + Usage: "Domains from which to accept websockets requests", + Value: "", } ExecFlag = cli.StringFlag{ Name: "exec", @@ -760,7 +775,7 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database return chain, chainDb } -func IpcSocketPath(ctx *cli.Context) (ipcpath string) { +func IPCSocketPath(ctx *cli.Context) (ipcpath string) { if runtime.GOOS == "windows" { ipcpath = common.DefaultIpcPath() if ctx.GlobalIsSet(IPCPathFlag.Name) { @@ -780,79 +795,83 @@ func IpcSocketPath(ctx *cli.Context) (ipcpath string) { } func StartIPC(stack *node.Node, ctx *cli.Context) error { - config := comms.IpcConfig{ - Endpoint: IpcSocketPath(ctx), - } - var ethereum *eth.Ethereum if err := stack.Service(ðereum); err != nil { return err } - if ctx.GlobalIsSet(IPCExperimental.Name) { - listener, err := comms.CreateListener(config) - if err != nil { - return err - } + endpoint := IPCSocketPath(ctx) + listener, err := rpc.CreateIPCListener(endpoint) + if err != nil { + return err + } - server := rpc.NewServer() + server := rpc.NewServer() - // register package API's this node provides - offered := stack.APIs() - for _, api := range offered { - server.RegisterName(api.Namespace, api.Service) - glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) - } + // register package API's this node provides + offered := stack.APIs() + for _, api := range offered { + server.RegisterName(api.Namespace, api.Service) + glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) + } - web3 := NewPublicWeb3API(stack) - server.RegisterName("web3", web3) - net := NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) - server.RegisterName("net", net) - - go func() { - glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint) - for { - conn, err := listener.Accept() - if err != nil { - glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err) - } - - codec := rpc.NewJSONCodec(conn) - go server.ServeCodec(codec) + go func() { + glog.V(logger.Info).Infof("Start IPC server on %s\n", endpoint) + for { + conn, err := listener.Accept() + if err != nil { + glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err) } - }() - return nil - } - - initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) { - fe := useragent.NewRemoteFrontend(conn, ethereum.AccountManager()) - xeth := xeth.New(stack, fe) - apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, stack) - if err != nil { - return nil, nil, err + codec := rpc.NewJSONCodec(conn) + go server.ServeCodec(codec) } - return xeth, api.Merge(apis...), nil - } - return comms.StartIpc(config, codec.JSON, initializer) + }() + + return nil + } // StartRPC starts a HTTP JSON-RPC API server. func StartRPC(stack *node.Node, ctx *cli.Context) error { - config := comms.HttpConfig{ - ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name), - ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)), - CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name), + for _, api := range stack.APIs() { + if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok { + address := ctx.GlobalString(RPCListenAddrFlag.Name) + port := ctx.GlobalInt(RPCPortFlag.Name) + cors := ctx.GlobalString(RPCCORSDomainFlag.Name) + apiStr := "" + if ctx.GlobalIsSet(RPCApiFlag.Name) { + apiStr = ctx.GlobalString(RPCApiFlag.Name) + } + + _, err := adminApi.StartRPC(address, port, cors, apiStr) + return err + } } - xeth := xeth.New(stack, nil) - codec := codec.JSON + glog.V(logger.Error).Infof("Unable to start RPC-HTTP interface, could not find admin API") + return errors.New("Unable to start RPC-HTTP interface") +} - apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, stack) - if err != nil { - return err +// StartWS starts a websocket JSON-RPC API server. +func StartWS(stack *node.Node, ctx *cli.Context) error { + for _, api := range stack.APIs() { + if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok { + address := ctx.GlobalString(WSListenAddrFlag.Name) + port := ctx.GlobalInt(WSAllowedDomainsFlag.Name) + allowedDomains := ctx.GlobalString(WSAllowedDomainsFlag.Name) + apiStr := "" + if ctx.GlobalIsSet(WSApiFlag.Name) { + apiStr = ctx.GlobalString(WSApiFlag.Name) + } + + _, err := adminApi.StartWS(address, port, allowedDomains, apiStr) + return err + } } - return comms.StartHttp(config, codec, api.Merge(apis...)) + + glog.V(logger.Error).Infof("Unable to start RPC-WS interface, could not find admin API") + return errors.New("Unable to start RPC-WS interface") } func StartPProf(ctx *cli.Context) { diff --git a/rpc/jeth.go b/cmd/utils/jeth.go similarity index 81% rename from rpc/jeth.go rename to cmd/utils/jeth.go index b195a49653..b460597c13 100644 --- a/rpc/jeth.go +++ b/cmd/utils/jeth.go @@ -14,38 +14,39 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package rpc +package utils import ( "encoding/json" "fmt" "time" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/jsre" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rpc/comms" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/rpc/useragent" - "github.com/ethereum/go-ethereum/xeth" + "github.com/ethereum/go-ethereum/rpc" "github.com/robertkrimen/otto" ) type Jeth struct { - ethApi shared.EthereumApi re *jsre.JSRE - client comms.EthereumClient - fe xeth.Frontend + client rpc.Client } -func NewJeth(ethApi shared.EthereumApi, re *jsre.JSRE, client comms.EthereumClient, fe xeth.Frontend) *Jeth { - return &Jeth{ethApi, re, client, fe} +// NewJeth create a new backend for the JSRE console +func NewJeth(re *jsre.JSRE, client rpc.Client) *Jeth { + return &Jeth{re, client} } -func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) { - m := shared.NewRpcErrorResponse(id, shared.JsonRpcVersion, code, fmt.Errorf(msg)) +func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id *int64) (response otto.Value) { + m := rpc.JSONErrResponse{ + Version: "2.0", + Id: id, + Error: rpc.JSONError{ + Code: code, + Message: msg, + }, + } + errObj, _ := json.Marshal(m.Error) errRes, _ := json.Marshal(m) @@ -71,7 +72,7 @@ func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) { if account, ok = accountExport.(string); ok { if len(call.ArgumentList) == 1 { fmt.Printf("Unlock account %s\n", account) - passwd, err = utils.PromptPassword("Passphrase: ", true) + passwd, err = PromptPassword("Passphrase: ", true) if err != nil { return otto.FalseValue() } @@ -102,11 +103,11 @@ func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) { // NewAccount asks the user for the password and than executes the jeth.newAccount callback in the jsre func (self *Jeth) NewAccount(call otto.FunctionCall) (response otto.Value) { if len(call.ArgumentList) == 0 { - passwd, err := utils.PromptPassword("Passphrase: ", true) + passwd, err := PromptPassword("Passphrase: ", true) if err != nil { return otto.FalseValue() } - passwd2, err := utils.PromptPassword("Repeat passphrase: ", true) + passwd2, err := PromptPassword("Repeat passphrase: ", true) if err != nil { return otto.FalseValue() } @@ -134,11 +135,11 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { } jsonreq, err := json.Marshal(reqif) - var reqs []shared.Request + var reqs []rpc.JSONRequest batch := true err = json.Unmarshal(jsonreq, &reqs) if err != nil { - reqs = make([]shared.Request, 1) + reqs = make([]rpc.JSONRequest, 1) err = json.Unmarshal(jsonreq, &reqs[0]) batch = false } @@ -147,42 +148,38 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { call.Otto.Run("var ret_response = new Array(response_len);") for i, req := range reqs { - var respif interface{} err := self.client.Send(&req) if err != nil { return self.err(call, -32603, err.Error(), req.Id) } - recv: - respif, err = self.client.Recv() + result := make(map[string]interface{}) + err = self.client.Recv(&result) if err != nil { return self.err(call, -32603, err.Error(), req.Id) } - agentreq, isRequest := respif.(*shared.Request) - if isRequest { - self.handleRequest(agentreq) - goto recv // receive response after agent interaction - } - - sucres, isSuccessResponse := respif.(*shared.SuccessResponse) - errres, isErrorResponse := respif.(*shared.ErrorResponse) + _, isSuccessResponse := result["result"] + _, isErrorResponse := result["error"] if !isSuccessResponse && !isErrorResponse { - return self.err(call, -32603, fmt.Sprintf("Invalid response type (%T)", respif), req.Id) + return self.err(call, -32603, fmt.Sprintf("Invalid response"), new(int64)) } - call.Otto.Set("ret_jsonrpc", shared.JsonRpcVersion) - call.Otto.Set("ret_id", req.Id) + id, _ := result["id"] + call.Otto.Set("ret_id", id) - var res []byte + jsonver, _ := result["jsonrpc"] + call.Otto.Set("ret_jsonrpc", jsonver) + + var payload []byte if isSuccessResponse { - res, err = json.Marshal(sucres.Result) + payload, _ = json.Marshal(result["result"]) } else if isErrorResponse { - res, err = json.Marshal(errres.Error) + payload, _ = json.Marshal(result["error"]) } - - call.Otto.Set("ret_result", string(res)) + call.Otto.Set("ret_result", string(payload)) call.Otto.Set("response_idx", i) + response, err = call.Otto.Run(` ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) }; `) @@ -195,7 +192,7 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { if call.Argument(1).IsObject() { call.Otto.Set("callback", call.Argument(1)) call.Otto.Run(` - if (Object.prototype.toString.call(callback) == '[object Function]') { + if (Object.prototype.toString.call(callback) == '[object Function]') { callback(null, ret_response); } `) @@ -204,6 +201,7 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { return } +/* // handleRequest will handle user agent requests by interacting with the user and sending // the user response back to the geth service func (self *Jeth) handleRequest(req *shared.Request) bool { @@ -235,7 +233,7 @@ func (self *Jeth) askPassword(id interface{}, jsonrpc string, args []interface{} return false } } - passwd, err = utils.PromptPassword("Passphrase: ", true) + passwd, err = PromptPassword("Passphrase: ", true) if err = self.client.Send(shared.NewRpcResponse(id, jsonrpc, passwd, err)); err != nil { glog.V(logger.Info).Infof("Unable to send user agent ask password response - %v\n", err) @@ -248,6 +246,7 @@ func (self *Jeth) confirmTransaction(id interface{}, jsonrpc string, args []inte // Accept all tx which are send from this console return self.client.Send(shared.NewRpcResponse(id, jsonrpc, true, nil)) == nil } +*/ // throwJSExeception panics on an otto value, the Otto VM will then throw msg as a javascript error. func throwJSExeception(msg interface{}) otto.Value { diff --git a/common/natspec/natspec.go b/common/natspec/natspec.go index d9627b4e1c..2e4d8d7a4a 100644 --- a/common/natspec/natspec.go +++ b/common/natspec/natspec.go @@ -13,6 +13,8 @@ // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . +// +// +build ignore package natspec diff --git a/common/natspec/natspec_e2e_test.go b/common/natspec/natspec_e2e_test.go index 95109ec07a..4a9b92eb67 100644 --- a/common/natspec/natspec_e2e_test.go +++ b/common/natspec/natspec_e2e_test.go @@ -13,6 +13,8 @@ // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . +// +// +build ignore package natspec diff --git a/common/natspec/natspec_test.go b/common/natspec/natspec_test.go index 9ec14829a9..53e6a3a90a 100644 --- a/common/natspec/natspec_test.go +++ b/common/natspec/natspec_test.go @@ -13,6 +13,8 @@ // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . +// +// +build ignore package natspec diff --git a/common/registrar/ethreg/api.go b/common/registrar/ethreg/api.go new file mode 100644 index 0000000000..92e885b10e --- /dev/null +++ b/common/registrar/ethreg/api.go @@ -0,0 +1,265 @@ +// Copyright 2015 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 . + +package ethreg + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/compiler" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/common/registrar" +) + +// registryAPIBackend is a backend for an Ethereum Registry. +type registryAPIBackend struct { + bc *core.BlockChain + chainDb ethdb.Database + txPool *core.TxPool + am *accounts.Manager +} + +// PrivateRegistarAPI offers various functions to access the Ethereum registry. +type PrivateRegistarAPI struct { + be *registryAPIBackend +} + +// NewPrivateRegistarAPI creates a new PrivateRegistarAPI instance. +func NewPrivateRegistarAPI(bc *core.BlockChain, chainDb ethdb.Database, txPool *core.TxPool, am *accounts.Manager) *PrivateRegistarAPI { + return &PrivateRegistarAPI{®istryAPIBackend{bc, chainDb, txPool, am}} +} + +// SetGlobalRegistrar allows clients to set the global registry for the node. +// This method can be used to deploy a new registry. First zero out the current +// address by calling the method with namereg = '0x0' and then call this method +// again with '' as namereg. This will submit a transaction to the network which +// will deploy a new registry on execution. The TX hash is returned. When called +// with namereg '' and the current address is not zero the current global is +// address is returned.. +func (api *PrivateRegistarAPI) SetGlobalRegistrar(namereg string, from common.Address) (string, error) { + return registrar.New(api.be).SetGlobalRegistrar(namereg, from) +} + +// SetHashReg queries the registry for a hash. +func (api *PrivateRegistarAPI) SetHashReg(hashreg string, from common.Address) (string, error) { + return registrar.New(api.be).SetHashReg(hashreg, from) +} + +// SetUrlHint queries the registry for an url. +func (api *PrivateRegistarAPI) SetUrlHint(hashreg string, from common.Address) (string, error) { + return registrar.New(api.be).SetUrlHint(hashreg, from) +} + +// SaveInfo stores contract information on the local file system. +func (api *PrivateRegistarAPI) SaveInfo(info *compiler.ContractInfo, filename string) (contenthash common.Hash, err error) { + return compiler.SaveInfo(info, filename) +} + +// Register registers a new content hash in the registry. +func (api *PrivateRegistarAPI) Register(sender common.Address, addr common.Address, contentHashHex string) (bool, error) { + block := api.be.bc.CurrentBlock() + state, err := state.New(block.Root(), api.be.chainDb) + if err != nil { + return false, err + } + + codeb := state.GetCode(addr) + codeHash := common.BytesToHash(crypto.Sha3(codeb)) + contentHash := common.HexToHash(contentHashHex) + + _, err = registrar.New(api.be).SetHashToHash(sender, codeHash, contentHash) + return err == nil, err +} + +// RegisterUrl registers a new url in the registry. +func (api *PrivateRegistarAPI) RegisterUrl(sender common.Address, contentHashHex string, url string) (bool, error) { + _, err := registrar.New(api.be).SetUrlToHash(sender, common.HexToHash(contentHashHex), url) + return err == nil, err +} + +// callmsg is the message type used for call transations. +type callmsg struct { + from *state.StateObject + to *common.Address + gas, gasPrice *big.Int + value *big.Int + data []byte +} + +// accessor boilerplate to implement core.Message +func (m callmsg) From() (common.Address, error) { + return m.from.Address(), nil +} +func (m callmsg) Nonce() uint64 { + return m.from.Nonce() +} +func (m callmsg) To() *common.Address { + return m.to +} +func (m callmsg) GasPrice() *big.Int { + return m.gasPrice +} +func (m callmsg) Gas() *big.Int { + return m.gas +} +func (m callmsg) Value() *big.Int { + return m.value +} +func (m callmsg) Data() []byte { + return m.data +} + +// Call forms a transaction from the given arguments and tries to execute it on +// a private VM with a copy of the state. Any changes are therefore only temporary +// and not part of the actual state. This allows for local execution/queries. +func (be *registryAPIBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, string, error) { + block := be.bc.CurrentBlock() + statedb, err := state.New(block.Root(), be.chainDb) + if err != nil { + return "", "", err + } + + var from *state.StateObject + if len(fromStr) == 0 { + accounts, err := be.am.Accounts() + if err != nil || len(accounts) == 0 { + from = statedb.GetOrNewStateObject(common.Address{}) + } else { + from = statedb.GetOrNewStateObject(accounts[0].Address) + } + } else { + from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr)) + } + + from.SetBalance(common.MaxBig) + + msg := callmsg{ + from: from, + gas: common.Big(gasStr), + gasPrice: common.Big(gasPriceStr), + value: common.Big(valueStr), + data: common.FromHex(dataStr), + } + if len(toStr) > 0 { + addr := common.HexToAddress(toStr) + msg.to = &addr + } + + if msg.gas.Cmp(big.NewInt(0)) == 0 { + msg.gas = big.NewInt(50000000) + } + + if msg.gasPrice.Cmp(big.NewInt(0)) == 0 { + msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + } + + header := be.bc.CurrentBlock().Header() + vmenv := core.NewEnv(statedb, be.bc, msg, header) + gp := new(core.GasPool).AddGas(common.MaxBig) + res, gas, err := core.ApplyMessage(vmenv, msg, gp) + + return common.ToHex(res), gas.String(), err +} + +// StorageAt returns the data stores in the state for the given address and location. +func (be *registryAPIBackend) StorageAt(addr string, storageAddr string) string { + block := be.bc.CurrentBlock() + state, err := state.New(block.Root(), be.chainDb) + if err != nil { + return "" + } + return state.GetState(common.HexToAddress(addr), common.HexToHash(storageAddr)).Hex() +} + +// Transact forms a transaction from the given arguments and submits it to the +// transactio pool for execution. +func (be *registryAPIBackend) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { + if len(toStr) > 0 && toStr != "0x" && !common.IsHexAddress(toStr) { + return "", errors.New("invalid address") + } + + var ( + from = common.HexToAddress(fromStr) + to = common.HexToAddress(toStr) + value = common.Big(valueStr) + gas *big.Int + price *big.Int + data []byte + contractCreation bool + ) + + if len(gasStr) == 0 { + gas = big.NewInt(90000) + } else { + gas = common.Big(gasStr) + } + + if len(gasPriceStr) == 0 { + price = big.NewInt(10000000000000) + } else { + price = common.Big(gasPriceStr) + } + + data = common.FromHex(codeStr) + if len(toStr) == 0 { + contractCreation = true + } + + nonce := be.txPool.State().GetNonce(from) + if len(nonceStr) != 0 { + nonce = common.Big(nonceStr).Uint64() + } + + var tx *types.Transaction + if contractCreation { + tx = types.NewContractCreation(nonce, value, gas, price, data) + } else { + tx = types.NewTransaction(nonce, to, value, gas, price, data) + } + + acc := accounts.Account{from} + signature, err := be.am.Sign(acc, tx.SigHash().Bytes()) + if err != nil { + return "", err + } + signedTx, err := tx.WithSignature(signature) + if err != nil { + return "", err + } + + be.txPool.SetLocal(signedTx) + if err := be.txPool.Add(signedTx); err != nil { + return "", nil + } + + if contractCreation { + addr := crypto.CreateAddress(from, nonce) + glog.V(logger.Info).Infof("Tx(%s) created: %s\n", signedTx.Hash().Hex(), addr.Hex()) + } else { + glog.V(logger.Info).Infof("Tx(%s) to: %s\n", signedTx.Hash().Hex(), tx.To().Hex()) + } + + return signedTx.Hash().Hex(), nil +} diff --git a/common/registrar/ethreg/ethreg.go b/common/registrar/ethreg/ethreg.go deleted file mode 100644 index 626ebe1d74..0000000000 --- a/common/registrar/ethreg/ethreg.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2015 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 . - -package ethreg - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common/registrar" - "github.com/ethereum/go-ethereum/xeth" -) - -// implements a versioned Registrar on an archiving full node -type EthReg struct { - backend *xeth.XEth - registry *registrar.Registrar -} - -func New(xe *xeth.XEth) (self *EthReg) { - self = &EthReg{backend: xe} - self.registry = registrar.New(xe) - return -} - -func (self *EthReg) Registry() *registrar.Registrar { - return self.registry -} - -func (self *EthReg) Resolver(n *big.Int) *registrar.Registrar { - xe := self.backend - if n != nil { - xe = self.backend.AtStateNum(n.Int64()) - } - return registrar.New(xe) -} diff --git a/eth/api.go b/eth/api.go index a1630e2d10..08b103a4cf 100644 --- a/eth/api.go +++ b/eth/api.go @@ -42,8 +42,10 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) const ( @@ -51,6 +53,19 @@ const ( defaultGas = uint64(90000) ) +// blockByNumber is a commonly used helper function which retrieves and returns the block for the given block number. It +// returns nil when no block could be found. +func blockByNumber(m *miner.Miner, bc *core.BlockChain, blockNr rpc.BlockNumber) *types.Block { + if blockNr == rpc.PendingBlockNumber { + return m.PendingBlock() + } + if blockNr == rpc.LatestBlockNumber { + return bc.CurrentBlock() + } + + return bc.GetBlockByNumber(uint64(blockNr)) +} + // PublicEthereumAPI provides an API to access Ethereum related information. // It offers only methods that operate on public data that is freely available to anyone. type PublicEthereumAPI struct { @@ -293,11 +308,12 @@ type PublicBlockChainAPI struct { chainDb ethdb.Database eventMux *event.TypeMux am *accounts.Manager + miner *miner.Miner } // NewPublicBlockChainAPI creates a new Etheruem blockchain API. -func NewPublicBlockChainAPI(bc *core.BlockChain, chainDb ethdb.Database, eventMux *event.TypeMux, am *accounts.Manager) *PublicBlockChainAPI { - return &PublicBlockChainAPI{bc: bc, chainDb: chainDb, eventMux: eventMux, am: am} +func NewPublicBlockChainAPI(bc *core.BlockChain, m *miner.Miner, chainDb ethdb.Database, eventMux *event.TypeMux, am *accounts.Manager) *PublicBlockChainAPI { + return &PublicBlockChainAPI{bc: bc, miner: m, chainDb: chainDb, eventMux: eventMux, am: am} } // BlockNumber returns the block number of the chain head. @@ -308,7 +324,7 @@ func (s *PublicBlockChainAPI) BlockNumber() *big.Int { // GetBalance returns the amount of wei for the given address in the state of the given block number. // When block number equals rpc.LatestBlockNumber the current block is used. func (s *PublicBlockChainAPI) GetBalance(address common.Address, blockNr rpc.BlockNumber) (*big.Int, error) { - block := blockByNumber(s.bc, blockNr) + block := blockByNumber(s.miner, s.bc, blockNr) if block == nil { return nil, nil } @@ -320,20 +336,10 @@ func (s *PublicBlockChainAPI) GetBalance(address common.Address, blockNr rpc.Blo return state.GetBalance(address), nil } -// blockByNumber is a commonly used helper function which retrieves and returns the block for the given block number. It -// returns nil when no block could be found. -func blockByNumber(bc *core.BlockChain, blockNr rpc.BlockNumber) *types.Block { - if blockNr == rpc.LatestBlockNumber { - return bc.CurrentBlock() - } - - return bc.GetBlockByNumber(uint64(blockNr)) -} - // GetBlockByNumber returns the requested block. When blockNr is -1 the chain head is returned. When fullTx is true all // transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return s.rpcOutputBlock(block, true, fullTx) } return nil, nil @@ -355,7 +361,7 @@ func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(blockNr rpc.BlockNum return nil, nil } - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { uncles := block.Uncles() if index.Int() < 0 || index.Int() >= len(uncles) { glog.V(logger.Debug).Infof("uncle block on index %d not found for block #%d", index.Int(), blockNr) @@ -388,7 +394,7 @@ func (s *PublicBlockChainAPI) GetUncleCountByBlockNumber(blockNr rpc.BlockNumber return rpc.NewHexNumber(0) } - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return rpc.NewHexNumber(len(block.Uncles())) } return nil @@ -433,7 +439,7 @@ func (s *PublicBlockChainAPI) GetCode(address common.Address, blockNr rpc.BlockN // GetData returns the data stored at the given address in the state for the given block number. func (s *PublicBlockChainAPI) GetData(address common.Address, blockNr rpc.BlockNumber) (string, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { state, err := state.New(block.Root(), s.chainDb) if err != nil { return "", err @@ -450,7 +456,7 @@ func (s *PublicBlockChainAPI) GetData(address common.Address, blockNr rpc.BlockN // GetStorageAt returns the storage from the state at the given address, key and block number. func (s *PublicBlockChainAPI) GetStorageAt(address common.Address, key string, blockNr rpc.BlockNumber) (string, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { state, err := state.New(block.Root(), s.chainDb) if err != nil { return "", err @@ -490,7 +496,7 @@ type CallArgs struct { } func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (string, *big.Int, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { stateDb, err := state.New(block.Root(), s.chainDb) if err != nil { return "0x", nil, err @@ -684,19 +690,21 @@ type PublicTransactionPoolAPI struct { eventMux *event.TypeMux chainDb ethdb.Database bc *core.BlockChain + miner *miner.Miner am *accounts.Manager txPool *core.TxPool txMu sync.Mutex } // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. -func NewPublicTransactionPoolAPI(txPool *core.TxPool, chainDb ethdb.Database, eventMux *event.TypeMux, bc *core.BlockChain, am *accounts.Manager) *PublicTransactionPoolAPI { +func NewPublicTransactionPoolAPI(txPool *core.TxPool, m *miner.Miner, chainDb ethdb.Database, eventMux *event.TypeMux, bc *core.BlockChain, am *accounts.Manager) *PublicTransactionPoolAPI { return &PublicTransactionPoolAPI{ eventMux: eventMux, chainDb: chainDb, bc: bc, am: am, txPool: txPool, + miner: m, } } @@ -724,7 +732,7 @@ func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByNumber(blockNr rpc. return rpc.NewHexNumber(0) } - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return rpc.NewHexNumber(len(block.Transactions())) } @@ -741,7 +749,7 @@ func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByHash(blockHash comm // GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. func (s *PublicTransactionPoolAPI) GetTransactionByBlockNumberAndIndex(blockNr rpc.BlockNumber, index rpc.HexNumber) (*RPCTransaction, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return newRPCTransactionFromBlockIndex(block, index.Int()) } return nil, nil @@ -757,7 +765,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(blockHash c // GetTransactionCount returns the number of transactions the given address has sent for the given block number func (s *PublicTransactionPoolAPI) GetTransactionCount(address common.Address, blockNr rpc.BlockNumber) (*rpc.HexNumber, error) { - block := blockByNumber(s.bc, blockNr) + block := blockByNumber(s.miner, s.bc, blockNr) if block == nil { return nil, nil } @@ -1256,6 +1264,16 @@ func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) { return true, nil } +func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool { + for _, b := range bs { + if !chain.HasBlock(b.Hash()) { + return false + } + } + + return true +} + // ImportChain imports a blockchain from a local file. func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) { // Make sure the can access the file to import @@ -1284,6 +1302,11 @@ func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) { if len(blocks) == 0 { break } + + if hasAllBlocks(api.eth.BlockChain(), blocks) { + blocks = blocks[:0] + continue + } // Import the batch and reset the buffer if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil { return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err) @@ -1403,3 +1426,29 @@ func (api *PrivateDebugAPI) ProcessBlock(number uint64) (bool, error) { func (api *PrivateDebugAPI) SetHead(number uint64) { api.eth.BlockChain().SetHead(number) } + +// PublicNetAPI offers network related RPC methods +type PublicNetAPI struct { + net *p2p.Server + networkVersion int +} + +// NewPublicNetAPI creates a new net api instance. +func NewPublicNetAPI(net *p2p.Server, networkVersion int) *PublicNetAPI { + return &PublicNetAPI{net, networkVersion} +} + +// Listening returns an indication if the node is listening for network connections. +func (s *PublicNetAPI) Listening() bool { + return true // always listening +} + +// Peercount returns the number of connected peers +func (s *PublicNetAPI) PeerCount() *rpc.HexNumber { + return rpc.NewHexNumber(s.net.PeerCount()) +} + +// ProtocolVersion returns the current ethereum protocol version. +func (s *PublicNetAPI) Version() string { + return fmt.Sprintf("%d", s.networkVersion) +} diff --git a/eth/backend.go b/eth/backend.go index abd1214caa..352522f614 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/httpclient" + "github.com/ethereum/go-ethereum/common/registrar/ethreg" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" @@ -44,7 +45,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) const ( @@ -121,14 +122,15 @@ type Ethereum struct { eventMux *event.TypeMux miner *miner.Miner - Mining bool - MinerThreads int - NatSpec bool - AutoDAG bool - PowTest bool - autodagquit chan bool - etherbase common.Address - netVersionId int + Mining bool + MinerThreads int + NatSpec bool + AutoDAG bool + PowTest bool + autodagquit chan bool + etherbase common.Address + netVersionId int + netRPCService *PublicNetAPI } func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { @@ -262,12 +264,12 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "eth", Version: "1.0", - Service: NewPublicBlockChainAPI(s.BlockChain(), s.ChainDb(), s.EventMux(), s.AccountManager()), + Service: NewPublicBlockChainAPI(s.BlockChain(), s.Miner(), s.ChainDb(), s.EventMux(), s.AccountManager()), Public: true, }, { Namespace: "eth", Version: "1.0", - Service: NewPublicTransactionPoolAPI(s.TxPool(), s.ChainDb(), s.EventMux(), s.BlockChain(), s.AccountManager()), + Service: NewPublicTransactionPoolAPI(s.TxPool(), s.Miner(), s.ChainDb(), s.EventMux(), s.BlockChain(), s.AccountManager()), Public: true, }, { Namespace: "eth", @@ -307,6 +309,15 @@ func (s *Ethereum) APIs() []rpc.API { Namespace: "debug", Version: "1.0", Service: NewPrivateDebugAPI(s), + }, { + Namespace: "net", + Version: "1.0", + Service: s.netRPCService, + Public: true, + }, { + Namespace: "admin", + Version: "1.0", + Service: ethreg.NewPrivateRegistarAPI(s.BlockChain(), s.ChainDb(), s.TxPool(), s.AccountManager()), }, } } @@ -356,11 +367,12 @@ func (s *Ethereum) Protocols() []p2p.Protocol { // Start implements node.Service, starting all internal goroutines needed by the // Ethereum protocol implementation. -func (s *Ethereum) Start(*p2p.Server) error { +func (s *Ethereum) Start(srvr *p2p.Server) error { if s.AutoDAG { s.StartAutoDAG() } s.protocolManager.Start() + s.netRPCService = NewPublicNetAPI(srvr, s.NetVersion()) return nil } diff --git a/eth/downloader/api.go b/eth/downloader/api.go index 9deff22a19..cc79e669f0 100644 --- a/eth/downloader/api.go +++ b/eth/downloader/api.go @@ -17,7 +17,7 @@ package downloader import ( - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) // PublicDownloaderAPI provides an API which gives informatoin about the current synchronisation status. diff --git a/eth/filters/api.go b/eth/filters/api.go index 411d8e5a3d..f2b0ed32f5 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) var ( diff --git a/jsre/ethereum_js.go b/jsre/ethereum_js.go index 7063a90ec6..94e4fde82e 100644 --- a/jsre/ethereum_js.go +++ b/jsre/ethereum_js.go @@ -5740,7 +5740,9 @@ Property.prototype.extractCallback = function (args) { */ Property.prototype.attachToObject = function (obj) { var proto = { - get: this.buildGet() + //get: this.buildGet() + get: this.buildGet(), + enumerable: true }; var names = this.name.split('.'); diff --git a/light/state_object.go b/light/state_object.go index 7660c38831..d67fd7e40f 100644 --- a/light/state_object.go +++ b/light/state_object.go @@ -264,4 +264,4 @@ func DecodeObject(ctx context.Context, address common.Address, odr OdrBackend, d obj.balance = ext.Balance obj.codeHash = ext.CodeHash return obj, nil -} \ No newline at end of file +} diff --git a/miner/api.go b/miner/api.go index 65d106afd8..42678b5ad7 100644 --- a/miner/api.go +++ b/miner/api.go @@ -21,7 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/logger/glog" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) // PublicMinerAPI provides an API to control the miner. diff --git a/node/api.go b/node/api.go index 909329e573..7b247dc514 100644 --- a/node/api.go +++ b/node/api.go @@ -21,11 +21,15 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" - "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/ethereum/go-ethereum/rpc" "github.com/rcrowley/go-metrics" + + "gopkg.in/fatih/set.v0" ) // PrivateAdminAPI is the collection of administrative API methods exposed only @@ -59,27 +63,83 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { // StartRPC starts the HTTP RPC API server. func (api *PrivateAdminAPI) StartRPC(address string, port int, cors string, apis string) (bool, error) { - /*// Parse the list of API modules to make available - apis, err := api.ParseApiString(apis, codec.JSON, xeth.New(api.node, nil), api.node) - if err != nil { - return false, err + var offeredAPIs []rpc.API + if len(apis) > 0 { + namespaces := set.New() + for _, a := range strings.Split(apis, ",") { + namespaces.Add(strings.TrimSpace(a)) + } + for _, api := range api.node.APIs() { + if namespaces.Has(api.Namespace) { + offeredAPIs = append(offeredAPIs, api) + } + } + } else { // use by default all public API's + for _, api := range api.node.APIs() { + if api.Public { + offeredAPIs = append(offeredAPIs, api) + } + } } - // Configure and start the HTTP RPC server - config := comms.HttpConfig{ - ListenAddress: address, - ListenPort: port, - CorsDomain: cors, + + if address == "" { + address = "127.0.0.1" } - if err := comms.StartHttp(config, self.codec, api.Merge(apis...)); err != nil { - return false, err + if port == 0 { + port = 8545 } - return true, nil*/ - return false, fmt.Errorf("needs new RPC implementation to resolve circular dependency") + + corsDomains := strings.Split(cors, " ") + err := rpc.StartHTTP(address, port, corsDomains, offeredAPIs) + return err == nil, err } // StopRPC terminates an already running HTTP RPC API endpoint. -func (api *PrivateAdminAPI) StopRPC() { - comms.StopHttp() +func (api *PrivateAdminAPI) StopRPC() (bool, error) { + err := rpc.StopHTTP() + return err == nil, err +} + + +// StartWS starts the websocket RPC API server. +func (api *PrivateAdminAPI) StartWS(address string, port int, cors string, apis string) (bool, error) { + var offeredAPIs []rpc.API + if len(apis) > 0 { + namespaces := set.New() + for _, a := range strings.Split(apis, ",") { + namespaces.Add(strings.TrimSpace(a)) + } + for _, api := range api.node.APIs() { + if namespaces.Has(api.Namespace) { + offeredAPIs = append(offeredAPIs, api) + } + } + } else { + // use by default all public API's + for _, api := range api.node.APIs() { + if api.Public { + offeredAPIs = append(offeredAPIs, api) + } + } + } + + if address == "" { + address = "127.0.0.1" + } + if port == 0 { + port = 8546 + } + + corsDomains := strings.Split(cors, " ") + + err := rpc.StartWS(address, port, corsDomains, offeredAPIs) + return err == nil, err +} + +// StopRPC terminates an already running websocket RPC API endpoint. +func (api *PrivateAdminAPI) StopWS() (bool, error) { + err := rpc.StopWS() + return err == nil, err } // PublicAdminAPI is the collection of administrative API methods exposed over @@ -247,3 +307,24 @@ func (api *PublicDebugAPI) Metrics(raw bool) (map[string]interface{}, error) { }) return counters, nil } + +// PublicWeb3API offers helper utils +type PublicWeb3API struct { + stack *Node +} + +// NewPublicWeb3API creates a new Web3Service instance +func NewPublicWeb3API(stack *Node) *PublicWeb3API { + return &PublicWeb3API{stack} +} + +// ClientVersion returns the node name +func (s *PublicWeb3API) ClientVersion() string { + return s.stack.Server().Name +} + +// Sha3 applies the ethereum sha3 implementation on the input. +// It assumes the input is hex encoded. +func (s *PublicWeb3API) Sha3(input string) string { + return common.ToHex(crypto.Sha3(common.FromHex(input))) +} diff --git a/node/node.go b/node/node.go index 5d7b5869c5..3d077b0bd1 100644 --- a/node/node.go +++ b/node/node.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) var ( @@ -290,6 +290,11 @@ func (n *Node) APIs() []rpc.API { Version: "1.0", Service: NewPublicDebugAPI(n), Public: true, + }, { + Namespace: "web3", + Version: "1.0", + Service: NewPublicWeb3API(n), + Public: true, }, } // Inject all the APIs owned by various services diff --git a/node/node_example_test.go b/node/node_example_test.go index ef41dddaf4..5ff5d06a6a 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) // SampleService is a trivial network service that can be attached to a node for diff --git a/node/service.go b/node/service.go index b83d4c80d9..26e9f1624f 100644 --- a/node/service.go +++ b/node/service.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) // ServiceContext is a collection of service independent options inherited from diff --git a/node/utils_test.go b/node/utils_test.go index 2b7bfadbea..7755605ae3 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -23,7 +23,7 @@ import ( "reflect" "github.com/ethereum/go-ethereum/p2p" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) // NoopService is a trivial implementation of the Service interface. diff --git a/rpc/api/admin.go b/rpc/api/admin.go deleted file mode 100644 index daf2f31b45..0000000000 --- a/rpc/api/admin.go +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "fmt" - "io" - "math/big" - "os" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/compiler" - "github.com/ethereum/go-ethereum/common/natspec" - "github.com/ethereum/go-ethereum/common/registrar" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/discover" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/rpc/useragent" - "github.com/ethereum/go-ethereum/xeth" -) - -const ( - AdminApiversion = "1.0" - importBatchSize = 2500 -) - -var ( - // mapping between methods and handlers - AdminMapping = map[string]adminhandler{ - "admin_addPeer": (*adminApi).AddPeer, - "admin_peers": (*adminApi).Peers, - "admin_nodeInfo": (*adminApi).NodeInfo, - "admin_exportChain": (*adminApi).ExportChain, - "admin_importChain": (*adminApi).ImportChain, - "admin_setSolc": (*adminApi).SetSolc, - "admin_datadir": (*adminApi).DataDir, - "admin_startRPC": (*adminApi).StartRPC, - "admin_stopRPC": (*adminApi).StopRPC, - "admin_setGlobalRegistrar": (*adminApi).SetGlobalRegistrar, - "admin_setHashReg": (*adminApi).SetHashReg, - "admin_setUrlHint": (*adminApi).SetUrlHint, - "admin_saveInfo": (*adminApi).SaveInfo, - "admin_register": (*adminApi).Register, - "admin_registerUrl": (*adminApi).RegisterUrl, - "admin_startNatSpec": (*adminApi).StartNatSpec, - "admin_stopNatSpec": (*adminApi).StopNatSpec, - "admin_getContractInfo": (*adminApi).GetContractInfo, - "admin_httpGet": (*adminApi).HttpGet, - "admin_sleepBlocks": (*adminApi).SleepBlocks, - "admin_sleep": (*adminApi).Sleep, - "admin_enableUserAgent": (*adminApi).EnableUserAgent, - } -) - -// admin callback handler -type adminhandler func(*adminApi, *shared.Request) (interface{}, error) - -// admin api provider -type adminApi struct { - xeth *xeth.XEth - stack *node.Node - ethereum *eth.Ethereum - codec codec.Codec - coder codec.ApiCoder -} - -// create a new admin api instance -func NewAdminApi(xeth *xeth.XEth, stack *node.Node, codec codec.Codec) *adminApi { - api := &adminApi{ - xeth: xeth, - stack: stack, - codec: codec, - coder: codec.New(nil), - } - if stack != nil { - stack.Service(&api.ethereum) - } - return api -} - -// collection with supported methods -func (self *adminApi) Methods() []string { - methods := make([]string, len(AdminMapping)) - i := 0 - for k := range AdminMapping { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *adminApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := AdminMapping[req.Method]; ok { - return callback(self, req) - } - - return nil, &shared.NotImplementedError{req.Method} -} - -func (self *adminApi) Name() string { - return shared.AdminApiName -} - -func (self *adminApi) ApiVersion() string { - return AdminApiversion -} - -func (self *adminApi) AddPeer(req *shared.Request) (interface{}, error) { - args := new(AddPeerArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - node, err := discover.ParseNode(args.Url) - if err != nil { - return nil, fmt.Errorf("invalid node URL: %v", err) - } - self.stack.Server().AddPeer(node) - return true, nil -} - -func (self *adminApi) Peers(req *shared.Request) (interface{}, error) { - return self.stack.Server().PeersInfo(), nil -} - -func (self *adminApi) NodeInfo(req *shared.Request) (interface{}, error) { - return self.stack.Server().NodeInfo(), nil -} - -func (self *adminApi) DataDir(req *shared.Request) (interface{}, error) { - return self.stack.DataDir(), nil -} - -func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool { - for _, b := range bs { - if !chain.HasBlock(b.Hash()) { - return false - } - } - return true -} - -func (self *adminApi) ImportChain(req *shared.Request) (interface{}, error) { - args := new(ImportExportChainArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - fh, err := os.Open(args.Filename) - if err != nil { - return false, err - } - defer fh.Close() - stream := rlp.NewStream(fh, 0) - - // Run actual the import. - blocks := make(types.Blocks, importBatchSize) - n := 0 - for batch := 0; ; batch++ { - - i := 0 - for ; i < importBatchSize; i++ { - var b types.Block - if err := stream.Decode(&b); err == io.EOF { - break - } else if err != nil { - return false, fmt.Errorf("at block %d: %v", n, err) - } - blocks[i] = &b - n++ - } - if i == 0 { - break - } - // Import the batch. - if hasAllBlocks(self.ethereum.BlockChain(), blocks[:i]) { - continue - } - if _, err := self.ethereum.BlockChain().InsertChain(blocks[:i]); err != nil { - return false, fmt.Errorf("invalid block %d: %v", n, err) - } - } - return true, nil -} - -func (self *adminApi) ExportChain(req *shared.Request) (interface{}, error) { - args := new(ImportExportChainArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - fh, err := os.OpenFile(args.Filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) - if err != nil { - return false, err - } - defer fh.Close() - if err := self.ethereum.BlockChain().Export(fh); err != nil { - return false, err - } - - return true, nil -} - -func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) { - args := new(SetSolcArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - solc, err := self.xeth.SetSolc(args.Path) - if err != nil { - return nil, err - } - return solc.Info(), nil -} - -func (self *adminApi) StartRPC(req *shared.Request) (interface{}, error) { - args := new(StartRPCArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - cfg := comms.HttpConfig{ - ListenAddress: args.ListenAddress, - ListenPort: args.ListenPort, - CorsDomain: args.CorsDomain, - } - - apis, err := ParseApiString(args.Apis, self.codec, self.xeth, self.stack) - if err != nil { - return false, err - } - - err = comms.StartHttp(cfg, self.codec, Merge(apis...)) - if err == nil { - return true, nil - } - return false, err -} - -func (self *adminApi) StopRPC(req *shared.Request) (interface{}, error) { - comms.StopHttp() - return true, nil -} - -func (self *adminApi) SleepBlocks(req *shared.Request) (interface{}, error) { - args := new(SleepBlocksArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - var timer <-chan time.Time - var height *big.Int - var err error - if args.Timeout > 0 { - timer = time.NewTimer(time.Duration(args.Timeout) * time.Second).C - } - - height = new(big.Int).Add(self.xeth.CurrentBlock().Number(), big.NewInt(args.N)) - height, err = sleepBlocks(self.xeth.UpdateState(), height, timer) - if err != nil { - return nil, err - } - return height.Uint64(), nil -} - -func sleepBlocks(wait chan *big.Int, height *big.Int, timer <-chan time.Time) (newHeight *big.Int, err error) { - wait <- height - select { - case <-timer: - // if times out make sure the xeth loop does not block - go func() { - select { - case wait <- nil: - case <-wait: - } - }() - return nil, fmt.Errorf("timeout") - case newHeight = <-wait: - } - return -} - -func (self *adminApi) Sleep(req *shared.Request) (interface{}, error) { - args := new(SleepArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - time.Sleep(time.Duration(args.S) * time.Second) - return nil, nil -} - -func (self *adminApi) SetGlobalRegistrar(req *shared.Request) (interface{}, error) { - args := new(SetGlobalRegistrarArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - sender := common.HexToAddress(args.ContractAddress) - - reg := registrar.New(self.xeth) - txhash, err := reg.SetGlobalRegistrar(args.NameReg, sender) - if err != nil { - return false, err - } - - return txhash, nil -} - -func (self *adminApi) SetHashReg(req *shared.Request) (interface{}, error) { - args := new(SetHashRegArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - reg := registrar.New(self.xeth) - sender := common.HexToAddress(args.Sender) - txhash, err := reg.SetHashReg(args.HashReg, sender) - if err != nil { - return false, err - } - - return txhash, nil -} - -func (self *adminApi) SetUrlHint(req *shared.Request) (interface{}, error) { - args := new(SetUrlHintArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - urlHint := args.UrlHint - sender := common.HexToAddress(args.Sender) - - reg := registrar.New(self.xeth) - txhash, err := reg.SetUrlHint(urlHint, sender) - if err != nil { - return nil, err - } - - return txhash, nil -} - -func (self *adminApi) SaveInfo(req *shared.Request) (interface{}, error) { - args := new(SaveInfoArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - contenthash, err := compiler.SaveInfo(&args.ContractInfo, args.Filename) - if err != nil { - return nil, err - } - - return contenthash.Hex(), nil -} - -func (self *adminApi) Register(req *shared.Request) (interface{}, error) { - args := new(RegisterArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - sender := common.HexToAddress(args.Sender) - // sender and contract address are passed as hex strings - codeb := self.xeth.CodeAtBytes(args.Address) - codeHash := common.BytesToHash(crypto.Sha3(codeb)) - contentHash := common.HexToHash(args.ContentHashHex) - registry := registrar.New(self.xeth) - - _, err := registry.SetHashToHash(sender, codeHash, contentHash) - if err != nil { - return false, err - } - - return true, nil -} - -func (self *adminApi) RegisterUrl(req *shared.Request) (interface{}, error) { - args := new(RegisterUrlArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - sender := common.HexToAddress(args.Sender) - registry := registrar.New(self.xeth) - _, err := registry.SetUrlToHash(sender, common.HexToHash(args.ContentHash), args.Url) - if err != nil { - return false, err - } - - return true, nil -} - -func (self *adminApi) StartNatSpec(req *shared.Request) (interface{}, error) { - self.ethereum.NatSpec = true - return true, nil -} - -func (self *adminApi) StopNatSpec(req *shared.Request) (interface{}, error) { - self.ethereum.NatSpec = false - return true, nil -} - -func (self *adminApi) GetContractInfo(req *shared.Request) (interface{}, error) { - args := new(GetContractInfoArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - infoDoc, err := natspec.FetchDocsForContract(args.Contract, self.xeth, self.ethereum.HTTPClient()) - if err != nil { - return nil, err - } - - var info interface{} - err = self.coder.Decode(infoDoc, &info) - if err != nil { - return nil, err - } - - return info, nil -} - -func (self *adminApi) HttpGet(req *shared.Request) (interface{}, error) { - args := new(HttpGetArgs) - if err := self.coder.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - resp, err := self.ethereum.HTTPClient().Get(args.Uri, args.Path) - if err != nil { - return nil, err - } - - return string(resp), nil -} - -func (self *adminApi) EnableUserAgent(req *shared.Request) (interface{}, error) { - if fe, ok := self.xeth.Frontend().(*useragent.RemoteFrontend); ok { - fe.Enable() - } - return true, nil -} diff --git a/rpc/api/admin_args.go b/rpc/api/admin_args.go deleted file mode 100644 index e09597ad49..0000000000 --- a/rpc/api/admin_args.go +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/common/compiler" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type AddPeerArgs struct { - Url string -} - -func (args *AddPeerArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) != 1 { - return shared.NewDecodeParamError("Expected enode as argument") - } - - urlstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("url", "not a string") - } - args.Url = urlstr - - return nil -} - -type ImportExportChainArgs struct { - Filename string -} - -func (args *ImportExportChainArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) != 1 { - return shared.NewDecodeParamError("Expected filename as argument") - } - - filename, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("filename", "not a string") - } - args.Filename = filename - - return nil -} - -type SetSolcArgs struct { - Path string -} - -func (args *SetSolcArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) != 1 { - return shared.NewDecodeParamError("Expected path as argument") - } - - if pathstr, ok := obj[0].(string); ok { - args.Path = pathstr - return nil - } - - return shared.NewInvalidTypeError("path", "not a string") -} - -type StartRPCArgs struct { - ListenAddress string - ListenPort uint - CorsDomain string - Apis string -} - -func (args *StartRPCArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - args.ListenAddress = "127.0.0.1" - args.ListenPort = 8545 - args.Apis = "net,eth,web3" - - if len(obj) >= 1 && obj[0] != nil { - if addr, ok := obj[0].(string); ok { - args.ListenAddress = addr - } else { - return shared.NewInvalidTypeError("listenAddress", "not a string") - } - } - - if len(obj) >= 2 && obj[1] != nil { - if port, ok := obj[1].(float64); ok && port >= 0 && port <= 64*1024 { - args.ListenPort = uint(port) - } else { - return shared.NewInvalidTypeError("listenPort", "not a valid port number") - } - } - - if len(obj) >= 3 && obj[2] != nil { - if corsDomain, ok := obj[2].(string); ok { - args.CorsDomain = corsDomain - } else { - return shared.NewInvalidTypeError("corsDomain", "not a string") - } - } - - if len(obj) >= 4 && obj[3] != nil { - if apis, ok := obj[3].(string); ok { - args.Apis = apis - } else { - return shared.NewInvalidTypeError("apis", "not a string") - } - } - - return nil -} - -type SleepArgs struct { - S int -} - -func (args *SleepArgs) UnmarshalJSON(b []byte) (err error) { - - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - if len(obj) >= 1 { - if obj[0] != nil { - if n, err := numString(obj[0]); err == nil { - args.S = int(n.Int64()) - } else { - return shared.NewInvalidTypeError("N", "not an integer: "+err.Error()) - } - } else { - return shared.NewInsufficientParamsError(0, 1) - } - } - return nil -} - -type SleepBlocksArgs struct { - N int64 - Timeout int64 -} - -func (args *SleepBlocksArgs) UnmarshalJSON(b []byte) (err error) { - - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - args.N = 1 - args.Timeout = 0 - if len(obj) >= 1 && obj[0] != nil { - if n, err := numString(obj[0]); err == nil { - args.N = n.Int64() - } else { - return shared.NewInvalidTypeError("N", "not an integer: "+err.Error()) - } - } - - if len(obj) >= 2 && obj[1] != nil { - if n, err := numString(obj[1]); err == nil { - args.Timeout = n.Int64() - } else { - return shared.NewInvalidTypeError("Timeout", "not an integer: "+err.Error()) - } - } - - return nil -} - -type SetGlobalRegistrarArgs struct { - NameReg string - ContractAddress string -} - -func (args *SetGlobalRegistrarArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) == 0 { - return shared.NewDecodeParamError("Expected namereg address") - } - - if len(obj) >= 1 { - if namereg, ok := obj[0].(string); ok { - args.NameReg = namereg - } else { - return shared.NewInvalidTypeError("NameReg", "not a string") - } - } - - if len(obj) >= 2 && obj[1] != nil { - if addr, ok := obj[1].(string); ok { - args.ContractAddress = addr - } else { - return shared.NewInvalidTypeError("ContractAddress", "not a string") - } - } - - return nil -} - -type SetHashRegArgs struct { - HashReg string - Sender string -} - -func (args *SetHashRegArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) >= 1 && obj[0] != nil { - if hashreg, ok := obj[0].(string); ok { - args.HashReg = hashreg - } else { - return shared.NewInvalidTypeError("HashReg", "not a string") - } - } - - if len(obj) >= 2 && obj[1] != nil { - if sender, ok := obj[1].(string); ok { - args.Sender = sender - } else { - return shared.NewInvalidTypeError("Sender", "not a string") - } - } - - return nil -} - -type SetUrlHintArgs struct { - UrlHint string - Sender string -} - -func (args *SetUrlHintArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) >= 1 && obj[0] != nil { - if urlhint, ok := obj[0].(string); ok { - args.UrlHint = urlhint - } else { - return shared.NewInvalidTypeError("UrlHint", "not a string") - } - } - - if len(obj) >= 2 && obj[1] != nil { - if sender, ok := obj[1].(string); ok { - args.Sender = sender - } else { - return shared.NewInvalidTypeError("Sender", "not a string") - } - } - - return nil -} - -type SaveInfoArgs struct { - ContractInfo compiler.ContractInfo - Filename string -} - -func (args *SaveInfoArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - if jsonraw, err := json.Marshal(obj[0]); err == nil { - if err = json.Unmarshal(jsonraw, &args.ContractInfo); err != nil { - return err - } - } else { - return err - } - - if filename, ok := obj[1].(string); ok { - args.Filename = filename - } else { - return shared.NewInvalidTypeError("Filename", "not a string") - } - - return nil -} - -type RegisterArgs struct { - Sender string - Address string - ContentHashHex string -} - -func (args *RegisterArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 3 { - return shared.NewInsufficientParamsError(len(obj), 3) - } - - if len(obj) >= 1 { - if sender, ok := obj[0].(string); ok { - args.Sender = sender - } else { - return shared.NewInvalidTypeError("Sender", "not a string") - } - } - - if len(obj) >= 2 { - if address, ok := obj[1].(string); ok { - args.Address = address - } else { - return shared.NewInvalidTypeError("Address", "not a string") - } - } - - if len(obj) >= 3 { - if hex, ok := obj[2].(string); ok { - args.ContentHashHex = hex - } else { - return shared.NewInvalidTypeError("ContentHashHex", "not a string") - } - } - - return nil -} - -type RegisterUrlArgs struct { - Sender string - ContentHash string - Url string -} - -func (args *RegisterUrlArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) >= 1 { - if sender, ok := obj[0].(string); ok { - args.Sender = sender - } else { - return shared.NewInvalidTypeError("Sender", "not a string") - } - } - - if len(obj) >= 2 { - if sender, ok := obj[1].(string); ok { - args.ContentHash = sender - } else { - return shared.NewInvalidTypeError("ContentHash", "not a string") - } - } - - if len(obj) >= 3 { - if sender, ok := obj[2].(string); ok { - args.Url = sender - } else { - return shared.NewInvalidTypeError("Url", "not a string") - } - } - - return nil -} - -type GetContractInfoArgs struct { - Contract string -} - -func (args *GetContractInfoArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - if len(obj) >= 1 { - if contract, ok := obj[0].(string); ok { - args.Contract = contract - } else { - return shared.NewInvalidTypeError("Contract", "not a string") - } - } - - return nil -} - -type HttpGetArgs struct { - Uri string - Path string -} - -func (args *HttpGetArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - if len(obj) >= 1 { - if uri, ok := obj[0].(string); ok { - args.Uri = uri - } else { - return shared.NewInvalidTypeError("Uri", "not a string") - } - } - - if len(obj) >= 2 && obj[1] != nil { - if path, ok := obj[1].(string); ok { - args.Path = path - } else { - return shared.NewInvalidTypeError("Path", "not a string") - } - } - - return nil -} diff --git a/rpc/api/admin_js.go b/rpc/api/admin_js.go deleted file mode 100644 index e6171cc74a..0000000000 --- a/rpc/api/admin_js.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2015 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 . - -package api - -const Admin_JS = ` -web3._extend({ - property: 'admin', - methods: - [ - new web3._extend.Method({ - name: 'addPeer', - call: 'admin_addPeer', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'exportChain', - call: 'admin_exportChain', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'importChain', - call: 'admin_importChain', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'sleepBlocks', - call: 'admin_sleepBlocks', - params: 2, - inputFormatter: [null, null] - }), - new web3._extend.Method({ - name: 'setSolc', - call: 'admin_setSolc', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'startRPC', - call: 'admin_startRPC', - params: 4, - inputFormatter: [null, null, null, null] - }), - new web3._extend.Method({ - name: 'stopRPC', - call: 'admin_stopRPC', - params: 0, - inputFormatter: [] - }), - new web3._extend.Method({ - name: 'setGlobalRegistrar', - call: 'admin_setGlobalRegistrar', - params: 2, - inputFormatter: [null,null] - }), - new web3._extend.Method({ - name: 'setHashReg', - call: 'admin_setHashReg', - params: 2, - inputFormatter: [null,null] - }), - new web3._extend.Method({ - name: 'setUrlHint', - call: 'admin_setUrlHint', - params: 2, - inputFormatter: [null,null] - }), - new web3._extend.Method({ - name: 'saveInfo', - call: 'admin_saveInfo', - params: 2, - inputFormatter: [null,null] - }), - new web3._extend.Method({ - name: 'register', - call: 'admin_register', - params: 3, - inputFormatter: [null,null,null] - }), - new web3._extend.Method({ - name: 'registerUrl', - call: 'admin_registerUrl', - params: 3, - inputFormatter: [null,null,null] - }), - new web3._extend.Method({ - name: 'startNatSpec', - call: 'admin_startNatSpec', - params: 0, - inputFormatter: [] - }), - new web3._extend.Method({ - name: 'stopNatSpec', - call: 'admin_stopNatSpec', - params: 0, - inputFormatter: [] - }), - new web3._extend.Method({ - name: 'getContractInfo', - call: 'admin_getContractInfo', - params: 1, - inputFormatter: [null], - }), - new web3._extend.Method({ - name: 'httpGet', - call: 'admin_httpGet', - params: 2, - inputFormatter: [null, null] - }) - ], - properties: - [ - new web3._extend.Property({ - name: 'nodeInfo', - getter: 'admin_nodeInfo' - }), - new web3._extend.Property({ - name: 'peers', - getter: 'admin_peers' - }), - new web3._extend.Property({ - name: 'datadir', - getter: 'admin_datadir' - }) - ] -}); -` diff --git a/rpc/api/api.go b/rpc/api/api.go deleted file mode 100644 index e03250ec6d..0000000000 --- a/rpc/api/api.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "github.com/ethereum/go-ethereum/rpc/shared" -) - -// Merge multiple API's to a single API instance -func Merge(apis ...shared.EthereumApi) shared.EthereumApi { - return newMergedApi(apis...) -} diff --git a/rpc/api/api_test.go b/rpc/api/api_test.go deleted file mode 100644 index eb63e81510..0000000000 --- a/rpc/api/api_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "testing" - - "encoding/json" - "strconv" - - "github.com/ethereum/go-ethereum/common/compiler" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" -) - -func TestParseApiString(t *testing.T) { - apis, err := ParseApiString("", codec.JSON, nil, nil) - if err == nil { - t.Errorf("Expected an err from parsing empty API string but got nil") - } - - if len(apis) != 0 { - t.Errorf("Expected 0 apis from empty API string") - } - - apis, err = ParseApiString("eth", codec.JSON, nil, nil) - if err != nil { - t.Errorf("Expected nil err from parsing empty API string but got %v", err) - } - - if len(apis) != 1 { - t.Errorf("Expected 1 apis but got %d - %v", apis, apis) - } - - apis, err = ParseApiString("eth,eth", codec.JSON, nil, nil) - if err != nil { - t.Errorf("Expected nil err from parsing empty API string but got \"%v\"", err) - } - - if len(apis) != 2 { - t.Errorf("Expected 2 apis but got %d - %v", apis, apis) - } - - apis, err = ParseApiString("eth,invalid", codec.JSON, nil, nil) - if err == nil { - t.Errorf("Expected an err but got no err") - } - -} - -const solcVersion = "0.9.23" - -func TestCompileSolidity(t *testing.T) { - - solc, err := compiler.New("") - if solc == nil { - t.Skip("no solc found: skip") - } else if solc.Version() != solcVersion { - t.Skip("WARNING: skipping test because of solc different version (%v, test written for %v, may need to update)", solc.Version(), solcVersion) - } - source := `contract test {\n` + - " /// @notice Will multiply `a` by 7." + `\n` + - ` function multiply(uint a) returns(uint d) {\n` + - ` return a * 7;\n` + - ` }\n` + - `}\n` - - jsonstr := `{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["` + source + `"],"id":64}` - - expCode := "0x605880600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b603d6004803590602001506047565b8060005260206000f35b60006007820290506053565b91905056" - expAbiDefinition := `[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]` - expUserDoc := `{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}}` - expDeveloperDoc := `{"methods":{}}` - expCompilerVersion := solc.Version() - expLanguage := "Solidity" - expLanguageVersion := "0" - expSource := source - - eth := ð.Ethereum{} - xeth := xeth.NewTest(nil, nil) - api := NewEthApi(xeth, eth, codec.JSON) - - var rpcRequest shared.Request - json.Unmarshal([]byte(jsonstr), &rpcRequest) - - response, err := api.CompileSolidity(&rpcRequest) - if err != nil { - t.Errorf("Execution failed, %v", err) - } - - respjson, err := json.Marshal(response) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - var contracts = make(map[string]*compiler.Contract) - err = json.Unmarshal(respjson, &contracts) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - if len(contracts) != 1 { - t.Errorf("expected one contract, got %v", len(contracts)) - } - - contract := contracts["test"] - - if contract.Code != expCode { - t.Errorf("Expected \n%s got \n%s", expCode, contract.Code) - } - - if strconv.Quote(contract.Info.Source) != `"`+expSource+`"` { - t.Errorf("Expected \n'%s' got \n'%s'", expSource, strconv.Quote(contract.Info.Source)) - } - - if contract.Info.Language != expLanguage { - t.Errorf("Expected %s got %s", expLanguage, contract.Info.Language) - } - - if contract.Info.LanguageVersion != expLanguageVersion { - t.Errorf("Expected %s got %s", expLanguageVersion, contract.Info.LanguageVersion) - } - - if contract.Info.CompilerVersion != expCompilerVersion { - t.Errorf("Expected %s got %s", expCompilerVersion, contract.Info.CompilerVersion) - } - - userdoc, err := json.Marshal(contract.Info.UserDoc) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - devdoc, err := json.Marshal(contract.Info.DeveloperDoc) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - abidef, err := json.Marshal(contract.Info.AbiDefinition) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - if string(abidef) != expAbiDefinition { - t.Errorf("Expected \n'%s' got \n'%s'", expAbiDefinition, string(abidef)) - } - - if string(userdoc) != expUserDoc { - t.Errorf("Expected \n'%s' got \n'%s'", expUserDoc, string(userdoc)) - } - - if string(devdoc) != expDeveloperDoc { - t.Errorf("Expected %s got %s", expDeveloperDoc, string(devdoc)) - } -} diff --git a/rpc/api/args.go b/rpc/api/args.go deleted file mode 100644 index 20f073b670..0000000000 --- a/rpc/api/args.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type CompileArgs struct { - Source string -} - -func (args *CompileArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - argstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("arg0", "is not a string") - } - args.Source = argstr - - return nil -} - -type FilterStringArgs struct { - Word string -} - -func (args *FilterStringArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - var argstr string - argstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("filter", "not a string") - } - switch argstr { - case "latest", "pending": - break - default: - return shared.NewValidationError("Word", "Must be `latest` or `pending`") - } - args.Word = argstr - return nil -} diff --git a/rpc/api/args_test.go b/rpc/api/args_test.go deleted file mode 100644 index 130315bd98..0000000000 --- a/rpc/api/args_test.go +++ /dev/null @@ -1,2649 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "bytes" - "encoding/json" - "fmt" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/rpc/shared" -) - -func TestBlockheightInvalidString(t *testing.T) { - v := "foo" - var num int64 - - str := ExpectInvalidTypeError(blockHeight(v, &num)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockheightEarliest(t *testing.T) { - v := "earliest" - e := int64(0) - var num int64 - - err := blockHeight(v, &num) - if err != nil { - t.Error(err) - } - - if num != e { - t.Errorf("Expected %s but got %s", e, num) - } -} - -func TestBlockheightLatest(t *testing.T) { - v := "latest" - e := int64(-1) - var num int64 - - err := blockHeight(v, &num) - if err != nil { - t.Error(err) - } - - if num != e { - t.Errorf("Expected %s but got %s", e, num) - } -} - -func TestBlockheightPending(t *testing.T) { - v := "pending" - e := int64(-2) - var num int64 - - err := blockHeight(v, &num) - if err != nil { - t.Error(err) - } - - if num != e { - t.Errorf("Expected %s but got %s", e, num) - } -} - -func ExpectValidationError(err error) string { - var str string - switch err.(type) { - case nil: - str = "Expected error but didn't get one" - case *shared.ValidationError: - break - default: - str = fmt.Sprintf("Expected *rpc.ValidationError but got %T with message `%s`", err, err.Error()) - } - return str -} - -func ExpectInvalidTypeError(err error) string { - var str string - switch err.(type) { - case nil: - str = "Expected error but didn't get one" - case *shared.InvalidTypeError: - break - default: - str = fmt.Sprintf("Expected *rpc.InvalidTypeError but got %T with message `%s`", err, err.Error()) - } - return str -} - -func ExpectInsufficientParamsError(err error) string { - var str string - switch err.(type) { - case nil: - str = "Expected error but didn't get one" - case *shared.InsufficientParamsError: - break - default: - str = fmt.Sprintf("Expected *rpc.InsufficientParamsError but got %T with message %s", err, err.Error()) - } - return str -} - -func ExpectDecodeParamError(err error) string { - var str string - switch err.(type) { - case nil: - str = "Expected error but didn't get one" - case *shared.DecodeParamError: - break - default: - str = fmt.Sprintf("Expected *rpc.DecodeParamError but got %T with message `%s`", err, err.Error()) - } - return str -} - -func TestSha3(t *testing.T) { - input := `["0x68656c6c6f20776f726c64"]` - expected := "0x68656c6c6f20776f726c64" - - args := new(Sha3Args) - json.Unmarshal([]byte(input), &args) - - if args.Data != expected { - t.Error("got %s expected %s", input, expected) - } -} - -func TestSha3ArgsInvalid(t *testing.T) { - input := `{}` - - args := new(Sha3Args) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSha3ArgsEmpty(t *testing.T) { - input := `[]` - - args := new(Sha3Args) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} -func TestSha3ArgsDataInvalid(t *testing.T) { - input := `[4]` - - args := new(Sha3Args) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBalanceArgs(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "0x1f"]` - expected := new(GetBalanceArgs) - expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" - expected.BlockNumber = 31 - - args := new(GetBalanceArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.Address != expected.Address { - t.Errorf("Address should be %v but is %v", expected.Address, args.Address) - } - - if args.BlockNumber != expected.BlockNumber { - t.Errorf("BlockNumber should be %v but is %v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetBalanceArgsBlocknumMissing(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1"]` - expected := new(GetBalanceArgs) - expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" - expected.BlockNumber = -1 - - args := new(GetBalanceArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.Address != expected.Address { - t.Errorf("Address should be %v but is %v", expected.Address, args.Address) - } - - if args.BlockNumber != expected.BlockNumber { - t.Errorf("BlockNumber should be %v but is %v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetBalanceArgsLatest(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]` - expected := new(GetBalanceArgs) - expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" - expected.BlockNumber = -1 - - args := new(GetBalanceArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.Address != expected.Address { - t.Errorf("Address should be %v but is %v", expected.Address, args.Address) - } - - if args.BlockNumber != expected.BlockNumber { - t.Errorf("BlockNumber should be %v but is %v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetBalanceArgsEmpty(t *testing.T) { - input := `[]` - - args := new(GetBalanceArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBalanceArgsInvalid(t *testing.T) { - input := `6` - - args := new(GetBalanceArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBalanceArgsBlockInvalid(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", false]` - - args := new(GetBalanceArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBalanceArgsAddressInvalid(t *testing.T) { - input := `[-9, "latest"]` - - args := new(GetBalanceArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBlockByHashArgs(t *testing.T) { - input := `["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", true]` - expected := new(GetBlockByHashArgs) - expected.BlockHash = "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" - expected.IncludeTxs = true - - args := new(GetBlockByHashArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.BlockHash != expected.BlockHash { - t.Errorf("BlockHash should be %v but is %v", expected.BlockHash, args.BlockHash) - } - - if args.IncludeTxs != expected.IncludeTxs { - t.Errorf("IncludeTxs should be %v but is %v", expected.IncludeTxs, args.IncludeTxs) - } -} - -func TestGetBlockByHashArgsEmpty(t *testing.T) { - input := `[]` - - args := new(GetBlockByHashArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBlockByHashArgsInvalid(t *testing.T) { - input := `{}` - - args := new(GetBlockByHashArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBlockByHashArgsHashInt(t *testing.T) { - input := `[8]` - - args := new(GetBlockByHashArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBlockByHashArgsHashBool(t *testing.T) { - input := `[false, true]` - - args := new(GetBlockByHashArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBlockByNumberArgsBlockNum(t *testing.T) { - input := `[436, false]` - expected := new(GetBlockByNumberArgs) - expected.BlockNumber = 436 - expected.IncludeTxs = false - - args := new(GetBlockByNumberArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.BlockNumber != expected.BlockNumber { - t.Errorf("BlockNumber should be %v but is %v", expected.BlockNumber, args.BlockNumber) - } - - if args.IncludeTxs != expected.IncludeTxs { - t.Errorf("IncludeTxs should be %v but is %v", expected.IncludeTxs, args.IncludeTxs) - } -} - -func TestGetBlockByNumberArgsBlockHex(t *testing.T) { - input := `["0x1b4", false]` - expected := new(GetBlockByNumberArgs) - expected.BlockNumber = 436 - expected.IncludeTxs = false - - args := new(GetBlockByNumberArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.BlockNumber != expected.BlockNumber { - t.Errorf("BlockNumber should be %v but is %v", expected.BlockNumber, args.BlockNumber) - } - - if args.IncludeTxs != expected.IncludeTxs { - t.Errorf("IncludeTxs should be %v but is %v", expected.IncludeTxs, args.IncludeTxs) - } -} -func TestGetBlockByNumberArgsWords(t *testing.T) { - input := `["earliest", true]` - expected := new(GetBlockByNumberArgs) - expected.BlockNumber = 0 - expected.IncludeTxs = true - - args := new(GetBlockByNumberArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.BlockNumber != expected.BlockNumber { - t.Errorf("BlockNumber should be %v but is %v", expected.BlockNumber, args.BlockNumber) - } - - if args.IncludeTxs != expected.IncludeTxs { - t.Errorf("IncludeTxs should be %v but is %v", expected.IncludeTxs, args.IncludeTxs) - } -} - -func TestGetBlockByNumberEmpty(t *testing.T) { - input := `[]` - - args := new(GetBlockByNumberArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBlockByNumberShort(t *testing.T) { - input := `["0xbbb"]` - - args := new(GetBlockByNumberArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetBlockByNumberBool(t *testing.T) { - input := `[true, true]` - - args := new(GetBlockByNumberArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} -func TestGetBlockByNumberBlockObject(t *testing.T) { - input := `{}` - - args := new(GetBlockByNumberArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestNewTxArgs(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": "0x9184e72a000", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, - "0x10"]` - expected := new(NewTxArgs) - expected.From = "0xb60e8dd61c5d32be8058bb8eb970870f07233155" - expected.To = "0xd46e8dd67c5d32be8058bb8eb970870f072445675" - expected.Gas = big.NewInt(30400) - expected.GasPrice = big.NewInt(10000000000000) - expected.Value = big.NewInt(10000000000000) - expected.Data = "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - expected.BlockNumber = big.NewInt(16).Int64() - - args := new(NewTxArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.From != args.From { - t.Errorf("From shoud be %#v but is %#v", expected.From, args.From) - } - - if expected.To != args.To { - t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) - } - - if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { - t.Errorf("Gas shoud be %#v but is %#v", expected.Gas.Bytes(), args.Gas.Bytes()) - } - - if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { - t.Errorf("GasPrice shoud be %#v but is %#v", expected.GasPrice, args.GasPrice) - } - - if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { - t.Errorf("Value shoud be %#v but is %#v", expected.Value, args.Value) - } - - if expected.Data != args.Data { - t.Errorf("Data shoud be %#v but is %#v", expected.Data, args.Data) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestNewTxArgsInt(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": 100, - "gasPrice": 50, - "value": 8765456789, - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, - 5]` - expected := new(NewTxArgs) - expected.Gas = big.NewInt(100) - expected.GasPrice = big.NewInt(50) - expected.Value = big.NewInt(8765456789) - expected.BlockNumber = int64(5) - - args := new(NewTxArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { - t.Errorf("Gas shoud be %v but is %v", expected.Gas, args.Gas) - } - - if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { - t.Errorf("GasPrice shoud be %v but is %v", expected.GasPrice, args.GasPrice) - } - - if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { - t.Errorf("Value shoud be %v but is %v", expected.Value, args.Value) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %v but is %v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestNewTxArgsBlockBool(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": "0x9184e72a000", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, - false]` - - args := new(NewTxArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestNewTxArgsGasInvalid(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": false, - "gasPrice": "0x9184e72a000", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - - args := new(NewTxArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestNewTxArgsGaspriceInvalid(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": false, - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - - args := new(NewTxArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestNewTxArgsValueInvalid(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": "0x9184e72a000", - "value": false, - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - - args := new(NewTxArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestNewTxArgsGasMissing(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gasPrice": "0x9184e72a000", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - expected := new(NewTxArgs) - expected.Gas = nil - - args := new(NewTxArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.Gas != expected.Gas { - // if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { - t.Errorf("Gas shoud be %v but is %v", expected.Gas, args.Gas) - } -} - -func TestNewTxArgsBlockGaspriceMissing(t *testing.T) { - input := `[{ - "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - expected := new(NewTxArgs) - expected.GasPrice = nil - - args := new(NewTxArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.GasPrice != expected.GasPrice { - // if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { - t.Errorf("GasPrice shoud be %v but is %v", expected.GasPrice, args.GasPrice) - } - -} - -func TestNewTxArgsValueMissing(t *testing.T) { - input := `[{ - "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - expected := new(NewTxArgs) - expected.Value = big.NewInt(0) - - args := new(NewTxArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { - t.Errorf("Value shoud be %v but is %v", expected.Value, args.Value) - } - -} - -func TestNewTxArgsEmpty(t *testing.T) { - input := `[]` - - args := new(NewTxArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestNewTxArgsInvalid(t *testing.T) { - input := `{}` - - args := new(NewTxArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} -func TestNewTxArgsNotStrings(t *testing.T) { - input := `[{"from":6}]` - - args := new(NewTxArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestNewTxArgsFromEmpty(t *testing.T) { - input := `[{"to": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]` - - args := new(NewTxArgs) - str := ExpectValidationError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCallArgs(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": "0x9184e72a000", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, - "0x10"]` - expected := new(CallArgs) - expected.From = "0xb60e8dd61c5d32be8058bb8eb970870f07233155" - expected.To = "0xd46e8dd67c5d32be8058bb8eb970870f072445675" - expected.Gas = big.NewInt(30400) - expected.GasPrice = big.NewInt(10000000000000) - expected.Value = big.NewInt(10000000000000) - expected.Data = "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - expected.BlockNumber = big.NewInt(16).Int64() - - args := new(CallArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.To != args.To { - t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) - } - - if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { - t.Errorf("Gas shoud be %#v but is %#v", expected.Gas.Bytes(), args.Gas.Bytes()) - } - - if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { - t.Errorf("GasPrice shoud be %#v but is %#v", expected.GasPrice, args.GasPrice) - } - - if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { - t.Errorf("Value shoud be %#v but is %#v", expected.Value, args.Value) - } - - if expected.Data != args.Data { - t.Errorf("Data shoud be %#v but is %#v", expected.Data, args.Data) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestCallArgsInt(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": 100, - "gasPrice": 50, - "value": 8765456789, - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, - 5]` - expected := new(CallArgs) - expected.Gas = big.NewInt(100) - expected.GasPrice = big.NewInt(50) - expected.Value = big.NewInt(8765456789) - expected.BlockNumber = int64(5) - - args := new(CallArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { - t.Errorf("Gas shoud be %v but is %v", expected.Gas, args.Gas) - } - - if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { - t.Errorf("GasPrice shoud be %v but is %v", expected.GasPrice, args.GasPrice) - } - - if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { - t.Errorf("Value shoud be %v but is %v", expected.Value, args.Value) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %v but is %v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestCallArgsBlockBool(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": "0x9184e72a000", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, - false]` - - args := new(CallArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCallArgsGasInvalid(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": false, - "gasPrice": "0x9184e72a000", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - - args := new(CallArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCallArgsGaspriceInvalid(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": false, - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - - args := new(CallArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCallArgsValueInvalid(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": "0x9184e72a000", - "value": false, - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - - args := new(CallArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCallArgsGasMissing(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gasPrice": "0x9184e72a000", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - - args := new(CallArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - expected := new(CallArgs) - expected.Gas = nil - - if args.Gas != expected.Gas { - // if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 { - t.Errorf("Gas shoud be %v but is %v", expected.Gas, args.Gas) - } - -} - -func TestCallArgsBlockGaspriceMissing(t *testing.T) { - input := `[{ - "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "value": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - - args := new(CallArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - expected := new(CallArgs) - expected.GasPrice = nil - - if args.GasPrice != expected.GasPrice { - // if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 { - t.Errorf("GasPrice shoud be %v but is %v", expected.GasPrice, args.GasPrice) - } -} - -func TestCallArgsValueMissing(t *testing.T) { - input := `[{ - "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", - "gas": "0x76c0", - "gasPrice": "0x9184e72a000", - "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" - }]` - - args := new(CallArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - expected := new(CallArgs) - expected.Value = big.NewInt(int64(0)) - - if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 { - t.Errorf("GasPrice shoud be %v but is %v", expected.Value, args.Value) - } -} - -func TestCallArgsEmpty(t *testing.T) { - input := `[]` - - args := new(CallArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCallArgsInvalid(t *testing.T) { - input := `{}` - - args := new(CallArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} -func TestCallArgsNotStrings(t *testing.T) { - input := `[{"from":6}]` - - args := new(CallArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCallArgsToEmpty(t *testing.T) { - input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]` - args := new(CallArgs) - err := json.Unmarshal([]byte(input), &args) - if err != nil { - t.Error("Did not expect error. Got", err) - } -} - -func TestGetStorageArgs(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]` - expected := new(GetStorageArgs) - expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" - expected.BlockNumber = -1 - - args := new(GetStorageArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Address != args.Address { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetStorageArgsMissingBlocknum(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1"]` - expected := new(GetStorageArgs) - expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" - expected.BlockNumber = -1 - - args := new(GetStorageArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Address != args.Address { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetStorageInvalidArgs(t *testing.T) { - input := `{}` - - args := new(GetStorageArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetStorageInvalidBlockheight(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", {}]` - - args := new(GetStorageArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetStorageEmptyArgs(t *testing.T) { - input := `[]` - - args := new(GetStorageArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetStorageAddressInt(t *testing.T) { - input := `[32456785432456, "latest"]` - - args := new(GetStorageArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetStorageAtArgs(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "0x0", "0x2"]` - expected := new(GetStorageAtArgs) - expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" - expected.Key = "0x0" - expected.BlockNumber = 2 - - args := new(GetStorageAtArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Address != args.Address { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.Key != args.Key { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetStorageAtArgsMissingBlocknum(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "0x0"]` - expected := new(GetStorageAtArgs) - expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" - expected.Key = "0x0" - expected.BlockNumber = -1 - - args := new(GetStorageAtArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Address != args.Address { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.Key != args.Key { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetStorageAtEmptyArgs(t *testing.T) { - input := `[]` - - args := new(GetStorageAtArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetStorageAtArgsInvalid(t *testing.T) { - input := `{}` - - args := new(GetStorageAtArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetStorageAtArgsAddressNotString(t *testing.T) { - input := `[true, "0x0", "0x2"]` - - args := new(GetStorageAtArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetStorageAtArgsKeyNotString(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", true, "0x2"]` - - args := new(GetStorageAtArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetStorageAtArgsValueNotString(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "0x1", true]` - - args := new(GetStorageAtArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetTxCountArgs(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "pending"]` - expected := new(GetTxCountArgs) - expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" - expected.BlockNumber = -2 - - args := new(GetTxCountArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Address != args.Address { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetTxCountEmptyArgs(t *testing.T) { - input := `[]` - - args := new(GetTxCountArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetTxCountEmptyArgsInvalid(t *testing.T) { - input := `false` - - args := new(GetTxCountArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetTxCountAddressNotString(t *testing.T) { - input := `[false, "pending"]` - - args := new(GetTxCountArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetTxCountBlockheightMissing(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1"]` - expected := new(GetTxCountArgs) - expected.Address = "0x407d73d8a49eeb85d32cf465507dd71d507100c1" - expected.BlockNumber = -1 - - args := new(GetTxCountArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Address != args.Address { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetTxCountBlockheightInvalid(t *testing.T) { - input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", {}]` - - args := new(GetTxCountArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetDataArgs(t *testing.T) { - input := `["0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", "latest"]` - expected := new(GetDataArgs) - expected.Address = "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8" - expected.BlockNumber = -1 - - args := new(GetDataArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Address != args.Address { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetDataArgsBlocknumMissing(t *testing.T) { - input := `["0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8"]` - expected := new(GetDataArgs) - expected.Address = "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8" - expected.BlockNumber = -1 - - args := new(GetDataArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Address != args.Address { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestGetDataArgsEmpty(t *testing.T) { - input := `[]` - - args := new(GetDataArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetDataArgsInvalid(t *testing.T) { - input := `{}` - - args := new(GetDataArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetDataArgsAddressNotString(t *testing.T) { - input := `[12, "latest"]` - - args := new(GetDataArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestGetDataArgsBlocknumberNotString(t *testing.T) { - input := `["0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", false]` - - args := new(GetDataArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgs(t *testing.T) { - input := `[{ - "fromBlock": "0x1", - "toBlock": "0x2", - "limit": "0x3", - "offset": "0x0", - "address": "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", - "topics": - [ - ["0xAA", "0xBB"], - ["0xCC", "0xDD"] - ] - }]` - - expected := new(BlockFilterArgs) - expected.Earliest = 1 - expected.Latest = 2 - expected.Max = 3 - expected.Skip = 0 - expected.Address = []string{"0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8"} - expected.Topics = [][]string{ - []string{"0xAA", "0xBB"}, - []string{"0xCC", "0xDD"}, - } - - args := new(BlockFilterArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Earliest != args.Earliest { - t.Errorf("Earliest shoud be %#v but is %#v", expected.Earliest, args.Earliest) - } - - if expected.Latest != args.Latest { - t.Errorf("Latest shoud be %#v but is %#v", expected.Latest, args.Latest) - } - - if expected.Max != args.Max { - t.Errorf("Max shoud be %#v but is %#v", expected.Max, args.Max) - } - - if expected.Skip != args.Skip { - t.Errorf("Skip shoud be %#v but is %#v", expected.Skip, args.Skip) - } - - if expected.Address[0] != args.Address[0] { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.Topics[0][0] != args.Topics[0][0] { - t.Errorf("Topics shoud be %#v but is %#v", expected.Topics, args.Topics) - } - if expected.Topics[0][1] != args.Topics[0][1] { - t.Errorf("Topics shoud be %#v but is %#v", expected.Topics, args.Topics) - } - if expected.Topics[1][0] != args.Topics[1][0] { - t.Errorf("Topics shoud be %#v but is %#v", expected.Topics, args.Topics) - } - if expected.Topics[1][1] != args.Topics[1][1] { - t.Errorf("Topics shoud be %#v but is %#v", expected.Topics, args.Topics) - } - -} - -func TestBlockFilterArgsDefaults(t *testing.T) { - input := `[{ - "address": ["0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8"], - "topics": ["0xAA","0xBB"] - }]` - expected := new(BlockFilterArgs) - expected.Earliest = -1 - expected.Latest = -1 - expected.Max = 100 - expected.Skip = 0 - expected.Address = []string{"0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8"} - expected.Topics = [][]string{[]string{"0xAA"}, []string{"0xBB"}} - - args := new(BlockFilterArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Earliest != args.Earliest { - t.Errorf("Earliest shoud be %#v but is %#v", expected.Earliest, args.Earliest) - } - - if expected.Latest != args.Latest { - t.Errorf("Latest shoud be %#v but is %#v", expected.Latest, args.Latest) - } - - if expected.Max != args.Max { - t.Errorf("Max shoud be %#v but is %#v", expected.Max, args.Max) - } - - if expected.Skip != args.Skip { - t.Errorf("Skip shoud be %#v but is %#v", expected.Skip, args.Skip) - } - - if expected.Address[0] != args.Address[0] { - t.Errorf("Address shoud be %#v but is %#v", expected.Address, args.Address) - } - - if expected.Topics[0][0] != args.Topics[0][0] { - t.Errorf("Topics shoud be %#v but is %#v", expected.Topics, args.Topics) - } - - if expected.Topics[1][0] != args.Topics[1][0] { - t.Errorf("Topics shoud be %#v but is %#v", expected.Topics, args.Topics) - } -} - -func TestBlockFilterArgsWords(t *testing.T) { - input := `[{"fromBlock": "latest", "toBlock": "latest"}]` - expected := new(BlockFilterArgs) - expected.Earliest = -1 - expected.Latest = -1 - - args := new(BlockFilterArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Earliest != args.Earliest { - t.Errorf("Earliest shoud be %#v but is %#v", expected.Earliest, args.Earliest) - } - - input = `[{"toBlock": "pending"}]` - if err := json.Unmarshal([]byte(input), &args); err == nil { - t.Errorf("Pending isn't currently supported and should raise an unsupported error") - } -} - -func TestBlockFilterArgsInvalid(t *testing.T) { - input := `{}` - - args := new(BlockFilterArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsFromBool(t *testing.T) { - input := `[{ - "fromBlock": true, - "toBlock": "pending" - }]` - - args := new(BlockFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsToBool(t *testing.T) { - input := `[{ - "fromBlock": "pending", - "toBlock": true - }]` - - args := new(BlockFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsEmptyArgs(t *testing.T) { - input := `[]` - - args := new(BlockFilterArgs) - err := json.Unmarshal([]byte(input), &args) - if err == nil { - t.Error("Expected error but didn't get one") - } -} - -func TestBlockFilterArgsLimitInvalid(t *testing.T) { - input := `[{"limit": false}]` - - args := new(BlockFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsOffsetInvalid(t *testing.T) { - input := `[{"offset": true}]` - - args := new(BlockFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsAddressInt(t *testing.T) { - input := `[{ - "address": 1, - "topics": "0x12341234"}]` - - args := new(BlockFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsAddressSliceInt(t *testing.T) { - input := `[{ - "address": [1], - "topics": "0x12341234"}]` - - args := new(BlockFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsTopicInt(t *testing.T) { - input := `[{ - "address": ["0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8"], - "topics": 1}]` - - args := new(BlockFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsTopicSliceInt(t *testing.T) { - input := `[{ - "address": "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", - "topics": [1]}]` - - args := new(BlockFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsTopicSliceInt2(t *testing.T) { - input := `[{ - "address": "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", - "topics": ["0xAA", [1]]}]` - - args := new(BlockFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockFilterArgsTopicComplex(t *testing.T) { - input := `[{ - "address": "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", - "topics": ["0xAA", ["0xBB", "0xCC"]] - }]` - - args := new(BlockFilterArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - fmt.Printf("%v\n", args) - return - } - - if args.Topics[0][0] != "0xAA" { - t.Errorf("Topic should be %s but is %s", "0xAA", args.Topics[0][0]) - } - - if args.Topics[1][0] != "0xBB" { - t.Errorf("Topic should be %s but is %s", "0xBB", args.Topics[0][0]) - } - - if args.Topics[1][1] != "0xCC" { - t.Errorf("Topic should be %s but is %s", "0xCC", args.Topics[0][0]) - } -} - -func TestDbArgs(t *testing.T) { - input := `["testDB","myKey","0xbeef"]` - expected := new(DbArgs) - expected.Database = "testDB" - expected.Key = "myKey" - expected.Value = []byte("0xbeef") - - args := new(DbArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if err := args.requirements(); err != nil { - t.Error(err) - } - - if expected.Database != args.Database { - t.Errorf("Database shoud be %#v but is %#v", expected.Database, args.Database) - } - - if expected.Key != args.Key { - t.Errorf("Key shoud be %#v but is %#v", expected.Key, args.Key) - } - - if bytes.Compare(expected.Value, args.Value) != 0 { - t.Errorf("Value shoud be %#v but is %#v", expected.Value, args.Value) - } -} - -func TestDbArgsInvalid(t *testing.T) { - input := `{}` - - args := new(DbArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbArgsEmpty(t *testing.T) { - input := `[]` - - args := new(DbArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbArgsDatabaseType(t *testing.T) { - input := `[true, "keyval", "valval"]` - - args := new(DbArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbArgsKeyType(t *testing.T) { - input := `["dbval", 3, "valval"]` - - args := new(DbArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbArgsValType(t *testing.T) { - input := `["dbval", "keyval", {}]` - - args := new(DbArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbArgsDatabaseEmpty(t *testing.T) { - input := `["", "keyval", "valval"]` - - args := new(DbArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err.Error()) - } - - str := ExpectValidationError(args.requirements()) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbArgsKeyEmpty(t *testing.T) { - input := `["dbval", "", "valval"]` - - args := new(DbArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err.Error()) - } - - str := ExpectValidationError(args.requirements()) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbHexArgs(t *testing.T) { - input := `["testDB","myKey","0xbeef"]` - expected := new(DbHexArgs) - expected.Database = "testDB" - expected.Key = "myKey" - expected.Value = []byte{0xbe, 0xef} - - args := new(DbHexArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if err := args.requirements(); err != nil { - t.Error(err) - } - - if expected.Database != args.Database { - t.Errorf("Database shoud be %#v but is %#v", expected.Database, args.Database) - } - - if expected.Key != args.Key { - t.Errorf("Key shoud be %#v but is %#v", expected.Key, args.Key) - } - - if bytes.Compare(expected.Value, args.Value) != 0 { - t.Errorf("Value shoud be %#v but is %#v", expected.Value, args.Value) - } -} - -func TestDbHexArgsInvalid(t *testing.T) { - input := `{}` - - args := new(DbHexArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbHexArgsEmpty(t *testing.T) { - input := `[]` - - args := new(DbHexArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbHexArgsDatabaseType(t *testing.T) { - input := `[true, "keyval", "valval"]` - - args := new(DbHexArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbHexArgsKeyType(t *testing.T) { - input := `["dbval", 3, "valval"]` - - args := new(DbHexArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbHexArgsValType(t *testing.T) { - input := `["dbval", "keyval", {}]` - - args := new(DbHexArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbHexArgsDatabaseEmpty(t *testing.T) { - input := `["", "keyval", "valval"]` - - args := new(DbHexArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err.Error()) - } - - str := ExpectValidationError(args.requirements()) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDbHexArgsKeyEmpty(t *testing.T) { - input := `["dbval", "", "valval"]` - - args := new(DbHexArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err.Error()) - } - - str := ExpectValidationError(args.requirements()) - if len(str) > 0 { - t.Error(str) - } -} - -func TestWhisperMessageArgs(t *testing.T) { - input := `[{"from":"0xc931d93e97ab07fe42d923478ba2465f2", - "topics": ["0x68656c6c6f20776f726c64"], - "payload":"0x68656c6c6f20776f726c64", - "ttl": "0x64", - "priority": "0x64"}]` - expected := new(WhisperMessageArgs) - expected.From = "0xc931d93e97ab07fe42d923478ba2465f2" - expected.To = "" - expected.Payload = "0x68656c6c6f20776f726c64" - expected.Priority = 100 - expected.Ttl = 100 - // expected.Topics = []string{"0x68656c6c6f20776f726c64"} - - args := new(WhisperMessageArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.From != args.From { - t.Errorf("From shoud be %#v but is %#v", expected.From, args.From) - } - - if expected.To != args.To { - t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) - } - - if expected.Payload != args.Payload { - t.Errorf("Value shoud be %#v but is %#v", expected.Payload, args.Payload) - } - - if expected.Ttl != args.Ttl { - t.Errorf("Ttl shoud be %#v but is %#v", expected.Ttl, args.Ttl) - } - - if expected.Priority != args.Priority { - t.Errorf("Priority shoud be %#v but is %#v", expected.Priority, args.Priority) - } - - // if expected.Topics != args.Topics { - // t.Errorf("Topic shoud be %#v but is %#v", expected.Topic, args.Topic) - // } -} - -func TestWhisperMessageArgsInt(t *testing.T) { - input := `[{"from":"0xc931d93e97ab07fe42d923478ba2465f2", - "topics": ["0x68656c6c6f20776f726c64"], - "payload":"0x68656c6c6f20776f726c64", - "ttl": 12, - "priority": 16}]` - expected := new(WhisperMessageArgs) - expected.From = "0xc931d93e97ab07fe42d923478ba2465f2" - expected.To = "" - expected.Payload = "0x68656c6c6f20776f726c64" - expected.Priority = 16 - expected.Ttl = 12 - // expected.Topics = []string{"0x68656c6c6f20776f726c64"} - - args := new(WhisperMessageArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.From != args.From { - t.Errorf("From shoud be %#v but is %#v", expected.From, args.From) - } - - if expected.To != args.To { - t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) - } - - if expected.Payload != args.Payload { - t.Errorf("Value shoud be %#v but is %#v", expected.Payload, args.Payload) - } - - if expected.Ttl != args.Ttl { - t.Errorf("Ttl shoud be %v but is %v", expected.Ttl, args.Ttl) - } - - if expected.Priority != args.Priority { - t.Errorf("Priority shoud be %v but is %v", expected.Priority, args.Priority) - } - - // if expected.Topics != args.Topics { - // t.Errorf("Topic shoud be %#v but is %#v", expected.Topic, args.Topic) - // } -} - -func TestWhisperMessageArgsInvalid(t *testing.T) { - input := `{}` - - args := new(WhisperMessageArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestWhisperMessageArgsEmpty(t *testing.T) { - input := `[]` - - args := new(WhisperMessageArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestWhisperMessageArgsTtlBool(t *testing.T) { - input := `[{"from":"0xc931d93e97ab07fe42d923478ba2465f2", - "topics": ["0x68656c6c6f20776f726c64"], - "payload":"0x68656c6c6f20776f726c64", - "ttl": true, - "priority": "0x64"}]` - args := new(WhisperMessageArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestWhisperMessageArgsPriorityBool(t *testing.T) { - input := `[{"from":"0xc931d93e97ab07fe42d923478ba2465f2", - "topics": ["0x68656c6c6f20776f726c64"], - "payload":"0x68656c6c6f20776f726c64", - "ttl": "0x12", - "priority": true}]` - args := new(WhisperMessageArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestFilterIdArgs(t *testing.T) { - input := `["0x7"]` - expected := new(FilterIdArgs) - expected.Id = 7 - - args := new(FilterIdArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Id != args.Id { - t.Errorf("Id shoud be %#v but is %#v", expected.Id, args.Id) - } -} - -func TestFilterIdArgsInvalid(t *testing.T) { - input := `{}` - - args := new(FilterIdArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestFilterIdArgsEmpty(t *testing.T) { - input := `[]` - - args := new(FilterIdArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestFilterIdArgsBool(t *testing.T) { - input := `[true]` - - args := new(FilterIdArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestWhisperFilterArgs(t *testing.T) { - input := `[{"topics": ["0x68656c6c6f20776f726c64"], "to": "0x34ag445g3455b34"}]` - expected := new(WhisperFilterArgs) - expected.To = "0x34ag445g3455b34" - expected.Topics = [][]string{[]string{"0x68656c6c6f20776f726c64"}} - - args := new(WhisperFilterArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.To != args.To { - t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) - } - - // if expected.Topics != args.Topics { - // t.Errorf("Topics shoud be %#v but is %#v", expected.Topics, args.Topics) - // } -} - -func TestWhisperFilterArgsInvalid(t *testing.T) { - input := `{}` - - args := new(WhisperFilterArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestWhisperFilterArgsEmpty(t *testing.T) { - input := `[]` - - args := new(WhisperFilterArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestWhisperFilterArgsToInt(t *testing.T) { - input := `[{"to": 2}]` - - args := new(WhisperFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestWhisperFilterArgsToBool(t *testing.T) { - input := `[{"topics": ["0x68656c6c6f20776f726c64"], "to": false}]` - - args := new(WhisperFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestWhisperFilterArgsToMissing(t *testing.T) { - input := `[{"topics": ["0x68656c6c6f20776f726c64"]}]` - expected := new(WhisperFilterArgs) - expected.To = "" - - args := new(WhisperFilterArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if args.To != expected.To { - t.Errorf("To shoud be %v but is %v", expected.To, args.To) - } -} - -func TestWhisperFilterArgsTopicInt(t *testing.T) { - input := `[{"topics": [6], "to": "0x34ag445g3455b34"}]` - - args := new(WhisperFilterArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCompileArgs(t *testing.T) { - input := `["contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"]` - expected := new(CompileArgs) - expected.Source = `contract test { function multiply(uint a) returns(uint d) { return a * 7; } }` - - args := new(CompileArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Source != args.Source { - t.Errorf("Source shoud be %#v but is %#v", expected.Source, args.Source) - } -} - -func TestCompileArgsInvalid(t *testing.T) { - input := `{}` - - args := new(CompileArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCompileArgsEmpty(t *testing.T) { - input := `[]` - - args := new(CompileArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestCompileArgsBool(t *testing.T) { - input := `[false]` - - args := new(CompileArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestFilterStringArgs(t *testing.T) { - input := `["pending"]` - expected := new(FilterStringArgs) - expected.Word = "pending" - - args := new(FilterStringArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Word != args.Word { - t.Errorf("Word shoud be %#v but is %#v", expected.Word, args.Word) - } -} - -func TestFilterStringEmptyArgs(t *testing.T) { - input := `[]` - - args := new(FilterStringArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestFilterStringInvalidArgs(t *testing.T) { - input := `{}` - - args := new(FilterStringArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestFilterStringWordInt(t *testing.T) { - input := `[7]` - - args := new(FilterStringArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestFilterStringWordWrong(t *testing.T) { - input := `["foo"]` - - args := new(FilterStringArgs) - str := ExpectValidationError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestWhisperIdentityArgs(t *testing.T) { - input := `["0xc931d93e97ab07fe42d923478ba2465f283"]` - expected := new(WhisperIdentityArgs) - expected.Identity = "0xc931d93e97ab07fe42d923478ba2465f283" - - args := new(WhisperIdentityArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Identity != args.Identity { - t.Errorf("Identity shoud be %#v but is %#v", expected.Identity, args.Identity) - } -} - -func TestWhisperIdentityArgsInvalid(t *testing.T) { - input := `{}` - - args := new(WhisperIdentityArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestWhisperIdentityArgsEmpty(t *testing.T) { - input := `[]` - - args := new(WhisperIdentityArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestWhisperIdentityArgsInt(t *testing.T) { - input := `[4]` - - args := new(WhisperIdentityArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Errorf(str) - } -} - -func TestBlockNumArgs(t *testing.T) { - input := `["0x29a"]` - expected := new(BlockNumIndexArgs) - expected.BlockNumber = 666 - - args := new(BlockNumArg) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestBlockNumArgsWord(t *testing.T) { - input := `["pending"]` - expected := new(BlockNumIndexArgs) - expected.BlockNumber = -2 - - args := new(BlockNumArg) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } -} - -func TestBlockNumArgsInvalid(t *testing.T) { - input := `{}` - - args := new(BlockNumArg) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockNumArgsEmpty(t *testing.T) { - input := `[]` - - args := new(BlockNumArg) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} -func TestBlockNumArgsBool(t *testing.T) { - input := `[true]` - - args := new(BlockNumArg) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockNumIndexArgs(t *testing.T) { - input := `["0x29a", "0x0"]` - expected := new(BlockNumIndexArgs) - expected.BlockNumber = 666 - expected.Index = 0 - - args := new(BlockNumIndexArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } - - if expected.Index != args.Index { - t.Errorf("Index shoud be %#v but is %#v", expected.Index, args.Index) - } -} - -func TestBlockNumIndexArgsWord(t *testing.T) { - input := `["latest", 67]` - expected := new(BlockNumIndexArgs) - expected.BlockNumber = -1 - expected.Index = 67 - - args := new(BlockNumIndexArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.BlockNumber != args.BlockNumber { - t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber) - } - - if expected.Index != args.Index { - t.Errorf("Index shoud be %#v but is %#v", expected.Index, args.Index) - } -} - -func TestBlockNumIndexArgsEmpty(t *testing.T) { - input := `[]` - - args := new(BlockNumIndexArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockNumIndexArgsInvalid(t *testing.T) { - input := `"foo"` - - args := new(BlockNumIndexArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockNumIndexArgsBlocknumInvalid(t *testing.T) { - input := `[{}, "0x1"]` - - args := new(BlockNumIndexArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockNumIndexArgsIndexInvalid(t *testing.T) { - input := `["0x29a", true]` - - args := new(BlockNumIndexArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestHashIndexArgs(t *testing.T) { - input := `["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "0x1"]` - expected := new(HashIndexArgs) - expected.Hash = "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b" - expected.Index = 1 - - args := new(HashIndexArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Hash != args.Hash { - t.Errorf("Hash shoud be %#v but is %#v", expected.Hash, args.Hash) - } - - if expected.Index != args.Index { - t.Errorf("Index shoud be %#v but is %#v", expected.Index, args.Index) - } -} - -func TestHashIndexArgsEmpty(t *testing.T) { - input := `[]` - - args := new(HashIndexArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestHashIndexArgsInvalid(t *testing.T) { - input := `{}` - - args := new(HashIndexArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestHashIndexArgsInvalidHash(t *testing.T) { - input := `[7, "0x1"]` - - args := new(HashIndexArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestHashIndexArgsInvalidIndex(t *testing.T) { - input := `["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", false]` - - args := new(HashIndexArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestHashArgs(t *testing.T) { - input := `["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"]` - expected := new(HashIndexArgs) - expected.Hash = "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b" - - args := new(HashArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Hash != args.Hash { - t.Errorf("Hash shoud be %#v but is %#v", expected.Hash, args.Hash) - } -} - -func TestHashArgsEmpty(t *testing.T) { - input := `[]` - - args := new(HashArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestHashArgsInvalid(t *testing.T) { - input := `{}` - - args := new(HashArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestHashArgsInvalidHash(t *testing.T) { - input := `[7]` - - args := new(HashArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSubmitWorkArgs(t *testing.T) { - input := `["0x0000000000000001", "0x1234567890abcdef1234567890abcdef", "0xD1GE5700000000000000000000000000"]` - expected := new(SubmitWorkArgs) - expected.Nonce = 1 - expected.Header = "0x1234567890abcdef1234567890abcdef" - expected.Digest = "0xD1GE5700000000000000000000000000" - - args := new(SubmitWorkArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Nonce != args.Nonce { - t.Errorf("Nonce shoud be %d but is %d", expected.Nonce, args.Nonce) - } - - if expected.Header != args.Header { - t.Errorf("Header shoud be %#v but is %#v", expected.Header, args.Header) - } - - if expected.Digest != args.Digest { - t.Errorf("Digest shoud be %#v but is %#v", expected.Digest, args.Digest) - } -} - -func TestSubmitWorkArgsInvalid(t *testing.T) { - input := `{}` - - args := new(SubmitWorkArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSubmitWorkArgsEmpty(t *testing.T) { - input := `[]` - - args := new(SubmitWorkArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSubmitWorkArgsNonceInt(t *testing.T) { - input := `[1, "0x1234567890abcdef1234567890abcdef", "0xD1GE5700000000000000000000000000"]` - - args := new(SubmitWorkArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} -func TestSubmitWorkArgsHeaderInt(t *testing.T) { - input := `["0x0000000000000001", 1, "0xD1GE5700000000000000000000000000"]` - - args := new(SubmitWorkArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} -func TestSubmitWorkArgsDigestInt(t *testing.T) { - input := `["0x0000000000000001", "0x1234567890abcdef1234567890abcdef", 1]` - - args := new(SubmitWorkArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestBlockHeightFromJsonInvalid(t *testing.T) { - var num int64 - var msg json.RawMessage = []byte(`}{`) - str := ExpectDecodeParamError(blockHeightFromJson(msg, &num)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSourceArgsEmpty(t *testing.T) { - input := `[]` - - args := new(SourceArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSigArgs(t *testing.T) { - input := `["0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "0x0"]` - expected := new(NewSigArgs) - expected.From = "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" - expected.Data = "0x0" - - args := new(NewSigArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.From != args.From { - t.Errorf("From should be %v but is %v", expected.From, args.From) - } - - if expected.Data != args.Data { - t.Errorf("Data should be %v but is %v", expected.Data, args.Data) - } -} - -func TestSigArgsEmptyData(t *testing.T) { - input := `["0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", ""]` - - args := new(NewSigArgs) - str := ExpectValidationError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSigArgsDataType(t *testing.T) { - input := `["0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", 13]` - - args := new(NewSigArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSigArgsEmptyFrom(t *testing.T) { - input := `["", "0x0"]` - - args := new(NewSigArgs) - str := ExpectValidationError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSigArgsFromType(t *testing.T) { - input := `[false, "0x0"]` - - args := new(NewSigArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestSigArgsEmpty(t *testing.T) { - input := `[]` - args := new(NewSigArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDataArgs(t *testing.T) { - input := `["0x0123"]` - expected := new(NewDataArgs) - expected.Data = "0x0123" - - args := new(NewDataArgs) - if err := json.Unmarshal([]byte(input), &args); err != nil { - t.Error(err) - } - - if expected.Data != args.Data { - t.Errorf("Data should be %v but is %v", expected.Data, args.Data) - } -} - -func TestDataArgsEmptyData(t *testing.T) { - input := `[""]` - - args := new(NewDataArgs) - str := ExpectValidationError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDataArgsDataType(t *testing.T) { - input := `[13]` - - args := new(NewDataArgs) - str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDataArgsEmpty(t *testing.T) { - input := `[]` - args := new(NewDataArgs) - str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} - -func TestDataArgsInvalid(t *testing.T) { - input := `{}` - - args := new(NewDataArgs) - str := ExpectDecodeParamError(json.Unmarshal([]byte(input), args)) - if len(str) > 0 { - t.Error(str) - } -} diff --git a/rpc/api/db.go b/rpc/api/db.go deleted file mode 100644 index 0eddc410ed..0000000000 --- a/rpc/api/db.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" -) - -const ( - DbApiversion = "1.0" -) - -var ( - // mapping between methods and handlers - DbMapping = map[string]dbhandler{ - "db_getString": (*dbApi).GetString, - "db_putString": (*dbApi).PutString, - "db_getHex": (*dbApi).GetHex, - "db_putHex": (*dbApi).PutHex, - } -) - -// db callback handler -type dbhandler func(*dbApi, *shared.Request) (interface{}, error) - -// db api provider -type dbApi struct { - xeth *xeth.XEth - ethereum *eth.Ethereum - methods map[string]dbhandler - codec codec.ApiCoder -} - -// create a new db api instance -func NewDbApi(xeth *xeth.XEth, ethereum *eth.Ethereum, coder codec.Codec) *dbApi { - return &dbApi{ - xeth: xeth, - ethereum: ethereum, - methods: DbMapping, - codec: coder.New(nil), - } -} - -// collection with supported methods -func (self *dbApi) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *dbApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, &shared.NotImplementedError{req.Method} -} - -func (self *dbApi) Name() string { - return shared.DbApiName -} - -func (self *dbApi) ApiVersion() string { - return DbApiversion -} - -func (self *dbApi) GetString(req *shared.Request) (interface{}, error) { - args := new(DbArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - if err := args.requirements(); err != nil { - return nil, err - } - - ret, err := self.xeth.DbGet([]byte(args.Database + args.Key)) - return string(ret), err -} - -func (self *dbApi) PutString(req *shared.Request) (interface{}, error) { - args := new(DbArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - if err := args.requirements(); err != nil { - return nil, err - } - - return self.xeth.DbPut([]byte(args.Database+args.Key), args.Value), nil -} - -func (self *dbApi) GetHex(req *shared.Request) (interface{}, error) { - args := new(DbHexArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - if err := args.requirements(); err != nil { - return nil, err - } - - if res, err := self.xeth.DbGet([]byte(args.Database + args.Key)); err == nil { - return newHexData(res), nil - } else { - return nil, err - } -} - -func (self *dbApi) PutHex(req *shared.Request) (interface{}, error) { - args := new(DbHexArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - if err := args.requirements(); err != nil { - return nil, err - } - - return self.xeth.DbPut([]byte(args.Database+args.Key), args.Value), nil -} diff --git a/rpc/api/db_args.go b/rpc/api/db_args.go deleted file mode 100644 index d61ea77ee0..0000000000 --- a/rpc/api/db_args.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type DbArgs struct { - Database string - Key string - Value []byte -} - -func (args *DbArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - var objstr string - var ok bool - - if objstr, ok = obj[0].(string); !ok { - return shared.NewInvalidTypeError("database", "not a string") - } - args.Database = objstr - - if objstr, ok = obj[1].(string); !ok { - return shared.NewInvalidTypeError("key", "not a string") - } - args.Key = objstr - - if len(obj) > 2 { - objstr, ok = obj[2].(string) - if !ok { - return shared.NewInvalidTypeError("value", "not a string") - } - - args.Value = []byte(objstr) - } - - return nil -} - -func (a *DbArgs) requirements() error { - if len(a.Database) == 0 { - return shared.NewValidationError("Database", "cannot be blank") - } - if len(a.Key) == 0 { - return shared.NewValidationError("Key", "cannot be blank") - } - return nil -} - -type DbHexArgs struct { - Database string - Key string - Value []byte -} - -func (args *DbHexArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - var objstr string - var ok bool - - if objstr, ok = obj[0].(string); !ok { - return shared.NewInvalidTypeError("database", "not a string") - } - args.Database = objstr - - if objstr, ok = obj[1].(string); !ok { - return shared.NewInvalidTypeError("key", "not a string") - } - args.Key = objstr - - if len(obj) > 2 { - objstr, ok = obj[2].(string) - if !ok { - return shared.NewInvalidTypeError("value", "not a string") - } - - args.Value = common.FromHex(objstr) - } - - return nil -} - -func (a *DbHexArgs) requirements() error { - if len(a.Database) == 0 { - return shared.NewValidationError("Database", "cannot be blank") - } - if len(a.Key) == 0 { - return shared.NewValidationError("Key", "cannot be blank") - } - return nil -} diff --git a/rpc/api/db_js.go b/rpc/api/db_js.go deleted file mode 100644 index 899f8abd9a..0000000000 --- a/rpc/api/db_js.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2015 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 . - -package api - -const Db_JS = ` -web3._extend({ - property: 'db', - methods: - [ - ], - properties: - [ - ] -}); -` diff --git a/rpc/api/debug.go b/rpc/api/debug.go deleted file mode 100644 index a6faa335e3..0000000000 --- a/rpc/api/debug.go +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "fmt" - "strings" - "time" - - "github.com/ethereum/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" - "github.com/rcrowley/go-metrics" -) - -const ( - DebugApiVersion = "1.0" -) - -var ( - // mapping between methods and handlers - DebugMapping = map[string]debughandler{ - "debug_dumpBlock": (*debugApi).DumpBlock, - "debug_getBlockRlp": (*debugApi).GetBlockRlp, - "debug_printBlock": (*debugApi).PrintBlock, - "debug_processBlock": (*debugApi).ProcessBlock, - "debug_seedHash": (*debugApi).SeedHash, - "debug_setHead": (*debugApi).SetHead, - "debug_metrics": (*debugApi).Metrics, - } -) - -// debug callback handler -type debughandler func(*debugApi, *shared.Request) (interface{}, error) - -// admin api provider -type debugApi struct { - xeth *xeth.XEth - ethereum *eth.Ethereum - methods map[string]debughandler - codec codec.ApiCoder -} - -// create a new debug api instance -func NewDebugApi(xeth *xeth.XEth, ethereum *eth.Ethereum, coder codec.Codec) *debugApi { - return &debugApi{ - xeth: xeth, - ethereum: ethereum, - methods: DebugMapping, - codec: coder.New(nil), - } -} - -// collection with supported methods -func (self *debugApi) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *debugApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, &shared.NotImplementedError{req.Method} -} - -func (self *debugApi) Name() string { - return shared.DebugApiName -} - -func (self *debugApi) ApiVersion() string { - return DebugApiVersion -} - -func (self *debugApi) PrintBlock(req *shared.Request) (interface{}, error) { - args := new(BlockNumArg) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - block := self.xeth.EthBlockByNumber(args.BlockNumber) - return fmt.Sprintf("%s", block), nil -} - -func (self *debugApi) DumpBlock(req *shared.Request) (interface{}, error) { - args := new(BlockNumArg) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - block := self.xeth.EthBlockByNumber(args.BlockNumber) - if block == nil { - return nil, fmt.Errorf("block #%d not found", args.BlockNumber) - } - - stateDb, err := state.New(block.Root(), self.ethereum.ChainDb()) - if err != nil { - return nil, err - } - - return stateDb.RawDump(), nil -} - -func (self *debugApi) GetBlockRlp(req *shared.Request) (interface{}, error) { - args := new(BlockNumArg) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - block := self.xeth.EthBlockByNumber(args.BlockNumber) - if block == nil { - return nil, fmt.Errorf("block #%d not found", args.BlockNumber) - } - encoded, err := rlp.EncodeToBytes(block) - return fmt.Sprintf("%x", encoded), err -} - -func (self *debugApi) SetHead(req *shared.Request) (interface{}, error) { - args := new(BlockNumArg) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - self.ethereum.BlockChain().SetHead(uint64(args.BlockNumber)) - - return nil, nil -} - -func (self *debugApi) ProcessBlock(req *shared.Request) (interface{}, error) { - args := new(BlockNumArg) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - block := self.xeth.EthBlockByNumber(args.BlockNumber) - if block == nil { - return nil, fmt.Errorf("block #%d not found", args.BlockNumber) - } - - old := vm.Debug - defer func() { vm.Debug = old }() - vm.Debug = true - - var ( - blockchain = self.ethereum.BlockChain() - validator = blockchain.Validator() - processor = blockchain.Processor() - ) - - err := core.ValidateHeader(blockchain.AuxValidator(), block.Header(), blockchain.GetHeader(block.ParentHash()), true, false) - if err != nil { - return false, err - } - statedb, err := state.New(blockchain.GetBlock(block.ParentHash()).Root(), self.ethereum.ChainDb()) - if err != nil { - return false, err - } - receipts, _, usedGas, err := processor.Process(block, statedb) - if err != nil { - return false, err - } - err = validator.ValidateState(block, blockchain.GetBlock(block.ParentHash()), statedb, receipts, usedGas) - if err != nil { - return false, err - } - - return true, nil -} - -func (self *debugApi) SeedHash(req *shared.Request) (interface{}, error) { - args := new(BlockNumArg) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - if hash, err := ethash.GetSeedHash(uint64(args.BlockNumber)); err == nil { - return fmt.Sprintf("0x%x", hash), nil - } else { - return nil, err - } -} - -func (self *debugApi) Metrics(req *shared.Request) (interface{}, error) { - args := new(MetricsArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - // Create a rate formatter - units := []string{"", "K", "M", "G", "T", "E", "P"} - round := func(value float64, prec int) string { - unit := 0 - for value >= 1000 { - unit, value, prec = unit+1, value/1000, 2 - } - return fmt.Sprintf(fmt.Sprintf("%%.%df%s", prec, units[unit]), value) - } - format := func(total float64, rate float64) string { - return fmt.Sprintf("%s (%s/s)", round(total, 0), round(rate, 2)) - } - // Iterate over all the metrics, and just dump for now - counters := make(map[string]interface{}) - metrics.DefaultRegistry.Each(func(name string, metric interface{}) { - // Create or retrieve the counter hierarchy for this metric - root, parts := counters, strings.Split(name, "/") - for _, part := range parts[:len(parts)-1] { - if _, ok := root[part]; !ok { - root[part] = make(map[string]interface{}) - } - root = root[part].(map[string]interface{}) - } - name = parts[len(parts)-1] - - // Fill the counter with the metric details, formatting if requested - if args.Raw { - switch metric := metric.(type) { - case metrics.Meter: - root[name] = map[string]interface{}{ - "AvgRate01Min": metric.Rate1(), - "AvgRate05Min": metric.Rate5(), - "AvgRate15Min": metric.Rate15(), - "MeanRate": metric.RateMean(), - "Overall": float64(metric.Count()), - } - - case metrics.Timer: - root[name] = map[string]interface{}{ - "AvgRate01Min": metric.Rate1(), - "AvgRate05Min": metric.Rate5(), - "AvgRate15Min": metric.Rate15(), - "MeanRate": metric.RateMean(), - "Overall": float64(metric.Count()), - "Percentiles": map[string]interface{}{ - "5": metric.Percentile(0.05), - "20": metric.Percentile(0.2), - "50": metric.Percentile(0.5), - "80": metric.Percentile(0.8), - "95": metric.Percentile(0.95), - }, - } - - default: - root[name] = "Unknown metric type" - } - } else { - switch metric := metric.(type) { - case metrics.Meter: - root[name] = map[string]interface{}{ - "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), - "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), - "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), - "Overall": format(float64(metric.Count()), metric.RateMean()), - } - - case metrics.Timer: - root[name] = map[string]interface{}{ - "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), - "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), - "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), - "Overall": format(float64(metric.Count()), metric.RateMean()), - "Maximum": time.Duration(metric.Max()).String(), - "Minimum": time.Duration(metric.Min()).String(), - "Percentiles": map[string]interface{}{ - "5": time.Duration(metric.Percentile(0.05)).String(), - "20": time.Duration(metric.Percentile(0.2)).String(), - "50": time.Duration(metric.Percentile(0.5)).String(), - "80": time.Duration(metric.Percentile(0.8)).String(), - "95": time.Duration(metric.Percentile(0.95)).String(), - }, - } - - default: - root[name] = "Unknown metric type" - } - } - }) - return counters, nil -} diff --git a/rpc/api/debug_args.go b/rpc/api/debug_args.go deleted file mode 100644 index 041ad6b6ad..0000000000 --- a/rpc/api/debug_args.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "encoding/json" - "fmt" - "math/big" - "reflect" - - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type WaitForBlockArgs struct { - MinHeight int - Timeout int // in seconds -} - -func (args *WaitForBlockArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) > 2 { - return fmt.Errorf("waitForArgs needs 0, 1, 2 arguments") - } - - // default values when not provided - args.MinHeight = -1 - args.Timeout = -1 - - if len(obj) >= 1 { - var minHeight *big.Int - if minHeight, err = numString(obj[0]); err != nil { - return err - } - args.MinHeight = int(minHeight.Int64()) - } - - if len(obj) >= 2 { - timeout, err := numString(obj[1]) - if err != nil { - return err - } - args.Timeout = int(timeout.Int64()) - } - - return nil -} - -type MetricsArgs struct { - Raw bool -} - -func (args *MetricsArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - if len(obj) > 1 { - return fmt.Errorf("metricsArgs needs 0, 1 arguments") - } - // default values when not provided - if len(obj) >= 1 && obj[0] != nil { - if value, ok := obj[0].(bool); !ok { - return fmt.Errorf("invalid argument %v", reflect.TypeOf(obj[0])) - } else { - args.Raw = value - } - } - return nil -} diff --git a/rpc/api/debug_js.go b/rpc/api/debug_js.go deleted file mode 100644 index 030511add3..0000000000 --- a/rpc/api/debug_js.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2015 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 . - -package api - -const Debug_JS = ` -web3._extend({ - property: 'debug', - methods: - [ - new web3._extend.Method({ - name: 'printBlock', - call: 'debug_printBlock', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'getBlockRlp', - call: 'debug_getBlockRlp', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'setHead', - call: 'debug_setHead', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'processBlock', - call: 'debug_processBlock', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'seedHash', - call: 'debug_seedHash', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'dumpBlock', - call: 'debug_dumpBlock', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'metrics', - call: 'debug_metrics', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'verbosity', - call: 'debug_verbosity', - params: 1, - inputFormatter: [web3._extend.utils.fromDecimal] - }), - new web3._extend.Method({ - name: 'vmodule', - call: 'debug_vmodule', - params: 1, - inputFormatter: [null] - }), - ], - properties: - [ - ] -}); -` diff --git a/rpc/api/eth.go b/rpc/api/eth.go deleted file mode 100644 index db7a643d82..0000000000 --- a/rpc/api/eth.go +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "bytes" - "encoding/json" - "math/big" - - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/natspec" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" - "gopkg.in/fatih/set.v0" -) - -const ( - EthApiVersion = "1.0" -) - -// eth api provider -// See https://github.com/ethereum/wiki/wiki/JSON-RPC -type ethApi struct { - xeth *xeth.XEth - ethereum *eth.Ethereum - methods map[string]ethhandler - codec codec.ApiCoder -} - -// eth callback handler -type ethhandler func(*ethApi, *shared.Request) (interface{}, error) - -var ( - ethMapping = map[string]ethhandler{ - "eth_accounts": (*ethApi).Accounts, - "eth_blockNumber": (*ethApi).BlockNumber, - "eth_getBalance": (*ethApi).GetBalance, - "eth_protocolVersion": (*ethApi).ProtocolVersion, - "eth_coinbase": (*ethApi).Coinbase, - "eth_mining": (*ethApi).IsMining, - "eth_syncing": (*ethApi).IsSyncing, - "eth_gasPrice": (*ethApi).GasPrice, - "eth_getStorage": (*ethApi).GetStorage, - "eth_storageAt": (*ethApi).GetStorage, - "eth_getStorageAt": (*ethApi).GetStorageAt, - "eth_getTransactionCount": (*ethApi).GetTransactionCount, - "eth_getBlockTransactionCountByHash": (*ethApi).GetBlockTransactionCountByHash, - "eth_getBlockTransactionCountByNumber": (*ethApi).GetBlockTransactionCountByNumber, - "eth_getUncleCountByBlockHash": (*ethApi).GetUncleCountByBlockHash, - "eth_getUncleCountByBlockNumber": (*ethApi).GetUncleCountByBlockNumber, - "eth_getData": (*ethApi).GetData, - "eth_getCode": (*ethApi).GetData, - "eth_getNatSpec": (*ethApi).GetNatSpec, - "eth_sign": (*ethApi).Sign, - "eth_sendRawTransaction": (*ethApi).SubmitTransaction, - "eth_submitTransaction": (*ethApi).SubmitTransaction, - "eth_sendTransaction": (*ethApi).SendTransaction, - "eth_signTransaction": (*ethApi).SignTransaction, - "eth_transact": (*ethApi).SendTransaction, - "eth_estimateGas": (*ethApi).EstimateGas, - "eth_call": (*ethApi).Call, - "eth_flush": (*ethApi).Flush, - "eth_getBlockByHash": (*ethApi).GetBlockByHash, - "eth_getBlockByNumber": (*ethApi).GetBlockByNumber, - "eth_getTransactionByHash": (*ethApi).GetTransactionByHash, - "eth_getTransactionByBlockNumberAndIndex": (*ethApi).GetTransactionByBlockNumberAndIndex, - "eth_getTransactionByBlockHashAndIndex": (*ethApi).GetTransactionByBlockHashAndIndex, - "eth_getUncleByBlockHashAndIndex": (*ethApi).GetUncleByBlockHashAndIndex, - "eth_getUncleByBlockNumberAndIndex": (*ethApi).GetUncleByBlockNumberAndIndex, - "eth_getCompilers": (*ethApi).GetCompilers, - "eth_compileSolidity": (*ethApi).CompileSolidity, - "eth_newFilter": (*ethApi).NewFilter, - "eth_newBlockFilter": (*ethApi).NewBlockFilter, - "eth_newPendingTransactionFilter": (*ethApi).NewPendingTransactionFilter, - "eth_uninstallFilter": (*ethApi).UninstallFilter, - "eth_getFilterChanges": (*ethApi).GetFilterChanges, - "eth_getFilterLogs": (*ethApi).GetFilterLogs, - "eth_getLogs": (*ethApi).GetLogs, - "eth_hashrate": (*ethApi).Hashrate, - "eth_getWork": (*ethApi).GetWork, - "eth_submitWork": (*ethApi).SubmitWork, - "eth_submitHashrate": (*ethApi).SubmitHashrate, - "eth_resend": (*ethApi).Resend, - "eth_pendingTransactions": (*ethApi).PendingTransactions, - "eth_getTransactionReceipt": (*ethApi).GetTransactionReceipt, - } -) - -// create new ethApi instance -func NewEthApi(xeth *xeth.XEth, eth *eth.Ethereum, codec codec.Codec) *ethApi { - return ðApi{xeth, eth, ethMapping, codec.New(nil)} -} - -// collection with supported methods -func (self *ethApi) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *ethApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, shared.NewNotImplementedError(req.Method) -} - -func (self *ethApi) Name() string { - return shared.EthApiName -} - -func (self *ethApi) ApiVersion() string { - return EthApiVersion -} - -func (self *ethApi) Accounts(req *shared.Request) (interface{}, error) { - return self.xeth.Accounts(), nil -} - -func (self *ethApi) Hashrate(req *shared.Request) (interface{}, error) { - return newHexNum(self.xeth.HashRate()), nil -} - -func (self *ethApi) BlockNumber(req *shared.Request) (interface{}, error) { - num := self.xeth.CurrentBlock().Number() - return newHexNum(num.Bytes()), nil -} - -func (self *ethApi) GetBalance(req *shared.Request) (interface{}, error) { - args := new(GetBalanceArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return self.xeth.AtStateNum(args.BlockNumber).BalanceAt(args.Address), nil -} - -func (self *ethApi) ProtocolVersion(req *shared.Request) (interface{}, error) { - return self.xeth.EthVersion(), nil -} - -func (self *ethApi) Coinbase(req *shared.Request) (interface{}, error) { - return newHexData(self.xeth.Coinbase()), nil -} - -func (self *ethApi) IsMining(req *shared.Request) (interface{}, error) { - return self.xeth.IsMining(), nil -} - -func (self *ethApi) IsSyncing(req *shared.Request) (interface{}, error) { - origin, current, height := self.ethereum.Downloader().Progress() - if current < height { - return map[string]interface{}{ - "startingBlock": newHexNum(big.NewInt(int64(origin)).Bytes()), - "currentBlock": newHexNum(big.NewInt(int64(current)).Bytes()), - "highestBlock": newHexNum(big.NewInt(int64(height)).Bytes()), - }, nil - } - return false, nil -} - -func (self *ethApi) GasPrice(req *shared.Request) (interface{}, error) { - return newHexNum(self.xeth.DefaultGasPrice().Bytes()), nil -} - -func (self *ethApi) GetStorage(req *shared.Request) (interface{}, error) { - args := new(GetStorageArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return self.xeth.AtStateNum(args.BlockNumber).State().SafeGet(args.Address).Storage(), nil -} - -func (self *ethApi) GetStorageAt(req *shared.Request) (interface{}, error) { - args := new(GetStorageAtArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return self.xeth.AtStateNum(args.BlockNumber).StorageAt(args.Address, args.Key), nil -} - -func (self *ethApi) GetTransactionCount(req *shared.Request) (interface{}, error) { - args := new(GetTxCountArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - count := self.xeth.AtStateNum(args.BlockNumber).TxCountAt(args.Address) - return fmt.Sprintf("%#x", count), nil -} - -func (self *ethApi) GetBlockTransactionCountByHash(req *shared.Request) (interface{}, error) { - args := new(HashArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - block := self.xeth.EthBlockByHash(args.Hash) - if block == nil { - return nil, nil - } - return fmt.Sprintf("%#x", len(block.Transactions())), nil -} - -func (self *ethApi) GetBlockTransactionCountByNumber(req *shared.Request) (interface{}, error) { - args := new(BlockNumArg) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - block := self.xeth.EthBlockByNumber(args.BlockNumber) - if block == nil { - return nil, nil - } - return fmt.Sprintf("%#x", len(block.Transactions())), nil -} - -func (self *ethApi) GetUncleCountByBlockHash(req *shared.Request) (interface{}, error) { - args := new(HashArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - block := self.xeth.EthBlockByHash(args.Hash) - if block == nil { - return nil, nil - } - return fmt.Sprintf("%#x", len(block.Uncles())), nil -} - -func (self *ethApi) GetUncleCountByBlockNumber(req *shared.Request) (interface{}, error) { - args := new(BlockNumArg) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - block := self.xeth.EthBlockByNumber(args.BlockNumber) - if block == nil { - return nil, nil - } - return fmt.Sprintf("%#x", len(block.Uncles())), nil -} - -func (self *ethApi) GetData(req *shared.Request) (interface{}, error) { - args := new(GetDataArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - v := self.xeth.AtStateNum(args.BlockNumber).CodeAtBytes(args.Address) - return newHexData(v), nil -} - -func (self *ethApi) Sign(req *shared.Request) (interface{}, error) { - args := new(NewSigArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - v, err := self.xeth.Sign(args.From, args.Data, false) - if err != nil { - return nil, err - } - return v, nil -} - -func (self *ethApi) SubmitTransaction(req *shared.Request) (interface{}, error) { - args := new(NewDataArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - v, err := self.xeth.PushTx(args.Data) - if err != nil { - return nil, err - } - return v, nil -} - -// JsonTransaction is returned as response by the JSON RPC. It contains the -// signed RLP encoded transaction as Raw and the signed transaction object as Tx. -type JsonTransaction struct { - Raw string `json:"raw"` - Tx *tx `json:"tx"` -} - -func (self *ethApi) SignTransaction(req *shared.Request) (interface{}, error) { - args := new(NewTxArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - // nonce may be nil ("guess" mode) - var nonce string - if args.Nonce != nil { - nonce = args.Nonce.String() - } - - var gas, price string - if args.Gas != nil { - gas = args.Gas.String() - } - if args.GasPrice != nil { - price = args.GasPrice.String() - } - tx, err := self.xeth.SignTransaction(args.From, args.To, nonce, args.Value.String(), gas, price, args.Data) - if err != nil { - return nil, err - } - - data, err := rlp.EncodeToBytes(tx) - if err != nil { - return nil, err - } - - return JsonTransaction{"0x" + common.Bytes2Hex(data), newTx(tx)}, nil -} - -func (self *ethApi) SendTransaction(req *shared.Request) (interface{}, error) { - args := new(NewTxArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - // nonce may be nil ("guess" mode) - var nonce string - if args.Nonce != nil { - nonce = args.Nonce.String() - } - - var gas, price string - if args.Gas != nil { - gas = args.Gas.String() - } - if args.GasPrice != nil { - price = args.GasPrice.String() - } - v, err := self.xeth.Transact(args.From, args.To, nonce, args.Value.String(), gas, price, args.Data) - if err != nil { - return nil, err - } - return v, nil -} - -func (self *ethApi) GetNatSpec(req *shared.Request) (interface{}, error) { - args := new(NewTxArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - var jsontx = fmt.Sprintf(`{"params":[{"to":"%s","data": "%s"}]}`, args.To, args.Data) - notice := natspec.GetNotice(self.xeth, jsontx, self.ethereum.HTTPClient()) - - return notice, nil -} - -func (self *ethApi) EstimateGas(req *shared.Request) (interface{}, error) { - _, gas, err := self.doCall(req.Params) - if err != nil { - return nil, err - } - - // TODO unwrap the parent method's ToHex call - if len(gas) == 0 { - return newHexNum(0), nil - } else { - return newHexNum(common.String2Big(gas)), err - } -} - -func (self *ethApi) Call(req *shared.Request) (interface{}, error) { - v, _, err := self.doCall(req.Params) - if err != nil { - return nil, err - } - - // TODO unwrap the parent method's ToHex call - if v == "0x0" { - return newHexData([]byte{}), nil - } else { - return newHexData(common.FromHex(v)), nil - } -} - -func (self *ethApi) Flush(req *shared.Request) (interface{}, error) { - return nil, shared.NewNotImplementedError(req.Method) -} - -func (self *ethApi) doCall(params json.RawMessage) (string, string, error) { - args := new(CallArgs) - if err := self.codec.Decode(params, &args); err != nil { - return "", "", err - } - - return self.xeth.AtStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data) -} - -func (self *ethApi) GetBlockByHash(req *shared.Request) (interface{}, error) { - args := new(GetBlockByHashArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - block := self.xeth.EthBlockByHash(args.BlockHash) - if block == nil { - return nil, nil - } - return NewBlockRes(block, self.xeth.Td(block.Hash()), args.IncludeTxs), nil -} - -func (self *ethApi) GetBlockByNumber(req *shared.Request) (interface{}, error) { - args := new(GetBlockByNumberArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - block := self.xeth.EthBlockByNumber(args.BlockNumber) - if block == nil { - return nil, nil - } - return NewBlockRes(block, self.xeth.Td(block.Hash()), args.IncludeTxs), nil -} - -func (self *ethApi) GetTransactionByHash(req *shared.Request) (interface{}, error) { - args := new(HashArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - tx, bhash, bnum, txi := self.xeth.EthTransactionByHash(args.Hash) - if tx != nil { - v := NewTransactionRes(tx) - // if the blockhash is 0, assume this is a pending transaction - if bytes.Compare(bhash.Bytes(), bytes.Repeat([]byte{0}, 32)) != 0 { - v.BlockHash = newHexData(bhash) - v.BlockNumber = newHexNum(bnum) - v.TxIndex = newHexNum(txi) - } - return v, nil - } - return nil, nil -} - -func (self *ethApi) GetTransactionByBlockHashAndIndex(req *shared.Request) (interface{}, error) { - args := new(HashIndexArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - raw := self.xeth.EthBlockByHash(args.Hash) - if raw == nil { - return nil, nil - } - block := NewBlockRes(raw, self.xeth.Td(raw.Hash()), true) - if args.Index >= int64(len(block.Transactions)) || args.Index < 0 { - return nil, nil - } else { - return block.Transactions[args.Index], nil - } -} - -func (self *ethApi) GetTransactionByBlockNumberAndIndex(req *shared.Request) (interface{}, error) { - args := new(BlockNumIndexArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - raw := self.xeth.EthBlockByNumber(args.BlockNumber) - if raw == nil { - return nil, nil - } - block := NewBlockRes(raw, self.xeth.Td(raw.Hash()), true) - if args.Index >= int64(len(block.Transactions)) || args.Index < 0 { - // return NewValidationError("Index", "does not exist") - return nil, nil - } - return block.Transactions[args.Index], nil -} - -func (self *ethApi) GetUncleByBlockHashAndIndex(req *shared.Request) (interface{}, error) { - args := new(HashIndexArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - raw := self.xeth.EthBlockByHash(args.Hash) - if raw == nil { - return nil, nil - } - block := NewBlockRes(raw, self.xeth.Td(raw.Hash()), false) - if args.Index >= int64(len(block.Uncles)) || args.Index < 0 { - // return NewValidationError("Index", "does not exist") - return nil, nil - } - return block.Uncles[args.Index], nil -} - -func (self *ethApi) GetUncleByBlockNumberAndIndex(req *shared.Request) (interface{}, error) { - args := new(BlockNumIndexArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - raw := self.xeth.EthBlockByNumber(args.BlockNumber) - if raw == nil { - return nil, nil - } - block := NewBlockRes(raw, self.xeth.Td(raw.Hash()), true) - if args.Index >= int64(len(block.Uncles)) || args.Index < 0 { - return nil, nil - } else { - return block.Uncles[args.Index], nil - } -} - -func (self *ethApi) GetCompilers(req *shared.Request) (interface{}, error) { - var lang string - if solc, _ := self.xeth.Solc(); solc != nil { - lang = "Solidity" - } - c := []string{lang} - return c, nil -} - -func (self *ethApi) CompileSolidity(req *shared.Request) (interface{}, error) { - solc, _ := self.xeth.Solc() - if solc == nil { - return nil, shared.NewNotAvailableError(req.Method, "solc (solidity compiler) not found") - } - - args := new(SourceArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - contracts, err := solc.Compile(args.Source) - if err != nil { - return nil, err - } - return contracts, nil -} - -func (self *ethApi) NewFilter(req *shared.Request) (interface{}, error) { - args := new(BlockFilterArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - id := self.xeth.NewLogFilter(args.Earliest, args.Latest, args.Skip, args.Max, args.Address, args.Topics) - return newHexNum(big.NewInt(int64(id)).Bytes()), nil -} - -func (self *ethApi) NewBlockFilter(req *shared.Request) (interface{}, error) { - return newHexNum(self.xeth.NewBlockFilter()), nil -} - -func (self *ethApi) NewPendingTransactionFilter(req *shared.Request) (interface{}, error) { - return newHexNum(self.xeth.NewTransactionFilter()), nil -} - -func (self *ethApi) UninstallFilter(req *shared.Request) (interface{}, error) { - args := new(FilterIdArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - return self.xeth.UninstallFilter(args.Id), nil -} - -func (self *ethApi) GetFilterChanges(req *shared.Request) (interface{}, error) { - args := new(FilterIdArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - switch self.xeth.GetFilterType(args.Id) { - case xeth.BlockFilterTy: - return NewHashesRes(self.xeth.BlockFilterChanged(args.Id)), nil - case xeth.TransactionFilterTy: - return NewHashesRes(self.xeth.TransactionFilterChanged(args.Id)), nil - case xeth.LogFilterTy: - return NewLogsRes(self.xeth.LogFilterChanged(args.Id)), nil - default: - return []string{}, nil // reply empty string slice - } -} - -func (self *ethApi) GetFilterLogs(req *shared.Request) (interface{}, error) { - args := new(FilterIdArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return NewLogsRes(self.xeth.Logs(args.Id)), nil -} - -func (self *ethApi) GetLogs(req *shared.Request) (interface{}, error) { - args := new(BlockFilterArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - return NewLogsRes(self.xeth.AllLogs(args.Earliest, args.Latest, args.Skip, args.Max, args.Address, args.Topics)), nil -} - -func (self *ethApi) GetWork(req *shared.Request) (interface{}, error) { - self.xeth.SetMining(true, 0) - ret, err := self.xeth.RemoteMining().GetWork() - if err != nil { - return nil, shared.NewNotReadyError("mining work") - } else { - return ret, nil - } -} - -func (self *ethApi) SubmitWork(req *shared.Request) (interface{}, error) { - args := new(SubmitWorkArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - return self.xeth.RemoteMining().SubmitWork(args.Nonce, common.HexToHash(args.Digest), common.HexToHash(args.Header)), nil -} - -func (self *ethApi) SubmitHashrate(req *shared.Request) (interface{}, error) { - args := new(SubmitHashRateArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return false, shared.NewDecodeParamError(err.Error()) - } - self.xeth.RemoteMining().SubmitHashrate(common.HexToHash(args.Id), args.Rate) - return true, nil -} - -func (self *ethApi) Resend(req *shared.Request) (interface{}, error) { - args := new(ResendArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - from := common.HexToAddress(args.Tx.From) - - pending := self.ethereum.TxPool().GetTransactions() - for _, p := range pending { - if pFrom, err := p.From(); err == nil && pFrom == from && p.SigHash() == args.Tx.tx.SigHash() { - self.ethereum.TxPool().RemoveTx(common.HexToHash(args.Tx.Hash)) - return self.xeth.Transact(args.Tx.From, args.Tx.To, args.Tx.Nonce, args.Tx.Value, args.GasLimit, args.GasPrice, args.Tx.Data) - } - } - - return nil, fmt.Errorf("Transaction %s not found", args.Tx.Hash) -} - -func (self *ethApi) PendingTransactions(req *shared.Request) (interface{}, error) { - txs := self.ethereum.TxPool().GetTransactions() - - // grab the accounts from the account manager. This will help with determining which - // transactions should be returned. - accounts, err := self.ethereum.AccountManager().Accounts() - if err != nil { - return nil, err - } - - // Add the accouns to a new set - accountSet := set.New() - for _, account := range accounts { - accountSet.Add(account.Address) - } - - var ltxs []*tx - for _, tx := range txs { - if from, _ := tx.From(); accountSet.Has(from) { - ltxs = append(ltxs, newTx(tx)) - } - } - - return ltxs, nil -} - -func (self *ethApi) GetTransactionReceipt(req *shared.Request) (interface{}, error) { - args := new(HashArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - txhash := common.BytesToHash(common.FromHex(args.Hash)) - tx, bhash, bnum, txi := self.xeth.EthTransactionByHash(args.Hash) - rec := self.xeth.GetTxReceipt(txhash) - // We could have an error of "not found". Should disambiguate - // if err != nil { - // return err, nil - // } - if rec != nil && tx != nil { - v := NewReceiptRes(rec) - v.BlockHash = newHexData(bhash) - v.BlockNumber = newHexNum(bnum) - v.TransactionIndex = newHexNum(txi) - return v, nil - } - - return nil, nil -} diff --git a/rpc/api/eth_args.go b/rpc/api/eth_args.go deleted file mode 100644 index ed3d761f17..0000000000 --- a/rpc/api/eth_args.go +++ /dev/null @@ -1,1104 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "encoding/json" - "fmt" - "math/big" - "strconv" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -const ( - defaultLogLimit = 100 - defaultLogOffset = 0 -) - -type GetBalanceArgs struct { - Address string - BlockNumber int64 -} - -func (args *GetBalanceArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - if len(obj) > 1 { - if err := blockHeight(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type GetStorageArgs struct { - Address string - BlockNumber int64 -} - -func (args *GetStorageArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - if len(obj) > 1 { - if err := blockHeight(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type GetStorageAtArgs struct { - Address string - BlockNumber int64 - Key string -} - -func (args *GetStorageAtArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - keystr, ok := obj[1].(string) - if !ok { - return shared.NewInvalidTypeError("key", "not a string") - } - args.Key = keystr - - if len(obj) > 2 { - if err := blockHeight(obj[2], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type GetTxCountArgs struct { - Address string - BlockNumber int64 -} - -func (args *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - if len(obj) > 1 { - if err := blockHeight(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type SubmitHashRateArgs struct { - Id string - Rate uint64 -} - -func (args *SubmitHashRateArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - arg0, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("hash", "not a string") - } - args.Id = arg0 - - arg1, ok := obj[1].(string) - if !ok { - return shared.NewInvalidTypeError("rate", "not a string") - } - - args.Rate = common.String2Big(arg1).Uint64() - - return nil -} - -type HashArgs struct { - Hash string -} - -func (args *HashArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - arg0, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("hash", "not a string") - } - args.Hash = arg0 - - return nil -} - -type BlockNumArg struct { - BlockNumber int64 -} - -func (args *BlockNumArg) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - if err := blockHeight(obj[0], &args.BlockNumber); err != nil { - return err - } - - return nil -} - -type GetDataArgs struct { - Address string - BlockNumber int64 -} - -func (args *GetDataArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - if len(obj) > 1 { - if err := blockHeight(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type NewDataArgs struct { - Data string -} - -func (args *NewDataArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - // Check for sufficient params - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - data, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("data", "not a string") - } - args.Data = data - - if len(args.Data) == 0 { - return shared.NewValidationError("data", "is required") - } - - return nil -} - -type NewSigArgs struct { - From string - Data string -} - -func (args *NewSigArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - // Check for sufficient params - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - from, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("from", "not a string") - } - args.From = from - - if len(args.From) == 0 { - return shared.NewValidationError("from", "is required") - } - - data, ok := obj[1].(string) - if !ok { - return shared.NewInvalidTypeError("data", "not a string") - } - args.Data = data - - if len(args.Data) == 0 { - return shared.NewValidationError("data", "is required") - } - - return nil -} - -type NewTxArgs struct { - From string - To string - Nonce *big.Int - Value *big.Int - Gas *big.Int - GasPrice *big.Int - Data string - - BlockNumber int64 -} - -func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) { - var obj []json.RawMessage - var ext struct { - From string - To string - Nonce interface{} - Value interface{} - Gas interface{} - GasPrice interface{} - Data string - } - - // Decode byte slice to array of RawMessages - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - // Check for sufficient params - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - // Decode 0th RawMessage to temporary struct - if err := json.Unmarshal(obj[0], &ext); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(ext.From) == 0 { - return shared.NewValidationError("from", "is required") - } - - args.From = ext.From - args.To = ext.To - args.Data = ext.Data - - var num *big.Int - if ext.Nonce != nil { - num, err = numString(ext.Nonce) - if err != nil { - return err - } - } - args.Nonce = num - - if ext.Value == nil { - num = big.NewInt(0) - } else { - num, err = numString(ext.Value) - if err != nil { - return err - } - } - args.Value = num - - num = nil - if ext.Gas != nil { - if num, err = numString(ext.Gas); err != nil { - return err - } - } - args.Gas = num - - num = nil - if ext.GasPrice != nil { - if num, err = numString(ext.GasPrice); err != nil { - return err - } - } - args.GasPrice = num - - // Check for optional BlockNumber param - if len(obj) > 1 { - if err := blockHeightFromJson(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type SourceArgs struct { - Source string -} - -func (args *SourceArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - arg0, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("source code", "not a string") - } - args.Source = arg0 - - return nil -} - -type CallArgs struct { - From string - To string - Value *big.Int - Gas *big.Int - GasPrice *big.Int - Data string - - BlockNumber int64 -} - -func (args *CallArgs) UnmarshalJSON(b []byte) (err error) { - var obj []json.RawMessage - var ext struct { - From string - To string - Value interface{} - Gas interface{} - GasPrice interface{} - Data string - } - - // Decode byte slice to array of RawMessages - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - // Check for sufficient params - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - // Decode 0th RawMessage to temporary struct - if err := json.Unmarshal(obj[0], &ext); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - args.From = ext.From - args.To = ext.To - - var num *big.Int - if ext.Value == nil { - num = big.NewInt(0) - } else { - if num, err = numString(ext.Value); err != nil { - return err - } - } - args.Value = num - - if ext.Gas != nil { - if num, err = numString(ext.Gas); err != nil { - return err - } - } else { - num = nil - } - args.Gas = num - - if ext.GasPrice != nil { - if num, err = numString(ext.GasPrice); err != nil { - return err - } - } else { - num = nil - } - args.GasPrice = num - - args.Data = ext.Data - - // Check for optional BlockNumber param - if len(obj) > 1 { - if err := blockHeightFromJson(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type HashIndexArgs struct { - Hash string - Index int64 -} - -func (args *HashIndexArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - arg0, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("hash", "not a string") - } - args.Hash = arg0 - - arg1, ok := obj[1].(string) - if !ok { - return shared.NewInvalidTypeError("index", "not a string") - } - args.Index = common.Big(arg1).Int64() - - return nil -} - -type BlockNumIndexArgs struct { - BlockNumber int64 - Index int64 -} - -func (args *BlockNumIndexArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - if err := blockHeight(obj[0], &args.BlockNumber); err != nil { - return err - } - - var arg1 *big.Int - if arg1, err = numString(obj[1]); err != nil { - return err - } - args.Index = arg1.Int64() - - return nil -} - -type GetBlockByHashArgs struct { - BlockHash string - IncludeTxs bool -} - -func (args *GetBlockByHashArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - argstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("blockHash", "not a string") - } - args.BlockHash = argstr - - args.IncludeTxs = obj[1].(bool) - - if inclTx, ok := obj[1].(bool); ok { - args.IncludeTxs = inclTx - return nil - } - - return shared.NewInvalidTypeError("includeTxs", "not a bool") -} - -type GetBlockByNumberArgs struct { - BlockNumber int64 - IncludeTxs bool -} - -func (args *GetBlockByNumberArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - if err := blockHeight(obj[0], &args.BlockNumber); err != nil { - return err - } - - if inclTx, ok := obj[1].(bool); ok { - args.IncludeTxs = inclTx - return nil - } - - return shared.NewInvalidTypeError("includeTxs", "not a bool") -} - -type BlockFilterArgs struct { - Earliest int64 - Latest int64 - Address []string - Topics [][]string - Skip int - Max int -} - -func (args *BlockFilterArgs) UnmarshalJSON(b []byte) (err error) { - var obj []struct { - FromBlock interface{} `json:"fromBlock"` - ToBlock interface{} `json:"toBlock"` - Limit interface{} `json:"limit"` - Offset interface{} `json:"offset"` - Address interface{} `json:"address"` - Topics interface{} `json:"topics"` - } - - if err = json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - // args.Earliest, err = toNumber(obj[0].ToBlock) - // if err != nil { - // return shared.NewDecodeParamError(fmt.Sprintf("FromBlock %v", err)) - // } - // args.Latest, err = toNumber(obj[0].FromBlock) - // if err != nil { - // return shared.NewDecodeParamError(fmt.Sprintf("ToBlock %v", err)) - - var num int64 - var numBig *big.Int - - // if blank then latest - if obj[0].FromBlock == nil { - num = -1 - } else { - if err := blockHeight(obj[0].FromBlock, &num); err != nil { - return err - } - } - // if -2 or other "silly" number, use latest - if num < 0 { - args.Earliest = -1 //latest block - } else { - args.Earliest = num - } - - // if blank than latest - if obj[0].ToBlock == nil { - num = -1 - } else { - if err := blockHeight(obj[0].ToBlock, &num); err != nil { - return err - } - } - - if num == -2 { - return fmt.Errorf("\"pending\" is unsupported") - } else if num < -2 { - return fmt.Errorf("Invalid to block number") - } - - args.Latest = num - - if obj[0].Limit == nil { - numBig = big.NewInt(defaultLogLimit) - } else { - if numBig, err = numString(obj[0].Limit); err != nil { - return err - } - } - args.Max = int(numBig.Int64()) - - if obj[0].Offset == nil { - numBig = big.NewInt(defaultLogOffset) - } else { - if numBig, err = numString(obj[0].Offset); err != nil { - return err - } - } - args.Skip = int(numBig.Int64()) - - if obj[0].Address != nil { - marg, ok := obj[0].Address.([]interface{}) - if ok { - v := make([]string, len(marg)) - for i, arg := range marg { - argstr, ok := arg.(string) - if !ok { - return shared.NewInvalidTypeError(fmt.Sprintf("address[%d]", i), "is not a string") - } - v[i] = argstr - } - args.Address = v - } else { - argstr, ok := obj[0].Address.(string) - if ok { - v := make([]string, 1) - v[0] = argstr - args.Address = v - } else { - return shared.NewInvalidTypeError("address", "is not a string or array") - } - } - } - - if obj[0].Topics != nil { - other, ok := obj[0].Topics.([]interface{}) - if ok { - topicdbl := make([][]string, len(other)) - for i, iv := range other { - if argstr, ok := iv.(string); ok { - // Found a string, push into first element of array - topicsgl := make([]string, 1) - topicsgl[0] = argstr - topicdbl[i] = topicsgl - } else if argarray, ok := iv.([]interface{}); ok { - // Found an array of other - topicdbl[i] = make([]string, len(argarray)) - for j, jv := range argarray { - if v, ok := jv.(string); ok { - topicdbl[i][j] = v - } else if jv == nil { - topicdbl[i][j] = "" - } else { - return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d][%d]", i, j), "is not a string") - } - } - } else if iv == nil { - topicdbl[i] = []string{""} - } else { - return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d]", i), "not a string or array") - } - } - args.Topics = topicdbl - return nil - } else { - return shared.NewInvalidTypeError("topic", "is not a string or array") - } - } - - return nil -} - -type FilterIdArgs struct { - Id int -} - -func (args *FilterIdArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - var num *big.Int - if num, err = numString(obj[0]); err != nil { - return err - } - args.Id = int(num.Int64()) - - return nil -} - -type LogRes struct { - Address *hexdata `json:"address"` - Topics []*hexdata `json:"topics"` - Data *hexdata `json:"data"` - BlockNumber *hexnum `json:"blockNumber"` - LogIndex *hexnum `json:"logIndex"` - BlockHash *hexdata `json:"blockHash"` - TransactionHash *hexdata `json:"transactionHash"` - TransactionIndex *hexnum `json:"transactionIndex"` -} - -func NewLogRes(log *vm.Log) LogRes { - var l LogRes - l.Topics = make([]*hexdata, len(log.Topics)) - for j, topic := range log.Topics { - l.Topics[j] = newHexData(topic) - } - l.Address = newHexData(log.Address) - l.Data = newHexData(log.Data) - l.BlockNumber = newHexNum(log.BlockNumber) - l.LogIndex = newHexNum(log.Index) - l.TransactionHash = newHexData(log.TxHash) - l.TransactionIndex = newHexNum(log.TxIndex) - l.BlockHash = newHexData(log.BlockHash) - - return l -} - -func NewLogsRes(logs vm.Logs) (ls []LogRes) { - ls = make([]LogRes, len(logs)) - - for i, log := range logs { - ls[i] = NewLogRes(log) - } - - return -} - -func NewHashesRes(hs []common.Hash) []string { - hashes := make([]string, len(hs)) - - for i, hash := range hs { - hashes[i] = hash.Hex() - } - - return hashes -} - -type SubmitWorkArgs struct { - Nonce uint64 - Header string - Digest string -} - -func (args *SubmitWorkArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err = json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 3 { - return shared.NewInsufficientParamsError(len(obj), 3) - } - - var objstr string - var ok bool - if objstr, ok = obj[0].(string); !ok { - return shared.NewInvalidTypeError("nonce", "not a string") - } - - args.Nonce = common.String2Big(objstr).Uint64() - if objstr, ok = obj[1].(string); !ok { - return shared.NewInvalidTypeError("header", "not a string") - } - - args.Header = objstr - - if objstr, ok = obj[2].(string); !ok { - return shared.NewInvalidTypeError("digest", "not a string") - } - - args.Digest = objstr - - return nil -} - -type tx struct { - tx *types.Transaction - - To string `json:"to"` - From string `json:"from"` - Nonce string `json:"nonce"` - Value string `json:"value"` - Data string `json:"data"` - GasLimit string `json:"gas"` - GasPrice string `json:"gasPrice"` - Hash string `json:"hash"` -} - -func newTx(t *types.Transaction) *tx { - from, _ := t.From() - var to string - if t := t.To(); t != nil { - to = t.Hex() - } - - return &tx{ - tx: t, - To: to, - From: from.Hex(), - Value: t.Value().String(), - Nonce: strconv.Itoa(int(t.Nonce())), - Data: "0x" + common.Bytes2Hex(t.Data()), - GasLimit: t.Gas().String(), - GasPrice: t.GasPrice().String(), - Hash: t.Hash().Hex(), - } -} - -type ResendArgs struct { - Tx *tx - GasPrice string - GasLimit string -} - -func (tx *tx) UnmarshalJSON(b []byte) (err error) { - var fields map[string]interface{} - if err := json.Unmarshal(b, &fields); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - var ( - nonce uint64 - to common.Address - amount = new(big.Int).Set(common.Big0) - gasLimit = new(big.Int).Set(common.Big0) - gasPrice = new(big.Int).Set(common.Big0) - data []byte - contractCreation = true - ) - - if val, found := fields["Hash"]; found { - if hashVal, ok := val.(string); ok { - tx.Hash = hashVal - } - } - - if val, found := fields["To"]; found { - if strVal, ok := val.(string); ok && len(strVal) > 0 { - tx.To = strVal - to = common.HexToAddress(strVal) - contractCreation = false - } - } - - if val, found := fields["From"]; found { - if strVal, ok := val.(string); ok { - tx.From = strVal - } - } - - if val, found := fields["Nonce"]; found { - if strVal, ok := val.(string); ok { - tx.Nonce = strVal - if nonce, err = strconv.ParseUint(strVal, 10, 64); err != nil { - return shared.NewDecodeParamError(fmt.Sprintf("Unable to decode tx.Nonce - %v", err)) - } - } - } else { - return shared.NewDecodeParamError("tx.Nonce not found") - } - - var parseOk bool - if val, found := fields["Value"]; found { - if strVal, ok := val.(string); ok { - tx.Value = strVal - if _, parseOk = amount.SetString(strVal, 0); !parseOk { - return shared.NewDecodeParamError(fmt.Sprintf("Unable to decode tx.Amount - %v", err)) - } - } - } - - if val, found := fields["Data"]; found { - if strVal, ok := val.(string); ok { - tx.Data = strVal - if strings.HasPrefix(strVal, "0x") { - data = common.Hex2Bytes(strVal[2:]) - } else { - data = common.Hex2Bytes(strVal) - } - } - } - - if val, found := fields["GasLimit"]; found { - if strVal, ok := val.(string); ok { - tx.GasLimit = strVal - if _, parseOk = gasLimit.SetString(strVal, 0); !parseOk { - return shared.NewDecodeParamError(fmt.Sprintf("Unable to decode tx.GasLimit - %v", err)) - } - } - } - - if val, found := fields["GasPrice"]; found { - if strVal, ok := val.(string); ok { - tx.GasPrice = strVal - if _, parseOk = gasPrice.SetString(strVal, 0); !parseOk { - return shared.NewDecodeParamError(fmt.Sprintf("Unable to decode tx.GasPrice - %v", err)) - } - } - } - - if contractCreation { - tx.tx = types.NewContractCreation(nonce, amount, gasLimit, gasPrice, data) - } else { - tx.tx = types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data) - } - - return nil -} - -func (args *ResendArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err = json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - data, err := json.Marshal(obj[0]) - if err != nil { - return shared.NewDecodeParamError("Unable to parse transaction object") - } - - trans := new(tx) - err = json.Unmarshal(data, trans) - if err != nil { - return shared.NewDecodeParamError("Unable to parse transaction object") - } - - if trans == nil || trans.tx == nil { - return shared.NewDecodeParamError("Unable to parse transaction object") - } - - gasLimit, gasPrice := trans.GasLimit, trans.GasPrice - - if len(obj) > 1 && obj[1] != nil { - if gp, ok := obj[1].(string); ok { - gasPrice = gp - } else { - return shared.NewInvalidTypeError("gasPrice", "not a string") - } - } - if len(obj) > 2 && obj[2] != nil { - if gl, ok := obj[2].(string); ok { - gasLimit = gl - } else { - return shared.NewInvalidTypeError("gasLimit", "not a string") - } - } - - args.Tx = trans - args.GasPrice = gasPrice - args.GasLimit = gasLimit - - return nil -} diff --git a/rpc/api/eth_js.go b/rpc/api/eth_js.go deleted file mode 100644 index dfc104ad82..0000000000 --- a/rpc/api/eth_js.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2015 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 . - -package api - -// JS api provided by web3.js -// eth_sign not standard - -const Eth_JS = ` -web3._extend({ - property: 'eth', - methods: - [ - new web3._extend.Method({ - name: 'sign', - call: 'eth_sign', - params: 2, - inputFormatter: [web3._extend.utils.toAddress, null] - }), - new web3._extend.Method({ - name: 'resend', - call: 'eth_resend', - params: 3, - inputFormatter: [web3._extend.formatters.inputTransactionFormatter, web3._extend.utils.fromDecimal, web3._extend.utils.fromDecimal] - }), - new web3._extend.Method({ - name: 'getNatSpec', - call: 'eth_getNatSpec', - params: 1, - inputFormatter: [web3._extend.formatters.inputTransactionFormatter] - }), - new web3._extend.Method({ - name: 'signTransaction', - call: 'eth_signTransaction', - params: 1, - inputFormatter: [web3._extend.formatters.inputTransactionFormatter] - }), - new web3._extend.Method({ - name: 'submitTransaction', - call: 'eth_submitTransaction', - params: 1, - inputFormatter: [web3._extend.formatters.inputTransactionFormatter] - }) - ], - properties: - [ - new web3._extend.Property({ - name: 'pendingTransactions', - getter: 'eth_pendingTransactions' - }) - ] -}); -` diff --git a/rpc/api/mergedapi.go b/rpc/api/mergedapi.go deleted file mode 100644 index 92e1e2bb77..0000000000 --- a/rpc/api/mergedapi.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -const ( - MergedApiVersion = "1.0" -) - -// combines multiple API's -type MergedApi struct { - apis map[string]string - methods map[string]shared.EthereumApi -} - -// create new merged api instance -func newMergedApi(apis ...shared.EthereumApi) *MergedApi { - mergedApi := new(MergedApi) - mergedApi.apis = make(map[string]string, len(apis)) - mergedApi.methods = make(map[string]shared.EthereumApi) - - for _, api := range apis { - if api != nil { - mergedApi.apis[api.Name()] = api.ApiVersion() - for _, method := range api.Methods() { - mergedApi.methods[method] = api - } - } - } - return mergedApi -} - -// Supported RPC methods -func (self *MergedApi) Methods() []string { - all := make([]string, len(self.methods)) - for method, _ := range self.methods { - all = append(all, method) - } - return all -} - -// Call the correct API's Execute method for the given request -func (self *MergedApi) Execute(req *shared.Request) (interface{}, error) { - glog.V(logger.Detail).Infof("%s %s", req.Method, req.Params) - - if res, _ := self.handle(req); res != nil { - return res, nil - } - if api, found := self.methods[req.Method]; found { - return api.Execute(req) - } - return nil, shared.NewNotImplementedError(req.Method) -} - -func (self *MergedApi) Name() string { - return shared.MergedApiName -} - -func (self *MergedApi) ApiVersion() string { - return MergedApiVersion -} - -func (self *MergedApi) handle(req *shared.Request) (interface{}, error) { - if req.Method == "modules" { // provided API's - return self.apis, nil - } - - return nil, nil -} diff --git a/rpc/api/miner.go b/rpc/api/miner.go deleted file mode 100644 index e07855dd2e..0000000000 --- a/rpc/api/miner.go +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "github.com/ethereum/ethash" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -const ( - MinerApiVersion = "1.0" -) - -var ( - // mapping between methods and handlers - MinerMapping = map[string]minerhandler{ - "miner_hashrate": (*minerApi).Hashrate, - "miner_makeDAG": (*minerApi).MakeDAG, - "miner_setExtra": (*minerApi).SetExtra, - "miner_setGasPrice": (*minerApi).SetGasPrice, - "miner_setEtherbase": (*minerApi).SetEtherbase, - "miner_startAutoDAG": (*minerApi).StartAutoDAG, - "miner_start": (*minerApi).StartMiner, - "miner_stopAutoDAG": (*minerApi).StopAutoDAG, - "miner_stop": (*minerApi).StopMiner, - } -) - -// miner callback handler -type minerhandler func(*minerApi, *shared.Request) (interface{}, error) - -// miner api provider -type minerApi struct { - ethereum *eth.Ethereum - methods map[string]minerhandler - codec codec.ApiCoder -} - -// create a new miner api instance -func NewMinerApi(ethereum *eth.Ethereum, coder codec.Codec) *minerApi { - return &minerApi{ - ethereum: ethereum, - methods: MinerMapping, - codec: coder.New(nil), - } -} - -// Execute given request -func (self *minerApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, &shared.NotImplementedError{req.Method} -} - -// collection with supported methods -func (self *minerApi) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -func (self *minerApi) Name() string { - return shared.MinerApiName -} - -func (self *minerApi) ApiVersion() string { - return MinerApiVersion -} - -func (self *minerApi) StartMiner(req *shared.Request) (interface{}, error) { - args := new(StartMinerArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - if args.Threads == -1 { // (not specified by user, use default) - args.Threads = self.ethereum.MinerThreads - } - - self.ethereum.StartAutoDAG() - err := self.ethereum.StartMining(args.Threads, "") - if err == nil { - return true, nil - } - - return false, err -} - -func (self *minerApi) StopMiner(req *shared.Request) (interface{}, error) { - self.ethereum.StopMining() - return true, nil -} - -func (self *minerApi) Hashrate(req *shared.Request) (interface{}, error) { - return self.ethereum.Miner().HashRate(), nil -} - -func (self *minerApi) SetExtra(req *shared.Request) (interface{}, error) { - args := new(SetExtraArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - - if err := self.ethereum.Miner().SetExtra([]byte(args.Data)); err != nil { - return false, err - } - - return true, nil -} - -func (self *minerApi) SetGasPrice(req *shared.Request) (interface{}, error) { - args := new(GasPriceArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return false, err - } - - self.ethereum.Miner().SetGasPrice(common.String2Big(args.Price)) - return true, nil -} - -func (self *minerApi) SetEtherbase(req *shared.Request) (interface{}, error) { - args := new(SetEtherbaseArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return false, err - } - self.ethereum.SetEtherbase(args.Etherbase) - return nil, nil -} - -func (self *minerApi) StartAutoDAG(req *shared.Request) (interface{}, error) { - self.ethereum.StartAutoDAG() - return true, nil -} - -func (self *minerApi) StopAutoDAG(req *shared.Request) (interface{}, error) { - self.ethereum.StopAutoDAG() - return true, nil -} - -func (self *minerApi) MakeDAG(req *shared.Request) (interface{}, error) { - args := new(MakeDAGArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - - if args.BlockNumber < 0 { - return false, shared.NewValidationError("BlockNumber", "BlockNumber must be positive") - } - - err := ethash.MakeDAG(uint64(args.BlockNumber), "") - if err == nil { - return true, nil - } - return false, err -} diff --git a/rpc/api/miner_args.go b/rpc/api/miner_args.go deleted file mode 100644 index 5ceb244feb..0000000000 --- a/rpc/api/miner_args.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "encoding/json" - - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type StartMinerArgs struct { - Threads int -} - -func (args *StartMinerArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) == 0 || obj[0] == nil { - args.Threads = -1 - return nil - } - - var num *big.Int - if num, err = numString(obj[0]); err != nil { - return err - } - args.Threads = int(num.Int64()) - return nil -} - -type SetExtraArgs struct { - Data string -} - -func (args *SetExtraArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - extrastr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("Price", "not a string") - } - args.Data = extrastr - - return nil -} - -type GasPriceArgs struct { - Price string -} - -func (args *GasPriceArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - if pricestr, ok := obj[0].(string); ok { - args.Price = pricestr - return nil - } - - return shared.NewInvalidTypeError("Price", "not a string") -} - -type SetEtherbaseArgs struct { - Etherbase common.Address -} - -func (args *SetEtherbaseArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - if addr, ok := obj[0].(string); ok { - args.Etherbase = common.HexToAddress(addr) - if (args.Etherbase == common.Address{}) { - return shared.NewInvalidTypeError("Etherbase", "not a valid address") - } - return nil - } - - return shared.NewInvalidTypeError("Etherbase", "not a string") -} - -type MakeDAGArgs struct { - BlockNumber int64 -} - -func (args *MakeDAGArgs) UnmarshalJSON(b []byte) (err error) { - args.BlockNumber = -1 - var obj []interface{} - - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - if err := blockHeight(obj[0], &args.BlockNumber); err != nil { - return err - } - - return nil -} diff --git a/rpc/api/miner_js.go b/rpc/api/miner_js.go deleted file mode 100644 index 0998a9f41c..0000000000 --- a/rpc/api/miner_js.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2015 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 . - -package api - -const Miner_JS = ` -web3._extend({ - property: 'miner', - methods: - [ - new web3._extend.Method({ - name: 'start', - call: 'miner_start', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'stop', - call: 'miner_stop', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'setEtherbase', - call: 'miner_setEtherbase', - params: 1, - inputFormatter: [web3._extend.formatters.formatInputInt], - outputFormatter: web3._extend.formatters.formatOutputBool - }), - new web3._extend.Method({ - name: 'setExtra', - call: 'miner_setExtra', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'setGasPrice', - call: 'miner_setGasPrice', - params: 1, - inputFormatter: [web3._extend.utils.fromDecial] - }), - new web3._extend.Method({ - name: 'startAutoDAG', - call: 'miner_startAutoDAG', - params: 0, - inputFormatter: [] - }), - new web3._extend.Method({ - name: 'stopAutoDAG', - call: 'miner_stopAutoDAG', - params: 0, - inputFormatter: [] - }), - new web3._extend.Method({ - name: 'makeDAG', - call: 'miner_makeDAG', - params: 1, - inputFormatter: [web3._extend.formatters.inputDefaultBlockNumberFormatter] - }) - ], - properties: - [ - new web3._extend.Property({ - name: 'hashrate', - getter: 'miner_hashrate', - outputFormatter: web3._extend.utils.toDecimal - }) - ] -}); -` diff --git a/rpc/api/net.go b/rpc/api/net.go deleted file mode 100644 index 9c63696152..0000000000 --- a/rpc/api/net.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" -) - -const ( - NetApiVersion = "1.0" -) - -var ( - // mapping between methods and handlers - netMapping = map[string]nethandler{ - "net_peerCount": (*netApi).PeerCount, - "net_listening": (*netApi).IsListening, - "net_version": (*netApi).Version, - } -) - -// net callback handler -type nethandler func(*netApi, *shared.Request) (interface{}, error) - -// net api provider -type netApi struct { - xeth *xeth.XEth - ethereum *eth.Ethereum - methods map[string]nethandler - codec codec.ApiCoder -} - -// create a new net api instance -func NewNetApi(xeth *xeth.XEth, eth *eth.Ethereum, coder codec.Codec) *netApi { - return &netApi{ - xeth: xeth, - ethereum: eth, - methods: netMapping, - codec: coder.New(nil), - } -} - -// collection with supported methods -func (self *netApi) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *netApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, shared.NewNotImplementedError(req.Method) -} - -func (self *netApi) Name() string { - return shared.NetApiName -} - -func (self *netApi) ApiVersion() string { - return NetApiVersion -} - -// Number of connected peers -func (self *netApi) PeerCount(req *shared.Request) (interface{}, error) { - return newHexNum(self.xeth.PeerCount()), nil -} - -func (self *netApi) IsListening(req *shared.Request) (interface{}, error) { - return self.xeth.IsListening(), nil -} - -func (self *netApi) Version(req *shared.Request) (interface{}, error) { - return self.xeth.NetworkVersion(), nil -} diff --git a/rpc/api/net_js.go b/rpc/api/net_js.go deleted file mode 100644 index 2ee1f0041a..0000000000 --- a/rpc/api/net_js.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2015 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 . - -package api - -const Net_JS = ` -web3._extend({ - property: 'net', - methods: - [ - new web3._extend.Method({ - name: 'addPeer', - call: 'net_addPeer', - params: 1, - inputFormatter: [null] - }) - ], - properties: - [ - new web3._extend.Property({ - name: 'version', - getter: 'net_version' - }) - ] -}); -` diff --git a/rpc/api/parsing.go b/rpc/api/parsing.go deleted file mode 100644 index 7667616ff4..0000000000 --- a/rpc/api/parsing.go +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "encoding/json" - "math/big" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type hexdata struct { - data []byte - isNil bool -} - -func (d *hexdata) String() string { - return "0x" + common.Bytes2Hex(d.data) -} - -func (d *hexdata) MarshalJSON() ([]byte, error) { - if d.isNil { - return json.Marshal(nil) - } - return json.Marshal(d.String()) -} - -func newHexData(input interface{}) *hexdata { - d := new(hexdata) - - if input == nil { - d.isNil = true - return d - } - switch input := input.(type) { - case []byte: - d.data = input - case common.Hash: - d.data = input.Bytes() - case *common.Hash: - if input == nil { - d.isNil = true - } else { - d.data = input.Bytes() - } - case common.Address: - d.data = input.Bytes() - case *common.Address: - if input == nil { - d.isNil = true - } else { - d.data = input.Bytes() - } - case types.Bloom: - d.data = input.Bytes() - case *types.Bloom: - if input == nil { - d.isNil = true - } else { - d.data = input.Bytes() - } - case *big.Int: - if input == nil { - d.isNil = true - } else { - d.data = input.Bytes() - } - case int64: - d.data = big.NewInt(input).Bytes() - case uint64: - buff := make([]byte, 8) - binary.BigEndian.PutUint64(buff, input) - d.data = buff - case int: - d.data = big.NewInt(int64(input)).Bytes() - case uint: - d.data = big.NewInt(int64(input)).Bytes() - case int8: - d.data = big.NewInt(int64(input)).Bytes() - case uint8: - d.data = big.NewInt(int64(input)).Bytes() - case int16: - d.data = big.NewInt(int64(input)).Bytes() - case uint16: - buff := make([]byte, 2) - binary.BigEndian.PutUint16(buff, input) - d.data = buff - case int32: - d.data = big.NewInt(int64(input)).Bytes() - case uint32: - buff := make([]byte, 4) - binary.BigEndian.PutUint32(buff, input) - d.data = buff - case string: // hexstring - // aaargh ffs TODO: avoid back-and-forth hex encodings where unneeded - bytes, err := hex.DecodeString(strings.TrimPrefix(input, "0x")) - if err != nil { - d.isNil = true - } else { - d.data = bytes - } - default: - d.isNil = true - } - - return d -} - -type hexnum struct { - data []byte - isNil bool -} - -func (d *hexnum) String() string { - // Get hex string from bytes - out := common.Bytes2Hex(d.data) - // Trim leading 0s - out = strings.TrimLeft(out, "0") - // Output "0x0" when value is 0 - if len(out) == 0 { - out = "0" - } - return "0x" + out -} - -func (d *hexnum) MarshalJSON() ([]byte, error) { - if d.isNil { - return json.Marshal(nil) - } - return json.Marshal(d.String()) -} - -func newHexNum(input interface{}) *hexnum { - d := new(hexnum) - - d.data = newHexData(input).data - - return d -} - -type BlockRes struct { - fullTx bool - - BlockNumber *hexnum `json:"number"` - BlockHash *hexdata `json:"hash"` - ParentHash *hexdata `json:"parentHash"` - Nonce *hexdata `json:"nonce"` - Sha3Uncles *hexdata `json:"sha3Uncles"` - LogsBloom *hexdata `json:"logsBloom"` - TransactionRoot *hexdata `json:"transactionsRoot"` - StateRoot *hexdata `json:"stateRoot"` - ReceiptRoot *hexdata `json:"receiptRoot"` - Miner *hexdata `json:"miner"` - Difficulty *hexnum `json:"difficulty"` - TotalDifficulty *hexnum `json:"totalDifficulty"` - Size *hexnum `json:"size"` - ExtraData *hexdata `json:"extraData"` - GasLimit *hexnum `json:"gasLimit"` - GasUsed *hexnum `json:"gasUsed"` - UnixTimestamp *hexnum `json:"timestamp"` - Transactions []*TransactionRes `json:"transactions"` - Uncles []*UncleRes `json:"uncles"` -} - -func (b *BlockRes) MarshalJSON() ([]byte, error) { - if b.fullTx { - var ext struct { - BlockNumber *hexnum `json:"number"` - BlockHash *hexdata `json:"hash"` - ParentHash *hexdata `json:"parentHash"` - Nonce *hexdata `json:"nonce"` - Sha3Uncles *hexdata `json:"sha3Uncles"` - LogsBloom *hexdata `json:"logsBloom"` - TransactionRoot *hexdata `json:"transactionsRoot"` - StateRoot *hexdata `json:"stateRoot"` - ReceiptRoot *hexdata `json:"receiptRoot"` - Miner *hexdata `json:"miner"` - Difficulty *hexnum `json:"difficulty"` - TotalDifficulty *hexnum `json:"totalDifficulty"` - Size *hexnum `json:"size"` - ExtraData *hexdata `json:"extraData"` - GasLimit *hexnum `json:"gasLimit"` - GasUsed *hexnum `json:"gasUsed"` - UnixTimestamp *hexnum `json:"timestamp"` - Transactions []*TransactionRes `json:"transactions"` - Uncles []*hexdata `json:"uncles"` - } - - ext.BlockNumber = b.BlockNumber - ext.BlockHash = b.BlockHash - ext.ParentHash = b.ParentHash - ext.Nonce = b.Nonce - ext.Sha3Uncles = b.Sha3Uncles - ext.LogsBloom = b.LogsBloom - ext.TransactionRoot = b.TransactionRoot - ext.StateRoot = b.StateRoot - ext.ReceiptRoot = b.ReceiptRoot - ext.Miner = b.Miner - ext.Difficulty = b.Difficulty - ext.TotalDifficulty = b.TotalDifficulty - ext.Size = b.Size - ext.ExtraData = b.ExtraData - ext.GasLimit = b.GasLimit - ext.GasUsed = b.GasUsed - ext.UnixTimestamp = b.UnixTimestamp - ext.Transactions = b.Transactions - ext.Uncles = make([]*hexdata, len(b.Uncles)) - for i, u := range b.Uncles { - ext.Uncles[i] = u.BlockHash - } - return json.Marshal(ext) - } else { - var ext struct { - BlockNumber *hexnum `json:"number"` - BlockHash *hexdata `json:"hash"` - ParentHash *hexdata `json:"parentHash"` - Nonce *hexdata `json:"nonce"` - Sha3Uncles *hexdata `json:"sha3Uncles"` - LogsBloom *hexdata `json:"logsBloom"` - TransactionRoot *hexdata `json:"transactionsRoot"` - StateRoot *hexdata `json:"stateRoot"` - ReceiptRoot *hexdata `json:"receiptRoot"` - Miner *hexdata `json:"miner"` - Difficulty *hexnum `json:"difficulty"` - TotalDifficulty *hexnum `json:"totalDifficulty"` - Size *hexnum `json:"size"` - ExtraData *hexdata `json:"extraData"` - GasLimit *hexnum `json:"gasLimit"` - GasUsed *hexnum `json:"gasUsed"` - UnixTimestamp *hexnum `json:"timestamp"` - Transactions []*hexdata `json:"transactions"` - Uncles []*hexdata `json:"uncles"` - } - - ext.BlockNumber = b.BlockNumber - ext.BlockHash = b.BlockHash - ext.ParentHash = b.ParentHash - ext.Nonce = b.Nonce - ext.Sha3Uncles = b.Sha3Uncles - ext.LogsBloom = b.LogsBloom - ext.TransactionRoot = b.TransactionRoot - ext.StateRoot = b.StateRoot - ext.ReceiptRoot = b.ReceiptRoot - ext.Miner = b.Miner - ext.Difficulty = b.Difficulty - ext.TotalDifficulty = b.TotalDifficulty - ext.Size = b.Size - ext.ExtraData = b.ExtraData - ext.GasLimit = b.GasLimit - ext.GasUsed = b.GasUsed - ext.UnixTimestamp = b.UnixTimestamp - ext.Transactions = make([]*hexdata, len(b.Transactions)) - for i, tx := range b.Transactions { - ext.Transactions[i] = tx.Hash - } - ext.Uncles = make([]*hexdata, len(b.Uncles)) - for i, u := range b.Uncles { - ext.Uncles[i] = u.BlockHash - } - return json.Marshal(ext) - } -} - -func NewBlockRes(block *types.Block, td *big.Int, fullTx bool) *BlockRes { - if block == nil { - return nil - } - - res := new(BlockRes) - res.fullTx = fullTx - res.BlockNumber = newHexNum(block.Number()) - res.BlockHash = newHexData(block.Hash()) - res.ParentHash = newHexData(block.ParentHash()) - res.Nonce = newHexData(block.Nonce()) - res.Sha3Uncles = newHexData(block.UncleHash()) - res.LogsBloom = newHexData(block.Bloom()) - res.TransactionRoot = newHexData(block.TxHash()) - res.StateRoot = newHexData(block.Root()) - res.ReceiptRoot = newHexData(block.ReceiptHash()) - res.Miner = newHexData(block.Coinbase()) - res.Difficulty = newHexNum(block.Difficulty()) - res.TotalDifficulty = newHexNum(td) - res.Size = newHexNum(block.Size().Int64()) - res.ExtraData = newHexData(block.Extra()) - res.GasLimit = newHexNum(block.GasLimit()) - res.GasUsed = newHexNum(block.GasUsed()) - res.UnixTimestamp = newHexNum(block.Time()) - - txs := block.Transactions() - res.Transactions = make([]*TransactionRes, len(txs)) - for i, tx := range txs { - res.Transactions[i] = NewTransactionRes(tx) - res.Transactions[i].BlockHash = res.BlockHash - res.Transactions[i].BlockNumber = res.BlockNumber - res.Transactions[i].TxIndex = newHexNum(i) - } - - uncles := block.Uncles() - res.Uncles = make([]*UncleRes, len(uncles)) - for i, uncle := range uncles { - res.Uncles[i] = NewUncleRes(uncle) - } - - return res -} - -type TransactionRes struct { - Hash *hexdata `json:"hash"` - Nonce *hexnum `json:"nonce"` - BlockHash *hexdata `json:"blockHash"` - BlockNumber *hexnum `json:"blockNumber"` - TxIndex *hexnum `json:"transactionIndex"` - From *hexdata `json:"from"` - To *hexdata `json:"to"` - Value *hexnum `json:"value"` - Gas *hexnum `json:"gas"` - GasPrice *hexnum `json:"gasPrice"` - Input *hexdata `json:"input"` -} - -func NewTransactionRes(tx *types.Transaction) *TransactionRes { - if tx == nil { - return nil - } - - var v = new(TransactionRes) - v.Hash = newHexData(tx.Hash()) - v.Nonce = newHexNum(tx.Nonce()) - // v.BlockHash = - // v.BlockNumber = - // v.TxIndex = - from, _ := tx.From() - v.From = newHexData(from) - v.To = newHexData(tx.To()) - v.Value = newHexNum(tx.Value()) - v.Gas = newHexNum(tx.Gas()) - v.GasPrice = newHexNum(tx.GasPrice()) - v.Input = newHexData(tx.Data()) - return v -} - -type UncleRes struct { - BlockNumber *hexnum `json:"number"` - BlockHash *hexdata `json:"hash"` - ParentHash *hexdata `json:"parentHash"` - Nonce *hexdata `json:"nonce"` - Sha3Uncles *hexdata `json:"sha3Uncles"` - ReceiptHash *hexdata `json:"receiptHash"` - LogsBloom *hexdata `json:"logsBloom"` - TransactionRoot *hexdata `json:"transactionsRoot"` - StateRoot *hexdata `json:"stateRoot"` - Miner *hexdata `json:"miner"` - Difficulty *hexnum `json:"difficulty"` - ExtraData *hexdata `json:"extraData"` - GasLimit *hexnum `json:"gasLimit"` - GasUsed *hexnum `json:"gasUsed"` - UnixTimestamp *hexnum `json:"timestamp"` -} - -func NewUncleRes(h *types.Header) *UncleRes { - if h == nil { - return nil - } - - var v = new(UncleRes) - v.BlockNumber = newHexNum(h.Number) - v.BlockHash = newHexData(h.Hash()) - v.ParentHash = newHexData(h.ParentHash) - v.Sha3Uncles = newHexData(h.UncleHash) - v.Nonce = newHexData(h.Nonce[:]) - v.LogsBloom = newHexData(h.Bloom) - v.TransactionRoot = newHexData(h.TxHash) - v.StateRoot = newHexData(h.Root) - v.Miner = newHexData(h.Coinbase) - v.Difficulty = newHexNum(h.Difficulty) - v.ExtraData = newHexData(h.Extra) - v.GasLimit = newHexNum(h.GasLimit) - v.GasUsed = newHexNum(h.GasUsed) - v.UnixTimestamp = newHexNum(h.Time) - v.ReceiptHash = newHexData(h.ReceiptHash) - - return v -} - -// type FilterLogRes struct { -// Hash string `json:"hash"` -// Address string `json:"address"` -// Data string `json:"data"` -// BlockNumber string `json:"blockNumber"` -// TransactionHash string `json:"transactionHash"` -// BlockHash string `json:"blockHash"` -// TransactionIndex string `json:"transactionIndex"` -// LogIndex string `json:"logIndex"` -// } - -// type FilterWhisperRes struct { -// Hash string `json:"hash"` -// From string `json:"from"` -// To string `json:"to"` -// Expiry string `json:"expiry"` -// Sent string `json:"sent"` -// Ttl string `json:"ttl"` -// Topics string `json:"topics"` -// Payload string `json:"payload"` -// WorkProved string `json:"workProved"` -// } - -type ReceiptRes struct { - TransactionHash *hexdata `json:"transactionHash"` - TransactionIndex *hexnum `json:"transactionIndex"` - BlockNumber *hexnum `json:"blockNumber"` - BlockHash *hexdata `json:"blockHash"` - CumulativeGasUsed *hexnum `json:"cumulativeGasUsed"` - GasUsed *hexnum `json:"gasUsed"` - ContractAddress *hexdata `json:"contractAddress"` - Logs *[]interface{} `json:"logs"` -} - -func NewReceiptRes(rec *types.Receipt) *ReceiptRes { - if rec == nil { - return nil - } - - var v = new(ReceiptRes) - v.TransactionHash = newHexData(rec.TxHash) - if rec.GasUsed != nil { - v.GasUsed = newHexNum(rec.GasUsed.Bytes()) - } - v.CumulativeGasUsed = newHexNum(rec.CumulativeGasUsed) - - // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation - if bytes.Compare(rec.ContractAddress.Bytes(), bytes.Repeat([]byte{0}, 20)) != 0 { - v.ContractAddress = newHexData(rec.ContractAddress) - } - - logs := make([]interface{}, len(rec.Logs)) - for i, log := range rec.Logs { - logs[i] = NewLogRes(log) - } - v.Logs = &logs - - return v -} - -func numString(raw interface{}) (*big.Int, error) { - var number *big.Int - // Parse as integer - num, ok := raw.(float64) - if ok { - number = big.NewInt(int64(num)) - return number, nil - } - - // Parse as string/hexstring - str, ok := raw.(string) - if ok { - number = common.String2Big(str) - return number, nil - } - - return nil, shared.NewInvalidTypeError("", "not a number or string") -} - -func blockHeight(raw interface{}, number *int64) error { - // Parse as integer - num, ok := raw.(float64) - if ok { - *number = int64(num) - return nil - } - - // Parse as string/hexstring - str, ok := raw.(string) - if !ok { - return shared.NewInvalidTypeError("", "not a number or string") - } - - switch str { - case "earliest": - *number = 0 - case "latest": - *number = -1 - case "pending": - *number = -2 - default: - if common.HasHexPrefix(str) { - *number = common.String2Big(str).Int64() - } else { - return shared.NewInvalidTypeError("blockNumber", "is not a valid string") - } - } - - return nil -} - -func blockHeightFromJson(msg json.RawMessage, number *int64) error { - var raw interface{} - if err := json.Unmarshal(msg, &raw); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - return blockHeight(raw, number) -} diff --git a/rpc/api/personal.go b/rpc/api/personal.go deleted file mode 100644 index 4f347c6109..0000000000 --- a/rpc/api/personal.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "fmt" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" -) - -const ( - PersonalApiVersion = "1.0" -) - -var ( - // mapping between methods and handlers - personalMapping = map[string]personalhandler{ - "personal_listAccounts": (*personalApi).ListAccounts, - "personal_newAccount": (*personalApi).NewAccount, - "personal_unlockAccount": (*personalApi).UnlockAccount, - } -) - -// net callback handler -type personalhandler func(*personalApi, *shared.Request) (interface{}, error) - -// net api provider -type personalApi struct { - xeth *xeth.XEth - ethereum *eth.Ethereum - methods map[string]personalhandler - codec codec.ApiCoder -} - -// create a new net api instance -func NewPersonalApi(xeth *xeth.XEth, eth *eth.Ethereum, coder codec.Codec) *personalApi { - return &personalApi{ - xeth: xeth, - ethereum: eth, - methods: personalMapping, - codec: coder.New(nil), - } -} - -// collection with supported methods -func (self *personalApi) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *personalApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, shared.NewNotImplementedError(req.Method) -} - -func (self *personalApi) Name() string { - return shared.PersonalApiName -} - -func (self *personalApi) ApiVersion() string { - return PersonalApiVersion -} - -func (self *personalApi) ListAccounts(req *shared.Request) (interface{}, error) { - return self.xeth.Accounts(), nil -} - -func (self *personalApi) NewAccount(req *shared.Request) (interface{}, error) { - args := new(NewAccountArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - var passwd string - if args.Passphrase == nil { - fe := self.xeth.Frontend() - if fe == nil { - return false, fmt.Errorf("unable to create account: unable to interact with user") - } - var ok bool - passwd, ok = fe.AskPassword() - if !ok { - return false, fmt.Errorf("unable to create account: no password given") - } - } else { - passwd = *args.Passphrase - } - am := self.ethereum.AccountManager() - acc, err := am.NewAccount(passwd) - return acc.Address.Hex(), err -} - -func (self *personalApi) UnlockAccount(req *shared.Request) (interface{}, error) { - args := new(UnlockAccountArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - if args.Passphrase == nil { - fe := self.xeth.Frontend() - if fe == nil { - return false, fmt.Errorf("No password provided") - } - return fe.UnlockAccount(common.HexToAddress(args.Address).Bytes()), nil - } - - am := self.ethereum.AccountManager() - addr := common.HexToAddress(args.Address) - - err := am.TimedUnlock(addr, *args.Passphrase, time.Duration(args.Duration)*time.Second) - return err == nil, err -} diff --git a/rpc/api/personal_args.go b/rpc/api/personal_args.go deleted file mode 100644 index 5d215c71de..0000000000 --- a/rpc/api/personal_args.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type NewAccountArgs struct { - Passphrase *string -} - -func (args *NewAccountArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) >= 1 && obj[0] != nil { - if passphrasestr, ok := obj[0].(string); ok { - args.Passphrase = &passphrasestr - } else { - return shared.NewInvalidTypeError("passphrase", "not a string") - } - } - - return nil -} - -type UnlockAccountArgs struct { - Address string - Passphrase *string - Duration int -} - -func (args *UnlockAccountArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - args.Duration = 0 - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - if addrstr, ok := obj[0].(string); ok { - args.Address = addrstr - } else { - return shared.NewInvalidTypeError("address", "not a string") - } - - if len(obj) >= 2 && obj[1] != nil { - if passphrasestr, ok := obj[1].(string); ok { - args.Passphrase = &passphrasestr - } else { - return shared.NewInvalidTypeError("passphrase", "not a string") - } - } - - if len(obj) >= 3 && obj[2] != nil { - if duration, ok := obj[2].(float64); ok { - args.Duration = int(duration) - } - } - - return nil -} diff --git a/rpc/api/personal_js.go b/rpc/api/personal_js.go deleted file mode 100644 index 84c669af7b..0000000000 --- a/rpc/api/personal_js.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2015 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 . - -package api - -const Personal_JS = ` -web3._extend({ - property: 'personal', - methods: - [ - new web3._extend.Method({ - name: 'newAccount', - call: 'personal_newAccount', - params: 1, - inputFormatter: [null], - outputFormatter: web3._extend.utils.toAddress - }), - new web3._extend.Method({ - name: 'unlockAccount', - call: 'personal_unlockAccount', - params: 3, - inputFormatter: [null, null, null] - }), - new web3._extend.Method({ - name: 'lockAccount', - call: 'personal_lockAccount', - params: 1 - }) - ], - properties: - [ - new web3._extend.Property({ - name: 'listAccounts', - getter: 'personal_listAccounts' - }) - ] -}); -` diff --git a/rpc/api/shh.go b/rpc/api/shh.go deleted file mode 100644 index 60e8056052..0000000000 --- a/rpc/api/shh.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" -) - -const ( - ShhApiVersion = "1.0" -) - -var ( - // mapping between methods and handlers - shhMapping = map[string]shhhandler{ - "shh_version": (*shhApi).Version, - "shh_post": (*shhApi).Post, - "shh_hasIdentity": (*shhApi).HasIdentity, - "shh_newIdentity": (*shhApi).NewIdentity, - "shh_newFilter": (*shhApi).NewFilter, - "shh_uninstallFilter": (*shhApi).UninstallFilter, - "shh_getMessages": (*shhApi).GetMessages, - "shh_getFilterChanges": (*shhApi).GetFilterChanges, - } -) - -func newWhisperOfflineError(method string) error { - return shared.NewNotAvailableError(method, "whisper offline") -} - -// net callback handler -type shhhandler func(*shhApi, *shared.Request) (interface{}, error) - -// shh api provider -type shhApi struct { - xeth *xeth.XEth - ethereum *eth.Ethereum - methods map[string]shhhandler - codec codec.ApiCoder -} - -// create a new whisper api instance -func NewShhApi(xeth *xeth.XEth, eth *eth.Ethereum, coder codec.Codec) *shhApi { - return &shhApi{ - xeth: xeth, - ethereum: eth, - methods: shhMapping, - codec: coder.New(nil), - } -} - -// collection with supported methods -func (self *shhApi) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *shhApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, shared.NewNotImplementedError(req.Method) -} - -func (self *shhApi) Name() string { - return shared.ShhApiName -} - -func (self *shhApi) ApiVersion() string { - return ShhApiVersion -} - -func (self *shhApi) Version(req *shared.Request) (interface{}, error) { - w := self.xeth.Whisper() - if w == nil { - return nil, newWhisperOfflineError(req.Method) - } - - return w.Version(), nil -} - -func (self *shhApi) Post(req *shared.Request) (interface{}, error) { - w := self.xeth.Whisper() - if w == nil { - return nil, newWhisperOfflineError(req.Method) - } - - args := new(WhisperMessageArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - - err := w.Post(args.Payload, args.To, args.From, args.Topics, args.Priority, args.Ttl) - if err != nil { - return false, err - } - - return true, nil -} - -func (self *shhApi) HasIdentity(req *shared.Request) (interface{}, error) { - w := self.xeth.Whisper() - if w == nil { - return nil, newWhisperOfflineError(req.Method) - } - - args := new(WhisperIdentityArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - - return w.HasIdentity(args.Identity), nil -} - -func (self *shhApi) NewIdentity(req *shared.Request) (interface{}, error) { - w := self.xeth.Whisper() - if w == nil { - return nil, newWhisperOfflineError(req.Method) - } - - return w.NewIdentity(), nil -} - -func (self *shhApi) NewFilter(req *shared.Request) (interface{}, error) { - args := new(WhisperFilterArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - - id := self.xeth.NewWhisperFilter(args.To, args.From, args.Topics) - return newHexNum(big.NewInt(int64(id)).Bytes()), nil -} - -func (self *shhApi) UninstallFilter(req *shared.Request) (interface{}, error) { - args := new(FilterIdArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - return self.xeth.UninstallWhisperFilter(args.Id), nil -} - -func (self *shhApi) GetFilterChanges(req *shared.Request) (interface{}, error) { - w := self.xeth.Whisper() - if w == nil { - return nil, newWhisperOfflineError(req.Method) - } - - // Retrieve all the new messages arrived since the last request - args := new(FilterIdArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - - return self.xeth.WhisperMessagesChanged(args.Id), nil -} - -func (self *shhApi) GetMessages(req *shared.Request) (interface{}, error) { - w := self.xeth.Whisper() - if w == nil { - return nil, newWhisperOfflineError(req.Method) - } - - // Retrieve all the cached messages matching a specific, existing filter - args := new(FilterIdArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - - return self.xeth.WhisperMessages(args.Id), nil -} diff --git a/rpc/api/shh_args.go b/rpc/api/shh_args.go deleted file mode 100644 index 468a0b98fc..0000000000 --- a/rpc/api/shh_args.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "encoding/json" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type WhisperMessageArgs struct { - Payload string - To string - From string - Topics []string - Priority uint32 - Ttl uint32 -} - -func (args *WhisperMessageArgs) UnmarshalJSON(b []byte) (err error) { - var obj []struct { - Payload string - To string - From string - Topics []string - Priority interface{} - Ttl interface{} - } - - if err = json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - args.Payload = obj[0].Payload - args.To = obj[0].To - args.From = obj[0].From - args.Topics = obj[0].Topics - - var num *big.Int - if num, err = numString(obj[0].Priority); err != nil { - return err - } - args.Priority = uint32(num.Int64()) - - if num, err = numString(obj[0].Ttl); err != nil { - return err - } - args.Ttl = uint32(num.Int64()) - - return nil -} - -type WhisperIdentityArgs struct { - Identity string -} - -func (args *WhisperIdentityArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - argstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("arg0", "not a string") - } - - args.Identity = argstr - - return nil -} - -type WhisperFilterArgs struct { - To string - From string - Topics [][]string -} - -// UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a -// JSON message blob into a WhisperFilterArgs structure. -func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { - // Unmarshal the JSON message and sanity check - var obj []struct { - To interface{} `json:"to"` - From interface{} `json:"from"` - Topics interface{} `json:"topics"` - } - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - // Retrieve the simple data contents of the filter arguments - if obj[0].To == nil { - args.To = "" - } else { - argstr, ok := obj[0].To.(string) - if !ok { - return shared.NewInvalidTypeError("to", "is not a string") - } - args.To = argstr - } - if obj[0].From == nil { - args.From = "" - } else { - argstr, ok := obj[0].From.(string) - if !ok { - return shared.NewInvalidTypeError("from", "is not a string") - } - args.From = argstr - } - // Construct the nested topic array - if obj[0].Topics != nil { - // Make sure we have an actual topic array - list, ok := obj[0].Topics.([]interface{}) - if !ok { - return shared.NewInvalidTypeError("topics", "is not an array") - } - // Iterate over each topic and handle nil, string or array - topics := make([][]string, len(list)) - for idx, field := range list { - switch value := field.(type) { - case nil: - topics[idx] = []string{} - - case string: - topics[idx] = []string{value} - - case []interface{}: - topics[idx] = make([]string, len(value)) - for i, nested := range value { - switch value := nested.(type) { - case nil: - topics[idx][i] = "" - - case string: - topics[idx][i] = value - - default: - return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d][%d]", idx, i), "is not a string") - } - } - default: - return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d]", idx), "not a string or array") - } - } - args.Topics = topics - } - return nil -} diff --git a/rpc/api/shh_js.go b/rpc/api/shh_js.go deleted file mode 100644 index a92ad1644b..0000000000 --- a/rpc/api/shh_js.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2015 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 . - -package api - -const Shh_JS = ` -web3._extend({ - property: 'shh', - methods: - [ - - ], - properties: - [ - new web3._extend.Property({ - name: 'version', - getter: 'shh_version' - }) - ] -}); -` diff --git a/rpc/api/txpool.go b/rpc/api/txpool.go deleted file mode 100644 index 27e40cae59..0000000000 --- a/rpc/api/txpool.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" -) - -const ( - TxPoolApiVersion = "1.0" -) - -var ( - // mapping between methods and handlers - txpoolMapping = map[string]txpoolhandler{ - "txpool_status": (*txPoolApi).Status, - } -) - -// net callback handler -type txpoolhandler func(*txPoolApi, *shared.Request) (interface{}, error) - -// txpool api provider -type txPoolApi struct { - xeth *xeth.XEth - ethereum *eth.Ethereum - methods map[string]txpoolhandler - codec codec.ApiCoder -} - -// create a new txpool api instance -func NewTxPoolApi(xeth *xeth.XEth, eth *eth.Ethereum, coder codec.Codec) *txPoolApi { - return &txPoolApi{ - xeth: xeth, - ethereum: eth, - methods: txpoolMapping, - codec: coder.New(nil), - } -} - -// collection with supported methods -func (self *txPoolApi) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *txPoolApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, shared.NewNotImplementedError(req.Method) -} - -func (self *txPoolApi) Name() string { - return shared.TxPoolApiName -} - -func (self *txPoolApi) ApiVersion() string { - return TxPoolApiVersion -} - -func (self *txPoolApi) Status(req *shared.Request) (interface{}, error) { - pending, queue := self.ethereum.TxPool().Stats() - return map[string]int{ - "pending": pending, - "queued": queue, - }, nil -} diff --git a/rpc/api/txpool_js.go b/rpc/api/txpool_js.go deleted file mode 100644 index b6c29871a2..0000000000 --- a/rpc/api/txpool_js.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015 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 . - -package api - -const TxPool_JS = ` -web3._extend({ - property: 'txpool', - methods: - [ - ], - properties: - [ - new web3._extend.Property({ - name: 'status', - getter: 'txpool_status' - }) - ] -}); -` diff --git a/rpc/api/utils.go b/rpc/api/utils.go deleted file mode 100644 index 794b6abeea..0000000000 --- a/rpc/api/utils.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "strings" - - "fmt" - - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" -) - -var ( - // Mapping between the different methods each api supports - AutoCompletion = map[string][]string{ - "admin": []string{ - "addPeer", - "datadir", - "enableUserAgent", - "exportChain", - "getContractInfo", - "httpGet", - "importChain", - "nodeInfo", - "peers", - "register", - "registerUrl", - "saveInfo", - "setGlobalRegistrar", - "setHashReg", - "setUrlHint", - "setSolc", - "sleep", - "sleepBlocks", - "startNatSpec", - "startRPC", - "stopNatSpec", - "stopRPC", - "verbosity", - }, - "db": []string{ - "getString", - "putString", - "getHex", - "putHex", - }, - "debug": []string{ - "dumpBlock", - "getBlockRlp", - "metrics", - "printBlock", - "processBlock", - "seedHash", - "setHead", - }, - "eth": []string{ - "accounts", - "blockNumber", - "call", - "contract", - "coinbase", - "compile.lll", - "compile.serpent", - "compile.solidity", - "contract", - "defaultAccount", - "defaultBlock", - "estimateGas", - "filter", - "getBalance", - "getBlock", - "getBlockTransactionCount", - "getBlockUncleCount", - "getCode", - "getNatSpec", - "getCompilers", - "gasPrice", - "getStorageAt", - "getTransaction", - "getTransactionCount", - "getTransactionFromBlock", - "getTransactionReceipt", - "getUncle", - "hashrate", - "mining", - "namereg", - "pendingTransactions", - "resend", - "sendRawTransaction", - "sendTransaction", - "sign", - "syncing", - }, - "miner": []string{ - "hashrate", - "makeDAG", - "setEtherbase", - "setExtra", - "setGasPrice", - "startAutoDAG", - "start", - "stopAutoDAG", - "stop", - }, - "net": []string{ - "peerCount", - "listening", - }, - "personal": []string{ - "listAccounts", - "newAccount", - "unlockAccount", - }, - "shh": []string{ - "post", - "newIdentity", - "hasIdentity", - "newGroup", - "addToGroup", - "filter", - }, - "txpool": []string{ - "status", - }, - "web3": []string{ - "sha3", - "version", - "fromWei", - "toWei", - "toHex", - "toAscii", - "fromAscii", - "toBigNumber", - "isAddress", - }, - } -) - -// Parse a comma separated API string to individual api's -func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, stack *node.Node) ([]shared.EthereumApi, error) { - if len(strings.TrimSpace(apistr)) == 0 { - return nil, fmt.Errorf("Empty apistr provided") - } - - names := strings.Split(apistr, ",") - apis := make([]shared.EthereumApi, len(names)) - - var eth *eth.Ethereum - if stack != nil { - if err := stack.Service(ð); err != nil { - return nil, err - } - } - for i, name := range names { - switch strings.ToLower(strings.TrimSpace(name)) { - case shared.AdminApiName: - apis[i] = NewAdminApi(xeth, stack, codec) - case shared.DebugApiName: - apis[i] = NewDebugApi(xeth, eth, codec) - case shared.DbApiName: - apis[i] = NewDbApi(xeth, eth, codec) - case shared.EthApiName: - apis[i] = NewEthApi(xeth, eth, codec) - case shared.MinerApiName: - apis[i] = NewMinerApi(eth, codec) - case shared.NetApiName: - apis[i] = NewNetApi(xeth, eth, codec) - case shared.ShhApiName: - apis[i] = NewShhApi(xeth, eth, codec) - case shared.TxPoolApiName: - apis[i] = NewTxPoolApi(xeth, eth, codec) - case shared.PersonalApiName: - apis[i] = NewPersonalApi(xeth, eth, codec) - case shared.Web3ApiName: - apis[i] = NewWeb3Api(xeth, codec) - case "rpc": // gives information about the RPC interface - continue - default: - return nil, fmt.Errorf("Unknown API '%s'", name) - } - } - return apis, nil -} - -func Javascript(name string) string { - switch strings.ToLower(strings.TrimSpace(name)) { - case shared.AdminApiName: - return Admin_JS - case shared.DebugApiName: - return Debug_JS - case shared.DbApiName: - return Db_JS - case shared.EthApiName: - return Eth_JS - case shared.MinerApiName: - return Miner_JS - case shared.NetApiName: - return Net_JS - case shared.ShhApiName: - return Shh_JS - case shared.TxPoolApiName: - return TxPool_JS - case shared.PersonalApiName: - return Personal_JS - } - - return "" -} diff --git a/rpc/api/web3.go b/rpc/api/web3.go deleted file mode 100644 index e2d8543d34..0000000000 --- a/rpc/api/web3.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2015 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 . - -package api - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" -) - -const ( - Web3ApiVersion = "1.0" -) - -var ( - // mapping between methods and handlers - Web3Mapping = map[string]web3handler{ - "web3_sha3": (*web3Api).Sha3, - "web3_clientVersion": (*web3Api).ClientVersion, - } -) - -// web3 callback handler -type web3handler func(*web3Api, *shared.Request) (interface{}, error) - -// web3 api provider -type web3Api struct { - xeth *xeth.XEth - methods map[string]web3handler - codec codec.ApiCoder -} - -// create a new web3 api instance -func NewWeb3Api(xeth *xeth.XEth, coder codec.Codec) *web3Api { - return &web3Api{ - xeth: xeth, - methods: Web3Mapping, - codec: coder.New(nil), - } -} - -// collection with supported methods -func (self *web3Api) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *web3Api) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, &shared.NotImplementedError{req.Method} -} - -func (self *web3Api) Name() string { - return shared.Web3ApiName -} - -func (self *web3Api) ApiVersion() string { - return Web3ApiVersion -} - -// Calculates the sha3 over req.Params.Data -func (self *web3Api) Sha3(req *shared.Request) (interface{}, error) { - args := new(Sha3Args) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, err - } - - return common.ToHex(crypto.Sha3(common.FromHex(args.Data))), nil -} - -// returns the xeth client vrsion -func (self *web3Api) ClientVersion(req *shared.Request) (interface{}, error) { - return self.xeth.ClientVersion(), nil -} diff --git a/rpc/codec/codec.go b/rpc/codec/codec.go deleted file mode 100644 index 786080b44a..0000000000 --- a/rpc/codec/codec.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2015 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 . - -package codec - -import ( - "net" - "strconv" - - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type Codec int - -// (de)serialization support for rpc interface -type ApiCoder interface { - // Parse message to request from underlying stream - ReadRequest() ([]*shared.Request, bool, error) - // Parse response message from underlying stream - ReadResponse() (interface{}, error) - // Read raw message from underlying stream - Recv() (interface{}, error) - // Encode response to encoded form in underlying stream - WriteResponse(interface{}) error - // Decode single message from data - Decode([]byte, interface{}) error - // Encode msg to encoded form - Encode(msg interface{}) ([]byte, error) - // close the underlying stream - Close() -} - -// supported codecs -const ( - JSON Codec = iota - nCodecs -) - -var ( - // collection with supported coders - coders = make([]func(net.Conn) ApiCoder, nCodecs) -) - -// create a new coder instance -func (c Codec) New(conn net.Conn) ApiCoder { - switch c { - case JSON: - return NewJsonCoder(conn) - } - - panic("codec: request for codec #" + strconv.Itoa(int(c)) + " is unavailable") -} diff --git a/rpc/codec/json.go b/rpc/codec/json.go deleted file mode 100644 index cfc449143b..0000000000 --- a/rpc/codec/json.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2015 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 . - -package codec - -import ( - "encoding/json" - "fmt" - "net" - "time" - "strings" - - "github.com/ethereum/go-ethereum/rpc/shared" -) - -const ( - READ_TIMEOUT = 60 // in seconds - MAX_REQUEST_SIZE = 1024 * 1024 - MAX_RESPONSE_SIZE = 1024 * 1024 -) - -// Json serialization support -type JsonCodec struct { - c net.Conn - d *json.Decoder -} - -// Create new JSON coder instance -func NewJsonCoder(conn net.Conn) ApiCoder { - return &JsonCodec{ - c: conn, - d: json.NewDecoder(conn), - } -} - -// Read incoming request and parse it to RPC request -func (self *JsonCodec) ReadRequest() (requests []*shared.Request, isBatch bool, err error) { - deadline := time.Now().Add(READ_TIMEOUT * time.Second) - if err := self.c.SetDeadline(deadline); err != nil { - return nil, false, err - } - - var incoming json.RawMessage - err = self.d.Decode(&incoming) - if err == nil { - isBatch = incoming[0] == '[' - if isBatch { - requests = make([]*shared.Request, 0) - err = json.Unmarshal(incoming, &requests) - } else { - requests = make([]*shared.Request, 1) - var singleRequest shared.Request - if err = json.Unmarshal(incoming, &singleRequest); err == nil { - requests[0] = &singleRequest - } - } - return - } - - self.c.Close() - return nil, false, err -} - -func (self *JsonCodec) Recv() (interface{}, error) { - var msg json.RawMessage - err := self.d.Decode(&msg) - if err != nil { - self.c.Close() - return nil, err - } - - return msg, err -} - -func (self *JsonCodec) ReadResponse() (interface{}, error) { - in, err := self.Recv() - if err != nil { - return nil, err - } - - if msg, ok := in.(json.RawMessage); ok { - var req *shared.Request - if err = json.Unmarshal(msg, &req); err == nil && strings.HasPrefix(req.Method, "agent_") { - return req, nil - } - - var failure *shared.ErrorResponse - if err = json.Unmarshal(msg, &failure); err == nil && failure.Error != nil { - return failure, fmt.Errorf(failure.Error.Message) - } - - var success *shared.SuccessResponse - if err = json.Unmarshal(msg, &success); err == nil { - return success, nil - } - } - - return in, err -} - -// Decode data -func (self *JsonCodec) Decode(data []byte, msg interface{}) error { - return json.Unmarshal(data, msg) -} - -// Encode message -func (self *JsonCodec) Encode(msg interface{}) ([]byte, error) { - return json.Marshal(msg) -} - -// Parse JSON data from conn to obj -func (self *JsonCodec) WriteResponse(res interface{}) error { - data, err := json.Marshal(res) - if err != nil { - self.c.Close() - return err - } - - bytesWritten := 0 - - for bytesWritten < len(data) { - n, err := self.c.Write(data[bytesWritten:]) - if err != nil { - self.c.Close() - return err - } - bytesWritten += n - } - - return nil -} - -// Close decoder and encoder -func (self *JsonCodec) Close() { - self.c.Close() -} diff --git a/rpc/codec/json_test.go b/rpc/codec/json_test.go deleted file mode 100644 index 01ef77e57d..0000000000 --- a/rpc/codec/json_test.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2015 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 . - -package codec - -import ( - "bytes" - "io" - "net" - "testing" - "time" -) - -type jsonTestConn struct { - buffer *bytes.Buffer -} - -func newJsonTestConn(data []byte) *jsonTestConn { - return &jsonTestConn{ - buffer: bytes.NewBuffer(data), - } -} - -func (self *jsonTestConn) Read(p []byte) (n int, err error) { - return self.buffer.Read(p) -} - -func (self *jsonTestConn) Write(p []byte) (n int, err error) { - return self.buffer.Write(p) -} - -func (self *jsonTestConn) Close() error { - // not implemented - return nil -} - -func (self *jsonTestConn) LocalAddr() net.Addr { - // not implemented - return nil -} - -func (self *jsonTestConn) RemoteAddr() net.Addr { - // not implemented - return nil -} - -func (self *jsonTestConn) SetDeadline(t time.Time) error { - return nil -} - -func (self *jsonTestConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (self *jsonTestConn) SetWriteDeadline(t time.Time) error { - return nil -} - -func TestJsonDecoderWithValidRequest(t *testing.T) { - reqdata := []byte(`{"jsonrpc":"2.0","method":"modules","params":[],"id":64}`) - decoder := newJsonTestConn(reqdata) - - jsonDecoder := NewJsonCoder(decoder) - requests, batch, err := jsonDecoder.ReadRequest() - - if err != nil { - t.Errorf("Read valid request failed - %v", err) - } - - if len(requests) != 1 { - t.Errorf("Expected to get a single request but got %d", len(requests)) - } - - if batch { - t.Errorf("Got batch indication while expecting single request") - } - - if requests[0].Id != float64(64) { - t.Errorf("Expected req.Id == 64 but got %v", requests[0].Id) - } - - if requests[0].Method != "modules" { - t.Errorf("Expected req.Method == 'modules' got '%s'", requests[0].Method) - } -} - -func TestJsonDecoderWithValidBatchRequest(t *testing.T) { - reqdata := []byte(`[{"jsonrpc":"2.0","method":"modules","params":[],"id":64}, - {"jsonrpc":"2.0","method":"modules","params":[],"id":64}]`) - decoder := newJsonTestConn(reqdata) - - jsonDecoder := NewJsonCoder(decoder) - requests, batch, err := jsonDecoder.ReadRequest() - - if err != nil { - t.Errorf("Read valid batch request failed - %v", err) - } - - if len(requests) != 2 { - t.Errorf("Expected to get two requests but got %d", len(requests)) - } - - if !batch { - t.Errorf("Got no batch indication while expecting batch request") - } - - for i := 0; i < len(requests); i++ { - if requests[i].Id != float64(64) { - t.Errorf("Expected req.Id == 64 but got %v", requests[i].Id) - } - - if requests[i].Method != "modules" { - t.Errorf("Expected req.Method == 'modules' got '%s'", requests[i].Method) - } - } -} - -func TestJsonDecoderWithInvalidIncompleteMessage(t *testing.T) { - reqdata := []byte(`{"jsonrpc":"2.0","method":"modules","pa`) - decoder := newJsonTestConn(reqdata) - - jsonDecoder := NewJsonCoder(decoder) - requests, batch, err := jsonDecoder.ReadRequest() - - if err != io.ErrUnexpectedEOF { - t.Errorf("Expected to read an incomplete request err but got %v", err) - } - - // remaining message - decoder.Write([]byte(`rams":[],"id:64"}`)) - requests, batch, err = jsonDecoder.ReadRequest() - - if err == nil { - t.Errorf("Expected an error but got nil") - } - - if len(requests) != 0 { - t.Errorf("Expected to get no requests but got %d", len(requests)) - } - - if batch { - t.Errorf("Got batch indication while expecting non batch") - } -} diff --git a/rpc/comms/comms.go b/rpc/comms/comms.go deleted file mode 100644 index 61fba5722c..0000000000 --- a/rpc/comms/comms.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2015 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 . - -package comms - -import ( - "io" - "net" - - "fmt" - "strings" - - "strconv" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -const ( - maxHttpSizeReqLength = 1024 * 1024 // 1MB -) - -var ( - // List with all API's which are offered over the in proc interface by default - DefaultInProcApis = shared.AllApis - - // List with all API's which are offered over the IPC interface by default - DefaultIpcApis = shared.AllApis - - // List with API's which are offered over thr HTTP/RPC interface by default - DefaultHttpRpcApis = strings.Join([]string{ - shared.DbApiName, shared.EthApiName, shared.NetApiName, shared.Web3ApiName, - }, ",") -) - -type EthereumClient interface { - // Close underlying connection - Close() - // Send request - Send(interface{}) error - // Receive response - Recv() (interface{}, error) - // List with modules this client supports - SupportedModules() (map[string]string, error) -} - -func handle(id int, conn net.Conn, api shared.EthereumApi, c codec.Codec) { - codec := c.New(conn) - - defer func() { - if r := recover(); r != nil { - glog.Errorf("panic: %v\n", r) - } - codec.Close() - }() - - for { - requests, isBatch, err := codec.ReadRequest() - if err == io.EOF { - return - } else if err != nil { - glog.V(logger.Debug).Infof("Closed IPC Conn %06d recv err - %v\n", id, err) - return - } - - if isBatch { - responses := make([]*interface{}, len(requests)) - responseCount := 0 - for _, req := range requests { - res, err := api.Execute(req) - if req.Id != nil { - rpcResponse := shared.NewRpcResponse(req.Id, req.Jsonrpc, res, err) - responses[responseCount] = rpcResponse - responseCount += 1 - } - } - - err = codec.WriteResponse(responses[:responseCount]) - if err != nil { - glog.V(logger.Debug).Infof("Closed IPC Conn %06d send err - %v\n", id, err) - return - } - } else { - var rpcResponse interface{} - res, err := api.Execute(requests[0]) - - rpcResponse = shared.NewRpcResponse(requests[0].Id, requests[0].Jsonrpc, res, err) - err = codec.WriteResponse(rpcResponse) - if err != nil { - glog.V(logger.Debug).Infof("Closed IPC Conn %06d send err - %v\n", id, err) - return - } - } - } -} - -// Endpoint must be in the form of: -// ${protocol}:${path} -// e.g. ipc:/tmp/geth.ipc -// rpc:localhost:8545 -func ClientFromEndpoint(endpoint string, c codec.Codec) (EthereumClient, error) { - if strings.HasPrefix(endpoint, "ipc:") { - cfg := IpcConfig{ - Endpoint: endpoint[4:], - } - return NewIpcClient(cfg, codec.JSON) - } - - if strings.HasPrefix(endpoint, "rpc:") { - parts := strings.Split(endpoint, ":") - addr := "http://localhost" - port := uint(8545) - if len(parts) >= 3 { - addr = parts[1] + ":" + parts[2] - } - - if len(parts) >= 4 { - p, err := strconv.Atoi(parts[3]) - - if err != nil { - return nil, err - } - port = uint(p) - } - - cfg := HttpConfig{ - ListenAddress: addr, - ListenPort: port, - } - - return NewHttpClient(cfg, codec.JSON), nil - } - - return nil, fmt.Errorf("Invalid endpoint") -} diff --git a/rpc/comms/http.go b/rpc/comms/http.go deleted file mode 100644 index f4a930d0ef..0000000000 --- a/rpc/comms/http.go +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright 2015 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 . - -package comms - -import ( - "encoding/json" - "fmt" - "net" - "net/http" - "strings" - "sync" - "time" - - "bytes" - "io" - "io/ioutil" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/rs/cors" -) - -const ( - serverIdleTimeout = 10 * time.Second // idle keep-alive connections - serverReadTimeout = 15 * time.Second // per-request read timeout - serverWriteTimeout = 15 * time.Second // per-request read timeout -) - -var ( - httpServerMu sync.Mutex - httpServer *stopServer -) - -type HttpConfig struct { - ListenAddress string - ListenPort uint - CorsDomain string -} - -// stopServer augments http.Server with idle connection tracking. -// Idle keep-alive connections are shut down when Close is called. -type stopServer struct { - *http.Server - l net.Listener - // connection tracking state - mu sync.Mutex - shutdown bool // true when Stop has returned - idle map[net.Conn]struct{} -} - -type handler struct { - codec codec.Codec - api shared.EthereumApi -} - -// StartHTTP starts listening for RPC requests sent via HTTP. -func StartHttp(cfg HttpConfig, codec codec.Codec, api shared.EthereumApi) error { - httpServerMu.Lock() - defer httpServerMu.Unlock() - - addr := fmt.Sprintf("%s:%d", cfg.ListenAddress, cfg.ListenPort) - if httpServer != nil { - if addr != httpServer.Addr { - return fmt.Errorf("RPC service already running on %s ", httpServer.Addr) - } - return nil // RPC service already running on given host/port - } - // Set up the request handler, wrapping it with CORS headers if configured. - handler := http.Handler(&handler{codec, api}) - if len(cfg.CorsDomain) > 0 { - opts := cors.Options{ - AllowedMethods: []string{"POST"}, - AllowedOrigins: strings.Split(cfg.CorsDomain, " "), - } - handler = cors.New(opts).Handler(handler) - } - // Start the server. - s, err := listenHTTP(addr, handler) - if err != nil { - glog.V(logger.Error).Infof("Can't listen on %s:%d: %v", cfg.ListenAddress, cfg.ListenPort, err) - return err - } - httpServer = s - return nil -} - -func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "application/json") - - // Limit request size to resist DoS - if req.ContentLength > maxHttpSizeReqLength { - err := fmt.Errorf("Request too large") - response := shared.NewRpcErrorResponse(-1, shared.JsonRpcVersion, -32700, err) - sendJSON(w, &response) - return - } - - defer req.Body.Close() - payload, err := ioutil.ReadAll(req.Body) - if err != nil { - err := fmt.Errorf("Could not read request body") - response := shared.NewRpcErrorResponse(-1, shared.JsonRpcVersion, -32700, err) - sendJSON(w, &response) - return - } - - c := h.codec.New(nil) - var rpcReq shared.Request - if err = c.Decode(payload, &rpcReq); err == nil { - reply, err := h.api.Execute(&rpcReq) - res := shared.NewRpcResponse(rpcReq.Id, rpcReq.Jsonrpc, reply, err) - sendJSON(w, &res) - return - } - - var reqBatch []shared.Request - if err = c.Decode(payload, &reqBatch); err == nil { - resBatch := make([]*interface{}, len(reqBatch)) - resCount := 0 - for i, rpcReq := range reqBatch { - reply, err := h.api.Execute(&rpcReq) - if rpcReq.Id != nil { // this leaves nil entries in the response batch for later removal - resBatch[i] = shared.NewRpcResponse(rpcReq.Id, rpcReq.Jsonrpc, reply, err) - resCount += 1 - } - } - // make response omitting nil entries - sendJSON(w, resBatch[:resCount]) - return - } - - // invalid request - err = fmt.Errorf("Could not decode request") - res := shared.NewRpcErrorResponse(-1, shared.JsonRpcVersion, -32600, err) - sendJSON(w, res) -} - -func sendJSON(w io.Writer, v interface{}) { - if glog.V(logger.Detail) { - if payload, err := json.MarshalIndent(v, "", "\t"); err == nil { - glog.Infof("Sending payload: %s", payload) - } - } - if err := json.NewEncoder(w).Encode(v); err != nil { - glog.V(logger.Error).Infoln("Error sending JSON:", err) - } -} - -// Stop closes all active HTTP connections and shuts down the server. -func StopHttp() { - httpServerMu.Lock() - defer httpServerMu.Unlock() - if httpServer != nil { - httpServer.Close() - httpServer = nil - } -} - -func listenHTTP(addr string, h http.Handler) (*stopServer, error) { - l, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - s := &stopServer{l: l, idle: make(map[net.Conn]struct{})} - s.Server = &http.Server{ - Addr: addr, - Handler: h, - ReadTimeout: serverReadTimeout, - WriteTimeout: serverWriteTimeout, - ConnState: s.connState, - } - go s.Serve(l) - return s, nil -} - -func (s *stopServer) connState(c net.Conn, state http.ConnState) { - s.mu.Lock() - defer s.mu.Unlock() - // Close c immediately if we're past shutdown. - if s.shutdown { - if state != http.StateClosed { - c.Close() - } - return - } - if state == http.StateIdle { - s.idle[c] = struct{}{} - } else { - delete(s.idle, c) - } -} - -func (s *stopServer) Close() { - s.mu.Lock() - defer s.mu.Unlock() - // Shut down the acceptor. No new connections can be created. - s.l.Close() - // Drop all idle connections. Non-idle connections will be - // closed by connState as soon as they become idle. - s.shutdown = true - for c := range s.idle { - glog.V(logger.Detail).Infof("closing idle connection %v", c.RemoteAddr()) - c.Close() - delete(s.idle, c) - } -} - -type httpClient struct { - address string - port uint - codec codec.ApiCoder - lastRes interface{} - lastErr error -} - -// Create a new in process client -func NewHttpClient(cfg HttpConfig, c codec.Codec) *httpClient { - return &httpClient{ - address: cfg.ListenAddress, - port: cfg.ListenPort, - codec: c.New(nil), - } -} - -func (self *httpClient) Close() { - // do nothing -} - -func (self *httpClient) Send(req interface{}) error { - var body []byte - var err error - - self.lastRes = nil - self.lastErr = nil - - if body, err = self.codec.Encode(req); err != nil { - return err - } - - httpReq, err := http.NewRequest("POST", fmt.Sprintf("%s:%d", self.address, self.port), bytes.NewBuffer(body)) - if err != nil { - return err - } - httpReq.Header.Set("Content-Type", "application/json") - - client := http.Client{} - resp, err := client.Do(httpReq) - if err != nil { - return err - } - - defer resp.Body.Close() - - if resp.Status == "200 OK" { - reply, _ := ioutil.ReadAll(resp.Body) - var rpcSuccessResponse shared.SuccessResponse - if err = self.codec.Decode(reply, &rpcSuccessResponse); err == nil { - self.lastRes = &rpcSuccessResponse - self.lastErr = err - return nil - } else { - var rpcErrorResponse shared.ErrorResponse - if err = self.codec.Decode(reply, &rpcErrorResponse); err == nil { - self.lastRes = &rpcErrorResponse - self.lastErr = err - return nil - } else { - return err - } - } - } - - return fmt.Errorf("Not implemented") -} - -func (self *httpClient) Recv() (interface{}, error) { - return self.lastRes, self.lastErr -} - -func (self *httpClient) SupportedModules() (map[string]string, error) { - var body []byte - var err error - - payload := shared.Request{ - Id: 1, - Jsonrpc: "2.0", - Method: "modules", - } - - if body, err = self.codec.Encode(payload); err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", fmt.Sprintf("%s:%d", self.address, self.port), bytes.NewBuffer(body)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - - client := http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if resp.Status == "200 OK" { - reply, _ := ioutil.ReadAll(resp.Body) - var rpcRes shared.SuccessResponse - if err = self.codec.Decode(reply, &rpcRes); err != nil { - return nil, err - } - - result := make(map[string]string) - if modules, ok := rpcRes.Result.(map[string]interface{}); ok { - for a, v := range modules { - result[a] = fmt.Sprintf("%s", v) - } - return result, nil - } - err = fmt.Errorf("Unable to parse module response - %v", rpcRes.Result) - } else { - fmt.Printf("resp.Status = %s\n", resp.Status) - fmt.Printf("err = %v\n", err) - } - - return nil, err -} diff --git a/rpc/comms/inproc.go b/rpc/comms/inproc.go deleted file mode 100644 index e8058e32bf..0000000000 --- a/rpc/comms/inproc.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2015 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 . - -package comms - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type InProcClient struct { - api shared.EthereumApi - codec codec.Codec - lastId interface{} - lastJsonrpc string - lastErr error - lastRes interface{} -} - -// Create a new in process client -func NewInProcClient(codec codec.Codec) *InProcClient { - return &InProcClient{ - codec: codec, - } -} - -func (self *InProcClient) Close() { - // do nothing -} - -// Need to setup api support -func (self *InProcClient) Initialize(offeredApi shared.EthereumApi) { - self.api = offeredApi -} - -func (self *InProcClient) Send(req interface{}) error { - if r, ok := req.(*shared.Request); ok { - self.lastId = r.Id - self.lastJsonrpc = r.Jsonrpc - self.lastRes, self.lastErr = self.api.Execute(r) - return self.lastErr - } - - return fmt.Errorf("Invalid request (%T)", req) -} - -func (self *InProcClient) Recv() (interface{}, error) { - return *shared.NewRpcResponse(self.lastId, self.lastJsonrpc, self.lastRes, self.lastErr), nil -} - -func (self *InProcClient) SupportedModules() (map[string]string, error) { - req := shared.Request{ - Id: 1, - Jsonrpc: "2.0", - Method: "modules", - } - - if res, err := self.api.Execute(&req); err == nil { - if result, ok := res.(map[string]string); ok { - return result, nil - } - } else { - return nil, err - } - - return nil, fmt.Errorf("Invalid response") -} diff --git a/rpc/comms/ipc.go b/rpc/comms/ipc.go deleted file mode 100644 index 3ba747b1de..0000000000 --- a/rpc/comms/ipc.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2015 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 . - -package comms - -import ( - "fmt" - "math/rand" - "net" - "os" - - "encoding/json" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type Stopper interface { - Stop() -} - -type InitFunc func(conn net.Conn) (Stopper, shared.EthereumApi, error) - -type IpcConfig struct { - Endpoint string -} - -type ipcClient struct { - endpoint string - c net.Conn - codec codec.Codec - coder codec.ApiCoder -} - -func (self *ipcClient) Close() { - self.coder.Close() -} - -func (self *ipcClient) Send(msg interface{}) error { - var err error - if err = self.coder.WriteResponse(msg); err != nil { - if err = self.reconnect(); err == nil { - err = self.coder.WriteResponse(msg) - } - } - return err -} - -func (self *ipcClient) Recv() (interface{}, error) { - return self.coder.ReadResponse() -} - -func (self *ipcClient) SupportedModules() (map[string]string, error) { - req := shared.Request{ - Id: 1, - Jsonrpc: "2.0", - Method: "rpc_modules", - } - - if err := self.coder.WriteResponse(req); err != nil { - return nil, err - } - - res, _ := self.coder.ReadResponse() - if sucRes, ok := res.(*shared.SuccessResponse); ok { - data, _ := json.Marshal(sucRes.Result) - modules := make(map[string]string) - if err := json.Unmarshal(data, &modules); err == nil { - return modules, nil - } - } - - // old version uses modules instead of rpc_modules, this can be removed after full migration - req.Method = "modules" - if err := self.coder.WriteResponse(req); err != nil { - return nil, err - } - - res, err := self.coder.ReadResponse() - if err != nil { - return nil, err - } - - if sucRes, ok := res.(*shared.SuccessResponse); ok { - data, _ := json.Marshal(sucRes.Result) - modules := make(map[string]string) - err = json.Unmarshal(data, &modules) - if err == nil { - return modules, nil - } - } - - return nil, fmt.Errorf("Invalid response") -} - -// Create a new IPC client, UNIX domain socket on posix, named pipe on Windows -func NewIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) { - return newIpcClient(cfg, codec) -} - -// Start IPC server -func StartIpc(cfg IpcConfig, codec codec.Codec, initializer InitFunc) error { - l, err := ipcListen(cfg) - if err != nil { - return err - } - go ipcLoop(cfg, codec, initializer, l) - return nil -} - -// CreateListener creates an listener, on Unix platforms this is a unix socket, on Windows this is a named pipe -func CreateListener(cfg IpcConfig) (net.Listener, error) { - return ipcListen(cfg) -} - -func ipcLoop(cfg IpcConfig, codec codec.Codec, initializer InitFunc, l net.Listener) { - glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint) - defer os.Remove(cfg.Endpoint) - defer l.Close() - for { - conn, err := l.Accept() - if err != nil { - glog.V(logger.Debug).Infof("accept: %v", err) - return - } - id := newIpcConnId() - go func() { - defer conn.Close() - glog.V(logger.Debug).Infof("new connection with id %06d started", id) - stopper, api, err := initializer(conn) - if err != nil { - glog.V(logger.Error).Infof("Unable to initialize IPC connection: %v", err) - return - } - defer stopper.Stop() - handle(id, conn, api, codec) - }() - } -} - -func newIpcConnId() int { - return rand.Int() % 1000000 -} diff --git a/rpc/comms/ipc_unix.go b/rpc/comms/ipc_unix.go deleted file mode 100644 index 4b839572a5..0000000000 --- a/rpc/comms/ipc_unix.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2015 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 . - -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris - -package comms - -import ( - "net" - "os" - "path/filepath" - - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/rpc/useragent" -) - -func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) { - c, err := net.DialUnix("unix", nil, &net.UnixAddr{cfg.Endpoint, "unix"}) - if err != nil { - return nil, err - } - - coder := codec.New(c) - msg := shared.Request{ - Id: 0, - Method: useragent.EnableUserAgentMethod, - Jsonrpc: shared.JsonRpcVersion, - Params: []byte("[]"), - } - - coder.WriteResponse(msg) - coder.Recv() - - return &ipcClient{cfg.Endpoint, c, codec, coder}, nil -} - -func (self *ipcClient) reconnect() error { - self.coder.Close() - c, err := net.DialUnix("unix", nil, &net.UnixAddr{self.endpoint, "unix"}) - if err == nil { - self.coder = self.codec.New(c) - - msg := shared.Request{ - Id: 0, - Method: useragent.EnableUserAgentMethod, - Jsonrpc: shared.JsonRpcVersion, - Params: []byte("[]"), - } - self.coder.WriteResponse(msg) - self.coder.Recv() - } - - return err -} - -func ipcListen(cfg IpcConfig) (net.Listener, error) { - // Ensure the IPC path exists and remove any previous leftover - if err := os.MkdirAll(filepath.Dir(cfg.Endpoint), 0751); err != nil { - return nil, err - } - os.Remove(cfg.Endpoint) - l, err := net.Listen("unix", cfg.Endpoint) - if err != nil { - return nil, err - } - os.Chmod(cfg.Endpoint, 0600) - return l, nil -} diff --git a/rpc/v2/doc.go b/rpc/doc.go similarity index 64% rename from rpc/v2/doc.go rename to rpc/doc.go index e51494adb0..e8f8f977b5 100644 --- a/rpc/v2/doc.go +++ b/rpc/doc.go @@ -99,4 +99,130 @@ Subscriptions are deleted when: - the user sends an unsubscribe request - the connection which was used to create the subscription is closed */ -package v2 +package rpc + +var ( + // Mapping between the different methods each api supports + AutoCompletion = map[string][]string{ + "admin": []string{ + "addPeer", + "datadir", + "enableUserAgent", + "exportChain", + "getContractInfo", + "httpGet", + "importChain", + "nodeInfo", + "peers", + "register", + "registerUrl", + "saveInfo", + "setGlobalRegistrar", + "setHashReg", + "setUrlHint", + "setSolc", + "sleep", + "sleepBlocks", + "startNatSpec", + "startRPC", + "stopNatSpec", + "stopRPC", + "verbosity", + }, + "db": []string{ + "getString", + "putString", + "getHex", + "putHex", + }, + "debug": []string{ + "dumpBlock", + "getBlockRlp", + "metrics", + "printBlock", + "processBlock", + "seedHash", + "setHead", + }, + "eth": []string{ + "accounts", + "blockNumber", + "call", + "contract", + "coinbase", + "compile.lll", + "compile.serpent", + "compile.solidity", + "contract", + "defaultAccount", + "defaultBlock", + "estimateGas", + "filter", + "getBalance", + "getBlock", + "getBlockTransactionCount", + "getBlockUncleCount", + "getCode", + "getNatSpec", + "getCompilers", + "gasPrice", + "getStorageAt", + "getTransaction", + "getTransactionCount", + "getTransactionFromBlock", + "getTransactionReceipt", + "getUncle", + "hashrate", + "mining", + "namereg", + "pendingTransactions", + "resend", + "sendRawTransaction", + "sendTransaction", + "sign", + "syncing", + }, + "miner": []string{ + "hashrate", + "makeDAG", + "setEtherbase", + "setExtra", + "setGasPrice", + "startAutoDAG", + "start", + "stopAutoDAG", + "stop", + }, + "net": []string{ + "peerCount", + "listening", + }, + "personal": []string{ + "listAccounts", + "newAccount", + "unlockAccount", + }, + "shh": []string{ + "post", + "newIdentity", + "hasIdentity", + "newGroup", + "addToGroup", + "filter", + }, + "txpool": []string{ + "status", + }, + "web3": []string{ + "sha3", + "version", + "fromWei", + "toWei", + "toHex", + "toAscii", + "fromAscii", + "toBigNumber", + "isAddress", + }, + } +) diff --git a/rpc/v2/errors.go b/rpc/errors.go similarity index 89% rename from rpc/v2/errors.go rename to rpc/errors.go index a06d19d84c..bc352fc45b 100644 --- a/rpc/v2/errors.go +++ b/rpc/errors.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package v2 +package rpc import "fmt" @@ -83,3 +83,15 @@ func (e *callbackError) Code() int { func (e *callbackError) Error() string { return e.message } + +// issued when a request is received after the server is issued to stop. +type shutdownError struct { +} + +func (e *shutdownError) Code() int { + return -32000 +} + +func (e *shutdownError) Error() string { + return "server is shutting down" +} diff --git a/rpc/http.go b/rpc/http.go new file mode 100644 index 0000000000..e4b25bed8b --- /dev/null +++ b/rpc/http.go @@ -0,0 +1,368 @@ +// Copyright 2015 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 . + +package rpc + +import ( + "bufio" + "fmt" + "io" + "net" + "net/http" + "strconv" + "strings" + "time" + + "errors" + "sync" + + "bytes" + "encoding/json" + "io/ioutil" + "net/url" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "gopkg.in/fatih/set.v0" +) + +const ( + httpReadDeadLine = 60 * time.Second // wait max httpReadDeadeline for next request +) + +var ( + httpServerMu sync.Mutex // prevent concurrent access to the httpListener and httpServer + httpListener net.Listener // listener for the http server + httpRPCServer *Server // the node can only start 1 HTTP RPC server instance +) + +// httpMessageStream is the glue between a HTTP connection which is message based +// and the RPC codecs that expect json requests to be read from a stream. It will +// parse HTTP messages and offer the bodies of these requests as a stream through +// the Read method. This will require full control of the connection and thus need +// a "hijacked" HTTP connection. +type httpMessageStream struct { + conn net.Conn // TCP connection + rw *bufio.ReadWriter // buffered where HTTP requests/responses are read/written from/to + currentReq *http.Request // pending request, codec can pass in a too small buffer for a single read + // we need to keep track of the current requests if it was not read at once + payloadBytesRead int64 // number of bytes which are read from the current request + allowedOrigins *set.Set // allowed CORS domains + origin string // origin of this connection/request +} + +// NewHttpMessageStream will create a new http message stream parser that can be +// used by the codes in the RPC package. It will take full control of the given +// connection and thus needs to be hijacked. It will read and write HTTP messages +// from the passed rwbuf. The allowed origins are the RPC CORS domains the user has supplied. +func NewHTTPMessageStream(c net.Conn, rwbuf *bufio.ReadWriter, initialReq *http.Request, allowdOrigins []string) *httpMessageStream { + r := &httpMessageStream{conn: c, rw: rwbuf, currentReq: initialReq, allowedOrigins: set.New()} + for _, origin := range allowdOrigins { + r.allowedOrigins.Add(origin) + } + return r +} + +// handleOptionsRequest handles the HTTP preflight requests (OPTIONS) that browsers +// make to enforce CORS rules. Only the POST method is allowed and the origin must +// be on the rpccorsdomain list the user has specified. +func (h *httpMessageStream) handleOptionsRequest(req *http.Request) error { + headers := req.Header + + if !strings.EqualFold(req.Method, "OPTIONS") { + return fmt.Errorf("preflight aborted: %s!=OPTIONS", req.Method) + } + + origin := headers.Get("Origin") + if origin == "" { + return fmt.Errorf("preflight aborted: empty origin") + } + + responseHeaders := make(http.Header) + responseHeaders.Set("Access-Control-Allow-Methods", "POST") + if h.allowedOrigins.Has(origin) || h.allowedOrigins.Has("*") { + responseHeaders.Set("Access-Control-Allow-Origin", origin) + } else { + glog.V(logger.Info).Infof("origin '%s' not allowed", origin) + } + responseHeaders.Set("Access-Control-Allow-Headers", "Content-Type") + responseHeaders.Set("Date", string(httpTimestamp(time.Now()))) + responseHeaders.Set("Content-Type", "text/plain; charset=utf-8") + responseHeaders.Set("Content-Length", "0") + responseHeaders.Set("Vary", "Origin") + + defer h.rw.Flush() + + if _, err := h.rw.WriteString("HTTP/1.1 200 OK\r\n"); err != nil { + glog.V(logger.Error).Infof("unable to write OPTIONS response: %v\n", err) + return err + } + if err := responseHeaders.Write(h.rw); err != nil { + glog.V(logger.Error).Infof("unable to write OPTIONS headers: %v\n", err) + } + if _, err := h.rw.WriteString("\r\n"); err != nil { + glog.V(logger.Error).Infof("unable to write OPTIONS response: %v\n", err) + } + + return nil +} + +// Read will read incoming HTTP requests and reads the body data from these requests +// as an endless stream of data. +func (h *httpMessageStream) Read(buf []byte) (n int, err error) { + h.conn.SetReadDeadline(time.Now().Add(httpReadDeadLine)) + for { + // if the last request was read completely try to read the next request + if h.currentReq == nil { + if h.currentReq, err = http.ReadRequest(bufio.NewReader(h.rw)); err != nil { + return 0, err + } + } + + // The "options" method is http specific and not interested for the RPC server. + // Handle it internally and wait for the next request. + if strings.EqualFold(h.currentReq.Method, "OPTIONS") { + if err = h.handleOptionsRequest(h.currentReq); err != nil { + glog.V(logger.Info).Infof("RPC/HTTP OPTIONS error: %v\n", err) + h.currentReq = nil + return 0, err + } + + // processed valid request -> reset deadline + h.conn.SetReadDeadline(time.Now().Add(httpReadDeadLine)) + h.currentReq = nil + continue + } + + if strings.EqualFold(h.currentReq.Method, "POST") { + n, err := h.currentReq.Body.Read(buf) + h.payloadBytesRead += int64(n) + + // entire payload read, read new request next time + if err == io.EOF || h.payloadBytesRead >= h.currentReq.ContentLength { + h.origin = h.currentReq.Header.Get("origin") + h.payloadBytesRead = 0 + h.currentReq.Body.Close() + h.currentReq = nil + err = nil // io.EOF is not an error + } else if err != nil { + // unable to read body + h.currentReq.Body.Close() + h.currentReq = nil + h.payloadBytesRead = 0 + } + + // partial read of body + return n, err + } + + h.currentReq = nil + return 0, fmt.Errorf("unsupported HTTP method '%s'", h.currentReq.Method) + } +} + +// Write will create a HTTP response with the given payload and send it to the peer. +func (h *httpMessageStream) Write(payload []byte) (int, error) { + defer h.rw.Flush() + + responseHeaders := make(http.Header) + responseHeaders.Set("Content-Type", "application/json") + responseHeaders.Set("Content-Length", strconv.Itoa(len(payload))) + if h.origin != "" { + responseHeaders.Set("Access-Control-Allow-Origin", h.origin) + } + + h.rw.WriteString("HTTP/1.1 200 OK\r\n") + responseHeaders.Write(h.rw) + h.rw.WriteString("\r\n") + + return h.rw.Write(payload) +} + +// Close will close the underlying TCP connection this instance has taken ownership over. +func (h *httpMessageStream) Close() error { + h.rw.Flush() + return h.conn.Close() +} + +// TimeFormat is the time format to use with time.Parse and time.Time.Format when +// parsing or generating times in HTTP headers. It is like time.RFC1123 but hard +// codes GMT as the time zone. +const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" + +// httpTimestamp formats the given t as specified in RFC1123. +func httpTimestamp(t time.Time) []byte { + const days = "SunMonTueWedThuFriSat" + const months = "JanFebMarAprMayJunJulAugSepOctNovDec" + + b := make([]byte, 0) + t = t.UTC() + yy, mm, dd := t.Date() + hh, mn, ss := t.Clock() + day := days[3 * t.Weekday():] + mon := months[3 * (mm - 1):] + + return append(b, + day[0], day[1], day[2], ',', ' ', + byte('0' + dd / 10), byte('0' + dd % 10), ' ', + mon[0], mon[1], mon[2], ' ', + byte('0' + yy / 1000), byte('0' + (yy / 100) % 10), byte('0' + (yy / 10) % 10), byte('0' + yy % 10), ' ', + byte('0' + hh / 10), byte('0' + hh % 10), ':', + byte('0' + mn / 10), byte('0' + mn % 10), ':', + byte('0' + ss / 10), byte('0' + ss % 10), ' ', + 'G', 'M', 'T') +} + +// httpConnHijacker is a http.Handler implementation that will hijack the HTTP +// connection, wraps it in a HttpMessageStream that is then wrapped in a JSON +// codec which will be served on the rpcServer. +type httpConnHijacker struct { + corsdomains []string + rpcServer *Server +} + +// ServeHTTP will hijack the connection, wraps the captured connection in a +// HttpMessageStream which is then used as codec. +func (h *httpConnHijacker) ServeHTTP(w http.ResponseWriter, req *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError) + return + } + + conn, rwbuf, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + httpRequestStream := NewHTTPMessageStream(conn, rwbuf, req, h.corsdomains) + + codec := NewJSONCodec(httpRequestStream) + go h.rpcServer.ServeCodec(codec) +} + +// StartHTTP will start the JSONRPC HTTP RPC interface when its not yet running. +func StartHTTP(address string, port int, corsdomains []string, apis []API) error { + httpServerMu.Lock() + defer httpServerMu.Unlock() + + if httpRPCServer != nil { + return fmt.Errorf("HTTP RPC interface already started on %s", httpListener.Addr()) + } + + rpcServer := NewServer() + + for _, api := range apis { + if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + } + + listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", address, port)) + if err != nil { + return err + } + + httpServer := http.Server{Handler: &httpConnHijacker{corsdomains, rpcServer}} + go httpServer.Serve(listener) + + httpListener = listener + httpRPCServer = rpcServer + + return nil +} + +// StopHTTP will stop the running HTTP interface. If it is not running an error will be returned. +func StopHTTP() error { + httpServerMu.Lock() + defer httpServerMu.Unlock() + + if httpRPCServer == nil { + return errors.New("HTTP RPC interface not started") + } + + httpListener.Close() + httpRPCServer.Stop() + + httpRPCServer = nil + httpListener = nil + + return nil +} + +// httpClient connects to a geth RPC server over HTTP. +type httpClient struct { + endpoint *url.URL // HTTP-RPC server endpoint + lastRes []byte // HTTP requests are synchronous, store last response +} + +// NewHTTPClient create a new RPC clients that connection to a geth RPC server +// over HTTP. +func NewHTTPClient(endpoint string) (*httpClient, error) { + url, err := url.Parse(endpoint) + if err != nil { + return nil, err + } + return &httpClient{endpoint: url}, nil +} + +// Send will serialize the given msg to JSON and sends it to the RPC server. +// Since HTTP is synchronous the response is stored until Recv is called. +func (client *httpClient) Send(msg interface{}) error { + var body []byte + var err error + + client.lastRes = nil + + if body, err = json.Marshal(msg); err != nil { + return err + } + + httpReq, err := http.NewRequest("POST", client.endpoint.String(), bytes.NewBuffer(body)) + if err != nil { + return err + } + httpReq.Header.Set("Content-Type", "application/json") + + httpClient := http.Client{} + resp, err := httpClient.Do(httpReq) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + client.lastRes, err = ioutil.ReadAll(resp.Body) + return err + } + + return fmt.Errorf("unable to handle request") +} + +// Recv will try to deserialize the last received response into the given msg. +func (client *httpClient) Recv(msg interface{}) error { + return json.Unmarshal(client.lastRes, &msg) +} + +// Close is not necessary for httpClient +func (client *httpClient) Close() { +} + +// SupportedModules will return the collection of offered RPC modules. +func (client *httpClient) SupportedModules() (map[string]string, error) { + return SupportedModules(client) +} diff --git a/rpc/ipc.go b/rpc/ipc.go new file mode 100644 index 0000000000..b87bfcbd7f --- /dev/null +++ b/rpc/ipc.go @@ -0,0 +1,84 @@ +// Copyright 2015 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 . + +package rpc + +import ( + "encoding/json" + "net" +) + +// CreateIPCListener creates an listener, on Unix platforms this is a unix socket, on Windows this is a named pipe +func CreateIPCListener(endpoint string) (net.Listener, error) { + return ipcListen(endpoint) +} + +// ipcClient represent an IPC RPC client. It will connect to a given endpoint and tries to communicate with a node using +// JSON serialization. +type ipcClient struct { + endpoint string + conn net.Conn + out *json.Encoder + in *json.Decoder +} + +// NewIPCClient create a new IPC client that will connect on the given endpoint. Messages are JSON encoded and encoded. +// On Unix it assumes the endpoint is the full path to a unix socket, and Windows the endpoint is an identifier for a +// named pipe. +func NewIPCClient(endpoint string) (*ipcClient, error) { + conn, err := newIPCConnection(endpoint) + if err != nil { + return nil, err + } + return &ipcClient{endpoint: endpoint, conn: conn, in: json.NewDecoder(conn), out: json.NewEncoder(conn)}, nil +} + +// Send will serialize the given message and send it to the server. +// When sending the message fails it will try to reconnect once and send the message again. +func (client *ipcClient) Send(msg interface{}) error { + if err := client.out.Encode(msg); err == nil { + return nil + } + + // retry once + client.conn.Close() + + conn, err := newIPCConnection(client.endpoint) + if err != nil { + return err + } + + client.conn = conn + client.in = json.NewDecoder(conn) + client.out = json.NewEncoder(conn) + + return client.out.Encode(msg) +} + +// Recv will read a message from the connection and tries to parse it. It assumes the received message is JSON encoded. +func (client *ipcClient) Recv(msg interface{}) error { + return client.in.Decode(&msg) +} + +// Close will close the underlying IPC connection +func (client *ipcClient) Close() { + client.conn.Close() +} + +// SupportedModules will return the collection of offered RPC modules. +func (client *ipcClient) SupportedModules() (map[string]string, error) { + return SupportedModules(client) +} diff --git a/rpc/api/web3_args.go b/rpc/ipc_unix.go similarity index 53% rename from rpc/api/web3_args.go rename to rpc/ipc_unix.go index 9e39f7130f..310286e964 100644 --- a/rpc/api/web3_args.go +++ b/rpc/ipc_unix.go @@ -14,32 +14,32 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package api +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris + +package rpc import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/rpc/shared" + "net" + "os" + "path/filepath" ) -type Sha3Args struct { - Data string +// ipcListen will create a Unix socket on the given endpoint. +func ipcListen(endpoint string) (net.Listener, error) { + // Ensure the IPC path exists and remove any previous leftover + if err := os.MkdirAll(filepath.Dir(endpoint), 0751); err != nil { + return nil, err + } + os.Remove(endpoint) + l, err := net.Listen("unix", endpoint) + if err != nil { + return nil, err + } + os.Chmod(endpoint, 0600) + return l, nil } -func (args *Sha3Args) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - argstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("data", "is not a string") - } - args.Data = argstr - return nil +// newIPCConnection will connect to a Unix socket on the given endpoint. +func newIPCConnection(endpoint string) (net.Conn, error) { + return net.DialUnix("unix", nil, &net.UnixAddr{endpoint, "unix"}) } diff --git a/rpc/comms/ipc_windows.go b/rpc/ipc_windows.go similarity index 94% rename from rpc/comms/ipc_windows.go rename to rpc/ipc_windows.go index e25fba2530..1d4672ad27 100644 --- a/rpc/comms/ipc_windows.go +++ b/rpc/ipc_windows.go @@ -16,21 +16,16 @@ // +build windows -package comms +package rpc import ( "fmt" "io" "net" - "os" "sync" "syscall" "time" "unsafe" - - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/rpc/useragent" ) var ( @@ -649,49 +644,12 @@ func timeout(addr string) PipeError { return PipeError{fmt.Sprintf("Pipe IO timed out waiting for '%s'", addr), true} } -func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) { - c, err := Dial(cfg.Endpoint) - if err != nil { - return nil, err - } - - coder := codec.New(c) - msg := shared.Request{ - Id: 0, - Method: useragent.EnableUserAgentMethod, - Jsonrpc: shared.JsonRpcVersion, - Params: []byte("[]"), - } - - coder.WriteResponse(msg) - coder.Recv() - - return &ipcClient{cfg.Endpoint, c, codec, coder}, nil +// ipcListen will create a named pipe on the given endpoint. +func ipcListen(endpoint string) (net.Listener, error) { + return Listen(endpoint) } -func (self *ipcClient) reconnect() error { - c, err := Dial(self.endpoint) - if err == nil { - self.coder = self.codec.New(c) - - req := shared.Request{ - Id: 0, - Method: useragent.EnableUserAgentMethod, - Jsonrpc: shared.JsonRpcVersion, - Params: []byte("[]"), - } - self.coder.WriteResponse(req) - self.coder.Recv() - } - return err -} - -func ipcListen(cfg IpcConfig) (net.Listener, error) { - os.Remove(cfg.Endpoint) // in case it still exists from a previous run - l, err := Listen(cfg.Endpoint) - if err != nil { - return nil, err - } - os.Chmod(cfg.Endpoint, 0600) - return l, nil +// newIPCConnection will connect to a named pipe with the given endpoint as name. +func newIPCConnection(endpoint string) (net.Conn, error) { + return Dial(endpoint) } diff --git a/rpc/javascript.go b/rpc/javascript.go new file mode 100644 index 0000000000..d147aa0458 --- /dev/null +++ b/rpc/javascript.go @@ -0,0 +1,414 @@ +// Copyright 2015 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 . + +package rpc + +var ( + // Holds geth specific RPC extends which can be used to extend web3 + WEB3Extensions = map[string]string{ + "personal": Personal_JS, + "txpool": TxPool_JS, + "admin": Admin_JS, + "db": Db_JS, + "eth": Eth_JS, + "miner": Miner_JS, + "debug": Debug_JS, + "net": Net_JS, + } +) + +const Personal_JS = ` +web3._extend({ + property: 'personal', + methods: + [ + new web3._extend.Method({ + name: 'newAccount', + call: 'personal_newAccount', + params: 1, + outputFormatter: web3._extend.utils.toAddress + }), + new web3._extend.Method({ + name: 'unlockAccount', + call: 'personal_unlockAccount', + params: 3, + }), + new web3._extend.Method({ + name: 'lockAccount', + call: 'personal_lockAccount', + params: 1 + }) + ], + properties: + [ + new web3._extend.Property({ + name: 'listAccounts', + getter: 'personal_listAccounts' + }) + ] +}); +` + +const TxPool_JS = ` +web3._extend({ + property: 'txpool', + methods: + [ + ], + properties: + [ + new web3._extend.Property({ + name: 'status', + getter: 'txpool_status' + outputFormatter: function(status) { + status.pending = web3._extend.utils.toDecimal(status.pending); + status.queued = web3._extend.utils.toDecimal(status.queued); + return status; + } + }) + ] +}); +` + +const Admin_JS = ` +web3._extend({ + property: 'admin', + methods: + [ + new web3._extend.Method({ + name: 'addPeer', + call: 'admin_addPeer', + params: 1 + }), + new web3._extend.Method({ + name: 'exportChain', + call: 'admin_exportChain', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'importChain', + call: 'admin_importChain', + params: 1 + }), + new web3._extend.Method({ + name: 'sleepBlocks', + call: 'admin_sleepBlocks', + params: 2 + }), + new web3._extend.Method({ + name: 'verbosity', + call: 'admin_verbosity', + params: 1, + inputFormatter: [web3._extend.utils.fromDecimal] + }), + new web3._extend.Method({ + name: 'setSolc', + call: 'admin_setSolc', + params: 1 + }), + new web3._extend.Method({ + name: 'startRPC', + call: 'admin_startRPC', + params: 4 + }), + new web3._extend.Method({ + name: 'stopRPC', + call: 'admin_stopRPC', + params: 0 + }), + new web3._extend.Method({ + name: 'startWS', + call: 'admin_startWS', + params: 4 + }), + new web3._extend.Method({ + name: 'stopWS', + call: 'admin_stopWS', + params: 0 + }), + new web3._extend.Method({ + name: 'setGlobalRegistrar', + call: 'admin_setGlobalRegistrar', + params: 2 + }), + new web3._extend.Method({ + name: 'setHashReg', + call: 'admin_setHashReg', + params: 2 + }), + new web3._extend.Method({ + name: 'setUrlHint', + call: 'admin_setUrlHint', + params: 2 + }), + new web3._extend.Method({ + name: 'saveInfo', + call: 'admin_saveInfo', + params: 2 + }), + new web3._extend.Method({ + name: 'register', + call: 'admin_register', + params: 3 + }), + new web3._extend.Method({ + name: 'registerUrl', + call: 'admin_registerUrl', + params: 3 + }), + new web3._extend.Method({ + name: 'startNatSpec', + call: 'admin_startNatSpec', + params: 0 + }), + new web3._extend.Method({ + name: 'stopNatSpec', + call: 'admin_stopNatSpec', + params: 0 + }), + new web3._extend.Method({ + name: 'getContractInfo', + call: 'admin_getContractInfo', + params: 1 + }), + new web3._extend.Method({ + name: 'httpGet', + call: 'admin_httpGet', + params: 2 + }) + ], + properties: + [ + new web3._extend.Property({ + name: 'nodeInfo', + getter: 'admin_nodeInfo' + }), + new web3._extend.Property({ + name: 'peers', + getter: 'admin_peers' + }), + new web3._extend.Property({ + name: 'datadir', + getter: 'admin_datadir' + }) + ] +}); +` + +const Db_JS = ` +web3._extend({ + property: 'db', + methods: + [ + ], + properties: + [ + ] +}); +` + +const Eth_JS = ` +web3._extend({ + property: 'eth', + methods: + [ + new web3._extend.Method({ + name: 'sign', + call: 'eth_sign', + params: 2, + inputFormatter: [web3._extend.utils.toAddress, null] + }), + new web3._extend.Method({ + name: 'resend', + call: 'eth_resend', + params: 3, + inputFormatter: [web3._extend.formatters.inputTransactionFormatter, web3._extend.utils.fromDecimal, web3._extend.utils.fromDecimal] + }), + new web3._extend.Method({ + name: 'getNatSpec', + call: 'eth_getNatSpec', + params: 1, + inputFormatter: [web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'signTransaction', + call: 'eth_signTransaction', + params: 1, + inputFormatter: [web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'submitTransaction', + call: 'eth_submitTransaction', + params: 1, + inputFormatter: [web3._extend.formatters.inputTransactionFormatter] + }) + ], + properties: + [ + new web3._extend.Property({ + name: 'pendingTransactions', + getter: 'eth_pendingTransactions' + }) + ] +}); +` + +const Net_JS = ` +web3._extend({ + property: 'net', + methods: + [ + new web3._extend.Method({ + name: 'addPeer', + call: 'net_addPeer', + params: 1 + }) + ], + properties: + [ + new web3._extend.Property({ + name: 'version', + getter: 'net_version' + }) + ] +}); +` + +const Debug_JS = ` +web3._extend({ + property: 'debug', + methods: + [ + new web3._extend.Method({ + name: 'printBlock', + call: 'debug_printBlock', + params: 1 + }), + new web3._extend.Method({ + name: 'getBlockRlp', + call: 'debug_getBlockRlp', + params: 1 + }), + new web3._extend.Method({ + name: 'setHead', + call: 'debug_setHead', + params: 1 + }), + new web3._extend.Method({ + name: 'processBlock', + call: 'debug_processBlock', + params: 1 + }), + new web3._extend.Method({ + name: 'seedHash', + call: 'debug_seedHash', + params: 1 + }), + new web3._extend.Method({ + name: 'dumpBlock', + call: 'debug_dumpBlock', + params: 1 + }), + new web3._extend.Method({ + name: 'metrics', + call: 'debug_metrics', + params: 1 + }) + ], + properties: + [ + ] +}); +` + +const Miner_JS = ` +web3._extend({ + property: 'miner', + methods: + [ + new web3._extend.Method({ + name: 'start', + call: 'miner_start', + params: 1 + }), + new web3._extend.Method({ + name: 'stop', + call: 'miner_stop', + params: 1 + }), + new web3._extend.Method({ + name: 'setEtherbase', + call: 'miner_setEtherbase', + params: 1, + inputFormatter: [web3._extend.formatters.formatInputInt], + outputFormatter: web3._extend.formatters.formatOutputBool + }), + new web3._extend.Method({ + name: 'setExtra', + call: 'miner_setExtra', + params: 1 + }), + new web3._extend.Method({ + name: 'setGasPrice', + call: 'miner_setGasPrice', + params: 1, + inputFormatter: [web3._extend.utils.fromDecial] + }), + new web3._extend.Method({ + name: 'startAutoDAG', + call: 'miner_startAutoDAG', + params: 0 + }), + new web3._extend.Method({ + name: 'stopAutoDAG', + call: 'miner_stopAutoDAG', + params: 0 + }), + new web3._extend.Method({ + name: 'makeDAG', + call: 'miner_makeDAG', + params: 1, + inputFormatter: [web3._extend.formatters.inputDefaultBlockNumberFormatter] + }) + ], + properties: + [ + new web3._extend.Property({ + name: 'hashrate', + getter: 'miner_hashrate', + outputFormatter: web3._extend.utils.toDecimal + }) + ] +}); +` + +const Shh_JS = ` +web3._extend({ + property: 'shh', + methods: + [ + + ], + properties: + [ + new web3._extend.Property({ + name: 'version', + getter: 'shh_version' + }) + ] +}); +` diff --git a/rpc/v2/json.go b/rpc/json.go similarity index 95% rename from rpc/v2/json.go rename to rpc/json.go index 9208e2d370..8bdb4665df 100644 --- a/rpc/v2/json.go +++ b/rpc/json.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package v2 +package rpc import ( "encoding/json" @@ -37,7 +37,7 @@ const ( ) // JSON-RPC request -type jsonRequest struct { +type JSONRequest struct { Method string `json:"method"` Version string `json:"jsonrpc"` Id *int64 `json:"id,omitempty"` @@ -45,24 +45,24 @@ type jsonRequest struct { } // JSON-RPC response -type jsonSuccessResponse struct { +type JSONSuccessResponse struct { Version string `json:"jsonrpc"` Id int64 `json:"id"` Result interface{} `json:"result,omitempty"` } // JSON-RPC error object -type jsonError struct { +type JSONError struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` } // JSON-RPC error response -type jsonErrResponse struct { +type JSONErrResponse struct { Version string `json:"jsonrpc"` Id *int64 `json:"id,omitempty"` - Error jsonError `json:"error"` + Error JSONError `json:"error"` } // JSON-RPC notification payload @@ -85,7 +85,7 @@ type jsonCodec struct { isClosed int32 d *json.Decoder e *json.Encoder - req jsonRequest + req JSONRequest rw io.ReadWriteCloser } @@ -126,7 +126,7 @@ func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) { // parseRequest will parse a single request from the given RawMessage. It will return the parsed request, an indication // if the request was a batch or an error when the request could not be parsed. func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { - var in jsonRequest + var in JSONRequest if err := json.Unmarshal(incomingMsg, &in); err != nil { return nil, false, &invalidMessageError{err.Error()} } @@ -175,7 +175,7 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { // parseBatchRequest will parse a batch request into a collection of requests from the given RawMessage, an indication // if the request was a batch or an error when the request could not be read. func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { - var in []jsonRequest + var in []JSONRequest if err := json.Unmarshal(incomingMsg, &in); err != nil { return nil, false, &invalidMessageError{err.Error()} } @@ -296,21 +296,21 @@ func parsePositionalArguments(args json.RawMessage, argTypes []reflect.Type) ([] // CreateResponse will create a JSON-RPC success response with the given id and reply as result. func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} { if isHexNum(reflect.TypeOf(reply)) { - return &jsonSuccessResponse{Version: jsonRPCVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)} + return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)} } - return &jsonSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply} + return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply} } // CreateErrorResponse will create a JSON-RPC error response with the given id and error. func (c *jsonCodec) CreateErrorResponse(id *int64, err RPCError) interface{} { - return &jsonErrResponse{Version: jsonRPCVersion, Id: id, Error: jsonError{Code: err.Code(), Message: err.Error()}} + return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}} } // CreateErrorResponseWithInfo will create a JSON-RPC error response with the given id and error. // info is optional and contains additional information about the error. When an empty string is passed it is ignored. func (c *jsonCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} { - return &jsonErrResponse{Version: jsonRPCVersion, Id: id, - Error: jsonError{Code: err.Code(), Message: err.Error(), Data: info}} + return &JSONErrResponse{Version: jsonRPCVersion, Id: id, + Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}} } // CreateNotification will create a JSON-RPC notification with the given subscription id and event as params. diff --git a/rpc/v2/json_test.go b/rpc/json_test.go similarity index 99% rename from rpc/v2/json_test.go rename to rpc/json_test.go index dc8a345d7f..39aae1f54e 100644 --- a/rpc/v2/json_test.go +++ b/rpc/json_test.go @@ -1,4 +1,4 @@ -package v2 +package rpc import ( "bufio" diff --git a/rpc/v2/server.go b/rpc/server.go similarity index 89% rename from rpc/v2/server.go rename to rpc/server.go index 4c04f04d24..0b93a4e643 100644 --- a/rpc/v2/server.go +++ b/rpc/server.go @@ -14,23 +14,37 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package v2 +package rpc import ( "fmt" "reflect" - "runtime" + "sync/atomic" + "time" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "golang.org/x/net/context" + "gopkg.in/fatih/set.v0" +) + +const ( + stopPendingRequestTimeout = 3 * time.Second // give pending requests stopPendingRequestTimeout the time to finish when the server is stopped + + DefaultIpcApis = "admin,eth,debug,miner,net,shh,txpool,personal,web3" + DefaultHttpRpcApis = "eth,net,web3" ) // NewServer will create a new server instance with no registered handlers. func NewServer() *Server { - server := &Server{services: make(serviceRegistry), subscriptions: make(subscriptionRegistry)} + server := &Server{ + services: make(serviceRegistry), + subscriptions: make(subscriptionRegistry), + codecs: set.New(), + run: 1, + } // register a default service which will provide meta information about the RPC service such as the services and // methods it offers. @@ -124,14 +138,37 @@ func (s *Server) ServeCodec(codec ServerCodec) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - for { + s.codecsMu.Lock() + if atomic.LoadInt32(&s.run) != 1 { // server stopped + s.codecsMu.Unlock() + return + } + s.codecs.Add(codec) + s.codecsMu.Unlock() + + for atomic.LoadInt32(&s.run) == 1 { reqs, batch, err := s.readRequest(codec) + if err != nil { glog.V(logger.Debug).Infof("%v\n", err) codec.Write(codec.CreateErrorResponse(nil, err)) break } + if atomic.LoadInt32(&s.run) != 1 { + err = &shutdownError{} + if batch { + resps := make([]interface{}, len(reqs)) + for i, r := range reqs { + resps[i] = codec.CreateErrorResponse(&r.id, err) + } + codec.Write(resps) + } else { + codec.Write(codec.CreateErrorResponse(&reqs[0].id, err)) + } + break + } + if batch { go s.execBatch(ctx, codec, reqs) } else { @@ -140,6 +177,22 @@ func (s *Server) ServeCodec(codec ServerCodec) { } } +// Stop will stop reading new requests, wait for stopPendingRequestTimeout to allow pending requests to finish, +// close all codecs which will cancels pending requests/subscriptions. +func (s *Server) Stop() { + if atomic.CompareAndSwapInt32(&s.run, 1, 0) { + glog.V(logger.Debug).Infoln("RPC Server shutdown initiatied") + time.AfterFunc(stopPendingRequestTimeout, func() { + s.codecsMu.Lock() + defer s.codecsMu.Unlock() + s.codecs.Each(func(c interface{}) bool { + c.(ServerCodec).Close() + return true + }) + }) + } +} + // sendNotification will create a notification from the given event by serializing member fields of the event. // It will then send the notification to the client, when it fails the codec is closed. When the event has multiple // fields an array of values is returned. diff --git a/rpc/v2/server_test.go b/rpc/server_test.go similarity index 83% rename from rpc/v2/server_test.go rename to rpc/server_test.go index f250c184f9..5b91fe42ac 100644 --- a/rpc/v2/server_test.go +++ b/rpc/server_test.go @@ -1,4 +1,20 @@ -package v2 +// Copyright 2015 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 . + +package rpc import ( "encoding/json" @@ -91,7 +107,7 @@ func (c *ServerTestCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) { c.counter += 1 if c.counter == 1 { - var req jsonRequest + var req JSONRequest json.Unmarshal(c.input, &req) return []rpcRequest{rpcRequest{id: *req.Id, isPubSub: false, service: "test", method: req.Method, params: req.Payload}}, false, nil } @@ -157,16 +173,16 @@ func (c *ServerTestCodec) ParseRequestArguments(argTypes []reflect.Type, payload } func (c *ServerTestCodec) CreateResponse(id int64, reply interface{}) interface{} { - return &jsonSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply} + return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply} } func (c *ServerTestCodec) CreateErrorResponse(id *int64, err RPCError) interface{} { - return &jsonErrResponse{Version: jsonRPCVersion, Id: id, Error: jsonError{Code: err.Code(), Message: err.Error()}} + return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}} } func (c *ServerTestCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} { - return &jsonErrResponse{Version: jsonRPCVersion, Id: id, - Error: jsonError{Code: err.Code(), Message: err.Error(), Data: info}} + return &JSONErrResponse{Version: jsonRPCVersion, Id: id, + Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}} } func (c *ServerTestCodec) CreateNotification(subid string, event interface{}) interface{} { @@ -203,7 +219,7 @@ func TestServerMethodExecution(t *testing.T) { } id := int64(12345) - req := jsonRequest{ + req := JSONRequest{ Method: "echo", Version: "2.0", Id: &id, @@ -233,7 +249,7 @@ func TestServerMethodWithCtx(t *testing.T) { } id := int64(12345) - req := jsonRequest{ + req := JSONRequest{ Method: "echoWithCtx", Version: "2.0", Id: &id, diff --git a/rpc/shared/errors.go b/rpc/shared/errors.go deleted file mode 100644 index d5a7011f91..0000000000 --- a/rpc/shared/errors.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2015 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 . - -package shared - -import "fmt" - -type InvalidTypeError struct { - method string - msg string -} - -func (e *InvalidTypeError) Error() string { - return fmt.Sprintf("invalid type on field %s: %s", e.method, e.msg) -} - -func NewInvalidTypeError(method, msg string) *InvalidTypeError { - return &InvalidTypeError{ - method: method, - msg: msg, - } -} - -type InsufficientParamsError struct { - have int - want int -} - -func (e *InsufficientParamsError) Error() string { - return fmt.Sprintf("insufficient params, want %d have %d", e.want, e.have) -} - -func NewInsufficientParamsError(have int, want int) *InsufficientParamsError { - return &InsufficientParamsError{ - have: have, - want: want, - } -} - -type NotImplementedError struct { - Method string -} - -func (e *NotImplementedError) Error() string { - return fmt.Sprintf("%s method not implemented", e.Method) -} - -func NewNotImplementedError(method string) *NotImplementedError { - return &NotImplementedError{ - Method: method, - } -} - -type NotReadyError struct { - Resource string -} - -func (e *NotReadyError) Error() string { - return fmt.Sprintf("%s not ready", e.Resource) -} - -func NewNotReadyError(resource string) *NotReadyError { - return &NotReadyError{ - Resource: resource, - } -} - -type DecodeParamError struct { - err string -} - -func (e *DecodeParamError) Error() string { - return fmt.Sprintf("could not decode, %s", e.err) - -} - -func NewDecodeParamError(errstr string) error { - return &DecodeParamError{ - err: errstr, - } -} - -type ValidationError struct { - ParamName string - msg string -} - -func (e *ValidationError) Error() string { - return fmt.Sprintf("%s not valid, %s", e.ParamName, e.msg) -} - -func NewValidationError(param string, msg string) error { - return &ValidationError{ - ParamName: param, - msg: msg, - } -} - -type NotAvailableError struct { - Method string - Reason string -} - -func (e *NotAvailableError) Error() string { - return fmt.Sprintf("%s method not available: %s", e.Method, e.Reason) -} - -func NewNotAvailableError(method string, reason string) *NotAvailableError { - return &NotAvailableError{ - Method: method, - Reason: reason, - } -} diff --git a/rpc/shared/types.go b/rpc/shared/types.go deleted file mode 100644 index db328234d2..0000000000 --- a/rpc/shared/types.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2015 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 . - -package shared - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" -) - -// Ethereum RPC API interface -type EthereumApi interface { - // API identifier - Name() string - - // API version - ApiVersion() string - - // Execute the given request and returns the response or an error - Execute(*Request) (interface{}, error) - - // List of supported RCP methods this API provides - Methods() []string -} - -// RPC request -type Request struct { - Id interface{} `json:"id"` - Jsonrpc string `json:"jsonrpc"` - Method string `json:"method"` - Params json.RawMessage `json:"params"` -} - -// RPC response -type Response struct { - Id interface{} `json:"id"` - Jsonrpc string `json:"jsonrpc"` -} - -// RPC success response -type SuccessResponse struct { - Id interface{} `json:"id"` - Jsonrpc string `json:"jsonrpc"` - Result interface{} `json:"result"` -} - -// RPC error response -type ErrorResponse struct { - Id interface{} `json:"id"` - Jsonrpc string `json:"jsonrpc"` - Error *ErrorObject `json:"error"` -} - -// RPC error response details -type ErrorObject struct { - Code int `json:"code"` - Message string `json:"message"` - // Data interface{} `json:"data"` -} - -// Create RPC error response, this allows for custom error codes -func NewRpcErrorResponse(id interface{}, jsonrpcver string, errCode int, err error) *ErrorResponse { - jsonerr := &ErrorObject{errCode, err.Error()} - response := ErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: jsonerr} - - glog.V(logger.Detail).Infof("Generated error response: %s", response) - return &response -} - -// Create RPC response -func NewRpcResponse(id interface{}, jsonrpcver string, reply interface{}, err error) *interface{} { - var response interface{} - - switch err.(type) { - case nil: - response = &SuccessResponse{Jsonrpc: jsonrpcver, Id: id, Result: reply} - case *NotImplementedError: - jsonerr := &ErrorObject{-32601, err.Error()} - response = &ErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: jsonerr} - case *NotReadyError: - jsonerr := &ErrorObject{-32000, err.Error()} - response = &ErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: jsonerr} - case *DecodeParamError, *InsufficientParamsError, *ValidationError, *InvalidTypeError: - jsonerr := &ErrorObject{-32602, err.Error()} - response = &ErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: jsonerr} - default: - jsonerr := &ErrorObject{-32603, err.Error()} - response = &ErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: jsonerr} - } - - glog.V(logger.Detail).Infof("Generated response: %T %s", response, response) - return &response -} diff --git a/rpc/shared/utils.go b/rpc/shared/utils.go deleted file mode 100644 index b13e9eb1b4..0000000000 --- a/rpc/shared/utils.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2015 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 . - -package shared - -import "strings" - -const ( - AdminApiName = "admin" - EthApiName = "eth" - DbApiName = "db" - DebugApiName = "debug" - MergedApiName = "merged" - MinerApiName = "miner" - NetApiName = "net" - ShhApiName = "shh" - TxPoolApiName = "txpool" - PersonalApiName = "personal" - Web3ApiName = "web3" - - JsonRpcVersion = "2.0" -) - -var ( - // All API's - AllApis = strings.Join([]string{ - AdminApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName, - ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName, - }, ",") -) diff --git a/rpc/v2/types.go b/rpc/types.go similarity index 96% rename from rpc/v2/types.go rename to rpc/types.go index 8e638726fe..02295a022e 100644 --- a/rpc/v2/types.go +++ b/rpc/types.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package v2 +package rpc import ( "fmt" @@ -25,6 +25,7 @@ import ( "sync" "github.com/ethereum/go-ethereum/event" + "gopkg.in/fatih/set.v0" ) // API describes the set of methods offered over the RPC interface @@ -75,6 +76,10 @@ type Server struct { services serviceRegistry muSubcriptions sync.Mutex // protects subscriptions subscriptions subscriptionRegistry + + run int32 + codecsMu sync.Mutex + codecs *set.Set } // rpcRequest represents a raw incoming RPC request @@ -350,3 +355,14 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error { func (bn *BlockNumber) Int64() int64 { return (int64)(*bn) } + +// Client defines the interface for go client that wants to connect to a geth RPC endpoint +type Client interface { + // SupportedModules returns the collection of API's the server offers + SupportedModules() (map[string]string, error) + + Send(req interface{}) error + Recv(msg interface{}) error + + Close() +} diff --git a/rpc/v2/types_test.go b/rpc/types_test.go similarity index 62% rename from rpc/v2/types_test.go rename to rpc/types_test.go index f73a2369ee..c2c5c6db6c 100644 --- a/rpc/v2/types_test.go +++ b/rpc/types_test.go @@ -1,4 +1,20 @@ -package v2 +// Copyright 2015 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 . + +package rpc import ( "bytes" diff --git a/rpc/useragent/agent.go b/rpc/useragent/agent.go deleted file mode 100644 index df0739e659..0000000000 --- a/rpc/useragent/agent.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015 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 . - -// package user agent provides frontends and agents which can interact with the user -package useragent - -var ( - AskPasswordMethod = "agent_askPassword" - ConfirmTransactionMethod = "agent_confirmTransaction" - EnableUserAgentMethod = "admin_enableUserAgent" -) diff --git a/rpc/useragent/remote_frontend.go b/rpc/useragent/remote_frontend.go deleted file mode 100644 index 944ab287a0..0000000000 --- a/rpc/useragent/remote_frontend.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2015 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 . - -package useragent - -import ( - "encoding/json" - "fmt" - "net" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -// remoteFrontend implements xeth.Frontend and will communicate with an external -// user agent over a connection -type RemoteFrontend struct { - enabled bool - mgr *accounts.Manager - d *json.Decoder - e *json.Encoder - n int -} - -// NewRemoteFrontend creates a new frontend which will interact with an user agent -// over the given connection -func NewRemoteFrontend(conn net.Conn, mgr *accounts.Manager) *RemoteFrontend { - return &RemoteFrontend{false, mgr, json.NewDecoder(conn), json.NewEncoder(conn), 0} -} - -// Enable will enable user interaction -func (fe *RemoteFrontend) Enable() { - fe.enabled = true -} - -func (fe *RemoteFrontend) AskPassword() (string, bool) { - if !fe.enabled { - return "", false - } - - err := fe.send(AskPasswordMethod) - if err != nil { - glog.V(logger.Error).Infof("Unable to send password request to agent - %v\n", err) - return "", false - } - - passwdRes, err := fe.recv() - if err != nil { - glog.V(logger.Error).Infof("Unable to recv password response from agent - %v\n", err) - return "", false - } - - if passwd, ok := passwdRes.Result.(string); ok { - return passwd, true - } - - return "", false - -} - -// UnlockAccount asks the user agent for the user password and tries to unlock the account. -// It will try 3 attempts before giving up. -func (fe *RemoteFrontend) UnlockAccount(address []byte) bool { - if !fe.enabled { - return false - } - - err := fe.send(AskPasswordMethod, common.Bytes2Hex(address)) - if err != nil { - glog.V(logger.Error).Infof("Unable to send password request to agent - %v\n", err) - return false - } - - passwdRes, err := fe.recv() - if err != nil { - glog.V(logger.Error).Infof("Unable to recv password response from agent - %v\n", err) - return false - } - - if passwd, ok := passwdRes.Result.(string); ok { - err = fe.mgr.Unlock(common.BytesToAddress(address), passwd) - } - - if err == nil { - return true - } - - glog.V(logger.Debug).Infoln("3 invalid account unlock attempts") - return false -} - -// ConfirmTransaction asks the user for approval -func (fe *RemoteFrontend) ConfirmTransaction(tx string) bool { - if !fe.enabled { - return true // backwards compatibility - } - - err := fe.send(ConfirmTransactionMethod, tx) - if err != nil { - glog.V(logger.Error).Infof("Unable to send tx confirmation request to agent - %v\n", err) - return false - } - - confirmResponse, err := fe.recv() - if err != nil { - glog.V(logger.Error).Infof("Unable to recv tx confirmation response from agent - %v\n", err) - return false - } - - if confirmed, ok := confirmResponse.Result.(bool); ok { - return confirmed - } - - return false -} - -// send request to the agent -func (fe *RemoteFrontend) send(method string, params ...interface{}) error { - fe.n += 1 - - p, err := json.Marshal(params) - if err != nil { - glog.V(logger.Info).Infof("Unable to send agent request %v\n", err) - return err - } - - req := shared.Request{ - Method: method, - Jsonrpc: shared.JsonRpcVersion, - Id: fe.n, - Params: p, - } - - return fe.e.Encode(&req) -} - -// recv user response from agent -func (fe *RemoteFrontend) recv() (*shared.SuccessResponse, error) { - var res json.RawMessage - if err := fe.d.Decode(&res); err != nil { - return nil, err - } - - var response shared.SuccessResponse - if err := json.Unmarshal(res, &response); err == nil { - return &response, nil - } - - return nil, fmt.Errorf("Invalid user agent response") -} diff --git a/rpc/v2/utils.go b/rpc/utils.go similarity index 87% rename from rpc/v2/utils.go rename to rpc/utils.go index ca37924a3c..25321c543c 100644 --- a/rpc/v2/utils.go +++ b/rpc/utils.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package v2 +package rpc import ( "crypto/rand" @@ -25,6 +25,8 @@ import ( "unicode" "unicode/utf8" + "fmt" + "golang.org/x/net/context" ) @@ -212,3 +214,33 @@ func newSubscriptionId() (string, error) { } return "0x" + hex.EncodeToString(subid[:]), nil } + +// SupportedModules returns the collection of API's that the RPC server offers +// on which the given client connects. +func SupportedModules(client Client) (map[string]string, error) { + req := map[string]interface{}{ + "id": 1, + "method": "rpc_modules", + } + + if err := client.Send(req); err != nil { + return nil, err + } + + var response map[string]interface{} + if err := client.Recv(&response); err != nil { + return nil, err + } + + if payload, ok := response["result"]; ok { + mods := make(map[string]string) + if modules, ok := payload.(map[string]interface{}); ok { + for m, v := range modules { + mods[m] = fmt.Sprintf("%s", v) + } + return mods, nil + } + } + + return nil, fmt.Errorf("unable to retrieve modules") +} diff --git a/rpc/websocket.go b/rpc/websocket.go new file mode 100644 index 0000000000..b5bcbf4f66 --- /dev/null +++ b/rpc/websocket.go @@ -0,0 +1,235 @@ +// Copyright 2015 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 . + +package rpc + +import ( + "errors" + "fmt" + "net" + "net/http" + "sync" + + "os" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "golang.org/x/net/websocket" + "gopkg.in/fatih/set.v0" +) + +var ( + wsServerMu sync.Mutex + wsRPCServer *Server + wsListener net.Listener +) + +// wsReaderWriterCloser reads and write payloads from and to a websocket connection. +type wsReaderWriterCloser struct { + c *websocket.Conn +} + +// Read will read incoming payload data into p. +func (rw *wsReaderWriterCloser) Read(p []byte) (int, error) { + return rw.c.Read(p) +} + +// Write writes p to the websocket. +func (rw *wsReaderWriterCloser) Write(p []byte) (int, error) { + return rw.c.Write(p) +} + +// Close closes the websocket connection. +func (rw *wsReaderWriterCloser) Close() error { + return rw.c.Close() +} + +// wsHandler accepts a websocket connection and handles incoming RPC requests. +// Will return when the websocket connection is closed, either by the client or +// server. +func wsHandler(conn *websocket.Conn) { + rwc := &wsReaderWriterCloser{conn} + wsRPCServer.ServeCodec(NewJSONCodec(rwc)) +} + +// wsHandshakeValidator returns a handler that verifies the origin during the +// websocket upgrade process. When a '*' is specified as an allowed origins all +// connections are accepted. +func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http.Request) error { + origins := set.New() + allowAllOrigins := false + + for _, origin := range allowedOrigins { + if origin == "*" { + allowAllOrigins = true + } + if origin != "" { + origins.Add(origin) + } + } + + // allow localhost if no allowedOrigins are specified + if len(origins.List()) == 0 { + origins.Add("http://localhost") + if hostname, err := os.Hostname(); err == nil { + origins.Add("http://" + hostname) + } + } + + glog.V(logger.Debug).Infof("Allowed origin(s) for WS RPC interface %v\n", origins.List()) + + f := func(cfg *websocket.Config, req *http.Request) error { + origin := req.Header.Get("Origin") + if allowAllOrigins || origins.Has(origin) { + return nil + } + glog.V(logger.Debug).Infof("origin '%s' not allowed on WS-RPC interface\n", origin) + return fmt.Errorf("origin %s not allowed", origin) + } + + return f +} + +// StartWS will start a websocket RPC server on the given address and port. +func StartWS(address string, port int, corsdomains []string, apis []API) error { + wsServerMu.Lock() + defer wsServerMu.Unlock() + + if wsRPCServer != nil { + return fmt.Errorf("WS RPC interface already started on %s", wsListener.Addr()) + } + + rpcServer := NewServer() + for _, api := range apis { + if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + } + + listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", address, port)) + if err != nil { + return err + } + + wsServer := websocket.Server{Handshake: wsHandshakeValidator(corsdomains), Handler: wsHandler} + wsHTTPServer := http.Server{Handler: wsServer} + + go wsHTTPServer.Serve(listener) + + wsListener = listener + wsRPCServer = rpcServer + + return nil +} + +// StopWS stops the running websocket RPC server. +func StopWS() error { + wsServerMu.Lock() + defer wsServerMu.Unlock() + + if wsRPCServer == nil { + return errors.New("HTTP RPC interface not started") + } + + wsListener.Close() + wsRPCServer.Stop() + + wsRPCServer = nil + wsListener = nil + + return nil +} + +// wsClient represents a RPC client that communicates over websockets with a +// RPC server. +type wsClient struct { + endpoint string + connMu sync.Mutex + conn *websocket.Conn +} + +// NewWSClientj creates a new RPC client that communicates with a RPC server +// that is listening on the given endpoint using JSON encoding. +func NewWSClient(endpoint string) (*wsClient, error) { + return &wsClient{endpoint: endpoint}, nil +} + +// connection will return a websocket connection to the RPC server. It will +// (re)connect when necessary. +func (client *wsClient) connection() (*websocket.Conn, error) { + if client.conn != nil { + return client.conn, nil + } + + origin, err := os.Hostname() + if err != nil { + return nil, err + } + + origin = "http://" + origin + client.conn, err = websocket.Dial(client.endpoint, "", origin) + + return client.conn, err +} + +// SupportedModules is the collection of modules the RPC server offers. +func (client *wsClient) SupportedModules() (map[string]string, error) { + return SupportedModules(client) +} + +// Send writes the JSON serialized msg to the websocket. It will create a new +// websocket connection to the server if the client is currently not connected. +func (client *wsClient) Send(msg interface{}) (err error) { + client.connMu.Lock() + defer client.connMu.Unlock() + + var conn *websocket.Conn + if conn, err = client.connection(); err == nil { + if err = websocket.JSON.Send(conn, msg); err != nil { + client.conn.Close() + client.conn = nil + } + } + + return err +} + +// Recv reads a JSON message from the websocket and unmarshals it into msg. +func (client *wsClient) Recv(msg interface{}) (err error) { + client.connMu.Lock() + defer client.connMu.Unlock() + + var conn *websocket.Conn + if conn, err = client.connection(); err == nil { + if err = websocket.JSON.Receive(conn, msg); err != nil { + client.conn.Close() + client.conn = nil + } + } + return +} + +// Close closes the underlaying websocket connection. +func (client *wsClient) Close() { + client.connMu.Lock() + defer client.connMu.Unlock() + + if client.conn != nil { + client.conn.Close() + client.conn = nil + } + +} diff --git a/rpc/xeth.go b/rpc/xeth.go deleted file mode 100644 index 9527a96c00..0000000000 --- a/rpc/xeth.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2015 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 . - -// Package rpc implements the Ethereum JSON-RPC API. -package rpc - -import ( - "encoding/json" - "fmt" - "reflect" - "sync/atomic" - - "github.com/ethereum/go-ethereum/rpc/comms" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -// Xeth is a native API interface to a remote node. -type Xeth struct { - client comms.EthereumClient - reqId uint32 -} - -// NewXeth constructs a new native API interface to a remote node. -func NewXeth(client comms.EthereumClient) *Xeth { - return &Xeth{ - client: client, - } -} - -// Call invokes a method with the given parameters are the remote node. -func (self *Xeth) Call(method string, params []interface{}) (map[string]interface{}, error) { - // Assemble the json RPC request - data, err := json.Marshal(params) - if err != nil { - return nil, err - } - req := &shared.Request{ - Id: atomic.AddUint32(&self.reqId, 1), - Jsonrpc: "2.0", - Method: method, - Params: data, - } - // Send the request over and retrieve the response - if err := self.client.Send(req); err != nil { - return nil, err - } - res, err := self.client.Recv() - if err != nil { - return nil, err - } - // Ensure the response is valid, and extract the results - success, isSuccessResponse := res.(*shared.SuccessResponse) - failure, isFailureResponse := res.(*shared.ErrorResponse) - switch { - case isFailureResponse: - return nil, fmt.Errorf("Method invocation failed: %v", failure.Error) - - case isSuccessResponse: - return success.Result.(map[string]interface{}), nil - - default: - return nil, fmt.Errorf("Invalid response type: %v", reflect.TypeOf(res)) - } -} diff --git a/whisper/api.go b/whisper/api.go index 16f8bd3291..575b9bc894 100644 --- a/whisper/api.go +++ b/whisper/api.go @@ -24,7 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) // PublicWhisperAPI provides the whisper RPC service. diff --git a/whisper/whisper.go b/whisper/whisper.go index e99950b760..e5f686e43f 100644 --- a/whisper/whisper.go +++ b/whisper/whisper.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" "gopkg.in/fatih/set.v0" ) diff --git a/xeth/frontend.go b/xeth/frontend.go deleted file mode 100644 index 70d99ebc44..0000000000 --- a/xeth/frontend.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2015 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 . - -package xeth - -// Frontend should be implemented by users of XEth. Its methods are -// called whenever XEth makes a decision that requires user input. -type Frontend interface { - // AskPassword is called when a new account is created or updated - AskPassword() (string, bool) - - // UnlockAccount is called when a transaction needs to be signed - // but the key corresponding to the transaction's sender is - // locked. - // - // It should unlock the account with the given address and return - // true if unlocking succeeded. - UnlockAccount(address []byte) bool - - // This is called for all transactions inititated through - // Transact. It should prompt the user to confirm the transaction - // and return true if the transaction was acknowledged. - // - // ConfirmTransaction is not used for Call transactions - // because they cannot change any state. - ConfirmTransaction(tx string) bool -} - -// dummyFrontend is a non-interactive frontend that allows all -// transactions but cannot not unlock any keys. -type dummyFrontend struct{} - -func (dummyFrontend) AskPassword() (string, bool) { return "", false } -func (dummyFrontend) UnlockAccount([]byte) bool { return false } -func (dummyFrontend) ConfirmTransaction(string) bool { return true } diff --git a/xeth/state.go b/xeth/state.go deleted file mode 100644 index 7daccb5255..0000000000 --- a/xeth/state.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2015 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 . - -package xeth - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" -) - -type State struct { - xeth *XEth - state *state.StateDB -} - -func NewState(xeth *XEth, statedb *state.StateDB) *State { - return &State{xeth, statedb} -} - -func (self *State) State() *state.StateDB { - return self.state -} - -func (self *State) Get(addr string) *Object { - return &Object{self.state.GetStateObject(common.HexToAddress(addr))} -} - -func (self *State) SafeGet(addr string) *Object { - return &Object{self.safeGet(addr)} -} - -func (self *State) safeGet(addr string) *state.StateObject { - object := self.state.GetStateObject(common.HexToAddress(addr)) - if object == nil { - object = state.NewStateObject(common.HexToAddress(addr), self.xeth.EthereumService().ChainDb()) - } - return object -} diff --git a/xeth/types.go b/xeth/types.go deleted file mode 100644 index 090115b7ed..0000000000 --- a/xeth/types.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2014 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 . - -package xeth - -import ( - "bytes" - "fmt" - "math/big" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" -) - -type Object struct { - *state.StateObject -} - -func NewObject(state *state.StateObject) *Object { - return &Object{state} -} - -func (self *Object) StorageString(str string) []byte { - if common.IsHex(str) { - return self.storage(common.Hex2Bytes(str[2:])) - } else { - return self.storage(common.RightPadBytes([]byte(str), 32)) - } -} - -func (self *Object) storage(addr []byte) []byte { - return self.StateObject.GetState(common.BytesToHash(addr)).Bytes() -} - -func (self *Object) Storage() (storage map[string]string) { - storage = make(map[string]string) - - it := self.StateObject.Trie().Iterator() - for it.Next() { - var data []byte - rlp.Decode(bytes.NewReader(it.Value), &data) - storage[common.ToHex(self.Trie().GetKey(it.Key))] = common.ToHex(data) - } - - return -} - -// Block interface exposed to QML -type Block struct { - //Transactions string `json:"transactions"` - ref *types.Block - Size string `json:"size"` - Number int `json:"number"` - Hash string `json:"hash"` - Transactions *common.List `json:"transactions"` - Uncles *common.List `json:"uncles"` - Time *big.Int `json:"time"` - Coinbase string `json:"coinbase"` - Name string `json:"name"` - GasLimit string `json:"gasLimit"` - GasUsed string `json:"gasUsed"` - PrevHash string `json:"prevHash"` - Bloom string `json:"bloom"` - Raw string `json:"raw"` -} - -// Creates a new QML Block from a chain block -func NewBlock(block *types.Block) *Block { - if block == nil { - return &Block{} - } - - ptxs := make([]*Transaction, len(block.Transactions())) - /* - for i, tx := range block.Transactions() { - ptxs[i] = NewTx(tx) - } - */ - txlist := common.NewList(ptxs) - - puncles := make([]*Block, len(block.Uncles())) - /* - for i, uncle := range block.Uncles() { - puncles[i] = NewBlock(types.NewBlockWithHeader(uncle)) - } - */ - ulist := common.NewList(puncles) - - return &Block{ - ref: block, Size: block.Size().String(), - Number: int(block.NumberU64()), GasUsed: block.GasUsed().String(), - GasLimit: block.GasLimit().String(), Hash: block.Hash().Hex(), - Transactions: txlist, Uncles: ulist, - Time: block.Time(), - Coinbase: block.Coinbase().Hex(), - PrevHash: block.ParentHash().Hex(), - Bloom: common.ToHex(block.Bloom().Bytes()), - Raw: block.String(), - } -} - -func (self *Block) ToString() string { - if self.ref != nil { - return self.ref.String() - } - - return "" -} - -func (self *Block) GetTransaction(hash string) *Transaction { - tx := self.ref.Transaction(common.HexToHash(hash)) - if tx == nil { - return nil - } - - return NewTx(tx) -} - -type Transaction struct { - ref *types.Transaction - - Value string `json:"value"` - Gas string `json:"gas"` - GasPrice string `json:"gasPrice"` - Hash string `json:"hash"` - Address string `json:"address"` - Sender string `json:"sender"` - RawData string `json:"rawData"` - Data string `json:"data"` - Contract bool `json:"isContract"` - CreatesContract bool `json:"createsContract"` - Confirmations int `json:"confirmations"` -} - -func NewTx(tx *types.Transaction) *Transaction { - sender, err := tx.From() - if err != nil { - return nil - } - hash := tx.Hash().Hex() - - var receiver string - if to := tx.To(); to != nil { - receiver = to.Hex() - } else { - from, _ := tx.From() - receiver = crypto.CreateAddress(from, tx.Nonce()).Hex() - } - createsContract := core.MessageCreatesContract(tx) - - var data string - if createsContract { - data = strings.Join(core.Disassemble(tx.Data()), "\n") - } else { - data = common.ToHex(tx.Data()) - } - - return &Transaction{ref: tx, Hash: hash, Value: common.CurrencyToString(tx.Value()), Address: receiver, Contract: createsContract, Gas: tx.Gas().String(), GasPrice: tx.GasPrice().String(), Data: data, Sender: sender.Hex(), CreatesContract: createsContract, RawData: common.ToHex(tx.Data())} -} - -func (self *Transaction) ToString() string { - return self.ref.String() -} - -type PReceipt struct { - CreatedContract bool `json:"createdContract"` - Address string `json:"address"` - Hash string `json:"hash"` - Sender string `json:"sender"` -} - -func NewPReciept(contractCreation bool, creationAddress, hash, address []byte) *PReceipt { - return &PReceipt{ - contractCreation, - common.ToHex(creationAddress), - common.ToHex(hash), - common.ToHex(address), - } -} - -// Peer interface exposed to QML - -type Peer struct { - ref *p2p.Peer - Ip string `json:"ip"` - Version string `json:"version"` - Caps string `json:"caps"` -} - -func NewPeer(peer *p2p.Peer) *Peer { - var caps []string - for _, cap := range peer.Caps() { - caps = append(caps, fmt.Sprintf("%s/%d", cap.Name, cap.Version)) - } - - return &Peer{ - ref: peer, - Ip: fmt.Sprintf("%v", peer.RemoteAddr()), - Version: fmt.Sprintf("%v", peer.ID()), - Caps: fmt.Sprintf("%v", caps), - } -} - -type Receipt struct { - CreatedContract bool `json:"createdContract"` - Address string `json:"address"` - Hash string `json:"hash"` - Sender string `json:"sender"` -} - -func NewReciept(contractCreation bool, creationAddress, hash, address []byte) *Receipt { - return &Receipt{ - contractCreation, - common.ToHex(creationAddress), - common.ToHex(hash), - common.ToHex(address), - } -} diff --git a/xeth/whisper.go b/xeth/whisper.go deleted file mode 100644 index e7130978f2..0000000000 --- a/xeth/whisper.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2015 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 . - -// Contains the external API to the whisper sub-protocol. - -package xeth - -import ( - "fmt" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/whisper" -) - -var qlogger = logger.NewLogger("XSHH") - -// Whisper represents the API wrapper around the internal whisper implementation. -type Whisper struct { - *whisper.Whisper -} - -// NewWhisper wraps an internal whisper client into an external API version. -func NewWhisper(w *whisper.Whisper) *Whisper { - return &Whisper{w} -} - -// NewIdentity generates a new cryptographic identity for the client, and injects -// it into the known identities for message decryption. -func (self *Whisper) NewIdentity() string { - identity := self.Whisper.NewIdentity() - return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)) -} - -// HasIdentity checks if the the whisper node is configured with the private key -// of the specified public pair. -func (self *Whisper) HasIdentity(key string) bool { - return self.Whisper.HasIdentity(crypto.ToECDSAPub(common.FromHex(key))) -} - -// Post injects a message into the whisper network for distribution. -func (self *Whisper) Post(payload string, to, from string, topics []string, priority, ttl uint32) error { - // Decode the topic strings - topicsDecoded := make([][]byte, len(topics)) - for i, topic := range topics { - topicsDecoded[i] = common.FromHex(topic) - } - // Construct the whisper message and transmission options - message := whisper.NewMessage(common.FromHex(payload)) - options := whisper.Options{ - To: crypto.ToECDSAPub(common.FromHex(to)), - TTL: time.Duration(ttl) * time.Second, - Topics: whisper.NewTopics(topicsDecoded...), - } - if len(from) != 0 { - if key := self.Whisper.GetIdentity(crypto.ToECDSAPub(common.FromHex(from))); key != nil { - options.From = key - } else { - return fmt.Errorf("unknown identity to send from: %s", from) - } - } - // Wrap and send the message - pow := time.Duration(priority) * time.Millisecond - envelope, err := message.Wrap(pow, options) - if err != nil { - return err - } - if err := self.Whisper.Send(envelope); err != nil { - return err - } - return nil -} - -// Watch installs a new message handler to run in case a matching packet arrives -// from the whisper network. -func (self *Whisper) Watch(to, from string, topics [][]string, fn func(WhisperMessage)) int { - // Decode the topic strings - topicsDecoded := make([][][]byte, len(topics)) - for i, condition := range topics { - topicsDecoded[i] = make([][]byte, len(condition)) - for j, topic := range condition { - topicsDecoded[i][j] = common.FromHex(topic) - } - } - // Assemble and inject the filter into the whisper client - filter := whisper.Filter{ - To: crypto.ToECDSAPub(common.FromHex(to)), - From: crypto.ToECDSAPub(common.FromHex(from)), - Topics: whisper.NewFilterTopics(topicsDecoded...), - } - filter.Fn = func(message *whisper.Message) { - fn(NewWhisperMessage(message)) - } - return self.Whisper.Watch(filter) -} - -// Messages retrieves all the currently pooled messages matching a filter id. -func (self *Whisper) Messages(id int) []WhisperMessage { - pool := self.Whisper.Messages(id) - - messages := make([]WhisperMessage, len(pool)) - for i, message := range pool { - messages[i] = NewWhisperMessage(message) - } - return messages -} diff --git a/xeth/whisper_filter.go b/xeth/whisper_filter.go deleted file mode 100644 index fdf5cebae1..0000000000 --- a/xeth/whisper_filter.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2015 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 . - -// Contains the external API side message filter for watching, pooling and polling -// matched whisper messages, also serializing data access to avoid duplications. - -package xeth - -import ( - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" -) - -// whisperFilter is the message cache matching a specific filter, accumulating -// inbound messages until the are requested by the client. -type whisperFilter struct { - id int // Filter identifier for old message retrieval - ref *Whisper // Whisper reference for old message retrieval - - cache []WhisperMessage // Cache of messages not yet polled - skip map[common.Hash]struct{} // List of retrieved messages to avoid duplication - update time.Time // Time of the last message query - - lock sync.RWMutex // Lock protecting the filter internals -} - -// newWhisperFilter creates a new serialized, poll based whisper topic filter. -func newWhisperFilter(id int, ref *Whisper) *whisperFilter { - return &whisperFilter{ - id: id, - ref: ref, - - update: time.Now(), - skip: make(map[common.Hash]struct{}), - } -} - -// messages retrieves all the cached messages from the entire pool matching the -// filter, resetting the filter's change buffer. -func (w *whisperFilter) messages() []WhisperMessage { - w.lock.Lock() - defer w.lock.Unlock() - - w.cache = nil - w.update = time.Now() - - w.skip = make(map[common.Hash]struct{}) - messages := w.ref.Messages(w.id) - for _, message := range messages { - w.skip[message.ref.Hash] = struct{}{} - } - return messages -} - -// insert injects a new batch of messages into the filter cache. -func (w *whisperFilter) insert(messages ...WhisperMessage) { - w.lock.Lock() - defer w.lock.Unlock() - - for _, message := range messages { - if _, ok := w.skip[message.ref.Hash]; !ok { - w.cache = append(w.cache, messages...) - } - } -} - -// retrieve fetches all the cached messages from the filter. -func (w *whisperFilter) retrieve() (messages []WhisperMessage) { - w.lock.Lock() - defer w.lock.Unlock() - - messages, w.cache = w.cache, nil - w.update = time.Now() - - return -} - -// activity returns the last time instance when client requests were executed on -// the filter. -func (w *whisperFilter) activity() time.Time { - w.lock.RLock() - defer w.lock.RUnlock() - - return w.update -} diff --git a/xeth/whisper_message.go b/xeth/whisper_message.go deleted file mode 100644 index b3014a6972..0000000000 --- a/xeth/whisper_message.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2015 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 . - -// Contains the external API representation of a whisper message. - -package xeth - -import ( - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/whisper" -) - -// WhisperMessage is the external API representation of a whisper.Message. -type WhisperMessage struct { - ref *whisper.Message - - Payload string `json:"payload"` - To string `json:"to"` - From string `json:"from"` - Sent int64 `json:"sent"` - TTL int64 `json:"ttl"` - Hash string `json:"hash"` -} - -// NewWhisperMessage converts an internal message into an API version. -func NewWhisperMessage(message *whisper.Message) WhisperMessage { - return WhisperMessage{ - ref: message, - - Payload: common.ToHex(message.Payload), - From: common.ToHex(crypto.FromECDSAPub(message.Recover())), - To: common.ToHex(crypto.FromECDSAPub(message.To)), - Sent: message.Sent.Unix(), - TTL: int64(message.TTL / time.Second), - Hash: common.ToHex(message.Hash.Bytes()), - } -} diff --git a/xeth/xeth.go b/xeth/xeth.go deleted file mode 100644 index 5a5399a3e7..0000000000 --- a/xeth/xeth.go +++ /dev/null @@ -1,1137 +0,0 @@ -// Copyright 2014 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 . - -// Package xeth is the interface to all Ethereum functionality. -package xeth - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "math/big" - "regexp" - "sync" - "time" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/compiler" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/filters" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/miner" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/whisper" -) - -var ( - filterTickerTime = 5 * time.Minute - defaultGasPrice = big.NewInt(10000000000000) //150000000000 - defaultGas = big.NewInt(90000) //500000 - dappStorePre = []byte("dapp-") - addrReg = regexp.MustCompile(`^(0x)?[a-fA-F0-9]{40}$`) -) - -// byte will be inferred -const ( - UnknownFilterTy = iota - BlockFilterTy - TransactionFilterTy - LogFilterTy -) - -type XEth struct { - quit chan struct{} - - logMu sync.RWMutex - logQueue map[int]*logQueue - - blockMu sync.RWMutex - blockQueue map[int]*hashQueue - - transactionMu sync.RWMutex - transactionQueue map[int]*hashQueue - - messagesMu sync.RWMutex - messages map[int]*whisperFilter - - transactMu sync.Mutex - - // read-only fields - backend *node.Node - frontend Frontend - agent *miner.RemoteAgent - gpo *eth.GasPriceOracle - state *State - whisper *Whisper - filterManager *filters.FilterSystem -} - -func NewTest(stack *node.Node, frontend Frontend) *XEth { - return &XEth{backend: stack, frontend: frontend} -} - -// New creates an XEth that uses the given frontend. -// If a nil Frontend is provided, a default frontend which -// confirms all transactions will be used. -func New(stack *node.Node, frontend Frontend) *XEth { - var ( - ethereum *eth.Ethereum - whisper *whisper.Whisper - ) - stack.Service(ðereum) - stack.Service(&whisper) - - xeth := &XEth{ - backend: stack, - frontend: frontend, - quit: make(chan struct{}), - filterManager: filters.NewFilterSystem(stack.EventMux()), - logQueue: make(map[int]*logQueue), - blockQueue: make(map[int]*hashQueue), - transactionQueue: make(map[int]*hashQueue), - messages: make(map[int]*whisperFilter), - agent: miner.NewRemoteAgent(), - gpo: eth.NewGasPriceOracle(ethereum), - } - if whisper != nil { - xeth.whisper = NewWhisper(whisper) - } - ethereum.Miner().Register(xeth.agent) - if frontend == nil { - xeth.frontend = dummyFrontend{} - } - state, _ := ethereum.BlockChain().State() - xeth.state = NewState(xeth, state) - go xeth.start() - return xeth -} - -func (self *XEth) EthereumService() *eth.Ethereum { - var ethereum *eth.Ethereum - if err := self.backend.Service(ðereum); err != nil { - return nil - } - return ethereum -} - -func (self *XEth) WhisperService() *whisper.Whisper { - var whisper *whisper.Whisper - if err := self.backend.Service(&whisper); err != nil { - return nil - } - return whisper -} - -func (self *XEth) start() { - timer := time.NewTicker(2 * time.Second) - defer timer.Stop() -done: - for { - select { - case <-timer.C: - self.logMu.Lock() - for id, filter := range self.logQueue { - if time.Since(filter.timeout) > filterTickerTime { - self.filterManager.Remove(id) - delete(self.logQueue, id) - } - } - self.logMu.Unlock() - - self.blockMu.Lock() - for id, filter := range self.blockQueue { - if time.Since(filter.timeout) > filterTickerTime { - self.filterManager.Remove(id) - delete(self.blockQueue, id) - } - } - self.blockMu.Unlock() - - self.transactionMu.Lock() - for id, filter := range self.transactionQueue { - if time.Since(filter.timeout) > filterTickerTime { - self.filterManager.Remove(id) - delete(self.transactionQueue, id) - } - } - self.transactionMu.Unlock() - - self.messagesMu.Lock() - for id, filter := range self.messages { - if time.Since(filter.activity()) > filterTickerTime { - self.Whisper().Unwatch(id) - delete(self.messages, id) - } - } - self.messagesMu.Unlock() - case <-self.quit: - break done - } - } -} - -// Stop releases any resources associated with self. -// It may not be called more than once. -func (self *XEth) Stop() { - close(self.quit) - self.filterManager.Stop() - self.EthereumService().Miner().Unregister(self.agent) -} - -func cAddress(a []string) []common.Address { - bslice := make([]common.Address, len(a)) - for i, addr := range a { - bslice[i] = common.HexToAddress(addr) - } - return bslice -} - -func cTopics(t [][]string) [][]common.Hash { - topics := make([][]common.Hash, len(t)) - for i, iv := range t { - topics[i] = make([]common.Hash, len(iv)) - for j, jv := range iv { - topics[i][j] = common.HexToHash(jv) - } - } - return topics -} - -func DefaultGas() *big.Int { return new(big.Int).Set(defaultGas) } - -func (self *XEth) DefaultGasPrice() *big.Int { - return self.gpo.SuggestPrice() -} - -func (self *XEth) RemoteMining() *miner.RemoteAgent { return self.agent } - -func (self *XEth) AtStateNum(num int64) *XEth { - var st *state.StateDB - var err error - switch num { - case -2: - st = self.EthereumService().Miner().PendingState().Copy() - default: - if block := self.getBlockByHeight(num); block != nil { - st, err = state.New(block.Root(), self.EthereumService().ChainDb()) - if err != nil { - return nil - } - } else { - st, err = state.New(self.EthereumService().BlockChain().GetBlockByNumber(0).Root(), self.EthereumService().ChainDb()) - if err != nil { - return nil - } - } - } - return self.WithState(st) -} - -func (self *XEth) WithState(statedb *state.StateDB) *XEth { - xeth := &XEth{ - backend: self.backend, - frontend: self.frontend, - gpo: self.gpo, - } - - xeth.state = NewState(xeth, statedb) - return xeth -} - -func (self *XEth) State() *State { return self.state } - -// subscribes to new head block events and -// waits until blockchain height is greater n at any time -// given the current head, waits for the next chain event -// sets the state to the current head -// loop is async and quit by closing the channel -// used in tests and JS console debug module to control advancing private chain manually -// Note: this is not threadsafe, only called in JS single process and tests -func (self *XEth) UpdateState() (wait chan *big.Int) { - wait = make(chan *big.Int) - go func() { - eventSub := self.backend.EventMux().Subscribe(core.ChainHeadEvent{}) - defer eventSub.Unsubscribe() - - var m, n *big.Int - var ok bool - - eventCh := eventSub.Chan() - for { - select { - case event, ok := <-eventCh: - if !ok { - // Event subscription closed, set the channel to nil to stop spinning - eventCh = nil - continue - } - // A real event arrived, process if new head block assignment - if event, ok := event.Data.(core.ChainHeadEvent); ok { - m = event.Block.Number() - if n != nil && n.Cmp(m) < 0 { - wait <- n - n = nil - } - statedb, err := state.New(event.Block.Root(), self.EthereumService().ChainDb()) - if err != nil { - glog.V(logger.Error).Infoln("Could not create new state: %v", err) - return - } - self.state = NewState(self, statedb) - } - case n, ok = <-wait: - if !ok { - return - } - } - } - }() - return -} - -func (self *XEth) Whisper() *Whisper { return self.whisper } - -func (self *XEth) getBlockByHeight(height int64) *types.Block { - var num uint64 - - switch height { - case -2: - return self.EthereumService().Miner().PendingBlock() - case -1: - return self.CurrentBlock() - default: - if height < 0 { - return nil - } - - num = uint64(height) - } - - return self.EthereumService().BlockChain().GetBlockByNumber(num) -} - -func (self *XEth) BlockByHash(strHash string) *Block { - hash := common.HexToHash(strHash) - block := self.EthereumService().BlockChain().GetBlock(hash) - - return NewBlock(block) -} - -func (self *XEth) EthBlockByHash(strHash string) *types.Block { - hash := common.HexToHash(strHash) - block := self.EthereumService().BlockChain().GetBlock(hash) - - return block -} - -func (self *XEth) EthTransactionByHash(hash string) (*types.Transaction, common.Hash, uint64, uint64) { - ethereum := self.EthereumService() - if tx, hash, number, index := core.GetTransaction(ethereum.ChainDb(), common.HexToHash(hash)); tx != nil { - return tx, hash, number, index - } - return ethereum.TxPool().GetTransaction(common.HexToHash(hash)), common.Hash{}, 0, 0 -} - -func (self *XEth) BlockByNumber(num int64) *Block { - return NewBlock(self.getBlockByHeight(num)) -} - -func (self *XEth) EthBlockByNumber(num int64) *types.Block { - return self.getBlockByHeight(num) -} - -func (self *XEth) Td(hash common.Hash) *big.Int { - return self.EthereumService().BlockChain().GetTd(hash) -} - -func (self *XEth) CurrentBlock() *types.Block { - return self.EthereumService().BlockChain().CurrentBlock() -} - -func (self *XEth) GetBlockReceipts(bhash common.Hash) types.Receipts { - return core.GetBlockReceipts(self.EthereumService().ChainDb(), bhash) -} - -func (self *XEth) GetTxReceipt(txhash common.Hash) *types.Receipt { - return core.GetReceipt(self.EthereumService().ChainDb(), txhash) -} - -func (self *XEth) GasLimit() *big.Int { - return self.EthereumService().BlockChain().GasLimit() -} - -func (self *XEth) Block(v interface{}) *Block { - if n, ok := v.(int32); ok { - return self.BlockByNumber(int64(n)) - } else if str, ok := v.(string); ok { - return self.BlockByHash(str) - } else if f, ok := v.(float64); ok { // JSON numbers are represented as float64 - return self.BlockByNumber(int64(f)) - } - - return nil -} - -func (self *XEth) Accounts() []string { - // TODO: check err? - accounts, _ := self.EthereumService().AccountManager().Accounts() - accountAddresses := make([]string, len(accounts)) - for i, ac := range accounts { - accountAddresses[i] = ac.Address.Hex() - } - return accountAddresses -} - -// accessor for solidity compiler. -// memoized if available, retried on-demand if not -func (self *XEth) Solc() (*compiler.Solidity, error) { - return self.EthereumService().Solc() -} - -// set in js console via admin interface or wrapper from cli flags -func (self *XEth) SetSolc(solcPath string) (*compiler.Solidity, error) { - self.EthereumService().SetSolc(solcPath) - return self.Solc() -} - -// store DApp value in extra database -func (self *XEth) DbPut(key, val []byte) bool { - self.EthereumService().DappDb().Put(append(dappStorePre, key...), val) - return true -} - -// retrieve DApp value from extra database -func (self *XEth) DbGet(key []byte) ([]byte, error) { - val, err := self.EthereumService().DappDb().Get(append(dappStorePre, key...)) - return val, err -} - -func (self *XEth) PeerCount() int { - return self.backend.Server().PeerCount() -} - -func (self *XEth) IsMining() bool { - return self.EthereumService().IsMining() -} - -func (self *XEth) HashRate() int64 { - return self.EthereumService().Miner().HashRate() -} - -func (self *XEth) EthVersion() string { - return fmt.Sprintf("%d", self.EthereumService().EthVersion()) -} - -func (self *XEth) NetworkVersion() string { - return fmt.Sprintf("%d", self.EthereumService().NetVersion()) -} - -func (self *XEth) WhisperVersion() string { - return fmt.Sprintf("%d", self.WhisperService().Version()) -} - -func (self *XEth) ClientVersion() string { - return self.backend.Server().Name -} - -func (self *XEth) SetMining(shouldmine bool, threads int) bool { - ismining := self.EthereumService().IsMining() - if shouldmine && !ismining { - err := self.EthereumService().StartMining(threads, "") - return err == nil - } - if ismining && !shouldmine { - self.EthereumService().StopMining() - } - return self.EthereumService().IsMining() -} - -func (self *XEth) IsListening() bool { - return true -} - -func (self *XEth) Coinbase() string { - eb, err := self.EthereumService().Etherbase() - if err != nil { - return "0x0" - } - return eb.Hex() -} - -func (self *XEth) NumberToHuman(balance string) string { - b := common.Big(balance) - - return common.CurrencyToString(b) -} - -func (self *XEth) StorageAt(addr, storageAddr string) string { - return self.State().state.GetState(common.HexToAddress(addr), common.HexToHash(storageAddr)).Hex() -} - -func (self *XEth) BalanceAt(addr string) string { - return common.ToHex(self.State().state.GetBalance(common.HexToAddress(addr)).Bytes()) -} - -func (self *XEth) TxCountAt(address string) int { - return int(self.State().state.GetNonce(common.HexToAddress(address))) -} - -func (self *XEth) CodeAt(address string) string { - return common.ToHex(self.State().state.GetCode(common.HexToAddress(address))) -} - -func (self *XEth) CodeAtBytes(address string) []byte { - return self.State().SafeGet(address).Code() -} - -func (self *XEth) IsContract(address string) bool { - return len(self.State().SafeGet(address).Code()) > 0 -} - -func (self *XEth) UninstallFilter(id int) bool { - defer self.filterManager.Remove(id) - - if _, ok := self.logQueue[id]; ok { - self.logMu.Lock() - defer self.logMu.Unlock() - delete(self.logQueue, id) - return true - } - if _, ok := self.blockQueue[id]; ok { - self.blockMu.Lock() - defer self.blockMu.Unlock() - delete(self.blockQueue, id) - return true - } - if _, ok := self.transactionQueue[id]; ok { - self.transactionMu.Lock() - defer self.transactionMu.Unlock() - delete(self.transactionQueue, id) - return true - } - - return false -} - -func (self *XEth) NewLogFilter(earliest, latest int64, skip, max int, address []string, topics [][]string) int { - self.logMu.Lock() - defer self.logMu.Unlock() - - filter := filters.New(self.EthereumService().ChainDb()) - id := self.filterManager.Add(filter) - self.logQueue[id] = &logQueue{timeout: time.Now()} - - filter.SetBeginBlock(earliest) - filter.SetEndBlock(latest) - filter.SetAddresses(cAddress(address)) - filter.SetTopics(cTopics(topics)) - filter.LogsCallback = func(logs vm.Logs) { - self.logMu.Lock() - defer self.logMu.Unlock() - - if queue := self.logQueue[id]; queue != nil { - queue.add(logs...) - } - } - - return id -} - -func (self *XEth) NewTransactionFilter() int { - self.transactionMu.Lock() - defer self.transactionMu.Unlock() - - filter := filters.New(self.EthereumService().ChainDb()) - id := self.filterManager.Add(filter) - self.transactionQueue[id] = &hashQueue{timeout: time.Now()} - - filter.TransactionCallback = func(tx *types.Transaction) { - self.transactionMu.Lock() - defer self.transactionMu.Unlock() - - if queue := self.transactionQueue[id]; queue != nil { - queue.add(tx.Hash()) - } - } - return id -} - -func (self *XEth) NewBlockFilter() int { - self.blockMu.Lock() - defer self.blockMu.Unlock() - - filter := filters.New(self.EthereumService().ChainDb()) - id := self.filterManager.Add(filter) - self.blockQueue[id] = &hashQueue{timeout: time.Now()} - - filter.BlockCallback = func(block *types.Block, logs vm.Logs) { - self.blockMu.Lock() - defer self.blockMu.Unlock() - - if queue := self.blockQueue[id]; queue != nil { - queue.add(block.Hash()) - } - } - return id -} - -func (self *XEth) GetFilterType(id int) byte { - if _, ok := self.blockQueue[id]; ok { - return BlockFilterTy - } else if _, ok := self.transactionQueue[id]; ok { - return TransactionFilterTy - } else if _, ok := self.logQueue[id]; ok { - return LogFilterTy - } - - return UnknownFilterTy -} - -func (self *XEth) LogFilterChanged(id int) vm.Logs { - self.logMu.Lock() - defer self.logMu.Unlock() - - if self.logQueue[id] != nil { - return self.logQueue[id].get() - } - return nil -} - -func (self *XEth) BlockFilterChanged(id int) []common.Hash { - self.blockMu.Lock() - defer self.blockMu.Unlock() - - if self.blockQueue[id] != nil { - return self.blockQueue[id].get() - } - return nil -} - -func (self *XEth) TransactionFilterChanged(id int) []common.Hash { - self.blockMu.Lock() - defer self.blockMu.Unlock() - - if self.transactionQueue[id] != nil { - return self.transactionQueue[id].get() - } - return nil -} - -func (self *XEth) Logs(id int) vm.Logs { - filter := self.filterManager.Get(id) - if filter != nil { - return filter.Find() - } - - return nil -} - -func (self *XEth) AllLogs(earliest, latest int64, skip, max int, address []string, topics [][]string) vm.Logs { - filter := filters.New(self.EthereumService().ChainDb()) - filter.SetBeginBlock(earliest) - filter.SetEndBlock(latest) - filter.SetAddresses(cAddress(address)) - filter.SetTopics(cTopics(topics)) - - return filter.Find() -} - -// NewWhisperFilter creates and registers a new message filter to watch for -// inbound whisper messages. All parameters at this point are assumed to be -// HEX encoded. -func (p *XEth) NewWhisperFilter(to, from string, topics [][]string) int { - // Pre-define the id to be filled later - var id int - - // Callback to delegate core whisper messages to this xeth filter - callback := func(msg WhisperMessage) { - p.messagesMu.RLock() // Only read lock to the filter pool - defer p.messagesMu.RUnlock() - if p.messages[id] != nil { - p.messages[id].insert(msg) - } - } - // Initialize the core whisper filter and wrap into xeth - id = p.Whisper().Watch(to, from, topics, callback) - - p.messagesMu.Lock() - p.messages[id] = newWhisperFilter(id, p.Whisper()) - p.messagesMu.Unlock() - - return id -} - -// UninstallWhisperFilter disables and removes an existing filter. -func (p *XEth) UninstallWhisperFilter(id int) bool { - p.messagesMu.Lock() - defer p.messagesMu.Unlock() - - if _, ok := p.messages[id]; ok { - delete(p.messages, id) - return true - } - return false -} - -// WhisperMessages retrieves all the known messages that match a specific filter. -func (self *XEth) WhisperMessages(id int) []WhisperMessage { - self.messagesMu.RLock() - defer self.messagesMu.RUnlock() - - if self.messages[id] != nil { - return self.messages[id].messages() - } - return nil -} - -// WhisperMessagesChanged retrieves all the new messages matched by a filter -// since the last retrieval -func (self *XEth) WhisperMessagesChanged(id int) []WhisperMessage { - self.messagesMu.RLock() - defer self.messagesMu.RUnlock() - - if self.messages[id] != nil { - return self.messages[id].retrieve() - } - return nil -} - -// func (self *XEth) Register(args string) bool { -// self.regmut.Lock() -// defer self.regmut.Unlock() - -// if _, ok := self.register[args]; ok { -// self.register[args] = nil // register with empty -// } -// return true -// } - -// func (self *XEth) Unregister(args string) bool { -// self.regmut.Lock() -// defer self.regmut.Unlock() - -// if _, ok := self.register[args]; ok { -// delete(self.register, args) -// return true -// } - -// return false -// } - -// // TODO improve return type -// func (self *XEth) PullWatchTx(args string) []*interface{} { -// self.regmut.Lock() -// defer self.regmut.Unlock() - -// txs := self.register[args] -// self.register[args] = nil - -// return txs -// } - -type KeyVal struct { - Key string `json:"key"` - Value string `json:"value"` -} - -func (self *XEth) EachStorage(addr string) string { - var values []KeyVal - object := self.State().SafeGet(addr) - it := object.Trie().Iterator() - for it.Next() { - values = append(values, KeyVal{common.ToHex(object.Trie().GetKey(it.Key)), common.ToHex(it.Value)}) - } - - valuesJson, err := json.Marshal(values) - if err != nil { - return "" - } - - return string(valuesJson) -} - -func (self *XEth) ToAscii(str string) string { - padded := common.RightPadBytes([]byte(str), 32) - - return "0x" + common.ToHex(padded) -} - -func (self *XEth) FromAscii(str string) string { - if common.IsHex(str) { - str = str[2:] - } - - return string(bytes.Trim(common.FromHex(str), "\x00")) -} - -func (self *XEth) FromNumber(str string) string { - if common.IsHex(str) { - str = str[2:] - } - - return common.BigD(common.FromHex(str)).String() -} - -func (self *XEth) PushTx(encodedTx string) (string, error) { - tx := new(types.Transaction) - err := rlp.DecodeBytes(common.FromHex(encodedTx), tx) - if err != nil { - glog.V(logger.Error).Infoln(err) - return "", err - } - - err = self.EthereumService().TxPool().Add(tx) - if err != nil { - return "", err - } - - if tx.To() == nil { - from, err := tx.From() - if err != nil { - return "", err - } - - addr := crypto.CreateAddress(from, tx.Nonce()) - glog.V(logger.Info).Infof("Tx(%x) created: %x\n", tx.Hash(), addr) - } else { - glog.V(logger.Info).Infof("Tx(%x) to: %x\n", tx.Hash(), tx.To()) - } - - return tx.Hash().Hex(), nil -} - -func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, string, error) { - statedb := self.State().State().Copy() - var from *state.StateObject - if len(fromStr) == 0 { - accounts, err := self.EthereumService().AccountManager().Accounts() - if err != nil || len(accounts) == 0 { - from = statedb.GetOrNewStateObject(common.Address{}) - } else { - from = statedb.GetOrNewStateObject(accounts[0].Address) - } - } else { - from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr)) - } - - from.SetBalance(common.MaxBig) - - msg := callmsg{ - from: from, - gas: common.Big(gasStr), - gasPrice: common.Big(gasPriceStr), - value: common.Big(valueStr), - data: common.FromHex(dataStr), - } - if len(toStr) > 0 { - addr := common.HexToAddress(toStr) - msg.to = &addr - } - - if msg.gas.Cmp(big.NewInt(0)) == 0 { - msg.gas = big.NewInt(50000000) - } - - if msg.gasPrice.Cmp(big.NewInt(0)) == 0 { - msg.gasPrice = self.DefaultGasPrice() - } - - header := self.CurrentBlock().Header() - vmenv := core.NewEnv(statedb, self.EthereumService().BlockChain(), msg, header) - gp := new(core.GasPool).AddGas(common.MaxBig) - res, gas, err := core.ApplyMessage(vmenv, msg, gp) - return common.ToHex(res), gas.String(), err -} - -func (self *XEth) ConfirmTransaction(tx string) bool { - return self.frontend.ConfirmTransaction(tx) -} - -func (self *XEth) doSign(from common.Address, hash common.Hash, didUnlock bool) ([]byte, error) { - sig, err := self.EthereumService().AccountManager().Sign(accounts.Account{Address: from}, hash.Bytes()) - if err == accounts.ErrLocked { - if didUnlock { - return nil, fmt.Errorf("signer account still locked after successful unlock") - } - if !self.frontend.UnlockAccount(from.Bytes()) { - return nil, fmt.Errorf("could not unlock signer account") - } - // retry signing, the account should now be unlocked. - return self.doSign(from, hash, true) - } else if err != nil { - return nil, err - } - return sig, nil -} - -func (self *XEth) Sign(fromStr, hashStr string, didUnlock bool) (string, error) { - var ( - from = common.HexToAddress(fromStr) - hash = common.HexToHash(hashStr) - ) - sig, err := self.doSign(from, hash, didUnlock) - if err != nil { - return "", err - } - return common.ToHex(sig), nil -} - -func isAddress(addr string) bool { - return addrReg.MatchString(addr) -} - -func (self *XEth) Frontend() Frontend { - return self.frontend -} - -func (self *XEth) SignTransaction(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (*types.Transaction, error) { - if len(toStr) > 0 && toStr != "0x" && !isAddress(toStr) { - return nil, errors.New("Invalid address") - } - - var ( - from = common.HexToAddress(fromStr) - to = common.HexToAddress(toStr) - value = common.Big(valueStr) - gas *big.Int - price *big.Int - data []byte - contractCreation bool - ) - - if len(gasStr) == 0 { - gas = DefaultGas() - } else { - gas = common.Big(gasStr) - } - - if len(gasPriceStr) == 0 { - price = self.DefaultGasPrice() - } else { - price = common.Big(gasPriceStr) - } - - data = common.FromHex(codeStr) - if len(toStr) == 0 { - contractCreation = true - } - - var nonce uint64 - if len(nonceStr) != 0 { - nonce = common.Big(nonceStr).Uint64() - } else { - state := self.EthereumService().TxPool().State() - nonce = state.GetNonce(from) - } - var tx *types.Transaction - if contractCreation { - tx = types.NewContractCreation(nonce, value, gas, price, data) - } else { - tx = types.NewTransaction(nonce, to, value, gas, price, data) - } - - signed, err := self.sign(tx, from, false) - if err != nil { - return nil, err - } - - return signed, nil -} - -func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { - - // this minimalistic recoding is enough (works for natspec.js) - var jsontx = fmt.Sprintf(`{"params":[{"to":"%s","data": "%s"}]}`, toStr, codeStr) - if !self.ConfirmTransaction(jsontx) { - err := fmt.Errorf("Transaction not confirmed") - return "", err - } - - if len(toStr) > 0 && toStr != "0x" && !isAddress(toStr) { - return "", errors.New("Invalid address") - } - - var ( - from = common.HexToAddress(fromStr) - to = common.HexToAddress(toStr) - value = common.Big(valueStr) - gas *big.Int - price *big.Int - data []byte - contractCreation bool - ) - - if len(gasStr) == 0 { - gas = DefaultGas() - } else { - gas = common.Big(gasStr) - } - - if len(gasPriceStr) == 0 { - price = self.DefaultGasPrice() - } else { - price = common.Big(gasPriceStr) - } - - data = common.FromHex(codeStr) - if len(toStr) == 0 { - contractCreation = true - } - - // 2015-05-18 Is this still needed? - // TODO if no_private_key then - //if _, exists := p.register[args.From]; exists { - // p.register[args.From] = append(p.register[args.From], args) - //} else { - /* - account := accounts.Get(common.FromHex(args.From)) - if account != nil { - if account.Unlocked() { - if !unlockAccount(account) { - return - } - } - - result, _ := account.Transact(common.FromHex(args.To), common.FromHex(args.Value), common.FromHex(args.Gas), common.FromHex(args.GasPrice), common.FromHex(args.Data)) - if len(result) > 0 { - *reply = common.ToHex(result) - } - } else if _, exists := p.register[args.From]; exists { - p.register[ags.From] = append(p.register[args.From], args) - } - */ - - self.transactMu.Lock() - defer self.transactMu.Unlock() - - var nonce uint64 - if len(nonceStr) != 0 { - nonce = common.Big(nonceStr).Uint64() - } else { - state := self.EthereumService().TxPool().State() - nonce = state.GetNonce(from) - } - var tx *types.Transaction - if contractCreation { - tx = types.NewContractCreation(nonce, value, gas, price, data) - } else { - tx = types.NewTransaction(nonce, to, value, gas, price, data) - } - - signed, err := self.sign(tx, from, false) - if err != nil { - return "", err - } - self.EthereumService().TxPool().SetLocal(signed) - if err = self.EthereumService().TxPool().Add(signed); err != nil { - return "", err - } - - if contractCreation { - addr := crypto.CreateAddress(from, nonce) - glog.V(logger.Info).Infof("Tx(%s) created: %s\n", signed.Hash().Hex(), addr.Hex()) - } else { - glog.V(logger.Info).Infof("Tx(%s) to: %s\n", signed.Hash().Hex(), tx.To().Hex()) - } - - return signed.Hash().Hex(), nil -} - -func (self *XEth) sign(tx *types.Transaction, from common.Address, didUnlock bool) (*types.Transaction, error) { - hash := tx.SigHash() - sig, err := self.doSign(from, hash, didUnlock) - if err != nil { - return tx, err - } - return tx.WithSignature(sig) -} - -// callmsg is the message type used for call transations. -type callmsg struct { - from *state.StateObject - to *common.Address - gas, gasPrice *big.Int - value *big.Int - data []byte -} - -// accessor boilerplate to implement core.Message -func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } -func (m callmsg) Nonce() uint64 { return m.from.Nonce() } -func (m callmsg) To() *common.Address { return m.to } -func (m callmsg) GasPrice() *big.Int { return m.gasPrice } -func (m callmsg) Gas() *big.Int { return m.gas } -func (m callmsg) Value() *big.Int { return m.value } -func (m callmsg) Data() []byte { return m.data } - -type logQueue struct { - mu sync.Mutex - - logs vm.Logs - timeout time.Time - id int -} - -func (l *logQueue) add(logs ...*vm.Log) { - l.mu.Lock() - defer l.mu.Unlock() - - l.logs = append(l.logs, logs...) -} - -func (l *logQueue) get() vm.Logs { - l.mu.Lock() - defer l.mu.Unlock() - - l.timeout = time.Now() - tmp := l.logs - l.logs = nil - return tmp -} - -type hashQueue struct { - mu sync.Mutex - - hashes []common.Hash - timeout time.Time - id int -} - -func (l *hashQueue) add(hashes ...common.Hash) { - l.mu.Lock() - defer l.mu.Unlock() - - l.hashes = append(l.hashes, hashes...) -} - -func (l *hashQueue) get() []common.Hash { - l.mu.Lock() - defer l.mu.Unlock() - - l.timeout = time.Now() - tmp := l.hashes - l.hashes = nil - return tmp -} diff --git a/xeth/xeth_test.go b/xeth/xeth_test.go deleted file mode 100644 index e649d20ef1..0000000000 --- a/xeth/xeth_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package xeth - -import "testing" - -func TestIsAddress(t *testing.T) { - for _, invalid := range []string{ - "0x00", - "0xNN", - "0x00000000000000000000000000000000000000NN", - "0xAAar000000000000000000000000000000000000", - } { - if isAddress(invalid) { - t.Error("Expected", invalid, "to be invalid") - } - } - - for _, valid := range []string{ - "0x0000000000000000000000000000000000000000", - "0xAABBbbCCccff9900000000000000000000000000", - "AABBbbCCccff9900000000000000000000000000", - } { - if !isAddress(valid) { - t.Error("Expected", valid, "to be valid") - } - } -} From 5728dd381d344e8e8fca2df06be0ab7330980b66 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 26 Jan 2016 18:18:56 +0100 Subject: [PATCH 62/78] rpc: fix success response encoding for null return value The "result" field of JSON-RPC 2.0 responses was omitted if the result was nil, causing exceptions in web3.js. --- rpc/json.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/json.go b/rpc/json.go index 8bdb4665df..02bc71c268 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -48,7 +48,7 @@ type JSONRequest struct { type JSONSuccessResponse struct { Version string `json:"jsonrpc"` Id int64 `json:"id"` - Result interface{} `json:"result,omitempty"` + Result interface{} `json:"result"` } // JSON-RPC error object From a411fe7e6e8340064f5bf8c67bfb1ae7d172905d Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 27 Jan 2016 00:49:35 +0100 Subject: [PATCH 63/78] logger/glog: fix TraceLocation.Set("") --- logger/glog/glog.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/logger/glog/glog.go b/logger/glog/glog.go index b8eaf9359e..e760e507be 100644 --- a/logger/glog/glog.go +++ b/logger/glog/glog.go @@ -407,9 +407,13 @@ var errTraceSyntax = errors.New("syntax error: expect file.go:234") func (t *TraceLocation) Set(value string) error { if value == "" { // Unset. + logging.mu.Lock() t.line = 0 t.file = "" + logging.mu.Unlock() + return nil } + fields := strings.Split(value, ":") if len(fields) != 2 { return errTraceSyntax From e287b56b69cdf8340f679e9cfd229c959cc45e24 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 27 Jan 2016 00:50:24 +0100 Subject: [PATCH 64/78] logger/glog: clean up flag getters --- logger/glog/glog.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/logger/glog/glog.go b/logger/glog/glog.go index e760e507be..14e405955a 100644 --- a/logger/glog/glog.go +++ b/logger/glog/glog.go @@ -138,25 +138,28 @@ func SetV(v int) { logging.verbosity.set(Level(v)) } -// SetVmodule sets the global verbosity patterns. -func SetVmodule(pat string) error { - return logging.vmodule.Set(pat) -} - // SetToStderr sets the global output style func SetToStderr(toStderr bool) { + logging.mu.Lock() logging.toStderr = toStderr + logging.mu.Unlock() } -// GetTraceLocation returns the global TraceLocation object +// GetTraceLocation returns the global TraceLocation flag. func GetTraceLocation() *TraceLocation { return &logging.traceLocation } +// GetVModule returns the global verbosity pattern flag. func GetVModule() *moduleSpec { return &logging.vmodule } +// GetVerbosity returns the global verbosity level flag. +func GetVerbosity() *Level { + return &logging.verbosity +} + // get returns the value of the severity. func (s *severity) get() severity { return severity(atomic.LoadInt32((*int32)(s))) @@ -453,8 +456,7 @@ func init() { // Default stderrThreshold is ERROR. logging.stderrThreshold = errorLog - - logging.setVState(0, nil, false) + logging.setVState(3, nil, false) go logging.flushDaemon() } From 3750d835a1697f6784c727074cc959dda33cdcf3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 26 Jan 2016 14:39:21 +0100 Subject: [PATCH 65/78] internal/debug: APIs for profiling and tracing The debug package provides an RPC wrapper for glog settings and the debugging facilities of the Go runtime. They can be triggered through both command line flags and the IPC listener. --- cmd/geth/main.go | 28 ++--- cmd/geth/usage.go | 13 +-- cmd/utils/flags.go | 60 +--------- internal/debug/api.go | 192 +++++++++++++++++++++++++++++++ internal/debug/flags.go | 117 +++++++++++++++++++ internal/debug/trace.go | 64 +++++++++++ internal/debug/trace_fallback.go | 31 +++++ node/api.go | 28 +---- node/node.go | 3 +- rpc/javascript.go | 73 +++++++++++- 10 files changed, 502 insertions(+), 107 deletions(-) create mode 100644 internal/debug/api.go create mode 100644 internal/debug/flags.go create mode 100644 internal/debug/trace.go create mode 100644 internal/debug/trace_fallback.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e6d1909143..09c7eee050 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -20,7 +20,6 @@ package main import ( "fmt" "io/ioutil" - _ "net/http/pprof" "os" "path/filepath" "runtime" @@ -34,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/metrics" @@ -326,12 +326,6 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.VMEnableJitFlag, utils.NetworkIdFlag, utils.RPCCORSDomainFlag, - utils.VerbosityFlag, - utils.BacktraceAtFlag, - utils.LogVModuleFlag, - utils.LogFileFlag, - utils.PProfEanbledFlag, - utils.PProfPortFlag, utils.MetricsEnabledFlag, utils.SolcPathFlag, utils.GpoMinGasPriceFlag, @@ -342,23 +336,29 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.GpobaseCorrectionFactorFlag, utils.ExtraDataFlag, } + app.Flags = append(app.Flags, debug.Flags...) + app.Before = func(ctx *cli.Context) error { runtime.GOMAXPROCS(runtime.NumCPU()) + if err := debug.Setup(ctx); err != nil { + return err + } + // Start system runtime metrics collection + go metrics.CollectProcessMetrics(3 * time.Second) - utils.SetupLogger(ctx) utils.SetupNetwork(ctx) utils.SetupVM(ctx) - if ctx.GlobalBool(utils.PProfEanbledFlag.Name) { - utils.StartPProf(ctx) - } return nil } - // Start system runtime metrics collection - go metrics.CollectProcessMetrics(3 * time.Second) + + app.After = func(ctx *cli.Context) error { + logger.Flush() + debug.Exit() + return nil + } } func main() { - defer logger.Flush() if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index a9fce6418a..051c518784 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -23,6 +23,7 @@ import ( "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/internal/debug" ) // AppHelpTemplate is the test template for the default, global app help topic. @@ -147,16 +148,8 @@ var AppHelpFlagGroups = []flagGroup{ }, }, { - Name: "LOGGING AND DEBUGGING", - Flags: []cli.Flag{ - utils.VerbosityFlag, - utils.LogVModuleFlag, - utils.BacktraceAtFlag, - utils.LogFileFlag, - utils.PProfEanbledFlag, - utils.PProfPortFlag, - utils.MetricsEnabledFlag, - }, + Name: "LOGGING AND DEBUGGING", + Flags: append([]cli.Flag{utils.MetricsEnabledFlag}, debug.Flags...), }, { Name: "EXPERIMENTAL", diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9199432d87..1e3e58e510 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -18,20 +18,17 @@ package utils import ( "crypto/ecdsa" + "errors" "fmt" "io/ioutil" - "log" "math" "math/big" - "net/http" "os" "path/filepath" "runtime" "strconv" "strings" - "errors" - "github.com/codegangsta/cli" "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" @@ -223,35 +220,6 @@ var ( } // logging and debug settings - VerbosityFlag = cli.IntFlag{ - Name: "verbosity", - Usage: "Logging verbosity: 0-6 (0=silent, 1=error, 2=warn, 3=info, 4=core, 5=debug, 6=debug detail)", - Value: int(logger.InfoLevel), - } - LogFileFlag = cli.StringFlag{ - Name: "logfile", - Usage: "Log output file within the data dir (default = no log file generated)", - Value: "", - } - LogVModuleFlag = cli.GenericFlag{ - Name: "vmodule", - Usage: "Per-module verbosity: comma-separated list of =, where is file literal or a glog pattern", - Value: glog.GetVModule(), - } - BacktraceAtFlag = cli.GenericFlag{ - Name: "backtrace", - Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")", - Value: glog.GetTraceLocation(), - } - PProfEanbledFlag = cli.BoolFlag{ - Name: "pprof", - Usage: "Enable the profiling server on localhost", - } - PProfPortFlag = cli.IntFlag{ - Name: "pprofport", - Usage: "Profile server listening port", - Value: 6060, - } MetricsEnabledFlag = cli.BoolFlag{ Name: metrics.MetricsEnabledFlag, Usage: "Enable metrics collection and reporting", @@ -297,7 +265,7 @@ var ( Value: DirectoryString{common.DefaultIpcPath()}, } WSEnabledFlag = cli.BoolFlag{ - Name: "ws", + Name: "ws", Usage: "Enable the WS-RPC server", } WSListenAddrFlag = cli.StringFlag{ @@ -324,6 +292,7 @@ var ( Name: "exec", Usage: "Execute JavaScript statement (only in combination with console/attach)", } + // Network Settings MaxPeersFlag = cli.IntFlag{ Name: "maxpeers", @@ -714,19 +683,6 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. return stack } -// SetupLogger configures glog from the logging-related command line flags. -func SetupLogger(ctx *cli.Context) { - glog.SetV(ctx.GlobalInt(VerbosityFlag.Name)) - glog.CopyStandardLogTo("INFO") - glog.SetToStderr(true) - if ctx.GlobalIsSet(LogFileFlag.Name) { - logger.New("", ctx.GlobalString(LogFileFlag.Name), ctx.GlobalInt(VerbosityFlag.Name)) - } - if ctx.GlobalIsSet(VMDebugFlag.Name) { - vm.Debug = ctx.GlobalBool(VMDebugFlag.Name) - } -} - // SetupNetwork configures the system for either the main net or some test network. func SetupNetwork(ctx *cli.Context) { switch { @@ -746,6 +702,9 @@ func SetupVM(ctx *cli.Context) { vm.EnableJit = ctx.GlobalBool(VMEnableJitFlag.Name) vm.ForceJit = ctx.GlobalBool(VMForceJitFlag.Name) vm.SetJITCacheSize(ctx.GlobalInt(VMJitCacheFlag.Name)) + if ctx.GlobalIsSet(VMDebugFlag.Name) { + vm.Debug = ctx.GlobalBool(VMDebugFlag.Name) + } } // MakeChain creates a chain manager from set command line flags. @@ -873,10 +832,3 @@ func StartWS(stack *node.Node, ctx *cli.Context) error { glog.V(logger.Error).Infof("Unable to start RPC-WS interface, could not find admin API") return errors.New("Unable to start RPC-WS interface") } - -func StartPProf(ctx *cli.Context) { - address := fmt.Sprintf("localhost:%d", ctx.GlobalInt(PProfPortFlag.Name)) - go func() { - log.Println(http.ListenAndServe(address, nil)) - }() -} diff --git a/internal/debug/api.go b/internal/debug/api.go new file mode 100644 index 0000000000..83857eb2e1 --- /dev/null +++ b/internal/debug/api.go @@ -0,0 +1,192 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// 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 . + +// Package debug interfaces Go runtime debugging facilities. +// This package is mostly glue code making these facilities available +// through the CLI and RPC subsystem. If you want to use them from Go code, +// use package runtime instead. +package debug + +import ( + "errors" + "io" + "os" + "os/user" + "path/filepath" + "runtime" + "runtime/pprof" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +// Handler is the global debugging handler. +var Handler = new(HandlerT) + +// HandlerT implements the debugging API. +// Do not create values of this type, use the one +// in the Handler variable instead. +type HandlerT struct { + mu sync.Mutex + cpuW io.WriteCloser + cpuFile string + traceW io.WriteCloser + traceFile string +} + +// Verbosity sets the glog verbosity floor. +// The verbosity of individual packages and source files +// can be raised using Vmodule. +func (*HandlerT) Verbosity(level int) { + glog.SetV(level) +} + +// Vmodule sets the glog verbosity pattern. See package +// glog for details on pattern syntax. +func (*HandlerT) Vmodule(pattern string) error { + return glog.GetVModule().Set(pattern) +} + +// BacktraceAt sets the glog backtrace location. +// See package glog for details on pattern syntax. +func (*HandlerT) BacktraceAt(location string) error { + return glog.GetTraceLocation().Set(location) +} + +// CpuProfile turns on CPU profiling for nsec seconds and writes +// profile data to file. +func (h *HandlerT) CpuProfile(file string, nsec uint) error { + if err := h.StartCPUProfile(file); err != nil { + return err + } + time.Sleep(time.Duration(nsec) * time.Second) + h.StopCPUProfile() + return nil +} + +// StartCPUProfile turns on CPU profiling, writing to the given file. +func (h *HandlerT) StartCPUProfile(file string) error { + h.mu.Lock() + defer h.mu.Unlock() + if h.cpuW != nil { + return errors.New("CPU profiling already in progress") + } + f, err := os.Create(expandHome(file)) + if err != nil { + return err + } + if err := pprof.StartCPUProfile(f); err != nil { + f.Close() + return err + } + h.cpuW = f + h.cpuFile = file + glog.V(logger.Info).Infoln("CPU profiling started, writing to", h.cpuFile) + return nil +} + +// StopCPUProfile stops an ongoing CPU profile. +func (h *HandlerT) StopCPUProfile() error { + h.mu.Lock() + defer h.mu.Unlock() + pprof.StopCPUProfile() + if h.cpuW == nil { + return errors.New("CPU profiling not in progress") + } + glog.V(logger.Info).Infoln("done writing CPU profile to", h.cpuFile) + h.cpuW.Close() + h.cpuW = nil + h.cpuFile = "" + return nil +} + +// Trace turns on tracing for nsec seconds and writes +// trace data to file. +func (h *HandlerT) Trace(file string, nsec uint) error { + if err := h.StartTrace(file); err != nil { + return err + } + time.Sleep(time.Duration(nsec) * time.Second) + h.StopTrace() + return nil +} + +// BlockProfile turns on CPU profiling for nsec seconds and writes +// profile data to file. It uses a profile rate of 1 for most accurate +// information. If a different rate is desired, set the rate +// and write the profile manually. +func (*HandlerT) BlockProfile(file string, nsec uint) error { + runtime.SetBlockProfileRate(1) + time.Sleep(time.Duration(nsec) * time.Second) + defer runtime.SetBlockProfileRate(0) + return writeProfile("block", file) +} + +// SetBlockProfileRate sets the rate of goroutine block profile data collection. +// rate 0 disables block profiling. +func (*HandlerT) SetBlockProfileRate(rate int) { + runtime.SetBlockProfileRate(rate) +} + +// WriteBlockProfile writes a goroutine blocking profile to the given file. +func (*HandlerT) WriteBlockProfile(file string) error { + return writeProfile("block", file) +} + +// WriteMemProfile writes an allocation profile to the given file. +// Note that the profiling rate cannot be set through the API, +// it must be set on the command line. +func (*HandlerT) WriteMemProfile(file string) error { + return writeProfile("heap", file) +} + +// Stacks returns a printed representation of the stacks of all goroutines. +func (*HandlerT) Stacks() string { + buf := make([]byte, 1024*1024) + buf = buf[:runtime.Stack(buf, true)] + return string(buf) +} + +func writeProfile(name, file string) error { + p := pprof.Lookup(name) + glog.V(logger.Info).Infof("writing %d %s profile records to %s", p.Count(), name, file) + f, err := os.Create(expandHome(file)) + if err != nil { + return err + } + defer f.Close() + return p.WriteTo(f, 0) +} + +// expands home directory in file paths. +// ~someuser/tmp will not be expanded. +func expandHome(p string) string { + if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { + home := os.Getenv("HOME") + if home == "" { + if usr, err := user.Current(); err == nil { + home = usr.HomeDir + } + } + if home != "" { + p = home + p[1:] + } + } + return filepath.Clean(p) +} diff --git a/internal/debug/flags.go b/internal/debug/flags.go new file mode 100644 index 0000000000..22e524cd6e --- /dev/null +++ b/internal/debug/flags.go @@ -0,0 +1,117 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// 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 . + +package debug + +import ( + "fmt" + "net/http" + _ "net/http/pprof" + "runtime" + + "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +var ( + verbosityFlag = cli.GenericFlag{ + Name: "verbosity", + Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=core, 5=debug, 6=detail", + Value: glog.GetVerbosity(), + } + vmoduleFlag = cli.GenericFlag{ + Name: "vmodule", + Usage: "Per-module verbosity: comma-separated list of = (e.g. eth/*=6,p2p=5)", + Value: glog.GetVModule(), + } + backtraceAtFlag = cli.GenericFlag{ + Name: "backtrace", + Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")", + Value: glog.GetTraceLocation(), + } + pprofFlag = cli.BoolFlag{ + Name: "pprof", + Usage: "Enable the pprof HTTP server", + } + pprofPortFlag = cli.IntFlag{ + Name: "pprofport", + Usage: "pprof HTTP server listening port", + Value: 6060, + } + memprofilerateFlag = cli.IntFlag{ + Name: "memprofilerate", + Usage: "Turn on memory profiling with the given rate", + } + blockprofilerateFlag = cli.IntFlag{ + Name: "blockprofilerate", + Usage: "Turn on block profiling with the given rate", + } + cpuprofileFlag = cli.StringFlag{ + Name: "cpuprofile", + Usage: "Write CPU profile to the given file", + } + traceFlag = cli.StringFlag{ + Name: "trace", + Usage: "Write execution trace to the given file", + } +) + +// Flags holds all command-line flags required for debugging. +var Flags = []cli.Flag{ + verbosityFlag, vmoduleFlag, backtraceAtFlag, + pprofFlag, pprofPortFlag, + memprofilerateFlag, blockprofilerateFlag, cpuprofileFlag, traceFlag, +} + +// Setup initializes profiling and logging based on the CLI flags. +// It should be called as early as possible in the program. +func Setup(ctx *cli.Context) error { + // logging + glog.CopyStandardLogTo("INFO") + glog.SetToStderr(true) + + // profiling, tracing + runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name) + Handler.SetBlockProfileRate(ctx.GlobalInt(blockprofilerateFlag.Name)) + if traceFile := ctx.GlobalString(traceFlag.Name); traceFile != "" { + if err := Handler.StartTrace(traceFile); err != nil { + return err + } + } + if cpuFile := ctx.GlobalString(cpuprofileFlag.Name); cpuFile != "" { + if err := Handler.StartCPUProfile(cpuFile); err != nil { + return err + } + } + + // pprof server + if ctx.GlobalBool(pprofFlag.Name) { + address := fmt.Sprintf("127.0.0.1:%d", ctx.GlobalInt(pprofPortFlag.Name)) + go func() { + glog.V(logger.Info).Infof("starting pprof server at http://%s/debug/pprof", address) + glog.Errorln(http.ListenAndServe(address, nil)) + }() + } + return nil +} + +// Exit stops all running profiles, flushing their output to the +// respective file. +func Exit() { + Handler.StopCPUProfile() + Handler.StopTrace() +} diff --git a/internal/debug/trace.go b/internal/debug/trace.go new file mode 100644 index 0000000000..45637977a8 --- /dev/null +++ b/internal/debug/trace.go @@ -0,0 +1,64 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// 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 . + +//+build go1.5 + +package debug + +import ( + "errors" + "os" + "runtime/trace" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +// StartTrace turns on tracing, writing to the given file. +func (h *HandlerT) StartTrace(file string) error { + h.mu.Lock() + defer h.mu.Unlock() + if h.traceW != nil { + return errors.New("trace already in progress") + } + f, err := os.Create(expandHome(file)) + if err != nil { + return err + } + if err := trace.Start(f); err != nil { + f.Close() + return err + } + h.traceW = f + h.traceFile = file + glog.V(logger.Info).Infoln("trace started, writing to", h.traceFile) + return nil +} + +// StopTrace stops an ongoing trace. +func (h *HandlerT) StopTrace() error { + h.mu.Lock() + defer h.mu.Unlock() + trace.Stop() + if h.traceW == nil { + return errors.New("trace not in progress") + } + glog.V(logger.Info).Infoln("done writing trace to", h.traceFile) + h.traceW.Close() + h.traceW = nil + h.traceFile = "" + return nil +} diff --git a/internal/debug/trace_fallback.go b/internal/debug/trace_fallback.go new file mode 100644 index 0000000000..3199459259 --- /dev/null +++ b/internal/debug/trace_fallback.go @@ -0,0 +1,31 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// 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 . + +//+build !go1.5 + +// no-op implementation of tracing methods for Go < 1.5. + +package debug + +import "errors" + +func (*HandlerT) StartTrace(string) error { + return errors.New("tracing is not supported on Go < 1.5") +} + +func (*HandlerT) StopTrace() error { + return errors.New("tracing is not supported on Go < 1.5") +} diff --git a/node/api.go b/node/api.go index 7b247dc514..bc1795407a 100644 --- a/node/api.go +++ b/node/api.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/rpc" @@ -100,7 +99,6 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { return err == nil, err } - // StartWS starts the websocket RPC API server. func (api *PrivateAdminAPI) StartWS(address string, port int, cors string, apis string) (bool, error) { var offeredAPIs []rpc.API @@ -131,7 +129,7 @@ func (api *PrivateAdminAPI) StartWS(address string, port int, cors string, apis } corsDomains := strings.Split(cors, " ") - + err := rpc.StartWS(address, port, corsDomains, offeredAPIs) return err == nil, err } @@ -179,30 +177,6 @@ func (api *PublicAdminAPI) Datadir() string { return api.node.DataDir() } -// PrivateDebugAPI is the collection of debugging related API methods exposed -// only over a secure RPC channel. -type PrivateDebugAPI struct { - node *Node // Node interfaced by this API -} - -// NewPrivateDebugAPI creates a new API definition for the private debug methods -// of the node itself. -func NewPrivateDebugAPI(node *Node) *PrivateDebugAPI { - return &PrivateDebugAPI{node: node} -} - -// Verbosity updates the node's logging verbosity. Note, due to the lack of fine -// grained contextual loggers, this will update the verbosity level for the entire -// process, not just this node instance. -func (api *PrivateDebugAPI) Verbosity(level int) { - glog.SetV(level) -} - -// Vmodule updates the node's logging verbosity pattern. -func (api *PrivateDebugAPI) Vmodule(pattern string) error { - return glog.SetVmodule(pattern) -} - // PublicDebugAPI is the collection of debugging related API methods exposed over // both secure and unsecure RPC channels. type PublicDebugAPI struct { diff --git a/node/node.go b/node/node.go index 3d077b0bd1..4af111a410 100644 --- a/node/node.go +++ b/node/node.go @@ -26,6 +26,7 @@ import ( "syscall" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" ) @@ -284,7 +285,7 @@ func (n *Node) APIs() []rpc.API { }, { Namespace: "debug", Version: "1.0", - Service: NewPrivateDebugAPI(n), + Service: debug.Handler, }, { Namespace: "debug", Version: "1.0", diff --git a/rpc/javascript.go b/rpc/javascript.go index d147aa0458..9e3b2218bf 100644 --- a/rpc/javascript.go +++ b/rpc/javascript.go @@ -327,7 +327,78 @@ web3._extend({ name: 'metrics', call: 'debug_metrics', params: 1 - }) + }), + new web3._extend.Method({ + name: 'verbosity', + call: 'debug_verbosity', + params: 1 + }), + new web3._extend.Method({ + name: 'vmodule', + call: 'debug_vmodule', + params: 1 + }), + new web3._extend.Method({ + name: 'backtraceAt', + call: 'debug_backtraceAt', + params: 1, + }), + new web3._extend.Method({ + name: 'stacks', + call: 'debug_stacks', + params: 0, + outputFormatter: console.log + }), + new web3._extend.Method({ + name: 'cpuProfile', + call: 'debug_cpuProfile', + params: 2 + }), + new web3._extend.Method({ + name: 'startCPUProfile', + call: 'debug_startCPUProfile', + params: 1 + }), + new web3._extend.Method({ + name: 'stopCPUProfile', + call: 'debug_stopCPUProfile', + params: 0 + }), + new web3._extend.Method({ + name: 'trace', + call: 'debug_trace', + params: 2 + }), + new web3._extend.Method({ + name: 'startTrace', + call: 'debug_startTrace', + params: 1 + }), + new web3._extend.Method({ + name: 'stopTrace', + call: 'debug_stopTrace', + params: 0 + }), + new web3._extend.Method({ + name: 'blockProfile', + call: 'debug_blockProfile', + params: 2 + }), + new web3._extend.Method({ + name: 'setBlockProfileRate', + call: 'debug_setBlockProfileRate', + params: 1 + }), + new web3._extend.Method({ + name: 'writeBlockProfile', + call: 'debug_writeBlockProfile', + params: 1 + }), + new web3._extend.Method({ + name: 'writeMemProfile', + call: 'debug_writeMemProfile', + params: 1 + }), ], properties: [ From f9b2fd79e8b5996479fdb4db5c2d24470802eff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sat, 30 Jan 2016 20:55:44 +0200 Subject: [PATCH 66/78] containers: regroup and add Alpine images (31 MB) --- containers/docker/develop-alpine/Dockerfile | 15 +++++++++++++++ .../docker/develop-ubuntu}/Dockerfile | 0 containers/docker/master-alpine/Dockerfile | 14 ++++++++++++++ .../docker/master-ubuntu}/Dockerfile | 0 4 files changed, 29 insertions(+) create mode 100644 containers/docker/develop-alpine/Dockerfile rename {docker/develop => containers/docker/develop-ubuntu}/Dockerfile (100%) create mode 100644 containers/docker/master-alpine/Dockerfile rename {docker/master => containers/docker/master-ubuntu}/Dockerfile (100%) diff --git a/containers/docker/develop-alpine/Dockerfile b/containers/docker/develop-alpine/Dockerfile new file mode 100644 index 0000000000..6b00bf2538 --- /dev/null +++ b/containers/docker/develop-alpine/Dockerfile @@ -0,0 +1,15 @@ +FROM alpine:3.3 + +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 && \ + rm -rf /go-ethereum && rm -rf /var/cache/apk/* + +EXPOSE 8545 +EXPOSE 30303 + +ENTRYPOINT ["/geth"] diff --git a/docker/develop/Dockerfile b/containers/docker/develop-ubuntu/Dockerfile similarity index 100% rename from docker/develop/Dockerfile rename to containers/docker/develop-ubuntu/Dockerfile diff --git a/containers/docker/master-alpine/Dockerfile b/containers/docker/master-alpine/Dockerfile new file mode 100644 index 0000000000..7a4c535fda --- /dev/null +++ b/containers/docker/master-alpine/Dockerfile @@ -0,0 +1,14 @@ +FROM alpine:3.3 + +RUN \ + apk add --update go git make gcc musl-dev gmp-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 gmp-dev && \ + rm -rf /go-ethereum && rm -rf /var/cache/apk/* + +EXPOSE 8545 +EXPOSE 30303 + +ENTRYPOINT ["/geth"] diff --git a/docker/master/Dockerfile b/containers/docker/master-ubuntu/Dockerfile similarity index 100% rename from docker/master/Dockerfile rename to containers/docker/master-ubuntu/Dockerfile From 0781fd7aa077d73156b6a807b40455ec5dfa5ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sun, 31 Jan 2016 16:19:55 +0200 Subject: [PATCH 67/78] containers/docker/master-alpine: add the legacy gmp lib back for now --- containers/docker/master-alpine/Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/containers/docker/master-alpine/Dockerfile b/containers/docker/master-alpine/Dockerfile index 7a4c535fda..11d4159a3b 100644 --- a/containers/docker/master-alpine/Dockerfile +++ b/containers/docker/master-alpine/Dockerfile @@ -1,11 +1,11 @@ FROM alpine:3.3 RUN \ - apk add --update go git make gcc musl-dev gmp-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 gmp-dev && \ + apk add --update go git make gcc musl-dev gmp-dev gmp && \ + 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 gmp-dev && \ rm -rf /go-ethereum && rm -rf /var/cache/apk/* EXPOSE 8545 From d3642b0715f9168ee523f2cd8414f7fc4fc4ea33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 30 Dec 2015 13:32:15 +0200 Subject: [PATCH 68/78] core, eth, rpc/api: rpc method to inspect the txpool queue --- core/transaction_pool.go | 30 ++++++++++++++++ eth/api.go | 74 ++++++++++++++++++++++++++++++++++++++++ rpc/javascript.go | 10 +++++- 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/core/transaction_pool.go b/core/transaction_pool.go index 54c0ddebb0..456de3cb67 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -169,6 +169,36 @@ func (pool *TxPool) Stats() (pending int, queued int) { return } +// Content retrieves the data content of the transaction pool, returning all the +// pending as well as queued transactions, grouped by account and nonce. +func (pool *TxPool) Content() (map[common.Address]map[uint64][]*types.Transaction, map[common.Address]map[uint64][]*types.Transaction) { + pool.mu.RLock() + defer pool.mu.RUnlock() + + // Retrieve all the pending transactions and sort by account and by nonce + pending := make(map[common.Address]map[uint64][]*types.Transaction) + for _, tx := range pool.pending { + account, _ := tx.From() + + owned, ok := pending[account] + if !ok { + owned = make(map[uint64][]*types.Transaction) + pending[account] = owned + } + owned[tx.Nonce()] = append(owned[tx.Nonce()], tx) + } + // Retrieve all the queued transactions and sort by account and by nonce + queued := make(map[common.Address]map[uint64][]*types.Transaction) + for account, txs := range pool.queue { + owned := make(map[uint64][]*types.Transaction) + for _, tx := range txs { + owned[tx.Nonce()] = append(owned[tx.Nonce()], tx) + } + queued[account] = owned + } + return pending, queued +} + // SetLocal marks a transaction as local, skipping gas price // check against local miner minimum in the future func (pool *TxPool) SetLocal(tx *types.Transaction) { diff --git a/eth/api.go b/eth/api.go index 08b103a4cf..0f55c60a9e 100644 --- a/eth/api.go +++ b/eth/api.go @@ -227,6 +227,39 @@ func NewPublicTxPoolAPI(e *Ethereum) *PublicTxPoolAPI { return &PublicTxPoolAPI{e} } +// Content returns the transactions contained within the transaction pool. +func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string][]*RPCTransaction { + content := map[string]map[string]map[string][]*RPCTransaction{ + "pending": make(map[string]map[string][]*RPCTransaction), + "queued": make(map[string]map[string][]*RPCTransaction), + } + pending, queue := s.e.TxPool().Content() + + // Flatten the pending transactions + for account, batches := range pending { + dump := make(map[string][]*RPCTransaction) + for nonce, txs := range batches { + nonce := fmt.Sprintf("%d", nonce) + for _, tx := range txs { + dump[nonce] = append(dump[nonce], newRPCPendingTransaction(tx)) + } + } + content["pending"][account.Hex()] = dump + } + // Flatten the queued transactions + for account, batches := range queue { + dump := make(map[string][]*RPCTransaction) + for nonce, txs := range batches { + nonce := fmt.Sprintf("%d", nonce) + for _, tx := range txs { + dump[nonce] = append(dump[nonce], newRPCPendingTransaction(tx)) + } + } + content["queued"][account.Hex()] = dump + } + return content +} + // Status returns the number of pending and queued transaction in the pool. func (s *PublicTxPoolAPI) Status() map[string]*rpc.HexNumber { pending, queue := s.e.TxPool().Stats() @@ -236,6 +269,47 @@ func (s *PublicTxPoolAPI) Status() map[string]*rpc.HexNumber { } } +// Inspect retrieves the content of the transaction pool and flattens it into an +// easily inspectable list. +func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string][]string { + content := map[string]map[string]map[string][]string{ + "pending": make(map[string]map[string][]string), + "queued": make(map[string]map[string][]string), + } + pending, queue := s.e.TxPool().Content() + + // Define a formatter to flatten a transaction into a string + var format = func(tx *types.Transaction) string { + if to := tx.To(); to != nil { + return fmt.Sprintf("%s: %v wei + %v × %v gas", tx.To().Hex(), tx.Value(), tx.Gas(), tx.GasPrice()) + } + return fmt.Sprintf("contract creation: %v wei + %v × %v gas", tx.Value(), tx.Gas(), tx.GasPrice()) + } + // Flatten the pending transactions + for account, batches := range pending { + dump := make(map[string][]string) + for nonce, txs := range batches { + nonce := fmt.Sprintf("%d", nonce) + for _, tx := range txs { + dump[nonce] = append(dump[nonce], format(tx)) + } + } + content["pending"][account.Hex()] = dump + } + // Flatten the queued transactions + for account, batches := range queue { + dump := make(map[string][]string) + for nonce, txs := range batches { + nonce := fmt.Sprintf("%d", nonce) + for _, tx := range txs { + dump[nonce] = append(dump[nonce], format(tx)) + } + } + content["queued"][account.Hex()] = dump + } + return content +} + // PublicAccountAPI provides an API to access accounts managed by this node. // It offers only methods that can retrieve accounts. type PublicAccountAPI struct { diff --git a/rpc/javascript.go b/rpc/javascript.go index 9e3b2218bf..c145163e58 100644 --- a/rpc/javascript.go +++ b/rpc/javascript.go @@ -70,9 +70,17 @@ web3._extend({ ], properties: [ + new web3._extend.Property({ + name: 'content', + getter: 'txpool_content' + }), + new web3._extend.Property({ + name: 'inspect', + getter: 'txpool_inspect' + }), new web3._extend.Property({ name: 'status', - getter: 'txpool_status' + getter: 'txpool_status', outputFormatter: function(status) { status.pending = web3._extend.utils.toDecimal(status.pending); status.queued = web3._extend.utils.toDecimal(status.queued); From 15780ead07e650e829a5756857fdbb4e2871356a Mon Sep 17 00:00:00 2001 From: Peter Pratscher Date: Thu, 28 Jan 2016 19:21:05 +0100 Subject: [PATCH 69/78] core/vm, rpc/api: renamed to debug.replayTransaction, migrated to new RPC, integrated feedback Integrated code review suggestions Integrated last review comments --- core/vm/common.go | 2 + core/vm/vm.go | 2 +- eth/api.go | 139 ++++++++++++++++++++++++++++++++++++++++++++++ rpc/javascript.go | 5 ++ 4 files changed, 147 insertions(+), 1 deletion(-) diff --git a/core/vm/common.go b/core/vm/common.go index 2d1aa93326..395ed04710 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -28,6 +28,8 @@ import ( // Global Debug flag indicating Debug VM (full logging) var Debug bool +var GenerateStructLogs bool = false + // Type is the VM type accepted by **NewVm** type Type byte diff --git a/core/vm/vm.go b/core/vm/vm.go index 8e07aaa899..0c6bbcd42c 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -367,7 +367,7 @@ func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Co // log emits a log event to the environment for each opcode encountered. This is not to be confused with the // LOG* opcode. func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *stack, contract *Contract, err error) { - if Debug { + if Debug || GenerateStructLogs { mem := make([]byte, len(memory.Data())) copy(mem, memory.Data()) diff --git a/eth/api.go b/eth/api.go index 08b103a4cf..f287b1b014 100644 --- a/eth/api.go +++ b/eth/api.go @@ -1427,6 +1427,145 @@ func (api *PrivateDebugAPI) SetHead(number uint64) { api.eth.BlockChain().SetHead(number) } +// StructLogRes stores a structured log emitted by the evm while replaying a +// transaction in debug mode +type structLogRes struct { + Pc uint64 `json:"pc"` + Op string `json:"op"` + Gas *big.Int `json:"gas"` + GasCost *big.Int `json:"gasCost"` + Error error `json:"error"` + Stack []string `json:"stack"` + Memory map[string]string `json:"memory"` + Storage map[string]string `json:"storage"` +} + +// TransactionExecutionRes groups all structured logs emitted by the evm +// while replaying a transaction in debug mode as well as the amount of +// gas used and the return value +type TransactionExecutionResult struct { + Gas *big.Int `json:"gas"` + ReturnValue string `json:"returnValue"` + StructLogs []structLogRes `json:"structLogs"` +} + +func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLog, []byte, *big.Int, error) { + // Retrieve the tx from the chain + tx, _, blockIndex, _ := core.GetTransaction(s.eth.ChainDb(), txHash) + + if tx == nil { + return nil, nil, nil, fmt.Errorf("Transaction not found") + } + + block := s.eth.BlockChain().GetBlockByNumber(blockIndex - 1) + if block == nil { + return nil, nil, nil, fmt.Errorf("Unable to retrieve prior block") + } + + // Create the state database + stateDb, err := state.New(block.Root(), s.eth.ChainDb()) + if err != nil { + return nil, nil, nil, err + } + + txFrom, err := tx.From() + + if err != nil { + return nil, nil, nil, fmt.Errorf("Unable to create transaction sender") + } + from := stateDb.GetOrNewStateObject(txFrom) + msg := callmsg{ + from: from, + to: tx.To(), + gas: tx.Gas(), + gasPrice: tx.GasPrice(), + value: tx.Value(), + data: tx.Data(), + } + + vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header()) + gp := new(core.GasPool).AddGas(block.GasLimit()) + vm.GenerateStructLogs = true + defer func() { vm.GenerateStructLogs = false }() + + ret, gas, err := core.ApplyMessage(vmenv, msg, gp) + if err != nil { + return nil, nil, nil, fmt.Errorf("Error executing transaction %v", err) + } + + return vmenv.StructLogs(), ret, gas, nil +} + +// Executes a transaction and returns the structured logs of the evm +// gathered during the execution +func (s *PrivateDebugAPI) ReplayTransaction(txHash common.Hash, stackDepth int, memorySize int, storageSize int) (*TransactionExecutionResult, error) { + + structLogs, ret, gas, err := s.doReplayTransaction(txHash) + + if err != nil { + return nil, err + } + + res := TransactionExecutionResult{ + Gas: gas, + ReturnValue: fmt.Sprintf("%x", ret), + StructLogs: make([]structLogRes, len(structLogs)), + } + + for index, trace := range structLogs { + + stackLength := len(trace.Stack) + + // Return full stack by default + if stackDepth != -1 && stackDepth < stackLength { + stackLength = stackDepth + } + + res.StructLogs[index] = structLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Error: trace.Err, + Stack: make([]string, stackLength), + Memory: make(map[string]string), + Storage: make(map[string]string), + } + + for i := 0; i < stackLength; i++ { + res.StructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(trace.Stack[i].Bytes(), 32)) + } + + addr := 0 + memorySizeLocal := memorySize + + // Return full memory by default + if memorySize == -1 { + memorySizeLocal = len(trace.Memory) + } + + for i := 0; i+16 <= len(trace.Memory) && addr < memorySizeLocal; i += 16 { + res.StructLogs[index].Memory[fmt.Sprintf("%04d", addr*16)] = fmt.Sprintf("%x", trace.Memory[i:i+16]) + addr++ + } + + storageLength := len(trace.Stack) + if storageSize != -1 && storageSize < storageLength { + storageLength = storageSize + } + + i := 0 + for storageIndex, storageValue := range trace.Storage { + if i >= storageLength { + break + } + res.StructLogs[index].Storage[fmt.Sprintf("%x", storageIndex)] = fmt.Sprintf("%x", storageValue) + i++ + } + } + return &res, nil +} + // PublicNetAPI offers network related RPC methods type PublicNetAPI struct { net *p2p.Server diff --git a/rpc/javascript.go b/rpc/javascript.go index 9e3b2218bf..967c82ea62 100644 --- a/rpc/javascript.go +++ b/rpc/javascript.go @@ -399,6 +399,11 @@ web3._extend({ call: 'debug_writeMemProfile', params: 1 }), + new web3._extend.Method({ + name: 'replayTransaction', + call: 'debug_replayTransaction', + params: 4 + }) ], properties: [ From bddf8f76c862fc4d096e8f51cc09b16640e3f13a Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 27 Jan 2016 08:38:53 +0100 Subject: [PATCH 70/78] account/abi: implements event parsing Implementation of basic event parsing and its input types. This separates methods and events and fixes an issue with go type parsing and validation. --- accounts/abi/abi.go | 32 +++++++++++++-- accounts/abi/abi_test.go | 82 +++++++++++++++++++++++++++----------- accounts/abi/argument.go | 5 ++- accounts/abi/event.go | 44 ++++++++++++++++++++ accounts/abi/event_test.go | 40 +++++++++++++++++++ accounts/abi/type.go | 2 +- 6 files changed, 174 insertions(+), 31 deletions(-) create mode 100644 accounts/abi/event.go create mode 100644 accounts/abi/event_test.go diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 635dc43fe4..b84fd463ad 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -37,6 +37,7 @@ type Executer func(datain []byte) []byte // packs data accordingly. type ABI struct { Methods map[string]Method + Events map[string]Event } // JSON returns a parsed ABI interface and error if it failed. @@ -149,14 +150,37 @@ func (abi ABI) Call(executer Executer, name string, args ...interface{}) interfa } func (abi *ABI) UnmarshalJSON(data []byte) error { - var methods []Method - if err := json.Unmarshal(data, &methods); err != nil { + var fields []struct { + Type string + Name string + Const bool + Indexed bool + Inputs []Argument + Outputs []Argument + } + + if err := json.Unmarshal(data, &fields); err != nil { return err } abi.Methods = make(map[string]Method) - for _, method := range methods { - abi.Methods[method.Name] = method + abi.Events = make(map[string]Event) + for _, field := range fields { + switch field.Type { + // empty defaults to function according to the abi spec + case "function", "": + abi.Methods[field.Name] = Method{ + Name: field.Name, + Const: field.Const, + Inputs: field.Inputs, + Outputs: field.Outputs, + } + case "event": + abi.Events[field.Name] = Event{ + Name: field.Name, + Inputs: field.Inputs, + } + } } return nil diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index d514fb8c72..000c118f89 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -31,25 +31,25 @@ import ( const jsondata = ` [ - { "name" : "balance", "const" : true }, - { "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } + { "type" : "function", "name" : "balance", "const" : true }, + { "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } ]` const jsondata2 = ` [ - { "name" : "balance", "const" : true }, - { "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, - { "name" : "test", "const" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, - { "name" : "string", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, - { "name" : "bool", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, - { "name" : "address", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, - { "name" : "string32", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string32" } ] }, - { "name" : "uint64[2]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, - { "name" : "uint64[]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, - { "name" : "foo", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, - { "name" : "bar", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, - { "name" : "slice", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, - { "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] } + { "type" : "function", "name" : "balance", "const" : true }, + { "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, + { "type" : "function", "name" : "test", "const" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, + { "type" : "function", "name" : "string", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, + { "type" : "function", "name" : "bool", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, + { "type" : "function", "name" : "address", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, + { "type" : "function", "name" : "string32", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string32" } ] }, + { "type" : "function", "name" : "uint64[2]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, + { "type" : "function", "name" : "uint64[]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, + { "type" : "function", "name" : "foo", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, + { "type" : "function", "name" : "bar", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, + { "type" : "function", "name" : "slice", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, + { "type" : "function", "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] } ]` func TestType(t *testing.T) { @@ -96,7 +96,7 @@ func TestReader(t *testing.T) { }, "send": Method{ "send", false, []Argument{ - Argument{"amount", Uint256}, + Argument{"amount", Uint256, false}, }, nil, }, }, @@ -238,7 +238,7 @@ func TestTestAddress(t *testing.T) { func TestMethodSignature(t *testing.T) { String, _ := NewType("string") String32, _ := NewType("string32") - m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, nil} + m := Method{"foo", false, []Argument{Argument{"bar", String32, false}, Argument{"baz", String, false}}, nil} exp := "foo(string32,string)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -250,7 +250,7 @@ func TestMethodSignature(t *testing.T) { } uintt, _ := NewType("uint") - m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, nil} + m = Method{"foo", false, []Argument{Argument{"bar", uintt, false}}, nil} exp = "foo(uint256)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -367,8 +367,8 @@ func ExampleJSON() { func TestBytes(t *testing.T) { const definition = `[ - { "name" : "balance", "const" : true, "inputs" : [ { "name" : "address", "type" : "bytes20" } ] }, - { "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } + { "type" : "function", "name" : "balance", "const" : true, "inputs" : [ { "name" : "address", "type" : "bytes20" } ] }, + { "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } ]` abi, err := JSON(strings.NewReader(definition)) @@ -396,10 +396,8 @@ func TestBytes(t *testing.T) { func TestReturn(t *testing.T) { const definition = `[ - { "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] }, - { "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] } - -]` + { "type" : "function", "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] }, + { "type" : "function", "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] }]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -424,3 +422,39 @@ func TestReturn(t *testing.T) { t.Errorf("expected type common.Address, got %T", r) } } + +func TestDefaultFunctionParsing(t *testing.T) { + const definition = `[{ "name" : "balance" }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if _, ok := abi.Methods["balance"]; !ok { + t.Error("expected 'balance' to be present") + } +} + +func TestBareEvents(t *testing.T) { + const definition = `[ + { "type" : "event", "name" : "balance" }, + { "type" : "event", "name" : "name" }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if len(abi.Events) != 2 { + t.Error("expected 2 events") + } + + if _, ok := abi.Events["balance"]; !ok { + t.Error("expected 'balance' event to be present") + } + + if _, ok := abi.Events["name"]; !ok { + t.Error("expected 'name' event to be present") + } +} diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 8907b29793..188203a5d1 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -24,8 +24,9 @@ import ( // Argument holds the name of the argument and the corresponding type. // Types are used when packing and testing arguments. type Argument struct { - Name string - Type Type + Name string + Type Type + Indexed bool // indexed is only used by events } func (a *Argument) UnmarshalJSON(data []byte) error { diff --git a/accounts/abi/event.go b/accounts/abi/event.go new file mode 100644 index 0000000000..7c4e092ea5 --- /dev/null +++ b/accounts/abi/event.go @@ -0,0 +1,44 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// 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 . + +package abi + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// Event is an event potentially triggered by the EVM's LOG mechanism. The Event +// holds type information (inputs) about the yielded output +type Event struct { + Name string + Inputs []Argument +} + +// Id returns the canonical representation of the event's signature used by the +// abi definition to identify event names and types. +func (e Event) Id() common.Hash { + types := make([]string, len(e.Inputs)) + i := 0 + for _, input := range e.Inputs { + types[i] = input.Type.String() + i++ + } + return common.BytesToHash(crypto.Sha3([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ","))))) +} diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go new file mode 100644 index 0000000000..34a7a1684e --- /dev/null +++ b/accounts/abi/event_test.go @@ -0,0 +1,40 @@ +package abi + +import ( + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestEventId(t *testing.T) { + var table = []struct { + definition string + expectations map[string]common.Hash + }{ + { + definition: `[ + { "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint" }] }, + { "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] } + ]`, + expectations: map[string]common.Hash{ + "balance": crypto.Sha3Hash([]byte("balance(uint256)")), + "check": crypto.Sha3Hash([]byte("check(address,uint256)")), + }, + }, + } + + for _, test := range table { + abi, err := JSON(strings.NewReader(test.definition)) + if err != nil { + t.Fatal(err) + } + + for name, event := range abi.Events { + if event.Id() != test.expectations[name] { + t.Errorf("expected id to be %x, got %x", test.expectations[name], event.Id()) + } + } + } +} diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 8f0238fc99..32f761ef0b 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -218,5 +218,5 @@ func (t Type) pack(v interface{}) ([]byte, error) { } } - return nil, fmt.Errorf("ABI: bad input given %T", value.Kind()) + return nil, fmt.Errorf("ABI: bad input given %v", value.Kind()) } From 1894c9239f2ab48d29cb71dd1af72e6a83a5f9b5 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Mon, 1 Feb 2016 13:52:27 +0100 Subject: [PATCH 71/78] CONTRIBUTING: Added a feature section --- CONTRIBUTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 918a2c1546..829bf5d43b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,12 @@ +## Can I have feature X + +Before you do a feature request please check and make sure that it isn't possible +through some other means. The JavaScript enabled console is a powerful feature +in the right hands. Please check our [Bitchin' tricks](https://github.com/ethereum/go-ethereum/wiki/bitchin-tricks) wiki page for more info +and help. + +## Contributing + If you'd like to contribute to go-ethereum please fork, fix, commit and send a pull request. Commits who do not comply with the coding standards are ignored (use gofmt!). If you send pull requests make absolute sure that you From 9aa715805779b6d8856cd36edff01ec421d83f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 2 Feb 2016 20:22:21 +0200 Subject: [PATCH 72/78] miner: register newly created remote agent in the API --- miner/api.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/miner/api.go b/miner/api.go index 42678b5ad7..fae2203f52 100644 --- a/miner/api.go +++ b/miner/api.go @@ -33,7 +33,10 @@ type PublicMinerAPI struct { // NewPublicMinerAPI create a new PublicMinerAPI instance. func NewPublicMinerAPI(miner *Miner) *PublicMinerAPI { - return &PublicMinerAPI{miner, NewRemoteAgent()} + agent := NewRemoteAgent() + miner.Register(agent) + + return &PublicMinerAPI{miner, agent} } // Mining returns an indication if this node is currently mining. From 72c255caef14a00e7d82527beefc4f4b9c5a2b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 2 Feb 2016 19:29:54 +0200 Subject: [PATCH 73/78] rpc: allow RPC requests on GET too --- rpc/http.go | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/rpc/http.go b/rpc/http.go index e4b25bed8b..c5eb41af14 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -18,21 +18,19 @@ package rpc import ( "bufio" - "fmt" - "io" - "net" - "net/http" - "strconv" - "strings" - "time" - - "errors" - "sync" - "bytes" "encoding/json" + "errors" + "fmt" + "io" "io/ioutil" + "net" + "net/http" "net/url" + "strconv" + "strings" + "sync" + "time" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -44,8 +42,8 @@ const ( ) var ( - httpServerMu sync.Mutex // prevent concurrent access to the httpListener and httpServer - httpListener net.Listener // listener for the http server + httpServerMu sync.Mutex // prevent concurrent access to the httpListener and httpServer + httpListener net.Listener // listener for the http server httpRPCServer *Server // the node can only start 1 HTTP RPC server instance ) @@ -57,8 +55,7 @@ var ( type httpMessageStream struct { conn net.Conn // TCP connection rw *bufio.ReadWriter // buffered where HTTP requests/responses are read/written from/to - currentReq *http.Request // pending request, codec can pass in a too small buffer for a single read - // we need to keep track of the current requests if it was not read at once + currentReq *http.Request // pending request, codec can pass in a too small buffer for a single read we need to keep track of the current requests if it was not read at once payloadBytesRead int64 // number of bytes which are read from the current request allowedOrigins *set.Set // allowed CORS domains origin string // origin of this connection/request @@ -147,7 +144,7 @@ func (h *httpMessageStream) Read(buf []byte) (n int, err error) { continue } - if strings.EqualFold(h.currentReq.Method, "POST") { + if strings.EqualFold(h.currentReq.Method, "GET") || strings.EqualFold(h.currentReq.Method, "POST") { n, err := h.currentReq.Body.Read(buf) h.payloadBytesRead += int64(n) @@ -164,12 +161,9 @@ func (h *httpMessageStream) Read(buf []byte) (n int, err error) { h.currentReq = nil h.payloadBytesRead = 0 } - // partial read of body return n, err } - - h.currentReq = nil return 0, fmt.Errorf("unsupported HTTP method '%s'", h.currentReq.Method) } } @@ -212,17 +206,17 @@ func httpTimestamp(t time.Time) []byte { t = t.UTC() yy, mm, dd := t.Date() hh, mn, ss := t.Clock() - day := days[3 * t.Weekday():] - mon := months[3 * (mm - 1):] + day := days[3*t.Weekday():] + mon := months[3*(mm-1):] return append(b, day[0], day[1], day[2], ',', ' ', - byte('0' + dd / 10), byte('0' + dd % 10), ' ', + byte('0'+dd/10), byte('0'+dd%10), ' ', mon[0], mon[1], mon[2], ' ', - byte('0' + yy / 1000), byte('0' + (yy / 100) % 10), byte('0' + (yy / 10) % 10), byte('0' + yy % 10), ' ', - byte('0' + hh / 10), byte('0' + hh % 10), ':', - byte('0' + mn / 10), byte('0' + mn % 10), ':', - byte('0' + ss / 10), byte('0' + ss % 10), ' ', + byte('0'+yy/1000), byte('0'+(yy/100)%10), byte('0'+(yy/10)%10), byte('0'+yy%10), ' ', + byte('0'+hh/10), byte('0'+hh%10), ':', + byte('0'+mn/10), byte('0'+mn%10), ':', + byte('0'+ss/10), byte('0'+ss%10), ' ', 'G', 'M', 'T') } From 5da7ec7c18b0e37cec0950e9656f6cdddc1ae60c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 27 Jan 2016 18:01:30 +0200 Subject: [PATCH 74/78] cmd, eth, rpc: fix some RPC issues with pending blocks --- cmd/gethrpctest/main.go | 4 + eth/api.go | 204 +++++++++++++++++++--------------------- eth/filters/api.go | 4 +- rpc/server.go | 2 - rpc/types.go | 13 +-- 5 files changed, 110 insertions(+), 117 deletions(-) diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index b4530ca51f..8522258a9e 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -52,6 +52,10 @@ var ( func main() { flag.Parse() + // Enable logging errors, we really do want to see those + glog.SetV(2) + glog.SetToStderr(true) + // Load the test suite to run the RPC against tests, err := tests.LoadBlockTests(*testFile) if err != nil { diff --git a/eth/api.go b/eth/api.go index bc7d175347..0fa855f156 100644 --- a/eth/api.go +++ b/eth/api.go @@ -53,19 +53,40 @@ const ( defaultGas = uint64(90000) ) -// blockByNumber is a commonly used helper function which retrieves and returns the block for the given block number. It -// returns nil when no block could be found. +// blockByNumber is a commonly used helper function which retrieves and returns +// the block for the given block number, capable of handling two special blocks: +// rpc.LatestBlockNumber adn rpc.PendingBlockNumber. It returns nil when no block +// could be found. func blockByNumber(m *miner.Miner, bc *core.BlockChain, blockNr rpc.BlockNumber) *types.Block { + // Pending block is only known by the miner if blockNr == rpc.PendingBlockNumber { return m.PendingBlock() } + // Otherwise resolve and return the block if blockNr == rpc.LatestBlockNumber { return bc.CurrentBlock() } - return bc.GetBlockByNumber(uint64(blockNr)) } +// stateAndBlockByNumber is a commonly used helper function which retrieves and +// returns the state and containing block for the given block number, capable of +// handling two special states: rpc.LatestBlockNumber adn rpc.PendingBlockNumber. +// It returns nil when no block or state could be found. +func stateAndBlockByNumber(m *miner.Miner, bc *core.BlockChain, blockNr rpc.BlockNumber, chainDb ethdb.Database) (*state.StateDB, *types.Block, error) { + // Pending state is only known by the miner + if blockNr == rpc.PendingBlockNumber { + return m.PendingState(), m.PendingBlock(), nil + } + // Otherwise resolve the block number and return its state + block := blockByNumber(m, bc, blockNr) + if block == nil { + return nil, nil, nil + } + stateDb, err := state.New(block.Root(), chainDb) + return stateDb, block, err +} + // PublicEthereumAPI provides an API to access Ethereum related information. // It offers only methods that operate on public data that is freely available to anyone. type PublicEthereumAPI struct { @@ -395,16 +416,12 @@ func (s *PublicBlockChainAPI) BlockNumber() *big.Int { return s.bc.CurrentHeader().Number } -// GetBalance returns the amount of wei for the given address in the state of the given block number. -// When block number equals rpc.LatestBlockNumber the current block is used. +// GetBalance returns the amount of wei for the given address in the state of the +// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta +// block numbers are also allowed. func (s *PublicBlockChainAPI) GetBalance(address common.Address, blockNr rpc.BlockNumber) (*big.Int, error) { - block := blockByNumber(s.miner, s.bc, blockNr) - if block == nil { - return nil, nil - } - - state, err := state.New(block.Root(), s.chainDb) - if err != nil { + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { return nil, err } return state.GetBalance(address), nil @@ -414,7 +431,14 @@ func (s *PublicBlockChainAPI) GetBalance(address common.Address, blockNr rpc.Blo // transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { - return s.rpcOutputBlock(block, true, fullTx) + response, err := s.rpcOutputBlock(block, true, fullTx) + if err == nil && blockNr == rpc.PendingBlockNumber { + // Pending blocks need to nil out a few fields + for _, field := range []string{"hash", "nonce", "logsBloom", "miner"} { + response[field] = nil + } + } + return response, err } return nil, nil } @@ -431,10 +455,6 @@ func (s *PublicBlockChainAPI) GetBlockByHash(blockHash common.Hash, fullTx bool) // GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true // all transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(blockNr rpc.BlockNumber, index rpc.HexNumber) (map[string]interface{}, error) { - if blockNr == rpc.PendingBlockNumber { - return nil, nil - } - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { uncles := block.Uncles() if index.Int() < 0 || index.Int() >= len(uncles) { @@ -464,10 +484,6 @@ func (s *PublicBlockChainAPI) GetUncleByBlockHashAndIndex(blockHash common.Hash, // GetUncleCountByBlockNumber returns number of uncles in the block for the given block number func (s *PublicBlockChainAPI) GetUncleCountByBlockNumber(blockNr rpc.BlockNumber) *rpc.HexNumber { - if blockNr == rpc.PendingBlockNumber { - return rpc.NewHexNumber(0) - } - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return rpc.NewHexNumber(len(block.Uncles())) } @@ -508,38 +524,26 @@ func (s *PublicBlockChainAPI) NewBlocks(args NewBlocksArgs) (rpc.Subscription, e // GetCode returns the code stored at the given address in the state for the given block number. func (s *PublicBlockChainAPI) GetCode(address common.Address, blockNr rpc.BlockNumber) (string, error) { - return s.GetData(address, blockNr) -} - -// GetData returns the data stored at the given address in the state for the given block number. -func (s *PublicBlockChainAPI) GetData(address common.Address, blockNr rpc.BlockNumber) (string, error) { - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { - state, err := state.New(block.Root(), s.chainDb) - if err != nil { - return "", err - } - res := state.GetCode(address) - if len(res) == 0 { // backwards compatibility - return "0x", nil - } - return common.ToHex(res), nil + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { + return "", err } - - return "0x", nil + res := state.GetCode(address) + if len(res) == 0 { // backwards compatibility + return "0x", nil + } + return common.ToHex(res), nil } -// GetStorageAt returns the storage from the state at the given address, key and block number. +// GetStorageAt returns the storage from the state at the given address, key and +// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block +// numbers are also allowed. func (s *PublicBlockChainAPI) GetStorageAt(address common.Address, key string, blockNr rpc.BlockNumber) (string, error) { - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { - state, err := state.New(block.Root(), s.chainDb) - if err != nil { - return "", err - } - - return state.GetState(address, common.HexToHash(key)).Hex(), nil + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { + return "0x", err } - - return "0x", nil + return state.GetState(address, common.HexToHash(key)).Hex(), nil } // callmsg is the message type used for call transations. @@ -570,55 +574,51 @@ type CallArgs struct { } func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (string, *big.Int, error) { - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { - stateDb, err := state.New(block.Root(), s.chainDb) - if err != nil { - return "0x", nil, err - } - - stateDb = stateDb.Copy() - var from *state.StateObject - if args.From == (common.Address{}) { - accounts, err := s.am.Accounts() - if err != nil || len(accounts) == 0 { - from = stateDb.GetOrNewStateObject(common.Address{}) - } else { - from = stateDb.GetOrNewStateObject(accounts[0].Address) - } - } else { - from = stateDb.GetOrNewStateObject(args.From) - } - - from.SetBalance(common.MaxBig) - - msg := callmsg{ - from: from, - to: &args.To, - gas: args.Gas.BigInt(), - gasPrice: args.GasPrice.BigInt(), - value: args.Value.BigInt(), - data: common.FromHex(args.Data), - } - - if msg.gas.Cmp(common.Big0) == 0 { - msg.gas = big.NewInt(50000000) - } - - if msg.gasPrice.Cmp(common.Big0) == 0 { - msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) - } - - header := s.bc.CurrentBlock().Header() - vmenv := core.NewEnv(stateDb, s.bc, msg, header) - gp := new(core.GasPool).AddGas(common.MaxBig) - res, gas, err := core.ApplyMessage(vmenv, msg, gp) - if len(res) == 0 { // backwards compatability - return "0x", gas, err - } - return common.ToHex(res), gas, err + // Fetch the state associated with the block number + stateDb, block, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if stateDb == nil || err != nil { + return "0x", nil, err } + stateDb = stateDb.Copy() - return "0x", common.Big0, nil + // Retrieve the account state object to interact with + var from *state.StateObject + if args.From == (common.Address{}) { + accounts, err := s.am.Accounts() + if err != nil || len(accounts) == 0 { + from = stateDb.GetOrNewStateObject(common.Address{}) + } else { + from = stateDb.GetOrNewStateObject(accounts[0].Address) + } + } else { + from = stateDb.GetOrNewStateObject(args.From) + } + from.SetBalance(common.MaxBig) + + // Assemble the CALL invocation + msg := callmsg{ + from: from, + to: &args.To, + gas: args.Gas.BigInt(), + gasPrice: args.GasPrice.BigInt(), + value: args.Value.BigInt(), + data: common.FromHex(args.Data), + } + if msg.gas.Cmp(common.Big0) == 0 { + msg.gas = big.NewInt(50000000) + } + if msg.gasPrice.Cmp(common.Big0) == 0 { + msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + } + // Execute the call and return + vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header()) + gp := new(core.GasPool).AddGas(common.MaxBig) + + res, gas, err := core.ApplyMessage(vmenv, msg, gp) + if len(res) == 0 { // backwards compatibility + return "0x", gas, err + } + return common.ToHex(res), gas, err } // Call executes the given transaction on the state for the given block number. @@ -802,14 +802,9 @@ func getTransaction(chainDb ethdb.Database, txPool *core.TxPool, txHash common.H // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByNumber(blockNr rpc.BlockNumber) *rpc.HexNumber { - if blockNr == rpc.PendingBlockNumber { - return rpc.NewHexNumber(0) - } - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return rpc.NewHexNumber(len(block.Transactions())) } - return nil } @@ -839,13 +834,8 @@ func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(blockHash c // GetTransactionCount returns the number of transactions the given address has sent for the given block number func (s *PublicTransactionPoolAPI) GetTransactionCount(address common.Address, blockNr rpc.BlockNumber) (*rpc.HexNumber, error) { - block := blockByNumber(s.miner, s.bc, blockNr) - if block == nil { - return nil, nil - } - - state, err := state.New(block.Root(), s.chainDb) - if err != nil { + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { return nil, err } return rpc.NewHexNumber(state.GetNonce(address)), nil diff --git a/eth/filters/api.go b/eth/filters/api.go index f2b0ed32f5..aa4c305a6f 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -239,13 +239,13 @@ func (args *NewFilterArgs) UnmarshalJSON(data []byte) error { return err } - if raw.From == nil { + if raw.From == nil || raw.From.Int64() < 0 { args.FromBlock = rpc.LatestBlockNumber } else { args.FromBlock = *raw.From } - if raw.ToBlock == nil { + if raw.ToBlock == nil || raw.ToBlock.Int64() < 0 { args.ToBlock = rpc.LatestBlockNumber } else { args.ToBlock = *raw.ToBlock diff --git a/rpc/server.go b/rpc/server.go index 0b93a4e643..5b88d843ae 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -332,7 +332,6 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque return res } } - return codec.CreateResponse(req.id, reply[0].Interface()) } @@ -344,7 +343,6 @@ func (s *Server) exec(ctx context.Context, codec ServerCodec, req *serverRequest } else { response = s.handle(ctx, codec, req) } - if err := codec.Write(response); err != nil { glog.V(logger.Error).Infof("%v\n", err) codec.Close() diff --git a/rpc/types.go b/rpc/types.go index 02295a022e..f268d84db2 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -174,12 +174,14 @@ type HexNumber big.Int // NewHexNumber creates a new hex number instance which will serialize the given val with `%#x` on marshal. func NewHexNumber(val interface{}) *HexNumber { if val == nil { - return nil + return nil // note, this doesn't catch nil pointers, only passing nil directly! } - if v, ok := val.(*big.Int); ok && v != nil { - hn := new(big.Int).Set(v) - return (*HexNumber)(hn) + if v, ok := val.(*big.Int); ok { + if v != nil { + return (*HexNumber)(new(big.Int).Set(v)) + } + return nil } rval := reflect.ValueOf(val) @@ -303,10 +305,9 @@ const ( ) // UnmarshalJSON parses the given JSON fragement into a BlockNumber. It supports: -// - "latest" or "earliest" as string arguments +// - "latest", "earliest" or "pending" as string arguments // - the block number // Returned errors: -// - an unsupported error when "pending" is specified (not yet implemented) // - an invalid block number error when the given argument isn't a known strings // - an out of range error when the given block number is either too little or too large func (bn *BlockNumber) UnmarshalJSON(data []byte) error { From 188ab928c3f2a2eac5ee0f7ac42cbf2f35568bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 2 Feb 2016 19:06:43 +0200 Subject: [PATCH 75/78] cmd, common, node, rpc: move IPC into the node itself --- cmd/geth/main.go | 7 +-- cmd/geth/monitorcmd.go | 4 +- cmd/gethrpctest/main.go | 61 +---------------------- cmd/utils/client.go | 4 +- cmd/utils/flags.go | 71 +++++---------------------- common/path.go | 9 ++-- node/config.go | 40 +++++++++++++++ node/config_test.go | 32 ++++++++++++ node/node.go | 100 ++++++++++++++++++++++++++++++++++--- node/node_example_test.go | 1 + node/node_test.go | 101 +++++++++++++++++++++++++++++++++----- node/service_test.go | 2 +- node/utils_test.go | 14 +++++- rpc/json.go | 2 +- 14 files changed, 292 insertions(+), 156 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 09c7eee050..fa456a7ac5 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -502,12 +502,7 @@ func startNode(ctx *cli.Context, stack *node.Node) { unlockAccount(ctx, accman, trimmed, i, passwords) } } - // Start auxiliary services if enabled. - if !ctx.GlobalBool(utils.IPCDisabledFlag.Name) { - if err := utils.StartIPC(stack, ctx); err != nil { - utils.Fatalf("Failed to start IPC: %v", err) - } - } + // Start auxiliary services if enabled if ctx.GlobalBool(utils.RPCEnabledFlag.Name) { if err := utils.StartRPC(stack, ctx); err != nil { utils.Fatalf("Failed to start RPC: %v", err) diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 1d7bf3f6ac..4d56f22893 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -28,7 +28,7 @@ import ( "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/gizak/termui" ) @@ -36,7 +36,7 @@ import ( var ( monitorCommandAttachFlag = cli.StringFlag{ Name: "attach", - Value: "ipc:" + common.DefaultIpcPath(), + Value: "ipc:" + node.DefaultIpcEndpoint(), Usage: "API endpoint to attach to", } monitorCommandRowsFlag = cli.IntFlag{ diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index 8522258a9e..becd09f5ab 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -18,25 +18,20 @@ package main import ( + "errors" "flag" "io/ioutil" "log" "os" "os/signal" - "path/filepath" - "runtime" - - "errors" "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/whisper" ) @@ -89,11 +84,6 @@ func main() { } log.Println("Initial test suite passed...") - if err := StartIPC(stack); err != nil { - log.Fatalf("Failed to start IPC interface: %v\n", err) - } - log.Println("IPC Interface started, accepting requests...") - // Start the RPC interface and wait until terminated if err := StartRPC(stack); err != nil { log.Fatalf("Failed to start RPC interface: %v", err) @@ -109,7 +99,7 @@ func main() { // keystore path and initial pre-state. func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node.Node, error) { // Create a networkless protocol stack - stack, err := node.New(&node.Config{NoDiscovery: true}) + stack, err := node.New(&node.Config{IpcPath: node.DefaultIpcEndpoint(), NoDiscovery: true}) if err != nil { return nil, err } @@ -194,50 +184,3 @@ func StartRPC(stack *node.Node) error { glog.V(logger.Error).Infof("Unable to start RPC-HTTP interface, could not find admin API") return errors.New("Unable to start RPC-HTTP interface") } - -// StartIPC initializes an IPC interface to the given protocol stack. -func StartIPC(stack *node.Node) error { - var ethereum *eth.Ethereum - if err := stack.Service(ðereum); err != nil { - return err - } - - endpoint := `\\.\pipe\geth.ipc` - if runtime.GOOS != "windows" { - endpoint = filepath.Join(common.DefaultDataDir(), "geth.ipc") - } - - listener, err := rpc.CreateIPCListener(endpoint) - if err != nil { - return err - } - - server := rpc.NewServer() - - // register package API's this node provides - offered := stack.APIs() - for _, api := range offered { - server.RegisterName(api.Namespace, api.Service) - glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) - } - - //var ethereum *eth.Ethereum - //if err := stack.Service(ðereum); err != nil { - // return err - //} - - go func() { - glog.V(logger.Info).Infof("Start IPC server on %s\n", endpoint) - for { - conn, err := listener.Accept() - if err != nil { - glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err) - } - - codec := rpc.NewJSONCodec(conn) - go server.ServeCodec(codec) - } - }() - - return nil -} diff --git a/cmd/utils/client.go b/cmd/utils/client.go index bac4564911..40ebcd7294 100644 --- a/cmd/utils/client.go +++ b/cmd/utils/client.go @@ -150,10 +150,8 @@ func NewRemoteRPCClient(ctx *cli.Context) (rpc.Client, error) { endpoint := ctx.Args().First() return NewRemoteRPCClientFromString(endpoint) } - // use IPC by default - endpoint := IPCSocketPath(ctx) - return rpc.NewIPCClient(endpoint) + return rpc.NewIPCClient(node.DefaultIpcEndpoint()) } // NewRemoteRPCClientFromString returns a RPC client which connects to the given diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1e3e58e510..5d56ba7d01 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -261,8 +261,8 @@ var ( } IPCPathFlag = DirectoryFlag{ Name: "ipcpath", - Usage: "Filename for IPC socket/pipe", - Value: DirectoryString{common.DefaultIpcPath()}, + Usage: "Filename for IPC socket/pipe within the datadir (explicit paths escape it)", + Value: DirectoryString{common.DefaultIpcSocket()}, } WSEnabledFlag = cli.BoolFlag{ Name: "ws", @@ -394,6 +394,15 @@ func MustMakeDataDir(ctx *cli.Context) string { return "" } +// MakeIpcPath creates an IPC path configuration from the set command line flags, +// returning an empty string if IPC was explicitly disabled, or the set path. +func MakeIpcPath(ctx *cli.Context) string { + if ctx.GlobalBool(IPCDisabledFlag.Name) { + return "" + } + return ctx.GlobalString(IPCPathFlag.Name) +} + // MakeNodeKey creates a node key from set command line flags, either loading it // from a file or as a specified hex value. If neither flags were provided, this // method returns nil and an emphemeral key is to be generated. @@ -582,6 +591,7 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. // Configure the node's service container stackConf := &node.Config{ DataDir: MustMakeDataDir(ctx), + IpcPath: MakeIpcPath(ctx), PrivateKey: MakeNodeKey(ctx), Name: MakeNodeName(name, version, ctx), NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name), @@ -734,63 +744,6 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database return chain, chainDb } -func IPCSocketPath(ctx *cli.Context) (ipcpath string) { - if runtime.GOOS == "windows" { - ipcpath = common.DefaultIpcPath() - if ctx.GlobalIsSet(IPCPathFlag.Name) { - ipcpath = ctx.GlobalString(IPCPathFlag.Name) - } - } else { - ipcpath = common.DefaultIpcPath() - if ctx.GlobalIsSet(DataDirFlag.Name) { - ipcpath = filepath.Join(ctx.GlobalString(DataDirFlag.Name), "geth.ipc") - } - if ctx.GlobalIsSet(IPCPathFlag.Name) { - ipcpath = ctx.GlobalString(IPCPathFlag.Name) - } - } - - return -} - -func StartIPC(stack *node.Node, ctx *cli.Context) error { - var ethereum *eth.Ethereum - if err := stack.Service(ðereum); err != nil { - return err - } - - endpoint := IPCSocketPath(ctx) - listener, err := rpc.CreateIPCListener(endpoint) - if err != nil { - return err - } - - server := rpc.NewServer() - - // register package API's this node provides - offered := stack.APIs() - for _, api := range offered { - server.RegisterName(api.Namespace, api.Service) - glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) - } - - go func() { - glog.V(logger.Info).Infof("Start IPC server on %s\n", endpoint) - for { - conn, err := listener.Accept() - if err != nil { - glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err) - } - - codec := rpc.NewJSONCodec(conn) - go server.ServeCodec(codec) - } - }() - - return nil - -} - // StartRPC starts a HTTP JSON-RPC API server. func StartRPC(stack *node.Node, ctx *cli.Context) error { for _, api := range stack.APIs() { diff --git a/common/path.go b/common/path.go index 9ba2f93c0c..38c213a122 100644 --- a/common/path.go +++ b/common/path.go @@ -89,9 +89,8 @@ func DefaultDataDir() string { return "" } -func DefaultIpcPath() string { - if runtime.GOOS == "windows" { - return `\\.\pipe\geth.ipc` - } - return filepath.Join(DefaultDataDir(), "geth.ipc") +// DefaultIpcSocket returns the relative name of the default IPC socket. The path +// resolution is done by a node with other contextual infos. +func DefaultIpcSocket() string { + return "geth.ipc" } diff --git a/node/config.go b/node/config.go index 93f0ba79d6..d3eb1c78bc 100644 --- a/node/config.go +++ b/node/config.go @@ -23,7 +23,10 @@ import ( "net" "os" "path/filepath" + "runtime" + "strings" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -49,6 +52,12 @@ type Config struct { // in memory. DataDir string + // IpcPath is the requested location to place the IPC endpoint. If the path is + // a simple file name, it is placed inside the data directory (or on the root + // pipe path on Windows), whereas if it's a resolvable path name (absolute or + // relative), then that specific path is enforced. An empty path disables IPC. + IpcPath string + // This field should be a valid secp256k1 private key that will be used for both // remote peer identification as well as network traffic encryption. If no key // is configured, the preset one is loaded from the data dir, generating it if @@ -90,6 +99,37 @@ type Config struct { MaxPendingPeers int } +// IpcEndpoint resolves an IPC endpoint based on a configured value, taking into +// account the set data folders as well as the designated platform we're currently +// running on. +func (c *Config) IpcEndpoint() string { + // Short circuit if IPC has not been enabled + if c.IpcPath == "" { + return "" + } + // On windows we can only use plain top-level pipes + if runtime.GOOS == "windows" { + if strings.HasPrefix(c.IpcPath, `\\.\pipe\`) { + return c.IpcPath + } + return `\\.\pipe\` + c.IpcPath + } + // Resolve names into the data directory full paths otherwise + if filepath.Base(c.IpcPath) == c.IpcPath { + if c.DataDir == "" { + return filepath.Join(os.TempDir(), c.IpcPath) + } + return filepath.Join(c.DataDir, c.IpcPath) + } + return c.IpcPath +} + +// DefaultIpcEndpoint returns the IPC path used by default. +func DefaultIpcEndpoint() string { + config := &Config{DataDir: common.DefaultDataDir(), IpcPath: common.DefaultIpcSocket()} + return config.IpcEndpoint() +} + // NodeKey retrieves the currently configured private key of the node, checking // first any manually set key, falling back to the one found in the configured // data folder. If no key can be found, a new one is generated. diff --git a/node/config_test.go b/node/config_test.go index f59f3c0fe0..efb864ce42 100644 --- a/node/config_test.go +++ b/node/config_test.go @@ -21,6 +21,7 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "testing" "github.com/ethereum/go-ethereum/crypto" @@ -60,6 +61,37 @@ func TestDatadirCreation(t *testing.T) { } } +// Tests that IPC paths are correctly resolved to valid endpoints of different +// platforms. +func TestIpcPathResolution(t *testing.T) { + var tests = []struct { + DataDir string + IpcPath string + Windows bool + Endpoint string + }{ + {"", "", false, ""}, + {"data", "", false, ""}, + {"", "geth.ipc", false, filepath.Join(os.TempDir(), "geth.ipc")}, + {"data", "geth.ipc", false, "data/geth.ipc"}, + {"data", "./geth.ipc", false, "./geth.ipc"}, + {"data", "/geth.ipc", false, "/geth.ipc"}, + {"", "", true, ``}, + {"data", "", true, ``}, + {"", "geth.ipc", true, `\\.\pipe\geth.ipc`}, + {"data", "geth.ipc", true, `\\.\pipe\geth.ipc`}, + {"data", `\\.\pipe\geth.ipc`, true, `\\.\pipe\geth.ipc`}, + } + for i, test := range tests { + // Only run when platform/test match + if (runtime.GOOS == "windows") == test.Windows { + if endpoint := (&Config{DataDir: test.DataDir, IpcPath: test.IpcPath}).IpcEndpoint(); endpoint != test.Endpoint { + t.Errorf("test %d: IPC endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint) + } + } + } +} + // Tests that node keys can be correctly created, persisted, loaded and/or made // ephemeral. func TestNodeKeyPersistency(t *testing.T) { diff --git a/node/node.go b/node/node.go index 4af111a410..e3fc03360f 100644 --- a/node/node.go +++ b/node/node.go @@ -19,6 +19,7 @@ package node import ( "errors" + "net" "os" "path/filepath" "reflect" @@ -27,6 +28,8 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" ) @@ -52,6 +55,10 @@ type Node struct { serviceFuncs []ServiceConstructor // Service constructors (in dependency order) services map[reflect.Type]Service // Currently running services + ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled) + ipcListener net.Listener // IPC RPC listener socket to serve API requests + ipcHandler *rpc.Server // IPC RPC request handler to process the API requests + stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex } @@ -87,6 +94,7 @@ func New(conf *Config) (*Node, error) { MaxPendingPeers: conf.MaxPendingPeers, }, serviceFuncs: []ServiceConstructor{}, + ipcEndpoint: conf.IpcEndpoint(), eventmux: new(event.TypeMux), }, nil } @@ -164,6 +172,14 @@ func (n *Node) Start() error { // Mark the service started for potential cleanup started = append(started, kind) } + // Lastly start the configured RPC interfaces + if err := n.startRPC(services); err != nil { + for _, service := range services { + service.Stop() + } + running.Stop() + return err + } // Finish initializing the startup n.services = services n.server = running @@ -172,6 +188,58 @@ func (n *Node) Start() error { return nil } +// startRPC initializes and starts the IPC RPC endpoints. +func (n *Node) startRPC(services map[reflect.Type]Service) error { + // Gather and register all the APIs exposed by the services + apis := n.apis() + for _, service := range services { + apis = append(apis, service.APIs()...) + } + ipcHandler := rpc.NewServer() + for _, api := range apis { + if err := ipcHandler.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + glog.V(logger.Debug).Infof("Register %T under namespace '%s'", api.Service, api.Namespace) + } + // All APIs registered, start the IPC and HTTP listeners + var ( + ipcListener net.Listener + err error + ) + if n.ipcEndpoint != "" { + if ipcListener, err = rpc.CreateIPCListener(n.ipcEndpoint); err != nil { + return err + } + go func() { + glog.V(logger.Info).Infof("IPC endpoint opened: %s", n.ipcEndpoint) + defer glog.V(logger.Info).Infof("IPC endpoint closed: %s", n.ipcEndpoint) + + for { + conn, err := ipcListener.Accept() + if err != nil { + // Terminate if the listener was closed + n.lock.RLock() + closed := n.ipcListener == nil + n.lock.RUnlock() + if closed { + return + } + // Not closed, just some error; report and continue + glog.V(logger.Error).Infof("IPC accept failed: %v", err) + continue + } + go ipcHandler.ServeCodec(rpc.NewJSONCodec(conn)) + } + }() + } + // All listeners booted successfully + n.ipcListener = ipcListener + n.ipcHandler = ipcHandler + + return nil +} + // Stop terminates a running node along with all it's services. In the node was // not started, an error is returned. func (n *Node) Stop() error { @@ -182,7 +250,15 @@ func (n *Node) Stop() error { if n.server == nil { return ErrNodeStopped } - // Otherwise terminate all the services and the P2P server too + // Otherwise terminate the API, all services and the P2P server too + if n.ipcListener != nil { + n.ipcListener.Close() + n.ipcListener = nil + } + if n.ipcHandler != nil { + n.ipcHandler.Stop() + n.ipcHandler = nil + } failure := &StopError{ Services: make(map[reflect.Type]error), } @@ -261,18 +337,20 @@ func (n *Node) DataDir() string { return n.datadir } +// IpcEndpoint retrieves the current IPC endpoint used by the protocol stack. +func (n *Node) IpcEndpoint() string { + return n.ipcEndpoint +} + // EventMux retrieves the event multiplexer used by all the network services in // the current protocol stack. func (n *Node) EventMux() *event.TypeMux { return n.eventmux } -// APIs returns the collection of RPC descriptor this node offers. This method -// is just a quick placeholder passthrough for the RPC update, which in the next -// step will be fully integrated into the node itself. -func (n *Node) APIs() []rpc.API { - // Define all the APIs owned by the node itself - apis := []rpc.API{ +// apis returns the collection of RPC descriptors this node offers. +func (n *Node) apis() []rpc.API { + return []rpc.API{ { Namespace: "admin", Version: "1.0", @@ -298,7 +376,13 @@ func (n *Node) APIs() []rpc.API { Public: true, }, } - // Inject all the APIs owned by various services +} + +// APIs returns the collection of RPC descriptor this node offers. This method +// is just a quick placeholder passthrough for the RPC update, which in the next +// step will be fully integrated into the node itself. +func (n *Node) APIs() []rpc.API { + apis := n.apis() for _, api := range n.services { apis = append(apis, api.APIs()...) } diff --git a/node/node_example_test.go b/node/node_example_test.go index 5ff5d06a6a..01ff683c03 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -31,6 +31,7 @@ import ( // // The following methods are needed to implement a node.Service: // - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on +// - APIs() []rpc.API - api methods the service wants to expose on rpc channels // - Start() error - method invoked when the node is ready to start the service // - Stop() error - method invoked when the node terminates the service type SampleService struct{} diff --git a/node/node_test.go b/node/node_test.go index ef096fc917..53dcbcf74c 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -18,27 +18,34 @@ package node import ( "errors" + "fmt" "io/ioutil" + "math/rand" "os" "reflect" "testing" + "time" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" ) var ( testNodeKey, _ = crypto.GenerateKey() +) - testNodeConfig = &Config{ +func testNodeConfig() *Config { + return &Config{ + IpcPath: fmt.Sprintf("test-%d.ipc", rand.Int63()), PrivateKey: testNodeKey, Name: "test node", } -) +} // Tests that an empty protocol stack can be started, restarted and stopped. func TestNodeLifeCycle(t *testing.T) { - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } @@ -101,7 +108,7 @@ func TestNodeUsedDataDir(t *testing.T) { // Tests whether services can be registered and duplicates caught. func TestServiceRegistry(t *testing.T) { - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } @@ -133,7 +140,7 @@ func TestServiceRegistry(t *testing.T) { // Tests that registered services get started and stopped correctly. func TestServiceLifeCycle(t *testing.T) { - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } @@ -183,7 +190,7 @@ func TestServiceLifeCycle(t *testing.T) { // Tests that services are restarted cleanly as new instances. func TestServiceRestarts(t *testing.T) { - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } @@ -231,7 +238,7 @@ func TestServiceRestarts(t *testing.T) { // Tests that if a service fails to initialize itself, none of the other services // will be allowed to even start. func TestServiceConstructionAbortion(t *testing.T) { - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } @@ -278,7 +285,7 @@ func TestServiceConstructionAbortion(t *testing.T) { // Tests that if a service fails to start, all others started before it will be // shut down. func TestServiceStartupAbortion(t *testing.T) { - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } @@ -331,7 +338,7 @@ func TestServiceStartupAbortion(t *testing.T) { // Tests that even if a registered service fails to shut down cleanly, it does // not influece the rest of the shutdown invocations. func TestServiceTerminationGuarantee(t *testing.T) { - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } @@ -406,7 +413,7 @@ func TestServiceTerminationGuarantee(t *testing.T) { // TestServiceRetrieval tests that individual services can be retrieved. func TestServiceRetrieval(t *testing.T) { // Create a simple stack and register two service types - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } @@ -441,7 +448,7 @@ func TestServiceRetrieval(t *testing.T) { // Tests that all protocols defined by individual services get launched. func TestProtocolGather(t *testing.T) { - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } @@ -494,3 +501,75 @@ func TestProtocolGather(t *testing.T) { } } } + +// Tests that all APIs defined by individual services get exposed. +func TestAPIGather(t *testing.T) { + stack, err := New(testNodeConfig()) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of services with some configured APIs + calls := make(chan string, 1) + + services := map[string]struct { + APIs []rpc.API + Maker InstrumentingWrapper + }{ + "Zero APIs": {[]rpc.API{}, InstrumentedServiceMakerA}, + "Single API": {[]rpc.API{ + {"single", "1", &OneMethodApi{fun: func() { calls <- "single.v1" }}, true}, + }, InstrumentedServiceMakerB}, + "Many APIs": {[]rpc.API{ + {"multi", "1", &OneMethodApi{fun: func() { calls <- "multi.v1" }}, true}, + {"multi.v2", "2", &OneMethodApi{fun: func() { calls <- "multi.v2" }}, true}, + {"multi.v2.nested", "2", &OneMethodApi{fun: func() { calls <- "multi.v2.nested" }}, true}, + }, InstrumentedServiceMakerC}, + } + for id, config := range services { + config := config + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{apis: config.APIs}, nil + } + if err := stack.Register(config.Maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) + } + } + // Start the services and ensure all API start successfully + if err := stack.Start(); err != nil { + t.Fatalf("failed to start protocol stack: %v", err) + } + defer stack.Stop() + + // Connect to the RPC server and verify the various registered endpoints + ipcClient, err := rpc.NewIPCClient(stack.IpcEndpoint()) + if err != nil { + t.Fatalf("failed to connect to the IPC API server: %v", err) + } + + tests := []struct { + Method string + Result string + }{ + {"single_theOneMethod", "single.v1"}, + {"multi_theOneMethod", "multi.v1"}, + {"multi.v2_theOneMethod", "multi.v2"}, + {"multi.v2.nested_theOneMethod", "multi.v2.nested"}, + } + for i, test := range tests { + if err := ipcClient.Send(rpc.JSONRequest{Id: new(int64), Version: "2.0", Method: test.Method}); err != nil { + t.Fatalf("test %d: failed to send API request: %v", i, err) + } + reply := new(rpc.JSONSuccessResponse) + if err := ipcClient.Recv(reply); err != nil { + t.Fatalf("test %d: failed to read API reply: %v", i, err) + } + select { + case result := <-calls: + if result != test.Result { + t.Errorf("test %d: result mismatch: have %s, want %s", i, result, test.Result) + } + case <-time.After(time.Second): + t.Fatalf("test %d: rpc execution timeout", i) + } + } +} diff --git a/node/service_test.go b/node/service_test.go index 1dfb61f73a..cfe7fe5dc8 100644 --- a/node/service_test.go +++ b/node/service_test.go @@ -63,7 +63,7 @@ func TestContextDatabases(t *testing.T) { // Tests that already constructed services can be retrieves by later ones. func TestContextServices(t *testing.T) { - stack, err := New(testNodeConfig) + stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } diff --git a/node/utils_test.go b/node/utils_test.go index 7755605ae3..7cdfc2b3aa 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -52,6 +52,7 @@ func NewNoopServiceD(*ServiceContext) (Service, error) { return new(NoopServiceD // methods can be instrumented both return value as well as event hook wise. type InstrumentedService struct { protocols []p2p.Protocol + apis []rpc.API start error stop error @@ -70,7 +71,7 @@ func (s *InstrumentedService) Protocols() []p2p.Protocol { } func (s *InstrumentedService) APIs() []rpc.API { - return nil + return s.apis } func (s *InstrumentedService) Start(server *p2p.Server) error { @@ -121,3 +122,14 @@ func InstrumentedServiceMakerB(base ServiceConstructor) ServiceConstructor { func InstrumentedServiceMakerC(base ServiceConstructor) ServiceConstructor { return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceC{})) } + +// OneMethodApi is a single-method API handler to be returned by test services. +type OneMethodApi struct { + fun func() +} + +func (api *OneMethodApi) TheOneMethod() { + if api.fun != nil { + api.fun() + } +} diff --git a/rpc/json.go b/rpc/json.go index 02bc71c268..1ed943c004 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -41,7 +41,7 @@ type JSONRequest struct { Method string `json:"method"` Version string `json:"jsonrpc"` Id *int64 `json:"id,omitempty"` - Payload json.RawMessage `json:"params"` + Payload json.RawMessage `json:"params,omitempty"` } // JSON-RPC response From 6b939fbeaa7db139e06223926d6b4355ae0e71ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 4 Feb 2016 12:44:34 +0200 Subject: [PATCH 76/78] rpc: add jsonrpc version to module request, use json types --- eth/api.go | 3 +-- rpc/utils.go | 16 +++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/eth/api.go b/eth/api.go index 0fa855f156..617643e108 100644 --- a/eth/api.go +++ b/eth/api.go @@ -27,8 +27,6 @@ import ( "sync" "time" - "gopkg.in/fatih/set.v0" - "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -46,6 +44,7 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "gopkg.in/fatih/set.v0" ) const ( diff --git a/rpc/utils.go b/rpc/utils.go index 25321c543c..39acf81969 100644 --- a/rpc/utils.go +++ b/rpc/utils.go @@ -218,29 +218,27 @@ func newSubscriptionId() (string, error) { // SupportedModules returns the collection of API's that the RPC server offers // on which the given client connects. func SupportedModules(client Client) (map[string]string, error) { - req := map[string]interface{}{ - "id": 1, - "method": "rpc_modules", + req := JSONRequest{ + Id: new(int64), + Version: "2.0", + Method: "rpc_modules", } - if err := client.Send(req); err != nil { return nil, err } - var response map[string]interface{} + var response JSONSuccessResponse if err := client.Recv(&response); err != nil { return nil, err } - - if payload, ok := response["result"]; ok { + if response.Result != nil { mods := make(map[string]string) - if modules, ok := payload.(map[string]interface{}); ok { + if modules, ok := response.Result.(map[string]interface{}); ok { for m, v := range modules { mods[m] = fmt.Sprintf("%s", v) } return mods, nil } } - return nil, fmt.Errorf("unable to retrieve modules") } From cf47ef12d3d4845c9b6fd19d078bfb831dc526cb Mon Sep 17 00:00:00 2001 From: Isidoro Ghezzi Date: Fri, 5 Feb 2016 15:32:00 +0100 Subject: [PATCH 77/78] cmd/utils: fix jspath flag typo --- cmd/utils/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 5d56ba7d01..ca9fd74ba6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -338,7 +338,7 @@ var ( // ATM the url is left to the user and deployment to JSpathFlag = cli.StringFlag{ Name: "jspath", - Usage: "JavaSript root path for `loadScript` and document root for `admin.httpGet`", + Usage: "JavaScript root path for `loadScript` and document root for `admin.httpGet`", Value: ".", } SolcPathFlag = cli.StringFlag{ From 68dda3490585dd789fced5418507f0fda82bbf19 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 5 Jan 2016 14:55:28 +0100 Subject: [PATCH 78/78] eth/filters: added notifications for out of bound log events Out of Bound log events are events that were removed due to a fork. When logs are received the filtering mechanism should check for the `removed` field on the json structure. --- eth/filters/api.go | 46 +++++++++------- eth/filters/filter.go | 2 +- eth/filters/filter_system.go | 23 +++++--- eth/filters/filter_system_test.go | 87 +++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 26 deletions(-) create mode 100644 eth/filters/filter_system_test.go diff --git a/eth/filters/api.go b/eth/filters/api.go index aa4c305a6f..148daa649b 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -206,12 +206,12 @@ func (s *PublicFilterAPI) newLogFilter(earliest, latest int64, addresses []commo filter.SetEndBlock(latest) filter.SetAddresses(addresses) filter.SetTopics(topics) - filter.LogsCallback = func(logs vm.Logs) { + filter.LogCallback = func(log *vm.Log, removed bool) { s.logMu.Lock() defer s.logMu.Unlock() if queue := s.logQueue[id]; queue != nil { - queue.add(logs...) + queue.add(vmlog{log, removed}) } } @@ -365,14 +365,14 @@ func (s *PublicFilterAPI) NewFilter(args NewFilterArgs) (string, error) { } // GetLogs returns the logs matching the given argument. -func (s *PublicFilterAPI) GetLogs(args NewFilterArgs) vm.Logs { +func (s *PublicFilterAPI) GetLogs(args NewFilterArgs) []vmlog { filter := New(s.chainDb) filter.SetBeginBlock(args.FromBlock.Int64()) filter.SetEndBlock(args.ToBlock.Int64()) filter.SetAddresses(args.Addresses) filter.SetTopics(args.Topics) - return returnLogs(filter.Find()) + return toRPCLogs(filter.Find(), false) } // UninstallFilter removes the filter with the given filter id. @@ -447,7 +447,7 @@ func (s *PublicFilterAPI) transactionFilterChanged(id int) []common.Hash { } // logFilterChanged returns a collection of logs for the log filter with the given id. -func (s *PublicFilterAPI) logFilterChanged(id int) vm.Logs { +func (s *PublicFilterAPI) logFilterChanged(id int) []vmlog { s.logMu.Lock() defer s.logMu.Unlock() @@ -458,17 +458,17 @@ func (s *PublicFilterAPI) logFilterChanged(id int) vm.Logs { } // GetFilterLogs returns the logs for the filter with the given id. -func (s *PublicFilterAPI) GetFilterLogs(filterId string) vm.Logs { +func (s *PublicFilterAPI) GetFilterLogs(filterId string) []vmlog { id, ok := s.filterMapping[filterId] if !ok { - return returnLogs(nil) + return toRPCLogs(nil, false) } if filter := s.filterManager.Get(id); filter != nil { - return returnLogs(filter.Find()) + return toRPCLogs(filter.Find(), false) } - return returnLogs(nil) + return toRPCLogs(nil, false) } // GetFilterChanges returns the logs for the filter with the given id since last time is was called. @@ -488,28 +488,33 @@ func (s *PublicFilterAPI) GetFilterChanges(filterId string) interface{} { case transactionFilterTy: return returnHashes(s.transactionFilterChanged(id)) case logFilterTy: - return returnLogs(s.logFilterChanged(id)) + return s.logFilterChanged(id) } return []interface{}{} } +type vmlog struct { + *vm.Log + Removed bool `json:"removed"` +} + type logQueue struct { mu sync.Mutex - logs vm.Logs + logs []vmlog timeout time.Time id int } -func (l *logQueue) add(logs ...*vm.Log) { +func (l *logQueue) add(logs ...vmlog) { l.mu.Lock() defer l.mu.Unlock() l.logs = append(l.logs, logs...) } -func (l *logQueue) get() vm.Logs { +func (l *logQueue) get() []vmlog { l.mu.Lock() defer l.mu.Unlock() @@ -556,13 +561,16 @@ func newFilterId() (string, error) { return "0x" + hex.EncodeToString(subid[:]), nil } -// returnLogs is a helper that will return an empty logs array case the given logs is nil, otherwise is will return the -// given logs. The RPC interfaces defines that always an array is returned. -func returnLogs(logs vm.Logs) vm.Logs { - if logs == nil { - return vm.Logs{} +// toRPCLogs is a helper that will convert a vm.Logs array to an structure which +// can hold additional information about the logs such as whether it was deleted. +// Additionally when nil is given it will by default instead create an empty slice +// instead. This is required by the RPC specification. +func toRPCLogs(logs vm.Logs, removed bool) []vmlog { + convertedLogs := make([]vmlog, len(logs)) + for i, log := range logs { + convertedLogs[i] = vmlog{Log: log, Removed: removed} } - return logs + return convertedLogs } // returnHashes is a helper that will return an empty hash array case the given hash array is nil, otherwise is will diff --git a/eth/filters/filter.go b/eth/filters/filter.go index ff192cdf6b..2c92d20b14 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -39,7 +39,7 @@ type Filter struct { BlockCallback func(*types.Block, vm.Logs) TransactionCallback func(*types.Transaction) - LogsCallback func(vm.Logs) + LogCallback func(*vm.Log, bool) } // Create a new filter which uses a bloom filter on blocks to figure out whether a particular block diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index df3ce90c6e..04e58a08c5 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -46,6 +46,7 @@ func NewFilterSystem(mux *event.TypeMux) *FilterSystem { } fs.sub = mux.Subscribe( //core.PendingBlockEvent{}, + core.RemovedLogEvent{}, core.ChainEvent{}, core.TxPreEvent{}, vm.Logs(nil), @@ -96,7 +97,7 @@ func (fs *FilterSystem) filterLoop() { case core.ChainEvent: fs.filterMu.RLock() for id, filter := range fs.filters { - if filter.BlockCallback != nil && fs.created[id].Before(event.Time) { + if filter.BlockCallback != nil && !fs.created[id].After(event.Time) { filter.BlockCallback(ev.Block, ev.Logs) } } @@ -105,7 +106,7 @@ func (fs *FilterSystem) filterLoop() { case core.TxPreEvent: fs.filterMu.RLock() for id, filter := range fs.filters { - if filter.TransactionCallback != nil && fs.created[id].Before(event.Time) { + if filter.TransactionCallback != nil && !fs.created[id].After(event.Time) { filter.TransactionCallback(ev.Tx) } } @@ -114,10 +115,20 @@ func (fs *FilterSystem) filterLoop() { case vm.Logs: fs.filterMu.RLock() for id, filter := range fs.filters { - if filter.LogsCallback != nil && fs.created[id].Before(event.Time) { - msgs := filter.FilterLogs(ev) - if len(msgs) > 0 { - filter.LogsCallback(msgs) + if filter.LogCallback != nil && !fs.created[id].After(event.Time) { + for _, log := range filter.FilterLogs(ev) { + filter.LogCallback(log, false) + } + } + } + fs.filterMu.RUnlock() + + case core.RemovedLogEvent: + fs.filterMu.RLock() + for id, filter := range fs.filters { + if filter.LogCallback != nil && !fs.created[id].After(event.Time) { + for _, removedLog := range ev.Logs { + filter.LogCallback(removedLog, true) } } } diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go new file mode 100644 index 0000000000..7ddeb02bc9 --- /dev/null +++ b/eth/filters/filter_system_test.go @@ -0,0 +1,87 @@ +package filters + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/event" +) + +func TestCallbacks(t *testing.T) { + var ( + mux event.TypeMux + fs = NewFilterSystem(&mux) + blockDone = make(chan struct{}) + txDone = make(chan struct{}) + logDone = make(chan struct{}) + removedLogDone = make(chan struct{}) + ) + + blockFilter := &Filter{ + BlockCallback: func(*types.Block, vm.Logs) { + close(blockDone) + }, + } + txFilter := &Filter{ + TransactionCallback: func(*types.Transaction) { + close(txDone) + }, + } + logFilter := &Filter{ + LogCallback: func(l *vm.Log, oob bool) { + if !oob { + close(logDone) + } + }, + } + + removedLogFilter := &Filter{ + LogCallback: func(l *vm.Log, oob bool) { + if oob { + close(removedLogDone) + } + }, + } + + fs.Add(blockFilter) + fs.Add(txFilter) + fs.Add(logFilter) + fs.Add(removedLogFilter) + + mux.Post(core.ChainEvent{}) + mux.Post(core.TxPreEvent{}) + mux.Post(core.RemovedLogEvent{vm.Logs{&vm.Log{}}}) + mux.Post(vm.Logs{&vm.Log{}}) + + const dura = 5 * time.Second + failTimer := time.NewTimer(dura) + select { + case <-blockDone: + case <-failTimer.C: + t.Error("block filter failed to trigger (timeout)") + } + + failTimer.Reset(dura) + select { + case <-txDone: + case <-failTimer.C: + t.Error("transaction filter failed to trigger (timeout)") + } + + failTimer.Reset(dura) + select { + case <-logDone: + case <-failTimer.C: + t.Error("log filter failed to trigger (timeout)") + } + + failTimer.Reset(dura) + select { + case <-removedLogDone: + case <-failTimer.C: + t.Error("removed log filter failed to trigger (timeout)") + } +}