This commit is contained in:
Viktor Trón 2015-12-14 06:14:16 +00:00
commit b46e84745e
195 changed files with 28108 additions and 2381 deletions

2
Godeps/Godeps.json generated
View file

@ -55,7 +55,7 @@
}, },
{ {
"ImportPath": "github.com/nsf/termbox-go", "ImportPath": "github.com/nsf/termbox-go",
"Rev": "675ffd907b7401b8a709a5ef2249978af5616bb2" "Rev": "ca2931516914070bb7f934c83e408689cea8dfb7"
}, },
{ {
"ImportPath": "github.com/pborman/uuid", "ImportPath": "github.com/pborman/uuid",

View file

@ -16,6 +16,8 @@ There are also some interesting projects using termbox-go:
- [httopd](https://github.com/verdverm/httopd) is top for httpd logs. - [httopd](https://github.com/verdverm/httopd) is top for httpd logs.
- [mop](https://github.com/michaeldv/mop) is stock market tracker for hackers. - [mop](https://github.com/michaeldv/mop) is stock market tracker for hackers.
- [termui](https://github.com/gizak/termui) is a terminal dashboard. - [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 ### API reference
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go) [godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)

View file

@ -351,7 +351,7 @@ func PollEvent() Event {
// terminal's window size in characters). But it doesn't always match the size // 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 // 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. // 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 return termw, termh
} }
@ -380,6 +380,12 @@ func SetInputMode(mode InputMode) InputMode {
if mode == InputCurrent { if mode == InputCurrent {
return input_mode return input_mode
} }
if mode&(InputEsc|InputAlt) == 0 {
mode |= InputEsc
}
if mode&(InputEsc|InputAlt) == InputEsc|InputAlt {
mode &^= InputAlt
}
if mode&InputMouse != 0 { if mode&InputMouse != 0 {
out.WriteString(funcs[t_enter_mouse]) out.WriteString(funcs[t_enter_mouse])
} else { } else {
@ -391,6 +397,7 @@ func SetInputMode(mode InputMode) InputMode {
} }
// Sets the termbox output mode. Termbox has four output options: // Sets the termbox output mode. Termbox has four output options:
//
// 1. OutputNormal => [1..8] // 1. OutputNormal => [1..8]
// This mode provides 8 different colors: // This mode provides 8 different colors:
// black, red, green, yellow, blue, magenta, cyan, white // black, red, green, yellow, blue, magenta, cyan, white
@ -402,10 +409,10 @@ func SetInputMode(mode InputMode) InputMode {
// //
// 2. Output256 => [1..256] // 2. Output256 => [1..256]
// In this mode you can leverage the 256 terminal mode: // In this mode you can leverage the 256 terminal mode:
// 0x00 - 0x07: the 8 colors as in OutputNormal // 0x01 - 0x08: the 8 colors as in OutputNormal
// 0x08 - 0x0f: Color* | AttrBold // 0x09 - 0x10: Color* | AttrBold
// 0x10 - 0xe7: 216 different colors // 0x11 - 0xe8: 216 different colors
// 0xe8 - 0xff: 24 different shades of grey // 0xe9 - 0x1ff: 24 different shades of grey
// //
// Example usage: // Example usage:
// SetCell(x, y, '@', 184, 240); // 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. // This mode supports the 3rd range of the 256 mode only.
// But you dont need to provide an offset. // But you dont need to provide an offset.
// //
// 4. OutputGrayscale => [1..24] // 4. OutputGrayscale => [1..26]
// This mode supports the 4th range of the 256 mode only. // 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. // 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. // `go run _demos/output.go` to see its impact on your terminal.
// //

View file

@ -80,6 +80,10 @@ func Close() {
// stop event producer // stop event producer
cancel_comm <- true cancel_comm <- true
set_event(interrupt) set_event(interrupt)
select {
case <-input_comm:
default:
}
<-cancel_done_comm <-cancel_done_comm
set_console_cursor_info(out, &orig_cursor_info) set_console_cursor_info(out, &orig_cursor_info)

View file

@ -1,6 +1,8 @@
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs syscalls.go // cgo -godefs syscalls.go
// +build !amd64
package termbox package termbox
type syscall_Termios struct { type syscall_Termios struct {

View file

@ -72,6 +72,12 @@ var (
input_comm = make(chan input_event) input_comm = make(chan input_event)
interrupt_comm = make(chan struct{}) interrupt_comm = make(chan struct{})
intbuf = make([]byte, 0, 16) 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) { func write_cursor(x, y int) {
@ -171,17 +177,17 @@ func send_attr(fg, bg Attribute) {
case OutputGrayscale: case OutputGrayscale:
fgcol = fg & 0x1F fgcol = fg & 0x1F
bgcol = bg & 0x1F bgcol = bg & 0x1F
if fgcol > 24 { if fgcol > 26 {
fgcol = ColorDefault fgcol = ColorDefault
} }
if bgcol > 24 { if bgcol > 26 {
bgcol = ColorDefault bgcol = ColorDefault
} }
if fgcol != ColorDefault { if fgcol != ColorDefault {
fgcol += 0xe8 fgcol = grayscale[fgcol]
} }
if bgcol != ColorDefault { if bgcol != ColorDefault {
bgcol += 0xe8 bgcol = grayscale[bgcol]
} }
default: default:
fgcol = fg & 0x0F fgcol = fg & 0x0F

View file

@ -129,7 +129,7 @@ func create_console_screen_buffer() (h syscall.Handle, err error) {
err = syscall.EINVAL 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) { 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 err = syscall.EINVAL
} }
} }
return syscall.Handle(r0), nil return syscall.Handle(r0), err
} }
func wait_for_multiple_objects(objects []syscall.Handle) (err error) { func wait_for_multiple_objects(objects []syscall.Handle) (err error) {

View file

@ -3,14 +3,16 @@
# don't need to bother with make. # don't need to bother with make.
.PHONY: geth geth-cross evm all test travis-test-with-coverage xgo clean .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-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-darwin geth-darwin-386 geth-darwin-amd64
.PHONY: geth-windows geth-windows-386 geth-windows-amd64 .PHONY: geth-windows geth-windows-386 geth-windows-amd64
.PHONY: geth-android geth-android-16 geth-android-21 .PHONY: geth-android geth-ios
GOBIN = build/bin 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
MODE ?= default
GO ?= latest GO ?= latest
geth: geth:
@ -18,70 +20,85 @@ geth:
@echo "Done building." @echo "Done building."
@echo "Run \"$(GOBIN)/geth\" to launch geth." @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:" @echo "Full cross compilation done:"
@ls -l $(GOBIN)/geth-* @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:" @echo "Linux cross compilation done:"
@ls -l $(GOBIN)/geth-linux-* @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
@echo "Linux ARM cross compilation done:"
@ls -l $(GOBIN)/geth-linux-* | grep arm
geth-linux-386: xgo 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:" @echo "Linux 386 cross compilation done:"
@ls -l $(GOBIN)/geth-linux-* | grep 386 @ls -l $(GOBIN)/geth-linux-* | grep 386
geth-linux-amd64: xgo 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:" @echo "Linux amd64 cross compilation done:"
@ls -l $(GOBIN)/geth-linux-* | grep amd64 @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 geth-linux-arm64
@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-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:" @echo "Darwin cross compilation done:"
@ls -l $(GOBIN)/geth-darwin-* @ls -l $(GOBIN)/geth-darwin-*
geth-darwin-386: xgo 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:" @echo "Darwin 386 cross compilation done:"
@ls -l $(GOBIN)/geth-darwin-* | grep 386 @ls -l $(GOBIN)/geth-darwin-* | grep 386
geth-darwin-amd64: xgo 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:" @echo "Darwin amd64 cross compilation done:"
@ls -l $(GOBIN)/geth-darwin-* | grep amd64 @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:" @echo "Windows cross compilation done:"
@ls -l $(GOBIN)/geth-windows-* @ls -l $(GOBIN)/geth-windows-*
geth-windows-386: xgo 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:" @echo "Windows 386 cross compilation done:"
@ls -l $(GOBIN)/geth-windows-* | grep 386 @ls -l $(GOBIN)/geth-windows-* | grep 386
geth-windows-amd64: xgo 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:" @echo "Windows amd64 cross compilation done:"
@ls -l $(GOBIN)/geth-windows-* | grep amd64 @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:" @echo "Android cross compilation done:"
@ls -l $(GOBIN)/geth-android-* @ls -l $(GOBIN)/geth-android-*
geth-android-16: xgo geth-ios: 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) --depsargs=--disable-assembly --targets=ios/* -v $(shell build/flags.sh) ./cmd/geth
@echo "Android 16 cross compilation done:" @echo "iOS cross compilation done:"
@ls -l $(GOBIN)/geth-android-16-* @ls -l $(GOBIN)/geth-ios-*
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
@echo "Android 21 cross compilation done:"
@ls -l $(GOBIN)/geth-android-21-*
evm: evm:
build/env.sh $(GOROOT)/bin/go install -v $(shell build/flags.sh) ./cmd/evm build/env.sh $(GOROOT)/bin/go install -v $(shell build/flags.sh) ./cmd/evm

View file

@ -44,6 +44,10 @@ type Account struct {
Address common.Address Address common.Address
} }
func (acc *Account) MarshalJSON() ([]byte, error) {
return []byte(`"` + acc.Address.Hex() + `"`), nil
}
type Manager struct { type Manager struct {
keyStore crypto.KeyStore keyStore crypto.KeyStore
unlocked map[common.Address]*unlocked unlocked map[common.Address]*unlocked
@ -87,11 +91,32 @@ func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error)
return signature, err 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. // Unlock unlocks the given account indefinitely.
func (am *Manager) Unlock(addr common.Address, keyAuth string) error { func (am *Manager) Unlock(addr common.Address, keyAuth string) error {
return am.TimedUnlock(addr, keyAuth, 0) 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 // TimedUnlock unlocks the account with the given address. The account
// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account // stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
// until the program exits. // until the program exits.

View file

@ -68,7 +68,7 @@ func TestTimedUnlock(t *testing.T) {
} }
// Signing fails again after automatic locking // Signing fails again after automatic locking
time.Sleep(150 * time.Millisecond) time.Sleep(350 * time.Millisecond)
_, err = am.Sign(a1, testSigData) _, err = am.Sign(a1, testSigData)
if err != ErrLocked { if err != ErrLocked {
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)

View file

@ -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 <http://www.gnu.org/licenses/>.
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 <path-to-test-file> [ <test-name> [ "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)
}

View file

@ -24,9 +24,8 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"sort" "sort"
"strings"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -34,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/common/registrar" "github.com/ethereum/go-ethereum/common/registrar"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
re "github.com/ethereum/go-ethereum/jsre" 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"
"github.com/ethereum/go-ethereum/rpc/api" "github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/codec"
@ -77,7 +77,7 @@ func (r dumbterm) AppendHistory(string) {}
type jsre struct { type jsre struct {
re *re.JSRE re *re.JSRE
ethereum *eth.Ethereum stack *node.Node
xeth *xeth.XEth xeth *xeth.XEth
wait chan *big.Int wait chan *big.Int
ps1 string ps1 string
@ -176,19 +176,21 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str
return js return js
} }
func newJSRE(ethereum *eth.Ethereum, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre { func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre {
js := &jsre{ethereum: ethereum, ps1: "> "} js := &jsre{stack: stack, ps1: "> "}
// set default cors domain used by startRpc from CLI flag // set default cors domain used by startRpc from CLI flag
js.corsDomain = corsDomain js.corsDomain = corsDomain
if f == nil { if f == nil {
f = js f = js
} }
js.xeth = xeth.New(ethereum, f) js.xeth = xeth.New(stack, f)
js.wait = js.xeth.UpdateState() js.wait = js.xeth.UpdateState()
js.client = client js.client = client
if clt, ok := js.client.(*comms.InProcClient); ok { 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...)) clt.Initialize(api.Merge(offeredApis...))
} else {
utils.Fatalf("Unable to offer apis: %v", err)
} }
} }
@ -202,14 +204,14 @@ func newJSRE(ethereum *eth.Ethereum, docRoot, corsDomain string, client comms.Et
js.prompter = dumbterm{bufio.NewReader(os.Stdin)} js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
} else { } else {
lr := liner.NewLiner() 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) lr.SetCtrlCAborts(true)
js.loadAutoCompletion() js.loadAutoCompletion()
lr.SetWordCompleter(apiWordCompleter) lr.SetWordCompleter(apiWordCompleter)
lr.SetTabCompletionStyle(liner.TabPrints) lr.SetTabCompletionStyle(liner.TabPrints)
js.prompter = lr js.prompter = lr
js.atexit = func() { 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() lr.Close()
close(js.wait) close(js.wait)
} }
@ -244,11 +246,11 @@ func (self *jsre) batch(statement string) {
func (self *jsre) welcome() { func (self *jsre) welcome() {
self.re.Run(` self.re.Run(`
(function () { (function () {
console.log('instance: ' + web3.version.client); console.log('instance: ' + web3.version.node);
console.log(' datadir: ' + admin.datadir);
console.log("coinbase: " + eth.coinbase); console.log("coinbase: " + eth.coinbase);
var ts = 1000 * eth.getBlock(eth.blockNumber).timestamp; var ts = 1000 * eth.getBlock(eth.blockNumber).timestamp;
console.log("at block: " + eth.blockNumber + " (" + new Date(ts) + ")"); console.log("at block: " + eth.blockNumber + " (" + new Date(ts) + ")");
console.log(' datadir: ' + admin.datadir);
})(); })();
`) `)
if modules, err := self.supportedApis(); err == nil { if modules, err := self.supportedApis(); err == nil {
@ -257,7 +259,7 @@ func (self *jsre) welcome() {
loadedModules = append(loadedModules, fmt.Sprintf("%s:%s", api, version)) loadedModules = append(loadedModules, fmt.Sprintf("%s:%s", api, version))
} }
sort.Strings(loadedModules) sort.Strings(loadedModules)
fmt.Println("modules:", strings.Join(loadedModules, " "))
} }
} }
@ -276,7 +278,7 @@ func (js *jsre) apiBindings(f xeth.Frontend) error {
apiNames = append(apiNames, a) 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 { if err != nil {
utils.Fatalf("Unable to determine supported api's: %v", err) utils.Fatalf("Unable to determine supported api's: %v", err)
} }
@ -299,12 +301,12 @@ func (js *jsre) apiBindings(f xeth.Frontend) error {
utils.Fatalf("Error loading web3.js: %v", err) 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 { if err != nil {
utils.Fatalf("Error requiring web3: %v", err) 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 { if err != nil {
utils.Fatalf("Error setting web3 provider: %v", err) utils.Fatalf("Error setting web3 provider: %v", err)
} }
@ -324,12 +326,28 @@ func (js *jsre) apiBindings(f xeth.Frontend) error {
} }
_, err = js.re.Run(shortcuts) _, err = js.re.Run(shortcuts)
if err != nil { if err != nil {
utils.Fatalf("Error setting namespaces: %v", err) utils.Fatalf("Error setting namespaces: %v", err)
} }
js.re.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`) 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 return nil
} }
@ -342,8 +360,14 @@ func (self *jsre) AskPassword() (string, bool) {
} }
func (self *jsre) ConfirmTransaction(tx string) bool { func (self *jsre) ConfirmTransaction(tx string) bool {
if self.ethereum.NatSpec { // Retrieve the Ethereum instance from the node
notice := natspec.GetNotice(self.xeth, tx, self.ethereum.HTTPClient()) var ethereum *eth.Ethereum
if err := self.stack.Service(&ethereum); 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) fmt.Println(notice)
answer, _ := self.Prompt("Confirm Transaction [y/n]") answer, _ := self.Prompt("Confirm Transaction [y/n]")
return strings.HasPrefix(strings.Trim(answer, " "), "y") return strings.HasPrefix(strings.Trim(answer, " "), "y")
@ -359,7 +383,11 @@ func (self *jsre) UnlockAccount(addr []byte) bool {
return false return false
} }
// TODO: allow retry // TODO: allow retry
if err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil { var ethereum *eth.Ethereum
if err := self.stack.Service(&ethereum); err != nil {
return false
}
if err := ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil {
return false return false
} else { } else {
fmt.Println("Account is now unlocked for this session.") fmt.Println("Account is now unlocked for this session.")

83
cmd/geth/js_bzz_test.go Normal file
View file

@ -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
// }
})
}

View file

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb" "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/codec"
"github.com/ethereum/go-ethereum/rpc/comms" "github.com/ethereum/go-ethereum/rpc/comms"
) )
@ -66,7 +67,10 @@ type testjethre struct {
} }
func (self *testjethre) UnlockAccount(acc []byte) bool { func (self *testjethre) UnlockAccount(acc []byte) bool {
err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "") var ethereum *eth.Ethereum
self.stack.Service(&ethereum)
err := ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "")
if err != nil { if err != nil {
panic("unable to unlock") panic("unable to unlock")
} }
@ -74,67 +78,79 @@ func (self *testjethre) UnlockAccount(acc []byte) bool {
} }
func (self *testjethre) ConfirmTransaction(tx string) bool { func (self *testjethre) ConfirmTransaction(tx string) bool {
if self.ethereum.NatSpec { var ethereum *eth.Ethereum
self.stack.Service(&ethereum)
if ethereum.NatSpec {
self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client) self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client)
} }
return true return true
} }
func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) { func testJEthRE(t *testing.T) (string, *testjethre, *node.Node) {
return testREPL(t, nil) return testREPL(t, nil)
} }
func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *eth.Ethereum) { func testREPL(t *testing.T, config func(*node.Node)) (string, *testjethre, *node.Node) {
tmp, err := ioutil.TempDir("", "geth-test") tmp, err := ioutil.TempDir("", "geth-test")
if err != nil { if err != nil {
t.Fatal(err) 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() db, _ := ethdb.NewMemDatabase()
core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)}) core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)})
ks := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore")) coinbase := common.HexToAddress(testAddress)
am := accounts.NewManager(ks)
conf := &eth.Config{ ethConf := &eth.Config{
NodeKey: testNodeKey, TestGenesisState: db,
DataDir: tmp, AccountManager: accman,
AccountManager: am,
MaxPeers: 0,
Name: "test",
DocRoot: "/", DocRoot: "/",
SolcPath: testSolcPath, SolcPath: testSolcPath,
Etherbase: coinbase,
PowTest: true, PowTest: true,
NewDB: func(path string) (ethdb.Database, error) { return db, nil },
} }
if config != nil { if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil {
config(conf) t.Fatalf("failed to register ethereum protocol: %v", err)
} }
ethereum, err := eth.New(conf) // Initialize all the keys for testing
if err != nil {
t.Fatal("%v", err)
}
keyb, err := crypto.HexToECDSA(testKey) keyb, err := crypto.HexToECDSA(testKey)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
key := crypto.NewKeyFromECDSA(keyb) key := crypto.NewKeyFromECDSA(keyb)
err = ks.StoreKey(key, "") if err := keystore.StoreKey(key, ""); err != nil {
if err != nil { t.Fatal(err)
}
if err := accman.Unlock(key.Address, ""); err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = am.Unlock(key.Address, "") // tests can register services here
if err != nil { if config != nil {
t.Fatal(err) 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)
}
var ethereum *eth.Ethereum
stack.Service(&ethereum)
assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
client := comms.NewInProcClient(codec.JSON) client := comms.NewInProcClient(codec.JSON)
tf := &testjethre{client: ethereum.HTTPClient()} tf := &testjethre{client: ethereum.HTTPClient()}
repl := newJSRE(ethereum, assetPath, "", client, false, tf) repl := newJSRE(stack, assetPath, "", client, false, tf)
tf.jsre = repl tf.jsre = repl
return tmp, tf, ethereum return tmp, tf, stack
} }
func TestNodeInfo(t *testing.T) { func TestNodeInfo(t *testing.T) {
@ -151,16 +167,13 @@ func TestNodeInfo(t *testing.T) {
} }
func TestAccounts(t *testing.T) { func TestAccounts(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t) tmp, repl, node := testJEthRE(t)
if err := ethereum.Start(); err != nil { defer node.Stop()
t.Fatalf("error starting ethereum: %v", err)
}
defer ethereum.Stop()
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`) checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`)
checkEvalJSON(t, repl, `eth.coinbase`, `"`+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 { if err != nil {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
@ -174,11 +187,8 @@ func TestAccounts(t *testing.T) {
} }
func TestBlockChain(t *testing.T) { func TestBlockChain(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t) tmp, repl, node := testJEthRE(t)
if err := ethereum.Start(); err != nil { defer node.Stop()
t.Fatalf("error starting ethereum: %v", err)
}
defer ethereum.Stop()
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
// get current block dump before export/import. // get current block dump before export/import.
val, err := repl.re.Run("JSON.stringify(debug.dumpBlock(eth.blockNumber))") val, err := repl.re.Run("JSON.stringify(debug.dumpBlock(eth.blockNumber))")
@ -196,6 +206,8 @@ func TestBlockChain(t *testing.T) {
tmpfile := filepath.Join(extmp, "export.chain") tmpfile := filepath.Join(extmp, "export.chain")
tmpfileq := strconv.Quote(tmpfile) tmpfileq := strconv.Quote(tmpfile)
var ethereum *eth.Ethereum
node.Service(&ethereum)
ethereum.BlockChain().Reset() ethereum.BlockChain().Reset()
checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`) checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`)
@ -209,22 +221,15 @@ func TestBlockChain(t *testing.T) {
} }
func TestMining(t *testing.T) { func TestMining(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t) tmp, repl, node := testJEthRE(t)
if err := ethereum.Start(); err != nil { defer node.Stop()
t.Fatalf("error starting ethereum: %v", err)
}
defer ethereum.Stop()
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
checkEvalJSON(t, repl, `eth.mining`, `false`) checkEvalJSON(t, repl, `eth.mining`, `false`)
} }
func TestRPC(t *testing.T) { func TestRPC(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t) tmp, repl, node := testJEthRE(t)
if err := ethereum.Start(); err != nil { defer node.Stop()
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
checkEvalJSON(t, repl, `admin.startRPC("127.0.0.1", 5004, "*", "web3,eth,net")`, `true`) checkEvalJSON(t, repl, `admin.startRPC("127.0.0.1", 5004, "*", "web3,eth,net")`, `true`)
@ -234,12 +239,8 @@ func TestCheckTestAccountBalance(t *testing.T) {
t.Skip() // i don't think it tests the correct behaviour here. it's actually testing 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 // 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 // and i have no means to fix this, sorry - @obscuren
tmp, repl, ethereum := testJEthRE(t) tmp, repl, node := testJEthRE(t)
if err := ethereum.Start(); err != nil { defer node.Stop()
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
repl.re.Run(`primary = "` + testAddress + `"`) repl.re.Run(`primary = "` + testAddress + `"`)
@ -247,12 +248,8 @@ func TestCheckTestAccountBalance(t *testing.T) {
} }
func TestSignature(t *testing.T) { func TestSignature(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t) tmp, repl, node := testJEthRE(t)
if err := ethereum.Start(); err != nil { defer node.Stop()
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
val, err := repl.re.Run(`eth.sign("` + testAddress + `", "` + testHash + `")`) val, err := repl.re.Run(`eth.sign("` + testAddress + `", "` + testHash + `")`)
@ -275,10 +272,7 @@ func TestSignature(t *testing.T) {
func TestContract(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") 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) coinbase := common.HexToAddress(testAddress)
tmp, repl, ethereum := testREPL(t, func(conf *eth.Config) { tmp, repl, ethereum := testREPL(t, nil)
conf.Etherbase = coinbase
conf.PowTest = true
})
if err := ethereum.Start(); err != nil { if err := ethereum.Start(); err != nil {
t.Errorf("error starting ethereum: %v", err) t.Errorf("error starting ethereum: %v", err)
return return
@ -443,7 +437,10 @@ multiply7 = Multiply7.at(contractaddress);
} }
func pendingTransactions(repl *testjethre, t *testing.T) (txc int64, err error) { func pendingTransactions(repl *testjethre, t *testing.T) (txc int64, err error) {
txs := repl.ethereum.TxPool().GetTransactions() var ethereum *eth.Ethereum
repl.stack.Service(&ethereum)
txs := ethereum.TxPool().GetTransactions()
return int64(len(txs)), nil return int64(len(txs)), nil
} }
@ -468,12 +465,15 @@ func processTxs(repl *testjethre, t *testing.T, expTxc int) bool {
t.Errorf("incorrect number of pending transactions, expected %v, got %v", expTxc, txc) t.Errorf("incorrect number of pending transactions, expected %v, got %v", expTxc, txc)
return false return false
} }
err = repl.ethereum.StartMining(runtime.NumCPU(), "") var ethereum *eth.Ethereum
repl.stack.Service(&ethereum)
err = ethereum.StartMining(runtime.NumCPU(), "")
if err != nil { if err != nil {
t.Errorf("unexpected error mining: %v", err) t.Errorf("unexpected error mining: %v", err)
return false return false
} }
defer repl.ethereum.StopMining() defer ethereum.StopMining()
timer := time.NewTimer(100 * time.Second) timer := time.NewTimer(100 * time.Second)
height := new(big.Int).Add(repl.xeth.CurrentBlock().Number(), big.NewInt(1)) height := new(big.Int).Add(repl.xeth.CurrentBlock().Number(), big.NewInt(1))

View file

@ -30,16 +30,12 @@ import (
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/ethereum/ethash" "github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils" "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/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/codec"
@ -68,22 +64,9 @@ func init() {
} }
app = utils.NewApp(Version, "the go-ethereum command line interface") 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.HideVersion = true // we have a command to print the version
app.Commands = []cli.Command{ 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 <hex> recovers by hash
`,
},
blocktestCommand,
importCommand, importCommand,
exportCommand, exportCommand,
upgradedbCommand, upgradedbCommand,
@ -285,7 +268,7 @@ This command allows to open a console on a running geth node.
`, `,
}, },
{ {
Action: execJSFiles, Action: execScripts,
Name: "js", Name: "js",
Usage: `executes the given JavaScript files in the Geth JavaScript VM`, Usage: `executes the given JavaScript files in the Geth JavaScript VM`,
Description: ` Description: `
@ -328,8 +311,12 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.IPCDisabledFlag, utils.IPCDisabledFlag,
utils.IPCApiFlag, utils.IPCApiFlag,
utils.IPCPathFlag, utils.IPCPathFlag,
utils.IPCExperimental,
utils.ExecFlag, utils.ExecFlag,
utils.WhisperEnabledFlag, utils.WhisperEnabledFlag,
utils.SwarmConfigPathFlag,
utils.SwarmAccountAddrFlag,
utils.ChequebookAddrFlag,
utils.DevModeFlag, utils.DevModeFlag,
utils.TestNetFlag, utils.TestNetFlag,
utils.VMDebugFlag, utils.VMDebugFlag,
@ -376,14 +363,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 { func makeDefaultExtra() []byte {
var clientInfo = struct { var clientInfo = struct {
Version uint Version uint
@ -404,18 +383,13 @@ func makeDefaultExtra() []byte {
return extra return extra
} }
func run(ctx *cli.Context) { // geth is the main entry point into the system if no special subcommand is ran.
cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx) // It creates a default node based on the command line arguments and runs it in
cfg.ExtraData = makeExtra(ctx) // blocking mode, waiting for it to be shut down.
func geth(ctx *cli.Context) {
ethereum, err := eth.New(cfg) node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx)
if err != nil { startNode(ctx, node)
utils.Fatalf("%v", err) node.Wait()
}
startEth(ctx, ethereum)
// this blocks the thread
ethereum.WaitForShutdown()
} }
func attach(ctx *cli.Context) { func attach(ctx *cli.Context) {
@ -449,156 +423,83 @@ 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) { func console(ctx *cli.Context) {
cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx) // Create and start the node based on the CLI flags
cfg.ExtraData = makeExtra(ctx) node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx)
ethereum, err := eth.New(cfg) startNode(ctx, node)
if err != nil {
utils.Fatalf("%v", err)
}
// Attach to the newly started node, and either execute script or become interactive
client := comms.NewInProcClient(codec.JSON) client := comms.NewInProcClient(codec.JSON)
repl := newJSRE(node,
startEth(ctx, ethereum)
repl := newJSRE(
ethereum,
ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.JSpathFlag.Name),
ctx.GlobalString(utils.RPCCORSDomainFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
client, client, true, nil)
true,
nil,
)
if ctx.GlobalString(utils.ExecFlag.Name) != "" { if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
repl.batch(ctx.GlobalString(utils.ExecFlag.Name)) repl.batch(script)
} else { } else {
repl.welcome() repl.welcome()
repl.interactive() repl.interactive()
} }
node.Stop()
ethereum.Stop()
ethereum.WaitForShutdown()
} }
func execJSFiles(ctx *cli.Context) { // execScripts starts a new geth node based on the CLI flags, and executes each
cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx) // of the JavaScript files specified as command arguments.
ethereum, err := eth.New(cfg) func execScripts(ctx *cli.Context) {
if err != nil { // Create and start the node based on the CLI flags
utils.Fatalf("%v", err) 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) client := comms.NewInProcClient(codec.JSON)
startEth(ctx, ethereum) repl := newJSRE(node,
repl := newJSRE(
ethereum,
ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.JSpathFlag.Name),
ctx.GlobalString(utils.RPCCORSDomainFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
client, client, false, nil)
false,
nil,
)
for _, file := range ctx.Args() { for _, file := range ctx.Args() {
repl.exec(file) repl.exec(file)
} }
node.Stop()
ethereum.Stop()
ethereum.WaitForShutdown()
} }
func unlockAccount(ctx *cli.Context, am *accounts.Manager, addr string, i int, inputpassphrases []string) (addrHex, auth string, passphrases []string) { // startNode unlocks any requested accounts the boots up the system node
var err error // starts all registered protocols and
passphrases = inputpassphrases // starts the RPC/IPC interfaces and the
addrHex, err = utils.ParamToAddress(addr, am) // miner.
if err == nil { func startNode(ctx *cli.Context, stack *node.Node) {
// Attempt to unlock the account 3 times // Start up the node itself
attempts := 3 utils.StartNode(stack)
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
}
}
}
if err != nil {
utils.Fatalf("Unlock account '%s' (%v) failed: %v", addr, addrHex, err)
}
fmt.Printf("Account '%s' (%v) unlocked.\n", addr, addrHex)
return
}
func blockRecovery(ctx *cli.Context) {
if len(ctx.Args()) < 1 {
glog.Fatal("recover requires block number or hash")
}
arg := ctx.Args().First()
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
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)
}
}
// Start auxiliary services if enabled. // Start auxiliary services if enabled.
if !ctx.GlobalBool(utils.IPCDisabledFlag.Name) { if !ctx.GlobalBool(utils.IPCDisabledFlag.Name) {
if err := utils.StartIPC(eth, ctx); err != nil { if err := utils.StartIPC(stack, ctx); err != nil {
utils.Fatalf("Error string IPC: %v", err) utils.Fatalf("Failed to start IPC: %v", err)
} }
} }
if ctx.GlobalBool(utils.RPCEnabledFlag.Name) { if ctx.GlobalBool(utils.RPCEnabledFlag.Name) {
if err := utils.StartRPC(eth, ctx); err != nil { if err := utils.StartRPC(stack, ctx); err != nil {
utils.Fatalf("Error starting RPC: %v", err) utils.Fatalf("Failed to start RPC: %v", err)
} }
} }
var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); err != nil {
utils.Fatalf("ethereum service not running: %v", err)
}
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) { if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
err := eth.StartMining( if err := ethereum.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name), ctx.GlobalString(utils.MiningGPUFlag.Name)); err != nil {
ctx.GlobalInt(utils.MinerThreadsFlag.Name), utils.Fatalf("Failed to start mining: %v", err)
ctx.GlobalString(utils.MiningGPUFlag.Name))
if err != nil {
utils.Fatalf("%v", err)
} }
} }
} }
func accountList(ctx *cli.Context) { func accountList(ctx *cli.Context) {
am := utils.MakeAccountManager(ctx) accman := utils.MakeAccountManager(ctx)
accts, err := am.Accounts() accts, err := accman.Accounts()
if err != nil { if err != nil {
utils.Fatalf("Could not list accounts: %v", err) utils.Fatalf("Could not list accounts: %v", err)
} }
@ -607,67 +508,29 @@ func accountList(ctx *cli.Context) {
} }
} }
func getPassPhrase(ctx *cli.Context, desc string, confirmation bool, i int, inputpassphrases []string) (passphrase string, passphrases []string) { // accountCreate creates a new account into the keystore defined by the CLI flags.
passfile := ctx.GlobalString(utils.PasswordFileFlag.Name)
if len(passfile) == 0 {
fmt.Println(desc)
auth, err := utils.PromptPassword("Passphrase: ", true)
if err != nil {
utils.Fatalf("%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]
}
}
return
}
func accountCreate(ctx *cli.Context) { func accountCreate(ctx *cli.Context) {
am := utils.MakeAccountManager(ctx) accman := 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) 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))
acct, err := am.NewAccount(passphrase)
account, err := accman.NewAccount(password)
if err != nil { 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) { func accountUpdate(ctx *cli.Context) {
am := utils.MakeAccountManager(ctx) if len(ctx.Args()) == 0 {
arg := ctx.Args().First() utils.Fatalf("No accounts specified to update")
if len(arg) == 0 {
utils.Fatalf("account address or index must be given as argument")
} }
accman := utils.MakeAccountManager(ctx)
addr, authFrom, passphrases := unlockAccount(ctx, am, arg, 0, nil) account, oldPassword := utils.UnlockAccount(ctx, accman, ctx.Args().First(), 0, nil)
authTo, _ := getPassPhrase(ctx, "Please give a new password. Do not forget this password.", true, 0, passphrases) newPassword := utils.GetPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
err := am.Update(common.HexToAddress(addr), authFrom, authTo) if err := accman.Update(account, oldPassword, newPassword); err != nil {
if err != nil {
utils.Fatalf("Could not update the account: %v", err) utils.Fatalf("Could not update the account: %v", err)
} }
} }
@ -682,10 +545,10 @@ func importWallet(ctx *cli.Context) {
utils.Fatalf("Could not read wallet file: %v", err) utils.Fatalf("Could not read wallet file: %v", err)
} }
am := utils.MakeAccountManager(ctx) accman := utils.MakeAccountManager(ctx)
passphrase, _ := getPassPhrase(ctx, "", false, 0, nil) passphrase := utils.GetPassPhrase("", false, 0, utils.MakePasswordList(ctx))
acct, err := am.ImportPreSaleKey(keyJson, passphrase) acct, err := accman.ImportPreSaleKey(keyJson, passphrase)
if err != nil { if err != nil {
utils.Fatalf("Could not create the account: %v", err) utils.Fatalf("Could not create the account: %v", err)
} }
@ -697,9 +560,9 @@ func accountImport(ctx *cli.Context) {
if len(keyfile) == 0 { if len(keyfile) == 0 {
utils.Fatalf("keyfile must be given as argument") utils.Fatalf("keyfile must be given as argument")
} }
am := utils.MakeAccountManager(ctx) accman := 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) 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 := am.Import(keyfile, passphrase) acct, err := accman.Import(keyfile, passphrase)
if err != nil { if err != nil {
utils.Fatalf("Could not create the account: %v", err) utils.Fatalf("Could not create the account: %v", err)
} }

View file

@ -158,6 +158,7 @@ var AppHelpFlagGroups = []flagGroup{
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.WhisperEnabledFlag, utils.WhisperEnabledFlag,
utils.NatspecEnabledFlag, utils.NatspecEnabledFlag,
utils.IPCExperimental,
}, },
}, },
{ {

242
cmd/gethrpctest/main.go Normal file
View file

@ -0,0 +1,242 @@
// 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 <http://www.gnu.org/licenses/>.
// gethrpctest is a command to run the external RPC tests.
package main
import (
"flag"
"io/ioutil"
"log"
"os"
"os/signal"
"path/filepath"
"runtime"
"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"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/cmd/utils"
)
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")
)
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...")
if err := StartIPC(stack); err != nil {
log.Fatalf("Failed to start IPC interface: %v\n", err)
}
log.Println("IPC Interface started, accepting requests...")
// Start the RPC interface and wait until terminated
if err := StartRPC(stack); err != nil {
log.Fatalf("Failed to start RPC interface: %v", err)
}
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 := &eth.Config{
TestGenesisState: db,
TestGenesisBlock: test.Genesis,
AccountManager: accman,
}
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(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 {
var ethereum *eth.Ethereum
stack.Service(&ethereum)
blockchain := 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...))
}
// StartRPC initializes an IPC interface to the given protocol stack.
func StartIPC(stack *node.Node) error {
var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); 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
}

73
cmd/utils/api.go Normal file
View file

@ -0,0 +1,73 @@
// 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 <http://www.gnu.org/licenses/>.
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)))
}
// NetService 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())
}
func (s *PublicNetApi) Version() string {
return fmt.Sprintf("%d", s.networkVersion)
}

41
cmd/utils/bootnodes.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -29,9 +29,9 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/peterh/liner" "github.com/peterh/liner"
) )
@ -110,10 +110,9 @@ func Fatalf(format string, args ...interface{}) {
os.Exit(1) os.Exit(1)
} }
func StartEthereum(ethereum *eth.Ethereum) { func StartNode(stack *node.Node) {
glog.V(logger.Info).Infoln("Starting", ethereum.Name()) if err := stack.Start(); err != nil {
if err := ethereum.Start(); err != nil { Fatalf("Error starting protocol stack: %v", err)
Fatalf("Error starting Ethereum: %v", err)
} }
go func() { go func() {
sigc := make(chan os.Signal, 1) sigc := make(chan os.Signal, 1)
@ -121,7 +120,7 @@ func StartEthereum(ethereum *eth.Ethereum) {
defer signal.Stop(sigc) defer signal.Stop(sigc)
<-sigc <-sigc
glog.V(logger.Info).Infoln("Got interrupt, shutting down...") glog.V(logger.Info).Infoln("Got interrupt, shutting down...")
go ethereum.Stop() go stack.Stop()
logger.Flush() logger.Flush()
for i := 10; i > 0; i-- { for i := 10; i > 0; i-- {
<-sigc <-sigc

View file

@ -39,7 +39,7 @@ func (self *DirectoryString) String() string {
} }
func (self *DirectoryString) Set(value string) error { func (self *DirectoryString) Set(value string) error {
self.Value = expandPath(value) self.Value = ExpandPath(value)
return nil return nil
} }
@ -135,7 +135,7 @@ func (self *DirectoryFlag) Set(value string) {
// 2. expands embedded environment variables // 2. expands embedded environment variables
// 3. cleans the path, e.g. /a/b/../c -> /a/c // 3. cleans the path, e.g. /a/b/../c -> /a/c
// Note, it has limitations, e.g. ~someuser/tmp will not be expanded // 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 strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
if user, err := user.Current(); err == nil { if user, err := user.Current(); err == nil {
p = user.HomeDir + p[1:] p = user.HomeDir + p[1:]

View file

@ -33,7 +33,7 @@ func TestPathExpansion(t *testing.T) {
} }
os.Setenv("DDDXXX", "/tmp") os.Setenv("DDDXXX", "/tmp")
for test, expected := range tests { for test, expected := range tests {
got := expandPath(test) got := ExpandPath(test)
if got != expected { if got != expected {
t.Errorf("test %s, got %s, expected %s\n", test, got, expected) t.Errorf("test %s, got %s, expected %s\n", test, got, expected)
} }

View file

@ -19,6 +19,7 @@ package utils
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"math" "math"
"math/big" "math/big"
@ -28,12 +29,14 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/ethereum/ethash" "github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "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/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
@ -42,6 +45,8 @@ import (
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/metrics" "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/p2p/nat"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc/api" "github.com/ethereum/go-ethereum/rpc/api"
@ -49,6 +54,10 @@ import (
"github.com/ethereum/go-ethereum/rpc/comms" "github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/rpc/shared"
"github.com/ethereum/go-ethereum/rpc/useragent" "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" "github.com/ethereum/go-ethereum/xeth"
) )
@ -192,12 +201,12 @@ var (
// Account settings // Account settings
UnlockedAccountFlag = cli.StringFlag{ UnlockedAccountFlag = cli.StringFlag{
Name: "unlock", 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: "", Value: "",
} }
PasswordFileFlag = cli.StringFlag{ PasswordFileFlag = cli.StringFlag{
Name: "password", 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: "", Value: "",
} }
@ -294,6 +303,10 @@ var (
Usage: "Filename for IPC socket/pipe", Usage: "Filename for IPC socket/pipe",
Value: DirectoryString{common.DefaultIpcPath()}, Value: DirectoryString{common.DefaultIpcPath()},
} }
IPCExperimental = cli.BoolFlag{
Name: "ipcexp",
Usage: "Enable the new RPC implementation",
}
ExecFlag = cli.StringFlag{ ExecFlag = cli.StringFlag{
Name: "exec", Name: "exec",
Usage: "Execute JavaScript statement (only in combination with console/attach)", Usage: "Execute JavaScript statement (only in combination with console/attach)",
@ -316,7 +329,7 @@ var (
} }
BootnodesFlag = cli.StringFlag{ BootnodesFlag = cli.StringFlag{
Name: "bootnodes", Name: "bootnodes",
Usage: "Space-separated enode URLs for P2P discovery bootstrap", Usage: "Comma separated enode URLs for P2P discovery bootstrap",
Value: "", Value: "",
} }
NodeKeyFileFlag = cli.StringFlag{ NodeKeyFileFlag = cli.StringFlag{
@ -340,6 +353,22 @@ var (
Name: "shh", Name: "shh",
Usage: "Enable Whisper", 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 // ATM the url is left to the user and deployment to
JSpathFlag = cli.StringFlag{ JSpathFlag = cli.StringFlag{
Name: "jspath", Name: "jspath",
@ -385,6 +414,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. // MakeNAT creates a port mapper from set command line flags.
func MakeNAT(ctx *cli.Context) nat.Interface { func MakeNAT(ctx *cli.Context) nat.Interface {
natif, err := nat.Parse(ctx.GlobalString(NATFlag.Name)) natif, err := nat.Parse(ctx.GlobalString(NATFlag.Name))
@ -394,64 +507,196 @@ func MakeNAT(ctx *cli.Context) nat.Interface {
return natif return natif
} }
// MakeNodeKey creates a node key from set command line flags. // MakeGenesisBlock loads up a genesis block from an input file specified in the
func MakeNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) { // command line, or returns the empty string if none set.
hex, file := ctx.GlobalString(NodeKeyHexFlag.Name), ctx.GlobalString(NodeKeyFileFlag.Name) func MakeGenesisBlock(ctx *cli.Context) string {
var err error genesis := ctx.GlobalString(GenesisFileFlag.Name)
switch { if genesis == "" {
case file != "" && hex != "": return ""
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 != "": data, err := ioutil.ReadFile(genesis)
if key, err = crypto.HexToECDSA(hex); err != nil { if err != nil {
Fatalf("Option %q: %v", NodeKeyHexFlag.Name, err) Fatalf("Failed to load custom genesis file: %v", err)
} }
} return string(data)
return key
} }
// MakeEthConfig creates ethereum options from set command line flags. // MakeAccountManager creates an account manager from set command line flags.
func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { func MakeAccountManager(ctx *cli.Context) *accounts.Manager {
customName := ctx.GlobalString(IdentityFlag.Name) // Create the keystore crypto primitive, light if requested
if len(customName) > 0 { scryptN := crypto.StandardScryptN
clientID += "/" + customName scryptP := crypto.StandardScryptP
if ctx.GlobalBool(LightKDFFlag.Name) {
scryptN = crypto.LightScryptN
scryptP = crypto.LightScryptP
} }
am := MakeAccountManager(ctx) // Assemble an account manager using the configured datadir
etherbase, err := ParamToAddress(ctx.GlobalString(EtherbaseFlag.Name), am) 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) (a common.Address, err error) {
// If the specified account is a valid address, return it
if common.IsHexAddress(account) {
return common.HexToAddress(account), nil
}
// Otherwise try to interpret the account as a keystore index
index, err := strconv.Atoi(account)
if err != nil { if err != nil {
glog.V(logger.Error).Infoln("WARNING: No etherbase set and no accounts found as default") return a, fmt.Errorf("invalid account address or index %q", account)
} }
// Assemble the entire eth configuration and return hex, err := accman.AddressByIndex(index)
cfg := &eth.Config{ if err != nil {
Name: common.MakeName(clientID, version), return a, fmt.Errorf("can't get account #%d (%v)", index, err)
DataDir: MustDataDir(ctx), }
GenesisFile: ctx.GlobalString(GenesisFileFlag.Name), 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 {
accounts, _ := accman.Accounts()
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{}
}
etherbase := ctx.GlobalString(EtherbaseFlag.Name)
if etherbase == "" {
return common.Address{}
}
// If the specified etherbase is a valid address, return it
addr, err := MakeAddress(accman, etherbase)
if err != nil {
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
}
return addr
}
// 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
}
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 {
// 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)
}
datadir := MustMakeDataDir(ctx)
netprv := MakeNodeKey(ctx)
// Configure the node's service container
stackConf := &node.Config{
DataDir: datadir,
PrivateKey: netprv,
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)
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 := &eth.Config{
Genesis: MakeGenesisBlock(ctx),
FastSync: ctx.GlobalBool(FastSyncFlag.Name), FastSync: ctx.GlobalBool(FastSyncFlag.Name),
BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name), BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name),
DatabaseCache: ctx.GlobalInt(CacheFlag.Name), DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
SkipBcVersionCheck: false,
NetworkId: ctx.GlobalInt(NetworkIdFlag.Name), NetworkId: ctx.GlobalInt(NetworkIdFlag.Name),
LogFile: ctx.GlobalString(LogFileFlag.Name), AccountManager: accman,
Verbosity: ctx.GlobalInt(VerbosityFlag.Name), Etherbase: MakeEtherbase(accman, ctx),
Etherbase: common.HexToAddress(etherbase),
MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name), MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name),
AccountManager: am, ExtraData: MakeMinerExtra(extra, ctx),
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),
NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name),
DocRoot: ctx.GlobalString(DocRootFlag.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)), GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)),
GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)), GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)),
GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)), GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)),
@ -462,46 +707,103 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
SolcPath: ctx.GlobalString(SolcPathFlag.Name), SolcPath: ctx.GlobalString(SolcPathFlag.Name),
AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.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) { // Override any default configs in dev mode or the test net
glog.Fatalf("%s and %s are mutually exclusive\n", DevModeFlag.Name, TestNetFlag.Name) 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) { case ctx.GlobalBool(TestNetFlag.Name):
// testnet is always stored in the testnet folder if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.DataDir += "/testnet" ethConf.NetworkId = 2
cfg.NetworkId = 2
cfg.TestNet = true
} }
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
ethConf.Genesis = core.TestNetGenesisBlock()
}
state.StartingNonce = 1048576 // (2**20)
if ctx.GlobalBool(VMEnableJitFlag.Name) { case ctx.GlobalBool(DevModeFlag.Name):
cfg.Name += "/JIT" // Override the base network stack configs
} if !ctx.GlobalIsSet(DataDirFlag.Name) {
if ctx.GlobalBool(DevModeFlag.Name) { stackConf.DataDir = filepath.Join(os.TempDir(), "/ethereum_dev_mode")
if !ctx.GlobalIsSet(VMDebugFlag.Name) {
cfg.VmDebug = true
} }
if !ctx.GlobalIsSet(MaxPeersFlag.Name) { if !ctx.GlobalIsSet(MaxPeersFlag.Name) {
cfg.MaxPeers = 0 stackConf.MaxPeers = 0
}
if !ctx.GlobalIsSet(GasPriceFlag.Name) {
cfg.GasPrice = new(big.Int)
} }
if !ctx.GlobalIsSet(ListenPortFlag.Name) { 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) { if !ctx.GlobalIsSet(WhisperEnabledFlag.Name) {
cfg.Shh = true shhEnable = true
} }
if !ctx.GlobalIsSet(DataDirFlag.Name) { if !ctx.GlobalIsSet(VMDebugFlag.Name) {
cfg.DataDir = os.TempDir() + "/ethereum_dev_mode" vm.Debug = true
}
ethConf.PowTest = true
}
// Assemble and return the protocol stack
stack, err := node.New(stackConf)
if err != nil {
Fatalf("Failed to create the protocol stack: %v", err)
} }
cfg.PowTest = true
cfg.DevMode = true
glog.V(logger.Info).Infoln("dev mode enabled") // 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)
} }
return cfg
// 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
} }
// SetupLogger configures glog from the logging-related command line flags. // SetupLogger configures glog from the logging-related command line flags.
@ -509,7 +811,12 @@ func SetupLogger(ctx *cli.Context) {
glog.SetV(ctx.GlobalInt(VerbosityFlag.Name)) glog.SetV(ctx.GlobalInt(VerbosityFlag.Name))
glog.CopyStandardLogTo("INFO") glog.CopyStandardLogTo("INFO")
glog.SetToStderr(true) 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. // SetupNetwork configures the system for either the main net or some test network.
@ -535,7 +842,7 @@ func SetupVM(ctx *cli.Context) {
// MakeChain creates a chain manager from set command line flags. // MakeChain creates a chain manager from set command line flags.
func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database) { func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database) {
datadir := MustDataDir(ctx) datadir := MustMakeDataDir(ctx)
cache := ctx.GlobalInt(CacheFlag.Name) cache := ctx.GlobalInt(CacheFlag.Name)
var err error var err error
@ -543,7 +850,7 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database
Fatalf("Could not open database: %v", err) Fatalf("Could not open database: %v", err)
} }
if ctx.GlobalBool(OlympicFlag.Name) { if ctx.GlobalBool(OlympicFlag.Name) {
_, err := core.WriteTestNetGenesisBlock(chainDb, 42) _, err := core.WriteTestNetGenesisBlock(chainDb)
if err != nil { if err != nil {
glog.Fatalln(err) glog.Fatalln(err)
} }
@ -560,32 +867,6 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database
return chain, chainDb 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) { func IpcSocketPath(ctx *cli.Context) (ipcpath string) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
ipcpath = common.DefaultIpcPath() ipcpath = common.DefaultIpcPath()
@ -605,39 +886,79 @@ func IpcSocketPath(ctx *cli.Context) (ipcpath string) {
return return
} }
func StartIPC(eth *eth.Ethereum, ctx *cli.Context) error { func StartIPC(stack *node.Node, ctx *cli.Context) error {
config := comms.IpcConfig{ config := comms.IpcConfig{
Endpoint: IpcSocketPath(ctx), Endpoint: IpcSocketPath(ctx),
} }
var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); 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) { initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) {
fe := useragent.NewRemoteFrontend(conn, eth.AccountManager()) fe := useragent.NewRemoteFrontend(conn, ethereum.AccountManager())
xeth := xeth.New(eth, fe) xeth := xeth.New(stack, fe)
apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, eth) apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, stack)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return xeth, api.Merge(apis...), nil return xeth, api.Merge(apis...), nil
} }
return comms.StartIpc(config, codec.JSON, initializer) 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{ config := comms.HttpConfig{
ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name), ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name),
ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)), ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)),
CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name), CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name),
} }
xeth := xeth.New(eth, nil) xeth := xeth.New(stack, nil)
codec := codec.JSON 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 { if err != nil {
return err return err
} }
return comms.StartHttp(config, codec, api.Merge(apis...)) return comms.StartHttp(config, codec, api.Merge(apis...))
} }
@ -647,20 +968,3 @@ func StartPProf(ctx *cli.Context) {
log.Println(http.ListenAndServe(address, nil)) 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
}

29
common/chequebook/api.go Normal file
View file

@ -0,0 +1,29 @@
package chequebook
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
const Version = "1.0"
type Api struct {
ch *Chequebook
}
func NewApi(ch *Chequebook) *Api {
return &Api{ch}
}
func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *Cheque, err error) {
return self.ch.Issue(beneficiary, amount)
}
func (self *Api) Cash(cheque *Cheque) (txhash string, err error) {
return self.ch.Cash(cheque)
}
func (self *Api) Deposit(amount *big.Int) (txhash string, err error) {
return self.ch.Deposit(amount)
}

660
common/chequebook/cheque.go Normal file
View file

@ -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())
}

View file

@ -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))
}
}

View file

@ -0,0 +1,49 @@
import "mortal";
/// @title Chequebook for Ethereum micropayments
/// @author Daniel A. Nagy <daniel@ethdev.com>
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);
}
}
}

View file

@ -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 <daniel@ethdev.com>
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);
}
}
}
`
)

157
common/kademlia/address.go Normal file
View file

@ -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)
}

View file

@ -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)
}
}
}

360
common/kademlia/kaddb.go Normal file
View file

@ -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)
}

457
common/kademlia/kademlia.go Normal file
View file

@ -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")
}

View file

@ -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()
}

View file

@ -34,6 +34,8 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb" "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" 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 // only use minimalistic stack with no networking
return eth.New(&eth.Config{ return eth.New(&node.ServiceContext{EventMux: new(event.TypeMux)}, &eth.Config{
DataDir: tmp,
AccountManager: am, AccountManager: am,
Etherbase: common.HexToAddress(testAddress), Etherbase: common.HexToAddress(testAddress),
MaxPeers: 0,
PowTest: true, PowTest: true,
NewDB: func(path string) (ethdb.Database, error) { return db, nil }, TestGenesisState: db,
GpoMinGasPrice: common.Big1, GpoMinGasPrice: common.Big1,
GpobaseCorrectionFactor: 1, GpobaseCorrectionFactor: 1,
GpoMaxGasPrice: common.Big1, GpoMaxGasPrice: common.Big1,
@ -166,7 +166,7 @@ func testInit(t *testing.T) (self *testFrontend) {
t.Errorf("error creating ethereum: %v", err) t.Errorf("error creating ethereum: %v", err)
return return
} }
err = ethereum.Start() err = ethereum.Start(nil)
if err != nil { if err != nil {
t.Errorf("error starting ethereum: %v", err) t.Errorf("error starting ethereum: %v", err)
return return
@ -174,7 +174,7 @@ func testInit(t *testing.T) (self *testFrontend) {
// mock frontend // mock frontend
self = &testFrontend{t: t, ethereum: ethereum} self = &testFrontend{t: t, ethereum: ethereum}
self.xeth = xe.New(ethereum, self) self.xeth = xe.New(nil, self)
self.wait = self.xeth.UpdateState() self.wait = self.xeth.UpdateState()
addr, _ := self.ethereum.Etherbase() addr, _ := self.ethereum.Etherbase()

View file

@ -20,18 +20,22 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common/registrar" "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 // implements a versioned Registrar on an archiving full node
type EthReg struct { type EthReg struct {
backend *xeth.XEth backend Backend
registry *registrar.Registrar registry *registrar.Registrar
} }
func New(xe *xeth.XEth) (self *EthReg) { func New(backend Backend) (self *EthReg) {
self = &EthReg{backend: xe} self = &EthReg{backend: backend}
self.registry = registrar.New(xe) self.registry = registrar.New(backend)
return return
} }
@ -40,9 +44,11 @@ func (self *EthReg) Registry() *registrar.Registrar {
} }
func (self *EthReg) Resolver(n *big.Int) *registrar.Registrar { func (self *EthReg) Resolver(n *big.Int) *registrar.Registrar {
xe := self.backend var s registrar.Backend
if n != nil { 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)
} }

238
common/swap/swap.go Normal file
View file

@ -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()
}
}

178
common/swap/swap_test.go Normal file
View file

@ -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),
},
})
}

View file

@ -17,6 +17,8 @@
package common package common
import ( import (
"encoding/hex"
"encoding/json"
"fmt" "fmt"
"math/big" "math/big"
"math/rand" "math/rand"
@ -24,13 +26,13 @@ import (
) )
const ( const (
hashLength = 32 HashLength = 32
addressLength = 20 AddressLength = 20
) )
type ( type (
Hash [hashLength]byte Hash [HashLength]byte
Address [addressLength]byte Address [AddressLength]byte
) )
func BytesToHash(b []byte) Hash { func BytesToHash(b []byte) Hash {
@ -50,13 +52,28 @@ func (h Hash) Bytes() []byte { return h[:] }
func (h Hash) Big() *big.Int { return Bytes2Big(h[:]) } func (h Hash) Big() *big.Int { return Bytes2Big(h[:]) }
func (h Hash) Hex() string { return "0x" + Bytes2Hex(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 // Sets the hash to the value of b. If b is larger than len(h) it will panic
func (h *Hash) SetBytes(b []byte) { func (h *Hash) SetBytes(b []byte) {
if len(b) > len(h) { 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 // Set string `s` to h. If s is larger than len(h) it will panic
@ -92,6 +109,18 @@ func StringToAddress(s string) Address { return BytesToAddress([]byte(s)) }
func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) }
func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } 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) {
return true
}
if len(s) == 2*AddressLength && IsHex("0x"+s) {
return true
}
return false
}
// Get the string representation of the underlying address // Get the string representation of the underlying address
func (a Address) Str() string { return string(a[:]) } func (a Address) Str() string { return string(a[:]) }
func (a Address) Bytes() []byte { return a[:] } func (a Address) Bytes() []byte { return a[:] }
@ -102,9 +131,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 // Sets the address to the value of b. If b is larger than len(a) it will panic
func (a *Address) SetBytes(b []byte) { func (a *Address) SetBytes(b []byte) {
if len(b) > len(a) { 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 // Set string `s` to a. If s is larger than len(a) it will panic
@ -117,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: // PP Pretty Prints a byte slice in the following format:
// hex(value[:4])...(hex[len(value)-4:]) // hex(value[:4])...(hex[len(value)-4:])
func PP(value []byte) string { func PP(value []byte) string {
@ -126,3 +187,7 @@ func PP(value []byte) string {
return fmt.Sprintf("%x...%x", value[:4], value[len(value)-4]) return fmt.Sprintf("%x...%x", value[:4], value[len(value)-4])
} }
func quote(s string) string {
return `"` + s + `"`
}

974
core/api.go Normal file
View file

@ -0,0 +1,974 @@
// 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 <http://www.gnu.org/licenses/>.
package core
import (
"bytes"
"encoding/json"
"fmt"
"math/big"
"time"
"sync"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"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"
"gopkg.in/fatih/set.v0"
)
const (
FilterTimeout = 300 * time.Second // Remove filter after FilterTimeout
defaultGasPrice = uint64(10000000000000)
defaultGas = uint64(90000)
)
// 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 *BlockChain
am *accounts.Manager
}
// NewPublicBlockChainApi creates a new Etheruem blockchain API.
func NewPublicBlockChainApi(bc *BlockChain, am *accounts.Manager) *PublicBlockChainApi {
return &PublicBlockChainApi{bc: bc, 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.bc.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 *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.bc.eventMux.Subscribe(ChainEvent{})
output := func(rawBlock interface{}) interface{} {
if event, ok := rawBlock.(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.bc.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.bc.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.bc.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 := NewEnv(stateDb, s.bc, msg, header)
gp := new(GasPool).AddGas(common.MaxBig)
res, gas, err := 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
}
func (s *PublicBlockChainApi) Call(args CallArgs, blockNr rpc.BlockNumber) (string, error) {
result, _, err := s.doCall(args, blockNr)
return result, err
}
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 *BlockChain
am *accounts.Manager
txPool *TxPool
txMu sync.Mutex
}
// NewPublicTransactionPoolApi creates a new RPC service with methods specific for the transaction pool.
func NewPublicTransactionPoolApi(txPool *TxPool, chainDb ethdb.Database, bc *BlockChain, am *accounts.Manager) *PublicTransactionPoolApi {
return &PublicTransactionPoolApi{
eventMux: txPool.eventMux,
chainDb: chainDb,
bc: bc,
am: am,
txPool: txPool,
}
}
func getTransaction(chainDb ethdb.Database, txPool *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 := 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(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.(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)
}

View file

@ -34,7 +34,7 @@ func proc() (Validator, *BlockChain) {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
var mux event.TypeMux var mux event.TypeMux
WriteTestNetGenesisBlock(db, 0) WriteTestNetGenesisBlock(db)
blockchain, err := NewBlockChain(db, thePow(), &mux) blockchain, err := NewBlockChain(db, thePow(), &mux)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)

View file

@ -149,11 +149,7 @@ func NewBlockChain(chainDb ethdb.Database, pow pow.PoW, mux *event.TypeMux) (*Bl
bc.genesisBlock = bc.GetBlockByNumber(0) bc.genesisBlock = bc.GetBlockByNumber(0)
if bc.genesisBlock == nil { if bc.genesisBlock == nil {
reader, err := NewDefaultGenesisReader() bc.genesisBlock, err = WriteDefaultGenesisBlock(chainDb)
if err != nil {
return nil, err
}
bc.genesisBlock, err = WriteGenesisBlock(chainDb, reader)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -51,7 +51,7 @@ func thePow() pow.PoW {
func theBlockChain(db ethdb.Database, t *testing.T) *BlockChain { func theBlockChain(db ethdb.Database, t *testing.T) *BlockChain {
var eventMux event.TypeMux var eventMux event.TypeMux
WriteTestNetGenesisBlock(db, 0) WriteTestNetGenesisBlock(db)
blockchain, err := NewBlockChain(db, thePow(), &eventMux) blockchain, err := NewBlockChain(db, thePow(), &eventMux)
if err != nil { if err != nil {
t.Error("failed creating blockchain:", err) 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) { func testReorg(t *testing.T, first, second []int, td int64, full bool) {
// Create a pristine block chain // Create a pristine block chain
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
genesis, _ := WriteTestNetGenesisBlock(db, 0) genesis, _ := WriteTestNetGenesisBlock(db)
bc := chm(genesis, db) bc := chm(genesis, db)
// Insert an easy and a difficult chain afterwards // 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) { func testBadHashes(t *testing.T, full bool) {
// Create a pristine block chain // Create a pristine block chain
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
genesis, _ := WriteTestNetGenesisBlock(db, 0) genesis, _ := WriteTestNetGenesisBlock(db)
bc := chm(genesis, db) bc := chm(genesis, db)
// Create a chain, ban a hash and try to import // 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) { func testReorgBadHashes(t *testing.T, full bool) {
// Create a pristine block chain // Create a pristine block chain
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
genesis, _ := WriteTestNetGenesisBlock(db, 0) genesis, _ := WriteTestNetGenesisBlock(db)
bc := chm(genesis, db) bc := chm(genesis, db)
// Create a chain, import and ban aferwards // Create a chain, import and ban aferwards

View file

@ -220,7 +220,7 @@ func newCanonical(n int, full bool) (ethdb.Database, *BlockChain, error) {
evmux := &event.TypeMux{} evmux := &event.TypeMux{}
// Initialize a fresh chain with only a genesis block // Initialize a fresh chain with only a genesis block
genesis, _ := WriteTestNetGenesisBlock(db, 0) genesis, _ := WriteTestNetGenesisBlock(db)
blockchain, _ := NewBlockChain(db, FakePow{}, evmux) blockchain, _ := NewBlockChain(db, FakePow{}, evmux)
// Create and inject the requested chain // Create and inject the requested chain

File diff suppressed because one or more lines are too long

View file

@ -17,6 +17,8 @@
package core package core
import ( import (
"compress/gzip"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -33,6 +35,11 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
const (
TestAccount = "e273f01c99144c438695e10f24926dc1f9fbf62d"
TestBalance = "1000000000000"
)
// WriteGenesisBlock writes the genesis block to the database as block number 0 // WriteGenesisBlock writes the genesis block to the database as block number 0
func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block, error) { func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block, error) {
contents, err := ioutil.ReadAll(reader) contents, err := ioutil.ReadAll(reader)
@ -158,29 +165,42 @@ func WriteGenesisBlockForTesting(db ethdb.Database, accounts ...GenesisAccount)
return block return block
} }
func WriteTestNetGenesisBlock(chainDb ethdb.Database, nonce uint64) (*types.Block, error) { // WriteDefaultGenesisBlock assembles the official Ethereum genesis block and
testGenesis := fmt.Sprintf(`{ // writes it - along with all associated state - into a chain database.
"nonce": "0x%x", func WriteDefaultGenesisBlock(chainDb ethdb.Database) (*types.Block, error) {
"difficulty": "0x20000", return WriteGenesisBlock(chainDb, strings.NewReader(DefaultGenesisBlock()))
"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))
} }
func WriteOlympicGenesisBlock(chainDb ethdb.Database, nonce uint64) (*types.Block, error) { // WriteTestNetGenesisBlock assembles the Morden test network genesis block and
testGenesis := fmt.Sprintf(`{ // 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", "nonce":"0x%x",
"gasLimit":"0x%x", "gasLimit":"0x%x",
"difficulty":"0x%x", "difficulty":"0x%x",
@ -198,6 +218,27 @@ func WriteOlympicGenesisBlock(chainDb ethdb.Database, nonce uint64) (*types.Bloc
"e6716f9544a56c530d868e4bfbacb172315bdead": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, "e6716f9544a56c530d868e4bfbacb172315bdead": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
"1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {"balance": "1606938044258990275541962092341162602522202993782792835301376"} "1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}
} }
}`, types.EncodeNonce(nonce), params.GenesisGasLimit.Bytes(), params.GenesisDifficulty.Bytes()) }`, types.EncodeNonce(42), params.GenesisGasLimit.Bytes(), params.GenesisDifficulty.Bytes())
return WriteGenesisBlock(chainDb, strings.NewReader(testGenesis)) }
// 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))
} }

View file

@ -48,6 +48,10 @@ func (n BlockNonce) Uint64() uint64 {
return binary.BigEndian.Uint64(n[:]) return binary.BigEndian.Uint64(n[:])
} }
func (n BlockNonce) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"0x%x"`, n)), nil
}
type Header struct { type Header struct {
ParentHash common.Hash // Hash to the previous block ParentHash common.Hash // Hash to the previous block
UncleHash common.Hash // Uncles of this block UncleHash common.Hash // Uncles of this block

View file

@ -69,6 +69,10 @@ func (b Bloom) TestBytes(test []byte) bool {
return b.Test(common.BytesToBig(test)) 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 { func CreateBloom(receipts Receipts) Bloom {
bin := new(big.Int) bin := new(big.Int)
for _, receipt := range receipts { for _, receipt := range receipts {

View file

@ -17,11 +17,13 @@
package vm package vm
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
) )
type Log struct { type Log struct {
@ -63,6 +65,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) 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": rpc.NewHexNumber(r.BlockNumber),
"logIndex": rpc.NewHexNumber(r.Index),
"blockHash": r.BlockHash,
"transactionHash": r.TxHash,
"transactionIndex": rpc.NewHexNumber(r.TxIndex),
"topics": r.Topics,
}
return json.Marshal(fields)
}
type Logs []*Log type Logs []*Log
// LogForStorage is a wrapper around a Log that flattens and parses the entire // LogForStorage is a wrapper around a Log that flattens and parses the entire

View file

@ -43,14 +43,6 @@ import (
"golang.org/x/crypto/ripemd160" "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 { func Sha3(data ...[]byte) []byte {
d := sha3.NewKeccak256() d := sha3.NewKeccak256()
for _, b := range data { for _, b := range data {
@ -99,9 +91,9 @@ func ToECDSA(prv []byte) *ecdsa.PrivateKey {
} }
priv := new(ecdsa.PrivateKey) priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = S256() priv.PublicKey.Curve = secp256k1.S256()
priv.D = common.BigD(prv) 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 return priv
} }
@ -116,15 +108,15 @@ func ToECDSAPub(pub []byte) *ecdsa.PublicKey {
if len(pub) == 0 { if len(pub) == 0 {
return nil return nil
} }
x, y := elliptic.Unmarshal(S256(), pub) x, y := elliptic.Unmarshal(secp256k1.S256(), pub)
return &ecdsa.PublicKey{S256(), x, y} return &ecdsa.PublicKey{secp256k1.S256(), x, y}
} }
func FromECDSAPub(pub *ecdsa.PublicKey) []byte { func FromECDSAPub(pub *ecdsa.PublicKey) []byte {
if pub == nil || pub.X == nil || pub.Y == nil { if pub == nil || pub.X == nil || pub.Y == nil {
return 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. // HexToECDSA parses a secp256k1 private key.
@ -168,7 +160,7 @@ func SaveECDSA(file string, key *ecdsa.PrivateKey) error {
} }
func GenerateKey() (*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 { func ValidateSignatureValues(v byte, r, s *big.Int) bool {
@ -176,7 +168,7 @@ func ValidateSignatureValues(v byte, r, s *big.Int) bool {
return false return false
} }
vint := uint32(v) 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 return true
} else { } else {
return false return false
@ -189,8 +181,8 @@ func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
return nil, err return nil, err
} }
x, y := elliptic.Unmarshal(S256(), s) x, y := elliptic.Unmarshal(secp256k1.S256(), s)
return &ecdsa.PublicKey{S256(), x, y}, nil return &ecdsa.PublicKey{secp256k1.S256(), x, y}, nil
} }
func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {

View file

@ -181,7 +181,7 @@ func TestValidateSignatureValues(t *testing.T) {
minusOne := big.NewInt(-1) minusOne := big.NewInt(-1)
one := common.Big1 one := common.Big1
zero := common.Big0 zero := common.Big0
secp256k1nMinus1 := new(big.Int).Sub(secp256k1n, common.Big1) secp256k1nMinus1 := new(big.Int).Sub(secp256k1.N, common.Big1)
// correct v,r,s // correct v,r,s
check(true, 27, one, one) check(true, 27, one, one)
@ -208,9 +208,9 @@ func TestValidateSignatureValues(t *testing.T) {
// correct sig with max r,s // correct sig with max r,s
check(true, 27, secp256k1nMinus1, secp256k1nMinus1) check(true, 27, secp256k1nMinus1, secp256k1nMinus1)
// correct v, combinations of incorrect r,s at upper limit // correct v, combinations of incorrect r,s at upper limit
check(false, 27, secp256k1n, secp256k1nMinus1) check(false, 27, secp256k1.N, secp256k1nMinus1)
check(false, 27, secp256k1nMinus1, secp256k1n) check(false, 27, secp256k1nMinus1, secp256k1.N)
check(false, 27, secp256k1n, secp256k1n) check(false, 27, secp256k1.N, secp256k1.N)
// current callers ensures r,s cannot be negative, but let's test for that too // current callers ensures r,s cannot be negative, but let's test for that too
// as crypto package could be used stand-alone // as crypto package could be used stand-alone

View file

@ -41,6 +41,8 @@ import (
"fmt" "fmt"
"hash" "hash"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
) )
var ( var (
@ -81,6 +83,7 @@ func doScheme(base, v []int) asn1.ObjectIdentifier {
type secgNamedCurve asn1.ObjectIdentifier type secgNamedCurve asn1.ObjectIdentifier
var ( var (
secgNamedCurveS256 = secgNamedCurve{1, 3, 132, 0, 10}
secgNamedCurveP256 = secgNamedCurve{1, 2, 840, 10045, 3, 1, 7} secgNamedCurveP256 = secgNamedCurve{1, 2, 840, 10045, 3, 1, 7}
secgNamedCurveP384 = secgNamedCurve{1, 3, 132, 0, 34} secgNamedCurveP384 = secgNamedCurve{1, 3, 132, 0, 34}
secgNamedCurveP521 = secgNamedCurve{1, 3, 132, 0, 35} secgNamedCurveP521 = secgNamedCurve{1, 3, 132, 0, 35}
@ -116,6 +119,8 @@ func (curve secgNamedCurve) Equal(curve2 secgNamedCurve) bool {
func namedCurveFromOID(curve secgNamedCurve) elliptic.Curve { func namedCurveFromOID(curve secgNamedCurve) elliptic.Curve {
switch { switch {
case curve.Equal(secgNamedCurveS256):
return secp256k1.S256()
case curve.Equal(secgNamedCurveP256): case curve.Equal(secgNamedCurveP256):
return elliptic.P256() return elliptic.P256()
case curve.Equal(secgNamedCurveP384): case curve.Equal(secgNamedCurveP384):
@ -134,6 +139,8 @@ func oidFromNamedCurve(curve elliptic.Curve) (secgNamedCurve, bool) {
return secgNamedCurveP384, true return secgNamedCurveP384, true
case elliptic.P521(): case elliptic.P521():
return secgNamedCurveP521, true return secgNamedCurveP521, true
case secp256k1.S256():
return secgNamedCurveS256, true
} }
return nil, false return nil, false

View file

@ -125,6 +125,7 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b
if skLen+macLen > MaxSharedKeyLength(pub) { if skLen+macLen > MaxSharedKeyLength(pub) {
return nil, ErrSharedKeyTooBig return nil, ErrSharedKeyTooBig
} }
x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes()) x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes())
if x == nil { if x == nil {
return nil, ErrSharedKeyIsPointAtInfinity return nil, ErrSharedKeyIsPointAtInfinity

View file

@ -31,13 +31,18 @@ package ecies
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
) )
var dumpEnc bool 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") var ErrBadSharedKeys = fmt.Errorf("ecies: shared keys don't match")
// cmpParams compares a set of ECIES parameters. We assume, as per the // 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()) fmt.Println(err.Error())
t.FailNow() t.FailNow()
} }
skLen = MaxSharedKeyLength(&prv1.PublicKey) / 2 skLen := MaxSharedKeyLength(&prv1.PublicKey) / 2
prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil)
if err != 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 // Verify that the key generation code fails when too much key data is
// requested. // requested.
func TestTooBigSharedKey(t *testing.T) { func TestTooBigSharedKey(t *testing.T) {
@ -158,13 +200,13 @@ func TestTooBigSharedKey(t *testing.T) {
t.FailNow() t.FailNow()
} }
_, err = prv1.GenerateShared(&prv2.PublicKey, skLen*2, skLen*2) _, err = prv1.GenerateShared(&prv2.PublicKey, 32, 32)
if err != ErrSharedKeyTooBig { if err != ErrSharedKeyTooBig {
fmt.Println("ecdh: shared key should be too large for curve") fmt.Println("ecdh: shared key should be too large for curve")
t.FailNow() t.FailNow()
} }
_, err = prv2.GenerateShared(&prv1.PublicKey, skLen*2, skLen*2) _, err = prv2.GenerateShared(&prv1.PublicKey, 32, 32)
if err != ErrSharedKeyTooBig { if err != ErrSharedKeyTooBig {
fmt.Println("ecdh: shared key should be too large for curve") fmt.Println("ecdh: shared key should be too large for curve")
t.FailNow() t.FailNow()
@ -176,25 +218,21 @@ func TestTooBigSharedKey(t *testing.T) {
func TestMarshalPublic(t *testing.T) { func TestMarshalPublic(t *testing.T) {
prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) prv, err := GenerateKey(rand.Reader, DefaultCurve, nil)
if err != nil { if err != nil {
fmt.Println(err.Error()) t.Fatalf("GenerateKey error: %s", err)
t.FailNow()
} }
out, err := MarshalPublic(&prv.PublicKey) out, err := MarshalPublic(&prv.PublicKey)
if err != nil { if err != nil {
fmt.Println(err.Error()) t.Fatalf("MarshalPublic error: %s", err)
t.FailNow()
} }
pub, err := UnmarshalPublic(out) pub, err := UnmarshalPublic(out)
if err != nil { if err != nil {
fmt.Println(err.Error()) t.Fatalf("UnmarshalPublic error: %s", err)
t.FailNow()
} }
if !cmpPublic(prv.PublicKey, *pub) { if !cmpPublic(prv.PublicKey, *pub) {
fmt.Println("ecies: failed to unmarshal public key") t.Fatal("ecies: failed to unmarshal public key")
t.FailNow()
} }
} }
@ -304,9 +342,26 @@ func BenchmarkGenSharedKeyP256(b *testing.B) {
fmt.Println(err.Error()) fmt.Println(err.Error())
b.FailNow() b.FailNow()
} }
b.ResetTimer()
for i := 0; i < b.N; i++ { 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 { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
b.FailNow() 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)
}

View file

@ -41,13 +41,12 @@ import (
"crypto/sha512" "crypto/sha512"
"fmt" "fmt"
"hash" "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 ( var (
DefaultCurve = secp256k1.S256()
ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm") ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm")
ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters") ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters")
) )
@ -101,6 +100,7 @@ var (
) )
var paramsFromCurve = map[elliptic.Curve]*ECIESParams{ var paramsFromCurve = map[elliptic.Curve]*ECIESParams{
secp256k1.S256(): ECIES_AES128_SHA256,
elliptic.P256(): ECIES_AES128_SHA256, elliptic.P256(): ECIES_AES128_SHA256,
elliptic.P384(): ECIES_AES256_SHA384, elliptic.P384(): ECIES_AES256_SHA384,
elliptic.P521(): ECIES_AES256_SHA512, elliptic.P521(): ECIES_AES256_SHA512,

View file

@ -25,6 +25,7 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/pborman/uuid" "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()) panic("key generation: could not read from random source: " + err.Error())
} }
reader := bytes.NewReader(randBytes) reader := bytes.NewReader(randBytes)
privateKeyECDSA, err := ecdsa.GenerateKey(S256(), reader) privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), reader)
if err != nil { if err != nil {
panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) 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()) panic("key generation: could not read from random source: " + err.Error())
} }
reader := bytes.NewReader(randBytes) reader := bytes.NewReader(randBytes)
privateKeyECDSA, err := ecdsa.GenerateKey(S256(), reader) privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), reader)
if err != nil { if err != nil {
panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
} }

View file

@ -29,15 +29,22 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package crypto package secp256k1
import ( import (
"crypto/elliptic" "crypto/elliptic"
"io" "io"
"math/big" "math/big"
"sync" "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 // This code is from https://github.com/ThePiachu/GoBit and implements
// several Koblitz elliptic curves over prime fields. // 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 return x3, y3, z3
} }
//TODO: double check if it is okay func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
// ScalarMult returns k*(Bx,By) where k is a number in big-endian form. // Ensure scalar is exactly 32 bytes. We pad always, even if
func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) { // scalar is 32 bytes long, to avoid a timing side channel.
// We have a slight problem in that the identity of the group (the if len(scalar) > 32 {
// point at infinity) cannot be represented in (x, y) form on a finite panic("can't handle scalars > 256 bits")
// machine. Thus the standard add/double algorithm has to be tweaked }
// slightly: our initial state is not the identity, but x, and we padded := make([]byte, 32)
// ignore the first true bit in |k|. If we don't find any true bits in copy(padded[32-len(scalar):], scalar)
// |k|, then we return nil, nil, because we cannot return the identity scalar = padded
// element.
Bz := new(big.Int).SetInt64(1) // Do the multiplication in C, updating point.
x := Bx point := make([]byte, 64)
y := By readBits(point[:32], Bx)
z := Bz 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)
seenFirstTrue := false // Unpack the result and clear temporaries.
for _, byte := range k { x := new(big.Int).SetBytes(point[:32])
for bitNum := 0; bitNum < 8; bitNum++ { y := new(big.Int).SetBytes(point[32:])
if seenFirstTrue { for i := range point {
x, y, z = BitCurve.doubleJacobian(x, y, z) point[i] = 0
} }
if byte&0x80 == 0x80 { for i := range padded {
if !seenFirstTrue { scalar[i] = 0
seenFirstTrue = true
} else {
x, y, z = BitCurve.addJacobian(Bx, By, Bz, x, y, z)
} }
} if res != 1 {
byte <<= 1
}
}
if !seenFirstTrue {
return nil, nil return nil, nil
} }
return x, y
return BitCurve.affineFromJacobian(x, y, z)
} }
// ScalarBaseMult returns k*G, where G is the base point of the group and k is // 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 return
} }
//curve parameters taken from: var (
//http://www.secg.org/collateral/sec2_final.pdf initonce sync.Once
theCurve *BitCurve
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
}
// S256 returns a BitCurve which implements secp256k1 (see SEC 2 section 2.7.1) // S256 returns a BitCurve which implements secp256k1 (see SEC 2 section 2.7.1)
func S256() *BitCurve { func S256() *BitCurve {
initonce.Do(initAll) initonce.Do(func() {
return ecp256k1 // 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
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
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")
}

View file

@ -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 <http://www.gnu.org/licenses/>.
/** 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;
}

View file

@ -20,6 +20,7 @@ package secp256k1
/* /*
#cgo CFLAGS: -I./libsecp256k1 #cgo CFLAGS: -I./libsecp256k1
#cgo CFLAGS: -I./libsecp256k1/src/
#cgo darwin CFLAGS: -I/usr/local/include #cgo darwin CFLAGS: -I/usr/local/include
#cgo freebsd CFLAGS: -I/usr/local/include #cgo freebsd CFLAGS: -I/usr/local/include
#cgo linux,arm CFLAGS: -I/usr/local/arm/include #cgo linux,arm CFLAGS: -I/usr/local/arm/include
@ -35,6 +36,7 @@ package secp256k1
#define NDEBUG #define NDEBUG
#include "./libsecp256k1/src/secp256k1.c" #include "./libsecp256k1/src/secp256k1.c"
#include "./libsecp256k1/src/modules/recovery/main_impl.h" #include "./libsecp256k1/src/modules/recovery/main_impl.h"
#include "pubkey_scalar_mul.h"
typedef void (*callbackFunc) (const char* msg, void* data); typedef void (*callbackFunc) (const char* msg, void* data);
extern void secp256k1GoPanicIllegal(const char* msg, void* data); extern void secp256k1GoPanicIllegal(const char* msg, void* data);
@ -44,6 +46,7 @@ import "C"
import ( import (
"errors" "errors"
"math/big"
"unsafe" "unsafe"
"github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/ethereum/go-ethereum/crypto/randentropy"
@ -56,13 +59,16 @@ import (
> store private keys in buffer and shuffle (deters persistance on swap disc) > store private keys in buffer and shuffle (deters persistance on swap disc)
> byte permutation (changing) > byte permutation (changing)
> xor with chaning random block (to deter scanning memory for 0x63) (stream cipher?) > 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) // 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() { func init() {
N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16)
// around 20 ms on a modern CPU. // around 20 ms on a modern CPU.
context = C.secp256k1_context_create(3) // SECP256K1_START_SIGN | SECP256K1_START_VERIFY context = C.secp256k1_context_create(3) // SECP256K1_START_SIGN | SECP256K1_START_VERIFY
C.secp256k1_context_set_illegal_callback(context, C.callbackFunc(C.secp256k1GoPanicIllegal), nil) C.secp256k1_context_set_illegal_callback(context, C.callbackFunc(C.secp256k1GoPanicIllegal), nil)
@ -78,7 +84,6 @@ var (
func GenerateKeyPair() ([]byte, []byte) { func GenerateKeyPair() ([]byte, []byte) {
var seckey []byte = randentropy.GetEntropyCSPRNG(32) var seckey []byte = randentropy.GetEntropyCSPRNG(32)
var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0])) var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0]))
var pubkey64 []byte = make([]byte, 64) // secp256k1_pubkey var pubkey64 []byte = make([]byte, 64) // secp256k1_pubkey
var pubkey65 []byte = make([]byte, 65) // 65 byte uncompressed pubkey var pubkey65 []byte = make([]byte, 65) // 65 byte uncompressed pubkey
pubkey64_ptr := (*C.secp256k1_pubkey)(unsafe.Pointer(&pubkey64[0])) pubkey64_ptr := (*C.secp256k1_pubkey)(unsafe.Pointer(&pubkey64[0]))
@ -254,3 +259,16 @@ func checkSignature(sig []byte) error {
} }
return nil 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
}
}
}

View file

@ -24,7 +24,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/ethereum/go-ethereum/crypto/randentropy"
) )
const TestCount = 10000 const TestCount = 1000
func TestPrivkeyGenerate(t *testing.T) { func TestPrivkeyGenerate(t *testing.T) {
_, seckey := GenerateKeyPair() _, seckey := GenerateKeyPair()
@ -86,10 +86,7 @@ func TestSignAndRecover(t *testing.T) {
func TestRandomMessagesWithSameKey(t *testing.T) { func TestRandomMessagesWithSameKey(t *testing.T) {
pubkey, seckey := GenerateKeyPair() pubkey, seckey := GenerateKeyPair()
keys := func() ([]byte, []byte) { keys := func() ([]byte, []byte) {
// Sign function zeroes the privkey so we need a new one in each call return pubkey, seckey
newkey := make([]byte, len(seckey))
copy(newkey, seckey)
return pubkey, newkey
} }
signAndRecoverWithRandomMessages(t, keys) 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 // 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++ { for i := 0; i < b.N; i++ {
b.StopTimer()
_, seckey := GenerateKeyPair() _, seckey := GenerateKeyPair()
msg := randentropy.GetEntropyCSPRNG(32) msg := randentropy.GetEntropyCSPRNG(32)
b.StartTimer() b.StartTimer()
if _, err := Sign(msg, seckey); err != nil { _, e := Sign(msg, seckey)
b.Fatal(err) err = e
} b.StopTimer()
} }
} }
//godep go test -v -run=XXX -bench=BenchmarkRecoverRandomInputEachRound //godep go test -v -run=XXX -bench=BenchmarkECRec
func BenchmarkRecoverRandomInputEachRound(b *testing.B) { func BenchmarkRecover(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
b.StopTimer()
_, seckey := GenerateKeyPair() _, seckey := GenerateKeyPair()
msg := randentropy.GetEntropyCSPRNG(32) msg := randentropy.GetEntropyCSPRNG(32)
sig, _ := Sign(msg, seckey) sig, _ := Sign(msg, seckey)
b.StartTimer() b.StartTimer()
if _, err := RecoverPubkey(msg, sig); err != nil { _, e := RecoverPubkey(msg, sig)
b.Fatal(err) err = e
} b.StopTimer()
} }
} }

266
eth/api.go Normal file
View file

@ -0,0 +1,266 @@
// 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 <http://www.gnu.org/licenses/>.
package eth
import (
"errors"
"math/big"
"time"
"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/logger"
"github.com/ethereum/go-ethereum/logger/glog"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
)
// 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 nil, 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
}

View file

@ -19,16 +19,12 @@ package eth
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"syscall"
"time" "time"
"github.com/ethereum/ethash" "github.com/ethereum/ethash"
@ -37,21 +33,18 @@ import (
"github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/common/httpclient" "github.com/ethereum/go-ethereum/common/httpclient"
"github.com/ethereum/go-ethereum/core" "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/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/eth/downloader"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "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/rlp"
"github.com/ethereum/go-ethereum/whisper" rpc "github.com/ethereum/go-ethereum/rpc/v2"
) )
const ( const (
@ -63,74 +56,29 @@ const (
) )
var ( var (
jsonlogger = logger.NewJsonLogger()
datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
portInUseErrRE = regexp.MustCompile("address already in use") 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 <datadir> to search for the static node list
trustedNodes = "trusted-nodes.json" // Path within <datadir> to search for the trusted node list
) )
type Config struct { type Config struct {
DevMode bool NetworkId int // Network ID to use for selecting peers to connect to
TestNet bool Genesis string // Genesis JSON to seed the chain database with
FastSync bool // Enables the state download based fast synchronisation algorithm
Name string
NetworkId int
GenesisFile string
GenesisBlock *types.Block // used by block tests
FastSync bool
Olympic bool
BlockChainVersion int BlockChainVersion int
SkipBcVersionCheck bool // e.g. blockchain export SkipBcVersionCheck bool // e.g. blockchain export
DatabaseCache int DatabaseCache int
DataDir string
LogFile string
Verbosity int
VmDebug bool
NatSpec bool NatSpec bool
DocRoot string DocRoot string
AutoDAG bool AutoDAG bool
PowTest bool PowTest bool
ExtraData []byte ExtraData []byte
MaxPeers int AccountManager *accounts.Manager
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
Etherbase common.Address Etherbase common.Address
GasPrice *big.Int GasPrice *big.Int
MinerThreads int MinerThreads int
AccountManager *accounts.Manager
SolcPath string SolcPath string
GpoMinGasPrice *big.Int GpoMinGasPrice *big.Int
@ -140,87 +88,8 @@ type Config struct {
GpobaseStepUp int GpobaseStepUp int
GpobaseCorrectionFactor int GpobaseCorrectionFactor int
// NewDB is used to create databases. TestGenesisBlock *types.Block // Genesis block to seed the chain database with (testing only!)
// If nil, the default is to create leveldb databases on disk. TestGenesisState ethdb.Database // Genesis state to seed the database with (testing only!)
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
} }
type Ethereum struct { type Ethereum struct {
@ -235,7 +104,6 @@ type Ethereum struct {
txPool *core.TxPool txPool *core.TxPool
blockchain *core.BlockChain blockchain *core.BlockChain
accountManager *accounts.Manager accountManager *accounts.Manager
whisper *whisper.Whisper
pow *ethash.Ethash pow *ethash.Ethash
protocolManager *ProtocolManager protocolManager *ProtocolManager
SolcPath string SolcPath string
@ -250,44 +118,28 @@ type Ethereum struct {
httpclient *httpclient.HTTPClient httpclient *httpclient.HTTPClient
net *p2p.Server
eventMux *event.TypeMux eventMux *event.TypeMux
miner *miner.Miner miner *miner.Miner
// logger logger.LogSystem
Mining bool Mining bool
MinerThreads int MinerThreads int
NatSpec bool NatSpec bool
DataDir string
AutoDAG bool AutoDAG bool
PowTest bool PowTest bool
autodagquit chan bool autodagquit chan bool
etherbase common.Address etherbase common.Address
clientVersion string
netVersionId int netVersionId int
shhVersionId int
} }
func New(config *Config) (*Ethereum, error) { func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
logger.New(config.DataDir, config.LogFile, config.Verbosity)
// 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) // 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 const dbCount = 3
ethdb.OpenFileLimit = 128 / (dbCount + 1) 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 // Open the chain database and perform any upgrades needed
chainDb, err := newdb(filepath.Join(config.DataDir, "chaindata")) chainDb, err := ctx.OpenDatabase("chaindata", config.DatabaseCache)
if err != nil { if err != nil {
if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { return nil, err
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)
} }
if db, ok := chainDb.(*ethdb.LDBDatabase); ok { if db, ok := chainDb.(*ethdb.LDBDatabase); ok {
db.Meter("eth/db/chaindata/") db.Meter("eth/db/chaindata/")
@ -299,56 +151,32 @@ func New(config *Config) (*Ethereum, error) {
return nil, err return nil, err
} }
dappDb, err := newdb(filepath.Join(config.DataDir, "dapp")) dappDb, err := ctx.OpenDatabase("dapp", config.DatabaseCache)
if err != nil { if err != nil {
if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { return nil, err
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)
} }
if db, ok := dappDb.(*ethdb.LDBDatabase); ok { if db, ok := dappDb.(*ethdb.LDBDatabase); ok {
db.Meter("eth/db/dapp/") 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) glog.V(logger.Info).Infof("Protocol Versions: %v, Network Id: %v", ProtocolVersions, config.NetworkId)
if len(config.GenesisFile) > 0 { // Load up any custom genesis block if requested
fr, err := os.Open(config.GenesisFile) if len(config.Genesis) > 0 {
block, err := core.WriteGenesisBlock(chainDb, strings.NewReader(config.Genesis))
if err != nil { if err != nil {
return nil, err return nil, err
} }
glog.V(logger.Info).Infof("Successfully wrote custom genesis block: %x", block.Hash())
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()) // Load up a test setup if directly injected
if config.TestGenesisState != nil {
chainDb = config.TestGenesisState
} }
if config.TestGenesisBlock != nil {
// different modes core.WriteTd(chainDb, config.TestGenesisBlock.Hash(), config.TestGenesisBlock.Difficulty())
switch { core.WriteBlock(chainDb, config.TestGenesisBlock)
case config.Olympic: core.WriteCanonicalHash(chainDb, config.TestGenesisBlock.Hash(), config.TestGenesisBlock.NumberU64())
glog.V(logger.Error).Infoln("Starting Olympic network") core.WriteHeadBlockHash(chainDb, config.TestGenesisBlock.Hash())
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
}
}
// 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.SkipBcVersionCheck { if !config.SkipBcVersionCheck {
@ -365,11 +193,9 @@ func New(config *Config) (*Ethereum, error) {
shutdownChan: make(chan bool), shutdownChan: make(chan bool),
chainDb: chainDb, chainDb: chainDb,
dappDb: dappDb, dappDb: dappDb,
eventMux: &event.TypeMux{}, eventMux: ctx.EventMux,
accountManager: config.AccountManager, accountManager: config.AccountManager,
DataDir: config.DataDir,
etherbase: config.Etherbase, etherbase: config.Etherbase,
clientVersion: config.Name, // TODO should separate from Name
netVersionId: config.NetworkId, netVersionId: config.NetworkId,
NatSpec: config.NatSpec, NatSpec: config.NatSpec,
MinerThreads: config.MinerThreads, MinerThreads: config.MinerThreads,
@ -412,46 +238,64 @@ func New(config *Config) (*Ethereum, error) {
eth.miner.SetGasPrice(config.GasPrice) eth.miner.SetGasPrice(config.GasPrice)
eth.miner.SetExtra(config.ExtraData) 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 return eth, nil
} }
// Network retrieves the underlying P2P network server. This should eventually // Apis returns the collection of RPC services the ethereum package offers.
// be moved out into a protocol independent package, but for now use an accessor. // NOTE, some of these services probably need to be moved to somewhere else.
func (s *Ethereum) Network() *p2p.Server { func (s *Ethereum) Apis() []rpc.API {
return s.net return []rpc.API{
rpc.API{
Namespace: "eth",
Version: "1.0",
Service: NewPublicEthereumApi(s),
Public: true,
}, rpc.API{
Namespace: "eth",
Version: "1.0",
Service: NewPublicAccountApi(s.AccountManager()),
Public: true,
}, rpc.API{
Namespace: "personal",
Version: "1.0",
Service: NewPrivateAccountApi(s.AccountManager()),
Public: false,
}, rpc.API{
Namespace: "eth",
Version: "1.0",
Service: core.NewPublicBlockChainApi(s.BlockChain(), s.AccountManager()),
Public: true,
}, rpc.API{
Version: "1.0",
Service: core.NewPublicTransactionPoolApi(s.TxPool(), s.ChainDb(), s.BlockChain(), s.AccountManager()),
Public: true,
}, rpc.API{
Namespace: "eth",
Version: "1.0",
Service: miner.NewPublicMinerApi(s.Miner()),
Public: true,
}, rpc.API{
Namespace: "eth",
Version: "1.0",
Service: downloader.NewPublicDownloaderApi(s.Downloader()),
Public: true,
}, rpc.API{
Namespace: "miner",
Version: "1.0",
Service: NewPrivateMinerApi(s),
Public: false,
}, rpc.API{
Namespace: "txpool",
Version: "1.0",
Service: NewPublicTxPoolApi(s),
Public: true,
}, rpc.API{
Namespace: "eth",
Version: "1.0",
Service: filters.NewPublicFilterApi(s.ChainDb(), s.EventMux()),
Public: true,
},
}
} }
func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
@ -480,86 +324,48 @@ func (s *Ethereum) StopMining() { s.miner.Stop() }
func (s *Ethereum) IsMining() bool { return s.miner.Mining() } func (s *Ethereum) IsMining() bool { return s.miner.Mining() }
func (s *Ethereum) Miner() *miner.Miner { return s.miner } 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) AccountManager() *accounts.Manager { return s.accountManager }
func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain }
func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } 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) EventMux() *event.TypeMux { return s.eventMux }
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
func (s *Ethereum) DappDb() ethdb.Database { return s.dappDb } func (s *Ethereum) DappDb() ethdb.Database { return s.dappDb }
func (s *Ethereum) IsListening() bool { return true } // Always listening 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) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
func (s *Ethereum) NetVersion() int { return s.netVersionId } 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 } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
// Start the ethereum // Protocols implements node.Service, returning all the currently configured
func (s *Ethereum) Start() error { // network protocols to start.
jsonlogger.LogJson(&logger.LogStarting{ func (s *Ethereum) Protocols() []p2p.Protocol {
ClientString: s.net.Name, return s.protocolManager.SubProtocols
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
}
// Start implements node.Service, starting all internal goroutines needed by the
// Ethereum protocol implementation.
func (s *Ethereum) Start(*p2p.Server) error {
if s.AutoDAG { if s.AutoDAG {
s.StartAutoDAG() s.StartAutoDAG()
} }
s.protocolManager.Start() s.protocolManager.Start()
if s.whisper != nil {
s.whisper.Start()
}
glog.V(logger.Info).Infoln("Server started")
return nil return nil
} }
func (s *Ethereum) StartForTest() { // Stop implements node.Service, terminating all internal goroutines used by the
jsonlogger.LogJson(&logger.LogStarting{ // Ethereum protocol.
ClientString: s.net.Name, func (s *Ethereum) Stop() error {
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()
s.blockchain.Stop() s.blockchain.Stop()
s.protocolManager.Stop() s.protocolManager.Stop()
s.txPool.Stop() s.txPool.Stop()
s.eventMux.Stop() s.eventMux.Stop()
if s.whisper != nil {
s.whisper.Stop()
}
s.StopAutoDAG() s.StopAutoDAG()
s.chainDb.Close() s.chainDb.Close()
s.dappDb.Close() s.dappDb.Close()
close(s.shutdownChan) close(s.shutdownChan)
return nil
} }
// This function will wait for a shutdown and resumes main thread execution // This function will wait for a shutdown and resumes main thread execution

64
eth/downloader/api.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

575
eth/filters/api.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

File diff suppressed because it is too large Load diff

View file

@ -85,7 +85,6 @@ func (self *JSRE) runEventLoop() {
ready := make(chan *jsTimer) ready := make(chan *jsTimer)
newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) { newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) {
delay, _ := call.Argument(1).ToInteger() delay, _ := call.Argument(1).ToInteger()
if 0 >= delay { if 0 >= delay {
delay = 1 delay = 1
@ -105,7 +104,6 @@ func (self *JSRE) runEventLoop() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
return timer, value return timer, value
} }
@ -127,8 +125,20 @@ func (self *JSRE) runEventLoop() {
} }
return otto.UndefinedValue() return otto.UndefinedValue()
} }
vm.Set("setTimeout", setTimeout) vm.Set("_setTimeout", setTimeout)
vm.Set("setInterval", setInterval) 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("clearTimeout", clearTimeout)
vm.Set("clearInterval", clearTimeout) vm.Set("clearInterval", clearTimeout)

72
miner/miner_rpc.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -327,6 +327,7 @@ func (self *worker) wait() {
go func(block *types.Block, logs vm.Logs, receipts []*types.Receipt) { go func(block *types.Block, logs vm.Logs, receipts []*types.Receipt) {
self.mux.Post(core.NewMinedBlockEvent{block}) self.mux.Post(core.NewMinedBlockEvent{block})
self.mux.Post(core.ChainEvent{block, block.Hash(), logs}) self.mux.Post(core.ChainEvent{block, block.Hash(), logs})
if stat == core.CanonStatTy { if stat == core.CanonStatTy {
self.mux.Post(core.ChainHeadEvent{block}) self.mux.Post(core.ChainHeadEvent{block})
self.mux.Post(logs) self.mux.Post(logs)

171
node/config.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

120
node/config_test.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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")
}
}

45
node/errors.go Normal file
View file

@ -0,0 +1,45 @@
// 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 <http://www.gnu.org/licenses/>.
package node
import (
"fmt"
"reflect"
)
// 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[reflect.Type]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)
}

276
node/node.go Normal file
View file

@ -0,0 +1,276 @@
// 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 <http://www.gnu.org/licenses/>.
// Package node represents the Ethereum protocol stack container.
package node
import (
"errors"
"os"
"path/filepath"
"reflect"
"sync"
"syscall"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
)
var (
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 (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
serverConfig *p2p.Server // Configuration of the underlying P2P networking layer
server *p2p.Server // Currently running P2P networking layer
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
}
// 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,
serverConfig: &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,
},
serviceFuncs: []ServiceConstructor{},
eventmux: new(event.TypeMux),
}, nil
}
// 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()
if n.server != nil {
return ErrNodeRunning
}
n.serviceFuncs = append(n.serviceFuncs, constructor)
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.server != nil {
return ErrNodeRunning
}
// Otherwise copy and specialize the P2P configuration
running := new(p2p.Server)
*running = *n.serverConfig
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[reflect.Type]Service),
EventMux: n.eventmux,
}
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
}
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 {
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 := []reflect.Type{}
for kind, service := range services {
// Start the next service, stopping all previous upon failure
if err := service.Start(running); err != nil {
for _, kind := range started {
services[kind].Stop()
}
running.Stop()
return err
}
// Mark the service started for potential cleanup
started = append(started, kind)
}
// Finish initializing the startup
n.services = services
n.server = running
n.stop = make(chan struct{})
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.server == nil {
return ErrNodeStopped
}
// Otherwise terminate all the services and the P2P server too
failure := &StopError{
Services: make(map[reflect.Type]error),
}
for kind, service := range n.services {
if err := service.Stop(); err != nil {
failure.Services[kind] = err
}
}
n.server.Stop()
n.services = nil
n.server = nil
close(n.stop)
if len(failure.Services) > 0 {
return failure
}
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 {
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.server
}
// 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 ErrNodeStopped
}
// Otherwise try to find the service to return
element := reflect.ValueOf(service).Elem()
if running, ok := n.services[element.Type()]; ok {
element.Set(reflect.ValueOf(running))
return nil
}
return ErrServiceUnknown
}
// 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.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
}

89
node/node_example_test.go Normal file
View file

@ -0,0 +1,89 @@
// 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 <http://www.gnu.org/licenses/>.
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"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
)
// 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) 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 }
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(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:
// Service starting...
// Service stopping...
// Service starting...
// Service stopping...
}

496
node/node_test.go Normal file
View file

@ -0,0 +1,496 @@
// 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 <http://www.gnu.org/licenses/>.
package node
import (
"errors"
"io/ioutil"
"os"
"reflect"
"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)
}
}
// 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)
}
// 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)
}
}
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{})
}
}
}
// 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
services := map[string]InstrumentingWrapper{
"A": InstrumentedServiceMakerA,
"B": InstrumentedServiceMakerB,
"C": InstrumentedServiceMakerC,
}
started := make(map[string]bool)
stopped := make(map[string]bool)
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 },
stopHook: func() { stopped[id] = true },
}, nil
}
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 id, _ := range services {
if !started[id] {
t.Fatalf("service %s: freshly started service not running", id)
}
if stopped[id] {
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 id, _ := range services {
if !stopped[id] {
t.Fatalf("service %s: freshly terminated service still running", id)
}
}
}
// 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(*p2p.Server) {
if running {
panic("already running")
}
running = true
started++
},
}, nil
}
// Register the service and start the protocol stack
if err := stack.Register(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
services := map[string]InstrumentingWrapper{
"A": InstrumentedServiceMakerA,
"B": InstrumentedServiceMakerB,
"C": InstrumentedServiceMakerC,
}
started := make(map[string]bool)
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(maker(constructor)); err != nil {
t.Fatalf("service %s: registration failed: %v", id, 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); 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 id, _ := range services {
if started[id] {
t.Fatalf("service %s: started should not have", id)
}
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
services := map[string]InstrumentingWrapper{
"A": InstrumentedServiceMakerA,
"B": InstrumentedServiceMakerB,
"C": InstrumentedServiceMakerC,
}
started := make(map[string]bool)
stopped := make(map[string]bool)
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 },
stopHook: func() { stopped[id] = true },
}, nil
}
if err := stack.Register(maker(constructor)); err != nil {
t.Fatalf("service %s: registration failed: %v", id, 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); 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 id, _ := range services {
if started[id] && !stopped[id] {
t.Fatalf("service %s: started but not stopped", id)
}
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
services := map[string]InstrumentingWrapper{
"A": InstrumentedServiceMakerA,
"B": InstrumentedServiceMakerB,
"C": InstrumentedServiceMakerC,
}
started := make(map[string]bool)
stopped := make(map[string]bool)
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 },
stopHook: func() { stopped[id] = true },
}, nil
}
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
failure := errors.New("fail")
failer := func(*ServiceContext) (Service, error) {
return &InstrumentedService{
stop: failure,
}, 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
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 id, _ := range services {
if !started[id] {
t.Fatalf("iter %d, service %s: service not running", i, id)
}
if stopped[id] {
t.Fatalf("iter %d, service %s: service already stopped", i, id)
}
}
// 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 {
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 id, _ := range services {
if !stopped[id] {
t.Fatalf("iter %d, service %s: service not terminated", i, id)
}
delete(started, id)
delete(stopped, id)
}
}
}
// 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(NewNoopService); err != nil {
t.Fatalf("noop service registration failed: %v", err)
}
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 err := stack.Service(&noopServ); err != ErrNodeStopped {
t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped)
}
var instServ *InstrumentedService
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 {
t.Fatalf("failed to start stack: %v", err)
}
defer stack.Stop()
if err := stack.Service(&noopServ); err != nil {
t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil)
}
if err := stack.Service(&instServ); err != nil {
t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil)
}
}
// 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]struct {
Count int
Maker InstrumentingWrapper
}{
"Zero Protocols": {0, InstrumentedServiceMakerA},
"Single Protocol": {1, InstrumentedServiceMakerB},
"Many Protocols": {25, InstrumentedServiceMakerC},
}
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)
}
constructor := func(*ServiceContext) (Service, error) {
return &InstrumentedService{
protocols: protocols,
}, nil
}
if err := stack.Register(config.Maker(constructor)); err != nil {
t.Fatalf("service %s: registration failed: %v", id, err)
}
}
// Start the services and ensure all 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, 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) {
launched = true
break
}
}
if !launched {
t.Errorf("configured protocol not launched: %s v%d", id, ver)
}
}
}
}

84
node/service.go Normal file
View file

@ -0,0 +1,84 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package node
import (
"path/filepath"
"reflect"
"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
// 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[reflect.Type]Service // Index of the already constructed services
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
}
// 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 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
}
// 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
// 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
// Stop terminates all goroutines belonging to the service, blocking until they
// are all terminated.
Stop() error
}

97
node/service_test.go Normal file
View file

@ -0,0 +1,97 @@
// 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 <http://www.gnu.org/licenses/>.
package node
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
// 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 {
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.OpenDatabase("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.OpenDatabase("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")
}
}
// 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 verifier that ensures a NoopA is before it and NoopB after
verifier := func(ctx *ServiceContext) (Service, error) {
var objA *NoopServiceA
if ctx.Service(&objA) != nil {
return nil, fmt.Errorf("former service not found")
}
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
if err := stack.Register(NewNoopServiceA); err != nil {
t.Fatalf("former failed to register service: %v", err)
}
if err := stack.Register(verifier); err != nil {
t.Fatalf("failed to register service verifier: %v", 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 {
t.Fatalf("failed to start stack: %v", err)
}
defer stack.Stop()
}

123
node/utils_test.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
// 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"
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 }
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) Apis() []rpc.API {
return nil
}
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{}))
}

View file

@ -210,7 +210,7 @@ func PubkeyID(pub *ecdsa.PublicKey) NodeID {
// Pubkey returns the public key represented by the node ID. // Pubkey returns the public key represented by the node ID.
// It returns an error if the ID is not a point on the curve. // It returns an error if the ID is not a point on the curve.
func (id NodeID) Pubkey() (*ecdsa.PublicKey, error) { 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 half := len(id) / 2
p.X.SetBytes(id[:half]) p.X.SetBytes(id[:half])
p.Y.SetBytes(id[half:]) p.Y.SetBytes(id[half:])

View file

@ -90,12 +90,11 @@ type transport interface {
// that was most recently active is the first element in entries. // that was most recently active is the first element in entries.
type bucket struct{ entries []*Node } 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 // If no node database was given, use an in-memory one
db, err := newNodeDB(nodeDBPath, Version, ourID) db, err := newNodeDB(nodeDBPath, Version, ourID)
if err != nil { if err != nil {
glog.V(logger.Warn).Infoln("Failed to open node database:", err) return nil, err
db, _ = newNodeDB("", Version, ourID)
} }
tab := &Table{ tab := &Table{
net: t, net: t,
@ -114,7 +113,7 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string
tab.buckets[i] = new(bucket) tab.buckets[i] = new(bucket)
} }
go tab.refreshLoop() go tab.refreshLoop()
return tab return tab, nil
} }
// Self returns the local node. // Self returns the local node.

View file

@ -34,7 +34,7 @@ import (
func TestTable_pingReplace(t *testing.T) { func TestTable_pingReplace(t *testing.T) {
doit := func(newNodeIsResponding, lastInBucketIsResponding bool) { doit := func(newNodeIsResponding, lastInBucketIsResponding bool) {
transport := newPingRecorder() transport := newPingRecorder()
tab := newTable(transport, NodeID{}, &net.UDPAddr{}, "") tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "")
defer tab.Close() defer tab.Close()
pingSender := newNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99) pingSender := newNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99)
@ -177,7 +177,7 @@ func TestTable_closest(t *testing.T) {
test := func(test *closeTest) bool { test := func(test *closeTest) bool {
// for any node table, Target and N // for any node table, Target and N
tab := newTable(nil, test.Self, &net.UDPAddr{}, "") tab, _ := newTable(nil, test.Self, &net.UDPAddr{}, "")
defer tab.Close() defer tab.Close()
tab.stuff(test.All) tab.stuff(test.All)
@ -236,7 +236,7 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) {
}, },
} }
test := func(buf []*Node) bool { test := func(buf []*Node) bool {
tab := newTable(nil, NodeID{}, &net.UDPAddr{}, "") tab, _ := newTable(nil, NodeID{}, &net.UDPAddr{}, "")
defer tab.Close() defer tab.Close()
for i := 0; i < len(buf); i++ { for i := 0; i < len(buf); i++ {
ld := cfg.Rand.Intn(len(tab.buckets)) 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) { func TestTable_Lookup(t *testing.T) {
self := nodeAtDistance(common.Hash{}, 0) self := nodeAtDistance(common.Hash{}, 0)
tab := newTable(lookupTestnet, self.ID, &net.UDPAddr{}, "") tab, _ := newTable(lookupTestnet, self.ID, &net.UDPAddr{}, "")
defer tab.Close() defer tab.Close()
// lookup on empty table returns no nodes // lookup on empty table returns no nodes

View file

@ -200,12 +200,15 @@ func ListenUDP(priv *ecdsa.PrivateKey, laddr string, natm nat.Interface, nodeDBP
if err != nil { if err != nil {
return nil, err 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) glog.V(logger.Info).Infoln("Listening,", tab.self)
return tab, nil 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{ udp := &udp{
conn: c, conn: c,
priv: priv, priv: priv,
@ -225,10 +228,15 @@ func newUDP(priv *ecdsa.PrivateKey, c conn, natm nat.Interface, nodeDBPath strin
} }
// TODO: separate TCP port // TODO: separate TCP port
udp.ourEndpoint = makeEndpoint(realaddr, uint16(realaddr.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.loop()
go udp.readLoop() go udp.readLoop()
return udp.Table, udp return udp.Table, udp, nil
} }
func (t *udp) close() { func (t *udp) close() {

View file

@ -69,7 +69,7 @@ func newUDPTest(t *testing.T) *udpTest {
remotekey: newkey(), remotekey: newkey(),
remoteaddr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 30303}, 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 return test
} }

View file

@ -277,7 +277,7 @@ func newInitiatorHandshake(remoteID discover.NodeID) (*encHandshake, error) {
return nil, err return nil, err
} }
// generate random keypair to use for signing // 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 { if err != nil {
return nil, err return nil, err
} }
@ -376,7 +376,7 @@ func decodeAuthMsg(prv *ecdsa.PrivateKey, token []byte, auth []byte) (*encHandsh
var err error var err error
h := new(encHandshake) h := new(encHandshake)
// generate random keypair for session // 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -93,6 +93,7 @@ func testEncHandshake(token []byte) error {
go func() { go func() {
r := result{side: "initiator"} r := result{side: "initiator"}
defer func() { output <- r }() defer func() { output <- r }()
defer fd0.Close()
dest := &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey)} dest := &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey)}
r.id, r.err = c0.doEncHandshake(prv0, dest) r.id, r.err = c0.doEncHandshake(prv0, dest)
@ -107,6 +108,7 @@ func testEncHandshake(token []byte) error {
go func() { go func() {
r := result{side: "receiver"} r := result{side: "receiver"}
defer func() { output <- r }() defer func() { output <- r }()
defer fd1.Close()
r.id, r.err = c1.doEncHandshake(prv1, nil) r.id, r.err = c1.doEncHandshake(prv1, nil)
if r.err != nil { if r.err != nil {

View file

@ -32,6 +32,8 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/logger/glog" "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/rlp"
"github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms" "github.com/ethereum/go-ethereum/rpc/comms"
@ -80,19 +82,24 @@ type adminhandler func(*adminApi, *shared.Request) (interface{}, error)
// admin api provider // admin api provider
type adminApi struct { type adminApi struct {
xeth *xeth.XEth xeth *xeth.XEth
stack *node.Node
ethereum *eth.Ethereum ethereum *eth.Ethereum
codec codec.Codec codec codec.Codec
coder codec.ApiCoder coder codec.ApiCoder
} }
// create a new admin api instance // create a new admin api instance
func NewAdminApi(xeth *xeth.XEth, ethereum *eth.Ethereum, codec codec.Codec) *adminApi { func NewAdminApi(xeth *xeth.XEth, stack *node.Node, codec codec.Codec) *adminApi {
return &adminApi{ api := &adminApi{
xeth: xeth, xeth: xeth,
ethereum: ethereum, stack: stack,
codec: codec, codec: codec,
coder: codec.New(nil), coder: codec.New(nil),
} }
if stack != nil {
stack.Service(&api.ethereum)
}
return api
} }
// collection with supported methods // 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 { if err := self.coder.Decode(req.Params, &args); err != nil {
return nil, shared.NewDecodeParamError(err.Error()) return nil, shared.NewDecodeParamError(err.Error())
} }
node, err := discover.ParseNode(args.Url)
err := self.ethereum.AddPeer(args.Url) if err != nil {
if err == nil { return nil, fmt.Errorf("invalid node URL: %v", err)
return true, nil
} }
return false, err self.stack.Server().AddPeer(node)
return true, nil
} }
func (self *adminApi) Peers(req *shared.Request) (interface{}, error) { 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) { 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) { 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 { 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, 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 { if err != nil {
return false, err return false, err
} }

View file

@ -93,7 +93,7 @@ func TestCompileSolidity(t *testing.T) {
expSource := source expSource := source
eth := &eth.Ethereum{} eth := &eth.Ethereum{}
xeth := xeth.NewTest(eth, nil) xeth := xeth.NewTest(nil, nil)
api := NewEthApi(xeth, eth, codec.JSON) api := NewEthApi(xeth, eth, codec.JSON)
var rpcRequest shared.Request var rpcRequest shared.Request

View file

@ -39,11 +39,13 @@ func newMergedApi(apis ...shared.EthereumApi) *MergedApi {
mergedApi.methods = make(map[string]shared.EthereumApi) mergedApi.methods = make(map[string]shared.EthereumApi)
for _, api := range apis { for _, api := range apis {
if api != nil {
mergedApi.apis[api.Name()] = api.ApiVersion() mergedApi.apis[api.Name()] = api.ApiVersion()
for _, method := range api.Methods() { for _, method := range api.Methods() {
mergedApi.methods[method] = api mergedApi.methods[method] = api
} }
} }
}
return mergedApi return mergedApi
} }

View file

@ -33,6 +33,11 @@ web3._extend({
call: 'personal_unlockAccount', call: 'personal_unlockAccount',
params: 3, params: 3,
inputFormatter: [null, null, null] inputFormatter: [null, null, null]
}),
new web3._extend.Method({
name: 'lockAccount',
call: 'personal_lockAccount',
params: 1
}) })
], ],
properties: properties:

View file

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/eth" "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/codec"
"github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/rpc/shared"
"github.com/ethereum/go-ethereum/xeth" "github.com/ethereum/go-ethereum/xeth"
@ -53,6 +54,13 @@ var (
"startRPC", "startRPC",
"stopNatSpec", "stopNatSpec",
"stopRPC", "stopRPC",
"setGlobalRegistrar",
"setHashReg",
"setUrlHint",
"saveInfo",
"getContractInfo",
"sleep",
"httpGet",
"verbosity", "verbosity",
}, },
"db": []string{ "db": []string{
@ -101,7 +109,7 @@ var (
"hashrate", "hashrate",
"mining", "mining",
"namereg", "namereg",
"pendingTransactions", "getPendingTransactions",
"resend", "resend",
"sendRawTransaction", "sendRawTransaction",
"sendTransaction", "sendTransaction",
@ -115,6 +123,7 @@ var (
"setExtra", "setExtra",
"setGasPrice", "setGasPrice",
"startAutoDAG", "startAutoDAG",
"setEtherbase",
"start", "start",
"stopAutoDAG", "stopAutoDAG",
"stop", "stop",
@ -154,7 +163,7 @@ var (
) )
// Parse a comma separated API string to individual api's // 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 { if len(strings.TrimSpace(apistr)) == 0 {
return nil, fmt.Errorf("Empty apistr provided") return nil, fmt.Errorf("Empty apistr provided")
} }
@ -162,10 +171,16 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth.
names := strings.Split(apistr, ",") names := strings.Split(apistr, ",")
apis := make([]shared.EthereumApi, len(names)) apis := make([]shared.EthereumApi, len(names))
var eth *eth.Ethereum
if stack != nil {
if err := stack.Service(&eth); err != nil {
return nil, err
}
}
for i, name := range names { for i, name := range names {
switch strings.ToLower(strings.TrimSpace(name)) { switch strings.ToLower(strings.TrimSpace(name)) {
case shared.AdminApiName: case shared.AdminApiName:
apis[i] = NewAdminApi(xeth, eth, codec) apis[i] = NewAdminApi(xeth, stack, codec)
case shared.DebugApiName: case shared.DebugApiName:
apis[i] = NewDebugApi(xeth, eth, codec) apis[i] = NewDebugApi(xeth, eth, codec)
case shared.DbApiName: case shared.DbApiName:
@ -184,11 +199,12 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth.
apis[i] = NewPersonalApi(xeth, eth, codec) apis[i] = NewPersonalApi(xeth, eth, codec)
case shared.Web3ApiName: case shared.Web3ApiName:
apis[i] = NewWeb3Api(xeth, codec) apis[i] = NewWeb3Api(xeth, codec)
case "rpc": // gives information about the RPC interface
continue
default: default:
return nil, fmt.Errorf("Unknown API '%s'", name) return nil, fmt.Errorf("Unknown API '%s'", name)
} }
} }
return apis, nil return apis, nil
} }

View file

@ -69,13 +69,28 @@ func (self *ipcClient) SupportedModules() (map[string]string, error) {
req := shared.Request{ req := shared.Request{
Id: 1, Id: 1,
Jsonrpc: "2.0", Jsonrpc: "2.0",
Method: "modules", Method: "rpc_modules",
} }
if err := self.coder.WriteResponse(req); err != nil { if err := self.coder.WriteResponse(req); err != nil {
return nil, err 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() res, err := self.coder.ReadResponse()
if err != nil { if err != nil {
return nil, err return nil, err
@ -108,6 +123,11 @@ func StartIpc(cfg IpcConfig, codec codec.Codec, initializer InitFunc) error {
return nil 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) { func ipcLoop(cfg IpcConfig, codec codec.Codec, initializer InitFunc, l net.Listener) {
glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint) glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint)
defer os.Remove(cfg.Endpoint) defer os.Remove(cfg.Endpoint)

View file

@ -54,6 +54,78 @@ func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface
return res 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) { func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
reqif, err := call.Argument(0).Export() reqif, err := call.Argument(0).Export()
if err != nil { if err != nil {

102
rpc/v2/doc.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
/*
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

85
rpc/v2/errors.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

336
rpc/v2/json.go Normal file
View file

@ -0,0 +1,336 @@
// 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 <http://www.gnu.org/licenses/>.
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"`
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()}
}
// 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 {
// (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, &params); 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
}

73
rpc/v2/json_test.go Normal file
View file

@ -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())
}
}

459
rpc/v2/server.go Normal file
View file

@ -0,0 +1,459 @@
// 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 <http://www.gnu.org/licenses/>.
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
}
// exec executes the given request and writes the result back using the codec.
func (s *Server) exec(codec ServerCodec, req *serverRequest) {
if req.err != nil { // error during request parsing
rpcErr := codec.CreateErrorResponse(&req.id, req.err)
if err := codec.Write(rpcErr); err != nil {
codec.Close()
}
return
}
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) {
if err := codec.Write(codec.CreateResponse(req.id, true)); err != nil {
codec.Close()
}
} else {
rpcErr := codec.CreateErrorResponse(&req.id,
&callbackError{fmt.Sprintf("subscription '%s' not found", subid)})
if err := codec.Write(rpcErr); err != nil {
codec.Close()
}
}
} else {
rpcErr := codec.CreateErrorResponse(&req.id, &invalidParamsError{"Expected subscription id as argument"})
if err := codec.Write(rpcErr); err != nil {
codec.Close()
}
}
return
}
if req.callb.isSubscribe {
subid, err := s.createSubscription(codec, req)
var response interface{}
if err == nil {
response = codec.CreateResponse(req.id, subid)
} else {
response = codec.CreateErrorResponse(&req.id, &callbackError{err.Error()})
}
if err = codec.Write(response); err != nil {
codec.Close()
}
return
}
// 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))}
res := codec.CreateErrorResponse(&req.id, rpcErr)
if err := codec.Write(res); err != nil {
codec.Close()
}
return
}
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 {
if err := codec.Write(codec.CreateResponse(req.id, nil)); err != nil {
codec.Close()
}
return
}
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()})
if err := codec.Write(res); err != nil {
codec.Close()
}
return
}
}
if err := codec.Write(codec.CreateResponse(req.id, reply[0].Interface())); err != nil {
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 { // error during parsing of request
responses[i] = codec.CreateErrorResponse(&req.id, req.err)
continue
}
if req.isUnsubscribe {
if len(req.args) == 1 && req.args[0].Kind() == reflect.String {
subid := req.args[0].String()
if s.unsubscribe(subid) {
responses[i] = codec.CreateResponse(req.id, true)
} else {
e := &callbackError{fmt.Sprintf("subscription '%s' not found", subid)}
responses[i] = codec.CreateErrorResponse(&req.id, e)
}
} else {
e := &invalidParamsError{"Expected subscription id as argument"}
responses[i] = codec.CreateErrorResponse(&req.id, e)
}
continue
}
if req.callb.isSubscribe {
subid, err := s.createSubscription(codec, req)
var response interface{}
if err == nil {
response = codec.CreateResponse(req.id, subid)
} else {
response = codec.CreateErrorResponse(&req.id, &callbackError{err.Error()})
}
responses[i] = response
continue
}
var reply []reflect.Value
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))}
responses[i] = codec.CreateErrorResponse(&req.id, rpcErr)
continue
}
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 {
responses[i] = codec.CreateResponse(req.id, nil)
continue
}
if req.callb.errPos >= 0 {
if !reply[req.callb.errPos].IsNil() {
if e, ok := reply[req.callb.errPos].Interface().(error); ok {
rpcErr := &callbackError{e.Error()}
responses[i] = codec.CreateErrorResponse(&req.id, rpcErr)
continue
}
}
}
responses[i] = codec.CreateResponse(req.id, reply[0].Interface())
}
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
}

218
rpc/v2/server_test.go Normal file
View file

@ -0,0 +1,218 @@
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, &params); 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)
}
req := jsonRequest{
Method: "echo",
Version: "2.0",
Id: 12345,
}
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)
}
}

352
rpc/v2/types.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}

57
rpc/v2/types_test.go Normal file
View file

@ -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)
}
}

205
rpc/v2/utils.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

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