mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 14:16:44 +00:00
Merge a898f45f68 into c541b38fb3
This commit is contained in:
commit
31b37fb4d0
253 changed files with 32446 additions and 4010 deletions
|
|
@ -20,10 +20,6 @@ env:
|
||||||
global:
|
global:
|
||||||
- secure: "U2U1AmkU4NJBgKR/uUAebQY87cNL0+1JHjnLOmmXwxYYyj5ralWb1aSuSH3qSXiT93qLBmtaUkuv9fberHVqrbAeVlztVdUsKAq7JMQH+M99iFkC9UiRMqHmtjWJ0ok4COD1sRYixxi21wb/JrMe3M1iL4QJVS61iltjHhVdM64="
|
- secure: "U2U1AmkU4NJBgKR/uUAebQY87cNL0+1JHjnLOmmXwxYYyj5ralWb1aSuSH3qSXiT93qLBmtaUkuv9fberHVqrbAeVlztVdUsKAq7JMQH+M99iFkC9UiRMqHmtjWJ0ok4COD1sRYixxi21wb/JrMe3M1iL4QJVS61iltjHhVdM64="
|
||||||
sudo: false
|
sudo: false
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- libgmp3-dev
|
|
||||||
notifications:
|
notifications:
|
||||||
webhooks:
|
webhooks:
|
||||||
urls:
|
urls:
|
||||||
|
|
|
||||||
6
Godeps/Godeps.json
generated
6
Godeps/Godeps.json
generated
|
|
@ -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",
|
||||||
|
|
@ -101,6 +101,10 @@
|
||||||
"ImportPath": "golang.org/x/crypto/scrypt",
|
"ImportPath": "golang.org/x/crypto/scrypt",
|
||||||
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
|
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/context",
|
||||||
|
"Rev": "e0403b4e005737430c05a57aac078479844f919c"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/net/html",
|
"ImportPath": "golang.org/x/net/html",
|
||||||
"Rev": "e0403b4e005737430c05a57aac078479844f919c"
|
"Rev": "e0403b4e005737430c05a57aac078479844f919c"
|
||||||
|
|
|
||||||
2
Godeps/_workspace/src/github.com/nsf/termbox-go/README.md
generated
vendored
2
Godeps/_workspace/src/github.com/nsf/termbox-go/README.md
generated
vendored
|
|
@ -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)
|
||||||
|
|
|
||||||
24
Godeps/_workspace/src/github.com/nsf/termbox-go/api.go
generated
vendored
24
Godeps/_workspace/src/github.com/nsf/termbox-go/api.go
generated
vendored
|
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
4
Godeps/_workspace/src/github.com/nsf/termbox-go/api_windows.go
generated
vendored
4
Godeps/_workspace/src/github.com/nsf/termbox-go/api_windows.go
generated
vendored
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
14
Godeps/_workspace/src/github.com/nsf/termbox-go/termbox.go
generated
vendored
14
Godeps/_workspace/src/github.com/nsf/termbox-go/termbox.go
generated
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
4
Godeps/_workspace/src/github.com/nsf/termbox-go/termbox_windows.go
generated
vendored
4
Godeps/_workspace/src/github.com/nsf/termbox-go/termbox_windows.go
generated
vendored
|
|
@ -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) {
|
||||||
|
|
|
||||||
447
Godeps/_workspace/src/golang.org/x/net/context/context.go
generated
vendored
Normal file
447
Godeps/_workspace/src/golang.org/x/net/context/context.go
generated
vendored
Normal file
|
|
@ -0,0 +1,447 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package context defines the Context type, which carries deadlines,
|
||||||
|
// cancelation signals, and other request-scoped values across API boundaries
|
||||||
|
// and between processes.
|
||||||
|
//
|
||||||
|
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||||
|
// servers should accept a Context. The chain of function calls between must
|
||||||
|
// propagate the Context, optionally replacing it with a modified copy created
|
||||||
|
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
||||||
|
//
|
||||||
|
// Programs that use Contexts should follow these rules to keep interfaces
|
||||||
|
// consistent across packages and enable static analysis tools to check context
|
||||||
|
// propagation:
|
||||||
|
//
|
||||||
|
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||||
|
// explicitly to each function that needs it. The Context should be the first
|
||||||
|
// parameter, typically named ctx:
|
||||||
|
//
|
||||||
|
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||||
|
// // ... use ctx ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
||||||
|
// if you are unsure about which Context to use.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
//
|
||||||
|
// The same Context may be passed to functions running in different goroutines;
|
||||||
|
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// See http://blog.golang.org/context for example code for a server that uses
|
||||||
|
// Contexts.
|
||||||
|
package context // import "golang.org/x/net/context"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Context carries a deadline, a cancelation signal, and other values across
|
||||||
|
// API boundaries.
|
||||||
|
//
|
||||||
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
type Context interface {
|
||||||
|
// Deadline returns the time when work done on behalf of this context
|
||||||
|
// should be canceled. Deadline returns ok==false when no deadline is
|
||||||
|
// set. Successive calls to Deadline return the same results.
|
||||||
|
Deadline() (deadline time.Time, ok bool)
|
||||||
|
|
||||||
|
// Done returns a channel that's closed when work done on behalf of this
|
||||||
|
// context should be canceled. Done may return nil if this context can
|
||||||
|
// never be canceled. Successive calls to Done return the same value.
|
||||||
|
//
|
||||||
|
// WithCancel arranges for Done to be closed when cancel is called;
|
||||||
|
// WithDeadline arranges for Done to be closed when the deadline
|
||||||
|
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||||
|
// elapses.
|
||||||
|
//
|
||||||
|
// Done is provided for use in select statements:
|
||||||
|
//
|
||||||
|
// // Stream generates values with DoSomething and sends them to out
|
||||||
|
// // until DoSomething returns an error or ctx.Done is closed.
|
||||||
|
// func Stream(ctx context.Context, out <-chan Value) error {
|
||||||
|
// for {
|
||||||
|
// v, err := DoSomething(ctx)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// return ctx.Err()
|
||||||
|
// case out <- v:
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||||
|
// a Done channel for cancelation.
|
||||||
|
Done() <-chan struct{}
|
||||||
|
|
||||||
|
// Err returns a non-nil error value after Done is closed. Err returns
|
||||||
|
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||||
|
// context's deadline passed. No other values for Err are defined.
|
||||||
|
// After Done is closed, successive calls to Err return the same value.
|
||||||
|
Err() error
|
||||||
|
|
||||||
|
// Value returns the value associated with this context for key, or nil
|
||||||
|
// if no value is associated with key. Successive calls to Value with
|
||||||
|
// the same key returns the same result.
|
||||||
|
//
|
||||||
|
// Use context values only for request-scoped data that transits
|
||||||
|
// processes and API boundaries, not for passing optional parameters to
|
||||||
|
// functions.
|
||||||
|
//
|
||||||
|
// A key identifies a specific value in a Context. Functions that wish
|
||||||
|
// to store values in Context typically allocate a key in a global
|
||||||
|
// variable then use that key as the argument to context.WithValue and
|
||||||
|
// Context.Value. A key can be any type that supports equality;
|
||||||
|
// packages should define keys as an unexported type to avoid
|
||||||
|
// collisions.
|
||||||
|
//
|
||||||
|
// Packages that define a Context key should provide type-safe accessors
|
||||||
|
// for the values stores using that key:
|
||||||
|
//
|
||||||
|
// // Package user defines a User type that's stored in Contexts.
|
||||||
|
// package user
|
||||||
|
//
|
||||||
|
// import "golang.org/x/net/context"
|
||||||
|
//
|
||||||
|
// // User is the type of value stored in the Contexts.
|
||||||
|
// type User struct {...}
|
||||||
|
//
|
||||||
|
// // key is an unexported type for keys defined in this package.
|
||||||
|
// // This prevents collisions with keys defined in other packages.
|
||||||
|
// type key int
|
||||||
|
//
|
||||||
|
// // userKey is the key for user.User values in Contexts. It is
|
||||||
|
// // unexported; clients use user.NewContext and user.FromContext
|
||||||
|
// // instead of using this key directly.
|
||||||
|
// var userKey key = 0
|
||||||
|
//
|
||||||
|
// // NewContext returns a new Context that carries value u.
|
||||||
|
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||||
|
// return context.WithValue(ctx, userKey, u)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // FromContext returns the User value stored in ctx, if any.
|
||||||
|
// func FromContext(ctx context.Context) (*User, bool) {
|
||||||
|
// u, ok := ctx.Value(userKey).(*User)
|
||||||
|
// return u, ok
|
||||||
|
// }
|
||||||
|
Value(key interface{}) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||||
|
var Canceled = errors.New("context canceled")
|
||||||
|
|
||||||
|
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||||
|
// deadline passes.
|
||||||
|
var DeadlineExceeded = errors.New("context deadline exceeded")
|
||||||
|
|
||||||
|
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||||
|
// struct{}, since vars of this type must have distinct addresses.
|
||||||
|
type emptyCtx int
|
||||||
|
|
||||||
|
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*emptyCtx) Done() <-chan struct{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*emptyCtx) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*emptyCtx) Value(key interface{}) interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *emptyCtx) String() string {
|
||||||
|
switch e {
|
||||||
|
case background:
|
||||||
|
return "context.Background"
|
||||||
|
case todo:
|
||||||
|
return "context.TODO"
|
||||||
|
}
|
||||||
|
return "unknown empty Context"
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
background = new(emptyCtx)
|
||||||
|
todo = new(emptyCtx)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||||
|
// values, and has no deadline. It is typically used by the main function,
|
||||||
|
// initialization, and tests, and as the top-level Context for incoming
|
||||||
|
// requests.
|
||||||
|
func Background() Context {
|
||||||
|
return background
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||||
|
// it's unclear which Context to use or it's is not yet available (because the
|
||||||
|
// surrounding function has not yet been extended to accept a Context
|
||||||
|
// parameter). TODO is recognized by static analysis tools that determine
|
||||||
|
// whether Contexts are propagated correctly in a program.
|
||||||
|
func TODO() Context {
|
||||||
|
return todo
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
|
// A CancelFunc does not wait for the work to stop.
|
||||||
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||||
|
type CancelFunc func()
|
||||||
|
|
||||||
|
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||||
|
// context's Done channel is closed when the returned cancel function is called
|
||||||
|
// or when the parent context's Done channel is closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||||
|
c := newCancelCtx(parent)
|
||||||
|
propagateCancel(parent, &c)
|
||||||
|
return &c, func() { c.cancel(true, Canceled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCancelCtx returns an initialized cancelCtx.
|
||||||
|
func newCancelCtx(parent Context) cancelCtx {
|
||||||
|
return cancelCtx{
|
||||||
|
Context: parent,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// propagateCancel arranges for child to be canceled when parent is.
|
||||||
|
func propagateCancel(parent Context, child canceler) {
|
||||||
|
if parent.Done() == nil {
|
||||||
|
return // parent is never canceled
|
||||||
|
}
|
||||||
|
if p, ok := parentCancelCtx(parent); ok {
|
||||||
|
p.mu.Lock()
|
||||||
|
if p.err != nil {
|
||||||
|
// parent has already been canceled
|
||||||
|
child.cancel(false, p.err)
|
||||||
|
} else {
|
||||||
|
if p.children == nil {
|
||||||
|
p.children = make(map[canceler]bool)
|
||||||
|
}
|
||||||
|
p.children[child] = true
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-parent.Done():
|
||||||
|
child.cancel(false, parent.Err())
|
||||||
|
case <-child.Done():
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parentCancelCtx follows a chain of parent references until it finds a
|
||||||
|
// *cancelCtx. This function understands how each of the concrete types in this
|
||||||
|
// package represents its parent.
|
||||||
|
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
||||||
|
for {
|
||||||
|
switch c := parent.(type) {
|
||||||
|
case *cancelCtx:
|
||||||
|
return c, true
|
||||||
|
case *timerCtx:
|
||||||
|
return &c.cancelCtx, true
|
||||||
|
case *valueCtx:
|
||||||
|
parent = c.Context
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeChild removes a context from its parent.
|
||||||
|
func removeChild(parent Context, child canceler) {
|
||||||
|
p, ok := parentCancelCtx(parent)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
if p.children != nil {
|
||||||
|
delete(p.children, child)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A canceler is a context type that can be canceled directly. The
|
||||||
|
// implementations are *cancelCtx and *timerCtx.
|
||||||
|
type canceler interface {
|
||||||
|
cancel(removeFromParent bool, err error)
|
||||||
|
Done() <-chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
||||||
|
// that implement canceler.
|
||||||
|
type cancelCtx struct {
|
||||||
|
Context
|
||||||
|
|
||||||
|
done chan struct{} // closed by the first cancel call.
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
children map[canceler]bool // set to nil by the first cancel call
|
||||||
|
err error // set to non-nil by the first cancel call
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCtx) Done() <-chan struct{} {
|
||||||
|
return c.done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCtx) Err() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCtx) String() string {
|
||||||
|
return fmt.Sprintf("%v.WithCancel", c.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel closes c.done, cancels each of c's children, and, if
|
||||||
|
// removeFromParent is true, removes c from its parent's children.
|
||||||
|
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
||||||
|
if err == nil {
|
||||||
|
panic("context: internal error: missing cancel error")
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.err != nil {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return // already canceled
|
||||||
|
}
|
||||||
|
c.err = err
|
||||||
|
close(c.done)
|
||||||
|
for child := range c.children {
|
||||||
|
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||||
|
child.cancel(false, err)
|
||||||
|
}
|
||||||
|
c.children = nil
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
if removeFromParent {
|
||||||
|
removeChild(c.Context, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||||
|
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||||
|
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||||
|
// context's Done channel is closed when the deadline expires, when the returned
|
||||||
|
// cancel function is called, or when the parent context's Done channel is
|
||||||
|
// closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||||
|
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
||||||
|
// The current deadline is already sooner than the new one.
|
||||||
|
return WithCancel(parent)
|
||||||
|
}
|
||||||
|
c := &timerCtx{
|
||||||
|
cancelCtx: newCancelCtx(parent),
|
||||||
|
deadline: deadline,
|
||||||
|
}
|
||||||
|
propagateCancel(parent, c)
|
||||||
|
d := deadline.Sub(time.Now())
|
||||||
|
if d <= 0 {
|
||||||
|
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
||||||
|
return c, func() { c.cancel(true, Canceled) }
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if c.err == nil {
|
||||||
|
c.timer = time.AfterFunc(d, func() {
|
||||||
|
c.cancel(true, DeadlineExceeded)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c, func() { c.cancel(true, Canceled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
||||||
|
// implement Done and Err. It implements cancel by stopping its timer then
|
||||||
|
// delegating to cancelCtx.cancel.
|
||||||
|
type timerCtx struct {
|
||||||
|
cancelCtx
|
||||||
|
timer *time.Timer // Under cancelCtx.mu.
|
||||||
|
|
||||||
|
deadline time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return c.deadline, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *timerCtx) String() string {
|
||||||
|
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
||||||
|
c.cancelCtx.cancel(false, err)
|
||||||
|
if removeFromParent {
|
||||||
|
// Remove this timerCtx from its parent cancelCtx's children.
|
||||||
|
removeChild(c.cancelCtx.Context, c)
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.timer != nil {
|
||||||
|
c.timer.Stop()
|
||||||
|
c.timer = nil
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete:
|
||||||
|
//
|
||||||
|
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||||
|
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||||
|
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||||
|
// return slowOperation(ctx)
|
||||||
|
// }
|
||||||
|
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||||
|
return WithDeadline(parent, time.Now().Add(timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue returns a copy of parent in which the value associated with key is
|
||||||
|
// val.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||||
|
return &valueCtx{parent, key, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A valueCtx carries a key-value pair. It implements Value for that key and
|
||||||
|
// delegates all other calls to the embedded Context.
|
||||||
|
type valueCtx struct {
|
||||||
|
Context
|
||||||
|
key, val interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *valueCtx) String() string {
|
||||||
|
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *valueCtx) Value(key interface{}) interface{} {
|
||||||
|
if c.key == key {
|
||||||
|
return c.val
|
||||||
|
}
|
||||||
|
return c.Context.Value(key)
|
||||||
|
}
|
||||||
575
Godeps/_workspace/src/golang.org/x/net/context/context_test.go
generated
vendored
Normal file
575
Godeps/_workspace/src/golang.org/x/net/context/context_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,575 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// otherContext is a Context that's not one of the types defined in context.go.
|
||||||
|
// This lets us test code paths that differ based on the underlying type of the
|
||||||
|
// Context.
|
||||||
|
type otherContext struct {
|
||||||
|
Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackground(t *testing.T) {
|
||||||
|
c := Background()
|
||||||
|
if c == nil {
|
||||||
|
t.Fatalf("Background returned nil")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case x := <-c.Done():
|
||||||
|
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if got, want := fmt.Sprint(c), "context.Background"; got != want {
|
||||||
|
t.Errorf("Background().String() = %q want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTODO(t *testing.T) {
|
||||||
|
c := TODO()
|
||||||
|
if c == nil {
|
||||||
|
t.Fatalf("TODO returned nil")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case x := <-c.Done():
|
||||||
|
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if got, want := fmt.Sprint(c), "context.TODO"; got != want {
|
||||||
|
t.Errorf("TODO().String() = %q want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithCancel(t *testing.T) {
|
||||||
|
c1, cancel := WithCancel(Background())
|
||||||
|
|
||||||
|
if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
|
||||||
|
t.Errorf("c1.String() = %q want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
o := otherContext{c1}
|
||||||
|
c2, _ := WithCancel(o)
|
||||||
|
contexts := []Context{c1, o, c2}
|
||||||
|
|
||||||
|
for i, c := range contexts {
|
||||||
|
if d := c.Done(); d == nil {
|
||||||
|
t.Errorf("c[%d].Done() == %v want non-nil", i, d)
|
||||||
|
}
|
||||||
|
if e := c.Err(); e != nil {
|
||||||
|
t.Errorf("c[%d].Err() == %v want nil", i, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case x := <-c.Done():
|
||||||
|
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
time.Sleep(100 * time.Millisecond) // let cancelation propagate
|
||||||
|
|
||||||
|
for i, c := range contexts {
|
||||||
|
select {
|
||||||
|
case <-c.Done():
|
||||||
|
default:
|
||||||
|
t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
|
||||||
|
}
|
||||||
|
if e := c.Err(); e != Canceled {
|
||||||
|
t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParentFinishesChild(t *testing.T) {
|
||||||
|
// Context tree:
|
||||||
|
// parent -> cancelChild
|
||||||
|
// parent -> valueChild -> timerChild
|
||||||
|
parent, cancel := WithCancel(Background())
|
||||||
|
cancelChild, stop := WithCancel(parent)
|
||||||
|
defer stop()
|
||||||
|
valueChild := WithValue(parent, "key", "value")
|
||||||
|
timerChild, stop := WithTimeout(valueChild, 10000*time.Hour)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case x := <-parent.Done():
|
||||||
|
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
||||||
|
case x := <-cancelChild.Done():
|
||||||
|
t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
|
||||||
|
case x := <-timerChild.Done():
|
||||||
|
t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
|
||||||
|
case x := <-valueChild.Done():
|
||||||
|
t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// The parent's children should contain the two cancelable children.
|
||||||
|
pc := parent.(*cancelCtx)
|
||||||
|
cc := cancelChild.(*cancelCtx)
|
||||||
|
tc := timerChild.(*timerCtx)
|
||||||
|
pc.mu.Lock()
|
||||||
|
if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] {
|
||||||
|
t.Errorf("bad linkage: pc.children = %v, want %v and %v",
|
||||||
|
pc.children, cc, tc)
|
||||||
|
}
|
||||||
|
pc.mu.Unlock()
|
||||||
|
|
||||||
|
if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
|
||||||
|
t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
|
||||||
|
}
|
||||||
|
if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
|
||||||
|
t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
pc.mu.Lock()
|
||||||
|
if len(pc.children) != 0 {
|
||||||
|
t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
|
||||||
|
}
|
||||||
|
pc.mu.Unlock()
|
||||||
|
|
||||||
|
// parent and children should all be finished.
|
||||||
|
check := func(ctx Context, name string) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
default:
|
||||||
|
t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
|
||||||
|
}
|
||||||
|
if e := ctx.Err(); e != Canceled {
|
||||||
|
t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(parent, "parent")
|
||||||
|
check(cancelChild, "cancelChild")
|
||||||
|
check(valueChild, "valueChild")
|
||||||
|
check(timerChild, "timerChild")
|
||||||
|
|
||||||
|
// WithCancel should return a canceled context on a canceled parent.
|
||||||
|
precanceledChild := WithValue(parent, "key", "value")
|
||||||
|
select {
|
||||||
|
case <-precanceledChild.Done():
|
||||||
|
default:
|
||||||
|
t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
|
||||||
|
}
|
||||||
|
if e := precanceledChild.Err(); e != Canceled {
|
||||||
|
t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChildFinishesFirst(t *testing.T) {
|
||||||
|
cancelable, stop := WithCancel(Background())
|
||||||
|
defer stop()
|
||||||
|
for _, parent := range []Context{Background(), cancelable} {
|
||||||
|
child, cancel := WithCancel(parent)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case x := <-parent.Done():
|
||||||
|
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
||||||
|
case x := <-child.Done():
|
||||||
|
t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
cc := child.(*cancelCtx)
|
||||||
|
pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
|
||||||
|
if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
|
||||||
|
t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pcok {
|
||||||
|
pc.mu.Lock()
|
||||||
|
if len(pc.children) != 1 || !pc.children[cc] {
|
||||||
|
t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
|
||||||
|
}
|
||||||
|
pc.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
if pcok {
|
||||||
|
pc.mu.Lock()
|
||||||
|
if len(pc.children) != 0 {
|
||||||
|
t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
|
||||||
|
}
|
||||||
|
pc.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// child should be finished.
|
||||||
|
select {
|
||||||
|
case <-child.Done():
|
||||||
|
default:
|
||||||
|
t.Errorf("<-child.Done() blocked, but shouldn't have")
|
||||||
|
}
|
||||||
|
if e := child.Err(); e != Canceled {
|
||||||
|
t.Errorf("child.Err() == %v want %v", e, Canceled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent should not be finished.
|
||||||
|
select {
|
||||||
|
case x := <-parent.Done():
|
||||||
|
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if e := parent.Err(); e != nil {
|
||||||
|
t.Errorf("parent.Err() == %v want nil", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDeadline(c Context, wait time.Duration, t *testing.T) {
|
||||||
|
select {
|
||||||
|
case <-time.After(wait):
|
||||||
|
t.Fatalf("context should have timed out")
|
||||||
|
case <-c.Done():
|
||||||
|
}
|
||||||
|
if e := c.Err(); e != DeadlineExceeded {
|
||||||
|
t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeadline(t *testing.T) {
|
||||||
|
c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
|
||||||
|
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||||
|
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||||
|
}
|
||||||
|
testDeadline(c, 200*time.Millisecond, t)
|
||||||
|
|
||||||
|
c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
|
||||||
|
o := otherContext{c}
|
||||||
|
testDeadline(o, 200*time.Millisecond, t)
|
||||||
|
|
||||||
|
c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
|
||||||
|
o = otherContext{c}
|
||||||
|
c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond))
|
||||||
|
testDeadline(c, 200*time.Millisecond, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeout(t *testing.T) {
|
||||||
|
c, _ := WithTimeout(Background(), 100*time.Millisecond)
|
||||||
|
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||||
|
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||||
|
}
|
||||||
|
testDeadline(c, 200*time.Millisecond, t)
|
||||||
|
|
||||||
|
c, _ = WithTimeout(Background(), 100*time.Millisecond)
|
||||||
|
o := otherContext{c}
|
||||||
|
testDeadline(o, 200*time.Millisecond, t)
|
||||||
|
|
||||||
|
c, _ = WithTimeout(Background(), 100*time.Millisecond)
|
||||||
|
o = otherContext{c}
|
||||||
|
c, _ = WithTimeout(o, 300*time.Millisecond)
|
||||||
|
testDeadline(c, 200*time.Millisecond, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCanceledTimeout(t *testing.T) {
|
||||||
|
c, _ := WithTimeout(Background(), 200*time.Millisecond)
|
||||||
|
o := otherContext{c}
|
||||||
|
c, cancel := WithTimeout(o, 400*time.Millisecond)
|
||||||
|
cancel()
|
||||||
|
time.Sleep(100 * time.Millisecond) // let cancelation propagate
|
||||||
|
select {
|
||||||
|
case <-c.Done():
|
||||||
|
default:
|
||||||
|
t.Errorf("<-c.Done() blocked, but shouldn't have")
|
||||||
|
}
|
||||||
|
if e := c.Err(); e != Canceled {
|
||||||
|
t.Errorf("c.Err() == %v want %v", e, Canceled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type key1 int
|
||||||
|
type key2 int
|
||||||
|
|
||||||
|
var k1 = key1(1)
|
||||||
|
var k2 = key2(1) // same int as k1, different type
|
||||||
|
var k3 = key2(3) // same type as k2, different int
|
||||||
|
|
||||||
|
func TestValues(t *testing.T) {
|
||||||
|
check := func(c Context, nm, v1, v2, v3 string) {
|
||||||
|
if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
|
||||||
|
t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
|
||||||
|
}
|
||||||
|
if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
|
||||||
|
t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
|
||||||
|
}
|
||||||
|
if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
|
||||||
|
t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c0 := Background()
|
||||||
|
check(c0, "c0", "", "", "")
|
||||||
|
|
||||||
|
c1 := WithValue(Background(), k1, "c1k1")
|
||||||
|
check(c1, "c1", "c1k1", "", "")
|
||||||
|
|
||||||
|
if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want {
|
||||||
|
t.Errorf("c.String() = %q want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
c2 := WithValue(c1, k2, "c2k2")
|
||||||
|
check(c2, "c2", "c1k1", "c2k2", "")
|
||||||
|
|
||||||
|
c3 := WithValue(c2, k3, "c3k3")
|
||||||
|
check(c3, "c2", "c1k1", "c2k2", "c3k3")
|
||||||
|
|
||||||
|
c4 := WithValue(c3, k1, nil)
|
||||||
|
check(c4, "c4", "", "c2k2", "c3k3")
|
||||||
|
|
||||||
|
o0 := otherContext{Background()}
|
||||||
|
check(o0, "o0", "", "", "")
|
||||||
|
|
||||||
|
o1 := otherContext{WithValue(Background(), k1, "c1k1")}
|
||||||
|
check(o1, "o1", "c1k1", "", "")
|
||||||
|
|
||||||
|
o2 := WithValue(o1, k2, "o2k2")
|
||||||
|
check(o2, "o2", "c1k1", "o2k2", "")
|
||||||
|
|
||||||
|
o3 := otherContext{c4}
|
||||||
|
check(o3, "o3", "", "c2k2", "c3k3")
|
||||||
|
|
||||||
|
o4 := WithValue(o3, k3, nil)
|
||||||
|
check(o4, "o4", "", "c2k2", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocs(t *testing.T) {
|
||||||
|
bg := Background()
|
||||||
|
for _, test := range []struct {
|
||||||
|
desc string
|
||||||
|
f func()
|
||||||
|
limit float64
|
||||||
|
gccgoLimit float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Background()",
|
||||||
|
f: func() { Background() },
|
||||||
|
limit: 0,
|
||||||
|
gccgoLimit: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
|
||||||
|
f: func() {
|
||||||
|
c := WithValue(bg, k1, nil)
|
||||||
|
c.Value(k1)
|
||||||
|
},
|
||||||
|
limit: 3,
|
||||||
|
gccgoLimit: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "WithTimeout(bg, 15*time.Millisecond)",
|
||||||
|
f: func() {
|
||||||
|
c, _ := WithTimeout(bg, 15*time.Millisecond)
|
||||||
|
<-c.Done()
|
||||||
|
},
|
||||||
|
limit: 8,
|
||||||
|
gccgoLimit: 13,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "WithCancel(bg)",
|
||||||
|
f: func() {
|
||||||
|
c, cancel := WithCancel(bg)
|
||||||
|
cancel()
|
||||||
|
<-c.Done()
|
||||||
|
},
|
||||||
|
limit: 5,
|
||||||
|
gccgoLimit: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "WithTimeout(bg, 100*time.Millisecond)",
|
||||||
|
f: func() {
|
||||||
|
c, cancel := WithTimeout(bg, 100*time.Millisecond)
|
||||||
|
cancel()
|
||||||
|
<-c.Done()
|
||||||
|
},
|
||||||
|
limit: 8,
|
||||||
|
gccgoLimit: 25,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
limit := test.limit
|
||||||
|
if runtime.Compiler == "gccgo" {
|
||||||
|
// gccgo does not yet do escape analysis.
|
||||||
|
// TOOD(iant): Remove this when gccgo does do escape analysis.
|
||||||
|
limit = test.gccgoLimit
|
||||||
|
}
|
||||||
|
if n := testing.AllocsPerRun(100, test.f); n > limit {
|
||||||
|
t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimultaneousCancels(t *testing.T) {
|
||||||
|
root, cancel := WithCancel(Background())
|
||||||
|
m := map[Context]CancelFunc{root: cancel}
|
||||||
|
q := []Context{root}
|
||||||
|
// Create a tree of contexts.
|
||||||
|
for len(q) != 0 && len(m) < 100 {
|
||||||
|
parent := q[0]
|
||||||
|
q = q[1:]
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
ctx, cancel := WithCancel(parent)
|
||||||
|
m[ctx] = cancel
|
||||||
|
q = append(q, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Start all the cancels in a random order.
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(m))
|
||||||
|
for _, cancel := range m {
|
||||||
|
go func(cancel CancelFunc) {
|
||||||
|
cancel()
|
||||||
|
wg.Done()
|
||||||
|
}(cancel)
|
||||||
|
}
|
||||||
|
// Wait on all the contexts in a random order.
|
||||||
|
for ctx := range m {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
buf := make([]byte, 10<<10)
|
||||||
|
n := runtime.Stack(buf, true)
|
||||||
|
t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wait for all the cancel functions to return.
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
buf := make([]byte, 10<<10)
|
||||||
|
n := runtime.Stack(buf, true)
|
||||||
|
t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterlockedCancels(t *testing.T) {
|
||||||
|
parent, cancelParent := WithCancel(Background())
|
||||||
|
child, cancelChild := WithCancel(parent)
|
||||||
|
go func() {
|
||||||
|
parent.Done()
|
||||||
|
cancelChild()
|
||||||
|
}()
|
||||||
|
cancelParent()
|
||||||
|
select {
|
||||||
|
case <-child.Done():
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
buf := make([]byte, 10<<10)
|
||||||
|
n := runtime.Stack(buf, true)
|
||||||
|
t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLayersCancel(t *testing.T) {
|
||||||
|
testLayers(t, time.Now().UnixNano(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLayersTimeout(t *testing.T) {
|
||||||
|
testLayers(t, time.Now().UnixNano(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLayers(t *testing.T, seed int64, testTimeout bool) {
|
||||||
|
rand.Seed(seed)
|
||||||
|
errorf := func(format string, a ...interface{}) {
|
||||||
|
t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
|
||||||
|
}
|
||||||
|
const (
|
||||||
|
timeout = 200 * time.Millisecond
|
||||||
|
minLayers = 30
|
||||||
|
)
|
||||||
|
type value int
|
||||||
|
var (
|
||||||
|
vals []*value
|
||||||
|
cancels []CancelFunc
|
||||||
|
numTimers int
|
||||||
|
ctx = Background()
|
||||||
|
)
|
||||||
|
for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
|
||||||
|
switch rand.Intn(3) {
|
||||||
|
case 0:
|
||||||
|
v := new(value)
|
||||||
|
ctx = WithValue(ctx, v, v)
|
||||||
|
vals = append(vals, v)
|
||||||
|
case 1:
|
||||||
|
var cancel CancelFunc
|
||||||
|
ctx, cancel = WithCancel(ctx)
|
||||||
|
cancels = append(cancels, cancel)
|
||||||
|
case 2:
|
||||||
|
var cancel CancelFunc
|
||||||
|
ctx, cancel = WithTimeout(ctx, timeout)
|
||||||
|
cancels = append(cancels, cancel)
|
||||||
|
numTimers++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkValues := func(when string) {
|
||||||
|
for _, key := range vals {
|
||||||
|
if val := ctx.Value(key).(*value); key != val {
|
||||||
|
errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
errorf("ctx should not be canceled yet")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
|
||||||
|
t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
|
||||||
|
}
|
||||||
|
t.Log(ctx)
|
||||||
|
checkValues("before cancel")
|
||||||
|
if testTimeout {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-time.After(timeout + timeout/10):
|
||||||
|
errorf("ctx should have timed out")
|
||||||
|
}
|
||||||
|
checkValues("after timeout")
|
||||||
|
} else {
|
||||||
|
cancel := cancels[rand.Intn(len(cancels))]
|
||||||
|
cancel()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
default:
|
||||||
|
errorf("ctx should be canceled")
|
||||||
|
}
|
||||||
|
checkValues("after cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCancelRemoves(t *testing.T) {
|
||||||
|
checkChildren := func(when string, ctx Context, want int) {
|
||||||
|
if got := len(ctx.(*cancelCtx).children); got != want {
|
||||||
|
t.Errorf("%s: context has %d children, want %d", when, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, _ := WithCancel(Background())
|
||||||
|
checkChildren("after creation", ctx, 0)
|
||||||
|
_, cancel := WithCancel(ctx)
|
||||||
|
checkChildren("with WithCancel child ", ctx, 1)
|
||||||
|
cancel()
|
||||||
|
checkChildren("after cancelling WithCancel child", ctx, 0)
|
||||||
|
|
||||||
|
ctx, _ = WithCancel(Background())
|
||||||
|
checkChildren("after creation", ctx, 0)
|
||||||
|
_, cancel = WithTimeout(ctx, 60*time.Minute)
|
||||||
|
checkChildren("with WithTimeout child ", ctx, 1)
|
||||||
|
cancel()
|
||||||
|
checkChildren("after cancelling WithTimeout child", ctx, 0)
|
||||||
|
}
|
||||||
26
Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go
generated
vendored
Normal file
26
Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package context_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleWithTimeout() {
|
||||||
|
// Pass a context with a timeout to tell a blocking function that it
|
||||||
|
// should abandon its work after the timeout elapses.
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
select {
|
||||||
|
case <-time.After(200 * time.Millisecond):
|
||||||
|
fmt.Println("overslept")
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// context deadline exceeded
|
||||||
|
}
|
||||||
94
Makefile
94
Makefile
|
|
@ -3,14 +3,13 @@
|
||||||
# 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
|
|
||||||
GO ?= latest
|
GO ?= latest
|
||||||
|
|
||||||
geth:
|
geth:
|
||||||
|
|
@ -18,70 +17,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 -ld $(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 -ld $(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) --dest=$(GOBIN) --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 -ld $(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) --dest=$(GOBIN) --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 -ld $(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 -ld $(GOBIN)/geth-linux-* | grep arm
|
||||||
|
|
||||||
|
geth-linux-arm-5: xgo
|
||||||
|
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/geth
|
||||||
|
@echo "Linux ARMv5 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep arm-5
|
||||||
|
|
||||||
|
geth-linux-arm-6: xgo
|
||||||
|
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/geth
|
||||||
|
@echo "Linux ARMv6 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep arm-6
|
||||||
|
|
||||||
|
geth-linux-arm-7: xgo
|
||||||
|
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/geth
|
||||||
|
@echo "Linux ARMv7 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep arm-7
|
||||||
|
|
||||||
|
geth-linux-arm64: xgo
|
||||||
|
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/geth
|
||||||
|
@echo "Linux ARM64 cross compilation done:"
|
||||||
|
@ls -ld $(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 -ld $(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) --dest=$(GOBIN) --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 -ld $(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) --dest=$(GOBIN) --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 -ld $(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 -ld $(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) --dest=$(GOBIN) --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 -ld $(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) --dest=$(GOBIN) --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 -ld $(GOBIN)/geth-windows-* | grep amd64
|
||||||
|
|
||||||
geth-android: xgo geth-android-16 geth-android-21
|
geth-android: xgo
|
||||||
|
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v $(shell build/flags.sh) ./cmd/geth
|
||||||
@echo "Android cross compilation done:"
|
@echo "Android cross compilation done:"
|
||||||
@ls -l $(GOBIN)/geth-android-*
|
@ls -ld $(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) --dest=$(GOBIN) --targets=ios-7.0/framework -v $(shell build/flags.sh) ./cmd/geth
|
||||||
@echo "Android 16 cross compilation done:"
|
@echo "iOS framework cross compilation done:"
|
||||||
@ls -l $(GOBIN)/geth-android-16-*
|
@ls -ld $(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
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ For prerequisites and detailed build instructions please read the
|
||||||
[Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum)
|
[Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum)
|
||||||
on the wiki.
|
on the wiki.
|
||||||
|
|
||||||
Building geth requires two external dependencies, Go and GMP.
|
Building geth requires both a Go and a C compiler.
|
||||||
You can install them using your favourite package manager.
|
You can install them using your favourite package manager.
|
||||||
Once the dependencies are installed, run
|
Once the dependencies are installed, run
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,75 +20,17 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"math"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Callable method given a `Name` and whether the method is a constant.
|
// Executer is an executer method for performing state executions. It takes one
|
||||||
// If the method is `Const` no transaction needs to be created for this
|
// argument which is the input data and expects output data to be returned as
|
||||||
// particular Method call. It can easily be simulated using a local VM.
|
// multiple 32 byte word length concatenated slice
|
||||||
// For example a `Balance()` method only needs to retrieve something
|
type Executer func(datain []byte) []byte
|
||||||
// from the storage and therefor requires no Tx to be send to the
|
|
||||||
// network. A method such as `Transact` does require a Tx and thus will
|
|
||||||
// be flagged `true`.
|
|
||||||
// Input specifies the required input parameters for this gives method.
|
|
||||||
type Method struct {
|
|
||||||
Name string
|
|
||||||
Const bool
|
|
||||||
Inputs []Argument
|
|
||||||
Return Type // not yet implemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the methods string signature according to the ABI spec.
|
|
||||||
//
|
|
||||||
// Example
|
|
||||||
//
|
|
||||||
// function foo(uint32 a, int b) = "foo(uint32,int256)"
|
|
||||||
//
|
|
||||||
// Please note that "int" is substitute for its canonical representation "int256"
|
|
||||||
func (m Method) String() (out string) {
|
|
||||||
out += m.Name
|
|
||||||
types := make([]string, len(m.Inputs))
|
|
||||||
i := 0
|
|
||||||
for _, input := range m.Inputs {
|
|
||||||
types[i] = input.Type.String()
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
out += "(" + strings.Join(types, ",") + ")"
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Method) Id() []byte {
|
|
||||||
return crypto.Sha3([]byte(m.String()))[:4]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Argument holds the name of the argument and the corresponding type.
|
|
||||||
// Types are used when packing and testing arguments.
|
|
||||||
type Argument struct {
|
|
||||||
Name string
|
|
||||||
Type Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Argument) UnmarshalJSON(data []byte) error {
|
|
||||||
var extarg struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(data, &extarg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("argument json err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Type, err = NewType(extarg.Type)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Name = extarg.Name
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The ABI holds information about a contract's context and available
|
// The ABI holds information about a contract's context and available
|
||||||
// invokable methods. It will allow you to type check function calls and
|
// invokable methods. It will allow you to type check function calls and
|
||||||
|
|
@ -97,6 +39,18 @@ type ABI struct {
|
||||||
Methods map[string]Method
|
Methods map[string]Method
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSON returns a parsed ABI interface and error if it failed.
|
||||||
|
func JSON(reader io.Reader) (ABI, error) {
|
||||||
|
dec := json.NewDecoder(reader)
|
||||||
|
|
||||||
|
var abi ABI
|
||||||
|
if err := dec.Decode(&abi); err != nil {
|
||||||
|
return ABI{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return abi, nil
|
||||||
|
}
|
||||||
|
|
||||||
// tests, tests whether the given input would result in a successful
|
// tests, tests whether the given input would result in a successful
|
||||||
// call. Checks argument list count and matches input to `input`.
|
// call. Checks argument list count and matches input to `input`.
|
||||||
func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) {
|
func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
|
|
@ -145,6 +99,55 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
return packed, nil
|
return packed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toGoType parses the input and casts it to the proper type defined by the ABI
|
||||||
|
// argument in t.
|
||||||
|
func toGoType(t Argument, input []byte) interface{} {
|
||||||
|
switch t.Type.T {
|
||||||
|
case IntTy:
|
||||||
|
return common.BytesToBig(input)
|
||||||
|
case UintTy:
|
||||||
|
return common.BytesToBig(input)
|
||||||
|
case BoolTy:
|
||||||
|
return common.BytesToBig(input).Uint64() > 0
|
||||||
|
case AddressTy:
|
||||||
|
return common.BytesToAddress(input)
|
||||||
|
case HashTy:
|
||||||
|
return common.BytesToHash(input)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call executes a call and attemps to parse the return values and returns it as
|
||||||
|
// an interface. It uses the executer method to perform the actual call since
|
||||||
|
// the abi knows nothing of the lower level calling mechanism.
|
||||||
|
//
|
||||||
|
// Call supports all abi types and includes multiple return values. When only
|
||||||
|
// one item is returned a single interface{} will be returned, if a contract
|
||||||
|
// method returns multiple values an []interface{} slice is returned.
|
||||||
|
func (abi ABI) Call(executer Executer, name string, args ...interface{}) interface{} {
|
||||||
|
callData, err := abi.Pack(name, args...)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Debug).Infoln("pack error:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
output := executer(callData)
|
||||||
|
|
||||||
|
method := abi.Methods[name]
|
||||||
|
ret := make([]interface{}, int(math.Max(float64(len(method.Outputs)), float64(len(output)/32))))
|
||||||
|
for i := 0; i < len(ret); i += 32 {
|
||||||
|
index := i / 32
|
||||||
|
ret[index] = toGoType(method.Outputs[index], output[i:i+32])
|
||||||
|
}
|
||||||
|
|
||||||
|
// return single interface
|
||||||
|
if len(ret) == 1 {
|
||||||
|
return ret[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
var methods []Method
|
var methods []Method
|
||||||
if err := json.Unmarshal(data, &methods); err != nil {
|
if err := json.Unmarshal(data, &methods); err != nil {
|
||||||
|
|
@ -158,14 +161,3 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func JSON(reader io.Reader) (ABI, error) {
|
|
||||||
dec := json.NewDecoder(reader)
|
|
||||||
|
|
||||||
var abi ABI
|
|
||||||
if err := dec.Decode(&abi); err != nil {
|
|
||||||
return ABI{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return abi, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -92,12 +92,12 @@ func TestReader(t *testing.T) {
|
||||||
exp := ABI{
|
exp := ABI{
|
||||||
Methods: map[string]Method{
|
Methods: map[string]Method{
|
||||||
"balance": Method{
|
"balance": Method{
|
||||||
"balance", true, nil, Type{},
|
"balance", true, nil, nil,
|
||||||
},
|
},
|
||||||
"send": Method{
|
"send": Method{
|
||||||
"send", false, []Argument{
|
"send", false, []Argument{
|
||||||
Argument{"amount", Uint256},
|
Argument{"amount", Uint256},
|
||||||
}, Type{},
|
}, nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -238,10 +238,10 @@ func TestTestAddress(t *testing.T) {
|
||||||
func TestMethodSignature(t *testing.T) {
|
func TestMethodSignature(t *testing.T) {
|
||||||
String, _ := NewType("string")
|
String, _ := NewType("string")
|
||||||
String32, _ := NewType("string32")
|
String32, _ := NewType("string32")
|
||||||
m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, Type{}}
|
m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, nil}
|
||||||
exp := "foo(string32,string)"
|
exp := "foo(string32,string)"
|
||||||
if m.String() != exp {
|
if m.Sig() != exp {
|
||||||
t.Error("signature mismatch", exp, "!=", m.String())
|
t.Error("signature mismatch", exp, "!=", m.Sig())
|
||||||
}
|
}
|
||||||
|
|
||||||
idexp := crypto.Sha3([]byte(exp))[:4]
|
idexp := crypto.Sha3([]byte(exp))[:4]
|
||||||
|
|
@ -250,10 +250,10 @@ func TestMethodSignature(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uintt, _ := NewType("uint")
|
uintt, _ := NewType("uint")
|
||||||
m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, Type{}}
|
m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, nil}
|
||||||
exp = "foo(uint256)"
|
exp = "foo(uint256)"
|
||||||
if m.String() != exp {
|
if m.Sig() != exp {
|
||||||
t.Error("signature mismatch", exp, "!=", m.String())
|
t.Error("signature mismatch", exp, "!=", m.Sig())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,3 +393,34 @@ func TestBytes(t *testing.T) {
|
||||||
t.Error("expected error")
|
t.Error("expected error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReturn(t *testing.T) {
|
||||||
|
const definition = `[
|
||||||
|
{ "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] },
|
||||||
|
{ "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] }
|
||||||
|
|
||||||
|
]`
|
||||||
|
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := abi.Call(func([]byte) []byte {
|
||||||
|
t := make([]byte, 32)
|
||||||
|
t[0] = 1
|
||||||
|
return t
|
||||||
|
}, "balance")
|
||||||
|
if _, ok := r.(common.Hash); !ok {
|
||||||
|
t.Errorf("expected type common.Hash, got %T", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
r = abi.Call(func([]byte) []byte {
|
||||||
|
t := make([]byte, 32)
|
||||||
|
t[0] = 1
|
||||||
|
return t
|
||||||
|
}, "name")
|
||||||
|
if _, ok := r.(common.Address); !ok {
|
||||||
|
t.Errorf("expected type common.Address, got %T", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
48
accounts/abi/argument.go
Normal file
48
accounts/abi/argument.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Argument holds the name of the argument and the corresponding type.
|
||||||
|
// Types are used when packing and testing arguments.
|
||||||
|
type Argument struct {
|
||||||
|
Name string
|
||||||
|
Type Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Argument) UnmarshalJSON(data []byte) error {
|
||||||
|
var extarg struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(data, &extarg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("argument json err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Type, err = NewType(extarg.Type)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.Name = extarg.Name
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
76
accounts/abi/method.go
Normal file
76
accounts/abi/method.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Callable method given a `Name` and whether the method is a constant.
|
||||||
|
// If the method is `Const` no transaction needs to be created for this
|
||||||
|
// particular Method call. It can easily be simulated using a local VM.
|
||||||
|
// For example a `Balance()` method only needs to retrieve something
|
||||||
|
// from the storage and therefor requires no Tx to be send to the
|
||||||
|
// network. A method such as `Transact` does require a Tx and thus will
|
||||||
|
// be flagged `true`.
|
||||||
|
// Input specifies the required input parameters for this gives method.
|
||||||
|
type Method struct {
|
||||||
|
Name string
|
||||||
|
Const bool
|
||||||
|
Inputs []Argument
|
||||||
|
Outputs []Argument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sig returns the methods string signature according to the ABI spec.
|
||||||
|
//
|
||||||
|
// Example
|
||||||
|
//
|
||||||
|
// function foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||||
|
//
|
||||||
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
|
func (m Method) Sig() string {
|
||||||
|
types := make([]string, len(m.Inputs))
|
||||||
|
i := 0
|
||||||
|
for _, input := range m.Inputs {
|
||||||
|
types[i] = input.Type.String()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v(%v)", m.Name, strings.Join(types, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Method) String() string {
|
||||||
|
inputs := make([]string, len(m.Inputs))
|
||||||
|
for i, input := range m.Inputs {
|
||||||
|
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
||||||
|
}
|
||||||
|
outputs := make([]string, len(m.Outputs))
|
||||||
|
for i, output := range m.Outputs {
|
||||||
|
if len(output.Name) > 0 {
|
||||||
|
outputs[i] = fmt.Sprintf("%v ", output.Name)
|
||||||
|
}
|
||||||
|
outputs[i] += output.Type.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("function %v(%v) returns(%v)", m.Name, strings.Join(inputs, ", "), strings.Join(outputs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Method) Id() []byte {
|
||||||
|
return crypto.Sha3([]byte(m.Sig()))[:4]
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,8 @@ var int8_t = reflect.TypeOf(int8(0))
|
||||||
var int16_t = reflect.TypeOf(int16(0))
|
var int16_t = reflect.TypeOf(int16(0))
|
||||||
var int32_t = reflect.TypeOf(int32(0))
|
var int32_t = reflect.TypeOf(int32(0))
|
||||||
var int64_t = reflect.TypeOf(int64(0))
|
var int64_t = reflect.TypeOf(int64(0))
|
||||||
|
var hash_t = reflect.TypeOf(common.Hash{})
|
||||||
|
var address_t = reflect.TypeOf(common.Address{})
|
||||||
|
|
||||||
var uint_ts = reflect.TypeOf([]uint(nil))
|
var uint_ts = reflect.TypeOf([]uint(nil))
|
||||||
var uint8_ts = reflect.TypeOf([]uint8(nil))
|
var uint8_ts = reflect.TypeOf([]uint8(nil))
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ const (
|
||||||
BoolTy
|
BoolTy
|
||||||
SliceTy
|
SliceTy
|
||||||
AddressTy
|
AddressTy
|
||||||
|
HashTy
|
||||||
RealTy
|
RealTy
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -121,7 +122,7 @@ func NewType(t string) (typ Type, err error) {
|
||||||
typ.Kind = reflect.Invalid
|
typ.Kind = reflect.Invalid
|
||||||
case "address":
|
case "address":
|
||||||
typ.Kind = reflect.Slice
|
typ.Kind = reflect.Slice
|
||||||
typ.Type = byte_ts
|
typ.Type = address_t
|
||||||
typ.Size = 20
|
typ.Size = 20
|
||||||
typ.T = AddressTy
|
typ.T = AddressTy
|
||||||
case "string":
|
case "string":
|
||||||
|
|
@ -130,6 +131,11 @@ func NewType(t string) (typ Type, err error) {
|
||||||
if vsize > 0 {
|
if vsize > 0 {
|
||||||
typ.Size = 32
|
typ.Size = 32
|
||||||
}
|
}
|
||||||
|
case "hash":
|
||||||
|
typ.Kind = reflect.Slice
|
||||||
|
typ.Size = 32
|
||||||
|
typ.Type = hash_t
|
||||||
|
typ.T = HashTy
|
||||||
case "bytes":
|
case "bytes":
|
||||||
typ.Kind = reflect.Slice
|
typ.Kind = reflect.Slice
|
||||||
typ.Type = byte_ts
|
typ.Type = byte_ts
|
||||||
|
|
@ -206,9 +212,9 @@ func (t Type) pack(v interface{}) ([]byte, error) {
|
||||||
}
|
}
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
if v, ok := value.Interface().(common.Address); ok {
|
if v, ok := value.Interface().(common.Address); ok {
|
||||||
return t.pack(v[:])
|
return common.LeftPadBytes(v[:], 32), nil
|
||||||
} else if v, ok := value.Interface().(common.Hash); ok {
|
} else if v, ok := value.Interface().(common.Hash); ok {
|
||||||
return t.pack(v[:])
|
return v[:], nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -137,8 +137,7 @@ func upgradeDB(ctx *cli.Context) {
|
||||||
glog.Infoln("Upgrading blockchain database")
|
glog.Infoln("Upgrading blockchain database")
|
||||||
|
|
||||||
chain, chainDb := utils.MakeChain(ctx)
|
chain, chainDb := utils.MakeChain(ctx)
|
||||||
v, _ := chainDb.Get([]byte("BlockchainVersion"))
|
bcVersion := core.GetBlockChainVersion(chainDb)
|
||||||
bcVersion := int(common.NewValue(v).Uint())
|
|
||||||
if bcVersion == 0 {
|
if bcVersion == 0 {
|
||||||
bcVersion = core.BlockChainVersion
|
bcVersion = core.BlockChainVersion
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +153,7 @@ func upgradeDB(ctx *cli.Context) {
|
||||||
|
|
||||||
// Import the chain file.
|
// Import the chain file.
|
||||||
chain, chainDb = utils.MakeChain(ctx)
|
chain, chainDb = utils.MakeChain(ctx)
|
||||||
chainDb.Put([]byte("BlockchainVersion"), common.NewValue(core.BlockChainVersion).Bytes())
|
core.WriteBlockChainVersion(chainDb, core.BlockChainVersion)
|
||||||
err := utils.ImportChain(chain, exportFile)
|
err := utils.ImportChain(chain, exportFile)
|
||||||
chainDb.Close()
|
chainDb.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -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,29 @@ 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.
|
||||||
|
if persObj := p.Object(); persObj != nil { // make sure the personal api is enabled over the interface
|
||||||
|
js.re.Run(`jeth.unlockAccount = personal.unlockAccount;`)
|
||||||
|
persObj.Set("unlockAccount", jeth.UnlockAccount)
|
||||||
|
js.re.Run(`jeth.newAccount = personal.newAccount;`)
|
||||||
|
persObj.Set("newAccount", jeth.NewAccount)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,8 +361,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(ðereum); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// If natspec is enabled, ask for permission
|
||||||
|
if ethereum.NatSpec {
|
||||||
|
notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient())
|
||||||
fmt.Println(notice)
|
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 +384,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(ðereum); 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.")
|
||||||
|
|
|
||||||
|
|
@ -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(ðereum)
|
||||||
|
|
||||||
|
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(ðereum)
|
||||||
|
|
||||||
|
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 := ð.Config{
|
|
||||||
NodeKey: testNodeKey,
|
|
||||||
DataDir: tmp,
|
|
||||||
AccountManager: am,
|
|
||||||
MaxPeers: 0,
|
|
||||||
Name: "test",
|
|
||||||
DocRoot: "/",
|
|
||||||
SolcPath: testSolcPath,
|
|
||||||
PowTest: true,
|
|
||||||
NewDB: func(path string) (ethdb.Database, error) { return db, nil },
|
|
||||||
}
|
|
||||||
if config != nil {
|
|
||||||
config(conf)
|
|
||||||
}
|
|
||||||
ethereum, err := eth.New(conf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ethConf := ð.Config{
|
||||||
|
TestGenesisState: db,
|
||||||
|
AccountManager: accman,
|
||||||
|
DocRoot: "/",
|
||||||
|
SolcPath: testSolcPath,
|
||||||
|
Etherbase: coinbase,
|
||||||
|
PowTest: true,
|
||||||
|
}
|
||||||
|
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil {
|
||||||
|
t.Fatalf("failed to register ethereum protocol: %v", err)
|
||||||
|
}
|
||||||
|
// Initialize all the keys for testing
|
||||||
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(ðereum)
|
||||||
|
|
||||||
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(ðereum)
|
||||||
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(ðereum)
|
||||||
|
|
||||||
|
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(ðereum)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
|
||||||
24
cmd/geth/library.c
Normal file
24
cmd/geth/library.c
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Simple wrapper to translate the API exposed methods and types to inthernal
|
||||||
|
// Go versions of the same types.
|
||||||
|
|
||||||
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
|
int run(const char* args) {
|
||||||
|
return doRun((char*)args);
|
||||||
|
}
|
||||||
46
cmd/geth/library.go
Normal file
46
cmd/geth/library.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Contains a simple library definition to allow creating a Geth instance from
|
||||||
|
// straight C code.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// #ifdef __cplusplus
|
||||||
|
// extern "C" {
|
||||||
|
// #endif
|
||||||
|
//
|
||||||
|
// extern int run(const char*);
|
||||||
|
//
|
||||||
|
// #ifdef __cplusplus
|
||||||
|
// }
|
||||||
|
// #endif
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//export doRun
|
||||||
|
func doRun(args *C.char) C.int {
|
||||||
|
// This is equivalent to geth.main, just modified to handle the function arg passing
|
||||||
|
if err := app.Run(strings.Split("geth "+C.GoString(args), " ")); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
56
cmd/geth/library_android.go
Normal file
56
cmd/geth/library_android.go
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Contains specialized code for running Geth on Android.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// #include <android/log.h>
|
||||||
|
// #cgo LDFLAGS: -llog
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Redirect the standard output and error to logcat
|
||||||
|
oldStdout, oldStderr := os.Stdout, os.Stderr
|
||||||
|
|
||||||
|
outRead, outWrite, _ := os.Pipe()
|
||||||
|
errRead, errWrite, _ := os.Pipe()
|
||||||
|
|
||||||
|
os.Stdout = outWrite
|
||||||
|
os.Stderr = errWrite
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
scanner := bufio.NewScanner(outRead)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
C.__android_log_write(C.ANDROID_LOG_INFO, C.CString("Stdout"), C.CString(line))
|
||||||
|
oldStdout.WriteString(line + "\n")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
scanner := bufio.NewScanner(errRead)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
C.__android_log_write(C.ANDROID_LOG_INFO, C.CString("Stderr"), C.CString(line))
|
||||||
|
oldStderr.WriteString(line + "\n")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
296
cmd/geth/main.go
296
cmd/geth/main.go
|
|
@ -4,7 +4,7 @@
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
//
|
//
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
|
@ -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,14 @@ 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.SwarmSwapDisabled,
|
||||||
|
utils.SwarmPortFlag,
|
||||||
|
utils.SwarmAccountAddrFlag,
|
||||||
|
utils.ChequebookAddrFlag,
|
||||||
utils.DevModeFlag,
|
utils.DevModeFlag,
|
||||||
utils.TestNetFlag,
|
utils.TestNetFlag,
|
||||||
utils.VMDebugFlag,
|
utils.VMDebugFlag,
|
||||||
|
|
@ -355,6 +344,8 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
||||||
utils.ExtraDataFlag,
|
utils.ExtraDataFlag,
|
||||||
}
|
}
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
||||||
utils.SetupLogger(ctx)
|
utils.SetupLogger(ctx)
|
||||||
utils.SetupNetwork(ctx)
|
utils.SetupNetwork(ctx)
|
||||||
utils.SetupVM(ctx)
|
utils.SetupVM(ctx)
|
||||||
|
|
@ -368,7 +359,6 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
||||||
defer logger.Flush()
|
defer logger.Flush()
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
|
@ -376,14 +366,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 +386,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 +426,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(ðereum); 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 +511,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 +548,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 +563,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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
242
cmd/gethrpctest/main.go
Normal 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/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc/api"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc/codec"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc/comms"
|
||||||
|
rpc "github.com/ethereum/go-ethereum/rpc/v2"
|
||||||
|
"github.com/ethereum/go-ethereum/tests"
|
||||||
|
"github.com/ethereum/go-ethereum/whisper"
|
||||||
|
"github.com/ethereum/go-ethereum/xeth"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 := ð.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(ðereum)
|
||||||
|
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(ðereum); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := `\\.\pipe\geth.ipc`
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
endpoint = filepath.Join(common.DefaultDataDir(), "geth.ipc")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := comms.IpcConfig{
|
||||||
|
Endpoint: endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := comms.CreateListener(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
server := rpc.NewServer()
|
||||||
|
|
||||||
|
// register package API's this node provides
|
||||||
|
offered := stack.APIs()
|
||||||
|
for _, api := range offered {
|
||||||
|
server.RegisterName(api.Namespace, api.Service)
|
||||||
|
glog.V(logger.Debug).Infof("Register %T@%s for IPC service\n", api.Service, api.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
74
cmd/utils/api.go
Normal file
74
cmd/utils/api.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicNetAPI offers network related RPC methods
|
||||||
|
type PublicNetAPI struct {
|
||||||
|
net *p2p.Server
|
||||||
|
networkVersion int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPublicNetAPI creates a new net api instance.
|
||||||
|
func NewPublicNetAPI(net *p2p.Server, networkVersion int) *PublicNetAPI {
|
||||||
|
return &PublicNetAPI{net, networkVersion}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listening returns an indication if the node is listening for network connections.
|
||||||
|
func (s *PublicNetAPI) Listening() bool {
|
||||||
|
return true // always listening
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peercount returns the number of connected peers
|
||||||
|
func (s *PublicNetAPI) PeerCount() *rpc.HexNumber {
|
||||||
|
return rpc.NewHexNumber(s.net.PeerCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProtocolVersion returns the current ethereum protocol version.
|
||||||
|
func (s *PublicNetAPI) Version() string {
|
||||||
|
return fmt.Sprintf("%d", s.networkVersion)
|
||||||
|
}
|
||||||
41
cmd/utils/bootnodes.go
Normal file
41
cmd/utils/bootnodes.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,26 @@ 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)",
|
||||||
|
}
|
||||||
|
SwarmPortFlag = cli.StringFlag{
|
||||||
|
Name: "bzzport",
|
||||||
|
Usage: "Swarm local http api port",
|
||||||
|
}
|
||||||
|
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 +418,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 +511,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 != "":
|
|
||||||
if key, err = crypto.HexToECDSA(hex); err != nil {
|
|
||||||
Fatalf("Option %q: %v", NodeKeyHexFlag.Name, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return key
|
data, err := ioutil.ReadFile(genesis)
|
||||||
|
if err != nil {
|
||||||
|
Fatalf("Failed to load custom genesis file: %v", err)
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeEthConfig creates ethereum options from set command line flags.
|
// 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 := ð.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 := ð.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 +711,107 @@ 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
|
||||||
}
|
}
|
||||||
cfg.PowTest = true
|
ethConf.PowTest = true
|
||||||
cfg.DevMode = true
|
|
||||||
|
|
||||||
glog.V(logger.Info).Infoln("dev mode enabled")
|
|
||||||
}
|
}
|
||||||
return cfg
|
// Assemble and return the protocol stack
|
||||||
|
stack, err := node.New(stackConf)
|
||||||
|
if err != nil {
|
||||||
|
Fatalf("Failed to create the protocol stack: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eth.Ethereum: ethereum
|
||||||
|
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||||
|
return eth.New(ctx, ethConf)
|
||||||
|
}); err != nil {
|
||||||
|
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whisper
|
||||||
|
if shhEnable {
|
||||||
|
if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil {
|
||||||
|
Fatalf("Failed to register the Whisper service: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bzz. Swarm
|
||||||
|
var bzzconfig *bzzapi.Config
|
||||||
|
hexaddr := ctx.GlobalString(SwarmAccountAddrFlag.Name)
|
||||||
|
if hexaddr != "" {
|
||||||
|
swarmaccount := common.HexToAddress(hexaddr)
|
||||||
|
if !accman.HasAccount(swarmaccount) {
|
||||||
|
Fatalf("swarm account '%v' does not exist: %v", hexaddr, err)
|
||||||
|
}
|
||||||
|
prvkey, err := accman.GetUnlocked(swarmaccount)
|
||||||
|
if err != nil {
|
||||||
|
Fatalf("unable to unlock swarm account: %v", err)
|
||||||
|
}
|
||||||
|
chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name))
|
||||||
|
bzzdir := ctx.GlobalString(SwarmConfigPathFlag.Name)
|
||||||
|
if bzzdir == "" {
|
||||||
|
bzzdir = filepath.Join(datadir, "bzz")
|
||||||
|
}
|
||||||
|
bzzconfig, err = bzzapi.NewConfig(bzzdir, chbookaddr, prvkey)
|
||||||
|
if err != nil {
|
||||||
|
Fatalf("unable to configure swarm: %v", err)
|
||||||
|
}
|
||||||
|
bzzport := ctx.GlobalString(SwarmPortFlag.Name)
|
||||||
|
if len(bzzport) > 0 {
|
||||||
|
bzzconfig.Port = bzzport
|
||||||
|
}
|
||||||
|
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 +819,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 +850,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 +858,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 +875,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 +894,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(ðereum); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.GlobalIsSet(IPCExperimental.Name) {
|
||||||
|
listener, err := comms.CreateListener(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
server := rpc.NewServer()
|
||||||
|
|
||||||
|
// register package API's this node provides
|
||||||
|
offered := stack.APIs()
|
||||||
|
for _, api := range offered {
|
||||||
|
server.RegisterName(api.Namespace, api.Service)
|
||||||
|
glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
web3 := NewPublicWeb3API(stack)
|
||||||
|
server.RegisterName("web3", web3)
|
||||||
|
net := NewPublicNetAPI(stack.Server(), ethereum.NetVersion())
|
||||||
|
server.RegisterName("net", net)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint)
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
codec := rpc.NewJSONCodec(conn)
|
||||||
|
go server.ServeCodec(codec)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
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 +976,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
29
common/chequebook/api.go
Normal 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
660
common/chequebook/cheque.go
Normal 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())
|
||||||
|
}
|
||||||
498
common/chequebook/cheque_test.go
Normal file
498
common/chequebook/cheque_test.go
Normal 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
49
common/chequebook/chequebook.sol
Normal file
49
common/chequebook/chequebook.sol
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
common/chequebook/contract.go
Normal file
63
common/chequebook/contract.go
Normal 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
157
common/kademlia/address.go
Normal 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)
|
||||||
|
}
|
||||||
80
common/kademlia/address_test.go
Normal file
80
common/kademlia/address_test.go
Normal 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
360
common/kademlia/kaddb.go
Normal 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
457
common/kademlia/kademlia.go
Normal 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")
|
||||||
|
}
|
||||||
389
common/kademlia/kademlia_test.go
Normal file
389
common/kademlia/kademlia_test.go
Normal 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()
|
||||||
|
}
|
||||||
|
|
@ -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(ð.Config{
|
return eth.New(&node.ServiceContext{EventMux: new(event.TypeMux)}, ð.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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Manifest object
|
|
||||||
//
|
|
||||||
// The manifest object holds all the relevant information supplied with the
|
|
||||||
// the manifest specified in the package
|
|
||||||
type Manifest struct {
|
|
||||||
Entry string
|
|
||||||
Height, Width int
|
|
||||||
}
|
|
||||||
|
|
||||||
// External package
|
|
||||||
//
|
|
||||||
// External package contains the main html file and manifest
|
|
||||||
type ExtPackage struct {
|
|
||||||
EntryHtml string
|
|
||||||
Manifest *Manifest
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read file
|
|
||||||
//
|
|
||||||
// Read a given compressed file and returns the read bytes.
|
|
||||||
// Returns an error otherwise
|
|
||||||
func ReadFile(f *zip.File) ([]byte, error) {
|
|
||||||
rc, err := f.Open()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
content, err := ioutil.ReadAll(rc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads manifest
|
|
||||||
//
|
|
||||||
// Reads and returns a manifest object. Returns error otherwise
|
|
||||||
func ReadManifest(m []byte) (*Manifest, error) {
|
|
||||||
var manifest Manifest
|
|
||||||
|
|
||||||
dec := json.NewDecoder(strings.NewReader(string(m)))
|
|
||||||
if err := dec.Decode(&manifest); err == io.EOF {
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &manifest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find file in archive
|
|
||||||
//
|
|
||||||
// Returns the index of the given file name if it exists. -1 if file not found
|
|
||||||
func FindFileInArchive(fn string, files []*zip.File) (index int) {
|
|
||||||
index = -1
|
|
||||||
// Find the manifest first
|
|
||||||
for i, f := range files {
|
|
||||||
if f.Name == fn {
|
|
||||||
index = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open package
|
|
||||||
//
|
|
||||||
// Opens a prepared ethereum package
|
|
||||||
// Reads the manifest file and determines file contents and returns and
|
|
||||||
// the external package.
|
|
||||||
func OpenPackage(fn string) (*ExtPackage, error) {
|
|
||||||
r, err := zip.OpenReader(fn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
manifestIndex := FindFileInArchive("manifest.json", r.File)
|
|
||||||
|
|
||||||
if manifestIndex < 0 {
|
|
||||||
return nil, fmt.Errorf("No manifest file found in archive")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := ReadFile(r.File[manifestIndex])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest, err := ReadManifest(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if manifest.Entry == "" {
|
|
||||||
return nil, fmt.Errorf("Entry file specified but appears to be empty: %s", manifest.Entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
entryIndex := FindFileInArchive(manifest.Entry, r.File)
|
|
||||||
if entryIndex < 0 {
|
|
||||||
return nil, fmt.Errorf("Entry file not found: '%s'", manifest.Entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err = ReadFile(r.File[entryIndex])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
extPackage := &ExtPackage{string(f), manifest}
|
|
||||||
|
|
||||||
return extPackage, nil
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
292
common/rlp.go
292
common/rlp.go
|
|
@ -1,292 +0,0 @@
|
||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RlpEncode interface {
|
|
||||||
RlpEncode() []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type RlpEncodeDecode interface {
|
|
||||||
RlpEncode
|
|
||||||
RlpValue() []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type RlpEncodable interface {
|
|
||||||
RlpData() interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Rlp(encoder RlpEncode) []byte {
|
|
||||||
return encoder.RlpEncode()
|
|
||||||
}
|
|
||||||
|
|
||||||
type RlpEncoder struct {
|
|
||||||
rlpData []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRlpEncoder() *RlpEncoder {
|
|
||||||
encoder := &RlpEncoder{}
|
|
||||||
|
|
||||||
return encoder
|
|
||||||
}
|
|
||||||
func (coder *RlpEncoder) EncodeData(rlpData interface{}) []byte {
|
|
||||||
return Encode(rlpData)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
RlpEmptyList = 0x80
|
|
||||||
RlpEmptyStr = 0x40
|
|
||||||
)
|
|
||||||
|
|
||||||
const rlpEof = -1
|
|
||||||
|
|
||||||
func Char(c []byte) int {
|
|
||||||
if len(c) > 0 {
|
|
||||||
return int(c[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return rlpEof
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecodeWithReader(reader *bytes.Buffer) interface{} {
|
|
||||||
var slice []interface{}
|
|
||||||
|
|
||||||
// Read the next byte
|
|
||||||
char := Char(reader.Next(1))
|
|
||||||
switch {
|
|
||||||
case char <= 0x7f:
|
|
||||||
return char
|
|
||||||
|
|
||||||
case char <= 0xb7:
|
|
||||||
return reader.Next(int(char - 0x80))
|
|
||||||
|
|
||||||
case char <= 0xbf:
|
|
||||||
length := ReadVarInt(reader.Next(int(char - 0xb7)))
|
|
||||||
|
|
||||||
return reader.Next(int(length))
|
|
||||||
|
|
||||||
case char <= 0xf7:
|
|
||||||
length := int(char - 0xc0)
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
obj := DecodeWithReader(reader)
|
|
||||||
slice = append(slice, obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
return slice
|
|
||||||
case char <= 0xff:
|
|
||||||
length := ReadVarInt(reader.Next(int(char - 0xf7)))
|
|
||||||
for i := uint64(0); i < length; i++ {
|
|
||||||
obj := DecodeWithReader(reader)
|
|
||||||
slice = append(slice, obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
return slice
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("byte not supported: %q", char))
|
|
||||||
}
|
|
||||||
|
|
||||||
return slice
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
directRlp = big.NewInt(0x7f)
|
|
||||||
numberRlp = big.NewInt(0xb7)
|
|
||||||
zeroRlp = big.NewInt(0x0)
|
|
||||||
)
|
|
||||||
|
|
||||||
func intlen(i int64) (length int) {
|
|
||||||
for i > 0 {
|
|
||||||
i = i >> 8
|
|
||||||
length++
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Encode(object interface{}) []byte {
|
|
||||||
var buff bytes.Buffer
|
|
||||||
|
|
||||||
if object != nil {
|
|
||||||
switch t := object.(type) {
|
|
||||||
case *Value:
|
|
||||||
buff.Write(Encode(t.Val))
|
|
||||||
case RlpEncodable:
|
|
||||||
buff.Write(Encode(t.RlpData()))
|
|
||||||
// Code dup :-/
|
|
||||||
case int:
|
|
||||||
buff.Write(Encode(big.NewInt(int64(t))))
|
|
||||||
case uint:
|
|
||||||
buff.Write(Encode(big.NewInt(int64(t))))
|
|
||||||
case int8:
|
|
||||||
buff.Write(Encode(big.NewInt(int64(t))))
|
|
||||||
case int16:
|
|
||||||
buff.Write(Encode(big.NewInt(int64(t))))
|
|
||||||
case int32:
|
|
||||||
buff.Write(Encode(big.NewInt(int64(t))))
|
|
||||||
case int64:
|
|
||||||
buff.Write(Encode(big.NewInt(t)))
|
|
||||||
case uint16:
|
|
||||||
buff.Write(Encode(big.NewInt(int64(t))))
|
|
||||||
case uint32:
|
|
||||||
buff.Write(Encode(big.NewInt(int64(t))))
|
|
||||||
case uint64:
|
|
||||||
buff.Write(Encode(big.NewInt(int64(t))))
|
|
||||||
case byte:
|
|
||||||
buff.Write(Encode(big.NewInt(int64(t))))
|
|
||||||
case *big.Int:
|
|
||||||
// Not sure how this is possible while we check for nil
|
|
||||||
if t == nil {
|
|
||||||
buff.WriteByte(0xc0)
|
|
||||||
} else {
|
|
||||||
buff.Write(Encode(t.Bytes()))
|
|
||||||
}
|
|
||||||
case Bytes:
|
|
||||||
buff.Write(Encode([]byte(t)))
|
|
||||||
case []byte:
|
|
||||||
if len(t) == 1 && t[0] <= 0x7f {
|
|
||||||
buff.Write(t)
|
|
||||||
} else if len(t) < 56 {
|
|
||||||
buff.WriteByte(byte(len(t) + 0x80))
|
|
||||||
buff.Write(t)
|
|
||||||
} else {
|
|
||||||
b := big.NewInt(int64(len(t)))
|
|
||||||
buff.WriteByte(byte(len(b.Bytes()) + 0xb7))
|
|
||||||
buff.Write(b.Bytes())
|
|
||||||
buff.Write(t)
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
buff.Write(Encode([]byte(t)))
|
|
||||||
case []interface{}:
|
|
||||||
// Inline function for writing the slice header
|
|
||||||
WriteSliceHeader := func(length int) {
|
|
||||||
if length < 56 {
|
|
||||||
buff.WriteByte(byte(length + 0xc0))
|
|
||||||
} else {
|
|
||||||
b := big.NewInt(int64(length))
|
|
||||||
buff.WriteByte(byte(len(b.Bytes()) + 0xf7))
|
|
||||||
buff.Write(b.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
for _, val := range t {
|
|
||||||
b.Write(Encode(val))
|
|
||||||
}
|
|
||||||
WriteSliceHeader(len(b.Bytes()))
|
|
||||||
buff.Write(b.Bytes())
|
|
||||||
default:
|
|
||||||
// This is how it should have been from the start
|
|
||||||
// needs refactoring (@fjl)
|
|
||||||
v := reflect.ValueOf(t)
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
var b bytes.Buffer
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
b.Write(Encode(v.Index(i).Interface()))
|
|
||||||
}
|
|
||||||
|
|
||||||
blen := b.Len()
|
|
||||||
if blen < 56 {
|
|
||||||
buff.WriteByte(byte(blen) + 0xc0)
|
|
||||||
} else {
|
|
||||||
ilen := byte(intlen(int64(blen)))
|
|
||||||
buff.WriteByte(ilen + 0xf7)
|
|
||||||
t := make([]byte, ilen)
|
|
||||||
for i := byte(0); i < ilen; i++ {
|
|
||||||
t[ilen-i-1] = byte(blen >> (i * 8))
|
|
||||||
}
|
|
||||||
buff.Write(t)
|
|
||||||
}
|
|
||||||
buff.ReadFrom(&b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Empty list for nil
|
|
||||||
buff.WriteByte(0xc0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buff.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Use a bytes.Buffer instead of a raw byte slice.
|
|
||||||
// Cleaner code, and use draining instead of seeking the next bytes to read
|
|
||||||
func Decode(data []byte, pos uint64) (interface{}, uint64) {
|
|
||||||
var slice []interface{}
|
|
||||||
char := int(data[pos])
|
|
||||||
switch {
|
|
||||||
case char <= 0x7f:
|
|
||||||
return data[pos], pos + 1
|
|
||||||
|
|
||||||
case char <= 0xb7:
|
|
||||||
b := uint64(data[pos]) - 0x80
|
|
||||||
|
|
||||||
return data[pos+1 : pos+1+b], pos + 1 + b
|
|
||||||
|
|
||||||
case char <= 0xbf:
|
|
||||||
b := uint64(data[pos]) - 0xb7
|
|
||||||
|
|
||||||
b2 := ReadVarInt(data[pos+1 : pos+1+b])
|
|
||||||
|
|
||||||
return data[pos+1+b : pos+1+b+b2], pos + 1 + b + b2
|
|
||||||
|
|
||||||
case char <= 0xf7:
|
|
||||||
b := uint64(data[pos]) - 0xc0
|
|
||||||
prevPos := pos
|
|
||||||
pos++
|
|
||||||
for i := uint64(0); i < b; {
|
|
||||||
var obj interface{}
|
|
||||||
|
|
||||||
// Get the next item in the data list and append it
|
|
||||||
obj, prevPos = Decode(data, pos)
|
|
||||||
slice = append(slice, obj)
|
|
||||||
|
|
||||||
// Increment i by the amount bytes read in the previous
|
|
||||||
// read
|
|
||||||
i += (prevPos - pos)
|
|
||||||
pos = prevPos
|
|
||||||
}
|
|
||||||
return slice, pos
|
|
||||||
|
|
||||||
case char <= 0xff:
|
|
||||||
l := uint64(data[pos]) - 0xf7
|
|
||||||
b := ReadVarInt(data[pos+1 : pos+1+l])
|
|
||||||
|
|
||||||
pos = pos + l + 1
|
|
||||||
|
|
||||||
prevPos := b
|
|
||||||
for i := uint64(0); i < uint64(b); {
|
|
||||||
var obj interface{}
|
|
||||||
|
|
||||||
obj, prevPos = Decode(data, pos)
|
|
||||||
slice = append(slice, obj)
|
|
||||||
|
|
||||||
i += (prevPos - pos)
|
|
||||||
pos = prevPos
|
|
||||||
}
|
|
||||||
return slice, pos
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("byte not supported: %q", char))
|
|
||||||
}
|
|
||||||
|
|
||||||
return slice, 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,176 +0,0 @@
|
||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNonInterfaceSlice(t *testing.T) {
|
|
||||||
vala := []string{"value1", "value2", "value3"}
|
|
||||||
valb := []interface{}{"value1", "value2", "value3"}
|
|
||||||
resa := Encode(vala)
|
|
||||||
resb := Encode(valb)
|
|
||||||
if !bytes.Equal(resa, resb) {
|
|
||||||
t.Errorf("expected []string & []interface{} to be equal")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRlpValueEncoding(t *testing.T) {
|
|
||||||
val := EmptyValue()
|
|
||||||
val.AppendList().Append(byte(1)).Append(byte(2)).Append(byte(3))
|
|
||||||
val.Append("4").AppendList().Append(byte(5))
|
|
||||||
|
|
||||||
res, err := rlp.EncodeToBytes(val)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("encode error: %v", err)
|
|
||||||
}
|
|
||||||
exp := Encode([]interface{}{[]interface{}{1, 2, 3}, "4", []interface{}{5}})
|
|
||||||
if bytes.Compare(res, exp) != 0 {
|
|
||||||
t.Errorf("expected %x, got %x", exp, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValueSlice(t *testing.T) {
|
|
||||||
val := []interface{}{
|
|
||||||
"value1",
|
|
||||||
"valeu2",
|
|
||||||
"value3",
|
|
||||||
}
|
|
||||||
|
|
||||||
value := NewValue(val)
|
|
||||||
splitVal := value.SliceFrom(1)
|
|
||||||
|
|
||||||
if splitVal.Len() != 2 {
|
|
||||||
t.Error("SliceFrom: Expected len", 2, "got", splitVal.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
splitVal = value.SliceTo(2)
|
|
||||||
if splitVal.Len() != 2 {
|
|
||||||
t.Error("SliceTo: Expected len", 2, "got", splitVal.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
splitVal = value.SliceFromTo(1, 3)
|
|
||||||
if splitVal.Len() != 2 {
|
|
||||||
t.Error("SliceFromTo: Expected len", 2, "got", splitVal.Len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLargeData(t *testing.T) {
|
|
||||||
data := make([]byte, 100000)
|
|
||||||
enc := Encode(data)
|
|
||||||
value := NewValueFromBytes(enc)
|
|
||||||
if value.Len() != len(data) {
|
|
||||||
t.Error("Expected data to be", len(data), "got", value.Len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValue(t *testing.T) {
|
|
||||||
value := NewValueFromBytes([]byte("\xcd\x83dog\x83god\x83cat\x01"))
|
|
||||||
if value.Get(0).Str() != "dog" {
|
|
||||||
t.Errorf("expected '%v', got '%v'", value.Get(0).Str(), "dog")
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.Get(3).Uint() != 1 {
|
|
||||||
t.Errorf("expected '%v', got '%v'", value.Get(3).Uint(), 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncode(t *testing.T) {
|
|
||||||
strRes := "\x83dog"
|
|
||||||
bytes := Encode("dog")
|
|
||||||
|
|
||||||
str := string(bytes)
|
|
||||||
if str != strRes {
|
|
||||||
t.Errorf("Expected %q, got %q", strRes, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
sliceRes := "\xcc\x83dog\x83god\x83cat"
|
|
||||||
strs := []interface{}{"dog", "god", "cat"}
|
|
||||||
bytes = Encode(strs)
|
|
||||||
slice := string(bytes)
|
|
||||||
if slice != sliceRes {
|
|
||||||
t.Error("Expected %q, got %q", sliceRes, slice)
|
|
||||||
}
|
|
||||||
|
|
||||||
intRes := "\x82\x04\x00"
|
|
||||||
bytes = Encode(1024)
|
|
||||||
if string(bytes) != intRes {
|
|
||||||
t.Errorf("Expected %q, got %q", intRes, bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecode(t *testing.T) {
|
|
||||||
single := []byte("\x01")
|
|
||||||
b, _ := Decode(single, 0)
|
|
||||||
|
|
||||||
if b.(uint8) != 1 {
|
|
||||||
t.Errorf("Expected 1, got %q", b)
|
|
||||||
}
|
|
||||||
|
|
||||||
str := []byte("\x83dog")
|
|
||||||
b, _ = Decode(str, 0)
|
|
||||||
if bytes.Compare(b.([]byte), []byte("dog")) != 0 {
|
|
||||||
t.Errorf("Expected dog, got %q", b)
|
|
||||||
}
|
|
||||||
|
|
||||||
slice := []byte("\xcc\x83dog\x83god\x83cat")
|
|
||||||
res := []interface{}{"dog", "god", "cat"}
|
|
||||||
b, _ = Decode(slice, 0)
|
|
||||||
if reflect.DeepEqual(b, res) {
|
|
||||||
t.Errorf("Expected %q, got %q", res, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeDecodeBigInt(t *testing.T) {
|
|
||||||
bigInt := big.NewInt(1391787038)
|
|
||||||
encoded := Encode(bigInt)
|
|
||||||
|
|
||||||
value := NewValueFromBytes(encoded)
|
|
||||||
if value.BigInt().Cmp(bigInt) != 0 {
|
|
||||||
t.Errorf("Expected %v, got %v", bigInt, value.BigInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeDecodeBytes(t *testing.T) {
|
|
||||||
bv := NewValue([]interface{}{[]byte{1, 2, 3, 4, 5}, []byte{6}})
|
|
||||||
b, _ := rlp.EncodeToBytes(bv)
|
|
||||||
val := NewValueFromBytes(b)
|
|
||||||
if !bv.Cmp(val) {
|
|
||||||
t.Errorf("Expected %#v, got %#v", bv, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeZero(t *testing.T) {
|
|
||||||
b, _ := rlp.EncodeToBytes(NewValue(0))
|
|
||||||
exp := []byte{0xc0}
|
|
||||||
if bytes.Compare(b, exp) == 0 {
|
|
||||||
t.Error("Expected", exp, "got", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkEncodeDecode(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
bytes := Encode([]interface{}{"dog", "god", "cat"})
|
|
||||||
Decode(bytes, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
238
common/swap/swap.go
Normal file
238
common/swap/swap.go
Normal 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
178
common/swap/swap_test.go
Normal 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),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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 + `"`
|
||||||
|
}
|
||||||
|
|
|
||||||
428
common/value.go
428
common/value.go
|
|
@ -1,428 +0,0 @@
|
||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Value can hold values of certain basic types and provides ways to
|
|
||||||
// convert between types without bothering to check whether the
|
|
||||||
// conversion is actually meaningful.
|
|
||||||
//
|
|
||||||
// It currently supports the following types:
|
|
||||||
//
|
|
||||||
// - int{,8,16,32,64}
|
|
||||||
// - uint{,8,16,32,64}
|
|
||||||
// - *big.Int
|
|
||||||
// - []byte, string
|
|
||||||
// - []interface{}
|
|
||||||
//
|
|
||||||
// Value is useful whenever you feel that Go's types limit your
|
|
||||||
// ability to express yourself. In these situations, use Value and
|
|
||||||
// forget about this strong typing nonsense.
|
|
||||||
type Value struct{ Val interface{} }
|
|
||||||
|
|
||||||
func (val *Value) String() string {
|
|
||||||
return fmt.Sprintf("%x", val.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewValue(val interface{}) *Value {
|
|
||||||
t := val
|
|
||||||
if v, ok := val.(*Value); ok {
|
|
||||||
t = v.Val
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Value{Val: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Type() reflect.Kind {
|
|
||||||
return reflect.TypeOf(val.Val).Kind()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) IsNil() bool {
|
|
||||||
return val.Val == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Len() int {
|
|
||||||
if data, ok := val.Val.([]interface{}); ok {
|
|
||||||
return len(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(val.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Uint() uint64 {
|
|
||||||
if Val, ok := val.Val.(uint8); ok {
|
|
||||||
return uint64(Val)
|
|
||||||
} else if Val, ok := val.Val.(uint16); ok {
|
|
||||||
return uint64(Val)
|
|
||||||
} else if Val, ok := val.Val.(uint32); ok {
|
|
||||||
return uint64(Val)
|
|
||||||
} else if Val, ok := val.Val.(uint64); ok {
|
|
||||||
return Val
|
|
||||||
} else if Val, ok := val.Val.(float32); ok {
|
|
||||||
return uint64(Val)
|
|
||||||
} else if Val, ok := val.Val.(float64); ok {
|
|
||||||
return uint64(Val)
|
|
||||||
} else if Val, ok := val.Val.(int); ok {
|
|
||||||
return uint64(Val)
|
|
||||||
} else if Val, ok := val.Val.(uint); ok {
|
|
||||||
return uint64(Val)
|
|
||||||
} else if Val, ok := val.Val.([]byte); ok {
|
|
||||||
return new(big.Int).SetBytes(Val).Uint64()
|
|
||||||
} else if Val, ok := val.Val.(*big.Int); ok {
|
|
||||||
return Val.Uint64()
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Int() int64 {
|
|
||||||
if Val, ok := val.Val.(int8); ok {
|
|
||||||
return int64(Val)
|
|
||||||
} else if Val, ok := val.Val.(int16); ok {
|
|
||||||
return int64(Val)
|
|
||||||
} else if Val, ok := val.Val.(int32); ok {
|
|
||||||
return int64(Val)
|
|
||||||
} else if Val, ok := val.Val.(int64); ok {
|
|
||||||
return Val
|
|
||||||
} else if Val, ok := val.Val.(int); ok {
|
|
||||||
return int64(Val)
|
|
||||||
} else if Val, ok := val.Val.(float32); ok {
|
|
||||||
return int64(Val)
|
|
||||||
} else if Val, ok := val.Val.(float64); ok {
|
|
||||||
return int64(Val)
|
|
||||||
} else if Val, ok := val.Val.([]byte); ok {
|
|
||||||
return new(big.Int).SetBytes(Val).Int64()
|
|
||||||
} else if Val, ok := val.Val.(*big.Int); ok {
|
|
||||||
return Val.Int64()
|
|
||||||
} else if Val, ok := val.Val.(string); ok {
|
|
||||||
n, _ := strconv.Atoi(Val)
|
|
||||||
return int64(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Byte() byte {
|
|
||||||
if Val, ok := val.Val.(byte); ok {
|
|
||||||
return Val
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0x0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) BigInt() *big.Int {
|
|
||||||
if a, ok := val.Val.([]byte); ok {
|
|
||||||
b := new(big.Int).SetBytes(a)
|
|
||||||
|
|
||||||
return b
|
|
||||||
} else if a, ok := val.Val.(*big.Int); ok {
|
|
||||||
return a
|
|
||||||
} else if a, ok := val.Val.(string); ok {
|
|
||||||
return Big(a)
|
|
||||||
} else {
|
|
||||||
return big.NewInt(int64(val.Uint()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return big.NewInt(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Str() string {
|
|
||||||
if a, ok := val.Val.([]byte); ok {
|
|
||||||
return string(a)
|
|
||||||
} else if a, ok := val.Val.(string); ok {
|
|
||||||
return a
|
|
||||||
} else if a, ok := val.Val.(byte); ok {
|
|
||||||
return string(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Bytes() []byte {
|
|
||||||
if a, ok := val.Val.([]byte); ok {
|
|
||||||
return a
|
|
||||||
} else if s, ok := val.Val.(byte); ok {
|
|
||||||
return []byte{s}
|
|
||||||
} else if s, ok := val.Val.(string); ok {
|
|
||||||
return []byte(s)
|
|
||||||
} else if s, ok := val.Val.(*big.Int); ok {
|
|
||||||
return s.Bytes()
|
|
||||||
} else {
|
|
||||||
return big.NewInt(val.Int()).Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Err() error {
|
|
||||||
if err, ok := val.Val.(error); ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Slice() []interface{} {
|
|
||||||
if d, ok := val.Val.([]interface{}); ok {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
return []interface{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) SliceFrom(from int) *Value {
|
|
||||||
slice := val.Slice()
|
|
||||||
|
|
||||||
return NewValue(slice[from:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) SliceTo(to int) *Value {
|
|
||||||
slice := val.Slice()
|
|
||||||
|
|
||||||
return NewValue(slice[:to])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) SliceFromTo(from, to int) *Value {
|
|
||||||
slice := val.Slice()
|
|
||||||
|
|
||||||
return NewValue(slice[from:to])
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO More type checking methods
|
|
||||||
func (val *Value) IsSlice() bool {
|
|
||||||
return val.Type() == reflect.Slice
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) IsStr() bool {
|
|
||||||
return val.Type() == reflect.String
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) IsErr() bool {
|
|
||||||
_, ok := self.Val.(error)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special list checking function. Something is considered
|
|
||||||
// a list if it's of type []interface{}. The list is usually
|
|
||||||
// used in conjunction with rlp decoded streams.
|
|
||||||
func (val *Value) IsList() bool {
|
|
||||||
_, ok := val.Val.([]interface{})
|
|
||||||
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) IsEmpty() bool {
|
|
||||||
return val.Val == nil || ((val.IsSlice() || val.IsStr()) && val.Len() == 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Threat the value as a slice
|
|
||||||
func (val *Value) Get(idx int) *Value {
|
|
||||||
if d, ok := val.Val.([]interface{}); ok {
|
|
||||||
// Guard for oob
|
|
||||||
if len(d) <= idx {
|
|
||||||
return NewValue(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx < 0 {
|
|
||||||
return NewValue(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewValue(d[idx])
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this wasn't a slice you probably shouldn't be using this function
|
|
||||||
return NewValue(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) Copy() *Value {
|
|
||||||
switch val := self.Val.(type) {
|
|
||||||
case *big.Int:
|
|
||||||
return NewValue(new(big.Int).Set(val))
|
|
||||||
case []byte:
|
|
||||||
return NewValue(CopyBytes(val))
|
|
||||||
default:
|
|
||||||
return NewValue(self.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Cmp(o *Value) bool {
|
|
||||||
return reflect.DeepEqual(val.Val, o.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) DeepCmp(o *Value) bool {
|
|
||||||
return bytes.Compare(self.Bytes(), o.Bytes()) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) DecodeRLP(s *rlp.Stream) error {
|
|
||||||
var v interface{}
|
|
||||||
if err := s.Decode(&v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
self.Val = v
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) EncodeRLP(w io.Writer) error {
|
|
||||||
if self == nil {
|
|
||||||
w.Write(rlp.EmptyList)
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return rlp.Encode(w, self.Val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewValueFromBytes decodes RLP data.
|
|
||||||
// The contained value will be nil if data contains invalid RLP.
|
|
||||||
func NewValueFromBytes(data []byte) *Value {
|
|
||||||
v := new(Value)
|
|
||||||
if len(data) != 0 {
|
|
||||||
if err := rlp.DecodeBytes(data, v); err != nil {
|
|
||||||
v.Val = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value setters
|
|
||||||
func NewSliceValue(s interface{}) *Value {
|
|
||||||
list := EmptyValue()
|
|
||||||
|
|
||||||
if s != nil {
|
|
||||||
if slice, ok := s.([]interface{}); ok {
|
|
||||||
for _, val := range slice {
|
|
||||||
list.Append(val)
|
|
||||||
}
|
|
||||||
} else if slice, ok := s.([]string); ok {
|
|
||||||
for _, val := range slice {
|
|
||||||
list.Append(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func EmptyValue() *Value {
|
|
||||||
return NewValue([]interface{}{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) AppendList() *Value {
|
|
||||||
list := EmptyValue()
|
|
||||||
val.Val = append(val.Slice(), list)
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) Append(v interface{}) *Value {
|
|
||||||
val.Val = append(val.Slice(), v)
|
|
||||||
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
valOpAdd = iota
|
|
||||||
valOpDiv
|
|
||||||
valOpMul
|
|
||||||
valOpPow
|
|
||||||
valOpSub
|
|
||||||
)
|
|
||||||
|
|
||||||
// Math stuff
|
|
||||||
func (self *Value) doOp(op int, other interface{}) *Value {
|
|
||||||
left := self.BigInt()
|
|
||||||
right := NewValue(other).BigInt()
|
|
||||||
|
|
||||||
switch op {
|
|
||||||
case valOpAdd:
|
|
||||||
self.Val = left.Add(left, right)
|
|
||||||
case valOpDiv:
|
|
||||||
self.Val = left.Div(left, right)
|
|
||||||
case valOpMul:
|
|
||||||
self.Val = left.Mul(left, right)
|
|
||||||
case valOpPow:
|
|
||||||
self.Val = left.Exp(left, right, Big0)
|
|
||||||
case valOpSub:
|
|
||||||
self.Val = left.Sub(left, right)
|
|
||||||
}
|
|
||||||
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) Add(other interface{}) *Value {
|
|
||||||
return self.doOp(valOpAdd, other)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) Sub(other interface{}) *Value {
|
|
||||||
return self.doOp(valOpSub, other)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) Div(other interface{}) *Value {
|
|
||||||
return self.doOp(valOpDiv, other)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) Mul(other interface{}) *Value {
|
|
||||||
return self.doOp(valOpMul, other)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Value) Pow(other interface{}) *Value {
|
|
||||||
return self.doOp(valOpPow, other)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ValueIterator struct {
|
|
||||||
value *Value
|
|
||||||
currentValue *Value
|
|
||||||
idx int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (val *Value) NewIterator() *ValueIterator {
|
|
||||||
return &ValueIterator{value: val}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ValueIterator) Len() int {
|
|
||||||
return it.value.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ValueIterator) Next() bool {
|
|
||||||
if it.idx >= it.value.Len() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
it.currentValue = it.value.Get(it.idx)
|
|
||||||
it.idx++
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ValueIterator) Value() *Value {
|
|
||||||
return it.currentValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ValueIterator) Idx() int {
|
|
||||||
return it.idx - 1
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
checker "gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ValueSuite struct{}
|
|
||||||
|
|
||||||
var _ = checker.Suite(&ValueSuite{})
|
|
||||||
|
|
||||||
func (s *ValueSuite) TestValueCmp(c *checker.C) {
|
|
||||||
val1 := NewValue("hello")
|
|
||||||
val2 := NewValue("world")
|
|
||||||
c.Assert(val1.Cmp(val2), checker.Equals, false)
|
|
||||||
|
|
||||||
val3 := NewValue("hello")
|
|
||||||
val4 := NewValue("hello")
|
|
||||||
c.Assert(val3.Cmp(val4), checker.Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ValueSuite) TestValueTypes(c *checker.C) {
|
|
||||||
str := NewValue("str")
|
|
||||||
num := NewValue(1)
|
|
||||||
inter := NewValue([]interface{}{1})
|
|
||||||
byt := NewValue([]byte{1, 2, 3, 4})
|
|
||||||
bigInt := NewValue(big.NewInt(10))
|
|
||||||
|
|
||||||
strExp := "str"
|
|
||||||
numExp := uint64(1)
|
|
||||||
interExp := []interface{}{1}
|
|
||||||
bytExp := []byte{1, 2, 3, 4}
|
|
||||||
bigExp := big.NewInt(10)
|
|
||||||
|
|
||||||
c.Assert(str.Str(), checker.Equals, strExp)
|
|
||||||
c.Assert(num.Uint(), checker.Equals, numExp)
|
|
||||||
c.Assert(NewValue(inter.Val).Cmp(NewValue(interExp)), checker.Equals, true)
|
|
||||||
c.Assert(byt.Bytes(), checker.DeepEquals, bytExp)
|
|
||||||
c.Assert(bigInt.BigInt(), checker.DeepEquals, bigExp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ValueSuite) TestIterator(c *checker.C) {
|
|
||||||
value := NewValue([]interface{}{1, 2, 3})
|
|
||||||
iter := value.NewIterator()
|
|
||||||
values := []uint64{1, 2, 3}
|
|
||||||
i := 0
|
|
||||||
for iter.Next() {
|
|
||||||
c.Assert(values[i], checker.Equals, iter.Value().Uint())
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ValueSuite) TestMath(c *checker.C) {
|
|
||||||
data1 := NewValue(1)
|
|
||||||
data1.Add(1).Add(1)
|
|
||||||
exp1 := NewValue(3)
|
|
||||||
data2 := NewValue(2)
|
|
||||||
data2.Sub(1).Sub(1)
|
|
||||||
exp2 := NewValue(0)
|
|
||||||
|
|
||||||
c.Assert(data1.DeepCmp(exp1), checker.Equals, true)
|
|
||||||
c.Assert(data2.DeepCmp(exp2), checker.Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ValueSuite) TestString(c *checker.C) {
|
|
||||||
data := "10"
|
|
||||||
exp := int64(10)
|
|
||||||
c.Assert(NewValue(data).Int(), checker.DeepEquals, exp)
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
@ -997,6 +993,18 @@ func (self *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain
|
||||||
glog.Fatal(errs[index])
|
glog.Fatal(errs[index])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := WriteTransactions(self.chainDb, block); err != nil {
|
||||||
|
errs[index] = fmt.Errorf("failed to write individual transactions: %v", err)
|
||||||
|
atomic.AddInt32(&failed, 1)
|
||||||
|
glog.Fatal(errs[index])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := WriteReceipts(self.chainDb, receipts); err != nil {
|
||||||
|
errs[index] = fmt.Errorf("failed to write individual receipts: %v", err)
|
||||||
|
atomic.AddInt32(&failed, 1)
|
||||||
|
glog.Fatal(errs[index])
|
||||||
|
return
|
||||||
|
}
|
||||||
atomic.AddInt32(&stats.processed, 1)
|
atomic.AddInt32(&stats.processed, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1257,6 +1265,17 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||||
oldStart = oldBlock
|
oldStart = oldBlock
|
||||||
newStart = newBlock
|
newStart = newBlock
|
||||||
deletedTxs types.Transactions
|
deletedTxs types.Transactions
|
||||||
|
deletedLogs vm.Logs
|
||||||
|
// collectLogs collects the logs that were generated during the
|
||||||
|
// processing of the block that corresponds with the given hash.
|
||||||
|
// These logs are later announced as deleted.
|
||||||
|
collectLogs = func(h common.Hash) {
|
||||||
|
// Coalesce logs
|
||||||
|
receipts := GetBlockReceipts(self.chainDb, h)
|
||||||
|
for _, receipt := range receipts {
|
||||||
|
deletedLogs = append(deletedLogs, receipt.Logs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// first reduce whoever is higher bound
|
// first reduce whoever is higher bound
|
||||||
|
|
@ -1264,6 +1283,8 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||||
// reduce old chain
|
// reduce old chain
|
||||||
for oldBlock = oldBlock; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = self.GetBlock(oldBlock.ParentHash()) {
|
for oldBlock = oldBlock; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = self.GetBlock(oldBlock.ParentHash()) {
|
||||||
deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
|
deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
|
||||||
|
|
||||||
|
collectLogs(oldBlock.Hash())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// reduce new chain and append new chain blocks for inserting later on
|
// reduce new chain and append new chain blocks for inserting later on
|
||||||
|
|
@ -1286,6 +1307,7 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||||
}
|
}
|
||||||
newChain = append(newChain, newBlock)
|
newChain = append(newChain, newBlock)
|
||||||
deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
|
deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
|
||||||
|
collectLogs(oldBlock.Hash())
|
||||||
|
|
||||||
oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash())
|
oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash())
|
||||||
if oldBlock == nil {
|
if oldBlock == nil {
|
||||||
|
|
@ -1319,7 +1341,6 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||||
if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil {
|
if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
addedTxs = append(addedTxs, block.Transactions()...)
|
addedTxs = append(addedTxs, block.Transactions()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1333,7 +1354,12 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||||
}
|
}
|
||||||
// Must be posted in a goroutine because of the transaction pool trying
|
// Must be posted in a goroutine because of the transaction pool trying
|
||||||
// to acquire the chain manager lock
|
// to acquire the chain manager lock
|
||||||
go self.eventMux.Post(RemovedTransactionEvent{diff})
|
if len(diff) > 0 {
|
||||||
|
go self.eventMux.Post(RemovedTransactionEvent{diff})
|
||||||
|
}
|
||||||
|
if len(deletedLogs) > 0 {
|
||||||
|
go self.eventMux.Post(RemovedLogEvent{deletedLogs})
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -963,3 +963,46 @@ func TestChainTxReorgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLogReorgs(t *testing.T) {
|
||||||
|
params.MinGasLimit = big.NewInt(125000) // Minimum the gas limit may ever be.
|
||||||
|
params.GenesisGasLimit = big.NewInt(3141592) // Gas limit of the Genesis block.
|
||||||
|
|
||||||
|
var (
|
||||||
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
|
db, _ = ethdb.NewMemDatabase()
|
||||||
|
// this code generates a log
|
||||||
|
code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
|
||||||
|
)
|
||||||
|
genesis := WriteGenesisBlockForTesting(db,
|
||||||
|
GenesisAccount{addr1, big.NewInt(10000000000000)},
|
||||||
|
)
|
||||||
|
|
||||||
|
evmux := &event.TypeMux{}
|
||||||
|
blockchain, _ := NewBlockChain(db, FakePow{}, evmux)
|
||||||
|
|
||||||
|
subs := evmux.Subscribe(RemovedLogEvent{})
|
||||||
|
chain, _ := GenerateChain(genesis, db, 2, func(i int, gen *BlockGen) {
|
||||||
|
if i == 1 {
|
||||||
|
tx, err := types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code).SignECDSA(key1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create tx: %v", err)
|
||||||
|
}
|
||||||
|
gen.AddTx(tx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
||||||
|
t.Fatalf("failed to insert chain: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, _ = GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) {})
|
||||||
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
||||||
|
t.Fatalf("failed to insert forked chain: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ev := <-subs.Chan()
|
||||||
|
if len(ev.Data.(RemovedLogEvent).Logs) == 0 {
|
||||||
|
t.Error("expected logs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ func (b *BlockGen) AddTx(tx *types.Transaction) {
|
||||||
if b.gasPool == nil {
|
if b.gasPool == nil {
|
||||||
b.SetCoinbase(common.Address{})
|
b.SetCoinbase(common.Address{})
|
||||||
}
|
}
|
||||||
|
b.statedb.StartRecord(tx.Hash(), common.Hash{}, len(b.txs))
|
||||||
_, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.gasPool)
|
_, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.gasPool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -97,13 +98,17 @@ func (b *BlockGen) AddTx(tx *types.Transaction) {
|
||||||
root := b.statedb.IntermediateRoot()
|
root := b.statedb.IntermediateRoot()
|
||||||
b.header.GasUsed.Add(b.header.GasUsed, gas)
|
b.header.GasUsed.Add(b.header.GasUsed, gas)
|
||||||
receipt := types.NewReceipt(root.Bytes(), b.header.GasUsed)
|
receipt := types.NewReceipt(root.Bytes(), b.header.GasUsed)
|
||||||
logs := b.statedb.GetLogs(tx.Hash())
|
receipt.Logs = b.statedb.GetLogs(tx.Hash())
|
||||||
receipt.Logs = logs
|
|
||||||
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
|
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
|
||||||
b.txs = append(b.txs, tx)
|
b.txs = append(b.txs, tx)
|
||||||
b.receipts = append(b.receipts, receipt)
|
b.receipts = append(b.receipts, receipt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Number returns the block number of the block being generated.
|
||||||
|
func (b *BlockGen) Number() *big.Int {
|
||||||
|
return new(big.Int).Set(b.header.Number)
|
||||||
|
}
|
||||||
|
|
||||||
// AddUncheckedReceipts forcefully adds a receipts to the block without a
|
// AddUncheckedReceipts forcefully adds a receipts to the block without a
|
||||||
// backing transaction.
|
// backing transaction.
|
||||||
//
|
//
|
||||||
|
|
@ -220,7 +225,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
|
||||||
|
|
|
||||||
|
|
@ -582,3 +582,17 @@ func GetMipmapBloom(db ethdb.Database, number, level uint64) types.Bloom {
|
||||||
bloomDat, _ := db.Get(mipmapKey(number, level))
|
bloomDat, _ := db.Get(mipmapKey(number, level))
|
||||||
return types.BytesToBloom(bloomDat)
|
return types.BytesToBloom(bloomDat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBlockChainVersion reads the version number from db.
|
||||||
|
func GetBlockChainVersion(db ethdb.Database) int {
|
||||||
|
var vsn uint
|
||||||
|
enc, _ := db.Get([]byte("BlockchainVersion"))
|
||||||
|
rlp.DecodeBytes(enc, &vsn)
|
||||||
|
return int(vsn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBlockChainVersion writes vsn as the version number to db.
|
||||||
|
func WriteBlockChainVersion(db ethdb.Database, vsn int) {
|
||||||
|
enc, _ := rlp.EncodeToBytes(uint(vsn))
|
||||||
|
db.Put([]byte("BlockchainVersion"), enc)
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -39,6 +39,9 @@ type NewMinedBlockEvent struct{ Block *types.Block }
|
||||||
// RemovedTransactionEvent is posted when a reorg happens
|
// RemovedTransactionEvent is posted when a reorg happens
|
||||||
type RemovedTransactionEvent struct{ Txs types.Transactions }
|
type RemovedTransactionEvent struct{ Txs types.Transactions }
|
||||||
|
|
||||||
|
// RemovedLogEvent is posted when a reorg happens
|
||||||
|
type RemovedLogEvent struct{ Logs vm.Logs }
|
||||||
|
|
||||||
// ChainSplit is posted when a new head is detected
|
// ChainSplit is posted when a new head is detected
|
||||||
type ChainSplitEvent struct {
|
type ChainSplitEvent struct {
|
||||||
Block *types.Block
|
Block *types.Block
|
||||||
|
|
|
||||||
121
core/genesis.go
121
core/genesis.go
|
|
@ -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,46 +165,80 @@ 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.
|
||||||
"nonce":"0x%x",
|
func WriteTestNetGenesisBlock(chainDb ethdb.Database) (*types.Block, error) {
|
||||||
"gasLimit":"0x%x",
|
return WriteGenesisBlock(chainDb, strings.NewReader(TestNetGenesisBlock()))
|
||||||
"difficulty":"0x%x",
|
}
|
||||||
"alloc": {
|
|
||||||
"0000000000000000000000000000000000000001": {"balance": "1"},
|
// WriteOlympicGenesisBlock assembles the Olympic genesis block and writes it
|
||||||
"0000000000000000000000000000000000000002": {"balance": "1"},
|
// along with all associated state into a chain database.
|
||||||
"0000000000000000000000000000000000000003": {"balance": "1"},
|
func WriteOlympicGenesisBlock(db ethdb.Database) (*types.Block, error) {
|
||||||
"0000000000000000000000000000000000000004": {"balance": "1"},
|
return WriteGenesisBlock(db, strings.NewReader(OlympicGenesisBlock()))
|
||||||
"dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
}
|
||||||
"e4157b34ea9615cfbde6b4fda419828124b70c78": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
|
||||||
"b9c015918bdaba24b4ff057a92a3873d6eb201be": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
// DefaultGenesisBlock assembles a JSON string representing the default Ethereum
|
||||||
"6c386a4b26f73c802f34673f7248bb118f97424a": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
// genesis block.
|
||||||
"cd2a3d9f938e13cd947ec05abc7fe734df8dd826": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
func DefaultGenesisBlock() string {
|
||||||
"2ef47100e0787b915105fd5e3f4ff6752079d5cb": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
reader, err := gzip.NewReader(base64.NewDecoder(base64.StdEncoding, strings.NewReader(defaultGenesisBlock)))
|
||||||
"e6716f9544a56c530d868e4bfbacb172315bdead": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
if err != nil {
|
||||||
"1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}
|
panic(fmt.Sprintf("failed to access default genesis: %v", err))
|
||||||
}
|
}
|
||||||
}`, types.EncodeNonce(nonce), params.GenesisGasLimit.Bytes(), params.GenesisDifficulty.Bytes())
|
blob, err := ioutil.ReadAll(reader)
|
||||||
return WriteGenesisBlock(chainDb, strings.NewReader(testGenesis))
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to load default genesis: %v", err))
|
||||||
|
}
|
||||||
|
return string(blob)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OlympicGenesisBlock assembles a JSON string representing the Olympic genesis
|
||||||
|
// block.
|
||||||
|
func OlympicGenesisBlock() string {
|
||||||
|
return fmt.Sprintf(`{
|
||||||
|
"nonce":"0x%x",
|
||||||
|
"gasLimit":"0x%x",
|
||||||
|
"difficulty":"0x%x",
|
||||||
|
"alloc": {
|
||||||
|
"0000000000000000000000000000000000000001": {"balance": "1"},
|
||||||
|
"0000000000000000000000000000000000000002": {"balance": "1"},
|
||||||
|
"0000000000000000000000000000000000000003": {"balance": "1"},
|
||||||
|
"0000000000000000000000000000000000000004": {"balance": "1"},
|
||||||
|
"dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
||||||
|
"e4157b34ea9615cfbde6b4fda419828124b70c78": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
||||||
|
"b9c015918bdaba24b4ff057a92a3873d6eb201be": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
||||||
|
"6c386a4b26f73c802f34673f7248bb118f97424a": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
||||||
|
"cd2a3d9f938e13cd947ec05abc7fe734df8dd826": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
||||||
|
"2ef47100e0787b915105fd5e3f4ff6752079d5cb": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
||||||
|
"e6716f9544a56c530d868e4bfbacb172315bdead": {"balance": "1606938044258990275541962092341162602522202993782792835301376"},
|
||||||
|
"1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}
|
||||||
|
}
|
||||||
|
}`, types.EncodeNonce(42), params.GenesisGasLimit.Bytes(), params.GenesisDifficulty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNetGenesisBlock assembles a JSON string representing the Morden test net
|
||||||
|
// genenis block.
|
||||||
|
func TestNetGenesisBlock() string {
|
||||||
|
return fmt.Sprintf(`{
|
||||||
|
"nonce": "0x%x",
|
||||||
|
"difficulty": "0x20000",
|
||||||
|
"mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
|
||||||
|
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||||
|
"timestamp": "0x00",
|
||||||
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"extraData": "0x",
|
||||||
|
"gasLimit": "0x2FEFD8",
|
||||||
|
"alloc": {
|
||||||
|
"0000000000000000000000000000000000000001": { "balance": "1" },
|
||||||
|
"0000000000000000000000000000000000000002": { "balance": "1" },
|
||||||
|
"0000000000000000000000000000000000000003": { "balance": "1" },
|
||||||
|
"0000000000000000000000000000000000000004": { "balance": "1" },
|
||||||
|
"102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }
|
||||||
|
}
|
||||||
|
}`, types.EncodeNonce(0x6d6f7264656e))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func (self *StateDB) RawDump() World {
|
||||||
it := self.trie.Iterator()
|
it := self.trie.Iterator()
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
addr := self.trie.GetKey(it.Key)
|
addr := self.trie.GetKey(it.Key)
|
||||||
stateObject := NewStateObjectFromBytes(common.BytesToAddress(addr), it.Value, self.db)
|
stateObject, _ := DecodeObject(common.BytesToAddress(addr), self.db, it.Value)
|
||||||
|
|
||||||
account := Account{Balance: stateObject.balance.String(), Nonce: stateObject.nonce, Root: common.Bytes2Hex(stateObject.Root()), CodeHash: common.Bytes2Hex(stateObject.codeHash)}
|
account := Account{Balance: stateObject.balance.String(), Nonce: stateObject.nonce, Root: common.Bytes2Hex(stateObject.Root()), CodeHash: common.Bytes2Hex(stateObject.codeHash)}
|
||||||
account.Storage = make(map[string]string)
|
account.Storage = make(map[string]string)
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,19 @@ package state
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"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/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var emptyCodeHash = crypto.Sha3(nil)
|
||||||
|
|
||||||
type Code []byte
|
type Code []byte
|
||||||
|
|
||||||
func (self Code) String() string {
|
func (self Code) String() string {
|
||||||
|
|
@ -56,8 +58,7 @@ func (self Storage) Copy() Storage {
|
||||||
}
|
}
|
||||||
|
|
||||||
type StateObject struct {
|
type StateObject struct {
|
||||||
// State database for storing state changes
|
db trie.Database // State database for storing state changes
|
||||||
db ethdb.Database
|
|
||||||
trie *trie.SecureTrie
|
trie *trie.SecureTrie
|
||||||
|
|
||||||
// Address belonging to this account
|
// Address belonging to this account
|
||||||
|
|
@ -83,39 +84,16 @@ type StateObject struct {
|
||||||
dirty bool
|
dirty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStateObject(address common.Address, db ethdb.Database) *StateObject {
|
func NewStateObject(address common.Address, db trie.Database) *StateObject {
|
||||||
object := &StateObject{db: db, address: address, balance: new(big.Int), dirty: true}
|
object := &StateObject{
|
||||||
|
db: db,
|
||||||
|
address: address,
|
||||||
|
balance: new(big.Int),
|
||||||
|
dirty: true,
|
||||||
|
codeHash: emptyCodeHash,
|
||||||
|
storage: make(Storage),
|
||||||
|
}
|
||||||
object.trie, _ = trie.NewSecure(common.Hash{}, db)
|
object.trie, _ = trie.NewSecure(common.Hash{}, db)
|
||||||
object.storage = make(Storage)
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStateObjectFromBytes(address common.Address, data []byte, db ethdb.Database) *StateObject {
|
|
||||||
var extobject struct {
|
|
||||||
Nonce uint64
|
|
||||||
Balance *big.Int
|
|
||||||
Root common.Hash
|
|
||||||
CodeHash []byte
|
|
||||||
}
|
|
||||||
err := rlp.Decode(bytes.NewReader(data), &extobject)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("can't decode state object %x: %v", address, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
trie, err := trie.NewSecure(extobject.Root, db)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: bubble this up or panic
|
|
||||||
glog.Errorf("can't create account trie with root %x: %v", extobject.Root[:], err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
object := &StateObject{address: address, db: db}
|
|
||||||
object.nonce = extobject.Nonce
|
|
||||||
object.balance = extobject.Balance
|
|
||||||
object.codeHash = extobject.CodeHash
|
|
||||||
object.trie = trie
|
|
||||||
object.storage = make(map[string]common.Hash)
|
|
||||||
object.code, _ = db.Get(extobject.CodeHash)
|
|
||||||
return object
|
return object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,7 +150,6 @@ func (self *StateObject) Update() {
|
||||||
self.trie.Delete([]byte(key))
|
self.trie.Delete([]byte(key))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setAddr([]byte(key), value)
|
self.setAddr([]byte(key), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -248,6 +225,7 @@ func (self *StateObject) Code() []byte {
|
||||||
|
|
||||||
func (self *StateObject) SetCode(code []byte) {
|
func (self *StateObject) SetCode(code []byte) {
|
||||||
self.code = code
|
self.code = code
|
||||||
|
self.codeHash = crypto.Sha3(code)
|
||||||
self.dirty = true
|
self.dirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,23 +254,40 @@ func (self *StateObject) EachStorage(cb func(key, value []byte)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Encoding
|
// Encoding
|
||||||
//
|
|
||||||
|
|
||||||
// State object encoding methods
|
type extStateObject struct {
|
||||||
func (c *StateObject) RlpEncode() []byte {
|
Nonce uint64
|
||||||
return common.Encode([]interface{}{c.nonce, c.balance, c.Root(), c.CodeHash()})
|
Balance *big.Int
|
||||||
|
Root common.Hash
|
||||||
|
CodeHash []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StateObject) CodeHash() common.Bytes {
|
// EncodeRLP implements rlp.Encoder.
|
||||||
return crypto.Sha3(c.code)
|
func (c *StateObject) EncodeRLP(w io.Writer) error {
|
||||||
|
return rlp.Encode(w, []interface{}{c.nonce, c.balance, c.Root(), c.codeHash})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Storage change object. Used by the manifest for notifying changes to
|
// DecodeObject decodes an RLP-encoded state object.
|
||||||
// the sub channels.
|
func DecodeObject(address common.Address, db trie.Database, data []byte) (*StateObject, error) {
|
||||||
type StorageState struct {
|
var (
|
||||||
StateAddress []byte
|
obj = &StateObject{address: address, db: db, storage: make(Storage)}
|
||||||
Address []byte
|
ext extStateObject
|
||||||
Value *big.Int
|
err error
|
||||||
|
)
|
||||||
|
if err = rlp.DecodeBytes(data, &ext); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if obj.trie, err = trie.NewSecure(ext.Root, db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(ext.CodeHash, emptyCodeHash) {
|
||||||
|
if obj.code, err = db.Get(ext.CodeHash); err != nil {
|
||||||
|
return nil, fmt.Errorf("can't find code for hash %x: %v", ext.CodeHash, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.nonce = ext.Nonce
|
||||||
|
obj.balance = ext.Balance
|
||||||
|
obj.codeHash = ext.CodeHash
|
||||||
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,8 +138,7 @@ func TestSnapshot2(t *testing.T) {
|
||||||
so0 := state.GetStateObject(stateobjaddr0)
|
so0 := state.GetStateObject(stateobjaddr0)
|
||||||
so0.balance = big.NewInt(42)
|
so0.balance = big.NewInt(42)
|
||||||
so0.nonce = 43
|
so0.nonce = 43
|
||||||
so0.code = []byte{'c', 'a', 'f', 'e'}
|
so0.SetCode([]byte{'c', 'a', 'f', 'e'})
|
||||||
so0.codeHash = so0.CodeHash()
|
|
||||||
so0.remove = true
|
so0.remove = true
|
||||||
so0.deleted = false
|
so0.deleted = false
|
||||||
so0.dirty = false
|
so0.dirty = false
|
||||||
|
|
@ -149,8 +148,7 @@ func TestSnapshot2(t *testing.T) {
|
||||||
so1 := state.GetStateObject(stateobjaddr1)
|
so1 := state.GetStateObject(stateobjaddr1)
|
||||||
so1.balance = big.NewInt(52)
|
so1.balance = big.NewInt(52)
|
||||||
so1.nonce = 53
|
so1.nonce = 53
|
||||||
so1.code = []byte{'c', 'a', 'f', 'e', '2'}
|
so1.SetCode([]byte{'c', 'a', 'f', 'e', '2'})
|
||||||
so1.codeHash = so1.CodeHash()
|
|
||||||
so1.remove = true
|
so1.remove = true
|
||||||
so1.deleted = true
|
so1.deleted = true
|
||||||
so1.dirty = true
|
so1.dirty = true
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -25,6 +26,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"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/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -204,13 +206,15 @@ func (self *StateDB) Delete(addr common.Address) bool {
|
||||||
|
|
||||||
// Update the given state object and apply it to state trie
|
// Update the given state object and apply it to state trie
|
||||||
func (self *StateDB) UpdateStateObject(stateObject *StateObject) {
|
func (self *StateDB) UpdateStateObject(stateObject *StateObject) {
|
||||||
//addr := stateObject.Address()
|
if len(stateObject.code) > 0 {
|
||||||
|
self.db.Put(stateObject.codeHash, stateObject.code)
|
||||||
if len(stateObject.CodeHash()) > 0 {
|
|
||||||
self.db.Put(stateObject.CodeHash(), stateObject.code)
|
|
||||||
}
|
}
|
||||||
addr := stateObject.Address()
|
addr := stateObject.Address()
|
||||||
self.trie.Update(addr[:], stateObject.RlpEncode())
|
data, err := rlp.EncodeToBytes(stateObject)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
|
||||||
|
}
|
||||||
|
self.trie.Update(addr[:], data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the given state object and delete it from the state trie
|
// Delete the given state object and delete it from the state trie
|
||||||
|
|
@ -237,10 +241,12 @@ func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObje
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
stateObject, err := DecodeObject(addr, self.db, data)
|
||||||
stateObject = NewStateObjectFromBytes(addr, []byte(data), self.db)
|
if err != nil {
|
||||||
|
glog.Errorf("can't decode object at %x: %v", addr[:], err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
self.SetStateObject(stateObject)
|
self.SetStateObject(stateObject)
|
||||||
|
|
||||||
return stateObject
|
return stateObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
@ -65,10 +66,10 @@ type TxPool struct {
|
||||||
minGasPrice *big.Int
|
minGasPrice *big.Int
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
events event.Subscription
|
events event.Subscription
|
||||||
|
localTx *txSet
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
pending map[common.Hash]*types.Transaction // processable transactions
|
pending map[common.Hash]*types.Transaction // processable transactions
|
||||||
queue map[common.Address]map[common.Hash]*types.Transaction
|
queue map[common.Address]map[common.Hash]*types.Transaction
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn, gasLimitFn func() *big.Int) *TxPool {
|
func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn, gasLimitFn func() *big.Int) *TxPool {
|
||||||
|
|
@ -81,6 +82,7 @@ func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn, gasLimitFn func(
|
||||||
gasLimit: gasLimitFn,
|
gasLimit: gasLimitFn,
|
||||||
minGasPrice: new(big.Int),
|
minGasPrice: new(big.Int),
|
||||||
pendingState: nil,
|
pendingState: nil,
|
||||||
|
localTx: newTxSet(),
|
||||||
events: eventMux.Subscribe(ChainHeadEvent{}, GasPriceChanged{}, RemovedTransactionEvent{}),
|
events: eventMux.Subscribe(ChainHeadEvent{}, GasPriceChanged{}, RemovedTransactionEvent{}),
|
||||||
}
|
}
|
||||||
go pool.eventLoop()
|
go pool.eventLoop()
|
||||||
|
|
@ -167,6 +169,14 @@ func (pool *TxPool) Stats() (pending int, queued int) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLocal marks a transaction as local, skipping gas price
|
||||||
|
// check against local miner minimum in the future
|
||||||
|
func (pool *TxPool) SetLocal(tx *types.Transaction) {
|
||||||
|
pool.mu.Lock()
|
||||||
|
defer pool.mu.Unlock()
|
||||||
|
pool.localTx.add(tx.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
// validateTx checks whether a transaction is valid according
|
// validateTx checks whether a transaction is valid according
|
||||||
// to the consensus rules.
|
// to the consensus rules.
|
||||||
func (pool *TxPool) validateTx(tx *types.Transaction) error {
|
func (pool *TxPool) validateTx(tx *types.Transaction) error {
|
||||||
|
|
@ -176,8 +186,9 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
local := pool.localTx.contains(tx.Hash())
|
||||||
// Drop transactions under our own minimal accepted gas price
|
// Drop transactions under our own minimal accepted gas price
|
||||||
if pool.minGasPrice.Cmp(tx.GasPrice()) > 0 {
|
if !local && pool.minGasPrice.Cmp(tx.GasPrice()) > 0 {
|
||||||
return ErrCheap
|
return ErrCheap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,3 +537,49 @@ type txQueueEntry struct {
|
||||||
func (q txQueue) Len() int { return len(q) }
|
func (q txQueue) Len() int { return len(q) }
|
||||||
func (q txQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] }
|
func (q txQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] }
|
||||||
func (q txQueue) Less(i, j int) bool { return q[i].Nonce() < q[j].Nonce() }
|
func (q txQueue) Less(i, j int) bool { return q[i].Nonce() < q[j].Nonce() }
|
||||||
|
|
||||||
|
// txSet represents a set of transaction hashes in which entries
|
||||||
|
// are automatically dropped after txSetDuration time
|
||||||
|
type txSet struct {
|
||||||
|
txMap map[common.Hash]struct{}
|
||||||
|
txOrd map[uint64]txOrdType
|
||||||
|
addPtr, delPtr uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
const txSetDuration = time.Hour * 2
|
||||||
|
|
||||||
|
// txOrdType represents an entry in the time-ordered list of transaction hashes
|
||||||
|
type txOrdType struct {
|
||||||
|
hash common.Hash
|
||||||
|
time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTxSet creates a new transaction set
|
||||||
|
func newTxSet() *txSet {
|
||||||
|
return &txSet{
|
||||||
|
txMap: make(map[common.Hash]struct{}),
|
||||||
|
txOrd: make(map[uint64]txOrdType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains returns true if the set contains the given transaction hash
|
||||||
|
// (not thread safe, should be called from a locked environment)
|
||||||
|
func (self *txSet) contains(hash common.Hash) bool {
|
||||||
|
_, ok := self.txMap[hash]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds a transaction hash to the set, then removes entries older than txSetDuration
|
||||||
|
// (not thread safe, should be called from a locked environment)
|
||||||
|
func (self *txSet) add(hash common.Hash) {
|
||||||
|
self.txMap[hash] = struct{}{}
|
||||||
|
now := time.Now()
|
||||||
|
self.txOrd[self.addPtr] = txOrdType{hash: hash, time: now}
|
||||||
|
self.addPtr++
|
||||||
|
delBefore := now.Add(-txSetDuration)
|
||||||
|
for self.delPtr < self.addPtr && self.txOrd[self.delPtr].time.Before(delBefore) {
|
||||||
|
delete(self.txMap, self.txOrd[self.delPtr].hash)
|
||||||
|
delete(self.txOrd, self.delPtr)
|
||||||
|
self.delPtr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,17 @@ func TestInvalidTransactions(t *testing.T) {
|
||||||
if err := pool.Add(tx); err != ErrNonce {
|
if err := pool.Add(tx); err != ErrNonce {
|
||||||
t.Error("expected", ErrNonce)
|
t.Error("expected", ErrNonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx = transaction(1, big.NewInt(100000), key)
|
||||||
|
pool.minGasPrice = big.NewInt(1000)
|
||||||
|
if err := pool.Add(tx); err != ErrCheap {
|
||||||
|
t.Error("expected", ErrCheap, "got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.SetLocal(tx)
|
||||||
|
if err := pool.Add(tx); err != nil {
|
||||||
|
t.Error("expected", nil, "got", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransactionQueue(t *testing.T) {
|
func TestTransactionQueue(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -125,17 +125,14 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
|
||||||
// Receipts is a wrapper around a Receipt array to implement types.DerivableList.
|
// Receipts is a wrapper around a Receipt array to implement types.DerivableList.
|
||||||
type Receipts []*Receipt
|
type Receipts []*Receipt
|
||||||
|
|
||||||
// RlpEncode implements common.RlpEncode required for SHA3 derivation.
|
// Len returns the number of receipts in this list.
|
||||||
func (r Receipts) RlpEncode() []byte {
|
func (r Receipts) Len() int { return len(r) }
|
||||||
bytes, err := rlp.EncodeToBytes(r)
|
|
||||||
|
// GetRlp returns the RLP encoding of one receipt from the list.
|
||||||
|
func (r Receipts) GetRlp(i int) []byte {
|
||||||
|
bytes, err := rlp.EncodeToBytes(r[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return bytes
|
return bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the number of receipts in this list.
|
|
||||||
func (r Receipts) Len() int { return len(r) }
|
|
||||||
|
|
||||||
// GetRlp returns the RLP encoding of one receipt from the list.
|
|
||||||
func (r Receipts) GetRlp(i int) []byte { return common.Rlp(r[i]) }
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
|
@ -63,6 +64,21 @@ func (l *Log) String() string {
|
||||||
return fmt.Sprintf(`log: %x %x %x %x %d %x %d`, l.Address, l.Topics, l.Data, l.TxHash, l.TxIndex, l.BlockHash, l.Index)
|
return fmt.Sprintf(`log: %x %x %x %x %d %x %d`, l.Address, l.Topics, l.Data, l.TxHash, l.TxIndex, l.BlockHash, l.Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Log) MarshalJSON() ([]byte, error) {
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"address": r.Address,
|
||||||
|
"data": fmt.Sprintf("%#x", r.Data),
|
||||||
|
"blockNumber": fmt.Sprintf("%#x", r.BlockNumber),
|
||||||
|
"logIndex": fmt.Sprintf("%#x", r.Index),
|
||||||
|
"blockHash": r.BlockHash,
|
||||||
|
"transactionHash": r.TxHash,
|
||||||
|
"transactionIndex": fmt.Sprintf("%#x", r.TxIndex),
|
||||||
|
"topics": r.Topics,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(fields)
|
||||||
|
}
|
||||||
|
|
||||||
type Logs []*Log
|
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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,9 +100,10 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var paramsFromCurve = map[elliptic.Curve]*ECIESParams{
|
var paramsFromCurve = map[elliptic.Curve]*ECIESParams{
|
||||||
elliptic.P256(): ECIES_AES128_SHA256,
|
secp256k1.S256(): ECIES_AES128_SHA256,
|
||||||
elliptic.P384(): ECIES_AES256_SHA384,
|
elliptic.P256(): ECIES_AES128_SHA256,
|
||||||
elliptic.P521(): ECIES_AES256_SHA512,
|
elliptic.P384(): ECIES_AES256_SHA384,
|
||||||
|
elliptic.P521(): ECIES_AES256_SHA512,
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) {
|
func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -49,17 +50,17 @@ type plainKeyJSON struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptedKeyJSONV3 struct {
|
type encryptedKeyJSONV3 struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Crypto cryptoJSON
|
Crypto cryptoJSON `json:"crypto"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptedKeyJSONV1 struct {
|
type encryptedKeyJSONV1 struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Crypto cryptoJSON
|
Crypto cryptoJSON `json:"crypto"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cryptoJSON struct {
|
type cryptoJSON struct {
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
secp256k1-go
|
|
||||||
=======
|
|
||||||
|
|
||||||
golang secp256k1 library
|
|
||||||
|
|
||||||
Implements cryptographic operations for the secp256k1 ECDSA curve used by Bitcoin.
|
|
||||||
|
|
||||||
Installing
|
|
||||||
===
|
|
||||||
|
|
||||||
GMP library headers are required to build. On Debian-based systems, the package is called `libgmp-dev`.
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo apt-get install libgmp-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Now compiles with cgo!
|
|
||||||
|
|
||||||
Test
|
|
||||||
===
|
|
||||||
|
|
||||||
To run tests do
|
|
||||||
```
|
|
||||||
go tests
|
|
||||||
```
|
|
||||||
|
|
@ -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
|
|
||||||
// ignore the first true bit in |k|. If we don't find any true bits in
|
|
||||||
// |k|, then we return nil, nil, because we cannot return the identity
|
|
||||||
// element.
|
|
||||||
|
|
||||||
Bz := new(big.Int).SetInt64(1)
|
|
||||||
x := Bx
|
|
||||||
y := By
|
|
||||||
z := Bz
|
|
||||||
|
|
||||||
seenFirstTrue := false
|
|
||||||
for _, byte := range k {
|
|
||||||
for bitNum := 0; bitNum < 8; bitNum++ {
|
|
||||||
if seenFirstTrue {
|
|
||||||
x, y, z = BitCurve.doubleJacobian(x, y, z)
|
|
||||||
}
|
|
||||||
if byte&0x80 == 0x80 {
|
|
||||||
if !seenFirstTrue {
|
|
||||||
seenFirstTrue = true
|
|
||||||
} else {
|
|
||||||
x, y, z = BitCurve.addJacobian(Bx, By, Bz, x, y, z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byte <<= 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
padded := make([]byte, 32)
|
||||||
|
copy(padded[32-len(scalar):], scalar)
|
||||||
|
scalar = padded
|
||||||
|
|
||||||
if !seenFirstTrue {
|
// Do the multiplication in C, updating point.
|
||||||
|
point := make([]byte, 64)
|
||||||
|
readBits(point[:32], Bx)
|
||||||
|
readBits(point[32:], By)
|
||||||
|
pointPtr := (*C.uchar)(unsafe.Pointer(&point[0]))
|
||||||
|
scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0]))
|
||||||
|
res := C.secp256k1_pubkey_scalar_mul(context, pointPtr, scalarPtr)
|
||||||
|
|
||||||
|
// Unpack the result and clear temporaries.
|
||||||
|
x := new(big.Int).SetBytes(point[:32])
|
||||||
|
y := new(big.Int).SetBytes(point[32:])
|
||||||
|
for i := range point {
|
||||||
|
point[i] = 0
|
||||||
|
}
|
||||||
|
for i := range padded {
|
||||||
|
scalar[i] = 0
|
||||||
|
}
|
||||||
|
if res != 1 {
|
||||||
return nil, nil
|
return 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
|
||||||
}
|
}
|
||||||
39
crypto/secp256k1/curve_test.go
Normal file
39
crypto/secp256k1/curve_test.go
Normal 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")
|
||||||
|
}
|
||||||
56
crypto/secp256k1/pubkey_scalar_mul.h
Normal file
56
crypto/secp256k1/pubkey_scalar_mul.h
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -20,14 +20,8 @@ package secp256k1
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -I./libsecp256k1
|
#cgo CFLAGS: -I./libsecp256k1
|
||||||
#cgo darwin CFLAGS: -I/usr/local/include
|
#cgo CFLAGS: -I./libsecp256k1/src/
|
||||||
#cgo freebsd CFLAGS: -I/usr/local/include
|
#define USE_NUM_NONE
|
||||||
#cgo linux,arm CFLAGS: -I/usr/local/arm/include
|
|
||||||
#cgo LDFLAGS: -lgmp
|
|
||||||
#cgo darwin LDFLAGS: -L/usr/local/lib
|
|
||||||
#cgo freebsd LDFLAGS: -L/usr/local/lib
|
|
||||||
#cgo linux,arm LDFLAGS: -L/usr/local/arm/lib
|
|
||||||
#define USE_NUM_GMP
|
|
||||||
#define USE_FIELD_10X26
|
#define USE_FIELD_10X26
|
||||||
#define USE_FIELD_INV_BUILTIN
|
#define USE_FIELD_INV_BUILTIN
|
||||||
#define USE_SCALAR_8X32
|
#define USE_SCALAR_8X32
|
||||||
|
|
@ -35,6 +29,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 +39,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 +52,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 +77,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 +252,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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1405
eth/api.go
Normal file
1405
eth/api.go
Normal file
File diff suppressed because it is too large
Load diff
430
eth/backend.go
430
eth/backend.go
|
|
@ -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
|
||||||
|
MinerThreads int
|
||||||
Mining bool
|
NatSpec bool
|
||||||
MinerThreads int
|
AutoDAG bool
|
||||||
NatSpec bool
|
PowTest bool
|
||||||
DataDir string
|
autodagquit chan bool
|
||||||
AutoDAG bool
|
etherbase common.Address
|
||||||
PowTest bool
|
netVersionId int
|
||||||
autodagquit chan bool
|
|
||||||
etherbase common.Address
|
|
||||||
clientVersion string
|
|
||||||
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,65 +151,40 @@ 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
|
||||||
// different modes
|
if config.TestGenesisState != nil {
|
||||||
switch {
|
chainDb = config.TestGenesisState
|
||||||
case config.Olympic:
|
|
||||||
glog.V(logger.Error).Infoln("Starting Olympic network")
|
|
||||||
fallthrough
|
|
||||||
case config.DevMode:
|
|
||||||
_, err := core.WriteOlympicGenesisBlock(chainDb, 42)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case config.TestNet:
|
|
||||||
state.StartingNonce = 1048576 // (2**20)
|
|
||||||
_, err := core.WriteTestNetGenesisBlock(chainDb, 0x6d6f7264656e)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// This is for testing only.
|
if config.TestGenesisBlock != nil {
|
||||||
if config.GenesisBlock != nil {
|
core.WriteTd(chainDb, config.TestGenesisBlock.Hash(), config.TestGenesisBlock.Difficulty())
|
||||||
core.WriteTd(chainDb, config.GenesisBlock.Hash(), config.GenesisBlock.Difficulty())
|
core.WriteBlock(chainDb, config.TestGenesisBlock)
|
||||||
core.WriteBlock(chainDb, config.GenesisBlock)
|
core.WriteCanonicalHash(chainDb, config.TestGenesisBlock.Hash(), config.TestGenesisBlock.NumberU64())
|
||||||
core.WriteCanonicalHash(chainDb, config.GenesisBlock.Hash(), config.GenesisBlock.NumberU64())
|
core.WriteHeadBlockHash(chainDb, config.TestGenesisBlock.Hash())
|
||||||
core.WriteHeadBlockHash(chainDb, config.GenesisBlock.Hash())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.SkipBcVersionCheck {
|
if !config.SkipBcVersionCheck {
|
||||||
b, _ := chainDb.Get([]byte("BlockchainVersion"))
|
bcVersion := core.GetBlockChainVersion(chainDb)
|
||||||
bcVersion := int(common.NewValue(b).Uint())
|
|
||||||
if bcVersion != config.BlockChainVersion && bcVersion != 0 {
|
if bcVersion != config.BlockChainVersion && bcVersion != 0 {
|
||||||
return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d). Run geth upgradedb.\n", bcVersion, config.BlockChainVersion)
|
return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d). Run geth upgradedb.\n", bcVersion, config.BlockChainVersion)
|
||||||
}
|
}
|
||||||
saveBlockchainVersion(chainDb, config.BlockChainVersion)
|
core.WriteBlockChainVersion(chainDb, config.BlockChainVersion)
|
||||||
}
|
}
|
||||||
glog.V(logger.Info).Infof("Blockchain DB Version: %d", config.BlockChainVersion)
|
glog.V(logger.Info).Infof("Blockchain DB Version: %d", config.BlockChainVersion)
|
||||||
|
|
||||||
|
|
@ -365,11 +192,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 +237,78 @@ 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{
|
||||||
|
{
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicEthereumAPI(s),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicAccountAPI(s.AccountManager()),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "personal",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPrivateAccountAPI(s.AccountManager()),
|
||||||
|
Public: false,
|
||||||
|
}, {
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicBlockChainAPI(s.BlockChain(), s.ChainDb(), s.EventMux(), s.AccountManager()),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicTransactionPoolAPI(s.TxPool(), s.ChainDb(), s.EventMux(), s.BlockChain(), s.AccountManager()),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: miner.NewPublicMinerAPI(s.Miner()),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: downloader.NewPublicDownloaderAPI(s.Downloader()),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "miner",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPrivateMinerAPI(s),
|
||||||
|
Public: false,
|
||||||
|
}, {
|
||||||
|
Namespace: "txpool",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicTxPoolAPI(s),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: filters.NewPublicFilterAPI(s.ChainDb(), s.EventMux()),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "admin",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPrivateAdminAPI(s),
|
||||||
|
}, {
|
||||||
|
Namespace: "debug",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicDebugAPI(s),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "debug",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPrivateDebugAPI(s),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
|
func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
|
||||||
|
|
@ -480,86 +337,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
|
||||||
|
|
@ -659,15 +478,6 @@ func dagFiles(epoch uint64) (string, string) {
|
||||||
return dag, "full-R" + dag
|
return dag, "full-R" + dag
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveBlockchainVersion(db ethdb.Database, bcVersion int) {
|
|
||||||
d, _ := db.Get([]byte("BlockchainVersion"))
|
|
||||||
blockchainVersion := common.NewValue(d).Uint()
|
|
||||||
|
|
||||||
if blockchainVersion == 0 {
|
|
||||||
db.Put([]byte("BlockchainVersion"), common.NewValue(bcVersion).Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// upgradeChainDatabase ensures that the chain database stores block split into
|
// upgradeChainDatabase ensures that the chain database stores block split into
|
||||||
// separate header and body entries.
|
// separate header and body entries.
|
||||||
func upgradeChainDatabase(db ethdb.Database) error {
|
func upgradeChainDatabase(db ethdb.Database) error {
|
||||||
|
|
|
||||||
64
eth/downloader/api.go
Normal file
64
eth/downloader/api.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -61,8 +61,11 @@ func makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Recei
|
||||||
block.AddTx(tx)
|
block.AddTx(tx)
|
||||||
}
|
}
|
||||||
// If the block number is a multiple of 5, add a bonus uncle to the block
|
// If the block number is a multiple of 5, add a bonus uncle to the block
|
||||||
if i%5 == 0 {
|
if i > 0 && i%5 == 0 {
|
||||||
block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 1).Hash(), Number: big.NewInt(int64(i - 1))})
|
block.AddUncle(&types.Header{
|
||||||
|
ParentHash: block.PrevBlock(i - 1).Hash(),
|
||||||
|
Number: big.NewInt(block.Number().Int64() - 1),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Convert the block-chain into a hash-chain and header/block maps
|
// Convert the block-chain into a hash-chain and header/block maps
|
||||||
|
|
|
||||||
575
eth/filters/api.go
Normal file
575
eth/filters/api.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -373,6 +373,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&query); err != nil {
|
if err := msg.Decode(&query); err != nil {
|
||||||
return errResp(ErrDecode, "%v: %v", msg, err)
|
return errResp(ErrDecode, "%v: %v", msg, err)
|
||||||
}
|
}
|
||||||
|
hashMode := query.Origin.Hash != (common.Hash{})
|
||||||
|
|
||||||
// Gather headers until the fetch or network limits is reached
|
// Gather headers until the fetch or network limits is reached
|
||||||
var (
|
var (
|
||||||
bytes common.StorageSize
|
bytes common.StorageSize
|
||||||
|
|
@ -382,7 +384,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && len(headers) < downloader.MaxHeaderFetch {
|
for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && len(headers) < downloader.MaxHeaderFetch {
|
||||||
// Retrieve the next header satisfying the query
|
// Retrieve the next header satisfying the query
|
||||||
var origin *types.Header
|
var origin *types.Header
|
||||||
if query.Origin.Hash != (common.Hash{}) {
|
if hashMode {
|
||||||
origin = pm.blockchain.GetHeader(query.Origin.Hash)
|
origin = pm.blockchain.GetHeader(query.Origin.Hash)
|
||||||
} else {
|
} else {
|
||||||
origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number)
|
origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number)
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,15 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
|
||||||
pm.blockchain.GetBlockByNumber(1).Hash(),
|
pm.blockchain.GetBlockByNumber(1).Hash(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Check a corner case where requesting more can iterate past the endpoints
|
||||||
|
{
|
||||||
|
&getBlockHeadersData{Origin: hashOrNumber{Number: 2}, Amount: 5, Reverse: true},
|
||||||
|
[]common.Hash{
|
||||||
|
pm.blockchain.GetBlockByNumber(2).Hash(),
|
||||||
|
pm.blockchain.GetBlockByNumber(1).Hash(),
|
||||||
|
pm.blockchain.GetBlockByNumber(0).Hash(),
|
||||||
|
},
|
||||||
|
},
|
||||||
// Check that non existing headers aren't returned
|
// Check that non existing headers aren't returned
|
||||||
{
|
{
|
||||||
&getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1},
|
&getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1},
|
||||||
|
|
@ -322,6 +331,17 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
|
||||||
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
||||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
t.Errorf("test %d: headers mismatch: %v", i, err)
|
||||||
}
|
}
|
||||||
|
// If the test used number origins, repeat with hashes as the too
|
||||||
|
if tt.query.Origin.Hash == (common.Hash{}) {
|
||||||
|
if origin := pm.blockchain.GetBlockByNumber(tt.query.Origin.Number); origin != nil {
|
||||||
|
tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0
|
||||||
|
|
||||||
|
p2p.Send(peer.app, 0x03, tt.query)
|
||||||
|
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
||||||
|
t.Errorf("test %d: headers mismatch: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package ethdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -90,27 +89,7 @@ func (db *MemDatabase) Delete(key []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *MemDatabase) Print() {
|
func (db *MemDatabase) Close() {}
|
||||||
db.lock.RLock()
|
|
||||||
defer db.lock.RUnlock()
|
|
||||||
|
|
||||||
for key, val := range db.db {
|
|
||||||
fmt.Printf("%x(%d): ", key, len(key))
|
|
||||||
node := common.NewValueFromBytes(val)
|
|
||||||
fmt.Printf("%q\n", node.Val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *MemDatabase) Close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *MemDatabase) LastKnownTD() []byte {
|
|
||||||
data, _ := db.Get([]byte("LastKnownTotalDifficulty"))
|
|
||||||
if len(data) == 0 || data == nil {
|
|
||||||
data = []byte{0x0}
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *MemDatabase) NewBatch() Batch {
|
func (db *MemDatabase) NewBatch() Batch {
|
||||||
return &memBatch{db: db}
|
return &memBatch{db: db}
|
||||||
|
|
|
||||||
7266
jsre/ethereum_js.go
7266
jsre/ethereum_js.go
File diff suppressed because it is too large
Load diff
20
jsre/jsre.go
20
jsre/jsre.go
|
|
@ -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)
|
||||||
|
|
||||||
|
|
@ -154,7 +164,7 @@ loop:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("js error:", err, arguments)
|
fmt.Println("js error:", err, arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it
|
_, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it
|
||||||
if timer.interval && inreg {
|
if timer.interval && inreg {
|
||||||
timer.timer.Reset(timer.duration)
|
timer.timer.Reset(timer.duration)
|
||||||
|
|
|
||||||
98
light/odr.go
Normal file
98
light/odr.go
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package light implements on-demand retrieval capable state and chain objects
|
||||||
|
// for the Ethereum Light Client.
|
||||||
|
package light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OdrBackend is an interface to a backend service that handles odr retrievals
|
||||||
|
type OdrBackend interface {
|
||||||
|
Database() ethdb.Database
|
||||||
|
Retrieve(ctx context.Context, req OdrRequest) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// OdrRequest is an interface for retrieval requests
|
||||||
|
type OdrRequest interface {
|
||||||
|
StoreResult(db ethdb.Database)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrieRequest is the ODR request type for state/storage trie entries
|
||||||
|
type TrieRequest struct {
|
||||||
|
OdrRequest
|
||||||
|
root common.Hash
|
||||||
|
key []byte
|
||||||
|
proof []rlp.RawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreResult stores the retrieved data in local database
|
||||||
|
func (req *TrieRequest) StoreResult(db ethdb.Database) {
|
||||||
|
storeProof(db, req.proof)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeProof stores the new trie nodes obtained from a merkle proof in the database
|
||||||
|
func storeProof(db ethdb.Database, proof []rlp.RawValue) {
|
||||||
|
for _, buf := range proof {
|
||||||
|
hash := crypto.Sha3(buf)
|
||||||
|
val, _ := db.Get(hash)
|
||||||
|
if val == nil {
|
||||||
|
db.Put(hash, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeDataRequest is the ODR request type for node data (used for retrieving contract code)
|
||||||
|
type NodeDataRequest struct {
|
||||||
|
OdrRequest
|
||||||
|
hash common.Hash
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetData returns the retrieved node data after a successful request
|
||||||
|
func (req *NodeDataRequest) GetData() []byte {
|
||||||
|
return req.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreResult stores the retrieved data in local database
|
||||||
|
func (req *NodeDataRequest) StoreResult(db ethdb.Database) {
|
||||||
|
db.Put(req.hash[:], req.GetData())
|
||||||
|
}
|
||||||
|
|
||||||
|
var sha3_nil = crypto.Sha3Hash(nil)
|
||||||
|
|
||||||
|
// retrieveNodeData tries to retrieve node data with the given hash from the network
|
||||||
|
func retrieveNodeData(ctx context.Context, odr OdrBackend, hash common.Hash) ([]byte, error) {
|
||||||
|
if hash == sha3_nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
res, _ := odr.Database().Get(hash[:])
|
||||||
|
if res != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
r := &NodeDataRequest{hash: hash}
|
||||||
|
if err := odr.Retrieve(ctx, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return r.GetData(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
275
light/state.go
Normal file
275
light/state.go
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartingNonce determines the default nonce when new accounts are being created.
|
||||||
|
var StartingNonce uint64
|
||||||
|
|
||||||
|
// LightState is a memory representation of a state.
|
||||||
|
// This version is ODR capable, caching only the already accessed part of the
|
||||||
|
// state, retrieving unknown parts on-demand from the ODR backend. Changes are
|
||||||
|
// never stored in the local database, only in the memory objects.
|
||||||
|
type LightState struct {
|
||||||
|
odr OdrBackend
|
||||||
|
trie *LightTrie
|
||||||
|
|
||||||
|
stateObjects map[string]*StateObject
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLightState creates a new LightState with the specified root.
|
||||||
|
// Note that the creation of a light state is always successful, even if the
|
||||||
|
// root is non-existent. In that case, ODR retrieval will always be unsuccessful
|
||||||
|
// and every operation will return with an error or wait for the context to be
|
||||||
|
// cancelled.
|
||||||
|
func NewLightState(root common.Hash, odr OdrBackend) *LightState {
|
||||||
|
tr := NewLightTrie(root, odr, true)
|
||||||
|
return &LightState{
|
||||||
|
odr: odr,
|
||||||
|
trie: tr,
|
||||||
|
stateObjects: make(map[string]*StateObject),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAccount returns true if an account exists at the given address
|
||||||
|
func (self *LightState) HasAccount(ctx context.Context, addr common.Address) (bool, error) {
|
||||||
|
so, err := self.GetStateObject(ctx, addr)
|
||||||
|
return so != nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBalance retrieves the balance from the given address or 0 if the account does
|
||||||
|
// not exist
|
||||||
|
func (self *LightState) GetBalance(ctx context.Context, addr common.Address) (*big.Int, error) {
|
||||||
|
stateObject, err := self.GetStateObject(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return common.Big0, err
|
||||||
|
}
|
||||||
|
if stateObject != nil {
|
||||||
|
return stateObject.balance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return common.Big0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNonce returns the nonce at the given address or 0 if the account does
|
||||||
|
// not exist
|
||||||
|
func (self *LightState) GetNonce(ctx context.Context, addr common.Address) (uint64, error) {
|
||||||
|
stateObject, err := self.GetStateObject(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if stateObject != nil {
|
||||||
|
return stateObject.nonce, nil
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCode returns the contract code at the given address or nil if the account
|
||||||
|
// does not exist
|
||||||
|
func (self *LightState) GetCode(ctx context.Context, addr common.Address) ([]byte, error) {
|
||||||
|
stateObject, err := self.GetStateObject(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if stateObject != nil {
|
||||||
|
return stateObject.code, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the contract storage value at storage address b from the
|
||||||
|
// contract address a or common.Hash{} if the account does not exist
|
||||||
|
func (self *LightState) GetState(ctx context.Context, a common.Address, b common.Hash) (common.Hash, error) {
|
||||||
|
stateObject, err := self.GetStateObject(ctx, a)
|
||||||
|
if err == nil && stateObject != nil {
|
||||||
|
return stateObject.GetState(ctx, b)
|
||||||
|
}
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDeleted returns true if the given account has been marked for deletion
|
||||||
|
// or false if the account does not exist
|
||||||
|
func (self *LightState) IsDeleted(ctx context.Context, addr common.Address) (bool, error) {
|
||||||
|
stateObject, err := self.GetStateObject(ctx, addr)
|
||||||
|
if err == nil && stateObject != nil {
|
||||||
|
return stateObject.remove, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SETTERS
|
||||||
|
*/
|
||||||
|
|
||||||
|
// AddBalance adds the given amount to the balance of the specified account
|
||||||
|
func (self *LightState) AddBalance(ctx context.Context, addr common.Address, amount *big.Int) error {
|
||||||
|
stateObject, err := self.GetOrNewStateObject(ctx, addr)
|
||||||
|
if err == nil && stateObject != nil {
|
||||||
|
stateObject.AddBalance(amount)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNonce sets the nonce of the specified account
|
||||||
|
func (self *LightState) SetNonce(ctx context.Context, addr common.Address, nonce uint64) error {
|
||||||
|
stateObject, err := self.GetOrNewStateObject(ctx, addr)
|
||||||
|
if err == nil && stateObject != nil {
|
||||||
|
stateObject.SetNonce(nonce)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCode sets the contract code at the specified account
|
||||||
|
func (self *LightState) SetCode(ctx context.Context, addr common.Address, code []byte) error {
|
||||||
|
stateObject, err := self.GetOrNewStateObject(ctx, addr)
|
||||||
|
if err == nil && stateObject != nil {
|
||||||
|
stateObject.SetCode(code)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetState sets the storage value at storage address key of the account addr
|
||||||
|
func (self *LightState) SetState(ctx context.Context, addr common.Address, key common.Hash, value common.Hash) error {
|
||||||
|
stateObject, err := self.GetOrNewStateObject(ctx, addr)
|
||||||
|
if err == nil && stateObject != nil {
|
||||||
|
stateObject.SetState(key, value)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete marks an account to be removed and clears its balance
|
||||||
|
func (self *LightState) Delete(ctx context.Context, addr common.Address) (bool, error) {
|
||||||
|
stateObject, err := self.GetOrNewStateObject(ctx, addr)
|
||||||
|
if err == nil && stateObject != nil {
|
||||||
|
stateObject.MarkForDeletion()
|
||||||
|
stateObject.balance = new(big.Int)
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Get, set, new state object methods
|
||||||
|
//
|
||||||
|
|
||||||
|
// GetStateObject returns the state object of the given account or nil if the
|
||||||
|
// account does not exist
|
||||||
|
func (self *LightState) GetStateObject(ctx context.Context, addr common.Address) (stateObject *StateObject, err error) {
|
||||||
|
stateObject = self.stateObjects[addr.Str()]
|
||||||
|
if stateObject != nil {
|
||||||
|
if stateObject.deleted {
|
||||||
|
stateObject = nil
|
||||||
|
}
|
||||||
|
return stateObject, nil
|
||||||
|
}
|
||||||
|
data, err := self.trie.Get(ctx, addr[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stateObject, err = DecodeObject(ctx, addr, self.odr, []byte(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
self.SetStateObject(stateObject)
|
||||||
|
|
||||||
|
return stateObject, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStateObject sets the state object of the given account
|
||||||
|
func (self *LightState) SetStateObject(object *StateObject) {
|
||||||
|
self.stateObjects[object.Address().Str()] = object
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrNewStateObject returns the state object of the given account or creates a
|
||||||
|
// new one if the account does not exist
|
||||||
|
func (self *LightState) GetOrNewStateObject(ctx context.Context, addr common.Address) (*StateObject, error) {
|
||||||
|
stateObject, err := self.GetStateObject(ctx, addr)
|
||||||
|
if err == nil && (stateObject == nil || stateObject.deleted) {
|
||||||
|
stateObject, err = self.CreateStateObject(ctx, addr)
|
||||||
|
}
|
||||||
|
return stateObject, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// newStateObject creates a state object whether it exists in the state or not
|
||||||
|
func (self *LightState) newStateObject(addr common.Address) *StateObject {
|
||||||
|
if glog.V(logger.Core) {
|
||||||
|
glog.Infof("(+) %x\n", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
stateObject := NewStateObject(addr, self.odr)
|
||||||
|
stateObject.SetNonce(StartingNonce)
|
||||||
|
self.stateObjects[addr.Str()] = stateObject
|
||||||
|
|
||||||
|
return stateObject
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStateObject creates creates a new state object and takes ownership.
|
||||||
|
// This is different from "NewStateObject"
|
||||||
|
func (self *LightState) CreateStateObject(ctx context.Context, addr common.Address) (*StateObject, error) {
|
||||||
|
// Get previous (if any)
|
||||||
|
so, err := self.GetStateObject(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Create a new one
|
||||||
|
newSo := self.newStateObject(addr)
|
||||||
|
|
||||||
|
// If it existed set the balance to the new account
|
||||||
|
if so != nil {
|
||||||
|
newSo.balance = so.balance
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Setting, copying of the state methods
|
||||||
|
//
|
||||||
|
|
||||||
|
// Copy creates a copy of the state
|
||||||
|
func (self *LightState) Copy() *LightState {
|
||||||
|
// ignore error - we assume state-to-be-copied always exists
|
||||||
|
state := NewLightState(common.Hash{}, self.odr)
|
||||||
|
state.trie = self.trie
|
||||||
|
for k, stateObject := range self.stateObjects {
|
||||||
|
state.stateObjects[k] = stateObject.Copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set copies the contents of the given state onto this state, overwriting
|
||||||
|
// its contents
|
||||||
|
func (self *LightState) Set(state *LightState) {
|
||||||
|
self.trie = state.trie
|
||||||
|
self.stateObjects = state.stateObjects
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue