Merge branch 'tgerring-hexify' into develop

This commit is contained in:
obscuren 2015-04-01 12:51:44 +02:00
commit dba9b83aa0
29 changed files with 1725 additions and 326 deletions

4
Godeps/Godeps.json generated
View File

@ -90,6 +90,10 @@
"ImportPath": "github.com/robertkrimen/otto/token", "ImportPath": "github.com/robertkrimen/otto/token",
"Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba" "Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba"
}, },
{
"ImportPath": "github.com/rs/cors",
"Rev": "6e0c3cb65fc0fdb064c743d176a620e3ca446dfb"
},
{ {
"ImportPath": "github.com/syndtr/goleveldb/leveldb", "ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "832fa7ed4d28545eab80f19e1831fc004305cade" "Rev": "832fa7ed4d28545eab80f19e1831fc004305cade"

4
Godeps/_workspace/src/github.com/rs/cors/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,4 @@
language: go
go:
- 1.3
- 1.4

19
Godeps/_workspace/src/github.com/rs/cors/LICENSE generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2014 Olivier Poitrey <rs@dailymotion.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

84
Godeps/_workspace/src/github.com/rs/cors/README.md generated vendored Normal file
View File

@ -0,0 +1,84 @@
# Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![build](https://img.shields.io/travis/rs/cors.svg?style=flat)](https://travis-ci.org/rs/cors)
CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang.
## Getting Started
After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`.
```go
package main
import (
"net/http"
"github.com/rs/cors"
)
func main() {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"hello\": \"world\"}"))
})
// cors.Default() setup the middleware with default options being
// all origins accepted with simple methods (GET, POST). See
// documentation below for more options.
handler = cors.Default().Handler(h)
http.ListenAndServe(":8080", handler)
}
```
Install `cors`:
go get github.com/rs/cors
Then run your server:
go run server.go
The server now runs on `localhost:8080`:
$ curl -D - -H 'Origin: http://foo.com' http://localhost:8080/
HTTP/1.1 200 OK
Access-Control-Allow-Origin: foo.com
Content-Type: application/json
Date: Sat, 25 Oct 2014 03:43:57 GMT
Content-Length: 18
{"hello": "world"}
### More Examples
* `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go)
* [Goji](https://goji.io): [examples/goji/server.go](https://github.com/rs/cors/blob/master/examples/goji/server.go)
* [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go)
* [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go)
* [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go)
## Parameters
Parameters are passed to the middleware thru the `cors.New` method as follow:
```go
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://foo.com"},
AllowCredentials: true,
})
// Insert the middleware
handler = c.Handler(handler)
```
* **AllowedOrigins** `[]string`: A list of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. The default value is `*`.
* **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests.
* **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`)
* **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification
* **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`.
* **MaxAge** `int`: Indicates how long (in seconds) the results of a preflight request can be cached. The default is `0` which stands for no max age.
See [API documentation](http://godoc.org/github.com/rs/cors) for more info.
## Licenses
All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE).

37
Godeps/_workspace/src/github.com/rs/cors/bench_test.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
package cors
import (
"net/http"
"net/http/httptest"
"testing"
)
func BenchmarkWithout(b *testing.B) {
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
for i := 0; i < b.N; i++ {
testHandler.ServeHTTP(res, req)
}
}
func BenchmarkDefault(b *testing.B) {
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
handler := Default()
for i := 0; i < b.N; i++ {
handler.Handler(testHandler).ServeHTTP(res, req)
}
}
func BenchmarkPreflight(b *testing.B) {
res := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
req.Header.Add("Access-Control-Request-Method", "GET")
handler := Default()
for i := 0; i < b.N; i++ {
handler.Handler(testHandler).ServeHTTP(res, req)
}
}

308
Godeps/_workspace/src/github.com/rs/cors/cors.go generated vendored Normal file
View File

@ -0,0 +1,308 @@
/*
Package cors is net/http handler to handle CORS related requests
as defined by http://www.w3.org/TR/cors/
You can configure it by passing an option struct to cors.New:
c := cors.New(cors.Options{
AllowedOrigins: []string{"foo.com"},
AllowedMethods: []string{"GET", "POST", "DELETE"},
AllowCredentials: true,
})
Then insert the handler in the chain:
handler = c.Handler(handler)
See Options documentation for more options.
The resulting handler is a standard net/http handler.
*/
package cors
import (
"log"
"net/http"
"os"
"strconv"
"strings"
)
// Options is a configuration container to setup the CORS middleware.
type Options struct {
// AllowedOrigins is a list of origins a cross-domain request can be executed from.
// If the special "*" value is present in the list, all origins will be allowed.
// Default value is ["*"]
AllowedOrigins []string
// AllowedMethods is a list of methods the client is allowed to use with
// cross-domain requests. Default value is simple methods (GET and POST)
AllowedMethods []string
// AllowedHeaders is list of non simple headers the client is allowed to use with
// cross-domain requests.
// If the special "*" value is present in the list, all headers will be allowed.
// Default value is [] but "Origin" is always appended to the list.
AllowedHeaders []string
// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
// API specification
ExposedHeaders []string
// AllowCredentials indicates whether the request can include user credentials like
// cookies, HTTP authentication or client side SSL certificates.
AllowCredentials bool
// MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached
MaxAge int
// Debugging flag adds additional output to debug server side CORS issues
Debug bool
// log object to use when debugging
log *log.Logger
}
type Cors struct {
// The CORS Options
options Options
}
// New creates a new Cors handler with the provided options.
func New(options Options) *Cors {
// Normalize options
// Note: for origins and methods matching, the spec requires a case-sensitive matching.
// As it may error prone, we chose to ignore the spec here.
normOptions := Options{
AllowedOrigins: convert(options.AllowedOrigins, strings.ToLower),
AllowedMethods: convert(options.AllowedMethods, strings.ToUpper),
// Origin is always appended as some browsers will always request
// for this header at preflight
AllowedHeaders: convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey),
ExposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey),
AllowCredentials: options.AllowCredentials,
MaxAge: options.MaxAge,
Debug: options.Debug,
log: log.New(os.Stdout, "[cors] ", log.LstdFlags),
}
if len(normOptions.AllowedOrigins) == 0 {
// Default is all origins
normOptions.AllowedOrigins = []string{"*"}
}
if len(normOptions.AllowedHeaders) == 1 {
// Add some sensible defaults
normOptions.AllowedHeaders = []string{"Origin", "Accept", "Content-Type"}
}
if len(normOptions.AllowedMethods) == 0 {
// Default is simple methods
normOptions.AllowedMethods = []string{"GET", "POST"}
}
if normOptions.Debug {
normOptions.log.Printf("Options: %v", normOptions)
}
return &Cors{
options: normOptions,
}
}
// Default creates a new Cors handler with default options
func Default() *Cors {
return New(Options{})
}
// Handler apply the CORS specification on the request, and add relevant CORS headers
// as necessary.
func (cors *Cors) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "OPTIONS" {
cors.logf("Handler: Preflight request")
cors.handlePreflight(w, r)
// Preflight requests are standalone and should stop the chain as some other
// middleware may not handle OPTIONS requests correctly. One typical example
// is authentication middleware ; OPTIONS requests won't carry authentication
// headers (see #1)
} else {
cors.logf("Handler: Actual request")
cors.handleActualRequest(w, r)
h.ServeHTTP(w, r)
}
})
}
// Martini compatible handler
func (cors *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) {
if r.Method == "OPTIONS" {
cors.logf("HandlerFunc: Preflight request")
cors.handlePreflight(w, r)
} else {
cors.logf("HandlerFunc: Actual request")
cors.handleActualRequest(w, r)
}
}
// Negroni compatible interface
func (cors *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if r.Method == "OPTIONS" {
cors.logf("ServeHTTP: Preflight request")
cors.handlePreflight(w, r)
// Preflight requests are standalone and should stop the chain as some other
// middleware may not handle OPTIONS requests correctly. One typical example
// is authentication middleware ; OPTIONS requests won't carry authentication
// headers (see #1)
} else {
cors.logf("ServeHTTP: Actual request")
cors.handleActualRequest(w, r)
next(w, r)
}
}
// handlePreflight handles pre-flight CORS requests
func (cors *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) {
options := cors.options
headers := w.Header()
origin := r.Header.Get("Origin")
if r.Method != "OPTIONS" {
cors.logf(" Preflight aborted: %s!=OPTIONS", r.Method)
return
}
if origin == "" {
cors.logf(" Preflight aborted: empty origin")
return
}
if !cors.isOriginAllowed(origin) {
cors.logf(" Preflight aborted: origin '%s' not allowed", origin)
return
}
reqMethod := r.Header.Get("Access-Control-Request-Method")
if !cors.isMethodAllowed(reqMethod) {
cors.logf(" Preflight aborted: method '%s' not allowed", reqMethod)
return
}
reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers"))
if !cors.areHeadersAllowed(reqHeaders) {
cors.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders)
return
}
headers.Set("Access-Control-Allow-Origin", origin)
headers.Add("Vary", "Origin")
// Spec says: Since the list of methods can be unbounded, simply returning the method indicated
// by Access-Control-Request-Method (if supported) can be enough
headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod))
if len(reqHeaders) > 0 {
// Spec says: Since the list of headers can be unbounded, simply returning supported headers
// from Access-Control-Request-Headers can be enough
headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", "))
}
if options.AllowCredentials {
headers.Set("Access-Control-Allow-Credentials", "true")
}
if options.MaxAge > 0 {
headers.Set("Access-Control-Max-Age", strconv.Itoa(options.MaxAge))
}
cors.logf(" Preflight response headers: %v", headers)
}
// handleActualRequest handles simple cross-origin requests, actual request or redirects
func (cors *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
options := cors.options
headers := w.Header()
origin := r.Header.Get("Origin")
if r.Method == "OPTIONS" {
cors.logf(" Actual request no headers added: method == %s", r.Method)
return
}
if origin == "" {
cors.logf(" Actual request no headers added: missing origin")
return
}
if !cors.isOriginAllowed(origin) {
cors.logf(" Actual request no headers added: origin '%s' not allowed", origin)
return
}
// Note that spec does define a way to specifically disallow a simple method like GET or
// POST. Access-Control-Allow-Methods is only used for pre-flight requests and the
// spec doesn't instruct to check the allowed methods for simple cross-origin requests.
// We think it's a nice feature to be able to have control on those methods though.
if !cors.isMethodAllowed(r.Method) {
if cors.options.Debug {
cors.logf(" Actual request no headers added: method '%s' not allowed",
r.Method)
}
return
}
headers.Set("Access-Control-Allow-Origin", origin)
headers.Add("Vary", "Origin")
if len(options.ExposedHeaders) > 0 {
headers.Set("Access-Control-Expose-Headers", strings.Join(options.ExposedHeaders, ", "))
}
if options.AllowCredentials {
headers.Set("Access-Control-Allow-Credentials", "true")
}
cors.logf(" Actual response added headers: %v", headers)
}
// convenience method. checks if debugging is turned on before printing
func (cors *Cors) logf(format string, a ...interface{}) {
if cors.options.Debug {
cors.options.log.Printf(format, a...)
}
}
// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests
// on the endpoint
func (cors *Cors) isOriginAllowed(origin string) bool {
allowedOrigins := cors.options.AllowedOrigins
origin = strings.ToLower(origin)
for _, allowedOrigin := range allowedOrigins {
switch allowedOrigin {
case "*":
return true
case origin:
return true
}
}
return false
}
// isMethodAllowed checks if a given method can be used as part of a cross-domain request
// on the endpoing
func (cors *Cors) isMethodAllowed(method string) bool {
allowedMethods := cors.options.AllowedMethods
if len(allowedMethods) == 0 {
// If no method allowed, always return false, even for preflight request
return false
}
method = strings.ToUpper(method)
if method == "OPTIONS" {
// Always allow preflight requests
return true
}
for _, allowedMethod := range allowedMethods {
if allowedMethod == method {
return true
}
}
return false
}
// areHeadersAllowed checks if a given list of headers are allowed to used within
// a cross-domain request.
func (cors *Cors) areHeadersAllowed(requestedHeaders []string) bool {
if len(requestedHeaders) == 0 {
return true
}
for _, header := range requestedHeaders {
found := false
for _, allowedHeader := range cors.options.AllowedHeaders {
if allowedHeader == "*" || allowedHeader == header {
found = true
break
}
}
if !found {
return false
}
}
return true
}

288
Godeps/_workspace/src/github.com/rs/cors/cors_test.go generated vendored Normal file
View File

@ -0,0 +1,288 @@
package cors
import (
"net/http"
"net/http/httptest"
"testing"
)
var testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("bar"))
})
func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) {
for name, value := range reqHeaders {
if resHeaders.Get(name) != value {
t.Errorf("Invalid header `%s', wanted `%s', got `%s'", name, value, resHeaders.Get(name))
}
}
}
func TestNoConfig(t *testing.T) {
s := New(Options{
// Intentionally left blank.
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "",
"Access-Control-Allow-Methods": "",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestWildcardOrigin(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"*"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://foobar.com",
"Access-Control-Allow-Methods": "",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestAllowedOrigin(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://foobar.com",
"Access-Control-Allow-Methods": "",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestDisallowedOrigin(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://barbaz.com")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "",
"Access-Control-Allow-Methods": "",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestAllowedMethod(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
AllowedMethods: []string{"PUT", "DELETE"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
req.Header.Add("Access-Control-Request-Method", "PUT")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://foobar.com",
"Access-Control-Allow-Methods": "PUT",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestDisallowedMethod(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
AllowedMethods: []string{"PUT", "DELETE"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
req.Header.Add("Access-Control-Request-Method", "PATCH")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "",
"Access-Control-Allow-Methods": "",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestAllowedHeader(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
AllowedHeaders: []string{"X-Header-1", "x-header-2"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
req.Header.Add("Access-Control-Request-Method", "GET")
req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://foobar.com",
"Access-Control-Allow-Methods": "GET",
"Access-Control-Allow-Headers": "X-Header-2, X-Header-1",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestAllowedWildcardHeader(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
AllowedHeaders: []string{"*"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
req.Header.Add("Access-Control-Request-Method", "GET")
req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://foobar.com",
"Access-Control-Allow-Methods": "GET",
"Access-Control-Allow-Headers": "X-Header-2, X-Header-1",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestDisallowedHeader(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
AllowedHeaders: []string{"X-Header-1", "x-header-2"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
req.Header.Add("Access-Control-Request-Method", "GET")
req.Header.Add("Access-Control-Request-Headers", "X-Header-3, X-Header-1")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "",
"Access-Control-Allow-Methods": "",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestOriginHeader(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
req.Header.Add("Access-Control-Request-Method", "GET")
req.Header.Add("Access-Control-Request-Headers", "origin")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://foobar.com",
"Access-Control-Allow-Methods": "GET",
"Access-Control-Allow-Headers": "Origin",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}
func TestExposedHeader(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
ExposedHeaders: []string{"X-Header-1", "x-header-2"},
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://foobar.com",
"Access-Control-Allow-Methods": "",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "X-Header-1, X-Header-2",
})
}
func TestAllowedCredentials(t *testing.T) {
s := New(Options{
AllowedOrigins: []string{"http://foobar.com"},
AllowCredentials: true,
})
res := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")
req.Header.Add("Access-Control-Request-Method", "GET")
s.Handler(testHandler).ServeHTTP(res, req)
assertHeaders(t, res.Header(), map[string]string{
"Access-Control-Allow-Origin": "http://foobar.com",
"Access-Control-Allow-Methods": "GET",
"Access-Control-Allow-Headers": "",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Max-Age": "",
"Access-Control-Expose-Headers": "",
})
}

View File

@ -0,0 +1,24 @@
package main
import (
"net/http"
"github.com/justinas/alice"
"github.com/rs/cors"
)
func main() {
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://foo.com"},
})
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"hello\": \"world\"}"))
})
chain := alice.New(c.Handler).Then(mux)
http.ListenAndServe(":8080", chain)
}

View File

@ -0,0 +1,18 @@
package main
import (
"net/http"
"github.com/rs/cors"
)
func main() {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"hello\": \"world\"}"))
})
// Use default options
handler := cors.Default().Handler(h)
http.ListenAndServe(":8080", handler)
}

View File

@ -0,0 +1,22 @@
package main
import (
"net/http"
"github.com/rs/cors"
"github.com/zenazn/goji"
)
func main() {
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://foo.com"},
})
goji.Use(c.Handler)
goji.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"hello\": \"world\"}"))
})
goji.Serve()
}

View File

@ -0,0 +1,23 @@
package main
import (
"github.com/go-martini/martini"
"github.com/martini-contrib/render"
"github.com/rs/cors"
)
func main() {
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://foo.com"},
})
m := martini.Classic()
m.Use(render.Renderer())
m.Use(c.HandlerFunc)
m.Get("/", func(r render.Render) {
r.JSON(200, map[string]interface{}{"hello": "world"})
})
m.Run()
}

View File

@ -0,0 +1,26 @@
package main
import (
"net/http"
"github.com/codegangsta/negroni"
"github.com/rs/cors"
)
func main() {
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://foo.com"},
})
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"hello\": \"world\"}"))
})
n := negroni.Classic()
n.Use(c)
n.UseHandler(mux)
n.Run(":3000")
}

View File

@ -0,0 +1,20 @@
package main
import (
"net/http"
"github.com/rs/cors"
)
func main() {
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://foo.com"},
})
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"hello\": \"world\"}"))
})
http.ListenAndServe(":8080", c.Handler(handler))
}

View File

@ -0,0 +1,22 @@
package main
import (
"net/http"
"github.com/rs/cors"
)
func main() {
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowCredentials: true,
})
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"hello\": \"world\"}"))
})
http.ListenAndServe(":8080", c.Handler(h))
}

27
Godeps/_workspace/src/github.com/rs/cors/utils.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
package cors
import (
"net/http"
"strings"
)
type converter func(string) string
// convert converts a list of string using the passed converter function
func convert(s []string, c converter) []string {
out := []string{}
for _, i := range s {
out = append(out, c(i))
}
return out
}
func parseHeaderList(headerList string) (headers []string) {
for _, header := range strings.Split(headerList, ",") {
header = http.CanonicalHeaderKey(strings.TrimSpace(header))
if header != "" {
headers = append(headers, header)
}
}
return headers
}

28
Godeps/_workspace/src/github.com/rs/cors/utils_test.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
package cors
import (
"strings"
"testing"
)
func TestConvert(t *testing.T) {
s := convert([]string{"A", "b", "C"}, strings.ToLower)
e := []string{"a", "b", "c"}
if s[0] != e[0] || s[1] != e[1] || s[2] != e[2] {
t.Errorf("%v != %v", s, e)
}
}
func TestParseHeaderList(t *testing.T) {
h := parseHeaderList("header, second-header, THIRD-HEADER")
e := []string{"Header", "Second-Header", "Third-Header"}
if h[0] != e[0] || h[1] != e[1] || h[2] != e[2] {
t.Errorf("%v != %v", h, e)
}
}
func TestParseHeaderListEmpty(t *testing.T) {
if len(parseHeaderList("")) != 0 {
t.Error("should be empty sclice")
}
}

View File

@ -2,8 +2,6 @@ package main
import ( import (
"fmt" "fmt"
"net"
"net/http"
"os" "os"
"time" "time"
@ -70,12 +68,20 @@ func (js *jsre) startRPC(call otto.FunctionCall) otto.Value {
return otto.FalseValue() return otto.FalseValue()
} }
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port)) config := rpc.RpcConfig{
ListenAddress: addr,
ListenPort: uint(port),
// CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name),
}
xeth := xeth.New(js.ethereum, nil)
err = rpc.Start(xeth, config)
if err != nil { if err != nil {
fmt.Printf("Can't listen on %s:%d: %v", addr, port, err) fmt.Printf(err.Error())
return otto.FalseValue() return otto.FalseValue()
} }
go http.Serve(l, rpc.JSONRPC(xeth.New(js.ethereum, nil)))
return otto.TrueValue() return otto.TrueValue()
} }

View File

@ -233,6 +233,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.VMDebugFlag, utils.VMDebugFlag,
utils.ProtocolVersionFlag, utils.ProtocolVersionFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.RPCCORSDomainFlag,
} }
// missing: // missing:

View File

@ -47,12 +47,19 @@ var (
Usage: "absolute path to GUI assets directory", Usage: "absolute path to GUI assets directory",
Value: common.DefaultAssetPath(), Value: common.DefaultAssetPath(),
} }
rpcCorsFlag = utils.RPCCORSDomainFlag
) )
func init() { func init() {
// Mist-specific default
if len(rpcCorsFlag.Value) == 0 {
rpcCorsFlag.Value = "http://localhost"
}
app.Action = run app.Action = run
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
assetPathFlag, assetPathFlag,
rpcCorsFlag,
utils.BootnodesFlag, utils.BootnodesFlag,
utils.DataDirFlag, utils.DataDirFlag,

View File

@ -2,9 +2,6 @@ package utils
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt"
"net"
"net/http"
"os" "os"
"path" "path"
"runtime" "runtime"
@ -148,7 +145,11 @@ var (
Usage: "Port on which the JSON-RPC server should listen", Usage: "Port on which the JSON-RPC server should listen",
Value: 8545, Value: 8545,
} }
RPCCORSDomainFlag = cli.StringFlag{
Name: "rpccorsdomain",
Usage: "Domain on which to send Access-Control-Allow-Origin header",
Value: "",
}
// Network Settings // Network Settings
MaxPeersFlag = cli.IntFlag{ MaxPeersFlag = cli.IntFlag{
Name: "maxpeers", Name: "maxpeers",
@ -255,12 +256,12 @@ func GetAccountManager(ctx *cli.Context) *accounts.Manager {
} }
func StartRPC(eth *eth.Ethereum, ctx *cli.Context) { func StartRPC(eth *eth.Ethereum, ctx *cli.Context) {
addr := ctx.GlobalString(RPCListenAddrFlag.Name) config := rpc.RpcConfig{
port := ctx.GlobalInt(RPCPortFlag.Name) ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name),
fmt.Println("Starting RPC on port: ", port) ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)),
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port)) CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name),
if err != nil {
Fatalf("Can't listen on %s:%d: %v", addr, port, err)
} }
go http.Serve(l, rpc.JSONRPC(xeth.New(eth, nil)))
xeth := xeth.New(eth, nil)
_ = rpc.Start(xeth, config)
} }

View File

@ -233,8 +233,9 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (td *big
sm.txpool.RemoveSet(block.Transactions()) sm.txpool.RemoveSet(block.Transactions())
} }
for _, tx := range block.Transactions() { // This puts transactions in a extra db for rpc
putTx(sm.extraDb, tx) for i, tx := range block.Transactions() {
putTx(sm.extraDb, tx, block, uint64(i))
} }
if uncle { if uncle {
@ -362,11 +363,26 @@ func (sm *BlockProcessor) GetLogs(block *types.Block) (logs state.Logs, err erro
return state.Logs(), nil return state.Logs(), nil
} }
func putTx(db common.Database, tx *types.Transaction) { func putTx(db common.Database, tx *types.Transaction, block *types.Block, i uint64) {
rlpEnc, err := rlp.EncodeToBytes(tx) rlpEnc, err := rlp.EncodeToBytes(tx)
if err != nil { if err != nil {
statelogger.Infoln("Failed encoding tx", err) statelogger.Infoln("Failed encoding tx", err)
return return
} }
db.Put(tx.Hash().Bytes(), rlpEnc) db.Put(tx.Hash().Bytes(), rlpEnc)
var txExtra struct {
BlockHash common.Hash
BlockIndex uint64
Index uint64
}
txExtra.BlockHash = block.Hash()
txExtra.BlockIndex = block.NumberU64()
txExtra.Index = i
rlpMeta, err := rlp.EncodeToBytes(txExtra)
if err != nil {
statelogger.Infoln("Failed encoding meta", err)
return
}
db.Put(append(tx.Hash().Bytes(), 0x0001), rlpMeta)
} }

View File

@ -54,7 +54,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
case "net_peerCount": case "net_peerCount":
v := api.xeth().PeerCount() v := api.xeth().PeerCount()
*reply = common.ToHex(big.NewInt(int64(v)).Bytes()) *reply = common.ToHex(big.NewInt(int64(v)).Bytes())
case "eth_version": case "eth_protocolVersion":
*reply = api.xeth().EthVersion() *reply = api.xeth().EthVersion()
case "eth_coinbase": case "eth_coinbase":
// TODO handling of empty coinbase due to lack of accounts // TODO handling of empty coinbase due to lack of accounts
@ -159,7 +159,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
} }
*reply = v *reply = v
case "eth_call": case "eth_call":
args := new(NewTxArgs) args := new(CallArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
return err return err
} }
@ -199,9 +199,13 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
args := new(HashIndexArgs) args := new(HashIndexArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {
} }
tx := api.xeth().EthTransactionByHash(args.Hash) tx, bhash, bnum, txi := api.xeth().EthTransactionByHash(args.Hash)
if tx != nil { if tx != nil {
*reply = NewTransactionRes(tx) v := NewTransactionRes(tx)
v.BlockHash = newHexData(bhash)
v.BlockNumber = newHexNum(bnum)
v.TxIndex = newHexNum(txi)
*reply = v
} }
case "eth_getTransactionByBlockHashAndIndex": case "eth_getTransactionByBlockHashAndIndex":
args := new(HashIndexArgs) args := new(HashIndexArgs)
@ -213,7 +217,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
br := NewBlockRes(block) br := NewBlockRes(block)
br.fullTx = true br.fullTx = true
if args.Index > int64(len(br.Transactions)) || args.Index < 0 { if args.Index >= int64(len(br.Transactions)) || args.Index < 0 {
return NewValidationError("Index", "does not exist") return NewValidationError("Index", "does not exist")
} }
*reply = br.Transactions[args.Index] *reply = br.Transactions[args.Index]
@ -227,7 +231,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
v := NewBlockRes(block) v := NewBlockRes(block)
v.fullTx = true v.fullTx = true
if args.Index > int64(len(v.Transactions)) || args.Index < 0 { if args.Index >= int64(len(v.Transactions)) || args.Index < 0 {
return NewValidationError("Index", "does not exist") return NewValidationError("Index", "does not exist")
} }
*reply = v.Transactions[args.Index] *reply = v.Transactions[args.Index]
@ -239,12 +243,12 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
br := NewBlockRes(api.xeth().EthBlockByHash(args.Hash)) br := NewBlockRes(api.xeth().EthBlockByHash(args.Hash))
if args.Index > int64(len(br.Uncles)) || args.Index < 0 { if args.Index >= int64(len(br.Uncles)) || args.Index < 0 {
return NewValidationError("Index", "does not exist") return NewValidationError("Index", "does not exist")
} }
uhash := br.Uncles[args.Index] uhash := br.Uncles[args.Index]
uncle := NewBlockRes(api.xeth().EthBlockByHash(uhash.Hex())) uncle := NewBlockRes(api.xeth().EthBlockByHash(uhash.String()))
*reply = uncle *reply = uncle
case "eth_getUncleByBlockNumberAndIndex": case "eth_getUncleByBlockNumberAndIndex":
@ -257,12 +261,12 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
v := NewBlockRes(block) v := NewBlockRes(block)
v.fullTx = true v.fullTx = true
if args.Index > int64(len(v.Uncles)) || args.Index < 0 { if args.Index >= int64(len(v.Uncles)) || args.Index < 0 {
return NewValidationError("Index", "does not exist") return NewValidationError("Index", "does not exist")
} }
uhash := v.Uncles[args.Index] uhash := v.Uncles[args.Index]
uncle := NewBlockRes(api.xeth().EthBlockByHash(uhash.Hex())) uncle := NewBlockRes(api.xeth().EthBlockByHash(uhash.String()))
*reply = uncle *reply = uncle
case "eth_getCompilers": case "eth_getCompilers":

View File

@ -238,6 +238,93 @@ func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) {
return nil return nil
} }
type CallArgs struct {
From string
To string
Value *big.Int
Gas *big.Int
GasPrice *big.Int
Data string
BlockNumber int64
}
func (args *CallArgs) UnmarshalJSON(b []byte) (err error) {
var obj []json.RawMessage
var ext struct {
From string
To string
Value interface{}
Gas interface{}
GasPrice interface{}
Data string
}
// Decode byte slice to array of RawMessages
if err := json.Unmarshal(b, &obj); err != nil {
return NewDecodeParamError(err.Error())
}
// Check for sufficient params
if len(obj) < 1 {
return NewInsufficientParamsError(len(obj), 1)
}
// Decode 0th RawMessage to temporary struct
if err := json.Unmarshal(obj[0], &ext); err != nil {
return NewDecodeParamError(err.Error())
}
if len(ext.From) == 0 {
return NewValidationError("from", "is required")
}
args.From = ext.From
if len(ext.To) == 0 {
return NewValidationError("to", "is required")
}
args.To = ext.To
var num int64
if ext.Value == nil {
num = int64(0)
} else {
if err := numString(ext.Value, &num); err != nil {
return err
}
}
args.Value = big.NewInt(num)
if ext.Gas == nil {
num = int64(0)
} else {
if err := numString(ext.Gas, &num); err != nil {
return err
}
}
args.Gas = big.NewInt(num)
if ext.GasPrice == nil {
num = int64(0)
} else {
if err := numString(ext.GasPrice, &num); err != nil {
return err
}
}
args.GasPrice = big.NewInt(num)
args.Data = ext.Data
// Check for optional BlockNumber param
if len(obj) > 1 {
if err := blockHeightFromJson(obj[1], &args.BlockNumber); err != nil {
return err
}
}
return nil
}
type GetStorageArgs struct { type GetStorageArgs struct {
Address string Address string
BlockNumber int64 BlockNumber int64

View File

@ -531,14 +531,275 @@ func TestNewTxArgsFromEmpty(t *testing.T) {
input := `[{"to": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]` input := `[{"to": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]`
args := new(NewTxArgs) args := new(NewTxArgs)
err := json.Unmarshal([]byte(input), &args) str := ExpectValidationError(json.Unmarshal([]byte(input), &args))
switch err.(type) { if len(str) > 0 {
case nil: t.Error(str)
t.Error("Expected error but didn't get one") }
case *ValidationError: }
break
default: func TestCallArgs(t *testing.T) {
t.Errorf("Expected *rpc.ValidationError, but got %T with message `%s`", err, err.Error()) input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a000",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"},
"0x10"]`
expected := new(CallArgs)
expected.From = "0xb60e8dd61c5d32be8058bb8eb970870f07233155"
expected.To = "0xd46e8dd67c5d32be8058bb8eb970870f072445675"
expected.Gas = big.NewInt(30400)
expected.GasPrice = big.NewInt(10000000000000)
expected.Value = big.NewInt(10000000000000)
expected.Data = "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
expected.BlockNumber = big.NewInt(16).Int64()
args := new(CallArgs)
if err := json.Unmarshal([]byte(input), &args); err != nil {
t.Error(err)
}
if expected.From != args.From {
t.Errorf("From shoud be %#v but is %#v", expected.From, args.From)
}
if expected.To != args.To {
t.Errorf("To shoud be %#v but is %#v", expected.To, args.To)
}
if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 {
t.Errorf("Gas shoud be %#v but is %#v", expected.Gas.Bytes(), args.Gas.Bytes())
}
if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 {
t.Errorf("GasPrice shoud be %#v but is %#v", expected.GasPrice, args.GasPrice)
}
if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 {
t.Errorf("Value shoud be %#v but is %#v", expected.Value, args.Value)
}
if expected.Data != args.Data {
t.Errorf("Data shoud be %#v but is %#v", expected.Data, args.Data)
}
if expected.BlockNumber != args.BlockNumber {
t.Errorf("BlockNumber shoud be %#v but is %#v", expected.BlockNumber, args.BlockNumber)
}
}
func TestCallArgsInt(t *testing.T) {
input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
"gas": 100,
"gasPrice": 50,
"value": 8765456789,
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"},
5]`
expected := new(CallArgs)
expected.Gas = big.NewInt(100)
expected.GasPrice = big.NewInt(50)
expected.Value = big.NewInt(8765456789)
expected.BlockNumber = int64(5)
args := new(CallArgs)
if err := json.Unmarshal([]byte(input), &args); err != nil {
t.Error(err)
}
if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 {
t.Errorf("Gas shoud be %v but is %v", expected.Gas, args.Gas)
}
if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 {
t.Errorf("GasPrice shoud be %v but is %v", expected.GasPrice, args.GasPrice)
}
if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 {
t.Errorf("Value shoud be %v but is %v", expected.Value, args.Value)
}
if expected.BlockNumber != args.BlockNumber {
t.Errorf("BlockNumber shoud be %v but is %v", expected.BlockNumber, args.BlockNumber)
}
}
func TestCallArgsBlockBool(t *testing.T) {
input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a000",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"},
false]`
args := new(CallArgs)
str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args))
if len(str) > 0 {
t.Error(str)
}
}
func TestCallArgsGasInvalid(t *testing.T) {
input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
"gas": false,
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a000",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
}]`
args := new(CallArgs)
str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args))
if len(str) > 0 {
t.Error(str)
}
}
func TestCallArgsGaspriceInvalid(t *testing.T) {
input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
"gas": "0x76c0",
"gasPrice": false,
"value": "0x9184e72a000",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
}]`
args := new(CallArgs)
str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args))
if len(str) > 0 {
t.Error(str)
}
}
func TestCallArgsValueInvalid(t *testing.T) {
input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": false,
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
}]`
args := new(CallArgs)
str := ExpectInvalidTypeError(json.Unmarshal([]byte(input), &args))
if len(str) > 0 {
t.Error(str)
}
}
func TestCallArgsGasMissing(t *testing.T) {
input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a000",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
}]`
args := new(CallArgs)
if err := json.Unmarshal([]byte(input), &args); err != nil {
t.Error(err)
}
expected := new(CallArgs)
expected.Gas = big.NewInt(0)
if bytes.Compare(expected.Gas.Bytes(), args.Gas.Bytes()) != 0 {
t.Errorf("Gas shoud be %v but is %v", expected.Gas, args.Gas)
}
}
func TestCallArgsBlockGaspriceMissing(t *testing.T) {
input := `[{
"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
"gas": "0x76c0",
"value": "0x9184e72a000",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
}]`
args := new(CallArgs)
if err := json.Unmarshal([]byte(input), &args); err != nil {
t.Error(err)
}
expected := new(CallArgs)
expected.GasPrice = big.NewInt(0)
if bytes.Compare(expected.GasPrice.Bytes(), args.GasPrice.Bytes()) != 0 {
t.Errorf("GasPrice shoud be %v but is %v", expected.GasPrice, args.GasPrice)
}
}
func TestCallArgsValueMissing(t *testing.T) {
input := `[{
"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
}]`
args := new(CallArgs)
if err := json.Unmarshal([]byte(input), &args); err != nil {
t.Error(err)
}
expected := new(CallArgs)
expected.Value = big.NewInt(int64(0))
if bytes.Compare(expected.Value.Bytes(), args.Value.Bytes()) != 0 {
t.Errorf("GasPrice shoud be %v but is %v", expected.Value, args.Value)
}
}
func TestCallArgsEmpty(t *testing.T) {
input := `[]`
args := new(CallArgs)
str := ExpectInsufficientParamsError(json.Unmarshal([]byte(input), &args))
if len(str) > 0 {
t.Error(str)
}
}
func TestCallArgsInvalid(t *testing.T) {
input := `{}`
args := new(CallArgs)
str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args))
if len(str) > 0 {
t.Error(str)
}
}
func TestCallArgsNotStrings(t *testing.T) {
input := `[{"from":6}]`
args := new(CallArgs)
str := ExpectDecodeParamError(json.Unmarshal([]byte(input), &args))
if len(str) > 0 {
t.Error(str)
}
}
func TestCallArgsFromEmpty(t *testing.T) {
input := `[{"to": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]`
args := new(CallArgs)
str := ExpectValidationError(json.Unmarshal([]byte(input), &args))
if len(str) > 0 {
t.Error(str)
}
}
func TestCallArgsToEmpty(t *testing.T) {
input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]`
args := new(CallArgs)
str := ExpectValidationError(json.Unmarshal([]byte(input), &args))
if len(str) > 0 {
t.Error(str)
} }
} }

View File

@ -2,12 +2,15 @@ package rpc
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/xeth" "github.com/ethereum/go-ethereum/xeth"
"github.com/rs/cors"
) )
var rpclogger = logger.NewLogger("RPC") var rpclogger = logger.NewLogger("RPC")
@ -17,13 +20,36 @@ const (
maxSizeReqLength = 1024 * 1024 // 1MB maxSizeReqLength = 1024 * 1024 // 1MB
) )
func Start(pipe *xeth.XEth, config RpcConfig) error {
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.ListenAddress, config.ListenPort))
if err != nil {
rpclogger.Errorf("Can't listen on %s:%d: %v", config.ListenAddress, config.ListenPort, err)
return err
}
var handler http.Handler
if len(config.CorsDomain) > 0 {
var opts cors.Options
opts.AllowedMethods = []string{"POST"}
opts.AllowedOrigins = []string{config.CorsDomain}
c := cors.New(opts)
handler = c.Handler(JSONRPC(pipe))
} else {
handler = JSONRPC(pipe)
}
go http.Serve(l, handler)
return nil
}
// JSONRPC returns a handler that implements the Ethereum JSON-RPC API. // JSONRPC returns a handler that implements the Ethereum JSON-RPC API.
func JSONRPC(pipe *xeth.XEth) http.Handler { func JSONRPC(pipe *xeth.XEth) http.Handler {
api := NewEthereumApi(pipe) api := NewEthereumApi(pipe)
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// TODO this needs to be configurable w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
// Limit request size to resist DoS // Limit request size to resist DoS
if req.ContentLength > maxSizeReqLength { if req.ContentLength > maxSizeReqLength {

View File

@ -19,8 +19,101 @@ package rpc
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/common"
) )
type hexdata struct {
data []byte
}
func (d *hexdata) String() string {
return "0x" + common.Bytes2Hex(d.data)
}
func (d *hexdata) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
func (d *hexdata) UnmarshalJSON(b []byte) (err error) {
d.data = common.FromHex(string(b))
return nil
}
func newHexData(input interface{}) *hexdata {
d := new(hexdata)
switch input := input.(type) {
case []byte:
d.data = input
case common.Hash:
d.data = input.Bytes()
case *common.Hash:
d.data = input.Bytes()
case common.Address:
d.data = input.Bytes()
case *common.Address:
d.data = input.Bytes()
case *big.Int:
d.data = input.Bytes()
case int64:
d.data = big.NewInt(input).Bytes()
case uint64:
d.data = big.NewInt(int64(input)).Bytes()
case int:
d.data = big.NewInt(int64(input)).Bytes()
case uint:
d.data = big.NewInt(int64(input)).Bytes()
case string: // hexstring
d.data = common.Big(input).Bytes()
default:
d.data = nil
}
return d
}
type hexnum struct {
data []byte
}
func (d *hexnum) String() string {
// Get hex string from bytes
out := common.Bytes2Hex(d.data)
// Trim leading 0s
out = strings.Trim(out, "0")
// Output "0x0" when value is 0
if len(out) == 0 {
out = "0"
}
return "0x" + out
}
func (d *hexnum) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
func (d *hexnum) UnmarshalJSON(b []byte) (err error) {
d.data = common.FromHex(string(b))
return nil
}
func newHexNum(input interface{}) *hexnum {
d := new(hexnum)
d.data = newHexData(input).data
return d
}
type RpcConfig struct {
ListenAddress string
ListenPort uint
CorsDomain string
}
type InvalidTypeError struct { type InvalidTypeError struct {
method string method string
msg string msg string

View File

@ -1,11 +1,6 @@
package rpc package rpc
import ( import (
"encoding/json"
// "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
@ -13,84 +8,25 @@ import (
type BlockRes struct { type BlockRes struct {
fullTx bool fullTx bool
BlockNumber *big.Int `json:"number"` BlockNumber *hexnum `json:"number"`
BlockHash common.Hash `json:"hash"` BlockHash *hexdata `json:"hash"`
ParentHash common.Hash `json:"parentHash"` ParentHash *hexdata `json:"parentHash"`
Nonce [8]byte `json:"nonce"` Nonce *hexnum `json:"nonce"`
Sha3Uncles common.Hash `json:"sha3Uncles"` Sha3Uncles *hexdata `json:"sha3Uncles"`
LogsBloom types.Bloom `json:"logsBloom"` LogsBloom *hexdata `json:"logsBloom"`
TransactionRoot common.Hash `json:"transactionRoot"` TransactionRoot *hexdata `json:"transactionRoot"`
StateRoot common.Hash `json:"stateRoot"` StateRoot *hexdata `json:"stateRoot"`
Miner common.Address `json:"miner"` Miner *hexdata `json:"miner"`
Difficulty *big.Int `json:"difficulty"` Difficulty *hexnum `json:"difficulty"`
TotalDifficulty *big.Int `json:"totalDifficulty"` TotalDifficulty *hexnum `json:"totalDifficulty"`
Size *big.Int `json:"size"` Size *hexnum `json:"size"`
ExtraData []byte `json:"extraData"` ExtraData *hexdata `json:"extraData"`
GasLimit *big.Int `json:"gasLimit"` GasLimit *hexnum `json:"gasLimit"`
MinGasPrice int64 `json:"minGasPrice"` MinGasPrice *hexnum `json:"minGasPrice"`
GasUsed *big.Int `json:"gasUsed"` GasUsed *hexnum `json:"gasUsed"`
UnixTimestamp int64 `json:"timestamp"` UnixTimestamp *hexnum `json:"timestamp"`
Transactions []*TransactionRes `json:"transactions"` Transactions []*TransactionRes `json:"transactions"`
Uncles []common.Hash `json:"uncles"` Uncles []*hexdata `json:"uncles"`
}
func (b *BlockRes) MarshalJSON() ([]byte, error) {
var ext struct {
BlockNumber string `json:"number"`
BlockHash string `json:"hash"`
ParentHash string `json:"parentHash"`
Nonce string `json:"nonce"`
Sha3Uncles string `json:"sha3Uncles"`
LogsBloom string `json:"logsBloom"`
TransactionRoot string `json:"transactionRoot"`
StateRoot string `json:"stateRoot"`
Miner string `json:"miner"`
Difficulty string `json:"difficulty"`
TotalDifficulty string `json:"totalDifficulty"`
Size string `json:"size"`
ExtraData string `json:"extraData"`
GasLimit string `json:"gasLimit"`
MinGasPrice string `json:"minGasPrice"`
GasUsed string `json:"gasUsed"`
UnixTimestamp string `json:"timestamp"`
Transactions []interface{} `json:"transactions"`
Uncles []string `json:"uncles"`
}
// convert strict types to hexified strings
ext.BlockNumber = common.ToHex(b.BlockNumber.Bytes())
ext.BlockHash = b.BlockHash.Hex()
ext.ParentHash = b.ParentHash.Hex()
ext.Nonce = common.ToHex(b.Nonce[:])
ext.Sha3Uncles = b.Sha3Uncles.Hex()
ext.LogsBloom = common.ToHex(b.LogsBloom[:])
ext.TransactionRoot = b.TransactionRoot.Hex()
ext.StateRoot = b.StateRoot.Hex()
ext.Miner = b.Miner.Hex()
ext.Difficulty = common.ToHex(b.Difficulty.Bytes())
ext.TotalDifficulty = common.ToHex(b.TotalDifficulty.Bytes())
ext.Size = common.ToHex(b.Size.Bytes())
ext.ExtraData = common.ToHex(b.ExtraData)
ext.GasLimit = common.ToHex(b.GasLimit.Bytes())
// ext.MinGasPrice = common.ToHex(big.NewInt(b.MinGasPrice).Bytes())
ext.GasUsed = common.ToHex(b.GasUsed.Bytes())
ext.UnixTimestamp = common.ToHex(big.NewInt(b.UnixTimestamp).Bytes())
ext.Transactions = make([]interface{}, len(b.Transactions))
if b.fullTx {
for i, tx := range b.Transactions {
ext.Transactions[i] = tx
}
} else {
for i, tx := range b.Transactions {
ext.Transactions[i] = tx.Hash.Hex()
}
}
ext.Uncles = make([]string, len(b.Uncles))
for i, v := range b.Uncles {
ext.Uncles[i] = v.Hex()
}
return json.Marshal(ext)
} }
func NewBlockRes(block *types.Block) *BlockRes { func NewBlockRes(block *types.Block) *BlockRes {
@ -99,160 +35,118 @@ func NewBlockRes(block *types.Block) *BlockRes {
} }
res := new(BlockRes) res := new(BlockRes)
res.BlockNumber = block.Number() res.BlockNumber = newHexNum(block.Number())
res.BlockHash = block.Hash() res.BlockHash = newHexData(block.Hash())
res.ParentHash = block.ParentHash() res.ParentHash = newHexData(block.ParentHash())
res.Nonce = block.Header().Nonce res.Nonce = newHexNum(block.Header().Nonce)
res.Sha3Uncles = block.Header().UncleHash res.Sha3Uncles = newHexData(block.Header().UncleHash)
res.LogsBloom = block.Bloom() res.LogsBloom = newHexData(block.Bloom())
res.TransactionRoot = block.Header().TxHash res.TransactionRoot = newHexData(block.Header().TxHash)
res.StateRoot = block.Root() res.StateRoot = newHexData(block.Root())
res.Miner = block.Header().Coinbase res.Miner = newHexData(block.Header().Coinbase)
res.Difficulty = block.Difficulty() res.Difficulty = newHexNum(block.Difficulty())
res.TotalDifficulty = block.Td res.TotalDifficulty = newHexNum(block.Td)
res.Size = big.NewInt(int64(block.Size())) res.Size = newHexNum(block.Size())
res.ExtraData = []byte(block.Header().Extra) res.ExtraData = newHexData(block.Header().Extra)
res.GasLimit = block.GasLimit() res.GasLimit = newHexNum(block.GasLimit())
// res.MinGasPrice = // res.MinGasPrice =
res.GasUsed = block.GasUsed() res.GasUsed = newHexNum(block.GasUsed())
res.UnixTimestamp = block.Time() res.UnixTimestamp = newHexNum(block.Time())
res.Transactions = make([]*TransactionRes, len(block.Transactions())) res.Transactions = NewTransactionsRes(block.Transactions())
for i, tx := range block.Transactions() { res.Uncles = make([]*hexdata, len(block.Uncles()))
v := NewTransactionRes(tx)
v.BlockHash = block.Hash()
v.BlockNumber = block.Number().Int64()
v.TxIndex = int64(i)
res.Transactions[i] = v
}
res.Uncles = make([]common.Hash, len(block.Uncles()))
for i, uncle := range block.Uncles() { for i, uncle := range block.Uncles() {
res.Uncles[i] = uncle.Hash() res.Uncles[i] = newHexData(uncle.Hash())
} }
return res return res
} }
type TransactionRes struct { type TransactionRes struct {
Hash common.Hash `json:"hash"` Hash *hexdata `json:"hash"`
Nonce uint64 `json:"nonce"` Nonce *hexnum `json:"nonce"`
BlockHash common.Hash `json:"blockHash,omitempty"` BlockHash *hexdata `json:"blockHash"`
BlockNumber int64 `json:"blockNumber,omitempty"` BlockNumber *hexnum `json:"blockNumber"`
TxIndex int64 `json:"transactionIndex,omitempty"` TxIndex *hexnum `json:"transactionIndex"`
From common.Address `json:"from"` From *hexdata `json:"from"`
To *common.Address `json:"to"` To *hexdata `json:"to"`
Value *big.Int `json:"value"` Value *hexnum `json:"value"`
Gas *big.Int `json:"gas"` Gas *hexnum `json:"gas"`
GasPrice *big.Int `json:"gasPrice"` GasPrice *hexnum `json:"gasPrice"`
Input []byte `json:"input"` Input *hexdata `json:"input"`
}
func (t *TransactionRes) MarshalJSON() ([]byte, error) {
var ext struct {
Hash string `json:"hash"`
Nonce string `json:"nonce"`
BlockHash string `json:"blockHash,omitempty"`
BlockNumber string `json:"blockNumber,omitempty"`
TxIndex string `json:"transactionIndex,omitempty"`
From string `json:"from"`
To interface{} `json:"to"`
Value string `json:"value"`
Gas string `json:"gas"`
GasPrice string `json:"gasPrice"`
Input string `json:"input"`
}
ext.Hash = t.Hash.Hex()
ext.Nonce = common.ToHex(big.NewInt(int64(t.Nonce)).Bytes())
ext.BlockHash = t.BlockHash.Hex()
ext.BlockNumber = common.ToHex(big.NewInt(t.BlockNumber).Bytes())
ext.TxIndex = common.ToHex(big.NewInt(t.TxIndex).Bytes())
ext.From = t.From.Hex()
if t.To == nil {
ext.To = nil
} else {
ext.To = t.To.Hex()
}
ext.Value = common.ToHex(t.Value.Bytes())
ext.Gas = common.ToHex(t.Gas.Bytes())
ext.GasPrice = common.ToHex(t.GasPrice.Bytes())
ext.Input = common.ToHex(t.Input)
return json.Marshal(ext)
} }
func NewTransactionRes(tx *types.Transaction) *TransactionRes { func NewTransactionRes(tx *types.Transaction) *TransactionRes {
var v = new(TransactionRes) var v = new(TransactionRes)
v.Hash = tx.Hash() v.Hash = newHexData(tx.Hash())
v.Nonce = tx.Nonce() v.Nonce = newHexNum(tx.Nonce())
v.From, _ = tx.From() // v.BlockHash =
v.To = tx.To() // v.BlockNumber =
v.Value = tx.Value() // v.TxIndex =
v.Gas = tx.Gas() from, _ := tx.From()
v.GasPrice = tx.GasPrice() v.From = newHexData(from)
v.Input = tx.Data() v.To = newHexData(tx.To())
v.Value = newHexNum(tx.Value())
v.Gas = newHexNum(tx.Gas())
v.GasPrice = newHexNum(tx.GasPrice())
v.Input = newHexData(tx.Data())
return v return v
} }
type FilterLogRes struct { func NewTransactionsRes(txs []*types.Transaction) []*TransactionRes {
Hash string `json:"hash"` v := make([]*TransactionRes, len(txs))
Address string `json:"address"` for i, tx := range txs {
Data string `json:"data"` v[i] = NewTransactionRes(tx)
BlockNumber string `json:"blockNumber"` }
TransactionHash string `json:"transactionHash"` return v
BlockHash string `json:"blockHash"`
TransactionIndex string `json:"transactionIndex"`
LogIndex string `json:"logIndex"`
} }
type FilterWhisperRes struct { // type FilterLogRes struct {
Hash string `json:"hash"` // Hash string `json:"hash"`
From string `json:"from"` // Address string `json:"address"`
To string `json:"to"` // Data string `json:"data"`
Expiry string `json:"expiry"` // BlockNumber string `json:"blockNumber"`
Sent string `json:"sent"` // TransactionHash string `json:"transactionHash"`
Ttl string `json:"ttl"` // BlockHash string `json:"blockHash"`
Topics string `json:"topics"` // TransactionIndex string `json:"transactionIndex"`
Payload string `json:"payload"` // LogIndex string `json:"logIndex"`
WorkProved string `json:"workProved"` // }
}
// type FilterWhisperRes struct {
// Hash string `json:"hash"`
// From string `json:"from"`
// To string `json:"to"`
// Expiry string `json:"expiry"`
// Sent string `json:"sent"`
// Ttl string `json:"ttl"`
// Topics string `json:"topics"`
// Payload string `json:"payload"`
// WorkProved string `json:"workProved"`
// }
type LogRes struct { type LogRes struct {
Address common.Address `json:"address"` Address *hexdata `json:"address"`
Topics []common.Hash `json:"topics"` Topics []*hexdata `json:"topics"`
Data []byte `json:"data"` Data *hexdata `json:"data"`
Number uint64 `json:"number"` BlockNumber *hexnum `json:"blockNumber"`
Hash *hexdata `json:"hash"`
LogIndex *hexnum `json:"logIndex"`
BlockHash *hexdata `json:"blockHash"`
TransactionHash *hexdata `json:"transactionHash"`
TransactionIndex *hexnum `json:"transactionIndex"`
} }
func NewLogRes(log state.Log) LogRes { func NewLogRes(log state.Log) LogRes {
var l LogRes var l LogRes
l.Topics = make([]common.Hash, len(log.Topics())) l.Topics = make([]*hexdata, len(log.Topics()))
l.Address = log.Address()
l.Data = log.Data()
l.Number = log.Number()
for j, topic := range log.Topics() { for j, topic := range log.Topics() {
l.Topics[j] = topic l.Topics[j] = newHexData(topic)
} }
l.Address = newHexData(log.Address())
l.Data = newHexData(log.Data())
l.BlockNumber = newHexNum(log.Number())
return l return l
} }
func (l *LogRes) MarshalJSON() ([]byte, error) {
var ext struct {
Address string `json:"address"`
Topics []string `json:"topics"`
Data string `json:"data"`
Number string `json:"number"`
}
ext.Address = l.Address.Hex()
ext.Data = common.ToHex(l.Data)
ext.Number = common.ToHex(big.NewInt(int64(l.Number)).Bytes())
ext.Topics = make([]string, len(l.Topics))
for i, v := range l.Topics {
ext.Topics[i] = v.Hex()
}
return json.Marshal(ext)
}
func NewLogsRes(logs state.Logs) (ls []LogRes) { func NewLogsRes(logs state.Logs) (ls []LogRes) {
ls = make([]LogRes, len(logs)) ls = make([]LogRes, len(logs))

View File

@ -2,7 +2,9 @@ package rpc
import ( import (
"encoding/json" "encoding/json"
"fmt"
"math/big" "math/big"
"regexp"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -10,6 +12,15 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
const (
reHash = `"0x[0-9a-f]{64}"` // 32 bytes
reHashOpt = `"(0x[0-9a-f]{64})"|null` // 32 bytes or null
reAddress = `"0x[0-9a-f]{40}"` // 20 bytes
reAddressOpt = `"0x[0-9a-f]{40}"|null` // 20 bytes or null
reNum = `"0x([1-9a-f][0-9a-f]{1,15})|0"` // must not have left-padded zeros
reData = `"0x[0-9a-f]*"` // can be "empty"
)
func TestNewBlockRes(t *testing.T) { func TestNewBlockRes(t *testing.T) {
parentHash := common.HexToHash("0x01") parentHash := common.HexToHash("0x01")
coinbase := common.HexToAddress("0x01") coinbase := common.HexToAddress("0x01")
@ -18,56 +29,35 @@ func TestNewBlockRes(t *testing.T) {
nonce := uint64(1) nonce := uint64(1)
extra := "" extra := ""
block := types.NewBlock(parentHash, coinbase, root, difficulty, nonce, extra) block := types.NewBlock(parentHash, coinbase, root, difficulty, nonce, extra)
tests := map[string]string{
_ = NewBlockRes(block) "number": reNum,
} "hash": reHash,
"parentHash": reHash,
func TestBlockRes(t *testing.T) { "nonce": reNum,
v := &BlockRes{ "sha3Uncles": reHash,
BlockNumber: big.NewInt(0), "logsBloom": reData,
BlockHash: common.HexToHash("0x0"), "transactionRoot": reHash,
ParentHash: common.HexToHash("0x0"), "stateRoot": reHash,
Nonce: [8]byte{0, 0, 0, 0, 0, 0, 0, 0}, "miner": reAddress,
Sha3Uncles: common.HexToHash("0x0"), "difficulty": `"0x1"`,
LogsBloom: types.BytesToBloom([]byte{0}), "totalDifficulty": reNum,
TransactionRoot: common.HexToHash("0x0"), "size": reNum,
StateRoot: common.HexToHash("0x0"), "extraData": reData,
Miner: common.HexToAddress("0x0"), "gasLimit": reNum,
Difficulty: big.NewInt(0), // "minGasPrice": "0x",
TotalDifficulty: big.NewInt(0), "gasUsed": reNum,
Size: big.NewInt(0), "timestamp": reNum,
ExtraData: []byte{},
GasLimit: big.NewInt(0),
MinGasPrice: int64(0),
GasUsed: big.NewInt(0),
UnixTimestamp: int64(0),
// Transactions []*TransactionRes `json:"transactions"`
// Uncles []common.Hash `json:"uncles"`
} }
_, _ = json.Marshal(v) v := NewBlockRes(block)
j, _ := json.Marshal(v)
// fmt.Println(string(j)) for k, re := range tests {
match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j))
} if !match {
t.Error(fmt.Sprintf("%s output json does not match format %s. Got %s", k, re, j))
func TestTransactionRes(t *testing.T) { }
a := common.HexToAddress("0x0")
v := &TransactionRes{
Hash: common.HexToHash("0x0"),
Nonce: uint64(0),
BlockHash: common.HexToHash("0x0"),
BlockNumber: int64(0),
TxIndex: int64(0),
From: common.HexToAddress("0x0"),
To: &a,
Value: big.NewInt(0),
Gas: big.NewInt(0),
GasPrice: big.NewInt(0),
Input: []byte{0},
} }
_, _ = json.Marshal(v)
} }
func TestNewTransactionRes(t *testing.T) { func TestNewTransactionRes(t *testing.T) {
@ -78,26 +68,80 @@ func TestNewTransactionRes(t *testing.T) {
data := []byte{1, 2, 3} data := []byte{1, 2, 3}
tx := types.NewTransactionMessage(to, amount, gasAmount, gasPrice, data) tx := types.NewTransactionMessage(to, amount, gasAmount, gasPrice, data)
_ = NewTransactionRes(tx) tests := map[string]string{
} "hash": reHash,
"nonce": reNum,
func TestLogRes(t *testing.T) { "blockHash": reHash,
topics := make([]common.Hash, 3) "blockNum": reNum,
topics = append(topics, common.HexToHash("0x00")) "transactionIndex": reNum,
topics = append(topics, common.HexToHash("0x10")) "from": reAddress,
topics = append(topics, common.HexToHash("0x20")) "to": reAddressOpt,
"value": reNum,
v := &LogRes{ "gas": reNum,
Topics: topics, "gasPrice": reNum,
Address: common.HexToAddress("0x0"), "input": reData,
Data: []byte{1, 2, 3}, }
Number: uint64(5),
v := NewTransactionRes(tx)
v.BlockHash = newHexData(common.HexToHash("0x030201"))
v.BlockNumber = newHexNum(5)
v.TxIndex = newHexNum(0)
j, _ := json.Marshal(v)
for k, re := range tests {
match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j))
if !match {
t.Error(fmt.Sprintf("`%s` output json does not match format %s. Source %s", k, re, j))
}
} }
_, _ = json.Marshal(v)
} }
func MakeStateLog(num int) state.Log { func TestNewLogRes(t *testing.T) {
log := makeStateLog(0)
tests := map[string]string{
"address": reAddress,
// "topics": "[.*]"
"data": reData,
"blockNumber": reNum,
// "hash": reHash,
// "logIndex": reNum,
// "blockHash": reHash,
// "transactionHash": reHash,
"transactionIndex": reNum,
}
v := NewLogRes(log)
j, _ := json.Marshal(v)
for k, re := range tests {
match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j))
if !match {
t.Error(fmt.Sprintf("`%s` output json does not match format %s. Got %s", k, re, j))
}
}
}
func TestNewLogsRes(t *testing.T) {
logs := make([]state.Log, 3)
logs[0] = makeStateLog(1)
logs[1] = makeStateLog(2)
logs[2] = makeStateLog(3)
tests := map[string]string{}
v := NewLogsRes(logs)
j, _ := json.Marshal(v)
for k, re := range tests {
match, _ := regexp.MatchString(fmt.Sprintf(`[{.*"%s":%s.*}]`, k, re), string(j))
if !match {
t.Error(fmt.Sprintf("%s output json does not match format %s. Got %s", k, re, j))
}
}
}
func makeStateLog(num int) state.Log {
address := common.HexToAddress("0x0") address := common.HexToAddress("0x0")
data := []byte{1, 2, 3} data := []byte{1, 2, 3}
number := uint64(num) number := uint64(num)
@ -108,16 +152,3 @@ func MakeStateLog(num int) state.Log {
log := state.NewLog(address, topics, data, number) log := state.NewLog(address, topics, data, number)
return log return log
} }
func TestNewLogRes(t *testing.T) {
log := MakeStateLog(0)
_ = NewLogRes(log)
}
func TestNewLogsRes(t *testing.T) {
logs := make([]state.Log, 3)
logs[0] = MakeStateLog(1)
logs[1] = MakeStateLog(2)
logs[2] = MakeStateLog(3)
_ = NewLogsRes(logs)
}

View File

@ -19,6 +19,7 @@ import (
"github.com/ethereum/go-ethereum/event/filter" "github.com/ethereum/go-ethereum/event/filter"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/rlp"
) )
var ( var (
@ -185,12 +186,29 @@ func (self *XEth) EthBlockByHash(strHash string) *types.Block {
return block return block
} }
func (self *XEth) EthTransactionByHash(hash string) *types.Transaction { func (self *XEth) EthTransactionByHash(hash string) (tx *types.Transaction, blhash common.Hash, blnum *big.Int, txi uint64) {
data, _ := self.backend.ExtraDb().Get(common.FromHex(hash)) data, _ := self.backend.ExtraDb().Get(common.FromHex(hash))
if len(data) != 0 { if len(data) != 0 {
return types.NewTransactionFromBytes(data) tx = types.NewTransactionFromBytes(data)
} }
return nil
// meta
var txExtra struct {
BlockHash common.Hash
BlockIndex int64
Index uint64
}
v, _ := self.backend.ExtraDb().Get(append(common.FromHex(hash), 0x0001))
r := bytes.NewReader(v)
err := rlp.Decode(r, &txExtra)
if err == nil {
blhash = txExtra.BlockHash
blnum = big.NewInt(txExtra.BlockIndex)
txi = txExtra.Index
}
return
} }
func (self *XEth) BlockByNumber(num int64) *Block { func (self *XEth) BlockByNumber(num int64) *Block {