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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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 154fb69f7ecbb214a263f4c39f7bcc8b5d7f9d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=9ETH=CE=9E=D0=AFSPH=CE=9E=D0=AF=CE=9E?= Date: Thu, 3 Dec 2015 21:42:43 +0000 Subject: [PATCH 44/47] Swarm - plan bee for content storage and distribution on web3 --- accounts/account_manager.go | 10 + cmd/geth/js.go | 5 +- cmd/geth/js_bzz_test.go | 83 ++ cmd/geth/js_test.go | 18 +- cmd/geth/main.go | 88 +-- cmd/utils/customflags.go | 4 +- cmd/utils/customflags_test.go | 2 +- cmd/utils/flags.go | 111 ++- common/chequebook/cheque.go | 660 ++++++++++++++++ common/chequebook/cheque_test.go | 498 ++++++++++++ common/chequebook/chequebook.sol | 49 ++ common/chequebook/contract.go | 63 ++ common/kademlia/address.go | 157 ++++ common/kademlia/address_test.go | 80 ++ common/kademlia/kaddb.go | 360 +++++++++ common/kademlia/kademlia.go | 457 +++++++++++ common/kademlia/kademlia_test.go | 389 +++++++++ common/registrar/ethreg/ethreg.go | 22 +- common/swap/swap.go | 238 ++++++ common/swap/swap_test.go | 178 +++++ common/types.go | 4 + core/genesis.go | 5 + rpc/api/bzz.go | 278 +++++++ rpc/api/bzz_args.go | 322 ++++++++ rpc/api/bzz_js.go | 95 +++ rpc/api/utils.go | 27 +- rpc/shared/utils.go | 3 +- swarm/api/api.go | 456 +++++++++++ swarm/api/api_test.go | 205 +++++ swarm/api/config.go | 105 +++ swarm/api/config_test.go | 107 +++ swarm/api/ethereum.go | 320 ++++++++ swarm/api/http.go | 247 ++++++ swarm/api/manifest.go | 310 ++++++++ swarm/api/manifest_test.go | 61 ++ swarm/api/roundtripper.go | 22 + swarm/api/roundtripper_test.go | 50 ++ swarm/cmd/bzzhash/bzzhash.go | 38 + swarm/cmd/bzzup.sh | 46 ++ swarm/examples/album/add.png | Bin 0 -> 129 bytes swarm/examples/album/back.png | Bin 0 -> 388 bytes swarm/examples/album/cut-left.png | Bin 0 -> 221 bytes swarm/examples/album/cut-mov.png | Bin 0 -> 197 bytes swarm/examples/album/cut-right.png | Bin 0 -> 193 bytes swarm/examples/album/cut-top.png | Bin 0 -> 205 bytes swarm/examples/album/data.json | 1 + swarm/examples/album/delete.png | Bin 0 -> 552 bytes swarm/examples/album/down.png | Bin 0 -> 403 bytes swarm/examples/album/download.png | Bin 0 -> 422 bytes swarm/examples/album/eye.png | Bin 0 -> 525 bytes swarm/examples/album/imgs/apron.jpg | Bin 0 -> 419037 bytes swarm/examples/album/imgs/bekas1.jpg | Bin 0 -> 554726 bytes swarm/examples/album/imgs/bekas2.jpg | Bin 0 -> 508279 bytes swarm/examples/album/imgs/bekas_blanik.jpg | Bin 0 -> 604048 bytes swarm/examples/album/imgs/hangar.jpg | Bin 0 -> 435136 bytes swarm/examples/album/imgs/hangar_inside.jpg | Bin 0 -> 646483 bytes swarm/examples/album/imgs/ruin.jpg | Bin 0 -> 467321 bytes swarm/examples/album/index.css | 291 +++++++ swarm/examples/album/index.html | 20 + swarm/examples/album/index.js | 836 ++++++++++++++++++++ swarm/examples/album/left.png | Bin 0 -> 686 bytes swarm/examples/album/mootools-core-1.4.js | 491 ++++++++++++ swarm/examples/album/mootools-idle.js | 198 +++++ swarm/examples/album/mootools-mooswipe.js | 77 ++ swarm/examples/album/mootools-more-1.4.js | 45 ++ swarm/examples/album/noise.png | Bin 0 -> 65982 bytes swarm/examples/album/right.png | Bin 0 -> 669 bytes swarm/examples/album/throbber.gif | Bin 0 -> 1657 bytes swarm/examples/album/up.png | Bin 0 -> 367 bytes swarm/examples/bzzhandler.html | 15 + swarm/network/depo.go | 190 +++++ swarm/network/forwarding.go | 131 +++ swarm/network/hive.go | 322 ++++++++ swarm/network/messages.go | 294 +++++++ swarm/network/protocol.go | 505 ++++++++++++ swarm/network/protocol_test.go | 1 + swarm/network/syncdb.go | 371 +++++++++ swarm/network/syncdb_test.go | 205 +++++ swarm/network/syncer.go | 762 ++++++++++++++++++ swarm/services/swap/swap.go | 271 +++++++ swarm/services/swear/doc.go | 3 + swarm/services/swear/swear.sol | 161 ++++ swarm/services/swear/swear_test.go | 1 + swarm/storage/chunker.go | 411 ++++++++++ swarm/storage/chunker_test.go | 214 +++++ swarm/storage/chunkreader.go | 194 +++++ swarm/storage/common_test.go | 94 +++ swarm/storage/database.go | 96 +++ swarm/storage/dbstore.go | 457 +++++++++++ swarm/storage/dbstore_test.go | 172 ++++ swarm/storage/dpa.go | 234 ++++++ swarm/storage/dpa_test.go | 133 ++++ swarm/storage/localstore.go | 53 ++ swarm/storage/memstore.go | 336 ++++++++ swarm/storage/memstore_test.go | 34 + swarm/storage/netstore.go | 117 +++ swarm/storage/types.go | 155 ++++ swarm/swarm.go | 231 ++++++ swarm/test/syncing/00.sh | 83 ++ swarm/test/test0/img/logo.png | Bin 0 -> 18136 bytes swarm/test/test0/index.css | 9 + swarm/test/test0/index.html | 10 + 102 files changed, 14301 insertions(+), 96 deletions(-) create mode 100644 cmd/geth/js_bzz_test.go create mode 100644 common/chequebook/cheque.go create mode 100644 common/chequebook/cheque_test.go create mode 100644 common/chequebook/chequebook.sol create mode 100644 common/chequebook/contract.go create mode 100644 common/kademlia/address.go create mode 100644 common/kademlia/address_test.go create mode 100644 common/kademlia/kaddb.go create mode 100644 common/kademlia/kademlia.go create mode 100644 common/kademlia/kademlia_test.go create mode 100644 common/swap/swap.go create mode 100644 common/swap/swap_test.go create mode 100644 rpc/api/bzz.go create mode 100644 rpc/api/bzz_args.go create mode 100644 rpc/api/bzz_js.go create mode 100644 swarm/api/api.go create mode 100644 swarm/api/api_test.go create mode 100644 swarm/api/config.go create mode 100644 swarm/api/config_test.go create mode 100644 swarm/api/ethereum.go create mode 100644 swarm/api/http.go create mode 100644 swarm/api/manifest.go create mode 100644 swarm/api/manifest_test.go create mode 100644 swarm/api/roundtripper.go create mode 100644 swarm/api/roundtripper_test.go create mode 100644 swarm/cmd/bzzhash/bzzhash.go create mode 100755 swarm/cmd/bzzup.sh create mode 100644 swarm/examples/album/add.png create mode 100644 swarm/examples/album/back.png create mode 100644 swarm/examples/album/cut-left.png create mode 100644 swarm/examples/album/cut-mov.png create mode 100644 swarm/examples/album/cut-right.png create mode 100644 swarm/examples/album/cut-top.png create mode 100644 swarm/examples/album/data.json create mode 100644 swarm/examples/album/delete.png create mode 100644 swarm/examples/album/down.png create mode 100644 swarm/examples/album/download.png create mode 100644 swarm/examples/album/eye.png create mode 100644 swarm/examples/album/imgs/apron.jpg create mode 100644 swarm/examples/album/imgs/bekas1.jpg create mode 100644 swarm/examples/album/imgs/bekas2.jpg create mode 100644 swarm/examples/album/imgs/bekas_blanik.jpg create mode 100644 swarm/examples/album/imgs/hangar.jpg create mode 100644 swarm/examples/album/imgs/hangar_inside.jpg create mode 100644 swarm/examples/album/imgs/ruin.jpg create mode 100644 swarm/examples/album/index.css create mode 100644 swarm/examples/album/index.html create mode 100644 swarm/examples/album/index.js create mode 100644 swarm/examples/album/left.png create mode 100644 swarm/examples/album/mootools-core-1.4.js create mode 100644 swarm/examples/album/mootools-idle.js create mode 100644 swarm/examples/album/mootools-mooswipe.js create mode 100644 swarm/examples/album/mootools-more-1.4.js create mode 100644 swarm/examples/album/noise.png create mode 100644 swarm/examples/album/right.png create mode 100644 swarm/examples/album/throbber.gif create mode 100644 swarm/examples/album/up.png create mode 100644 swarm/examples/bzzhandler.html create mode 100644 swarm/network/depo.go create mode 100644 swarm/network/forwarding.go create mode 100644 swarm/network/hive.go create mode 100644 swarm/network/messages.go create mode 100644 swarm/network/protocol.go create mode 100644 swarm/network/protocol_test.go create mode 100644 swarm/network/syncdb.go create mode 100644 swarm/network/syncdb_test.go create mode 100644 swarm/network/syncer.go create mode 100644 swarm/services/swap/swap.go create mode 100644 swarm/services/swear/doc.go create mode 100644 swarm/services/swear/swear.sol create mode 100644 swarm/services/swear/swear_test.go create mode 100644 swarm/storage/chunker.go create mode 100644 swarm/storage/chunker_test.go create mode 100644 swarm/storage/chunkreader.go create mode 100644 swarm/storage/common_test.go create mode 100644 swarm/storage/database.go create mode 100644 swarm/storage/dbstore.go create mode 100644 swarm/storage/dbstore_test.go create mode 100644 swarm/storage/dpa.go create mode 100644 swarm/storage/dpa_test.go create mode 100644 swarm/storage/localstore.go create mode 100644 swarm/storage/memstore.go create mode 100644 swarm/storage/memstore_test.go create mode 100644 swarm/storage/netstore.go create mode 100644 swarm/storage/types.go create mode 100644 swarm/swarm.go create mode 100644 swarm/test/syncing/00.sh create mode 100644 swarm/test/test0/img/logo.png create mode 100644 swarm/test/test0/index.css create mode 100644 swarm/test/test0/index.html diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 74006395cd..2ada1a9804 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -91,6 +91,16 @@ func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) return signature, err } +func (am *Manager) GetUnlocked(addr common.Address) (prvkey *ecdsa.PrivateKey, err error) { + am.mutex.RLock() + defer am.mutex.RUnlock() + unlockedKey, found := am.unlocked[addr] + if !found { + return nil, ErrLocked + } + return unlockedKey.PrivateKey, nil +} + // Unlock unlocks the given account indefinitely. func (am *Manager) Unlock(addr common.Address, keyAuth string) error { return am.TimedUnlock(addr, keyAuth, 0) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 9329eaa0e7..20fd44d6ae 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -24,9 +24,8 @@ import ( "os/signal" "path/filepath" "regexp" - "strings" - "sort" + "strings" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" @@ -190,6 +189,8 @@ func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.Ethereum 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...)) + } else { + utils.Fatalf("Unable to offer apis: %v", err) } } diff --git a/cmd/geth/js_bzz_test.go b/cmd/geth/js_bzz_test.go new file mode 100644 index 0000000000..2d8b70efb9 --- /dev/null +++ b/cmd/geth/js_bzz_test.go @@ -0,0 +1,83 @@ +package main + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/swarm" + "github.com/ethereum/go-ethereum/swarm/api" +) + +var port = 8500 + +func bzzREPL(t *testing.T, configf func(*api.Config)) (string, string, *testjethre, *node.Node) { + prvKey, err := crypto.GenerateKey() + if err != nil { + t.Fatal("unable to generate key") + } + bzztmp, err := ioutil.TempDir("", "bzz-js-test") + config, err := api.NewConfig(bzztmp, common.Address{}, prvKey) + if err != nil { + t.Fatal("unable to configure swarm") + } + if configf != nil { + configf(config) + } + tmp, repl, stack := testREPL(t, func(n *node.Node) { + if err := n.Register(func(ctx *node.ServiceContext) (node.Service, error) { + return swarm.NewSwarm(ctx, config, false) + }); err != nil { + t.Fatalf("Failed to register the Swarm service: %v", err) + } + }) + return bzztmp, tmp, repl, stack +} + +func withREPL(t *testing.T, cf func(*api.Config), f func(repl *testjethre)) { + bzztmp, tmp, repl, stack := bzzREPL(t, cf) + defer stack.Stop() + defer os.RemoveAll(tmp) + defer os.RemoveAll(bzztmp) + f(repl) +} + +func TestBzzPutGet(t *testing.T) { + withREPL(t, + func(c *api.Config) { + c.Port = "" + }, func(repl *testjethre) { + if checkEvalJSON(t, repl, `hash = bzz.put("console.log(\"hello from console\")", "application/javascript")`, `"97f1b7c7ea12468fd37c262383b9aa862d0cfbc4fc7218652374679fc5cf40cd"`) != nil { + return + } + want := `{"content":"console.log(\"hello from console\")","contentType":"application/javascript","size":"33","status":"0"}` + if checkEvalJSON(t, repl, `bzz.get(hash)`, want) != nil { + return + } + }) +} + +// the server can be initialized only once per test session ! +// until we implement a stoppable http server +// further http tests will need to make sure the correct server is running +func TestHTTP(t *testing.T) { + withREPL(t, nil, func(repl *testjethre) { + if checkEvalJSON(t, repl, `hash = bzz.put("f42 = function() { return 42 }", "application/javascript")`, `"e6847876f00102441f850b2d438a06d10e3bf24e6a0a76d47b073a86c3c2f9ac"`) != nil { + return + } + if checkEvalJSON(t, repl, `admin.httpGet("bzz://"+hash)`, `"f42 = function() { return 42 }"`) != nil { + return + } + + // if checkEvalJSON(t, repl, `http.loadScript("bzz://"+hash)`, `true`) != nil { + // return + // } + + // if checkEvalJSON(t, repl, `f42()`, `42`) != nil { + // return + // } + }) +} diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index ca636188f4..975f8dde5e 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -91,7 +91,7 @@ func testJEthRE(t *testing.T) (string, *testjethre, *node.Node) { return testREPL(t, nil) } -func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *node.Node) { +func testREPL(t *testing.T, config func(*node.Node)) (string, *testjethre, *node.Node) { tmp, err := ioutil.TempDir("", "geth-test") if err != nil { t.Fatal(err) @@ -107,17 +107,16 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod db, _ := ethdb.NewMemDatabase() core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)}) + coinbase := common.HexToAddress(testAddress) ethConf := ð.Config{ TestGenesisState: db, AccountManager: accman, DocRoot: "/", SolcPath: testSolcPath, + Etherbase: coinbase, PowTest: true, } - if config != nil { - config(ethConf) - } 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) } @@ -133,6 +132,12 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod if err := accman.Unlock(key.Address, ""); err != nil { t.Fatal(err) } + + // tests can register services here + if config != nil { + config(stack) + } + // Start the node and assemble the REPL tester if err := stack.Start(); err != nil { t.Fatalf("failed to start test stack: %v", err) @@ -267,10 +272,7 @@ func TestSignature(t *testing.T) { func TestContract(t *testing.T) { t.Skip("contract testing is implemented with mining in ethash test mode. This takes about 7seconds to run. Unskip and run on demand") coinbase := common.HexToAddress(testAddress) - tmp, repl, ethereum := testREPL(t, func(conf *eth.Config) { - conf.Etherbase = coinbase - conf.PowTest = true - }) + tmp, repl, ethereum := testREPL(t, nil) if err := ethereum.Start(); err != nil { t.Errorf("error starting ethereum: %v", err) return diff --git a/cmd/geth/main.go b/cmd/geth/main.go index f2bb275520..3912cbc764 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -4,7 +4,7 @@ // 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. +// (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 @@ -30,9 +30,7 @@ import ( "github.com/codegangsta/cli" "github.com/ethereum/ethash" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -316,6 +314,9 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.IPCExperimental, utils.ExecFlag, utils.WhisperEnabledFlag, + utils.SwarmConfigPathFlag, + utils.SwarmAccountAddrFlag, + utils.ChequebookAddrFlag, utils.DevModeFlag, utils.TestNetFlag, utils.VMDebugFlag, @@ -428,6 +429,7 @@ func attach(ctx *cli.Context) { func console(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 either execute script or become interactive @@ -466,46 +468,14 @@ 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) { - 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) - password := getPassPhrase(prompt, false, i, passwords) - if err := accman.Unlock(account, password); err == nil { - return account, password - } - } - // All trials expended to unlock account, bail out - utils.Fatalf("Failed to unlock account: %s", address) - return common.Address{}, "" -} - -// 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 +// startNode unlocks any requested accounts the boots up the system node +// starts all registered protocols 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.Service(ðereum); err != nil { - utils.Fatalf("ethereum service not running: %v", err) - } - accman := ethereum.AccountManager() - passwords := utils.MakePasswordList(ctx) - - accounts := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",") - for i, account := range accounts { - 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(stack, ctx); err != nil { @@ -517,6 +487,10 @@ func startNode(ctx *cli.Context, stack *node.Node) { utils.Fatalf("Failed to start RPC: %v", err) } } + var ethereum *eth.Ethereum + if err := stack.Service(ðereum); err != nil { + utils.Fatalf("ethereum service not running: %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) @@ -535,38 +509,10 @@ func accountList(ctx *cli.Context) { } } -// 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("Failed to read passphrase confirmation: %v", err) - } - if password != confirm { - utils.Fatalf("Passphrases do not match") - } - } - return password -} - // accountCreate creates a new account into the keystore defined by the CLI flags. func accountCreate(ctx *cli.Context) { 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)) + password := utils.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 { @@ -583,8 +529,8 @@ func accountUpdate(ctx *cli.Context) { } accman := utils.MakeAccountManager(ctx) - 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) + account, oldPassword := utils.UnlockAccount(ctx, accman, ctx.Args().First(), 0, nil) + newPassword := utils.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) } @@ -601,7 +547,7 @@ func importWallet(ctx *cli.Context) { } accman := utils.MakeAccountManager(ctx) - passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) + passphrase := utils.GetPassPhrase("", false, 0, utils.MakePasswordList(ctx)) acct, err := accman.ImportPreSaleKey(keyJson, passphrase) if err != nil { @@ -616,7 +562,7 @@ func accountImport(ctx *cli.Context) { utils.Fatalf("keyfile must be given as argument") } 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)) + passphrase := utils.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/utils/customflags.go b/cmd/utils/customflags.go index 4450065c14..faf531121d 100644 --- a/cmd/utils/customflags.go +++ b/cmd/utils/customflags.go @@ -39,7 +39,7 @@ func (self *DirectoryString) String() string { } func (self *DirectoryString) Set(value string) error { - self.Value = expandPath(value) + self.Value = ExpandPath(value) return nil } @@ -135,7 +135,7 @@ func (self *DirectoryFlag) Set(value string) { // 2. expands embedded environment variables // 3. cleans the path, e.g. /a/b/../c -> /a/c // Note, it has limitations, e.g. ~someuser/tmp will not be expanded -func expandPath(p string) string { +func ExpandPath(p string) string { if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { if user, err := user.Current(); err == nil { p = user.HomeDir + p[1:] diff --git a/cmd/utils/customflags_test.go b/cmd/utils/customflags_test.go index de39ca36a1..05e47abe4d 100644 --- a/cmd/utils/customflags_test.go +++ b/cmd/utils/customflags_test.go @@ -33,7 +33,7 @@ func TestPathExpansion(t *testing.T) { } os.Setenv("DDDXXX", "/tmp") for test, expected := range tests { - got := expandPath(test) + got := ExpandPath(test) if got != expected { t.Errorf("test %s, got %s, expected %s\n", test, got, expected) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 63efa08ee3..dc28f75cff 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -55,6 +55,8 @@ import ( "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/swarm" + bzzapi "github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/whisper" "github.com/ethereum/go-ethereum/xeth" ) @@ -351,6 +353,22 @@ var ( Name: "shh", Usage: "Enable Whisper", } + ChequebookAddrFlag = cli.StringFlag{ + Name: "chequebook", + Usage: "chequebook contract address", + } + SwarmAccountAddrFlag = cli.StringFlag{ + Name: "bzzaccount", + Usage: "Swarm account address (swarm disabled if empty)", + } + SwarmConfigPathFlag = cli.StringFlag{ + Name: "bzzconfig", + Usage: "Swarm config file path (datadir/bzz)", + } + SwarmSwapDisabled = cli.BoolFlag{ + Name: "bzznoswap", + Usage: "Swarm SWAP disabled (false)", + } // ATM the url is left to the user and deployment to JSpathFlag = cli.StringFlag{ Name: "jspath", @@ -582,6 +600,53 @@ func MakePasswordList(ctx *cli.Context) []string { return nil } +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, err := MakeAddress(accman, address) + if err != nil { + Fatalf("unable to unlock account %v: %v", address, err) + } + + 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 + } + } + // All trials expended to unlock account, bail out + Fatalf("Failed to unlock account: %s", address) + return common.Address{}, "" +} + +// 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 := PromptPassword("Passphrase: ", true) + if err != nil { + Fatalf("Failed to read passphrase: %v", err) + } + if confirmation { + confirm, err := PromptPassword("Repeat passphrase: ", false) + if err != nil { + Fatalf("Failed to read passphrase confirmation: %v", err) + } + if password != confirm { + Fatalf("Passphrases do not match") + } + } + return password +} + // 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 { @@ -595,10 +660,12 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. if networks > 1 { Fatalf("The %v flags are mutually exclusive", netFlags) } + datadir := MustMakeDataDir(ctx) + netprv := MakeNodeKey(ctx) // Configure the node's service container stackConf := &node.Config{ - DataDir: MustMakeDataDir(ctx), - PrivateKey: MakeNodeKey(ctx), + DataDir: datadir, + PrivateKey: netprv, Name: MakeNodeName(name, version, ctx), NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name), BootstrapNodes: MakeBootstrapNodes(ctx), @@ -609,6 +676,14 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. } // Configure the Ethereum service accman := MakeAccountManager(ctx) + passwords := MakePasswordList(ctx) + + accounts := strings.Split(ctx.GlobalString(UnlockedAccountFlag.Name), ",") + for i, account := range accounts { + if trimmed := strings.TrimSpace(account); trimmed != "" { + UnlockAccount(ctx, accman, trimmed, i, passwords) + } + } ethConf := ð.Config{ Genesis: MakeGenesisBlock(ctx), @@ -685,17 +760,49 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. if err != nil { Fatalf("Failed to create the protocol stack: %v", err) } + + // eth.Ethereum: ethereum 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) } + + // Whisper if shhEnable { 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) } } + // bzz. Swarm + var bzzconfig *bzzapi.Config + hexaddr := ctx.GlobalString(SwarmAccountAddrFlag.Name) + if hexaddr != "" { + swarmaccount := common.HexToAddress(hexaddr) + if !accman.HasAccount(swarmaccount) { + Fatalf("swarm account '%v' does not exist: %v", hexaddr, err) + } + prvkey, err := accman.GetUnlocked(swarmaccount) + if err != nil { + Fatalf("unable to unlock swarm account: %v", err) + } + chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name)) + bzzdir := ctx.GlobalString(SwarmConfigPathFlag.Name) + if bzzdir == "" { + bzzdir = filepath.Join(datadir, "bzz") + } + bzzconfig, err = bzzapi.NewConfig(bzzdir, chbookaddr, prvkey) + if err != nil { + Fatalf("unable to configure swarm: %v", err) + } + swap := ctx.GlobalBool(SwarmSwapDisabled.Name) + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + return swarm.NewSwarm(ctx, bzzconfig, swap) + }); err != nil { + Fatalf("Failed to register the Swarm service: %v", err) + } + } return stack } diff --git a/common/chequebook/cheque.go b/common/chequebook/cheque.go new file mode 100644 index 0000000000..41c89418b0 --- /dev/null +++ b/common/chequebook/cheque.go @@ -0,0 +1,660 @@ +package chequebook + +import ( + "bytes" + "crypto/ecdsa" + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + "os" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/swap" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +/* +Chequebook package is a go API to the 'chequebook' ethereum smart contract +With convenience methods that allow using chequebook for +* issuing, receiving, verifying cheques in ether +* (auto)cashing cheques in ether +* (auto)depositing ether to the chequebook contract +TODO: +* watch peer solvency and notify of bouncing cheques +* enable paying with cheque by signing off + +Some functionality require interacting with the blockchain: +* setting current balance on peer's chequebook +* sending the transaction to cash the cheque +* depositing ether to the chequebook +* watching incoming ether + +Backend is the interface for that +*/ + +const ( + gasToCash = "2000000" // gas cost of a cash transaction using chequebook + getSentAbiPre = "d75d691d" // sent amount accessor in the chequebook contract + cashAbiPre = "fbf788d6" // abi preamble signature for cash method of the chequebook + queryInterval = 15000000000 // 15 seconds + deployGas = "3000000" + // confirmationInterval = 3 * 10 * *11 // 5 minutes +) + +// Backend is the interface to interact with the Ethereum blockchain +// implemented by xeth.XEth +type Backend interface { + Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) + Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error) + GetTxReceipt(txhash common.Hash) *types.Receipt + CodeAt(address string) string +} + +// rlp serialised cheque model for use with the chequebook +type Cheque struct { + // the address of the contract itself needed to avoid cross-contract submission + Contract common.Address // contract address + Beneficiary common.Address // beneficiary + Amount *big.Int // cumulative amount of all funds sent + Sig []byte // signature Sign(Sha3(contract, beneficiary, amount), prvKey) +} + +type Params struct { + ContractCode, ContractAbi, ContractSource string +} + +var ContractParams = &Params{ContractCode, ContractAbi, ContractSource} + +func (self *Cheque) String() string { + return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", self.Contract.Hex(), self.Beneficiary.Hex(), self.Amount, self.Sig) +} + +// chequebook to create, sign cheques from single contract to multiple beneficiarys +// outgoing payment handler for peer to peer micropayments +type Chequebook struct { + path string // path to chequebook file + prvKey *ecdsa.PrivateKey // private key to sign cheque with + lock sync.Mutex // + backend Backend // blockchain API + quit chan bool // when closed causes autodeposit to stop + owner common.Address // owner address (derived from pubkey) + + // persisted fields + balance *big.Int // not synced with blockchain + contract common.Address // contract address + sent map[common.Address]*big.Int //tallies for beneficiarys + + txhash string // tx hash of last deposit tx + threshold *big.Int // threshold that triggers autodeposit if not nil + buffer *big.Int // buffer to keep on top of balance for fork protection +} + +func Deploy(owner common.Address, backend Backend, amount *big.Int, confirmationInterval, timeout time.Duration) (contract common.Address, err error) { + if (owner == common.Address{}) { + return contract, fmt.Errorf("invalid owner %v. Owner needed to deploy chequebook contract: %v", owner.Hex(), err) + } + + txhash, err := backend.Transact(owner.Hex(), "", "", amount.String(), deployGas, "", ContractCode) + if err != nil { + return contract, fmt.Errorf("unable to send chequebook creation transaction: %v", err) + } + + timer := time.NewTimer(timeout).C + ticker := time.NewTicker(queryInterval).C +OUT: + for { + select { + + case <-timer: + if ticker == nil { + // ticker is nil, receipt was found and confirmation interval passed + err = Validate(contract, backend) + if err != nil { + return contract, fmt.Errorf("invalid contract at %v after %v: %v", contract.Hex(), confirmationInterval, err) + } + break OUT + } + // ticker is non-nil meaning receipt not found yet + return contract, fmt.Errorf("chequebook deployment timed out in %v", timeout) + + case <-ticker: + receipt := backend.GetTxReceipt(common.HexToHash(txhash)) + if receipt != nil { + contract = receipt.ContractAddress + glog.V(logger.Detail).Infof("[CHEQUEBOOK] chequebook deployed at %v (owner: %v)", contract.Hex(), owner.Hex()) + timer = time.NewTimer(confirmationInterval).C + ticker = nil + } else { + glog.V(logger.Detail).Infof("[CHEQUEBOOK] check if chequebook deployed (txhash: %v)", txhash) + } + + } + } + return contract, nil +} + +func Validate(contract common.Address, backend Backend) (err error) { + if (contract == common.Address{}) { + return fmt.Errorf("zero address") + } + code := backend.CodeAt(contract.Hex()) + if code != ContractDeployedCode { + return fmt.Errorf("incorrect code %v:\n%v\n%v", contract.Hex(), code, ContractDeployedCode) + } + return nil +} + +// NewChequebook(path, contract, balance, prvKey) creates a new Chequebook +func NewChequebook(path string, contract common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) { + balance := new(big.Int) + sent := make(map[common.Address]*big.Int) + owner := crypto.PubkeyToAddress(prvKey.PublicKey) + self = &Chequebook{ + balance: balance, + contract: contract, + sent: sent, + path: path, + prvKey: prvKey, + backend: backend, + owner: owner, + } + if (contract != common.Address{}) { + glog.V(logger.Detail).Infof("[CHEQUEBOOK] new chequebook initialised from %v (owner: %v)", contract.Hex(), owner.Hex()) + } + return +} + +// LoadChequebook(path, prvKey, backend) loads a chequebook from disk (file path) +func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) { + var data []byte + data, err = ioutil.ReadFile(path) + if err != nil { + return + } + + self, _ = NewChequebook(path, common.Address{}, prvKey, backend) + + err = json.Unmarshal(data, self) + if err != nil { + return nil, err + } + glog.V(logger.Detail).Infof("[CHEQUEBOOK] loaded chequebook (%s, owner: %v) initialised from %v", self.contract.Hex(), self.owner.Hex(), path) + + return +} + +// chequebook serialisation +type chequebookFile struct { + Balance string + Contract string + Owner string + Sent map[string]string +} + +func (self *Chequebook) UnmarshalJSON(data []byte) error { + var file chequebookFile + err := json.Unmarshal(data, &file) + if err != nil { + return err + } + _, ok := self.balance.SetString(file.Balance, 10) + if !ok { + return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) + } + self.contract = common.HexToAddress(file.Contract) + for addr, sent := range file.Sent { + self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10) + if !ok { + return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent) + } + } + return nil +} + +func (self *Chequebook) MarshalJSON() ([]byte, error) { + var file = &chequebookFile{ + Balance: self.balance.String(), + Contract: self.contract.Hex(), + Owner: self.owner.Hex(), + Sent: make(map[string]string), + } + for addr, sent := range self.sent { + file.Sent[addr.Hex()] = sent.String() + } + return json.Marshal(file) +} + +// Save() persists the chequebook on disk +// remembers balance, contract address and +// cumulative amount of funds sent for each beneficiary +func (self *Chequebook) Save() (err error) { + data, err := json.MarshalIndent(self, "", " ") + if err != nil { + return err + } + glog.V(logger.Detail).Infof("[CHEQUEBOOK] saving chequebook (%s) to %v", self.contract.Hex(), self.path) + + return ioutil.WriteFile(self.path, data, os.ModePerm) +} + +// Stop() quits the autodeposit go routine to terminate +func (self *Chequebook) Stop() { + defer self.lock.Unlock() + self.lock.Lock() + if self.quit != nil { + close(self.quit) + self.quit = nil + } +} + +// Issue(beneficiary, amount) will create a Cheque +// the cheque is signed by the chequebook owner's private key +// the signer commits to a contract (one that they own), a beneficiary and amount +func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) { + defer self.lock.Unlock() + self.lock.Lock() + if amount.Cmp(common.Big0) <= 0 { + return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) + } + if self.balance.Cmp(amount) < 0 { + err = fmt.Errorf("insufficent funds to issue cheque for amount: %v. balance: %v", amount, self.balance) + } else { + var sig []byte + sent, found := self.sent[beneficiary] + if !found { + sent = new(big.Int) + self.sent[beneficiary] = sent + } + sum := new(big.Int).Set(sent) + sum.Add(sum, amount) + sig, err = crypto.Sign(sigHash(self.contract, beneficiary, sum), self.prvKey) + if err == nil { + ch = &Cheque{ + Contract: self.contract, + Beneficiary: beneficiary, + Amount: sum, + Sig: sig, + } + sent.Set(sum) + self.balance.Sub(self.balance, amount) // subtract amount from balance + } + } + + // auto deposit if threshold is set and balance is less then threshold + // note this is called even if issueing cheque fails + // so we reattempt depositing + if self.threshold != nil { + if self.balance.Cmp(self.threshold) < 0 { + send := new(big.Int).Sub(self.buffer, self.balance) + self.deposit(send) + } + } + + return +} + +// convenience method to cash any cheque +func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) { + return ch.Cash(self.owner, self.backend) +} + +// data to sign: contract address, beneficiary, cumulative amount of funds ever sent +func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte { + bigamount := sum.Bytes() + if len(bigamount) > 32 { + return nil + } + var amount32 [32]byte + copy(amount32[32-len(bigamount):32], bigamount) + input := append(contract.Bytes(), beneficiary.Bytes()...) + input = append(input, amount32[:]...) + return crypto.Sha3(input) +} + +// Balance() public accessor for balance +func (self *Chequebook) Balance() *big.Int { + defer self.lock.Unlock() + self.lock.Lock() + return new(big.Int).Set(self.balance) +} + +// Balance() public accessor for balance +func (self *Chequebook) Owner() common.Address { + return self.owner +} + +// Backend() public accessor for backend +func (self *Chequebook) Backend() Backend { + return self.backend +} + +// Address() public accessor for contract +func (self *Chequebook) Address() common.Address { + return self.contract +} + +// Deposit(amount) deposits amount to the chequebook account +func (self *Chequebook) Deposit(amount *big.Int) (string, error) { + defer self.lock.Unlock() + self.lock.Lock() + return self.deposit(amount) +} + +// deposit(amount) deposits amount to the chequebook account +// caller holds the lock +func (self *Chequebook) deposit(amount *big.Int) (string, error) { + txhash, err := self.backend.Transact(self.owner.Hex(), self.contract.Hex(), "", amount.String(), "", "", "") + // assume that transaction is actually successful, we add the amount to balance right away + if err != nil { + glog.V(logger.Warn).Infof("[CHEQUEBOOK] error depositing %d wei to chequebook (%s, balance: %v, target: %v): %v", amount, self.contract.Hex(), self.balance, self.buffer, err) + } else { + self.balance.Add(self.balance, amount) + glog.V(logger.Detail).Infof("[CHEQUEBOOK] deposited %d wei to chequebook (%s, balance: %v, target: %v)", amount, self.contract.Hex(), self.balance, self.buffer) + } + return txhash, err +} + +// AutoDeposit(interval, threshold, buffer) (re)sets interval time and amount +// which triggers sending funds to the chequebook contract +// backend needs to be set +// if threshold is not less than buffer, then deposit will be triggered on +// every new cheque issued +func (self *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { + defer self.lock.Unlock() + self.lock.Lock() + self.threshold = threshold + self.buffer = buffer + self.autoDeposit(interval) +} + +// autoDeposit(interval) starts a go routine that periodically sends funds to the +// chequebook contract +// caller holds the lock +// the go routine terminates if Chequebook.quit us closed +func (self *Chequebook) autoDeposit(interval time.Duration) { + if self.quit != nil { + close(self.quit) + self.quit = nil + } + // if threshold >= balance autodeposit after every cheque issued + if interval == time.Duration(0) || self.threshold != nil && self.buffer != nil && self.threshold.Cmp(self.buffer) >= 0 { + return + } + + ticker := time.NewTicker(interval) + self.quit = make(chan bool) + quit := self.quit + go func() { + FOR: + for { + select { + case <-quit: + break FOR + case <-ticker.C: + self.lock.Lock() + if self.balance.Cmp(self.buffer) < 0 { + amount := new(big.Int).Sub(self.buffer, self.balance) + txhash, err := self.deposit(amount) + if err == nil { + self.txhash = txhash + } + } + self.lock.Unlock() + } + } + }() + return +} + +type Outbox struct { + chequeBook *Chequebook + beneficiary common.Address +} + +func NewOutbox(chbook *Chequebook, beneficiary common.Address) *Outbox { + return &Outbox{chbook, beneficiary} +} + +func (self *Outbox) Issue(amount *big.Int) (swap.Promise, error) { + return self.chequeBook.Issue(self.beneficiary, amount) +} + +func (self *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { + self.chequeBook.AutoDeposit(interval, threshold, buffer) +} + +func (self *Outbox) Stop() {} + +func (self *Outbox) String() string { + return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.chequeBook.Address().Hex(), self.beneficiary.Hex(), self.chequeBook.Balance()) +} + +// type ChequeQueue struct { +// beneficiary common.Address +// last map[string]*Inbox +// } + +// inbox to deposit, verify and cash cheques +// from a single contract to single beneficiary +// incoming payment handler for peer to peer micropayments +type Inbox struct { + lock sync.Mutex + contract common.Address // peer's chequebook contract + beneficiary common.Address // local peer's receiving address + sender common.Address // local peer's address to send cashing tx from + signer *ecdsa.PublicKey // peer's public key + txhash string // tx hash of last cashing tx + backend Backend // blockchain API + quit chan bool // when closed causes autocash to stop + maxUncashed *big.Int // threshold that triggers autocashing + cashed *big.Int // cumulative amount cashed + cheque *Cheque // last cheque, nil if none yet received +} + +// NewInbox(contract, beneficiary, signer, backend) constructor for Inbox +// not persisted, cumulative sum updated from blockchain when first cheque received +// backend used to sync amount (Call) as well as cash the cheques (Transact) +func NewInbox(contract, sender, beneficiary common.Address, signer *ecdsa.PublicKey, backend Backend) (self *Inbox, err error) { + + if signer == nil { + return nil, fmt.Errorf("signer is null") + } + + self = &Inbox{ + contract: contract, + beneficiary: beneficiary, + sender: sender, + signer: signer, + backend: backend, + cashed: new(big.Int).Set(common.Big0), + } + glog.V(logger.Detail).Infof("[CHEQUEBOOK] initialised inbox (%s -> %s) expected signer: %x", self.contract.Hex(), self.beneficiary.Hex(), crypto.FromECDSAPub(signer)) + return +} + +func (self *Inbox) String() string { + return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.contract.Hex(), self.beneficiary.Hex(), self.cheque.Amount) +} + +// Stop() quits the autocash go routine to terminate +func (self *Inbox) Stop() { + defer self.lock.Unlock() + self.lock.Lock() + if self.quit != nil { + close(self.quit) + self.quit = nil + } +} + +func (self *Inbox) Cash() (txhash string, err error) { + if self.cheque != nil { + txhash, err = self.cheque.Cash(self.sender, self.backend) + glog.V(logger.Detail).Infof("[CHEQUEBOOK] cashing cheque (total: %v) on chequebook (%s) sending to %v", self.cheque.Amount, self.contract.Hex(), self.beneficiary.Hex()) + self.cashed = self.cheque.Amount + } + return +} + +// AutoCash(cashInterval, maxUncashed) (re)sets maximum time and amount which +// triggers cashing of the last uncashed cheque +// if maxUncashed is set to 0, then autocash on receipt +func (self *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) { + defer self.lock.Unlock() + self.lock.Lock() + self.maxUncashed = maxUncashed + self.autoCash(cashInterval) +} + +// autoCash(d) starts a loop that periodically clears the last check +// if the peer is trusted, clearing period could be 24h, or a week +// caller holds the lock +func (self *Inbox) autoCash(cashInterval time.Duration) { + if self.quit != nil { + close(self.quit) + self.quit = nil + } + // if maxUncashed is set to 0, then autocash on receipt + if cashInterval == time.Duration(0) || self.maxUncashed != nil && self.maxUncashed.Cmp(common.Big0) == 0 { + return + } + + ticker := time.NewTicker(cashInterval) + self.quit = make(chan bool) + quit := self.quit + go func() { + FOR: + for { + select { + case <-quit: + break FOR + case <-ticker.C: + self.lock.Lock() + if self.cheque != nil && self.cheque.Amount.Cmp(self.cashed) != 0 { + txhash, err := self.Cash() + if err == nil { + self.txhash = txhash + } + } + self.lock.Unlock() + } + } + }() + return +} + +// Reveive(cheque) called to deposit latest cheque to incoming Inbox +func (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) { + ch := promise.(*Cheque) + + defer self.lock.Unlock() + self.lock.Lock() + var sum *big.Int + if self.cheque == nil { + // the sum is checked against the blockchain once a check is received + tallyhex, _, err := self.backend.Call(self.beneficiary.Hex(), self.contract.Hex(), "", "", "", getSentAbiEncode(ch.Contract)) + if err != nil { + return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err) + } + + tally := common.FromHex(tallyhex) + // var ok bool + // sum, ok = new(big.Int).SetString(tally, 10) + // if !ok { + // return nil, fmt.Errorf("inbox: cannot convert amount '%s' (%v) to integer", tallyhex, tally) + // } + sum = new(big.Int).SetBytes(tally) + } else { + sum = self.cheque.Amount + } + + amount, err := ch.Verify(self.signer, self.contract, self.beneficiary, sum) + var uncashed *big.Int + if err == nil { + self.cheque = ch + + if self.maxUncashed != nil { + uncashed = new(big.Int).Sub(ch.Amount, self.cashed) + if self.maxUncashed.Cmp(uncashed) < 0 { + self.Cash() + } + } + glog.V(logger.Detail).Infof("[CHEQUEBOOK] received cheque of %v wei in inbox (%s, uncashed: %v)", amount, self.contract.Hex(), uncashed) + } + + return amount, err +} + +// RSV representation of signature +func sig2rsv(sig []byte) (v byte, r, s []byte) { + v = sig[64] + 27 + r = sig[:32] + s = sig[32:64] + return +} + +func getSentAbiEncode(beneficiary common.Address) string { + var beneficiary32 [32]byte + copy(beneficiary32[12:], beneficiary.Bytes()) + return getSentAbiPre + common.Bytes2Hex(beneficiary32[:]) +} + +// abi encoding of a cheque to send as eth tx data +func (self *Cheque) cashAbiEncode() string { + v, r, s := sig2rsv(self.Sig) + // cashAbiPre, beneficiary, amount, v, r, s + bigamount := self.Amount.Bytes() + if len(bigamount) > 32 { + glog.V(logger.Detail).Infof("[CHEQUEBOOK] number too big: %v (>32 bytes)", self.Amount) + return "" + } + var beneficiary32, amount32, vabi [32]byte + copy(beneficiary32[12:], self.Beneficiary.Bytes()) + copy(amount32[32-len(bigamount):32], bigamount) + vabi[31] = v + return cashAbiPre + common.Bytes2Hex(beneficiary32[:]) + common.Bytes2Hex(amount32[:]) + + common.Bytes2Hex(vabi[:]) + common.Bytes2Hex(r) + common.Bytes2Hex(s) +} + +// Verify(cheque) verifies cheque for signer, contract, beneficiary, amount, valid signature +func (self *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) { + glog.V(logger.Detail).Infof("[CHEQUEBOOK] verify cheque: %v - sum: %v", self, sum) + if sum == nil { + return nil, fmt.Errorf("invalid amount") + } + + if self.Beneficiary != beneficiary { + return nil, fmt.Errorf("beneficiary mismatch: %v != %v", self.Beneficiary.Hex(), beneficiary.Hex()) + } + if self.Contract != contract { + return nil, fmt.Errorf("contract mismatch: %v != %v", self.Contract.Hex(), contract.Hex()) + } + + amount := new(big.Int).Set(self.Amount) + if sum != nil { + amount.Sub(amount, sum) + if amount.Cmp(common.Big0) <= 0 { + return nil, fmt.Errorf("incorrect amount: %v <= 0", amount) + } + } + + pubKey, err := crypto.SigToPub(sigHash(self.Contract, beneficiary, self.Amount), self.Sig) + if err != nil { + return nil, fmt.Errorf("invalid signature: %v", err) + } + if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) { + return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) + } + return amount, nil +} + +// Cash(backend) will cash the check using xeth backend to send a transaction +// Beneficiary address should be unlocked +func (self *Cheque) Cash(sender common.Address, backend Backend) (string, error) { + return backend.Transact(sender.Hex(), self.Contract.Hex(), "", "", "", gasToCash, self.cashAbiEncode()) +} diff --git a/common/chequebook/cheque_test.go b/common/chequebook/cheque_test.go new file mode 100644 index 0000000000..06d95ff380 --- /dev/null +++ b/common/chequebook/cheque_test.go @@ -0,0 +1,498 @@ +package chequebook + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +type testBackend struct { + calls []string + errs []error + txs []string +} + +func newTestBackend() *testBackend { + return &testBackend{} +} + +func (b *testBackend) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { + txhash := string(crypto.Sha3([]byte(codeStr))) + b.txs = append(b.txs, txhash) + return txhash, nil +} + +func (b *testBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error) { + if len(b.calls) == 0 { + panic("test backend called too many times") + } + res := b.calls[0] + err := b.errs[0] + b.calls = b.calls[1:] + b.errs = b.errs[1:] + return res, "", err +} + +func (b *testBackend) GetTxReceipt(txhash common.Hash) *types.Receipt { + return nil +} + +func (b *testBackend) CodeAt(address string) string { + return "" +} + +func genAddr() common.Address { + prvKey, _ := crypto.GenerateKey() + return crypto.PubkeyToAddress(prvKey.PublicKey) +} + +func TestIssueAndReceive(t *testing.T) { + prvKey, _ := crypto.GenerateKey() + sender := genAddr() + path := "/tmp/checkbook.json" + chbook, err := NewChequebook(path, sender, prvKey, nil) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + recipient := genAddr() + chbook.sent[recipient] = new(big.Int).SetUint64(42) + amount := common.Big1 + ch, err := chbook.Issue(recipient, amount) + if err == nil { + t.Errorf("expected insufficient funds error, got none") + } + + chbook.balance = new(big.Int).Set(common.Big1) + if chbook.Balance().Cmp(common.Big1) != 0 { + t.Errorf("expected: %v, got %v", "0", chbook.Balance()) + } + + ch, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if chbook.Balance().Cmp(common.Big0) != 0 { + t.Errorf("expected: %v, got %v", "0", chbook.Balance()) + } + + backend := newTestBackend() + backend.calls = []string{common.ToHex(big.NewInt(42).Bytes())} + backend.errs = []error{nil} + chbox, err := NewInbox(sender, recipient, recipient, &prvKey.PublicKey, backend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + received, err := chbox.Receive(ch) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if received.Cmp(common.Big1) != 0 { + t.Errorf("expected: %v, got %v", "1", received) + } + +} + +func TestCheckbookFile(t *testing.T) { + prvKey, _ := crypto.GenerateKey() + sender := genAddr() + path := "/tmp/checkbook.json" + chbook, err := NewChequebook(path, sender, prvKey, nil) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + recipient := genAddr() + chbook.sent[recipient] = new(big.Int).SetUint64(42) + chbook.balance = new(big.Int).Set(common.Big1) + + chbook.Save() + + chbook, err = LoadChequebook(path, prvKey, nil) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if chbook.Balance().Cmp(common.Big1) != 0 { + t.Errorf("expected: %v, got %v", "0", chbook.Balance()) + } + + ch, err := chbook.Issue(recipient, common.Big1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if ch.Amount.Cmp(new(big.Int).SetUint64(43)) != 0 { + t.Errorf("expected: %v, got %v", "0", ch.Amount) + } + + err = chbook.Save() + if err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + +func TestVerifyErrors(t *testing.T) { + prvKey, _ := crypto.GenerateKey() + sender0 := genAddr() + sender1 := genAddr() + path0 := "/tmp/checkbook0.json" + chbook0, err := NewChequebook(path0, sender0, prvKey, nil) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + path1 := "/tmp/checkbook1.json" + chbook1, err := NewChequebook(path1, sender1, prvKey, nil) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + recipient0 := genAddr() + recipient1 := genAddr() + chbook0.balance = new(big.Int).Set(common.Big2) + chbook1.balance = new(big.Int).Set(common.Big1) + chbook0.sent[recipient0] = new(big.Int).SetUint64(42) + amount := common.Big1 + ch0, err := chbook0.Issue(recipient0, amount) + + backend := newTestBackend() + backend.calls = []string{common.ToHex(big.NewInt(42).Bytes())} + backend.errs = []error{nil} + chbox, err := NewInbox(sender0, recipient0, recipient0, &prvKey.PublicKey, backend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + received, err := chbox.Receive(ch0) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if received.Cmp(common.Big1) != 0 { + t.Errorf("expected: %v, got %v", "1", received) + } + + ch1, err := chbook0.Issue(recipient1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + received, err = chbox.Receive(ch1) + t.Log(err) + if err == nil { + t.Fatalf("expected receiver error, got none") + } + + ch2, err := chbook1.Issue(recipient0, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + received, err = chbox.Receive(ch2) + t.Log(err) + if err == nil { + t.Fatalf("expected sender error, got none") + } + + _, err = chbook1.Issue(recipient0, new(big.Int).SetInt64(-1)) + t.Log(err) + if err == nil { + t.Fatalf("expected incorrect amount error, got none") + } + + received, err = chbox.Receive(ch0) + t.Log(err) + if err == nil { + t.Fatalf("expected incorrect amount error, got none") + } + +} + +func TestDeposit(t *testing.T) { + prvKey, _ := crypto.GenerateKey() + sender := genAddr() + path := "/tmp/checkbook.json" + + backend := newTestBackend() + chbook, err := NewChequebook(path, sender, prvKey, backend) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + balance := new(big.Int).SetUint64(42) + chbook.Deposit(balance) + if len(backend.txs) != 1 { + t.Fatalf("expected 1 txs to send, got %v", len(backend.txs)) + } + if chbook.balance.Cmp(balance) != 0 { + t.Fatalf("expected balance %v, got %v", balance, chbook.balance) + } + + recipient := genAddr() + amount := common.Big1 + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + exp := new(big.Int).SetUint64(41) + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + + // autodeposit on each issue + chbook.AutoDeposit(0, balance, balance) + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if len(backend.txs) != 3 { + t.Fatalf("expected 3 txs to send, got %v", len(backend.txs)) + } + if chbook.balance.Cmp(balance) != 0 { + t.Fatalf("expected balance %v, got %v", balance, chbook.balance) + } + + // autodeposit off + chbook.AutoDeposit(0, common.Big0, balance) + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if len(backend.txs) != 3 { + t.Fatalf("expected 3 txs to send, got %v", len(backend.txs)) + } + exp = new(big.Int).SetUint64(40) + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + + // autodeposit every 10ms if new cheque issued + interval := 30 * time.Millisecond + chbook.AutoDeposit(interval, common.Big1, balance) + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if len(backend.txs) != 3 { + t.Fatalf("expected 3 txs to send, got %v", len(backend.txs)) + } + exp = new(big.Int).SetUint64(38) + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + + time.Sleep(3 * interval) + if len(backend.txs) != 4 { + t.Fatalf("expected 4 txs to send, got %v", len(backend.txs)) + } + if chbook.balance.Cmp(balance) != 0 { + t.Fatalf("expected balance %v, got %v", balance, chbook.balance) + } + + exp = new(big.Int).SetUint64(40) + chbook.AutoDeposit(4*interval, exp, balance) + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + time.Sleep(3 * interval) + if len(backend.txs) != 4 { + t.Fatalf("expected 4 txs to send, got %v", len(backend.txs)) + } + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + + _, err = chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + time.Sleep(1 * interval) + + if len(backend.txs) != 5 { + t.Fatalf("expected 5 txs to send, got %v", len(backend.txs)) + } + if chbook.balance.Cmp(balance) != 0 { + t.Fatalf("expected balance %v, got %v", balance, chbook.balance) + } + + chbook.AutoDeposit(1*interval, common.Big0, balance) + chbook.Stop() + + _, err = chbook.Issue(recipient, common.Big1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + _, err = chbook.Issue(recipient, common.Big2) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + time.Sleep(1 * interval) + + if len(backend.txs) != 5 { + t.Fatalf("expected 5 txs to send, got %v", len(backend.txs)) + } + exp = new(big.Int).SetUint64(39) + if chbook.balance.Cmp(exp) != 0 { + t.Fatalf("expected balance %v, got %v", exp, chbook.balance) + } + +} + +func TestCash(t *testing.T) { + prvKey, _ := crypto.GenerateKey() + sender := genAddr() + path := "/tmp/checkbook.json" + chbook, err := NewChequebook(path, sender, prvKey, nil) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + recipient := genAddr() + chbook.sent[recipient] = new(big.Int).SetUint64(42) + amount := common.Big1 + chbook.balance = new(big.Int).Set(common.Big1) + ch, err := chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + backend := newTestBackend() + backend.calls = []string{common.ToHex(big.NewInt(42).Bytes())} + backend.errs = []error{nil} + chbox, err := NewInbox(sender, recipient, recipient, &prvKey.PublicKey, backend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + // cashing latest cheque + _, err = chbox.Receive(ch) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = ch.Cash(recipient, backend) + if len(backend.txs) != 1 { + t.Fatalf("expected 1 txs to send, got %v", len(backend.txs)) + } + + chbook.balance = new(big.Int).Set(common.Big3) + ch0, err := chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + ch1, err := chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + interval := 10 * time.Millisecond + // setting autocash with interval of 10ms + chbox.AutoCash(interval, nil) + _, err = chbox.Receive(ch0) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = chbox.Receive(ch1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + // after < interval time and 2 cheques received, no new cashing tx is sent + if len(backend.txs) != 1 { + t.Fatalf("expected 1 txs to send, got %v", len(backend.txs)) + } + // after 3x interval time and 2 cheques received, exactly one cashing tx is sent + time.Sleep(4 * interval) + if len(backend.txs) != 2 { + t.Fatalf("expected 2 txs to send, got %v", len(backend.txs)) + } + + // after stopping autocash no more tx are sent + ch2, err := chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + chbox.Stop() + time.Sleep(interval) // make sure loop stops + _, err = chbox.Receive(ch2) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + time.Sleep(2 * interval) + if len(backend.txs) != 2 { + t.Fatalf("expected 2 txs to send, got %v", len(backend.txs)) + } + + // autocash below 1 + chbook.balance = new(big.Int).Set(common.Big2) + chbox.AutoCash(0, common.Big1) + + ch3, err := chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + ch4, err := chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + _, err = chbox.Receive(ch3) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = chbox.Receive(ch4) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + // 2 checks of amount 1 received, exactly 1 tx is sent + if len(backend.txs) != 3 { + t.Fatalf("expected 3 txs to send, got %v", len(backend.txs)) + } + + // autochash on receipt when maxUncashed is 0 + chbook.balance = new(big.Int).Set(common.Big2) + chbox.AutoCash(0, common.Big0) + + ch5, err := chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + ch6, err := chbook.Issue(recipient, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = chbox.Receive(ch5) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + _, err = chbox.Receive(ch6) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if len(backend.txs) != 5 { + t.Fatalf("expected 5 txs to send, got %v", len(backend.txs)) + } + +} diff --git a/common/chequebook/chequebook.sol b/common/chequebook/chequebook.sol new file mode 100644 index 0000000000..836d570472 --- /dev/null +++ b/common/chequebook/chequebook.sol @@ -0,0 +1,49 @@ +import "mortal"; + +/// @title Chequebook for Ethereum micropayments +/// @author Daniel A. Nagy +contract chequebook is mortal { + // Cumulative paid amount in wei to each beneficiary + mapping (address => uint256) sent; + + /// @notice Overdraft event + event Overdraft(address deadbeat); + + /// @notice Accessor for sent map + /// + /// @param beneficiary beneficiary address + /// @return cumulative amount in wei sent to beneficiary + function getSent(address beneficiary) constant returns (uint256) { + return sent[beneficiary]; + } + + /// @notice Cash cheque + /// + /// @param beneficiary beneficiary address + /// @param amount cumulative amount in wei + /// @param sig_v signature parameter v + /// @param sig_r signature parameter r + /// @param sig_s signature parameter s + /// The digital signature is calculated on the concatenated triplet of contract address, beneficiary address and cumulative amount + function cash(address beneficiary, uint256 amount, + uint8 sig_v, bytes32 sig_r, bytes32 sig_s) { + // Check if the cheque is old. + // Only cheques that are more recent than the last cashed one are considered. + if(amount <= sent[beneficiary]) return; + // Check the digital signature of the cheque. + bytes32 hash = sha3(address(this), beneficiary, amount); + if(owner != ecrecover(hash, sig_v, sig_r, sig_s)) return; + // Attempt sending the difference between the cumulative amount on the cheque + // and the cumulative amount on the last cashed cheque to beneficiary. + if (beneficiary.send(amount - sent[beneficiary])) { + // Upon success, update the cumulative amount. + sent[beneficiary] = amount; + } else { + // Upon failure, punish owner for writing a bounced cheque. + // owner.sendToDebtorsPrison(); + Overdraft(owner); + // Compensate beneficiary. + suicide(beneficiary); + } + } +} diff --git a/common/chequebook/contract.go b/common/chequebook/contract.go new file mode 100644 index 0000000000..866d7c3358 --- /dev/null +++ b/common/chequebook/contract.go @@ -0,0 +1,63 @@ +package chequebook + +import () + +const ( + ContractCode = `606060405260008054600160a060020a03191633179055610201806100246000396000f3606060405260e060020a600035046341c0e1b58114610031578063d75d691d14610059578063fbf788d61461007e575b005b61002f60005433600160a060020a03908116911614156101ff57600054600160a060020a0316ff5b600160a060020a03600435166000908152600160205260409020546060908152602090f35b61002f600435602435604435606435608435600160a060020a03851660009081526001602052604081205485116100b8575b505050505050565b30600160a060020a039081166c0100000000000000000000000090810260609081529188160260745260888690526048812080825260ff8616608090815260a086905260c0859052909260019260e0926020928290866161da5a03f11561000257505060405151600054600160a060020a03908116911614610139576100b0565b85600160a060020a031660006001600050600089600160a060020a03168152602001908152602001600020600050548703604051809050600060405180830381858888f19350505050156101b357846001600050600088600160a060020a03168152602001908152602001600020600050819055506100b0565b60005460408051600160a060020a03929092168252517f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f9789181900360200190a185600160a060020a0316ff5b56` + + ContractDeployedCode = `0x606060405260e060020a600035046341c0e1b58114610031578063d75d691d14610059578063fbf788d61461007e575b005b61002f60005433600160a060020a03908116911614156101ff57600054600160a060020a0316ff5b600160a060020a03600435166000908152600160205260409020546060908152602090f35b61002f600435602435604435606435608435600160a060020a03851660009081526001602052604081205485116100b8575b505050505050565b30600160a060020a039081166c0100000000000000000000000090810260609081529188160260745260888690526048812080825260ff8616608090815260a086905260c0859052909260019260e0926020928290866161da5a03f11561000257505060405151600054600160a060020a03908116911614610139576100b0565b85600160a060020a031660006001600050600089600160a060020a03168152602001908152602001600020600050548703604051809050600060405180830381858888f19350505050156101b357846001600050600088600160a060020a03168152602001908152602001600020600050819055506100b0565b60005460408051600160a060020a03929092168252517f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f9789181900360200190a185600160a060020a0316ff5b56` + + ContractAbi = `[{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"beneficiary","type":"address"}],"name":"getSent","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"beneficiary","type":"address"},{"name":"amount","type":"uint256"},{"name":"sig_v","type":"uint8"},{"name":"sig_r","type":"bytes32"},{"name":"sig_s","type":"bytes32"}],"name":"cash","outputs":[],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"deadbeat","type":"address"}],"name":"Overdraft","type":"event"}]` + + ContractSource = ` +import "mortal"; + +/// @title Chequebook for Ethereum micropayments +/// @author Daniel A. Nagy +contract chequebook is mortal { + // Cumulative paid amount in wei to each beneficiary + mapping (address => uint256) sent; + + /// @notice Overdraft event + event Overdraft(address deadbeat); + + /// @notice Accessor for sent map + /// + /// @param beneficiary beneficiary address + /// @return cumulative amount in wei sent to beneficiary + function getSent(address beneficiary) returns (uint256) { + return sent[beneficiary]; + } + + /// @notice Cash cheque + /// + /// @param beneficiary beneficiary address + /// @param amount cumulative amount in wei + /// @param sig_v signature parameter v + /// @param sig_r signature parameter r + /// @param sig_s signature parameter s + /// The digital signature is calculated on the concatenated triplet of contract address, beneficiary address and cumulative amount + function cash(address beneficiary, uint256 amount, + uint8 sig_v, bytes32 sig_r, bytes32 sig_s) { + // Check if the cheque is old. + // Only cheques that are more recent than the last cashed one are considered. + if(amount <= sent[beneficiary]) return; + // Check the digital signature of the cheque. + bytes32 hash = sha3(address(this), beneficiary, amount); + if(owner != ecrecover(hash, sig_v, sig_r, sig_s)) return; + // Attempt sending the difference between the cumulative amount on the cheque + // and the cumulative amount on the last cashed cheque to beneficiary. + if (beneficiary.send(amount - sent[beneficiary])) { + // Upon success, update the cumulative amount. + sent[beneficiary] = amount; + } else { + // Upon failure, punish owner for writing a bounced cheque. + // owner.sendToDebtorsPrison(); + Overdraft(owner); + // Compensate beneficiary. + suicide(beneficiary); + } + } +} +` +) diff --git a/common/kademlia/address.go b/common/kademlia/address.go new file mode 100644 index 0000000000..a6ce4e4770 --- /dev/null +++ b/common/kademlia/address.go @@ -0,0 +1,157 @@ +package kademlia + +import ( + "fmt" + "math/rand" + "strings" + + "github.com/ethereum/go-ethereum/common" +) + +type Address common.Hash + +func (a Address) String() string { + return fmt.Sprintf("%x", a[:]) +} + +func (a *Address) MarshalJSON() (out []byte, err error) { + return []byte(`"` + a.String() + `"`), nil +} + +func (a *Address) UnmarshalJSON(value []byte) error { + *a = Address(common.HexToHash(string(value[1 : len(value)-1]))) + return nil +} + +// the string form of the binary representation of an address (only first 8 bits) +func (a Address) Bin() string { + var bs []string + for _, b := range a[:] { + bs = append(bs, fmt.Sprintf("%08b", b)) + } + return strings.Join(bs, "") +} + +/* +Proximity(x, y) returns the proximity order of the MSB distance between x and y + +The distance metric MSB(x, y) of two equal length byte sequences x an y is the +value of the binary integer cast of the x^y, ie., x and y bitwise xor-ed. +the binary cast is big endian: most significant bit first (=MSB). + +Proximity(x, y) is a discrete logarithmic scaling of the MSB distance. +It is defined as the reverse rank of the integer part of the base 2 +logarithm of the distance. +It is calculated by counting the number of common leading zeros in the (MSB) +binary representation of the x^y. + +(0 farthest, 255 closest, 256 self) +*/ +func proximity(one, other Address) (ret int) { + for i := 0; i < len(one); i++ { + oxo := one[i] ^ other[i] + for j := 0; j < 8; j++ { + if (uint8(oxo)>>uint8(7-j))&0x01 != 0 { + return i*8 + j + } + } + } + return len(one) * 8 +} + +// Address.ProxCmp compares the distances a->target and b->target. +// Returns -1 if a is closer to target, 1 if b is closer to target +// and 0 if they are equal. +func (target Address) ProxCmp(a, b Address) int { + for i := range target { + da := a[i] ^ target[i] + db := b[i] ^ target[i] + if da > db { + return 1 + } else if da < db { + return -1 + } + } + return 0 +} + +// randomAddressAt(address, prox) generates a random address +// at proximity order prox relative to address +// if prox is negative a random address is generated +func RandomAddressAt(self Address, prox int) (addr Address) { + addr = self + var pos int + if prox >= 0 { + pos = prox / 8 + trans := prox % 8 + transbytea := byte(0) + for j := 0; j <= trans; j++ { + transbytea |= 1 << uint8(7-j) + } + flipbyte := byte(1 << uint8(7-trans)) + transbyteb := transbytea ^ byte(255) + randbyte := byte(rand.Intn(255)) + addr[pos] = ((addr[pos] & transbytea) ^ flipbyte) | randbyte&transbyteb + } + for i := pos + 1; i < len(addr); i++ { + addr[i] = byte(rand.Intn(255)) + } + + return +} + +// KeyRange(a0, a1, proxLimit) returns the address inclusive address +// range that contain addresses closer to one than other +func KeyRange(one, other Address, proxLimit int) (start, stop Address) { + prox := proximity(one, other) + if prox >= proxLimit { + prox = proxLimit + } + start = CommonBitsAddrByte(one, other, byte(0x00), prox) + stop = CommonBitsAddrByte(one, other, byte(0xff), prox) + return +} + +func CommonBitsAddrF(self, other Address, f func() byte, p int) (addr Address) { + prox := proximity(self, other) + var pos int + if p <= prox { + prox = p + } + pos = prox / 8 + addr = self + trans := byte(prox % 8) + var transbytea byte + if p > prox { + transbytea = byte(0x7f) + } else { + transbytea = byte(0xff) + } + transbytea >>= trans + transbyteb := transbytea ^ byte(0xff) + addrpos := addr[pos] + addrpos &= transbyteb + if p > prox { + addrpos ^= byte(0x80 >> trans) + } + addrpos |= transbytea & f() + addr[pos] = addrpos + for i := pos + 1; i < len(addr); i++ { + addr[i] = f() + } + + return +} + +func CommonBitsAddr(self, other Address, prox int) (addr Address) { + return CommonBitsAddrF(self, other, func() byte { return byte(rand.Intn(255)) }, prox) +} + +func CommonBitsAddrByte(self, other Address, b byte, prox int) (addr Address) { + return CommonBitsAddrF(self, other, func() byte { return b }, prox) +} + +// randomAddressAt() generates a random address +func RandomAddress() Address { + return RandomAddressAt(Address{}, -1) +} diff --git a/common/kademlia/address_test.go b/common/kademlia/address_test.go new file mode 100644 index 0000000000..b0577dc243 --- /dev/null +++ b/common/kademlia/address_test.go @@ -0,0 +1,80 @@ +package kademlia + +import ( + "math/rand" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func (Address) Generate(rand *rand.Rand, size int) reflect.Value { + var id Address + for i := 0; i < len(id); i++ { + id[i] = byte(uint8(rand.Intn(255))) + } + return reflect.ValueOf(id) +} + +func TestCommonBitsAddrF(t *testing.T) { + a := Address(common.HexToHash("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + b := Address(common.HexToHash("0x8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + c := Address(common.HexToHash("0x4123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + d := Address(common.HexToHash("0x0023456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + e := Address(common.HexToHash("0x01A3456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + ab := CommonBitsAddrF(a, b, func() byte { return byte(0x00) }, 10) + expab := Address(common.HexToHash("0x8000000000000000000000000000000000000000000000000000000000000000")) + + if ab != expab { + t.Fatalf("%v != %v", ab, expab) + } + ac := CommonBitsAddrF(a, c, func() byte { return byte(0x00) }, 10) + expac := Address(common.HexToHash("0x4000000000000000000000000000000000000000000000000000000000000000")) + + if ac != expac { + t.Fatalf("%v != %v", ac, expac) + } + ad := CommonBitsAddrF(a, d, func() byte { return byte(0x00) }, 10) + expad := Address(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000")) + + if ad != expad { + t.Fatalf("%v != %v", ad, expad) + } + ae := CommonBitsAddrF(a, e, func() byte { return byte(0x00) }, 10) + expae := Address(common.HexToHash("0x0180000000000000000000000000000000000000000000000000000000000000")) + + if ae != expae { + t.Fatalf("%v != %v", ae, expae) + } + acf := CommonBitsAddrF(a, c, func() byte { return byte(0xff) }, 10) + expacf := Address(common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + + if acf != expacf { + t.Fatalf("%v != %v", acf, expacf) + } + aeo := CommonBitsAddrF(a, e, func() byte { return byte(0x00) }, 2) + expaeo := Address(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000")) + + if aeo != expaeo { + t.Fatalf("%v != %v", aeo, expaeo) + } + aep := CommonBitsAddrF(a, e, func() byte { return byte(0xff) }, 2) + expaep := Address(common.HexToHash("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + + if aep != expaep { + t.Fatalf("%v != %v", aep, expaep) + } + +} + +func TestRandomAddressAt(t *testing.T) { + var a Address + for i := 0; i < 100; i++ { + a = RandomAddress() + prox := rand.Intn(255) + b := RandomAddressAt(a, prox) + if proximity(a, b) != prox { + t.Fatalf("incorrect address prox(%v, %v) == %v (expected %v)", a, b, proximity(a, b), prox) + } + } +} diff --git a/common/kademlia/kaddb.go b/common/kademlia/kaddb.go new file mode 100644 index 0000000000..e45a9dc48e --- /dev/null +++ b/common/kademlia/kaddb.go @@ -0,0 +1,360 @@ +package kademlia + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "sync" + "time" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +type Time time.Time + +func (t *Time) MarshalJSON() (out []byte, err error) { + return []byte(fmt.Sprintf("%d", t.Unix())), nil +} + +func (t *Time) UnmarshalJSON(value []byte) error { + var i int64 + _, err := fmt.Sscanf(string(value), "%d", &i) + if err != nil { + return err + } + *t = Time(time.Unix(i, 0)) + return nil +} + +func (t Time) Unix() int64 { + return time.Time(t).Unix() +} + +type NodeData interface { + json.Marshaler + json.Unmarshaler +} + +// allow inactive peers under +type NodeRecord struct { + Addr Address // address of node + Url string // Url, used to connect to node + After Time // next call after time + Seen Time // last connected at time + Meta *json.RawMessage // arbitrary metadata saved for a peer + + node Node + connected bool +} + +// set checked to current time, +func (self *NodeRecord) setSeen() { + self.Seen = Time(time.Now()) +} + +func (self *NodeRecord) String() string { + return fmt.Sprintf("<%v>", self.Addr) +} + +// persisted node record database () +type KadDb struct { + Address Address + Nodes [][]*NodeRecord + index map[Address]*NodeRecord + cursors []int + lock sync.Mutex + purgeInterval time.Duration + initialRetryInterval time.Duration + connRetryExp int +} + +func newKadDb(addr Address, params *KadParams) *KadDb { + return &KadDb{ + Address: addr, + Nodes: make([][]*NodeRecord, params.MaxProx+1), // overwritten by load + cursors: make([]int, params.MaxProx+1), + index: make(map[Address]*NodeRecord), + purgeInterval: params.PurgeInterval, + initialRetryInterval: params.InitialRetryInterval, + connRetryExp: params.ConnRetryExp, + } +} + +func (self *KadDb) findOrCreate(index int, a Address, url string) *NodeRecord { + defer self.lock.Unlock() + self.lock.Lock() + + record, found := self.index[a] + if !found { + record = &NodeRecord{ + Addr: a, + Url: url, + } + glog.V(logger.Info).Infof("[KΛÐ]: add new record %v to kaddb", record) + // insert in kaddb + self.index[a] = record + self.Nodes[index] = append(self.Nodes[index], record) + } else { + glog.V(logger.Info).Infof("[KΛÐ]: found record %v in kaddb", record) + } + // update last seen time + record.setSeen() + // update with url in case IP/port changes + record.Url = url + return record +} + +// add adds node records to kaddb (persisted node record db) +func (self *KadDb) add(nrs []*NodeRecord, proximityBin func(Address) int) { + defer self.lock.Unlock() + self.lock.Lock() + var n int + var nodes []*NodeRecord + for _, node := range nrs { + _, found := self.index[node.Addr] + if !found && node.Addr != self.Address { + node.setSeen() + self.index[node.Addr] = node + index := proximityBin(node.Addr) + dbcursor := self.cursors[index] + nodes = self.Nodes[index] + // this is inefficient for allocation, need to just append then shift + newnodes := make([]*NodeRecord, len(nodes)+1) + copy(newnodes[:], nodes[:dbcursor]) + newnodes[dbcursor] = node + copy(newnodes[dbcursor+1:], nodes[dbcursor:]) + glog.V(logger.Detail).Infof("[KΛÐ]: new nodes: %v (keys: %v)\nnodes: %v", newnodes, nodes) + self.Nodes[index] = newnodes + n++ + } + } + glog.V(logger.Detail).Infof("[KΛÐ]: received %d node records, added %d new", len(nrs), n) +} + +/* +next return one node record with the highest priority for desired +connection. +This is used to pick candidates for live nodes that are most wanted for +a higly connected low centrality network structure for Swarm which best suits +for a Kademlia-style routing. + +The candidate is chosen using the following strategy. +We check for missing online nodes in the buckets for 1 upto Max BucketSize rounds. +On each round we proceed from the low to high proximity order buckets. +If the number of active nodes (=connected peers) is < rounds, then start looking +for a known candidate. To determine if there is a candidate to recommend the +node record database row corresponding to the bucket is checked.a +If the row cursor is on position i, the ith element in the row is chosen. +If the record is scheduled not to be retried before NOW, the next element is taken. +If the record is scheduled can be retried, it is set as checked, scheduled for +checking and is returned. The time of the next check is in X (duration) such that +X = ConnRetryExp * delta where delta is the time past since the last check and +ConnRetryExp is constant obsoletion factor. (Note that when node records are added +from peer messages, they are marked as checked and placed at the cursor, ie. +given priority over older entries). Entries which were checked more than +purgeInterval ago are deleted from the kaddb row. If no candidate is found after +a full round of checking the next bucket up is considered. If no candidate is +found when we reach the maximum-proximity bucket, the next round starts. + +node record a is more favoured to b a > b iff a is a passive node (record of +offline past peer) +|proxBin(a)| < |proxBin(b)| +|| (proxBin(a) < proxBin(b) && |proxBin(a)| == |proxBin(b)|) +|| (proxBin(a) == proxBin(b) && lastChecked(a) < lastChecked(b)) + +This has double role. Starting as naive node with empty db, this implements +Kademlia bootstrapping +As a mature node, it fills short lines. All on demand. + +The second argument returned names the first missing slot found +*/ +func (self *KadDb) findBest(bucketSize int, binsize func(int) int) (node *NodeRecord, proxLimit int) { + // return value -1 indicates that buckets are filled in all + proxLimit = -1 + defer self.lock.Unlock() + self.lock.Lock() + + var interval int64 + var found bool + for rounds := 1; rounds <= bucketSize; rounds++ { + ROUND: + for po, dbrow := range self.Nodes { + if po > len(self.Nodes) { + break ROUND + } + size := binsize(po) + if size < rounds { + if proxLimit < 0 { + // set the first missing slot found + proxLimit = po + } + var count int + var purge []int + n := self.cursors[po] + + // try node records in the relavant kaddb row (of identical prox order) + // if they are ripe for checking + ROW: + for count < len(dbrow) { + node = dbrow[n] + + // skip already connected nodes + if !node.connected { + + glog.V(logger.Detail).Infof("[KΛÐ]: kaddb record %v (PO%03d:%d) not to be retried before %v", node.Addr, po, n, node.After) + + // time since last known connection attempt + delta := node.After.Unix() - node.Seen.Unix() + // if delta < 4 { + // node.After = Time(time.Time{}) + // } + + // if node is scheduled to connect + if time.Time(node.After).Before(time.Now()) { + + // if checked longer than purge interval + if time.Time(node.Seen).Add(self.purgeInterval).Before(time.Now()) { + // delete node + purge = append(purge, n) + glog.V(logger.Detail).Infof("[KΛÐ]: inactive node record %v (PO%03d:%d) last check: %v, next check: %v", node.Addr, po, n, node.Seen, node.After) + } else { + // scheduling next check + if (node.After == Time(time.Time{})) { + node.After = Time(time.Now().Add(self.initialRetryInterval)) + } else { + interval = delta * int64(self.connRetryExp) + node.After = Time(time.Unix(time.Now().Unix()+interval, 0)) + } + + glog.V(logger.Detail).Infof("[KΛÐ]: serve node record %v (PO%03d:%d), last check: %v, next check: %v", node.Addr, po, n, node.Seen, node.After) + } + found = true + break ROW + } + glog.V(logger.Detail).Infof("[KΛÐ]: kaddb record %v (PO%03d:%d) not ready. skipped. not to be retried before: %v", node.Addr, po, n, node.After) + } // if node.node == nil + n++ + count++ + // cycle: n = n % len(dbrow) + if n >= len(dbrow) { + n = 0 + } + } + self.cursors[po] = n + self.delete(po, purge...) + if found { + glog.V(logger.Detail).Infof("[KΛÐ]: rounds %d: prox limit: PO%03d\n%v", rounds, proxLimit, node) + node.setSeen() + return + } + } // if len < rounds + } // for po-s + glog.V(logger.Detail).Infof("[KΛÐ]: rounds %d: proxlimit: PO%03d", rounds, proxLimit) + if proxLimit == 0 || proxLimit < 0 && bucketSize == rounds { + return + } + } // for round + + return +} + +// deletes the noderecords of a kaddb row corresponding to the indexes +// caller must hold the dblock +// the call is unsafe, no index checks +func (self *KadDb) delete(row int, indexes ...int) { + var prev int + var nodes []*NodeRecord + dbrow := self.Nodes[row] + for _, next := range indexes { + // need to adjust dbcursor + if next > 0 { + if next <= self.cursors[row] { + self.cursors[row]-- + } + nodes = append(nodes, dbrow[prev:next]...) + } + prev = next + 1 + delete(self.index, dbrow[next].Addr) + } + self.Nodes[row] = append(nodes, dbrow[prev:]...) +} + +// save persists kaddb on disk (written to file on path in json format. +func (self *KadDb) save(path string, cb func(*NodeRecord, Node)) error { + defer self.lock.Unlock() + self.lock.Lock() + + var n int + + for _, b := range self.Nodes { + for _, node := range b { + n++ + node.After = Time(time.Now()) + node.Seen = Time(time.Now()) + if cb != nil { + cb(node, node.node) + } + } + } + + data, err := json.MarshalIndent(self, "", " ") + if err != nil { + return err + } + err = ioutil.WriteFile(path, data, os.ModePerm) + if err != nil { + glog.V(logger.Warn).Infof("[KΛÐ]: unable to save kaddb with %v nodes to %v: err", n, path, err) + } else { + glog.V(logger.Info).Infof("[KΛÐ] saved kaddb with %v nodes to %v", n, path) + } + return err +} + +// Load(path) loads the node record database (kaddb) from file on path. +func (self *KadDb) load(path string, cb func(*NodeRecord, Node) error) (err error) { + defer self.lock.Unlock() + self.lock.Lock() + + var data []byte + data, err = ioutil.ReadFile(path) + if err != nil { + return + } + + err = json.Unmarshal(data, self) + if err != nil { + return + } + var n int + var purge []int + for po, b := range self.Nodes { + ROW: + for i, node := range b { + if cb != nil { + err = cb(node, node.node) + if err != nil { + purge = append(purge, i) + continue ROW + } + } + n++ + if (node.After == Time(time.Time{})) { + node.After = Time(time.Now()) + } + self.index[node.Addr] = node + } + self.delete(po, purge...) + } + glog.V(logger.Info).Infof("[KΛÐ] loaded kaddb with %v nodes from %v", n, path) + + return +} + +// accessor for KAD offline db count +func (self *KadDb) count() int { + defer self.lock.Unlock() + self.lock.Lock() + return len(self.index) +} diff --git a/common/kademlia/kademlia.go b/common/kademlia/kademlia.go new file mode 100644 index 0000000000..ac80e8b406 --- /dev/null +++ b/common/kademlia/kademlia.go @@ -0,0 +1,457 @@ +package kademlia + +import ( + "fmt" + "sort" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +const ( + bucketSize = 20 + maxProx = 255 + connRetryExp = 2 +) + +var ( + purgeInterval = 42 * time.Hour + initialRetryInterval = 42 * 100 * time.Millisecond +) + +type KadParams struct { + // adjustable parameters + MaxProx int + ProxBinSize int + BucketSize int + PurgeInterval time.Duration + InitialRetryInterval time.Duration + ConnRetryExp int +} + +func NewKadParams() *KadParams { + return &KadParams{ + MaxProx: maxProx, + ProxBinSize: bucketSize, + BucketSize: bucketSize, + PurgeInterval: purgeInterval, + InitialRetryInterval: initialRetryInterval, + ConnRetryExp: connRetryExp, + } +} + +// Kademlia is a table of active nodes +type Kademlia struct { + addr Address // immutable baseaddress of the table + *KadParams // Kademlia configuration parameters + proxLimit int // state, the PO of the first row of the most proximate bin + proxSize int // state, the number of peers in the most proximate bin + count int // number of active peers (w live connection) + buckets []*bucket // the actual bins + db *KadDb // kaddb, node record database + lock sync.RWMutex // mutex to access buckets +} + +type Node interface { + Addr() Address + Url() string + LastActive() time.Time + Drop() +} + +// public constructor +// add is the base address of the table +// params is KadParams configuration +func New(addr Address, params *KadParams) *Kademlia { + buckets := make([]*bucket, params.MaxProx+1) + for i, _ := range buckets { + buckets[i] = &bucket{size: params.BucketSize} // will initialise bucket{int(0),[]Node(nil),sync.Mutex} + } + glog.V(logger.Info).Infof("[KΛÐ] base address %v", addr) + + return &Kademlia{ + addr: addr, + KadParams: params, + buckets: buckets, + db: newKadDb(addr, params), + } +} + +// accessor for KAD base address +func (self *Kademlia) Addr() Address { + return self.addr +} + +// accessor for KAD active node count +func (self *Kademlia) Count() int { + defer self.lock.Unlock() + self.lock.Lock() + return self.count +} + +// accessor for KAD active node count +func (self *Kademlia) DBCount() int { + return self.db.count() +} + +// On is the entry point called when a new nodes is added +// unsafe in that node is not checked to be already active node (to be called once) +func (self *Kademlia) On(node Node, cb func(*NodeRecord, Node) error) (err error) { + index := self.proximityBin(node.Addr()) + record := self.db.findOrCreate(index, node.Addr(), node.Url()) + // callback on add node + // setting the node on the record, set it checked (for connectivity) + record.node = node + + glog.V(logger.Info).Infof("[KΛÐ]: add node record %v with node %v", record, node) + if cb != nil { + err = cb(record, node) + glog.V(logger.Info).Infof("[KΛÐ]: cb(%v, %v) ->%v", record, node, err) + if err != nil { + return fmt.Errorf("node %v not added: %v", node.Addr(), err) + } + } + record.connected = true + + defer self.lock.Unlock() + self.lock.Lock() + + // insert in kademlia table of active nodes + bucket := self.buckets[index] + // if bucket is full insertion replaces the worst node + // TODO: give priority to peers with active traffic + if worst, pos := bucket.insert(node); worst != nil { + glog.V(logger.Info).Infof("[KΛÐ]: replace node %v (%d) with node %v", worst, pos, node) + // no prox adjustment needed + // do not change count + } else { + glog.V(logger.Info).Infof("[KΛÐ]: add node %v to table", node) + self.count++ + self.adjustProxMore(index) + } + + return + +} + +// is the entrypoint called when a node is taken offline +func (self *Kademlia) Off(node Node, cb func(*NodeRecord, Node)) (err error) { + self.lock.Lock() + defer self.lock.Unlock() + + var found bool + index := self.proximityBin(node.Addr()) + bucket := self.buckets[index] + for i := 0; i < len(bucket.nodes); i++ { + if node.Addr() == bucket.nodes[i].Addr() { + found = true + bucket.nodes = append(bucket.nodes[:i], bucket.nodes[(i+1):]...) + } + } + + if !found { + return + } + glog.V(logger.Info).Infof("[KΛÐ]: remove node %v from table", node) + + self.count-- + if len(bucket.nodes) < bucket.size { + err = fmt.Errorf("insufficient nodes (%v) in bucket %v", len(bucket.nodes), index) + } + self.adjustProxLess(index) + + r := self.db.index[node.Addr()] + // callback on remove + if cb != nil { + cb(r, r.node) + } + r.node = nil + r.connected = false + + return +} + +// proxLimit is dynamically adjusted so that 1) there is no +// empty buckets in bin < proxLimit and 2) the sum of all items sare the maximum +// possible but lower than ProxBinSize +// adjust Prox (proxLimit and proxSize after an insertion of add nodes into bucket r) +func (self *Kademlia) adjustProxMore(r int) { + if r >= self.proxLimit { + exLimit := self.proxLimit + exSize := self.proxSize + self.proxSize++ + + var i int + for i = self.proxLimit; i < self.MaxProx && len(self.buckets[i].nodes) > 0 && self.proxSize-len(self.buckets[i].nodes) > self.ProxBinSize; i++ { + self.proxSize -= len(self.buckets[i].nodes) + } + self.proxLimit = i + + glog.V(logger.Detail).Infof("[KΛÐ]: Max Prox Bin: Lower Limit: %v (was %v): Bin Size: %v (was %v)", self.proxLimit, exLimit, self.proxSize, exSize) + } +} + +func (self *Kademlia) adjustProxLess(r int) { + exLimit := self.proxLimit + exSize := self.proxSize + if r >= self.proxLimit { + self.proxSize-- + } + + if r < self.proxLimit && len(self.buckets[r].nodes) == 0 { + for i := self.proxLimit - 1; i > r; i-- { + self.proxSize += len(self.buckets[i].nodes) + } + self.proxLimit = r + } else if self.proxLimit > 0 && r >= self.proxLimit-1 { + var i int + for i = self.proxLimit - 1; i > 0 && len(self.buckets[i].nodes)+self.proxSize <= self.ProxBinSize; i-- { + self.proxSize += len(self.buckets[i].nodes) + } + self.proxLimit = i + } + + if exLimit != self.proxLimit || exSize != self.proxSize { + glog.V(logger.Detail).Infof("[KΛÐ]: Max Prox Bin: Lower Limit: %v (was %v): Bin Size: %v (was %v)", self.proxLimit, exLimit, self.proxSize, exSize) + } +} + +/* +returns the list of nodes belonging to the same proximity bin +as the target. The most proximate bin will be the union of the bins between +proxLimit and MaxProx. +*/ +func (self *Kademlia) FindClosest(target Address, max int) []Node { + defer self.lock.RUnlock() + self.lock.RLock() + r := nodesByDistance{ + target: target, + } + index := self.proximityBin(target) + + start := index + var down bool + if index >= self.proxLimit { + index = self.proxLimit + start = self.MaxProx + down = true + } + var n int + limit := max + if max == 0 { + limit = 1000 + } + for { + + bucket := self.buckets[start].nodes + for i := 0; i < len(bucket); i++ { + r.push(bucket[i], limit) + n++ + } + if max == 0 && start <= index && (n > 0 || start == 0) || + max > 0 && down && start <= index && (n >= limit || n == self.count || start == 0) { + break + } + if down { + start-- + } else { + if start == self.MaxProx { + if index == 0 { + break + } + start = index - 1 + down = true + } else { + start++ + } + } + } + glog.V(logger.Detail).Infof("[KΛÐ]: serve %d (=<%d) nodes for target lookup %v (PO%d)", n, self.MaxProx, target, index) + return r.nodes +} + +func (self *Kademlia) binsize(p int) int { + b := self.buckets[p] + defer b.lock.RUnlock() + b.lock.RLock() + return len(b.nodes) +} + +func (self *Kademlia) FindBest() (node *NodeRecord, proxLimit int) { + return self.db.findBest(self.BucketSize, self.binsize) +} + +// adds node records to kaddb (persisted node record db) +func (self *Kademlia) Add(nrs []*NodeRecord) { + self.db.add(nrs, self.proximityBin) +} + +// in situ mutable bucket +type bucket struct { + size int + nodes []Node + lock sync.RWMutex +} + +// nodesByDistance is a list of nodes, ordered by distance to target. +type nodesByDistance struct { + nodes []Node + target Address +} + +func sortedByDistanceTo(target Address, slice []Node) bool { + var last Address + for i, node := range slice { + if i > 0 { + if target.ProxCmp(node.Addr(), last) < 0 { + return false + } + } + last = node.Addr() + } + return true +} + +// push(node, max) adds the given node to the list, keeping the total size +// below max elements. +func (h *nodesByDistance) push(node Node, max int) { + // returns the firt index ix such that func(i) returns true + ix := sort.Search(len(h.nodes), func(i int) bool { + return h.target.ProxCmp(h.nodes[i].Addr(), node.Addr()) >= 0 + }) + + if len(h.nodes) < max { + h.nodes = append(h.nodes, node) + } + if ix < len(h.nodes) { + copy(h.nodes[ix+1:], h.nodes[ix:]) + h.nodes[ix] = node + } +} + +// insert adds a peer to a bucket either by appending to existing items if +// bucket length does not exceed bucketSize, or by replacing the worst +// Node in the bucket +func (self *bucket) insert(node Node) (dropped Node, pos int) { + self.lock.Lock() + defer self.lock.Unlock() + if len(self.nodes) >= self.size { // >= allows us to add peers beyond the bucketsize limitation + dropped, pos = self.worstNode() + if dropped != nil { + self.nodes[pos] = node + glog.V(logger.Info).Infof("[KΛÐ] dropping node %v (%d)", dropped, pos) + dropped.Drop() + return + } + } + self.nodes = append(self.nodes, node) + return +} + +func (self *bucket) length(node Node) int { + self.lock.Lock() + defer self.lock.Unlock() + return len(self.nodes) +} + +// worst expunges the single worst node in a row, where worst entry is the node +// that has been inactive for the longests time +func (self *bucket) worstNode() (node Node, pos int) { + var oldest time.Time + for p, n := range self.nodes { + if (oldest == time.Time{}) || !oldest.Before(n.LastActive()) { + oldest = n.LastActive() + node = n + pos = p + } + } + return +} + +/* +Taking the proximity order relative to a fix point x classifies the points in +the space (n byte long byte sequences) into bins. Items in each are at +most half as distant from x as items in the previous bin. Given a sample of +uniformly distributed items (a hash function over arbitrary sequence) the +proximity scale maps onto series of subsets with cardinalities on a negative +exponential scale. + +It also has the property that any two item belonging to the same bin are at +most half as distant from each other as they are from x. + +If we think of random sample of items in the bins as connections in a network of interconnected nodes than relative proximity can serve as the basis for local +decisions for graph traversal where the task is to find a route between two +points. Since in every hop, the finite distance halves, there is +a guaranteed constant maximum limit on the number of hops needed to reach one +node from the other. +*/ + +func (self *Kademlia) proximityBin(other Address) (ret int) { + ret = proximity(self.addr, other) + if ret > self.MaxProx { + ret = self.MaxProx + } + return +} + +// provides keyrange for chunk db iteration +func (self *Kademlia) KeyRange(other Address) (start, stop Address) { + defer self.lock.RUnlock() + self.lock.RLock() + return KeyRange(self.addr, other, self.proxLimit) +} + +// save persists kaddb on disk (written to file on path in json format. +func (self *Kademlia) Save(path string, cb func(*NodeRecord, Node)) error { + return self.db.save(path, cb) +} + +// Load(path) loads the node record database (kaddb) from file on path. +func (self *Kademlia) Load(path string, cb func(*NodeRecord, Node) error) (err error) { + return self.db.load(path, cb) +} + +// kademlia table + kaddb table displayed with ascii +func (self *Kademlia) String() string { + + var rows []string + rows = append(rows, "=========================================================================") + rows = append(rows, fmt.Sprintf("%v : MaxProx: %d, ProxBinSize: %d, BucketSize: %d, proxLimit: %d, proxSize: %d", time.Now(), self.MaxProx, self.ProxBinSize, self.BucketSize, self.proxLimit, self.proxSize)) + + for i, b := range self.buckets { + + if i == self.proxLimit { + rows = append(rows, fmt.Sprintf("===================== PROX LIMIT: %d =================================", i)) + } + row := []string{fmt.Sprintf("%03d", i), fmt.Sprintf("%2d", len(b.nodes))} + var k int + c := self.db.cursors[i] + for ; k < len(b.nodes); k++ { + p := b.nodes[(c+k)%len(b.nodes)] + row = append(row, fmt.Sprintf("%s", p.Addr().String()[:8])) + if k == 3 { + break + } + } + for ; k < 3; k++ { + row = append(row, " ") + } + row = append(row, fmt.Sprintf("| %2d %2d", len(self.db.Nodes[i]), self.db.cursors[i])) + + for j, p := range self.db.Nodes[i] { + row = append(row, fmt.Sprintf("%08x", p.Addr[:4])) + if j == 2 { + break + } + } + rows = append(rows, strings.Join(row, " ")) + if i == self.MaxProx { + break + } + } + rows = append(rows, "=========================================================================") + return strings.Join(rows, "\n") +} diff --git a/common/kademlia/kademlia_test.go b/common/kademlia/kademlia_test.go new file mode 100644 index 0000000000..f36158c378 --- /dev/null +++ b/common/kademlia/kademlia_test.go @@ -0,0 +1,389 @@ +package kademlia + +import ( + "fmt" + "math/rand" + "reflect" + "testing" + "testing/quick" + "time" +) + +var ( + quickrand = rand.New(rand.NewSource(time.Now().Unix())) + quickcfgFindClosest = &quick.Config{MaxCount: 5000, Rand: quickrand} + quickcfgBootStrap = &quick.Config{MaxCount: 1000, Rand: quickrand} +) + +type testNode struct { + addr Address +} + +func (n *testNode) String() string { + return fmt.Sprintf("%x", n.addr[:]) +} + +func (n *testNode) Addr() Address { + return n.addr +} + +func (n *testNode) Drop() { +} + +func (n *testNode) Url() string { + return "" +} + +func (n *testNode) LastActive() time.Time { + return time.Now() +} + +func TestOn(t *testing.T) { + addr, ok := gen(Address{}, quickrand).(Address) + other, ok := gen(Address{}, quickrand).(Address) + if !ok { + t.Errorf("oops") + } + kad := New(addr, NewKadParams()) + err := kad.On(&testNode{addr: other}, nil) + _ = err +} + +func TestBootstrap(t *testing.T) { + + test := func(test *bootstrapTest) bool { + // for any node kad.le, Target and N + params := NewKadParams() + params.MaxProx = test.MaxProx + params.BucketSize = test.BucketSize + params.ProxBinSize = test.BucketSize + kad := New(test.Self, params) + var err error + + addr := RandomAddress() + prox := proximity(addr, test.Self) + + for p := 0; p <= prox; p++ { + var nrs []*NodeRecord + for i := 0; i < 3; i++ { + nrs = append(nrs, &NodeRecord{ + Addr: RandomAddressAt(test.Self, p), + }) + } + kad.Add(nrs) + } + + node := &testNode{addr} + + n := 0 + for n < 100 { + err = kad.On(node, nil) + if err != nil { + t.Errorf("backend not accepting node") + return false + } + var nrs []*NodeRecord + prox := proximity(test.Self, node.addr) + for i := 0; i < 13; i++ { + nrs = append(nrs, &NodeRecord{ + Addr: RandomAddressAt(test.Self, prox+1), + }) + } + kad.Add(nrs) + + record, _ := kad.FindBest() + if record == nil { + break + } + node = &testNode{record.Addr} + n++ + } + exp := test.BucketSize * (test.MaxProx + 1) + if kad.Count() != exp { + t.Errorf("incorrect number of peers, expected %d, got %d\n%v", exp, kad.Count(), kad) + return false + } + return true + } + if err := quick.Check(test, quickcfgBootStrap); err != nil { + t.Error(err) + } + +} + +func TestFindClosest(t *testing.T) { + + test := func(test *FindClosestTest) bool { + // for any node kad.le, Target and N + params := NewKadParams() + params.MaxProx = 10 + kad := New(test.Self, params) + var err error + // t.Logf("FindClosestTest %v: %v\n", len(test.All), test) + for _, node := range test.All { + err = kad.On(node, nil) + if err != nil { + t.Errorf("backend not accepting node") + return false + } + } + + if len(test.All) == 0 || test.N == 0 { + return true + } + nodes := kad.FindClosest(test.Target, test.N) + + // check that the number of results is min(N, kad.len) + wantN := test.N + if tlen := kad.Count(); tlen < test.N { + wantN = tlen + } + + if len(nodes) != wantN { + t.Errorf("wrong number of nodes: got %d, want %d", len(nodes), wantN) + return false + } + + if hasDuplicates(nodes) { + t.Errorf("result contains duplicates") + return false + } + + if !sortedByDistanceTo(test.Target, nodes) { + t.Errorf("result is not sorted by distance to target") + return false + } + + // check that the result nodes have minimum distance to target. + farthestResult := nodes[len(nodes)-1].Addr() + for i, b := range kad.buckets { + for j, n := range b.nodes { + if contains(nodes, n.Addr()) { + continue // don't run the check below for nodes in result + } + if test.Target.ProxCmp(n.Addr(), farthestResult) < 0 { + _ = i * j + t.Errorf("kad.le contains node that is closer to target but it's not in result") + // t.Logf("bucket %v, item %v\n", i, j) + // t.Logf(" Target: %x", test.Target) + // t.Logf(" Farthest Result: %x", farthestResult) + // t.Logf(" ID: %x (%d)", n.Addr(), kad.proximityBin(n.Addr())) + return false + } + } + } + return true + } + if err := quick.Check(test, quickcfgFindClosest); err != nil { + t.Error(err) + } +} + +type proxTest struct { + add bool + index int + addr Address +} + +var ( + addresses []Address +) + +func TestProxAdjust(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + self := gen(Address{}, r).(Address) + params := NewKadParams() + params.MaxProx = 10 + kad := New(self, params) + + var err error + for i := 0; i < 100; i++ { + a := gen(Address{}, r).(Address) + addresses = append(addresses, a) + err = kad.On(&testNode{addr: a}, nil) + if err != nil { + t.Errorf("backend not accepting node") + return + } + if !kad.proxCheck(t) { + return + } + } + + test := func(test *proxTest) bool { + node := &testNode{test.addr} + if test.add { + kad.On(node, nil) + } else { + kad.Off(node, nil) + } + return kad.proxCheck(t) + } + if err := quick.Check(test, quickcfgFindClosest); err != nil { + t.Error(err) + } +} + +func TestSaveLoad(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + addresses := gen([]Address{}, r).([]Address) + self := RandomAddress() + params := NewKadParams() + params.MaxProx = 10 + kad := New(self, params) + + var err error + + for _, a := range addresses { + err = kad.On(&testNode{addr: a}, nil) + if err != nil { + t.Errorf("backend not accepting node") + return + } + } + nodes := kad.FindClosest(self, 100) + path := "/tmp/bzz.peers" + err = kad.Save(path, nil) + if err != nil { + t.Fatalf("unepected error saving kaddb: %v", err) + } + kad = New(self, params) + err = kad.Load(path, nil) + if err != nil { + t.Fatalf("unepected error loading kaddb: %v", err) + } + for _, b := range kad.db.Nodes { + for _, node := range b { + err = kad.On(&testNode{node.Addr}, nil) + if err != nil { + t.Errorf("backend not accepting node") + return + } + } + } + loadednodes := kad.FindClosest(self, 100) + for i, node := range loadednodes { + if nodes[i].Addr() != node.Addr() { + t.Errorf("node mismatch at %d/%d: %v != %v", i, len(nodes), nodes[i].Addr(), node.Addr()) + } + } +} + +func (self *Kademlia) proxCheck(t *testing.T) bool { + var sum, i int + var b *bucket + for i, b = range self.buckets { + l := len(b.nodes) + // if we are in the high prox multibucket + if i >= self.proxLimit { + sum += l + } else if l == 0 { + t.Errorf("bucket %d empty, yet proxLimit is %d\n%v", len(b.nodes), self.proxLimit, self) + return false + } + } + // check if merged high prox bucket does not exceed size + if sum > 0 { + // if sum > self.ProxBinSize { + // t.Errorf("bucket %d is empty, yet proxSize is %d\n%v", i, self.proxSize, self) + // return false + // } + if sum != self.proxSize { + t.Errorf("proxSize incorrect, expected %v, got %v", sum, self.proxSize) + return false + } + if self.proxLimit > 0 && sum+len(self.buckets[self.proxLimit-1].nodes) < self.ProxBinSize { + t.Errorf("proxBinSize incorrect, expected %v got %v", sum, self.proxSize) + return false + } + } + return true +} + +type bootstrapTest struct { + MaxProx int + BucketSize int + Self Address +} + +func (*bootstrapTest) Generate(rand *rand.Rand, size int) reflect.Value { + t := &bootstrapTest{ + Self: gen(Address{}, rand).(Address), + MaxProx: 10 + rand.Intn(3), + BucketSize: rand.Intn(3) + 1, + } + return reflect.ValueOf(t) +} + +type FindClosestTest struct { + Self Address + Target Address + All []Node + N int +} + +func (c FindClosestTest) String() string { + return fmt.Sprintf("A: %064x\nT: %064x\n(%d)\n", c.Self[:], c.Target[:], c.N) +} + +func (*FindClosestTest) Generate(rand *rand.Rand, size int) reflect.Value { + t := &FindClosestTest{ + Self: gen(Address{}, rand).(Address), + Target: gen(Address{}, rand).(Address), + N: rand.Intn(bucketSize), + } + for _, a := range gen([]Address{}, rand).([]Address) { + t.All = append(t.All, &testNode{addr: a}) + } + return reflect.ValueOf(t) +} + +func (*proxTest) Generate(rand *rand.Rand, size int) reflect.Value { + var add bool + if rand.Intn(1) == 0 { + add = true + } + var t *proxTest + if add { + t = &proxTest{ + addr: gen(Address{}, rand).(Address), + add: add, + } + } else { + t = &proxTest{ + index: rand.Intn(len(addresses)), + add: add, + } + } + return reflect.ValueOf(t) +} + +func hasDuplicates(slice []Node) bool { + seen := make(map[Address]bool) + for _, node := range slice { + if seen[node.Addr()] { + return true + } + seen[node.Addr()] = true + } + return false +} + +func contains(nodes []Node, addr Address) bool { + for _, n := range nodes { + if n.Addr() == addr { + return true + } + } + return false +} + +// gen wraps quick.Value so it's easier to use. +// it generates a random value of the given value's type. +func gen(typ interface{}, rand *rand.Rand) interface{} { + v, ok := quick.Value(reflect.TypeOf(typ), rand) + if !ok { + panic(fmt.Sprintf("couldn't generate random value of type %T", typ)) + } + return v.Interface() +} diff --git a/common/registrar/ethreg/ethreg.go b/common/registrar/ethreg/ethreg.go index 626ebe1d74..7b3b41769c 100644 --- a/common/registrar/ethreg/ethreg.go +++ b/common/registrar/ethreg/ethreg.go @@ -20,18 +20,22 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common/registrar" - "github.com/ethereum/go-ethereum/xeth" ) +type Backend interface { + registrar.Backend + AtStateNum(int64) registrar.Backend +} + // implements a versioned Registrar on an archiving full node type EthReg struct { - backend *xeth.XEth + backend Backend registry *registrar.Registrar } -func New(xe *xeth.XEth) (self *EthReg) { - self = &EthReg{backend: xe} - self.registry = registrar.New(xe) +func New(backend Backend) (self *EthReg) { + self = &EthReg{backend: backend} + self.registry = registrar.New(backend) return } @@ -40,9 +44,11 @@ func (self *EthReg) Registry() *registrar.Registrar { } func (self *EthReg) Resolver(n *big.Int) *registrar.Registrar { - xe := self.backend + var s registrar.Backend if n != nil { - xe = self.backend.AtStateNum(n.Int64()) + s = self.backend.AtStateNum(n.Int64()) + } else { + s = registrar.Backend(self.backend) } - return registrar.New(xe) + return registrar.New(s) } diff --git a/common/swap/swap.go b/common/swap/swap.go new file mode 100644 index 0000000000..c8cf7a7770 --- /dev/null +++ b/common/swap/swap.go @@ -0,0 +1,238 @@ +package swap + +import ( + "fmt" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +// SwAP Swarm Accounting Protocol with +// Swift Automatic Payments +// a peer to peer micropayment system + +// public swap profile +// public parameters for SWAP, serializable config struct passed in handshake +type Profile struct { + BuyAt *big.Int // accepted max price for chunk + SellAt *big.Int // offered sale price for chunk + PayAt uint // threshold that triggers payment request + DropAt uint // threshold that triggers disconnect +} + +// Strategy encapsulates parameters relating to +// automatic deposit and automatic cashing +type Strategy struct { + AutoCashInterval time.Duration // default interval for autocash + AutoCashThreshold *big.Int // threshold that triggers autocash (wei) + AutoDepositInterval time.Duration // default interval for autocash + AutoDepositThreshold *big.Int // threshold that triggers autodeposit (wei) + AutoDepositBuffer *big.Int // buffer that is surplus for fork protection etc (wei) +} + +// Params extends the public profile with private parameters relating to +// automatic deposit and automatic cashing +type Params struct { + *Profile + *Strategy +} + +// Promise +// 3rd party Provable Promise of Payment +// issued by outPayment +// serialisable to send with Protocol +type Promise interface{} + +// interface for the peer protocol for testing or external alternative payment +type Protocol interface { + Pay(int, Promise) // units, payment proof + Drop() + String() string +} + +// interface for the (delayed) ougoing payment system with autodeposit +type OutPayment interface { + Issue(amount *big.Int) (promise Promise, err error) + AutoDeposit(interval time.Duration, threshold, buffer *big.Int) + Stop() +} + +// interface for the (delayed) incoming payment system with autocash +type InPayment interface { + Receive(promise Promise) (*big.Int, error) + AutoCash(cashInterval time.Duration, maxUncashed *big.Int) + Stop() +} + +// swap is the swarm accounting protocol instance +// * pairwise accounting and payments +type Swap struct { + lock sync.Mutex // mutex for balance access + balance int // units of chunk/retrieval request + local *Params // local peer's swap parameters + remote *Profile // remote peer's swap profile + proto Protocol // peer communication protocol + Payment +} + +type Payment struct { + Out OutPayment // outgoing payment handler + In InPayment // incoming payment handler + Buys, Sells bool +} + +// swap constructor +func New(local *Params, pm Payment, proto Protocol) (self *Swap, err error) { + + self = &Swap{ + local: local, + Payment: pm, + proto: proto, + } + + self.SetParams(local) + + return +} + +// entry point for setting remote swap profile (e.g from handshake or other message) +func (self *Swap) SetRemote(remote *Profile) { + defer self.lock.Unlock() + self.lock.Lock() + + self.remote = remote + if self.Sells && (remote.BuyAt.Cmp(common.Big0) <= 0 || self.local.SellAt.Cmp(common.Big0) <= 0 || remote.BuyAt.Cmp(self.local.SellAt) < 0) { + self.Out.Stop() + self.Sells = false + } + if self.Buys && (remote.SellAt.Cmp(common.Big0) <= 0 || self.local.BuyAt.Cmp(common.Big0) <= 0 || self.local.BuyAt.Cmp(self.remote.SellAt) < 0) { + self.In.Stop() + self.Buys = false + } + + glog.V(logger.Debug).Infof("[SWAP] <%v> remote profile set: pay at: %v, drop at: %v, buy at: %v, sell at: %v", self.proto, remote.PayAt, remote.DropAt, remote.BuyAt, remote.SellAt) + +} + +// to set strategy dynamically +func (self *Swap) SetParams(local *Params) { + defer self.lock.Unlock() + self.lock.Lock() + self.local = local + self.setParams(local) +} + +// caller holds the lock + +func (self *Swap) setParams(local *Params) { + + if self.Sells { + self.In.AutoCash(local.AutoCashInterval, local.AutoCashThreshold) + glog.V(logger.Info).Infof("[SWAP] <%v> set autocash to every %v, max uncashed limit: %v", self.proto, local.AutoCashInterval, local.AutoCashThreshold) + } else { + glog.V(logger.Info).Infof("[SWAP] <%v> autocash off (not selling)", self.proto) + } + if self.Buys { + self.Out.AutoDeposit(local.AutoDepositInterval, local.AutoDepositThreshold, local.AutoDepositBuffer) + glog.V(logger.Info).Infof("[SWAP] <%v> set autodeposit to every %v, pay at: %v, buffer: %v", self.proto, local.AutoDepositInterval, local.AutoDepositThreshold, local.AutoDepositBuffer) + } else { + glog.V(logger.Info).Infof("[SWAP] <%v> autodeposit off (not buying)", self.proto) + } +} + +// Add(n) +// n > 0 called when promised/provided n units of service +// n < 0 called when used/requested n units of service +func (self *Swap) Add(n int) error { + defer self.lock.Unlock() + self.lock.Lock() + self.balance += n + if !self.Sells && self.balance > 0 { + glog.V(logger.Detail).Infof("[SWAP] <%v> remote peer cannot have debt (unable to buy)", self.proto, self.balance) + self.proto.Drop() + return fmt.Errorf("[SWAP] <%v> remote peer cannot have debt (unable to buy)", self.proto, self.balance) + } + if !self.Buys && self.balance < 0 { + glog.V(logger.Detail).Infof("[SWAP] <%v> we cannot have debt (unable to buy)", self.proto, self.balance) + return fmt.Errorf("[SWAP] <%v> we cannot have debt (unable to buy)", self.proto, self.balance) + } + if self.balance >= int(self.local.DropAt) { + glog.V(logger.Detail).Infof("[SWAP] <%v> remote peer has too much debt (balance: %v, disconnect threshold: %v)", self.proto, self.balance, self.local.DropAt) + self.proto.Drop() + return fmt.Errorf("[SWAP] <%v> remote peer has too much debt (balance: %v, disconnect threshold: %v)", self.proto, self.balance, self.local.DropAt) + } else if self.balance <= -int(self.remote.PayAt) { + self.send() + } + return nil +} + +func (self *Swap) Balance() int { + defer self.lock.Unlock() + self.lock.Lock() + return self.balance +} + +// send(units) is called when payment is due +// In case of insolvency no promise is issued and sent, safe against fraud +// No return value: no error = payment is opportunistic = hang in till dropped +func (self *Swap) send() { + if self.local.BuyAt != nil && self.balance < 0 { + amount := big.NewInt(int64(-self.balance)) + amount.Mul(amount, self.remote.SellAt) + promise, err := self.Out.Issue(amount) + if err != nil { + glog.V(logger.Warn).Infof("[SWAP] <%v> cannot issue cheque (amount: %v, channel: %v): %v", self.proto, amount, self.Out, err) + } else { + glog.V(logger.Warn).Infof("[SWAP] <%v> cheque issued (amount: %v, channel: %v)", self.proto, amount, self.Out) + self.proto.Pay(-self.balance, promise) + self.balance = 0 + } + } +} + +// receive(units, promise) is called by the protocol when a payment msg is received +// returns error if promise is invalid. +func (self *Swap) Receive(units int, promise Promise) error { + if units <= 0 { + return fmt.Errorf("invalid units: %v <= 0", units) + } + + price := new(big.Int).SetInt64(int64(units)) + price.Mul(price, self.local.SellAt) + + amount, err := self.In.Receive(promise) + + if err != nil { + err = fmt.Errorf("invalid promise: %v", err) + } else if price.Cmp(amount) != 0 { + // verify amount = units * unit sale price + return fmt.Errorf("invalid amount: %v = %v * %v (units sent in msg * agreed sale unit price) != %v (signed in cheque)", price, units, self.local.SellAt, amount) + } + if err != nil { + glog.V(logger.Detail).Infof("[SWAP] <%v> invalid promise (amount: %v, channel: %v): %v", self.proto, amount, self.In, err) + return err + } + + // credit remote peer with units + self.Add(-units) + glog.V(logger.Detail).Infof("[SWAP] <%v> received promise (amount: %v, channel: %v): %v", self.proto, amount, self.In, promise) + + return nil +} + +// stop() causes autocash loop to terminate. +// Called after protocol handle loop terminates. +func (self *Swap) Stop() { + defer self.lock.Unlock() + self.lock.Lock() + if self.Buys { + self.Out.Stop() + } + if self.Sells { + self.In.Stop() + } +} diff --git a/common/swap/swap_test.go b/common/swap/swap_test.go new file mode 100644 index 0000000000..664cd81d20 --- /dev/null +++ b/common/swap/swap_test.go @@ -0,0 +1,178 @@ +package swap + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" +) + +type testInPayment struct { + received []*testPromise + autocashInterval time.Duration + autocashLimit *big.Int +} + +type testPromise struct { + amount *big.Int +} + +func (self *testInPayment) Receive(promise Promise) (*big.Int, error) { + p := promise.(*testPromise) + self.received = append(self.received, p) + return p.amount, nil +} + +func (self *testInPayment) AutoCash(interval time.Duration, limit *big.Int) { + self.autocashInterval = interval + self.autocashLimit = limit +} + +func (self *testInPayment) Cash() (string, error) { return "", nil } + +func (self *testInPayment) Stop() {} + +type testOutPayment struct { + deposits []*big.Int + autodepositInterval time.Duration + autodepositThreshold *big.Int + autodepositBuffer *big.Int +} + +func (self *testOutPayment) Issue(amount *big.Int) (promise Promise, err error) { + return &testPromise{amount}, nil +} + +func (self *testOutPayment) Deposit(amount *big.Int) (string, error) { + self.deposits = append(self.deposits, amount) + return "", nil +} + +func (self *testOutPayment) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { + self.autodepositInterval = interval + self.autodepositThreshold = threshold + self.autodepositBuffer = buffer +} + +func (self *testOutPayment) Stop() {} + +type testProtocol struct { + drop bool + amounts []int + promises []*testPromise +} + +func (self *testProtocol) Drop() { + self.drop = true +} + +func (self *testProtocol) String() string { + return "" +} + +func (self *testProtocol) Pay(amount int, promise Promise) { + p := promise.(*testPromise) + self.promises = append(self.promises, p) + self.amounts = append(self.amounts, amount) +} + +func TestSwap(t *testing.T) { + + strategy := &Strategy{ + AutoCashInterval: 1 * time.Second, + AutoCashThreshold: big.NewInt(20), + AutoDepositInterval: 1 * time.Second, + AutoDepositThreshold: big.NewInt(20), + AutoDepositBuffer: big.NewInt(40), + } + + local := &Params{ + Profile: &Profile{ + PayAt: 5, + DropAt: 10, + BuyAt: common.Big3, + SellAt: common.Big2, + }, + Strategy: strategy, + } + + in := &testInPayment{} + out := &testOutPayment{} + proto := &testProtocol{} + + swap, _ := New(local, Payment{In: in, Out: out, Buys: true, Sells: true}, proto) + + if in.autocashInterval != strategy.AutoCashInterval { + t.Fatalf("autocash interval not properly set, expect %v, got ", strategy.AutoCashInterval, in.autocashInterval) + } + if out.autodepositInterval != strategy.AutoDepositInterval { + t.Fatalf("autodeposit interval not properly set, expect %v, got ", strategy.AutoDepositInterval, out.autodepositInterval) + } + + remote := &Profile{ + PayAt: 3, + DropAt: 10, + BuyAt: common.Big2, + SellAt: common.Big3, + } + swap.SetRemote(remote) + + swap.Add(9) + if proto.drop { + t.Fatalf("not expected peer to be dropped") + } + swap.Add(1) + if !proto.drop { + t.Fatalf("expected peer to be dropped") + } + if !proto.drop { + t.Fatalf("expected peer to be dropped") + } + proto.drop = false + + swap.Receive(10, &testPromise{big.NewInt(20)}) + if swap.balance != 0 { + t.Fatalf("expected zero balance, got %v", swap.balance) + } + + if len(proto.amounts) != 0 { + t.Fatalf("expected zero balance, got %v", swap.balance) + } + + swap.Add(-2) + if len(proto.amounts) > 0 { + t.Fatalf("expected no payments yet, got %v", proto.amounts) + } + + swap.Add(-1) + if len(proto.amounts) != 1 { + t.Fatalf("expected one payment, got %v", len(proto.amounts)) + } + + if proto.amounts[0] != 3 { + t.Fatalf("expected payment for %v units, got %v", proto.amounts[0]) + } + + exp := new(big.Int).Mul(big.NewInt(int64(proto.amounts[0])), remote.SellAt) + if proto.promises[0].amount.Cmp(exp) != 0 { + t.Fatalf("expected payment amount %v, got %v", exp, proto.promises[0].amount) + } + + swap.SetParams(&Params{ + Profile: &Profile{ + PayAt: 5, + DropAt: 10, + BuyAt: common.Big3, + SellAt: common.Big2, + }, + Strategy: &Strategy{ + AutoCashInterval: 2 * time.Second, + AutoCashThreshold: big.NewInt(40), + AutoDepositInterval: 2 * time.Second, + AutoDepositThreshold: big.NewInt(40), + AutoDepositBuffer: big.NewInt(60), + }, + }) + +} diff --git a/common/types.go b/common/types.go index b1666d7338..58bd091346 100644 --- a/common/types.go +++ b/common/types.go @@ -187,3 +187,7 @@ func PP(value []byte) string { return fmt.Sprintf("%x...%x", value[:4], value[len(value)-4]) } + +func quote(s string) string { + return `"` + s + `"` +} diff --git a/core/genesis.go b/core/genesis.go index d8c6e9cea3..6e54f50495 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -35,6 +35,11 @@ import ( "github.com/ethereum/go-ethereum/params" ) +const ( + TestAccount = "e273f01c99144c438695e10f24926dc1f9fbf62d" + TestBalance = "1000000000000" +) + // WriteGenesisBlock writes the genesis block to the database as block number 0 func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block, error) { contents, err := ioutil.ReadAll(reader) diff --git a/rpc/api/bzz.go b/rpc/api/bzz.go new file mode 100644 index 0000000000..c258879857 --- /dev/null +++ b/rpc/api/bzz.go @@ -0,0 +1,278 @@ +// 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 Lesser 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with go-ethereum. If not, see . + +package api + +import ( + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "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/swarm" +) + +const ( + BzzApiVersion = "1.0" +) + +// eth api provider +// See https://github.com/ethereum/wiki/wiki/JSON-RPC +type bzzApi struct { + swarm *swarm.Swarm + methods map[string]bzzhandler + codec codec.ApiCoder +} + +// eth callback handler +type bzzhandler func(*bzzApi, *shared.Request) (interface{}, error) + +var ( + bzzMapping = map[string]bzzhandler{ + "bzz_info": (*bzzApi).Info, + "bzz_issue": (*bzzApi).Issue, + "bzz_cash": (*bzzApi).Cash, + "bzz_deposit": (*bzzApi).Deposit, + "bzz_register": (*bzzApi).Register, + "bzz_resolve": (*bzzApi).Resolve, + "bzz_download": (*bzzApi).Download, + "bzz_upload": (*bzzApi).Upload, + "bzz_get": (*bzzApi).Get, + "bzz_put": (*bzzApi).Put, + "bzz_modify": (*bzzApi).Modify, + } +) + +func newSwarmOfflineError(method string) error { + return shared.NewNotAvailableError(method, "swarm offline") +} + +// create new bzzApi instance +func NewBzzApi(stack *node.Node, codec codec.Codec) *bzzApi { + var swarm *swarm.Swarm + stack.Service(&swarm) + return &bzzApi{swarm, bzzMapping, codec.New(nil)} +} + +// collection with supported methods +func (self *bzzApi) 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 *bzzApi) 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 *bzzApi) Name() string { + return shared.BzzApiName +} + +func (self *bzzApi) ApiVersion() string { + return BzzApiVersion +} + +func (self *bzzApi) Info(req *shared.Request) (interface{}, error) { + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + return s.Api().Info(), nil +} + +func (self *bzzApi) Issue(req *shared.Request) (interface{}, error) { + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzIssueArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + cheque, err := s.Api().Issue(common.HexToAddress(args.Beneficiary), args.Amount) + if err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + out, err := json.MarshalIndent(cheque, " ", "") + if err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + return string(out), nil +} + +func (self *bzzApi) Cash(req *shared.Request) (interface{}, error) { + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzCashArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + return s.Api().Cash(args.Cheque) + +} + +func (self *bzzApi) Deposit(req *shared.Request) (interface{}, error) { + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzDepositArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + return s.Api().Deposit(args.Amount) +} + +func (self *bzzApi) Register(req *shared.Request) (interface{}, error) { + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzRegisterArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + err := s.Api().Register(common.HexToAddress(args.Address), args.Domain, common.HexToHash(args.ContentHash)) + return err == nil, err +} + +func (self *bzzApi) Resolve(req *shared.Request) (interface{}, error) { + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzResolveArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + key, err := s.Api().Resolve(args.Domain) + return key.Hex(), err +} + +func (self *bzzApi) Download(req *shared.Request) (interface{}, error) { + + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzDownloadArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + err := s.Api().Download(args.BzzPath, args.LocalPath) + return err == nil, err +} + +func (self *bzzApi) Upload(req *shared.Request) (interface{}, error) { + + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzUploadArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + return s.Api().Upload(args.LocalPath, args.Index) +} + +func (self *bzzApi) Get(req *shared.Request) (interface{}, error) { + + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzGetArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + var content []byte + var mimeType string + var status, size int + var err error + content, mimeType, status, size, err = s.Api().Get(args.Path) + + obj := map[string]string{ + "content": string(content), + "contentType": mimeType, + "status": fmt.Sprintf("%v", status), + "size": fmt.Sprintf("%v", size), + } + + return obj, err +} + +func (self *bzzApi) Put(req *shared.Request) (interface{}, error) { + + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzPutArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + return s.Api().Put(args.Content, args.ContenType) +} + +func (self *bzzApi) Modify(req *shared.Request) (interface{}, error) { + + s := self.swarm + if s == nil { + return nil, newSwarmOfflineError(req.Method) + } + + args := new(BzzModifyArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + return s.Api().Modify(args.RootHash, args.Path, args.ContentHash, args.ContentType) +} diff --git a/rpc/api/bzz_args.go b/rpc/api/bzz_args.go new file mode 100644 index 0000000000..2aee255e15 --- /dev/null +++ b/rpc/api/bzz_args.go @@ -0,0 +1,322 @@ +// 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 Lesser 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with go-ethereum. If not, see . + +package api + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common/chequebook" + "github.com/ethereum/go-ethereum/rpc/shared" +) + +type BzzDepositArgs struct { + Amount *big.Int +} + +func (args *BzzDepositArgs) 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) + } + + amount, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("Amount", "not a string") + } + args.Amount, ok = new(big.Int).SetString(amount, 10) + if !ok { + return shared.NewInvalidTypeError("Amount", "not a number") + } + + return nil +} + +type BzzCashArgs struct { + Cheque *chequebook.Cheque +} + +func (args *BzzCashArgs) 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) + } + + chequestr, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("Cheque", "not a string") + } + var cheque chequebook.Cheque + err = json.Unmarshal([]byte(chequestr), &cheque) + if err != nil { + return shared.NewDecodeParamError(err.Error()) + } + args.Cheque = &cheque + + return nil +} + +type BzzIssueArgs struct { + Beneficiary string + Amount *big.Int +} + +func (args *BzzIssueArgs) 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) + } + + beneficiary, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("Amount", "not a string") + } + args.Beneficiary = beneficiary + + amount, ok := obj[1].(string) + if !ok { + return shared.NewInvalidTypeError("Amount", "not a string") + } + args.Amount, ok = new(big.Int).SetString(amount, 10) + if !ok { + return shared.NewInvalidTypeError("Amount", "not a number") + } + + return nil +} + +type BzzRegisterArgs struct { + Address, ContentHash, Domain string +} + +func (args *BzzRegisterArgs) 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), 1) + } + + addstr, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("Address", "not a string") + } + args.Address = addstr + + addstr, ok = obj[1].(string) + if !ok { + return shared.NewInvalidTypeError("Domain", "not a string") + } + args.Domain = addstr + + addstr, ok = obj[2].(string) + if !ok { + return shared.NewInvalidTypeError("ContentHash", "not a string") + } + args.ContentHash = addstr + + return nil +} + +type BzzResolveArgs struct { + Domain string +} + +func (args *BzzResolveArgs) 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("Domain", "not a string") + } + args.Domain = addstr + + return nil +} + +type BzzDownloadArgs struct { + BzzPath, LocalPath string +} + +func (args *BzzDownloadArgs) 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), 1) + } + + addstr, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("BzzPath", "not a string") + } + args.BzzPath = addstr + + addstr, ok = obj[1].(string) + if !ok { + return shared.NewInvalidTypeError("LocalPath", "not a string") + } + args.LocalPath = addstr + + return nil +} + +type BzzUploadArgs struct { + LocalPath, Index string +} + +func (args *BzzUploadArgs) 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("LocalPath", "not a string") + } + args.LocalPath = addstr + + if len(obj) > 1 { + addstr, ok := obj[1].(string) + if ok { + args.Index = addstr + } + } + + return nil +} + +type BzzGetArgs struct { + Path string +} + +func (args *BzzGetArgs) 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("Path", "not a string") + } + args.Path = addstr + + return nil +} + +type BzzPutArgs struct { + Content, ContenType string +} + +func (args *BzzPutArgs) 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("Content", "not a string") + } + args.Content = addstr + + addstr, ok = obj[1].(string) + if !ok { + return shared.NewInvalidTypeError("ContenType", "not a string") + } + args.ContenType = addstr + + return nil +} + +type BzzModifyArgs struct { + RootHash, Path, ContentHash, ContentType string +} + +func (args *BzzModifyArgs) 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), 1) + } + + addstr, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("RootHash", "not a string") + } + args.RootHash = addstr + + addstr, ok = obj[1].(string) + if !ok { + return shared.NewInvalidTypeError("Path", "not a string") + } + args.Path = addstr + + if len(obj) >= 4 { + addstr, ok = obj[2].(string) + if ok { + args.ContentHash = addstr + } + + addstr, ok = obj[3].(string) + if ok { + args.ContentType = addstr + } + } + + return nil +} diff --git a/rpc/api/bzz_js.go b/rpc/api/bzz_js.go new file mode 100644 index 0000000000..50bbd43d07 --- /dev/null +++ b/rpc/api/bzz_js.go @@ -0,0 +1,95 @@ +// 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 Lesser 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with go-ethereum. If not, see . + +package api + +const Bzz_JS = ` +web3._extend({ + property: 'bzz', + methods: + [ + new web3._extend.Method({ + name: 'deposit', + call: 'bzz_deposit', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'info', + call: 'bzz_info', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'cash', + call: 'bzz_cash', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'issue', + call: 'bzz_issue', + params: 2, + inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'register', + call: 'bzz_register', + params: 3, + inputFormatter: [null, null, null] + }), + new web3._extend.Method({ + name: 'resolve', + call: 'bzz_resolve', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'download', + call: 'bzz_download', + params: 2, + inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'upload', + call: 'bzz_upload', + params: 2, + inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'get', + call: 'bzz_get', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'put', + call: 'bzz_put', + params: 2, + inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'modify', + call: 'bzz_modify', + params: 4, + inputFormatter: [null, null, null, null] + }) + ], + properties: + [ + ] +}); +` diff --git a/rpc/api/utils.go b/rpc/api/utils.go index 794b6abeea..d52f6b3dc3 100644 --- a/rpc/api/utils.go +++ b/rpc/api/utils.go @@ -54,8 +54,28 @@ var ( "startRPC", "stopNatSpec", "stopRPC", + "setGlobalRegistrar", + "setHashReg", + "setUrlHint", + "saveInfo", + "getContractInfo", + "sleep", + "httpGet", "verbosity", }, + "bzz": []string{ + "info", + "issue", + "cash", + "deposit", + "register", + "resolve", + "download", + "upload", + "get", + "put", + "modify", + }, "db": []string{ "getString", "putString", @@ -102,7 +122,7 @@ var ( "hashrate", "mining", "namereg", - "pendingTransactions", + "getPendingTransactions", "resend", "sendRawTransaction", "sendTransaction", @@ -116,6 +136,7 @@ var ( "setExtra", "setGasPrice", "startAutoDAG", + "setEtherbase", "start", "stopAutoDAG", "stop", @@ -173,6 +194,8 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, stack *no switch strings.ToLower(strings.TrimSpace(name)) { case shared.AdminApiName: apis[i] = NewAdminApi(xeth, stack, codec) + case shared.BzzApiName: + apis[i] = NewBzzApi(stack, codec) case shared.DebugApiName: apis[i] = NewDebugApi(xeth, eth, codec) case shared.DbApiName: @@ -204,6 +227,8 @@ func Javascript(name string) string { switch strings.ToLower(strings.TrimSpace(name)) { case shared.AdminApiName: return Admin_JS + case shared.BzzApiName: + return Bzz_JS case shared.DebugApiName: return Debug_JS case shared.DbApiName: diff --git a/rpc/shared/utils.go b/rpc/shared/utils.go index b13e9eb1b4..1590265a01 100644 --- a/rpc/shared/utils.go +++ b/rpc/shared/utils.go @@ -20,6 +20,7 @@ import "strings" const ( AdminApiName = "admin" + BzzApiName = "bzz" EthApiName = "eth" DbApiName = "db" DebugApiName = "debug" @@ -37,7 +38,7 @@ const ( var ( // All API's AllApis = strings.Join([]string{ - AdminApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName, + AdminApiName, BzzApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName, ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName, }, ",") ) diff --git a/swarm/api/api.go b/swarm/api/api.go new file mode 100644 index 0000000000..11bf4d81ec --- /dev/null +++ b/swarm/api/api.go @@ -0,0 +1,456 @@ +package api + +import ( + "bufio" + "fmt" + "io" + "math/big" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/swarm/storage" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/chequebook" + "github.com/ethereum/go-ethereum/common/registrar" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +var ( + hashMatcher = regexp.MustCompile("^[0-9A-Fa-f]{64}") + slashes = regexp.MustCompile("/+") + domainAndVersion = regexp.MustCompile("[@:;,]+") +) + +/* +Api implements webserver/file system related content storage and retrieval +on top of the dpa +it is the public interface of the dpa which is included in the ethereum stack +*/ +type Api struct { + dpa *storage.DPA + registrar registrar.VersionedRegistrar + conf *Config +} + +//the api constructor initialises +func NewApi(dpa *storage.DPA, registrar registrar.VersionedRegistrar, conf *Config) (self *Api) { + return &Api{dpa, registrar, conf} +} + +// this should move over to chequebook ipc api +func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *chequebook.Cheque, err error) { + return self.conf.Swap.Chequebook().Issue(beneficiary, amount) +} + +func (self *Api) Cash(cheque *chequebook.Cheque) (txhash string, err error) { + return self.conf.Swap.Chequebook().Cash(cheque) +} + +func (self *Api) Deposit(amount *big.Int) (txhash string, err error) { + return self.conf.Swap.Chequebook().Deposit(amount) +} + +// serialisable info about swarm +type Info struct { + *Config + *chequebook.Params +} + +func (self *Api) Info() *Info { + + return &Info{ + Config: self.conf, + Params: chequebook.ContractParams, + } + +} + +// Get uses iterative manifest retrieval and prefix matching +// to resolve path to content using dpa retrieve +func (self *Api) Get(bzzpath string) (content []byte, mimeType string, status int, size int, err error) { + var reader storage.SectionReader + reader, mimeType, status, err = self.getPath("/" + bzzpath) + if err != nil { + return + } + content = make([]byte, reader.Size()) + size, err = reader.Read(content) + if err == io.EOF { + err = nil + } + return +} + +// Put provides singleton manifest creation and optional name registration +// on top of dpa store +func (self *Api) Put(content, contentType string) (string, error) { + sr := io.NewSectionReader(strings.NewReader(content), 0, int64(len(content))) + wg := &sync.WaitGroup{} + key, err := self.dpa.Store(sr, wg) + if err != nil { + return "", err + } + manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType) + sr = io.NewSectionReader(strings.NewReader(manifest), 0, int64(len(manifest))) + key, err = self.dpa.Store(sr, wg) + if err != nil { + return "", err + } + wg.Wait() + return key.String(), nil +} + +func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) { + root := common.Hex2Bytes(rootHash) + trie, err := loadManifest(self.dpa, root) + if err != nil { + return + } + + if contentHash != "" { + entry := &manifestTrieEntry{ + Path: path, + Hash: contentHash, + ContentType: contentType, + } + trie.addEntry(entry) + } else { + trie.deleteEntry(path) + } + + err = trie.recalcAndStore() + if err != nil { + return + } + return trie.hash.String(), nil +} + +const maxParallelFiles = 5 + +// Download replicates the manifest path structure on the local filesystem +// under localpath +func (self *Api) Download(bzzpath, localpath string) (err error) { + lpath, err := filepath.Abs(filepath.Clean(localpath)) + if err != nil { + return + } + err = os.MkdirAll(lpath, os.ModePerm) + if err != nil { + return + } + + parts := slashes.Split(bzzpath, 3) + if len(parts) < 2 { + return fmt.Errorf("Invalid bzz path") + } + hostPort := parts[1] + var path string + if len(parts) > 2 { + path = regularSlashes(parts[2]) + "/" + } + glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path) + + //resolving host and port + var key storage.Key + key, err = self.Resolve(hostPort) + if err != nil { + err = errResolve(err) + glog.V(logger.Debug).Infof("[BZZ] Swarm: error : %v", err) + return + } + + trie, err := loadManifest(self.dpa, key) + if err != nil { + glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err) + return + } + + type downloadListEntry struct { + key storage.Key + path string + } + + var list []*downloadListEntry + var mde, mderr error + + prevPath := lpath + err = trie.listWithPrefix(path, func(entry *manifestTrieEntry, suffix string) { // TODO: paralellize + key := common.Hex2Bytes(entry.Hash) + path := lpath + "/" + suffix + dir := filepath.Dir(path) + if dir != prevPath { + mde = os.MkdirAll(dir, os.ModePerm) + if mde != nil { + mderr = mde + } + prevPath = dir + } + if (mde == nil) && (path != dir+"/") { + list = append(list, &downloadListEntry{key: key, path: path}) + } + }) + if err == nil { + err = mderr + } + + cnt := len(list) + errors := make([]error, cnt) + done := make(chan bool, maxParallelFiles) + dcnt := 0 + + for i, entry := range list { + if i >= dcnt+maxParallelFiles { + <-done + dcnt++ + } + go func(i int, entry *downloadListEntry, done chan bool) { + f, err := os.Create(entry.path) // TODO: path separators + if err == nil { + reader := self.dpa.Retrieve(entry.key) + writer := bufio.NewWriter(f) + _, err = io.CopyN(writer, reader, reader.Size()) // TODO: handle errors + err2 := writer.Flush() + if err == nil { + err = err2 + } + err2 = f.Close() + if err == nil { + err = err2 + } + } + + errors[i] = err + done <- true + }(i, entry, done) + } + for dcnt < cnt { + <-done + dcnt++ + } + + if err != nil { + return + } + for i, _ := range list { + if errors[i] != nil { + return errors[i] + } + } + return +} + +// Upload replicates a local directory as a manifest file and uploads it +// using dpa store +// TODO: localpath should point to a manifest +func (self *Api) Upload(lpath, index string) (string, error) { + var list []*manifestTrieEntry + localpath, err := filepath.Abs(filepath.Clean(lpath)) + if err != nil { + return "", err + } + + f, err := os.Open(localpath) + if err != nil { + return "", err + } + stat, err := f.Stat() + if err != nil { + return "", err + } + + var start int + if stat.IsDir() { + start = len(localpath) + glog.V(logger.Debug).Infof("[BZZ] uploading '%s'", localpath) + err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { + if (err == nil) && !info.IsDir() { + //fmt.Printf("lp %s path %s\n", localpath, path) + if len(path) <= start { + return fmt.Errorf("Path is too short") + } + if path[:start] != localpath { + return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) + } + entry := &manifestTrieEntry{ + Path: path, + } + list = append(list, entry) + } + return err + }) + if err != nil { + return "", err + } + } else { + dir := filepath.Dir(localpath) + start = len(dir) + if len(localpath) <= start { + return "", fmt.Errorf("Path is too short") + } + if localpath[:start] != dir { + return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir) + } + entry := &manifestTrieEntry{ + Path: localpath, + } + list = append(list, entry) + } + + cnt := len(list) + errors := make([]error, cnt) + done := make(chan bool, maxParallelFiles) + dcnt := 0 + + for i, entry := range list { + if i >= dcnt+maxParallelFiles { + <-done + dcnt++ + } + go func(i int, entry *manifestTrieEntry, done chan bool) { + f, err := os.Open(entry.Path) + if err == nil { + stat, _ := f.Stat() + sr := io.NewSectionReader(f, 0, stat.Size()) + wg := &sync.WaitGroup{} + var hash storage.Key + hash, err = self.dpa.Store(sr, wg) + if hash != nil { + list[i].Hash = hash.String() + } + wg.Wait() + if err == nil { + first512 := make([]byte, 512) + fread, _ := sr.ReadAt(first512, 0) + if fread > 0 { + mimeType := http.DetectContentType(first512[:fread]) + if filepath.Ext(entry.Path) == ".css" { + mimeType = "text/css" + } + list[i].ContentType = mimeType + //fmt.Printf("%v %v %v\n", entry.Path, mimeType, filepath.Ext(entry.Path)) + } + } + f.Close() + } + errors[i] = err + done <- true + }(i, entry, done) + } + for dcnt < cnt { + <-done + dcnt++ + } + + trie := &manifestTrie{ + dpa: self.dpa, + } + for i, entry := range list { + if errors[i] != nil { + return "", errors[i] + } + entry.Path = regularSlashes(entry.Path[start:]) + if entry.Path == index { + ientry := &manifestTrieEntry{ + Path: "", + Hash: entry.Hash, + ContentType: entry.ContentType, + } + trie.addEntry(ientry) + } + trie.addEntry(entry) + } + + err2 := trie.recalcAndStore() + var hs string + if err2 == nil { + hs = trie.hash.String() + } + return hs, err2 +} + +func (self *Api) Register(sender common.Address, domain string, hash common.Hash) (err error) { + domainhash := common.BytesToHash(crypto.Sha3([]byte(domain))) + + if self.registrar != nil { + glog.V(logger.Debug).Infof("[BZZ] Swarm: host '%s' (hash: '%v') to be registered as '%v'", domain, domainhash.Hex(), hash.Hex()) + _, err = self.registrar.Registry().SetHashToHash(sender, domainhash, hash) + } else { + err = fmt.Errorf("no registry: %v", err) + } + return +} + +type errResolve error + +func (self *Api) Resolve(hostPort string) (contentHash storage.Key, err error) { + host := hostPort + if hashMatcher.MatchString(host) { + contentHash = storage.Key(common.Hex2Bytes(host)) + glog.V(logger.Debug).Infof("[BZZ] Swarm: host is a contentHash: '%v'", contentHash) + } else { + if self.registrar != nil { + var hash common.Hash + var version *big.Int + parts := domainAndVersion.Split(host, 3) + if len(parts) > 1 && parts[1] != "" { + host = parts[0] + version = common.Big(parts[1]) + } + hostHash := crypto.Sha3Hash([]byte(host)) + hash, err = self.registrar.Resolver(version).HashToHash(hostHash) + if err != nil { + err = fmt.Errorf("unable to resolve '%s': %v", hostPort, err) + } + contentHash = storage.Key(hash.Bytes()) + glog.V(logger.Debug).Infof("[BZZ] Swarm: resolve host '%s' to contentHash: '%v'", hostPort, contentHash) + } else { + err = fmt.Errorf("no resolver '%s': %v", hostPort, err) + } + } + return +} + +func (self *Api) getPath(uri string) (reader storage.SectionReader, mimeType string, status int, err error) { + parts := slashes.Split(uri, 3) + hostPort := parts[1] + var path string + if len(parts) > 2 { + path = parts[2] + } + glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path) + + //resolving host and port + var key storage.Key + key, err = self.Resolve(hostPort) + if err != nil { + err = errResolve(err) + glog.V(logger.Debug).Infof("[BZZ] Swarm: error : %v", err) + return + } + + trie, err := loadManifest(self.dpa, key) + if err != nil { + glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err) + return + } + + glog.V(logger.Debug).Infof("[BZZ] Swarm: getEntry(%s)", path) + entry, _ := trie.getEntry(path) + if entry != nil { + key = common.Hex2Bytes(entry.Hash) + status = entry.Status + mimeType = entry.ContentType + glog.V(logger.Debug).Infof("[BZZ] Swarm: content lookup key: '%v' (%v)", key, mimeType) + reader = self.dpa.Retrieve(key) + } else { + err = fmt.Errorf("manifest entry for '%s' not found", path) + glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err) + } + return +} diff --git a/swarm/api/api_test.go b/swarm/api/api_test.go new file mode 100644 index 0000000000..81156afcd8 --- /dev/null +++ b/swarm/api/api_test.go @@ -0,0 +1,205 @@ +package api + +import ( + "bytes" + "io/ioutil" + "os" + "path" + "runtime" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/swarm/storage" +) + +//TODO: add tests for resolver/registrar +// will most likely be its own package under service? + +var ( + testDir string +) + +func init() { + _, filename, _, _ := runtime.Caller(1) + testDir = path.Join(path.Dir(filename), "../test") +} + +func testApi() (api *Api, err error) { + datadir, err := ioutil.TempDir("", "bzz-test") + if err != nil { + return nil, err + } + os.RemoveAll(datadir) + dpa, err := storage.NewLocalDPA(datadir) + if err != nil { + return + } + prvkey, _ := crypto.GenerateKey() + + config, err := NewConfig(datadir, common.Address{}, prvkey) + if err != nil { + return + } + api = NewApi(dpa, nil, config) + api.dpa.Start() + + return +} + +func TestApiPut(t *testing.T) { + api, err := testApi() + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + defer api.dpa.Stop() + expContent := "hello" + expMimeType := "text/plain" + expStatus := 0 + expSize := len(expContent) + bzzhash, err := api.Put(expContent, expMimeType) + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + testGet(t, api, bzzhash, []byte(expContent), expMimeType, expStatus, expSize) +} + +func testGet(t *testing.T, api *Api, bzzhash string, expContent []byte, expMimeType string, expStatus int, expSize int) { + content, mimeType, status, size, err := api.Get(bzzhash) + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + if !bytes.Equal(content, expContent) { + t.Errorf("incorrect content. expected '%s...', got '%s...'", string(expContent), string(content)) + } + if mimeType != expMimeType { + t.Errorf("incorrect mimeType. expected '%s', got '%s'", expMimeType, mimeType) + } + if status != expStatus { + t.Errorf("incorrect status. expected '%d', got '%d'", expStatus, status) + } + if size != expSize { + t.Errorf("incorrect size. expected '%d', got '%d'", expSize, size) + } +} + +func TestApiDirUpload(t *testing.T) { + t.Skip("FIXME") + api, err := testApi() + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + bzzhash, err := api.Upload(path.Join(testDir, "test0"), "") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) + testGet(t, api, path.Join(bzzhash, "index.html"), content, "text/html; charset=utf-8", 0, 202) + + content, err = ioutil.ReadFile(path.Join(testDir, "test0", "index.css")) + testGet(t, api, path.Join(bzzhash, "index.css"), content, "text/css", 0, 132) + + content, err = ioutil.ReadFile(path.Join(testDir, "test0", "img", "logo.png")) + testGet(t, api, path.Join(bzzhash, "img", "logo.png"), content, "image/png", 0, 18136) + + _, _, _, _, err = api.Get(bzzhash) + if err == nil { + t.Errorf("expected error: %v", err) + } +} + +func TestApiDirUploadModify(t *testing.T) { + t.Skip("FIXME") + api, err := testApi() + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + bzzhash, err := api.Upload(path.Join(testDir, "test0"), "") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + bzzhash, err = api.Modify(bzzhash, "index.html", "", "") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + bzzhash, err = api.Modify(bzzhash, "index2.html", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + bzzhash, err = api.Modify(bzzhash, "img/logo.png", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) + testGet(t, api, path.Join(bzzhash, "index2.html"), content, "text/html; charset=utf-8", 0, 202) + testGet(t, api, path.Join(bzzhash, "img", "logo.png"), content, "text/html; charset=utf-8", 0, 202) + + content, err = ioutil.ReadFile(path.Join(testDir, "test0", "index.css")) + testGet(t, api, path.Join(bzzhash, "index.css"), content, "text/css", 0, 132) + + _, _, _, _, err = api.Get(bzzhash) + if err == nil { + t.Errorf("expected error: %v", err) + } +} + +func TestApiDirUploadWithRootFile(t *testing.T) { + api, err := testApi() + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + bzzhash, err := api.Upload(path.Join(testDir, "test0"), "index.html") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) + testGet(t, api, bzzhash, content, "text/html; charset=utf-8", 0, 202) +} + +func TestApiFileUpload(t *testing.T) { + api, err := testApi() + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + bzzhash, err := api.Upload(path.Join(testDir, "test0", "index.html"), "") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) + testGet(t, api, path.Join(bzzhash, "index.html"), content, "text/html; charset=utf-8", 0, 202) +} + +func TestApiFileUploadWithRootFile(t *testing.T) { + api, err := testApi() + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + bzzhash, err := api.Upload(path.Join(testDir, "test0", "index.html"), "index.html") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) + testGet(t, api, bzzhash, content, "text/html; charset=utf-8", 0, 202) +} diff --git a/swarm/api/config.go b/swarm/api/config.go new file mode 100644 index 0000000000..6657167984 --- /dev/null +++ b/swarm/api/config.go @@ -0,0 +1,105 @@ +package api + +import ( + "crypto/ecdsa" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/swarm/network" + "github.com/ethereum/go-ethereum/swarm/services/swap" + "github.com/ethereum/go-ethereum/swarm/storage" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + port = "8500" +) + +// separate bzz directories +// allow several bzz nodes running in parallel +type Config struct { + // serialised/persisted fields + *storage.StoreParams + *storage.ChunkerParams + *network.HiveParams + Swap *swap.SwapParams + *network.SyncParams + Path string + Port string + PublicKey string + BzzKey string +} + +// config is agnostic to where private key is coming from +// so managing accounts is outside swarm and left to wrappers +func NewConfig(path string, contract common.Address, prvKey *ecdsa.PrivateKey) (self *Config, err error) { + + address := crypto.PubkeyToAddress(prvKey.PublicKey) // default beneficiary address + dirpath := filepath.Join(path, common.Bytes2Hex(address.Bytes())) + err = os.MkdirAll(dirpath, os.ModePerm) + if err != nil { + return + } + confpath := filepath.Join(dirpath, "config.json") + var data []byte + pubkey := crypto.FromECDSAPub(&prvKey.PublicKey) + pubkeyhex := common.ToHex(pubkey) + keyhex := crypto.Sha3Hash(pubkey).Hex() + + self = &Config{ + SyncParams: network.NewSyncParams(dirpath), + HiveParams: network.NewHiveParams(dirpath), + ChunkerParams: storage.NewChunkerParams(), + StoreParams: storage.NewStoreParams(dirpath), + Port: port, + Path: dirpath, + Swap: swap.DefaultSwapParams(contract, prvKey), + PublicKey: pubkeyhex, + BzzKey: keyhex, + } + data, err = ioutil.ReadFile(confpath) + if err != nil { + if !os.IsNotExist(err) { + return + } + // file does not exist + // write out config file + err = self.Save() + if err != nil { + err = fmt.Errorf("error writing config: %v", err) + } + return + } + // file exists, deserialise + err = json.Unmarshal(data, self) + if err != nil { + return nil, fmt.Errorf("unable to parse config: %v", err) + } + // check public key + if pubkeyhex != self.PublicKey { + return nil, fmt.Errorf("public key does not match the one in the config file %v != %v", pubkeyhex, self.PublicKey) + } + if keyhex != self.BzzKey { + return nil, fmt.Errorf("bzz key does not match the one in the config file %v != %v", keyhex, self.BzzKey) + } + self.Swap.SetKey(prvKey) + + return +} + +func (self *Config) Save() error { + data, err := json.MarshalIndent(self, "", " ") + if err != nil { + return err + } + err = os.MkdirAll(self.Path, os.ModePerm) + if err != nil { + return err + } + confpath := filepath.Join(self.Path, "config.json") + return ioutil.WriteFile(confpath, data, os.ModePerm) +} diff --git a/swarm/api/config_test.go b/swarm/api/config_test.go new file mode 100644 index 0000000000..44bb484667 --- /dev/null +++ b/swarm/api/config_test.go @@ -0,0 +1,107 @@ +package api + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +var ( + hexprvkey = "65138b2aa745041b372153550584587da326ab440576b2a1191dd95cee30039c" + defaultConfig = `{ + "ChunkDbPath": "` + filepath.Join("TMPDIR", "0d2f62485607cf38d9d795d93682a517661e513e", "chunks") + `", + "DbCapacity": 5000000, + "CacheCapacity": 5000, + "Radius": 0, + "Branches": 128, + "Hash": "SHA256", + "JoinTimeout": 120, + "SplitTimeout": 120, + "CallInterval": 10000000000, + "KadDbPath": "` + filepath.Join("TMPDIR", "0d2f62485607cf38d9d795d93682a517661e513e", "bzz-peers.json") + `", + "MaxProx": 10, + "ProxBinSize": 8, + "BucketSize": 3, + "PurgeInterval": 151200000000000, + "InitialRetryInterval": 4200000000, + "ConnRetryExp": 2, + "Swap": { + "BuyAt": 20000000000, + "SellAt": 20000000000, + "PayAt": 100, + "DropAt": 10000, + "AutoCashInterval": 300000000000, + "AutoCashThreshold": 50000000000000, + "AutoDepositInterval": 300000000000, + "AutoDepositThreshold": 50000000000000, + "AutoDepositBuffer": 100000000000000, + "PublicKey": "0x045f5cfd26692e48d0017d380349bcf50982488bc11b5145f3ddf88b24924299048450542d43527fbe29a5cb32f38d62755393ac002e6bfdd71b8d7ba725ecd7a3", + "Contract": "0x0000000000000000000000000000000000000000", + "Beneficiary": "0x0d2f62485607cf38d9d795d93682a517661e513e" + }, + "RequestDbPath": "` + filepath.Join("TMPDIR", "0d2f62485607cf38d9d795d93682a517661e513e", "requests") + `", + "RequestDbBatchSize": 512, + "KeyBufferSize": 1024, + "SyncBatchSize": 128, + "SyncBufferSize": 128, + "SyncCacheSize": 1024, + "SyncPriorities": [ + 2, + 1, + 1, + 0, + 0 + ], + "SyncModes": [ + true, + true, + true, + true, + false + ], + "Path": "` + filepath.Join("TMPDIR", "0d2f62485607cf38d9d795d93682a517661e513e") + `", + "Port": "8500", + "PublicKey": "0x045f5cfd26692e48d0017d380349bcf50982488bc11b5145f3ddf88b24924299048450542d43527fbe29a5cb32f38d62755393ac002e6bfdd71b8d7ba725ecd7a3", + "BzzKey": "0xe861964402c0b78e2d44098329b8545726f215afa737d803714a4338552fcb81" +}` +) + +func TestConfigWriteRead(t *testing.T) { + tmp, err := ioutil.TempDir(os.TempDir(), "bzz-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + + prvkey := crypto.ToECDSA(common.Hex2Bytes(hexprvkey)) + orig, err := NewConfig(tmp, common.Address{}, prvkey) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + account := crypto.PubkeyToAddress(prvkey.PublicKey) + dirpath := filepath.Join(tmp, common.Bytes2Hex(account.Bytes())) + confpath := filepath.Join(dirpath, "config.json") + data, err := ioutil.ReadFile(confpath) + if err != nil { + t.Fatalf("default config file cannot be read: %v", err) + } + exp := strings.Replace(defaultConfig, "TMPDIR", tmp, -1) + + if string(data) != exp { + t.Fatalf("default config mismatch:\nexpected:\n'%v'\ngot:\n'%v'", exp, string(data)) + } + + conf, err := NewConfig(tmp, common.Address{}, prvkey) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if conf.Swap.Beneficiary.Hex() != orig.Swap.Beneficiary.Hex() { + t.Fatalf("expected beneficiary from loaded config %v to match original %v", conf.Swap.Beneficiary.Hex(), orig.Swap.Beneficiary.Hex()) + } + +} diff --git a/swarm/api/ethereum.go b/swarm/api/ethereum.go new file mode 100644 index 0000000000..1027d1e768 --- /dev/null +++ b/swarm/api/ethereum.go @@ -0,0 +1,320 @@ +package api + +import ( + "errors" + "fmt" + "math/big" + "regexp" + "sync" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/registrar" + "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/eth" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +/* +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!! THIS IS CURRENTLY A PLACEHOLEDER (HACK) UNTIL PRC v2 +!! https://github.com/ethereum/go-ethereum/pull/1912 is merged +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +*/ + +var ( + defaultGasPrice = big.NewInt(10000000000000) //150000000000 + defaultGas = big.NewInt(90000) //500000 + addrReg = regexp.MustCompile(`^(0x)?[a-fA-F0-9]{40}$`) +) + +type ethApi struct { + eth *eth.Ethereum + gpo *eth.GasPriceOracle + transactionMu sync.RWMutex + transactMu sync.RWMutex + state *state.StateDB +} + +func NewEthApi(ethereum *eth.Ethereum) *ethApi { + return ðApi{ + eth: ethereum, + gpo: eth.NewGasPriceOracle(ethereum), + } +} + +// 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 *ethApi) UpdateState() (wait chan *big.Int) { + wait = make(chan *big.Int) + self.state, _ = state.New(self.eth.BlockChain().GetBlockByNumber(0).Root(), self.eth.ChainDb()) + + go func() { + eventSub := self.eth.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.eth.ChainDb()) + if err != nil { + glog.V(logger.Error).Infoln("Could not create new state: %v", err) + return + } + self.state = statedb + } + case n, ok = <-wait: + if !ok { + return + } + } + } + }() + return +} + +func (self *ethApi) AtStateNum(num int64) registrar.Backend { + var st *state.StateDB + var err error + switch num { + case -2: + st = self.eth.Miner().PendingState().Copy() + default: + if block := self.getBlockByHeight(num); block != nil { + st, err = state.New(block.Root(), self.eth.ChainDb()) + if err != nil { + return nil + } + } else { + st, err = state.New(self.eth.BlockChain().GetBlockByNumber(0).Root(), self.eth.ChainDb()) + if err != nil { + return nil + } + } + } + return registrar.Backend(ðApi{ + eth: self.eth, + state: st, + }) +} +func (self *ethApi) GetTxReceipt(txhash common.Hash) *types.Receipt { + return core.GetReceipt(self.eth.ChainDb(), txhash) +} + +func (self *ethApi) StorageAt(addr, storageAddr string) string { + return self.state.GetState(common.HexToAddress(addr), common.HexToHash(storageAddr)).Hex() +} + +func (self *ethApi) CodeAt(address string) string { + return common.ToHex(self.state.GetCode(common.HexToAddress(address))) +} + +func (self *ethApi) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, string, error) { + statedb := self.state.Copy() + var from *state.StateObject + if len(fromStr) == 0 { + accounts, err := self.eth.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.eth.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 *ethApi) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { + + 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 + } + + self.transactMu.Lock() + defer self.transactMu.Unlock() + + var nonce uint64 + if len(nonceStr) != 0 { + nonce = common.Big(nonceStr).Uint64() + } else { + state := self.eth.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 + } + if err = self.eth.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 *ethApi) 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) +} + +func (self *ethApi) doSign(from common.Address, hash common.Hash, didUnlock bool) ([]byte, error) { + sig, err := self.eth.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") + } + // 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 DefaultGas() *big.Int { return new(big.Int).Set(defaultGas) } + +func (self *ethApi) DefaultGasPrice() *big.Int { + return self.gpo.SuggestPrice() +} + +func (self *ethApi) CurrentBlock() *types.Block { + return self.eth.BlockChain().CurrentBlock() +} + +func (self *ethApi) getBlockByHeight(height int64) *types.Block { + var num uint64 + + switch height { + case -2: + return self.eth.Miner().PendingBlock() + case -1: + return self.CurrentBlock() + default: + if height < 0 { + return nil + } + + num = uint64(height) + } + + return self.eth.BlockChain().GetBlockByNumber(num) +} + +// 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 +} + +func isAddress(addr string) bool { + return addrReg.MatchString(addr) +} + +// 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 } diff --git a/swarm/api/http.go b/swarm/api/http.go new file mode 100644 index 0000000000..b1cd039cce --- /dev/null +++ b/swarm/api/http.go @@ -0,0 +1,247 @@ +/* +A simple http server interface to Swarm +*/ +package api + +import ( + "bytes" + "io" + "net/http" + "regexp" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +const ( + rawType = "application/octet-stream" +) + +var ( + bzzPrefix = regexp.MustCompile("^/+bzz:/+") + rawUrl = regexp.MustCompile("^/+raw/*") + trailingSlashes = regexp.MustCompile("/+$") + // forever = func() time.Time { return time.Unix(0, 0) } + forever = time.Now +) + +type sequentialReader struct { + reader io.Reader + pos int64 + ahead map[int64](chan bool) + lock sync.Mutex +} + +// browser API for registering bzz url scheme handlers: +// https://developer.mozilla.org/en/docs/Web-based_protocol_handlers +// electron (chromium) api for registering bzz url scheme handlers: +// https://github.com/atom/electron/blob/master/docs/api/protocol.md + +// starts up http server +func StartHttpServer(api *Api, port string) { + serveMux := http.NewServeMux() + serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + handler(w, r, api) + }) + go http.ListenAndServe(":"+port, serveMux) + glog.V(logger.Info).Infof("[BZZ] Swarm HTTP proxy started on localhost:%s", port) +} + +func handler(w http.ResponseWriter, r *http.Request, api *Api) { + requestURL := r.URL + // This is wrong + // if requestURL.Host == "" { + // var err error + // requestURL, err = url.Parse(r.Referer() + requestURL.String()) + // if err != nil { + // http.Error(w, err.Error(), http.StatusBadRequest) + // return + // } + // } + glog.V(logger.Debug).Infof("[BZZ] Swarm: HTTP request URL: '%s', Host: '%s', Path: '%s', Referer: '%s', Accept: '%s'", r.RequestURI, requestURL.Host, requestURL.Path, r.Referer(), r.Header.Get("Accept")) + uri := requestURL.Path + var raw bool + + // HTTP-based URL protocol handler + uri = bzzPrefix.ReplaceAllString(uri, "") + glog.V(logger.Debug).Infof("[BZZ] Swarm: BZZ request URI: '%s'", uri) + + path := rawUrl.ReplaceAllStringFunc(uri, func(string) string { + raw = true + return "" + }) + + glog.V(logger.Debug).Infof("[BZZ] Swarm: %s request '%s' received.", r.Method, uri) + + switch { + case r.Method == "POST" || r.Method == "PUT": + key, err := api.dpa.Store(io.NewSectionReader(&sequentialReader{ + reader: r.Body, + ahead: make(map[int64]chan bool), + }, 0, r.ContentLength), nil) + if err == nil { + glog.V(logger.Debug).Infof("[BZZ] Swarm: Content for %v stored", key.Log()) + } else { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if r.Method == "POST" { + if raw { + w.Header().Set("Content-Type", "text/plain") + http.ServeContent(w, r, "", time.Now(), bytes.NewReader([]byte(common.Bytes2Hex(key)))) + } else { + http.Error(w, "No POST to "+uri+" allowed.", http.StatusBadRequest) + return + } + } else { + // PUT + if raw { + http.Error(w, "No PUT to /raw allowed.", http.StatusBadRequest) + return + } else { + path = regularSlashes(path) + mime := r.Header.Get("Content-Type") + // TODO proper root hash separation + glog.V(logger.Debug).Infof("[BZZ] Modify '%s' to store %v as '%s'.", path, key.Log(), mime) + newKey, err := api.Modify(path[:64], path[65:], common.Bytes2Hex(key), mime) + if err == nil { + glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey) + w.Header().Set("Content-Type", "text/plain") + http.ServeContent(w, r, "", time.Now(), bytes.NewReader([]byte(newKey))) + } else { + http.Error(w, "PUT to "+path+"failed.", http.StatusBadRequest) + return + } + } + } + case r.Method == "DELETE": + if raw { + http.Error(w, "No DELETE to /raw allowed.", http.StatusBadRequest) + return + } else { + path = regularSlashes(path) + glog.V(logger.Debug).Infof("[BZZ] Delete '%s'.", path) + newKey, err := api.Modify(path[:64], path[65:], "", "") + if err == nil { + glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey) + w.Header().Set("Content-Type", "text/plain") + http.ServeContent(w, r, "", time.Now(), bytes.NewReader([]byte(newKey))) + } else { + http.Error(w, "DELETE to "+path+"failed.", http.StatusBadRequest) + return + } + } + case r.Method == "GET" || r.Method == "HEAD": + path = trailingSlashes.ReplaceAllString(path, "") + if raw { + // resolving host + key, err := api.Resolve(path) + if err != nil { + glog.V(logger.Error).Infof("[BZZ] Swarm: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // retrieving content + reader := api.dpa.Retrieve(key) + glog.V(logger.Debug).Infof("[BZZ] Swarm: Reading %d bytes.", reader.Size()) + + // setting mime type + qv := requestURL.Query() + mimeType := qv.Get("content_type") + if mimeType == "" { + mimeType = rawType + } + + w.Header().Set("Content-Type", mimeType) + http.ServeContent(w, r, uri, forever(), reader) + glog.V(logger.Debug).Infof("[BZZ] Swarm: Serve raw content '%s' (%d bytes) as '%s'", uri, reader.Size(), mimeType) + + // retrieve path via manifest + } else { + + glog.V(logger.Debug).Infof("[BZZ] Swarm: Structured GET request '%s' received.", uri) + + // call to api.getPath on uri + reader, mimeType, status, err := api.getPath(path) + if err != nil { + if _, ok := err.(errResolve); ok { + glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err) + status = http.StatusBadRequest + } else { + glog.V(logger.Debug).Infof("[BZZ] Swarm: error retrieving '%s': %v", uri, err) + status = http.StatusNotFound + } + http.Error(w, err.Error(), status) + return + } + + // set mime type and status headers + w.Header().Set("Content-Type", mimeType) + if status > 0 { + w.WriteHeader(status) + } else { + status = 200 + } + glog.V(logger.Debug).Infof("[BZZ] Swarm: Served '%s' (%d bytes) as '%s' (status code: %v)", uri, reader.Size(), mimeType, status) + + http.ServeContent(w, r, path, forever(), reader) + + } + default: + http.Error(w, "Method "+r.Method+" is not supported.", http.StatusMethodNotAllowed) + } +} + +func (self *sequentialReader) ReadAt(target []byte, off int64) (n int, err error) { + self.lock.Lock() + // assert self.pos <= off + if self.pos > off { + glog.V(logger.Error).Infof("[BZZ] Swarm: non-sequential read attempted from sequentialReader; %d > %d", + self.pos, off) + panic("Non-sequential read attempt") + } + if self.pos != off { + glog.V(logger.Debug).Infof("[BZZ] Swarm: deferred read in POST at position %d, offset %d.", + self.pos, off) + wait := make(chan bool) + self.ahead[off] = wait + self.lock.Unlock() + if <-wait { + // failed read behind + n = 0 + err = io.ErrUnexpectedEOF + return + } + self.lock.Lock() + } + localPos := 0 + for localPos < len(target) { + n, err = self.reader.Read(target[localPos:]) + localPos += n + glog.V(logger.Debug).Infof("[BZZ] Swarm: Read %d bytes into buffer size %d from POST, error %v.", + n, len(target), err) + if err != nil { + glog.V(logger.Debug).Infof("[BZZ] Swarm: POST stream's reading terminated with %v.", err) + for i := range self.ahead { + self.ahead[i] <- true + delete(self.ahead, i) + } + self.lock.Unlock() + return localPos, err + } + self.pos += int64(n) + } + wait := self.ahead[self.pos] + if wait != nil { + glog.V(logger.Debug).Infof("[BZZ] Swarm: deferred read in POST at position %d triggered.", + self.pos) + delete(self.ahead, self.pos) + close(wait) + } + self.lock.Unlock() + return localPos, err +} diff --git a/swarm/api/manifest.go b/swarm/api/manifest.go new file mode 100644 index 0000000000..c1781ab7d3 --- /dev/null +++ b/swarm/api/manifest.go @@ -0,0 +1,310 @@ +package api + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/swarm/storage" +) + +const ( + manifestType = "application/bzz-manifest+json" +) + +type manifestTrie struct { + dpa *storage.DPA + entries [257]*manifestTrieEntry // indexed by first character of path, entries[256] is the empty path entry + hash storage.Key // if hash != nil, it is stored +} + +type manifestJSON struct { + Entries []*manifestTrieEntry `json:"entries"` +} + +type manifestTrieEntry struct { + Path string `json:"path"` + Hash string `json:"hash"` // for manifest content type, empty until subtrie is evaluated + ContentType string `json:"contentType"` + Status int `json:"status"` + subtrie *manifestTrie +} + +func loadManifest(dpa *storage.DPA, hash storage.Key) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand + + glog.V(logger.Detail).Infof("[BZZ] manifest lookup key: '%v'.", hash.Log()) + // retrieve manifest via DPA + manifestReader := dpa.Retrieve(hash) + return readManifest(manifestReader, hash, dpa) +} + +func readManifest(manifestReader storage.SectionReader, hash storage.Key, dpa *storage.DPA) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand + + // TODO check size for oversized manifests + manifestData := make([]byte, manifestReader.Size()) + var size int + size, err = manifestReader.Read(manifestData) + if int64(size) < manifestReader.Size() { + glog.V(logger.Detail).Infof("[BZZ] Manifest %v not found.", hash.Log()) + if err == nil { + err = fmt.Errorf("Manifest retrieval cut short: read %v, expect %v", size, manifestReader.Size()) + } + return + } + + glog.V(logger.Detail).Infof("[BZZ] Manifest %v retrieved", hash.Log()) + man := manifestJSON{} + err = json.Unmarshal(manifestData, &man) + if err != nil { + err = fmt.Errorf("Manifest %v is malformed: %v", hash.Log(), err) + glog.V(logger.Detail).Infof("[BZZ] %v", err) + return + } + + glog.V(logger.Detail).Infof("[BZZ] Manifest %v has %d entries.", hash.Log(), len(man.Entries)) + + trie = &manifestTrie{ + dpa: dpa, + } + for _, entry := range man.Entries { + trie.addEntry(entry) + } + return +} + +func (self *manifestTrie) addEntry(entry *manifestTrieEntry) { + self.hash = nil // trie modified, hash needs to be re-calculated on demand + + if len(entry.Path) == 0 { + self.entries[256] = entry + return + } + + b := byte(entry.Path[0]) + if (self.entries[b] == nil) || (self.entries[b].Path == entry.Path) { + self.entries[b] = entry + return + } + + oldentry := self.entries[b] + cpl := 0 + for (len(entry.Path) > cpl) && (len(oldentry.Path) > cpl) && (entry.Path[cpl] == oldentry.Path[cpl]) { + cpl++ + } + + if (oldentry.ContentType == manifestType) && (cpl == len(oldentry.Path)) { + if self.loadSubTrie(oldentry) != nil { + return + } + entry.Path = entry.Path[cpl:] + oldentry.subtrie.addEntry(entry) + oldentry.Hash = "" + return + } + + commonPrefix := entry.Path[:cpl] + + subtrie := &manifestTrie{ + dpa: self.dpa, + } + entry.Path = entry.Path[cpl:] + oldentry.Path = oldentry.Path[cpl:] + subtrie.addEntry(entry) + subtrie.addEntry(oldentry) + + self.entries[b] = &manifestTrieEntry{ + Path: commonPrefix, + Hash: "", + ContentType: manifestType, + subtrie: subtrie, + } +} + +func (self *manifestTrie) getCountLast() (cnt int, entry *manifestTrieEntry) { + for _, e := range self.entries { + if e != nil { + cnt++ + entry = e + } + } + return +} + +func (self *manifestTrie) deleteEntry(path string) { + self.hash = nil // trie modified, hash needs to be re-calculated on demand + + if len(path) == 0 { + self.entries[256] = nil + return + } + + b := byte(path[0]) + entry := self.entries[b] + if (entry != nil) && (entry.Path == path) { + self.entries[b] = nil + return + } + + epl := len(entry.Path) + if (entry.ContentType == manifestType) && (len(path) >= epl) && (path[:epl] == entry.Path) { + if self.loadSubTrie(entry) != nil { + return + } + entry.subtrie.deleteEntry(path[epl:]) + entry.Hash = "" + // remove subtree if it has less than 2 elements + cnt, lastentry := entry.subtrie.getCountLast() + if cnt < 2 { + if lastentry != nil { + lastentry.Path = entry.Path + lastentry.Path + } + self.entries[b] = lastentry + } + } +} + +func (self *manifestTrie) recalcAndStore() error { + if self.hash != nil { + return nil + } + + var buffer bytes.Buffer + buffer.WriteString(`{"entries":[`) + + list := &manifestJSON{} + for _, entry := range self.entries { + if entry != nil { + if entry.Hash == "" { // TODO: paralellize + err := entry.subtrie.recalcAndStore() + if err != nil { + return err + } + entry.Hash = entry.subtrie.hash.String() + } + list.Entries = append(list.Entries, entry) + } + } + + manifest, err := json.Marshal(list) + if err != nil { + return err + } + + sr := io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))) + wg := &sync.WaitGroup{} + key, err2 := self.dpa.Store(sr, wg) + wg.Wait() + self.hash = key + return err2 +} + +func (self *manifestTrie) loadSubTrie(entry *manifestTrieEntry) (err error) { + if entry.subtrie == nil { + hash := common.Hex2Bytes(entry.Hash) + entry.subtrie, err = loadManifest(self.dpa, hash) + entry.Hash = "" // might not match, should be recalculated + } + return +} + +func (self *manifestTrie) listWithPrefixInt(prefix, rp string, cb func(entry *manifestTrieEntry, suffix string)) (err error) { + plen := len(prefix) + var start, stop int + if plen == 0 { + start = 0 + stop = 256 + } else { + start = int(prefix[0]) + stop = start + } + + for i := start; i <= stop; i++ { + entry := self.entries[i] + if entry != nil { + epl := len(entry.Path) + if entry.ContentType == manifestType { + l := plen + if epl < l { + l = epl + } + if prefix[:l] == entry.Path[:l] { + sterr := self.loadSubTrie(entry) + if sterr == nil { + entry.subtrie.listWithPrefixInt(prefix[l:], rp+entry.Path[l:], cb) + } else { + err = sterr + } + } + } else { + if (epl >= plen) && (prefix == entry.Path[:plen]) { + cb(entry, rp+entry.Path[plen:]) + } + } + } + } + return +} + +func (self *manifestTrie) listWithPrefix(prefix string, cb func(entry *manifestTrieEntry, suffix string)) (err error) { + return self.listWithPrefixInt(prefix, "", cb) +} + +func (self *manifestTrie) findPrefixOf(path string) (entry *manifestTrieEntry, pos int) { + + glog.V(logger.Detail).Infof("[BZZ] findPrefixOf(%s)", path) + + if len(path) == 0 { + return self.entries[256], 0 + } + + b := byte(path[0]) + entry = self.entries[b] + if entry == nil { + return self.entries[256], 0 + } + epl := len(entry.Path) + glog.V(logger.Detail).Infof("[BZZ] path = %v entry.Path = %v epl = %v", path, entry.Path, epl) + if (len(path) >= epl) && (path[:epl] == entry.Path) { + glog.V(logger.Detail).Infof("[BZZ] entry.ContentType = %v", entry.ContentType) + if entry.ContentType == manifestType { + if self.loadSubTrie(entry) != nil { + return nil, 0 + } + entry, pos = entry.subtrie.findPrefixOf(path[epl:]) + if entry != nil { + pos += epl + } + } else { + pos = epl + } + } else { + entry = nil + } + return +} + +// file system manifest always contains regularized paths +// no leading or trailing slashes, only single slashes inside +func regularSlashes(path string) (res string) { + for i := 0; i < len(path); i++ { + if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) { + res = res + path[i:i+1] + } + } + if (len(res) > 0) && (res[len(res)-1] == '/') { + res = res[:len(res)-1] + } + return +} + +func (self *manifestTrie) getEntry(spath string) (entry *manifestTrieEntry, fullpath string) { + path := regularSlashes(spath) + var pos int + entry, pos = self.findPrefixOf(path) + return entry, path[:pos] +} diff --git a/swarm/api/manifest_test.go b/swarm/api/manifest_test.go new file mode 100644 index 0000000000..558bdfb51b --- /dev/null +++ b/swarm/api/manifest_test.go @@ -0,0 +1,61 @@ +package api + +import ( + // "encoding/json" + "fmt" + "io" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/swarm/storage" +) + +func manifest(paths ...string) (manifestReader storage.SectionReader) { + var entries []string + for _, path := range paths { + entry := fmt.Sprintf(`{"path":"%s"}`, path) + entries = append(entries, entry) + } + manifest := fmt.Sprintf(`{"entries":[%s]}`, strings.Join(entries, ",")) + return io.NewSectionReader(strings.NewReader(manifest), 0, int64(len(manifest))) +} + +func testGetEntry(t *testing.T, path, match string, paths ...string) *manifestTrie { + trie, err := readManifest(manifest(paths...), nil, nil) + if err != nil { + t.Errorf("unexpected error making manifest: %v", err) + } + checkEntry(t, path, match, trie) + return trie +} + +func checkEntry(t *testing.T, path, match string, trie *manifestTrie) { + entry, fullpath := trie.getEntry(path) + if match == "-" && entry != nil { + t.Errorf("expected no match for '%s', got '%s'", path, fullpath) + } else if entry == nil { + if match != "-" { + t.Errorf("expected entry '%s' to match '%s', got no match", match, path) + } + } else if fullpath != match { + t.Errorf("incorrect entry retrieved for '%s'. expected path '%v', got '%s'", path, match, fullpath) + } +} + +func TestGetEntry(t *testing.T) { + // file system manifest always contains regularized paths + testGetEntry(t, "a", "a", "a") + testGetEntry(t, "b", "-", "a") + testGetEntry(t, "/a//", "a", "a") + // fallback + testGetEntry(t, "/a", "", "") + testGetEntry(t, "/a/b", "a/b", "a/b") + // longest/deepest math + testGetEntry(t, "a/b", "-", "a", "a/ba", "a/b/c") + testGetEntry(t, "a/b", "a/b", "a", "a/b", "a/bb", "a/b/c") + testGetEntry(t, "//a//b//", "a/b", "a", "a/b", "a/bb", "a/b/c") +} + +func TestDeleteEntry(t *testing.T) { + +} diff --git a/swarm/api/roundtripper.go b/swarm/api/roundtripper.go new file mode 100644 index 0000000000..3dad7d04ef --- /dev/null +++ b/swarm/api/roundtripper.go @@ -0,0 +1,22 @@ +package api + +import ( + "fmt" + "net/http" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + + // "github.com/ethereum/go-ethereum/common/httpclient" + // "github.com/ethereum/go-ethereum/jsre" +) + +type RoundTripper struct { + Port string +} + +func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) { + url := fmt.Sprintf("http://localhost:%s/%s/%s", self.Port, req.URL.Host, req.URL.Path) + glog.V(logger.Info).Infof("[BZZ] roundtripper: proxying request '%s' to '%s'", req.RequestURI, url) + return http.Get(url) +} diff --git a/swarm/api/roundtripper_test.go b/swarm/api/roundtripper_test.go new file mode 100644 index 0000000000..e0b15658dc --- /dev/null +++ b/swarm/api/roundtripper_test.go @@ -0,0 +1,50 @@ +package api + +import ( + "io/ioutil" + "net/http" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/httpclient" +) + +func TestRoundTripper(t *testing.T) { + serveMux := http.NewServeMux() + serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + w.Header().Set("Content-Type", "text/plain") + http.ServeContent(w, r, "", time.Unix(0, 0), strings.NewReader(r.RequestURI)) + } else { + http.Error(w, "Method "+r.Method+" is not supported.", http.StatusMethodNotAllowed) + } + }) + go http.ListenAndServe(":8600", serveMux) + + rt := &RoundTripper{"8600"} + client := httpclient.New("/") + client.RegisterProtocol("bzz", rt) + + resp, err := client.Client().Get("bzz://test.com/path") + if err != nil { + t.Errorf("expected no error, got %v", err) + return + } + + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("expected no error, got %v", err) + return + } + if string(content) != "/test.com/path" { + t.Errorf("incorrect response from http server: expected '%v', got '%v'", "/test.com/path", string(content)) + } + +} diff --git a/swarm/cmd/bzzhash/bzzhash.go b/swarm/cmd/bzzhash/bzzhash.go new file mode 100644 index 0000000000..d4df5fb50b --- /dev/null +++ b/swarm/cmd/bzzhash/bzzhash.go @@ -0,0 +1,38 @@ +// bzzhash +package main + +import ( + "fmt" + "io" + "os" + "runtime" + + "github.com/ethereum/go-ethereum/swarm/storage" +) + +func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) + + if len(os.Args) < 2 { + fmt.Println("Usage: bzzhash ") + os.Exit(0) + } + f, err := os.Open(os.Args[1]) + if err != nil { + fmt.Println("Error opening file " + os.Args[1]) + os.Exit(1) + } + + stat, _ := f.Stat() + sr := io.NewSectionReader(f, 0, stat.Size()) + chunker := storage.NewTreeChunker(storage.NewChunkerParams()) + hash := make([]byte, chunker.KeySize()) + errC := chunker.Split(hash, sr, nil, nil) + err, ok := <-errC + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + } + if !ok { + fmt.Printf("%064x\n", hash) + } +} diff --git a/swarm/cmd/bzzup.sh b/swarm/cmd/bzzup.sh new file mode 100755 index 0000000000..dc6490ad70 --- /dev/null +++ b/swarm/cmd/bzzup.sh @@ -0,0 +1,46 @@ +#! /bin/bash + +INDEX='index.html' +port="8500" +delimiter='{"entries":[{' + +if [[ ! -z "$2" ]]; then + port="$2" +fi + +if [ -f "$1" ]; then +hash=`wget -q -O- --post-file="$1" http://localhost:$port/raw` +mime=`mimetype -b "$1"` +wget -q -O- --post-data="$delimiter\"hash\":\"$hash\",\"contentType\":\"$mime\"}]}" http://localhost:8500/raw +echo + +else + +[ -d "$1" ] || exit -1 + +bzzroot="$1" +[ "_$1" = _ ] && bzzroot=. + +pushd "$bzzroot" > /dev/null + +(for path in `find . -type f` +do +name=`echo "$path" | cut -c3-` +[ _`basename "$name"` = "_$INDEX" ] && name=`dirname "$name"` +echo -n "$delimiter" +hash=`wget -q -O- --post-file="$path" http://localhost:$port/raw` +mime=`mimetype -b "$path"` +if [ "_$name" = '_.' ]; then +echo -n "\"hash\":\"$hash\",\"contentType\":\"$mime\"" +else +echo -n "\"hash\":\"$hash\",\"path\":\"$name\",\"contentType\":\"$mime\"" +fi +delimiter='},{' + +done +echo -n '}]}') | wget -q -O- --post-data=`cat` http://localhost:$port/raw +echo + +popd > /dev/null + +fi diff --git a/swarm/examples/album/add.png b/swarm/examples/album/add.png new file mode 100644 index 0000000000000000000000000000000000000000..6d87680603e65a28a5a4c61eaf741c3ea654b527 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz4^J1zkch)?uQ_r7C0qiR|Gr;& zXG>qpfdlX3Y9)AFee^@6bADR{Xq}C@D-Kl8z;IXX&gY}nhLubV4F66t*0}(MB)As{ XF$(gSIfYuy0V(lx^>bP0l+XkK7q}(Y literal 0 HcmV?d00001 diff --git a/swarm/examples/album/back.png b/swarm/examples/album/back.png new file mode 100644 index 0000000000000000000000000000000000000000..d05b45d9bd130af04157570b915780d20a9073e6 GIT binary patch literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^5V!L}5I};u8FPv(%iY|@b)X~;(f#<_bs-w_RuxcKiiVetbQP4 z*}A1=Mc>LBtsf}rOiyV6Ze! zM5iLy+{rW6RzIR>W!R(#vQh^m4d&-Is4Un0wK>7GQ&{8F1>EMFXD_?Ddd}sL ze+KTRUma?L`af~adR6K+!HwrlQ`N7_k9w+vIoy=~bDiF=dv4YC+Jg~G6lB9b{8W29 grPWHj+Lht`ZkN^&^K?xCV0bZjy85}Sb4q9e0FytQB>(^b literal 0 HcmV?d00001 diff --git a/swarm/examples/album/cut-left.png b/swarm/examples/album/cut-left.png new file mode 100644 index 0000000000000000000000000000000000000000..f24692daa3880047ac14bce2760e68bb1a8fc770 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4u_bxCyDx`7I;J! zGcYi}0Aa?h)j`=nLG}_)Usv|KT*Bg3M)T_Hdx1h(o-U3d5|?|=1#%s7;9)&*b6Zg9 z_nNqK(o^F6HA1e;XYmrueIUqmSedoFr^1zM*9lJMWsLr7D)Jrrr)fM?4v^>Ny}o04 z2cP!=!+^WWfwg^YQoG*-9@^!&Z($4X^mvW>Kix%XcVuQbzhD-;ExKjaxA%vEwla9S L`njxgN@xNA55Y_F literal 0 HcmV?d00001 diff --git a/swarm/examples/album/cut-mov.png b/swarm/examples/album/cut-mov.png new file mode 100644 index 0000000000000000000000000000000000000000..9744870681bfff97b86fcdb6e42d45aabb6b9358 GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kwj^(N7l!{JxM1({$v_d#0*}aI z1_tgOAk1jDNkSYb$X?><>&kwYn@LEaloaenK>gPabEJkIgIWKTYo zIrvoOf4u_bxCyDx`7I;J! zGcYi}0Aa?h)j`=nLG}_)Usv|KT*BfqCR+|g&H)PfdAc};NL=ncW6js#z{7m;r0Vx6 zGW<>lw|9njvz*AgW5ZjKtvBnC1FwP%%bLUmEYp5CP8XQ)vC*&bmwVs~4Fm38*M)l> h`aE10zGW?~U_QT7t=;`^W+KpJ22WQ%mvv4FO#oH3K5hU2 literal 0 HcmV?d00001 diff --git a/swarm/examples/album/cut-top.png b/swarm/examples/album/cut-top.png new file mode 100644 index 0000000000000000000000000000000000000000..61b0fd59788f663c64b1cccacabf558da28979e8 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4u_bxCyDx`7I;J! zGcYi}0Aa?h)j`=nLG}_)Usv|KTp|o?QZenyM}R_6o-U3d5|?|=S#vcw@UR~E82;kb z*PJ~ID?DcNwzys~d3f;2<%bRjgQ`6%kG2%AS=id>Hb*i|q>^Ka-tuc^h&N!X?vqbITNpfD{an^LB{Ts5w9rWI literal 0 HcmV?d00001 diff --git a/swarm/examples/album/data.json b/swarm/examples/album/data.json new file mode 100644 index 0000000000..20efd5d4f0 --- /dev/null +++ b/swarm/examples/album/data.json @@ -0,0 +1 @@ +{"blur":[600,336],"data":[{"blur":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gADKv/bAEMAAwICAwICAwMDAwQDAwQFCAUFBAQFCgcHBggMCgwMCwoLCw0OEhANDhEOCwsQFhARExQVFRUMDxcYFhQYEhQVFP/bAEMBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIAAcACgMBEQACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAAEBv/EACEQAAAEBQUAAAAAAAAAAAAAAAABAgMFCBESFSIlMVNh/8QAFwEAAwEAAAAAAAAAAAAAAAAAAAIDBf/EAB8RAAIBAgcAAAAAAAAAAAAAAAADAQIFBhESFBYxUv/aAAwDAQACEQMRAD8AQcxWXhlzcAaStB66uHQy8FF4mfXGekzItq56JpcwDN6thb57AvJneQ2FB//Z","file":["imgs/apron.jpg"],"img":["imgs/apron.jpg",[1600,1059]],"thumb":["data:image/jpeg;base64,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",[169,112]]},{"img":["imgs/bekas1.jpg",[1600,1059]],"thumb":["data:image/jpeg;base64,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",[169,112]],"blur":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gADKv/bAEMAAwICAwICAwMDAwQDAwQFCAUFBAQFCgcHBggMCgwMCwoLCw0OEhANDhEOCwsQFhARExQVFRUMDxcYFhQYEhQVFP/bAEMBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIAAcACgMBEQACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAABB//EACAQAAIBBAEFAAAAAAAAAAAAAAECAwAEBREGBxIhMUH/xAAWAQEBAQAAAAAAAAAAAAAAAAAFAwT/xAAeEQABAwQDAAAAAAAAAAAAAAABAAIDBAURIRQyQf/aAAwDAQACEQMRAD8And/zrLQzK9nBFaoGBHnZ19pCW7SmQkHXiPjoGNZhw2h+oefZ2IykignYHb6rIbhUk9lfhxr/2Q=="},{"thumb":["data:image/jpeg;base64,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",[169,112]],"img":["imgs/bekas2.jpg",[1600,1059]],"blur":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gADKv/bAEMAAwICAwICAwMDAwQDAwQFCAUFBAQFCgcHBggMCgwMCwoLCw0OEhANDhEOCwsQFhARExQVFRUMDxcYFhQYEhQVFP/bAEMBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIAAcACgMBEQACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAAEB//EACAQAAECBQUAAAAAAAAAAAAAAAMABAECBxESFCNSYXH/xAAXAQADAQAAAAAAAAAAAAAAAAABAgMF/8QAHhEAAgIBBQEAAAAAAAAAAAAAAQMAAhEEBRQWIVH/2gAMAwEAAhEDEQA/AIkCpj5sFuDSDMTLcJPx6Ulb++tcWOTM86NNj4IqapjrKNmwbeI9if8AI3CXP//Z"},{"stamp":4,"blur":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gADKv/bAEMAAwICAwICAwMDAwQDAwQFCAUFBAQFCgcHBggMCgwMCwoLCw0OEhANDhEOCwsQFhARExQVFRUMDxcYFhQYEhQVFP/bAEMBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIAAcACgMBEQACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAAFBv/EACAQAAAGAAcAAAAAAAAAAAAAAAABAgMEBQYRFBdDUpH/xAAWAQEBAQAAAAAAAAAAAAAAAAAEAwX/xAAeEQABBAEFAAAAAAAAAAAAAAAAAQIDBAYRFBYiMv/aAAwDAQACEQMRAD8AhqfGtrXtElyRqMu6QCPILcSJ31Mp9au5fIpuZNLha8COT2iOygP/2Q==","thumb":["data:image/jpeg;base64,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",[169,112]],"img":["imgs/bekas_blanik.jpg",[1600,1059]]},{"stamp":5,"blur":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gADKv/bAEMAAwICAwICAwMDAwQDAwQFCAUFBAQFCgcHBggMCgwMCwoLCw0OEhANDhEOCwsQFhARExQVFRUMDxcYFhQYEhQVFP/bAEMBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIAAcACgMBEQACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAAEBv/EAB8QAAECBgMAAAAAAAAAAAAAAAECEQADBAUGEiEiM//EABUBAQEAAAAAAAAAAAAAAAAAAAIE/8QAHREAAgIBBQAAAAAAAAAAAAAAAAIBExESITFRYf/aAAwDAQACEQMRAD8ABZMSmVpBYoCB01mM0T6VVt5zALmxwVCcdu6UgCp4Ab0MK6OgW+H/2Q==","img":["imgs/hangar_inside.jpg",[1600,1059]],"thumb":["data:image/jpeg;base64,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",[169,112]]},{"blur":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gADKv/bAEMAAwICAwICAwMDAwQDAwQFCAUFBAQFCgcHBggMCgwMCwoLCw0OEhANDhEOCwsQFhARExQVFRUMDxcYFhQYEhQVFP/bAEMBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIAAcACgMBEQACEQEDEQH/xAAUAAEAAAAAAAAAAAAAAAAAAAAF/8QAIBAAAQIFBQAAAAAAAAAAAAAAAgEEAAMFBhIRFSFhcv/EABUBAQEAAAAAAAAAAAAAAAAAAAQF/8QAHhEAAQMEAwAAAAAAAAAAAAAAAAECAwYREhUWUmL/2gAMAwEAAhEDEQA/AE31/XI4MRZs2R08jyKXOVchTqLzaimvkrSNqY8bIojv6LytNl6+4VyWXoG03o//2Q==","thumb":["data:image/jpeg;base64,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",[169,112]],"img":["imgs/hangar.jpg",[1600,1059]]},{"img":["imgs/ruin.jpg",[1600,1059]],"thumb":["data:image/jpeg;base64,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",[169,112]],"blur":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gADKv/bAEMAAwICAwICAwMDAwQDAwQFCAUFBAQFCgcHBggMCgwMCwoLCw0OEhANDhEOCwsQFhARExQVFRUMDxcYFhQYEhQVFP/bAEMBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIAAcACgMBEQACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAAEB//EAB8QAAICAQQDAAAAAAAAAAAAAAECAwQRAAUIFCJRYf/EABYBAQEBAAAAAAAAAAAAAAAAAAQAAv/EAB8RAAEDAwUAAAAAAAAAAAAAAAABAgQFFSERFDJRUv/aAAwDAQACEQMRAD8AFPyPsWtum72xxTRIPKWBwjD6AdMSuSl4uA26NrhuCZy8hqjSuUvWkUscKYc4HrW7xM7LYRvJ/9k="}],"thumb":{"min":[150,112],"max":[267,200]}} diff --git a/swarm/examples/album/delete.png b/swarm/examples/album/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..dfa04c717b3d5bdd01dd357c5fb6b586793d786f GIT binary patch literal 552 zcmV+@0@wYCP)1-C zVx^7I#v&vT5JeFz3~3V4ynvcc?G%D(EF^wuc43%1J2Th2TbLZU)t>YF&dlsCccuSb zrWrHPFKJfNgrvUghomz}r;@%(+A_wRmf8}S2ELQ7Z@^?3;9g+239zdjpy#geDd4gN zc&>}4cLNv)u1kRDx(1%7!iOkl<*qJ)!IT1f6~J@t#^A3igy(u0DsWhovON93mdEl1 z7`1#w+Ao4X3DtJIZNQp!`7`jq@q{#z``WF{Y~*-4jWNF@$r$qocrEFnq?)8TW6X`^ z?Fh}1G$ZN61#CuIF9XKOv;%FPvhO0Z)_p7d5eWgT0=39aEhNl2@CDBkf&fc6J7LXu zn>gbv&)BQE4Xw;lhm*_UMp9Sej7Ao4(8}ub62M}@oW@5H*ocHa;+%g5bZ^XB<_*bv z6V69Mbr0x0{->iJnf1n3U^tZUz*nuyD>Uv-NCQP@nDQ#F1D`yW4?t%^0spHw3%!Um zZ&kOzSYd&z#CC-RHiPi2z$4&i1@IHBBZ`%H2K+7&eg!;Df?FjX1IH!6kAUH(;8uwq qTK`+#eJ8k6q_Pi_`|)Y^PwOweFYLKGQkMJx0000P)LQib`tZe2rU=L zrR~^CNLLcGtBJWyv{wp`#GTkn$d{s-<;4A6bbcs23h2gBA)tYIEF{7Yz4yIWC^6vq zbBwvmub5N(_|)*I#7|r#PTb@ILthdel^DZKBK#VoUm6~jn81A$ev9#M36Dz5<}czS xepU6n`k|C1gs_gjEX)3d5H_lMYHDg~o&acrlchZ9WZwV)002ovPDHLkV1o27v$g;L literal 0 HcmV?d00001 diff --git a/swarm/examples/album/download.png b/swarm/examples/album/download.png new file mode 100644 index 0000000000000000000000000000000000000000..6a160c8d524f29c4de63da98eac050f41fdbc170 GIT binary patch literal 422 zcmV;X0a^ZuP)L{SihzZHaHttce6Qiw*QlXZ>$=ar-RwzD-n=>Y%{w!9DynpkXJ8SyOND{( zyax6HQ?#|$5bpyu;1rnsbJ`Y~DuF55+GP|s)Hn4-os7Qyao8;`fNfw3sOMVl1s}O& znSxbd8W>LU@4y4_`72vuhPinbaxmj);~%$IKm%BgayzuvsL)|2t|hML`*F*K=zGE3 z40Asni{3{Qy$Xzn*fIqHn!rkdb{@r>WiMq&J?^klyDAk`;0d@XaPSDM(^~8kU?Fqn z09XaAsJA6rJ_NRaiO}L)q#XfEz!yN--HTaQ??ans>PW2jQV?|}%(Rwj-^4+;1paU= Q5C8xG07*qoM6N<$g70st&Hw-a literal 0 HcmV?d00001 diff --git a/swarm/examples/album/eye.png b/swarm/examples/album/eye.png new file mode 100644 index 0000000000000000000000000000000000000000..562942a58062015c716132f39fd1dfefaf44fde9 GIT binary patch literal 525 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�U^4-pN)4aqy@k)i6A4d*Yq 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

M@rT;E{K(`&p!NM*N2GwNY%qZ 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

9Qhd^qHwDSTC$)8B#-ScQ%8Y#? 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 literal 0 HcmV?d00001 diff --git a/swarm/examples/album/imgs/bekas1.jpg b/swarm/examples/album/imgs/bekas1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8e38e5f55bd8abb0b681173d42c6ed27ae0b2998 GIT binary patch literal 554726 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 I*T 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%0DRZ+zyB=Mn1HHo0Jiynv){&ytfui|x} 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%0DLB-E>Bk4d$`)qt%SNh1-WvaoR7hf(c@ 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