swarm: merge branch 'master' into fix-initial-peers-msg-races

This commit is contained in:
Janos Guljas 2019-04-15 16:51:11 +02:00
commit f741f38667
27 changed files with 1318 additions and 762 deletions

File diff suppressed because one or more lines are too long

View file

@ -14,8 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// signer is a utility that can be used so sign transactions and
// arbitrary data.
package main
import (
@ -51,22 +49,23 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core"
"github.com/ethereum/go-ethereum/signer/fourbyte"
"github.com/ethereum/go-ethereum/signer/rules"
"github.com/ethereum/go-ethereum/signer/storage"
"gopkg.in/urfave/cli.v1"
)
const legalWarning = `
WARNING!
WARNING!
Clef is an account management tool. It may, like any software, contain bugs.
Clef is an account management tool. It may, like any software, contain bugs.
Please take care to
- backup your keystore files,
- backup your keystore files,
- verify that the keystore(s) can be opened with your password.
Clef is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
Clef is distributed in the hope that 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.
`
@ -104,11 +103,6 @@ var (
Name: "signersecret",
Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash",
}
dBFlag = cli.StringFlag{
Name: "4bytedb",
Usage: "File containing 4byte-identifiers",
Value: "./4byte.json",
}
customDBFlag = cli.StringFlag{
Name: "4bytedb-custom",
Usage: "File used for writing new 4byte-identifiers submitted via API",
@ -145,7 +139,7 @@ var (
configdirFlag,
},
Description: `
The init command generates a master seed which Clef can use to store credentials and data needed for
The init command generates a master seed which Clef can use to store credentials and data needed for
the rule-engine to work.`,
}
attestCommand = cli.Command{
@ -159,10 +153,10 @@ the rule-engine to work.`,
signerSecretFlag,
},
Description: `
The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of
incoming requests.
The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of
incoming requests.
Whenever you make an edit to the rule file, you need to use attestation to tell
Whenever you make an edit to the rule file, you need to use attestation to tell
Clef that the file is 'safe' to execute.`,
}
@ -177,7 +171,7 @@ Clef that the file is 'safe' to execute.`,
signerSecretFlag,
},
Description: `
The setpw command stores a password for a given address (keyfile). If you enter a blank passphrase, it will
The setpw command stores a password for a given address (keyfile). If you enter a blank passphrase, it will
remove any stored credential for that address (keyfile)
`}
gendocCommand = cli.Command{
@ -206,7 +200,6 @@ func init() {
utils.RPCEnabledFlag,
rpcPortFlag,
signerSecretFlag,
dBFlag,
customDBFlag,
auditLogFlag,
ruleFlag,
@ -273,12 +266,12 @@ func initializeSecrets(c *cli.Context) error {
}
fmt.Printf("A master seed has been generated into %s\n", location)
fmt.Printf(`
This is required to be able to store credentials, such as :
This is required to be able to store credentials, such as :
* Passwords for keystores (used by rule engine)
* Storage for javascript rules
* Hash of rule-file
You should treat that file with utmost secrecy, and make a backup of it.
You should treat that file with utmost secrecy, and make a backup of it.
NOTE: This file does not contain your accounts. Those need to be backed up separately!
`)
@ -365,13 +358,14 @@ func signer(c *cli.Context) error {
log.Info("Using CLI as UI-channel")
ui = core.NewCommandlineUI()
}
fourByteDb := c.GlobalString(dBFlag.Name)
// 4bytedb data
fourByteLocal := c.GlobalString(customDBFlag.Name)
db, err := core.NewAbiDBFromFiles(fourByteDb, fourByteLocal)
db, err := fourbyte.NewWithFile(fourByteLocal)
if err != nil {
utils.Fatalf(err.Error())
}
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", fourByteDb, "local", fourByteLocal)
embeds, locals := db.Size()
log.Info("Loaded 4byte database", "embeds", embeds, "locals", locals, "local", fourByteLocal)
var (
api core.ExternalAPI

View file

@ -52,11 +52,12 @@ func TestACT(t *testing.T) {
t.Skip()
}
initCluster(t)
cluster := newTestCluster(t, clusterSize)
defer cluster.Shutdown()
cases := []struct {
name string
f func(t *testing.T)
f func(t *testing.T, cluster *testCluster)
}{
{"Password", testPassword},
{"PK", testPK},
@ -65,7 +66,9 @@ func TestACT(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, tc.f)
t.Run(tc.name, func(t *testing.T) {
tc.f(t, cluster)
})
}
}
@ -74,7 +77,7 @@ func TestACT(t *testing.T) {
// The parties participating - node (publisher), uploads to second node then disappears. Content which was uploaded
// is then fetched through 2nd node. since the tested code is not key-aware - we can just
// fetch from the 2nd node using HTTP BasicAuth
func testPassword(t *testing.T) {
func testPassword(t *testing.T, cluster *testCluster) {
dataFilename := testutil.TempFileWithContent(t, data)
defer os.RemoveAll(dataFilename)
@ -226,7 +229,7 @@ func testPassword(t *testing.T) {
// The parties participating - node (publisher), uploads to second node (which is also the grantee) then disappears.
// Content which was uploaded is then fetched through the grantee's http proxy. Since the tested code is private-key aware,
// the test will fail if the proxy's given private key is not granted on the ACT.
func testPK(t *testing.T) {
func testPK(t *testing.T, cluster *testCluster) {
dataFilename := testutil.TempFileWithContent(t, data)
defer os.RemoveAll(dataFilename)
@ -359,13 +362,13 @@ func testPK(t *testing.T) {
}
// testACTWithoutBogus tests the creation of the ACT manifest end-to-end, without any bogus entries (i.e. default scenario = 3 nodes 1 unauthorized)
func testACTWithoutBogus(t *testing.T) {
testACT(t, 0)
func testACTWithoutBogus(t *testing.T, cluster *testCluster) {
testACT(t, cluster, 0)
}
// testACTWithBogus tests the creation of the ACT manifest end-to-end, with 100 bogus entries (i.e. 100 EC keys + default scenario = 3 nodes 1 unauthorized = 103 keys in the ACT manifest)
func testACTWithBogus(t *testing.T) {
testACT(t, 100)
func testACTWithBogus(t *testing.T, cluster *testCluster) {
testACT(t, cluster, 100)
}
// testACT tests the e2e creation, uploading and downloading of an ACT access control with both EC keys AND password protection
@ -373,7 +376,7 @@ func testACTWithBogus(t *testing.T) {
// set and also protects the ACT with a password. the third node should fail decoding the reference as it will not be granted access.
// the third node then then tries to download using a correct password (and succeeds) then uses a wrong password and fails.
// the publisher uploads through one of the nodes then disappears.
func testACT(t *testing.T, bogusEntries int) {
func testACT(t *testing.T, cluster *testCluster, bogusEntries int) {
var uploadThroughNode = cluster.Nodes[0]
client := swarmapi.NewClient(uploadThroughNode.URL)

View file

@ -59,15 +59,6 @@ func init() {
const clusterSize = 3
var clusteronce sync.Once
var cluster *testCluster
func initCluster(t *testing.T) {
clusteronce.Do(func() {
cluster = newTestCluster(t, clusterSize)
})
}
func serverFunc(api *api.API) swarmhttp.TestServer {
return swarmhttp.NewServer(api, "")
}
@ -165,10 +156,8 @@ outer:
}
func (c *testCluster) Shutdown() {
for _, node := range c.Nodes {
node.Shutdown()
}
os.RemoveAll(c.TmpDir)
c.Stop()
c.Cleanup()
}
func (c *testCluster) Stop() {
@ -179,16 +168,35 @@ func (c *testCluster) Stop() {
func (c *testCluster) StartNewNodes(t *testing.T, size int) {
c.Nodes = make([]*testNode, 0, size)
errors := make(chan error, size)
nodes := make(chan *testNode, size)
for i := 0; i < size; i++ {
dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i))
if err := os.Mkdir(dir, 0700); err != nil {
t.Fatal(err)
go func(nodeIndex int) {
dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", nodeIndex))
if err := os.Mkdir(dir, 0700); err != nil {
errors <- err
return
}
node := newTestNode(t, dir)
node.Name = fmt.Sprintf("swarm%02d", nodeIndex)
nodes <- node
}(i)
}
for i := 0; i < size; i++ {
select {
case node := <-nodes:
c.Nodes = append(c.Nodes, node)
case err := <-errors:
t.Error(err)
}
}
node := newTestNode(t, dir)
node.Name = fmt.Sprintf("swarm%02d", i)
c.Nodes = append(c.Nodes, node)
if t.Failed() {
c.Shutdown()
t.FailNow()
}
}

View file

@ -46,11 +46,12 @@ func TestSwarmUp(t *testing.T) {
t.Skip()
}
initCluster(t)
cluster := newTestCluster(t, clusterSize)
defer cluster.Shutdown()
cases := []struct {
name string
f func(t *testing.T)
f func(t *testing.T, cluster *testCluster)
}{
{"NoEncryption", testNoEncryption},
{"Encrypted", testEncrypted},
@ -60,31 +61,33 @@ func TestSwarmUp(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, tc.f)
t.Run(tc.name, func(t *testing.T) {
tc.f(t, cluster)
})
}
}
// testNoEncryption tests that running 'swarm up' makes the resulting file
// available from all nodes via the HTTP API
func testNoEncryption(t *testing.T) {
testDefault(false, t)
func testNoEncryption(t *testing.T, cluster *testCluster) {
testDefault(t, cluster, false)
}
// testEncrypted tests that running 'swarm up --encrypted' makes the resulting file
// available from all nodes via the HTTP API
func testEncrypted(t *testing.T) {
testDefault(true, t)
func testEncrypted(t *testing.T, cluster *testCluster) {
testDefault(t, cluster, true)
}
func testRecursiveNoEncryption(t *testing.T) {
testRecursive(false, t)
func testRecursiveNoEncryption(t *testing.T, cluster *testCluster) {
testRecursive(t, cluster, false)
}
func testRecursiveEncrypted(t *testing.T) {
testRecursive(true, t)
func testRecursiveEncrypted(t *testing.T, cluster *testCluster) {
testRecursive(t, cluster, true)
}
func testDefault(toEncrypt bool, t *testing.T) {
func testDefault(t *testing.T, cluster *testCluster, toEncrypt bool) {
tmpFileName := testutil.TempFileWithContent(t, data)
defer os.Remove(tmpFileName)
@ -189,7 +192,7 @@ func testDefault(toEncrypt bool, t *testing.T) {
}
}
func testRecursive(toEncrypt bool, t *testing.T) {
func testRecursive(t *testing.T, cluster *testCluster, toEncrypt bool) {
tmpUploadDir, err := ioutil.TempDir("", "swarm-test")
if err != nil {
t.Fatal(err)
@ -279,14 +282,14 @@ func testRecursive(toEncrypt bool, t *testing.T) {
// testDefaultPathAll tests swarm recursive upload with relative and absolute
// default paths and with encryption.
func testDefaultPathAll(t *testing.T) {
testDefaultPath(false, false, t)
testDefaultPath(false, true, t)
testDefaultPath(true, false, t)
testDefaultPath(true, true, t)
func testDefaultPathAll(t *testing.T, cluster *testCluster) {
testDefaultPath(t, cluster, false, false)
testDefaultPath(t, cluster, false, true)
testDefaultPath(t, cluster, true, false)
testDefaultPath(t, cluster, true, true)
}
func testDefaultPath(toEncrypt bool, absDefaultPath bool, t *testing.T) {
func testDefaultPath(t *testing.T, cluster *testCluster, toEncrypt bool, absDefaultPath bool) {
tmp, err := ioutil.TempDir("", "swarm-defaultpath-test")
if err != nil {
t.Fatal(err)

View file

@ -9,6 +9,7 @@ import (
"sync"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/metrics/prometheus"
)
type exp struct {
@ -42,6 +43,7 @@ func Exp(r metrics.Registry) {
// http.HandleFunc("/debug/vars", e.expHandler)
// haven't found an elegant way, so just use a different endpoint
http.Handle("/debug/metrics", h)
http.Handle("/debug/metrics/prometheus", prometheus.Handler(r))
}
// ExpHandler will return an expvar powered metrics handler.

View file

@ -0,0 +1,115 @@
// Copyright 2019 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 prometheus
import (
"bytes"
"fmt"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/metrics"
)
var (
typeGaugeTpl = "# TYPE %s gauge\n"
typeCounterTpl = "# TYPE %s counter\n"
typeSummaryTpl = "# TYPE %s summary\n"
keyValueTpl = "%s %v\n\n"
keyQuantileTagValueTpl = "%s {quantile=\"%s\"} %v\n\n"
)
// collector is a collection of byte buffers that aggregate Prometheus reports
// for different metric types.
type collector struct {
buff *bytes.Buffer
}
// newCollector createa a new Prometheus metric aggregator.
func newCollector() *collector {
return &collector{
buff: &bytes.Buffer{},
}
}
func (c *collector) addCounter(name string, m metrics.Counter) {
c.writeGaugeCounter(name, m.Count())
}
func (c *collector) addGauge(name string, m metrics.Gauge) {
c.writeGaugeCounter(name, m.Value())
}
func (c *collector) addGaugeFloat64(name string, m metrics.GaugeFloat64) {
c.writeGaugeCounter(name, m.Value())
}
func (c *collector) addHistogram(name string, m metrics.Histogram) {
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
ps := m.Percentiles(pv)
c.writeSummaryCounter(name, m.Count())
for i := range pv {
c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
}
}
func (c *collector) addMeter(name string, m metrics.Meter) {
c.writeGaugeCounter(name, m.Count())
}
func (c *collector) addTimer(name string, m metrics.Timer) {
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
ps := m.Percentiles(pv)
c.writeSummaryCounter(name, m.Count())
for i := range pv {
c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
}
}
func (c *collector) addResettingTimer(name string, m metrics.ResettingTimer) {
if len(m.Values()) <= 0 {
return
}
ps := m.Percentiles([]float64{50, 95, 99})
val := m.Values()
c.writeSummaryCounter(name, len(val))
c.writeSummaryPercentile(name, "0.50", ps[0])
c.writeSummaryPercentile(name, "0.95", ps[1])
c.writeSummaryPercentile(name, "0.99", ps[2])
}
func (c *collector) writeGaugeCounter(name string, value interface{}) {
name = mutateKey(name)
c.buff.WriteString(fmt.Sprintf(typeGaugeTpl, name))
c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value))
}
func (c *collector) writeSummaryCounter(name string, value interface{}) {
name = mutateKey(name + "_count")
c.buff.WriteString(fmt.Sprintf(typeCounterTpl, name))
c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value))
}
func (c *collector) writeSummaryPercentile(name, p string, value interface{}) {
name = mutateKey(name)
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, name))
c.buff.WriteString(fmt.Sprintf(keyQuantileTagValueTpl, name, p, value))
}
func mutateKey(key string) string {
return strings.Replace(key, "/", "_", -1)
}

View file

@ -0,0 +1,68 @@
// Copyright 2019 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 prometheus exposes go-metrics into a Prometheus format.
package prometheus
import (
"fmt"
"net/http"
"sort"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
)
// Handler returns an HTTP handler which dump metrics in Prometheus format.
func Handler(reg metrics.Registry) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Gather and pre-sort the metrics to avoid random listings
var names []string
reg.Each(func(name string, i interface{}) {
names = append(names, name)
})
sort.Strings(names)
// Aggregate all the metris into a Prometheus collector
c := newCollector()
for _, name := range names {
i := reg.Get(name)
switch m := i.(type) {
case metrics.Counter:
c.addCounter(name, m.Snapshot())
case metrics.Gauge:
c.addGauge(name, m.Snapshot())
case metrics.GaugeFloat64:
c.addGaugeFloat64(name, m.Snapshot())
case metrics.Histogram:
c.addHistogram(name, m.Snapshot())
case metrics.Meter:
c.addMeter(name, m.Snapshot())
case metrics.Timer:
c.addTimer(name, m.Snapshot())
case metrics.ResettingTimer:
c.addResettingTimer(name, m.Snapshot())
default:
log.Warn("Unknown Prometheus metric type", "type", fmt.Sprintf("%T", i))
}
}
w.Header().Add("Content-Type", "text/plain")
w.Header().Add("Content-Length", fmt.Sprint(c.buff.Len()))
w.Write(c.buff.Bytes())
})
}

View file

@ -482,7 +482,7 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
if extapi, err := external.NewExternalBackend(conf.ExternalSigner); err == nil {
backends = append(backends, extapi)
} else {
log.Info("Error configuring external signer", "error", err)
return nil, "", fmt.Errorf("error connecting to external signer: %v", err)
}
}
if len(backends) == 0 {

View file

@ -1,260 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"regexp"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)
type decodedArgument struct {
soltype abi.Argument
value interface{}
}
type decodedCallData struct {
signature string
name string
inputs []decodedArgument
}
// String implements stringer interface, tries to use the underlying value-type
func (arg decodedArgument) String() string {
var value string
switch val := arg.value.(type) {
case fmt.Stringer:
value = val.String()
default:
value = fmt.Sprintf("%v", val)
}
return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value)
}
// String implements stringer interface for decodedCallData
func (cd decodedCallData) String() string {
args := make([]string, len(cd.inputs))
for i, arg := range cd.inputs {
args[i] = arg.String()
}
return fmt.Sprintf("%s(%s)", cd.name, strings.Join(args, ","))
}
// parseCallData matches the provided call data against the abi definition,
// and returns a struct containing the actual go-typed values
func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
if len(calldata) < 4 {
return nil, fmt.Errorf("Invalid ABI-data, incomplete method signature of (%d bytes)", len(calldata))
}
sigdata, argdata := calldata[:4], calldata[4:]
if len(argdata)%32 != 0 {
return nil, fmt.Errorf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", len(argdata))
}
abispec, err := abi.JSON(strings.NewReader(abidata))
if err != nil {
return nil, fmt.Errorf("Failed parsing JSON ABI: %v, abidata: %v", err, abidata)
}
method, err := abispec.MethodById(sigdata)
if err != nil {
return nil, err
}
v, err := method.Inputs.UnpackValues(argdata)
if err != nil {
return nil, err
}
decoded := decodedCallData{signature: method.Sig(), name: method.Name}
for n, argument := range method.Inputs {
if err != nil {
return nil, fmt.Errorf("Failed to decode argument %d (signature %v): %v", n, method.Sig(), err)
}
decodedArg := decodedArgument{
soltype: argument,
value: v[n],
}
decoded.inputs = append(decoded.inputs, decodedArg)
}
// We're finished decoding the data. At this point, we encode the decoded data to see if it matches with the
// original data. If we didn't do that, it would e.g. be possible to stuff extra data into the arguments, which
// is not detected by merely decoding the data.
var (
encoded []byte
)
encoded, err = method.Inputs.PackValues(v)
if err != nil {
return nil, err
}
if !bytes.Equal(encoded, argdata) {
was := common.Bytes2Hex(encoded)
exp := common.Bytes2Hex(argdata)
return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig())
}
return &decoded, nil
}
// MethodSelectorToAbi converts a method selector into an ABI struct. The returned data is a valid json string
// which can be consumed by the standard abi package.
func MethodSelectorToAbi(selector string) ([]byte, error) {
re := regexp.MustCompile(`^([^\)]+)\(([a-z0-9,\[\]]*)\)`)
type fakeArg struct {
Type string `json:"type"`
}
type fakeABI struct {
Name string `json:"name"`
Type string `json:"type"`
Inputs []fakeArg `json:"inputs"`
}
groups := re.FindStringSubmatch(selector)
if len(groups) != 3 {
return nil, fmt.Errorf("Did not match: %v (%v matches)", selector, len(groups))
}
name := groups[1]
args := groups[2]
arguments := make([]fakeArg, 0)
if len(args) > 0 {
for _, arg := range strings.Split(args, ",") {
arguments = append(arguments, fakeArg{arg})
}
}
abicheat := fakeABI{
name, "function", arguments,
}
return json.Marshal([]fakeABI{abicheat})
}
type AbiDb struct {
db map[string]string
customdb map[string]string
customdbPath string
}
// NewEmptyAbiDB exists for test purposes
func NewEmptyAbiDB() (*AbiDb, error) {
return &AbiDb{make(map[string]string), make(map[string]string), ""}, nil
}
// NewAbiDBFromFile loads signature database from file, and
// errors if the file is not valid json. Does no other validation of contents
func NewAbiDBFromFile(path string) (*AbiDb, error) {
raw, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
db, err := NewEmptyAbiDB()
if err != nil {
return nil, err
}
if err := json.Unmarshal(raw, &db.db); err != nil {
return nil, err
}
return db, nil
}
// NewAbiDBFromFiles loads both the standard signature database and a custom database. The latter will be used
// to write new values into if they are submitted via the API
func NewAbiDBFromFiles(standard, custom string) (*AbiDb, error) {
db := &AbiDb{make(map[string]string), make(map[string]string), custom}
db.customdbPath = custom
raw, err := ioutil.ReadFile(standard)
if err != nil {
return nil, err
}
if err := json.Unmarshal(raw, &db.db); err != nil {
return nil, err
}
// Custom file may not exist. Will be created during save, if needed
if _, err := os.Stat(custom); err == nil {
raw, err = ioutil.ReadFile(custom)
if err != nil {
return nil, err
}
if err := json.Unmarshal(raw, &db.customdb); err != nil {
return nil, err
}
}
return db, nil
}
// LookupMethodSelector checks the given 4byte-sequence against the known ABI methods.
// OBS: This method does not validate the match, it's assumed the caller will do so
func (db *AbiDb) LookupMethodSelector(id []byte) (string, error) {
if len(id) < 4 {
return "", fmt.Errorf("Expected 4-byte id, got %d", len(id))
}
sig := common.ToHex(id[:4])
if key, exists := db.db[sig]; exists {
return key, nil
}
if key, exists := db.customdb[sig]; exists {
return key, nil
}
return "", fmt.Errorf("Signature %v not found", sig)
}
func (db *AbiDb) Size() int {
return len(db.db)
}
// saveCustomAbi saves a signature ephemerally. If custom file is used, also saves to disk
func (db *AbiDb) saveCustomAbi(selector, signature string) error {
db.customdb[signature] = selector
if db.customdbPath == "" {
return nil //Not an error per se, just not used
}
d, err := json.Marshal(db.customdb)
if err != nil {
return err
}
err = ioutil.WriteFile(db.customdbPath, d, 0600)
return err
}
// AddSignature to the database, if custom database saving is enabled.
// OBS: This method does _not_ validate the correctness of the data,
// it is assumed that the caller has already done so
func (db *AbiDb) AddSignature(selector string, data []byte) error {
if len(data) < 4 {
return nil
}
_, err := db.LookupMethodSelector(data[:4])
if err == nil {
return nil
}
sig := common.ToHex(data[:4])
return db.saveCustomAbi(selector, sig)
}

View file

@ -92,12 +92,25 @@ type UIClientAPI interface {
RegisterUIServer(api *UIServerAPI)
}
// Validator defines the methods required to validate a transaction against some
// sanity defaults as well as any underlying 4byte method database.
//
// Use fourbyte.Database as an implementation. It is separated out of this package
// to allow pieces of the signer package to be used without having to load the
// 7MB embedded 4byte dump.
type Validator interface {
// ValidateTransaction does a number of checks on the supplied transaction, and
// returns either a list of warnings, or an error (indicating that the transaction
// should be immediately rejected).
ValidateTransaction(selector *string, tx *SendTxArgs) (*ValidationMessages, error)
}
// SignerAPI defines the actual implementation of ExternalAPI
type SignerAPI struct {
chainID *big.Int
am *accounts.Manager
UI UIClientAPI
validator *Validator
validator Validator
rejectMode bool
credentials storage.Storage
}
@ -235,11 +248,11 @@ var ErrRequestDenied = errors.New("Request denied")
// key that is generated when a new Account is created.
// noUSB disables USB support that is required to support hardware devices such as
// ledger and trezor.
func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, abidb *AbiDb, advancedMode bool, credentials storage.Storage) *SignerAPI {
func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, validator Validator, advancedMode bool, credentials storage.Storage) *SignerAPI {
if advancedMode {
log.Info("Clef is in advanced mode: will warn instead of reject")
}
signer := &SignerAPI{big.NewInt(chainID), am, ui, NewValidator(abidb), !advancedMode, credentials}
signer := &SignerAPI{big.NewInt(chainID), am, ui, validator, !advancedMode, credentials}
if !noUSB {
signer.startUSBListener()
}
@ -458,7 +471,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
err error
result SignTxResponse
)
msgs, err := api.validator.ValidateTransaction(&args, methodSelector)
msgs, err := api.validator.ValidateTransaction(methodSelector, &args)
if err != nil {
return nil, err
}

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
//
package core
package core_test
import (
"bytes"
@ -34,6 +34,8 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/signer/core"
"github.com/ethereum/go-ethereum/signer/fourbyte"
"github.com/ethereum/go-ethereum/signer/storage"
)
@ -43,56 +45,56 @@ type headlessUi struct {
inputCh chan string // to send password
}
func (ui *headlessUi) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
func (ui *headlessUi) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
input := <-ui.inputCh
return UserInputResponse{Text: input}, nil
return core.UserInputResponse{Text: input}, nil
}
func (ui *headlessUi) OnSignerStartup(info StartupInfo) {}
func (ui *headlessUi) RegisterUIServer(api *UIServerAPI) {}
func (ui *headlessUi) OnSignerStartup(info core.StartupInfo) {}
func (ui *headlessUi) RegisterUIServer(api *core.UIServerAPI) {}
func (ui *headlessUi) OnApprovedTx(tx ethapi.SignTransactionResult) {}
func (ui *headlessUi) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
func (ui *headlessUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
switch <-ui.approveCh {
case "Y":
return SignTxResponse{request.Transaction, true}, nil
return core.SignTxResponse{request.Transaction, true}, nil
case "M": // modify
// The headless UI always modifies the transaction
old := big.Int(request.Transaction.Value)
newVal := big.NewInt(0).Add(&old, big.NewInt(1))
request.Transaction.Value = hexutil.Big(*newVal)
return SignTxResponse{request.Transaction, true}, nil
return core.SignTxResponse{request.Transaction, true}, nil
default:
return SignTxResponse{request.Transaction, false}, nil
return core.SignTxResponse{request.Transaction, false}, nil
}
}
func (ui *headlessUi) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
func (ui *headlessUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
approved := "Y" == <-ui.approveCh
return SignDataResponse{approved}, nil
return core.SignDataResponse{approved}, nil
}
func (ui *headlessUi) ApproveListing(request *ListRequest) (ListResponse, error) {
func (ui *headlessUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
approval := <-ui.approveCh
//fmt.Printf("approval %s\n", approval)
switch approval {
case "A":
return ListResponse{request.Accounts}, nil
return core.ListResponse{request.Accounts}, nil
case "1":
l := make([]accounts.Account, 1)
l[0] = request.Accounts[1]
return ListResponse{l}, nil
return core.ListResponse{l}, nil
default:
return ListResponse{nil}, nil
return core.ListResponse{nil}, nil
}
}
func (ui *headlessUi) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
if "Y" == <-ui.approveCh {
return NewAccountResponse{true}, nil
return core.NewAccountResponse{true}, nil
}
return NewAccountResponse{false}, nil
return core.NewAccountResponse{false}, nil
}
func (ui *headlessUi) ShowError(message string) {
@ -117,18 +119,18 @@ func tmpDirName(t *testing.T) string {
return d
}
func setup(t *testing.T) (*SignerAPI, *headlessUi) {
db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json")
func setup(t *testing.T) (*core.SignerAPI, *headlessUi) {
db, err := fourbyte.New()
if err != nil {
t.Fatal(err.Error())
}
ui := &headlessUi{make(chan string, 20), make(chan string, 20)}
am := StartClefAccountManager(tmpDirName(t), true, true)
api := NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{})
am := core.StartClefAccountManager(tmpDirName(t), true, true)
api := core.NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{})
return api, ui
}
func createAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
ui.approveCh <- "Y"
ui.inputCh <- "a_long_password"
_, err := api.New(context.Background())
@ -139,7 +141,7 @@ func createAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
time.Sleep(250 * time.Millisecond)
}
func failCreateAccountWithPassword(ui *headlessUi, api *SignerAPI, password string, t *testing.T) {
func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password string, t *testing.T) {
ui.approveCh <- "Y"
// We will be asked three times to provide a suitable password
@ -156,10 +158,10 @@ func failCreateAccountWithPassword(ui *headlessUi, api *SignerAPI, password stri
}
}
func failCreateAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
ui.approveCh <- "N"
addr, err := api.New(context.Background())
if err != ErrRequestDenied {
if err != core.ErrRequestDenied {
t.Fatal(err)
}
if addr != (common.Address{}) {
@ -167,7 +169,7 @@ func failCreateAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
}
}
func list(ui *headlessUi, api *SignerAPI, t *testing.T) ([]common.Address, error) {
func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, error) {
ui.approveCh <- "A"
return api.List(context.Background())
@ -216,19 +218,19 @@ func TestNewAcc(t *testing.T) {
if len(list) != 0 {
t.Fatalf("List should be empty")
}
if err != ErrRequestDenied {
if err != core.ErrRequestDenied {
t.Fatal("Expected deny")
}
}
func mkTestTx(from common.MixedcaseAddress) SendTxArgs {
func mkTestTx(from common.MixedcaseAddress) core.SendTxArgs {
to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
gas := hexutil.Uint64(21000)
gasPrice := (hexutil.Big)(*big.NewInt(2000000000))
value := (hexutil.Big)(*big.NewInt(1e18))
nonce := (hexutil.Uint64)(0)
data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
tx := SendTxArgs{
tx := core.SendTxArgs{
From: from,
To: &to,
Gas: gas,
@ -272,7 +274,7 @@ func TestSignTx(t *testing.T) {
if res != nil {
t.Errorf("Expected nil-response, got %v", res)
}
if err != ErrRequestDenied {
if err != core.ErrRequestDenied {
t.Errorf("Expected ErrRequestDenied! %v", err)
}
// Sign with correct password

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
//
package core
package core_test
import (
"context"
@ -26,9 +26,10 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/signer/core"
)
var typesStandard = Types{
var typesStandard = core.Types{
"EIP712Domain": {
{
Name: "name",
@ -147,7 +148,7 @@ var jsonTypedData = `
const primaryType = "Mail"
var domainStandard = TypedDataDomain{
var domainStandard = core.TypedDataDomain{
"Ether Mail",
"1",
big.NewInt(1),
@ -167,7 +168,7 @@ var messageStandard = map[string]interface{}{
"contents": "Hello, Bob!",
}
var typedData = TypedData{
var typedData = core.TypedData{
Types: typesStandard,
PrimaryType: primaryType,
Domain: domainStandard,
@ -188,7 +189,7 @@ func TestSignData(t *testing.T) {
control.approveCh <- "Y"
control.inputCh <- "wrongpassword"
signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
signature, err := api.SignData(context.Background(), core.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
if signature != nil {
t.Errorf("Expected nil-data, got %x", signature)
}
@ -196,17 +197,17 @@ func TestSignData(t *testing.T) {
t.Errorf("Expected ErrLocked! '%v'", err)
}
control.approveCh <- "No way"
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
signature, err = api.SignData(context.Background(), core.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
if signature != nil {
t.Errorf("Expected nil-data, got %x", signature)
}
if err != ErrRequestDenied {
if err != core.ErrRequestDenied {
t.Errorf("Expected ErrRequestDenied! '%v'", err)
}
// text/plain
control.approveCh <- "Y"
control.inputCh <- "a_long_password"
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
signature, err = api.SignData(context.Background(), core.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
if err != nil {
t.Fatal(err)
}
@ -226,13 +227,13 @@ func TestSignData(t *testing.T) {
}
func TestDomainChainId(t *testing.T) {
withoutChainID := TypedData{
Types: Types{
"EIP712Domain": []Type{
withoutChainID := core.TypedData{
Types: core.Types{
"EIP712Domain": []core.Type{
{Name: "name", Type: "string"},
},
},
Domain: TypedDataDomain{
Domain: core.TypedDataDomain{
Name: "test",
},
}
@ -241,14 +242,14 @@ func TestDomainChainId(t *testing.T) {
t.Errorf("Expected the chainId key to not be present in the domain map")
}
withChainID := TypedData{
Types: Types{
"EIP712Domain": []Type{
withChainID := core.TypedData{
Types: core.Types{
"EIP712Domain": []core.Type{
{Name: "name", Type: "string"},
{Name: "chainId", Type: "uint256"},
},
},
Domain: TypedDataDomain{
Domain: core.TypedDataDomain{
Name: "test",
ChainId: big.NewInt(1),
},
@ -383,7 +384,7 @@ func TestMalformedDomainkeys(t *testing.T) {
}
}
`
var malformedDomainTypedData TypedData
var malformedDomainTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &malformedDomainTypedData)
if err != nil {
t.Fatalf("unmarshalling failed '%v'", err)
@ -471,7 +472,7 @@ func TestMalformedTypesAndExtradata(t *testing.T) {
}
}
`
var malformedTypedData TypedData
var malformedTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData)
if err != nil {
t.Fatalf("unmarshalling failed '%v'", err)
@ -567,7 +568,7 @@ func TestTypeMismatch(t *testing.T) {
}
}
`
var mismatchTypedData TypedData
var mismatchTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &mismatchTypedData)
if err != nil {
t.Fatalf("unmarshalling failed '%v'", err)
@ -589,7 +590,7 @@ func TestTypeOverflow(t *testing.T) {
//{
// "test": 65536 <-- test defined as uint8
//}
var overflowTypedData TypedData
var overflowTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &overflowTypedData)
if err != nil {
t.Fatalf("unmarshalling failed '%v'", err)
@ -667,7 +668,7 @@ func TestArray(t *testing.T) {
}
}
`
var arrayTypedData TypedData
var arrayTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &arrayTypedData)
if err != nil {
t.Fatalf("unmarshalling failed '%v'", err)
@ -780,7 +781,7 @@ func TestCustomTypeAsArray(t *testing.T) {
}
`
var malformedTypedData TypedData
var malformedTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData)
if err != nil {
t.Fatalf("unmarshalling failed '%v'", err)
@ -792,8 +793,7 @@ func TestCustomTypeAsArray(t *testing.T) {
}
func TestFormatter(t *testing.T) {
var d TypedData
var d core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &d)
if err != nil {
t.Fatalf("unmarshalling failed '%v'", err)

View file

@ -41,13 +41,13 @@ const (
INFO = "Info"
)
func (vs *ValidationMessages) crit(msg string) {
func (vs *ValidationMessages) Crit(msg string) {
vs.Messages = append(vs.Messages, ValidationInfo{CRIT, msg})
}
func (vs *ValidationMessages) warn(msg string) {
func (vs *ValidationMessages) Warn(msg string) {
vs.Messages = append(vs.Messages, ValidationInfo{WARN, msg})
}
func (vs *ValidationMessages) info(msg string) {
func (vs *ValidationMessages) Info(msg string) {
vs.Messages = append(vs.Messages, ValidationInfo{INFO, msg})
}

View file

@ -17,146 +17,11 @@
package core
import (
"bytes"
"errors"
"fmt"
"math/big"
"regexp"
"github.com/ethereum/go-ethereum/common"
)
// The validation package contains validation checks for transactions
// - ABI-data validation
// - Transaction semantics validation
// The package provides warnings for typical pitfalls
type Validator struct {
db *AbiDb
}
func NewValidator(db *AbiDb) *Validator {
return &Validator{db}
}
func testSelector(selector string, data []byte) (*decodedCallData, error) {
if selector == "" {
return nil, fmt.Errorf("selector not found")
}
abiData, err := MethodSelectorToAbi(selector)
if err != nil {
return nil, err
}
info, err := parseCallData(data, string(abiData))
if err != nil {
return nil, err
}
return info, nil
}
// validateCallData checks if the ABI-data + methodselector (if given) can be parsed and seems to match
func (v *Validator) validateCallData(msgs *ValidationMessages, data []byte, methodSelector *string) {
if len(data) == 0 {
return
}
if len(data) < 4 {
msgs.warn("Tx contains data which is not valid ABI")
return
}
if arglen := len(data) - 4; arglen%32 != 0 {
msgs.warn(fmt.Sprintf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", arglen))
}
var (
info *decodedCallData
err error
)
// Check the provided one
if methodSelector != nil {
info, err = testSelector(*methodSelector, data)
if err != nil {
msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err))
} else {
msgs.info(info.String())
//Successfull match. add to db if not there already (ignore errors there)
v.db.AddSignature(*methodSelector, data[:4])
}
return
}
// Check the db
selector, err := v.db.LookupMethodSelector(data[:4])
if err != nil {
msgs.warn(fmt.Sprintf("Tx contains data, but the ABI signature could not be found: %v", err))
return
}
info, err = testSelector(selector, data)
if err != nil {
msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err))
} else {
msgs.info(info.String())
}
}
// validateSemantics checks if the transactions 'makes sense', and generate warnings for a couple of typical scenarios
func (v *Validator) validate(msgs *ValidationMessages, txargs *SendTxArgs, methodSelector *string) error {
// Prevent accidental erroneous usage of both 'input' and 'data'
if txargs.Data != nil && txargs.Input != nil && !bytes.Equal(*txargs.Data, *txargs.Input) {
// This is a showstopper
return errors.New(`Ambiguous request: both "data" and "input" are set and are not identical`)
}
var (
data []byte
)
// Place data on 'data', and nil 'input'
if txargs.Input != nil {
txargs.Data = txargs.Input
txargs.Input = nil
}
if txargs.Data != nil {
data = *txargs.Data
}
if txargs.To == nil {
//Contract creation should contain sufficient data to deploy a contract
// A typical error is omitting sender due to some quirk in the javascript call
// e.g. https://github.com/ethereum/go-ethereum/issues/16106
if len(data) == 0 {
if txargs.Value.ToInt().Cmp(big.NewInt(0)) > 0 {
// Sending ether into black hole
return errors.New("Tx will create contract with value but empty code!")
}
// No value submitted at least
msgs.crit("Tx will create contract with empty code!")
} else if len(data) < 40 { //Arbitrary limit
msgs.warn(fmt.Sprintf("Tx will will create contract, but payload is suspiciously small (%d b)", len(data)))
}
// methodSelector should be nil for contract creation
if methodSelector != nil {
msgs.warn("Tx will create contract, but method selector supplied; indicating intent to call a method.")
}
} else {
if !txargs.To.ValidChecksum() {
msgs.warn("Invalid checksum on to-address")
}
// Normal transaction
if bytes.Equal(txargs.To.Address().Bytes(), common.Address{}.Bytes()) {
// Sending to 0
msgs.crit("Tx destination is the zero address!")
}
// Validate calldata
v.validateCallData(msgs, data, methodSelector)
}
return nil
}
// ValidateTransaction does a number of checks on the supplied transaction, and returns either a list of warnings,
// or an error, indicating that the transaction should be immediately rejected
func (v *Validator) ValidateTransaction(txArgs *SendTxArgs, methodSelector *string) (*ValidationMessages, error) {
msgs := &ValidationMessages{}
return msgs, v.validate(msgs, txArgs, methodSelector)
}
var Printable7BitAscii = regexp.MustCompile("^[A-Za-z0-9!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ ]+$")
var printable7BitAscii = regexp.MustCompile("^[A-Za-z0-9!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ ]+$")
// ValidatePasswordFormat returns an error if the password is too short, or consists of characters
// outside the range of the printable 7bit ascii set
@ -164,7 +29,7 @@ func ValidatePasswordFormat(password string) error {
if len(password) < 10 {
return errors.New("password too short (<10 characters)")
}
if !Printable7BitAscii.MatchString(password) {
if !printable7BitAscii.MatchString(password) {
return errors.New("password contains invalid characters - only 7bit printable ascii allowed")
}
return nil

View file

@ -16,126 +16,7 @@
package core
import (
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func mixAddr(a string) (*common.MixedcaseAddress, error) {
return common.NewMixedcaseAddressFromString(a)
}
func toHexBig(h string) hexutil.Big {
b := big.NewInt(0).SetBytes(common.FromHex(h))
return hexutil.Big(*b)
}
func toHexUint(h string) hexutil.Uint64 {
b := big.NewInt(0).SetBytes(common.FromHex(h))
return hexutil.Uint64(b.Uint64())
}
func dummyTxArgs(t txtestcase) *SendTxArgs {
to, _ := mixAddr(t.to)
from, _ := mixAddr(t.from)
n := toHexUint(t.n)
gas := toHexUint(t.g)
gasPrice := toHexBig(t.gp)
value := toHexBig(t.value)
var (
data, input *hexutil.Bytes
)
if t.d != "" {
a := hexutil.Bytes(common.FromHex(t.d))
data = &a
}
if t.i != "" {
a := hexutil.Bytes(common.FromHex(t.i))
input = &a
}
return &SendTxArgs{
From: *from,
To: to,
Value: value,
Nonce: n,
GasPrice: gasPrice,
Gas: gas,
Data: data,
Input: input,
}
}
type txtestcase struct {
from, to, n, g, gp, value, d, i string
expectErr bool
numMessages int
}
func TestValidator(t *testing.T) {
var (
// use empty db, there are other tests for the abi-specific stuff
db, _ = NewEmptyAbiDB()
v = NewValidator(db)
)
testcases := []txtestcase{
// Invalid to checksum
{from: "000000000000000000000000000000000000dead", to: "000000000000000000000000000000000000dead",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1},
// valid 0x000000000000000000000000000000000000dEaD
{from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 0},
// conflicting input and data
{from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", i: "0x02", expectErr: true},
// Data can't be parsed
{from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x0102", numMessages: 1},
// Data (on Input) can't be parsed
{from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", i: "0x0102", numMessages: 1},
// Send to 0
{from: "000000000000000000000000000000000000dead", to: "0x0000000000000000000000000000000000000000",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1},
// Create empty contract (no value)
{from: "000000000000000000000000000000000000dead", to: "",
n: "0x01", g: "0x20", gp: "0x40", value: "0x00", numMessages: 1},
// Create empty contract (with value)
{from: "000000000000000000000000000000000000dead", to: "",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", expectErr: true},
// Small payload for create
{from: "000000000000000000000000000000000000dead", to: "",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", numMessages: 1},
}
for i, test := range testcases {
msgs, err := v.ValidateTransaction(dummyTxArgs(test), nil)
if err == nil && test.expectErr {
t.Errorf("Test %d, expected error", i)
for _, msg := range msgs.Messages {
fmt.Printf("* %s: %s\n", msg.Typ, msg.Message)
}
}
if err != nil && !test.expectErr {
t.Errorf("Test %d, unexpected error: %v", i, err)
}
if err == nil {
got := len(msgs.Messages)
if got != test.numMessages {
for _, msg := range msgs.Messages {
fmt.Printf("* %s: %s\n", msg.Typ, msg.Message)
}
t.Errorf("Test %d, expected %d messages, got %d", i, test.numMessages, got)
} else {
//Debug printout, remove later
for _, msg := range msgs.Messages {
fmt.Printf("* [%d] %s: %s\n", i, msg.Typ, msg.Message)
}
fmt.Println()
}
}
}
}
import "testing"
func TestPasswordValidation(t *testing.T) {
testcases := []struct {

271
signer/fourbyte/4byte.go Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

164
signer/fourbyte/abi.go Normal file
View file

@ -0,0 +1,164 @@
// Copyright 2018 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 fourbyte
import (
"bytes"
"encoding/json"
"fmt"
"regexp"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)
// decodedCallData is an internal type to represent a method call parsed according
// to an ABI method signature.
type decodedCallData struct {
signature string
name string
inputs []decodedArgument
}
// decodedArgument is an internal type to represent an argument parsed according
// to an ABI method signature.
type decodedArgument struct {
soltype abi.Argument
value interface{}
}
// String implements stringer interface, tries to use the underlying value-type
func (arg decodedArgument) String() string {
var value string
switch val := arg.value.(type) {
case fmt.Stringer:
value = val.String()
default:
value = fmt.Sprintf("%v", val)
}
return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value)
}
// String implements stringer interface for decodedCallData
func (cd decodedCallData) String() string {
args := make([]string, len(cd.inputs))
for i, arg := range cd.inputs {
args[i] = arg.String()
}
return fmt.Sprintf("%s(%s)", cd.name, strings.Join(args, ","))
}
// verifySelector checks whether the ABI encoded data blob matches the requested
// function signature.
func verifySelector(selector string, calldata []byte) (*decodedCallData, error) {
// Parse the selector into an ABI JSON spec
abidata, err := parseSelector(selector)
if err != nil {
return nil, err
}
// Parse the call data according to the requested selector
return parseCallData(calldata, string(abidata))
}
// selectorRegexp is used to validate that a 4byte database selector corresponds
// to a valid ABI function declaration.
//
// Note, although uppercase letters are not part of the ABI spec, this regexp
// still accepts it as the general format is valid. It will be rejected later
// by the type checker.
var selectorRegexp = regexp.MustCompile(`^([^\)]+)\(([A-Za-z0-9,\[\]]*)\)`)
// parseSelector converts a method selector into an ABI JSON spec. The returned
// data is a valid JSON string which can be consumed by the standard abi package.
func parseSelector(selector string) ([]byte, error) {
// Define a tiny fake ABI struct for JSON marshalling
type fakeArg struct {
Type string `json:"type"`
}
type fakeABI struct {
Name string `json:"name"`
Type string `json:"type"`
Inputs []fakeArg `json:"inputs"`
}
// Validate the selector and extract it's components
groups := selectorRegexp.FindStringSubmatch(selector)
if len(groups) != 3 {
return nil, fmt.Errorf("invalid selector %s (%v matches)", selector, len(groups))
}
name := groups[1]
args := groups[2]
// Reassemble the fake ABI and constuct the JSON
arguments := make([]fakeArg, 0)
if len(args) > 0 {
for _, arg := range strings.Split(args, ",") {
arguments = append(arguments, fakeArg{arg})
}
}
return json.Marshal([]fakeABI{{name, "function", arguments}})
}
// parseCallData matches the provided call data against the ABI definition and
// returns a struct containing the actual go-typed values.
func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
// Validate the call data that it has the 4byte prefix and the rest divisible by 32 bytes
if len(calldata) < 4 {
return nil, fmt.Errorf("invalid call data, incomplete method signature (%d bytes < 4)", len(calldata))
}
sigdata := calldata[:4]
argdata := calldata[4:]
if len(argdata)%32 != 0 {
return nil, fmt.Errorf("invalid call data; length should be a multiple of 32 bytes (was %d)", len(argdata))
}
// Validate the called method and upack the call data accordingly
abispec, err := abi.JSON(strings.NewReader(abidata))
if err != nil {
return nil, fmt.Errorf("invalid method signature (%s): %v", abidata, err)
}
method, err := abispec.MethodById(sigdata)
if err != nil {
return nil, err
}
values, err := method.Inputs.UnpackValues(argdata)
if err != nil {
return nil, err
}
// Everything valid, assemble the call infos for the signer
decoded := decodedCallData{signature: method.Sig(), name: method.Name}
for i := 0; i < len(method.Inputs); i++ {
decoded.inputs = append(decoded.inputs, decodedArgument{
soltype: method.Inputs[i],
value: values[i],
})
}
// We're finished decoding the data. At this point, we encode the decoded data
// to see if it matches with the original data. If we didn't do that, it would
// be possible to stuff extra data into the arguments, which is not detected
// by merely decoding the data.
encoded, err := method.Inputs.PackValues(values)
if err != nil {
return nil, err
}
if !bytes.Equal(encoded, argdata) {
was := common.Bytes2Hex(encoded)
exp := common.Bytes2Hex(argdata)
return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig())
}
return &decoded, nil
}

View file

@ -1,24 +1,22 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
// This file is part of the go-ethereum library.
//
// 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 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.
//
// go-ethereum is distributed in the hope that it will be useful,
// 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 General Public License for more details.
// GNU Lesser 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/>.
// 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 core
package fourbyte
import (
"fmt"
"io/ioutil"
"math/big"
"reflect"
"strings"
@ -29,7 +27,6 @@ import (
)
func verify(t *testing.T, jsondata, calldata string, exp []interface{}) {
abispec, err := abi.JSON(strings.NewReader(jsondata))
if err != nil {
t.Fatal(err)
@ -53,6 +50,7 @@ func verify(t *testing.T, jsondata, calldata string, exp []interface{}) {
}
}
}
func TestNewUnpacker(t *testing.T) {
type unpackTest struct {
jsondata string
@ -96,11 +94,9 @@ func TestNewUnpacker(t *testing.T) {
for _, c := range testcases {
verify(t, c.jsondata, c.calldata, c.exp)
}
}
func TestCalldataDecoding(t *testing.T) {
// send(uint256) : a52c101e
// compareAndApprove(address,uint256,uint256) : 751e1079
// issue(address[],uint256) : 42958b54
@ -111,7 +107,7 @@ func TestCalldataDecoding(t *testing.T) {
{"type":"function","name":"issue","inputs":[{"name":"a","type":"address[]"},{"name":"a","type":"uint256"}]},
{"type":"function","name":"sam","inputs":[{"name":"a","type":"bytes"},{"name":"a","type":"bool"},{"name":"a","type":"uint256[]"}]}
]`
//Expected failures
// Expected failures
for i, hexdata := range []string{
"a52c101e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042",
"a52c101e000000000000000000000000000000000000000000000000000000000000001200",
@ -122,9 +118,9 @@ func TestCalldataDecoding(t *testing.T) {
// Too short
"751e10790000000000000000000000000000000000000000000000000000000000000012",
"751e1079FFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
//Not valid multiple of 32
// Not valid multiple of 32
"deadbeef00000000000000000000000000000000000000000000000000000000000000",
//Too short 'issue'
// Too short 'issue'
"42958b5400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042",
// Too short compareAndApprove
"a52c101e00ff0000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042",
@ -137,7 +133,7 @@ func TestCalldataDecoding(t *testing.T) {
t.Errorf("test %d: expected decoding to fail: %s", i, hexdata)
}
}
//Expected success
// Expected success
for i, hexdata := range []string{
// From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
"a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
@ -147,7 +143,7 @@ func TestCalldataDecoding(t *testing.T) {
"42958b54" +
// start of dynamic type
"0000000000000000000000000000000000000000000000000000000000000040" +
//uint256
// uint256
"0000000000000000000000000000000000000000000000000000000000000001" +
// length of array
"0000000000000000000000000000000000000000000000000000000000000002" +
@ -162,79 +158,7 @@ func TestCalldataDecoding(t *testing.T) {
}
}
func TestSelectorUnmarshalling(t *testing.T) {
var (
db *AbiDb
err error
abistring []byte
abistruct abi.ABI
)
db, err = NewAbiDBFromFile("../../cmd/clef/4byte.json")
if err != nil {
t.Fatal(err)
}
fmt.Printf("DB size %v\n", db.Size())
for id, selector := range db.db {
abistring, err = MethodSelectorToAbi(selector)
if err != nil {
t.Error(err)
return
}
abistruct, err = abi.JSON(strings.NewReader(string(abistring)))
if err != nil {
t.Error(err)
return
}
m, err := abistruct.MethodById(common.Hex2Bytes(id[2:]))
if err != nil {
t.Error(err)
return
}
if m.Sig() != selector {
t.Errorf("Expected equality: %v != %v", m.Sig(), selector)
}
}
}
func TestCustomABI(t *testing.T) {
d, err := ioutil.TempDir("", "signer-4byte-test")
if err != nil {
t.Fatal(err)
}
filename := fmt.Sprintf("%s/4byte_custom.json", d)
abidb, err := NewAbiDBFromFiles("../../cmd/clef/4byte.json", filename)
if err != nil {
t.Fatal(err)
}
// Now we'll remove all existing signatures
abidb.db = make(map[string]string)
calldata := common.Hex2Bytes("a52c101edeadbeef")
_, err = abidb.LookupMethodSelector(calldata)
if err == nil {
t.Fatalf("Should not find a match on empty db")
}
if err = abidb.AddSignature("send(uint256)", calldata); err != nil {
t.Fatalf("Failed to save file: %v", err)
}
_, err = abidb.LookupMethodSelector(calldata)
if err != nil {
t.Fatalf("Should find a match for abi signature, got: %v", err)
}
//Check that it wrote to file
abidb2, err := NewAbiDBFromFile(filename)
if err != nil {
t.Fatalf("Failed to create new abidb: %v", err)
}
_, err = abidb2.LookupMethodSelector(calldata)
if err != nil {
t.Fatalf("Save failed: should find a match for abi signature after loading from disk")
}
}
func TestMaliciousAbiStrings(t *testing.T) {
func TestMaliciousABIStrings(t *testing.T) {
tests := []string{
"func(uint256,uint256,[]uint256)",
"func(uint256,uint256,uint256,)",
@ -242,7 +166,7 @@ func TestMaliciousAbiStrings(t *testing.T) {
}
data := common.Hex2Bytes("4401a6e40000000000000000000000000000000000000000000000000000000000000012")
for i, tt := range tests {
_, err := testSelector(tt, data)
_, err := verifySelector(tt, data)
if err == nil {
t.Errorf("test %d: expected error for selector '%v'", i, tt)
}

143
signer/fourbyte/fourbyte.go Normal file
View file

@ -0,0 +1,143 @@
// Copyright 2019 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/>.
//go:generate go-bindata -nometadata -o 4byte.go -pkg fourbyte 4byte.json
//go:generate gofmt -s -w 4byte.go
// Package fourbyte contains the 4byte database.
package fourbyte
import (
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
// Database is a 4byte database with the possibility of maintaining an immutable
// set (embedded) into the process and a mutable set (loaded and written to file).
type Database struct {
embedded map[string]string
custom map[string]string
customPath string
}
// newEmpty exists for testing purposes.
func newEmpty() *Database {
return &Database{
embedded: make(map[string]string),
custom: make(map[string]string),
}
}
// New loads the standard signature database embedded in the package.
func New() (*Database, error) {
return NewWithFile("")
}
// NewFromFile loads signature database from file, and errors if the file is not
// valid JSON. The constructor does no other validation of contents. This method
// does not load the embedded 4byte database.
//
// The provided path will be used to write new values into if they are submitted
// via the API.
func NewFromFile(path string) (*Database, error) {
raw, err := os.Open(path)
if err != nil {
return nil, err
}
defer raw.Close()
db := newEmpty()
if err := json.NewDecoder(raw).Decode(&db.embedded); err != nil {
return nil, err
}
return db, nil
}
// NewWithFile loads both the standard signature database (embedded resource
// file) as well as a custom database. The latter will be used to write new
// values into if they are submitted via the API.
func NewWithFile(path string) (*Database, error) {
db := &Database{make(map[string]string), make(map[string]string), path}
db.customPath = path
blob, err := Asset("4byte.json")
if err != nil {
return nil, err
}
if err := json.Unmarshal(blob, &db.embedded); err != nil {
return nil, err
}
// Custom file may not exist. Will be created during save, if needed.
if _, err := os.Stat(path); err == nil {
if blob, err = ioutil.ReadFile(path); err != nil {
return nil, err
}
if err := json.Unmarshal(blob, &db.custom); err != nil {
return nil, err
}
}
return db, nil
}
// Size returns the number of 4byte entries in the embedded and custom datasets.
func (db *Database) Size() (int, int) {
return len(db.embedded), len(db.custom)
}
// Selector checks the given 4byte ID against the known ABI methods.
//
// This method does not validate the match, it's assumed the caller will do.
func (db *Database) Selector(id []byte) (string, error) {
if len(id) < 4 {
return "", fmt.Errorf("expected 4-byte id, got %d", len(id))
}
sig := hex.EncodeToString(id[:4])
if selector, exists := db.embedded[sig]; exists {
return selector, nil
}
if selector, exists := db.custom[sig]; exists {
return selector, nil
}
return "", fmt.Errorf("signature %v not found", sig)
}
// AddSelector inserts a new 4byte entry into the database. If custom database
// saving is enabled, the new dataset is also persisted to disk.
//
// Node, this method does _not_ validate the correctness of the data. It assumes
// the caller has already done so.
func (db *Database) AddSelector(selector string, data []byte) error {
// If the selector is already known, skip duplicating it
if len(data) < 4 {
return nil
}
if _, err := db.Selector(data[:4]); err == nil {
return nil
}
// Inject the custom selector into the database and persist if needed
db.custom[hex.EncodeToString(data[:4])] = selector
if db.customPath == "" {
return nil
}
blob, err := json.Marshal(db.custom)
if err != nil {
return err
}
return ioutil.WriteFile(db.customPath, blob, 0600)
}

View file

@ -0,0 +1,91 @@
// Copyright 2018 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 fourbyte
import (
"fmt"
"io/ioutil"
"strings"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)
// Tests that all the selectors contained in the 4byte database are valid.
func TestEmbeddedDatabase(t *testing.T) {
db, err := New()
if err != nil {
t.Fatal(err)
}
for id, selector := range db.embedded {
abistring, err := parseSelector(selector)
if err != nil {
t.Errorf("Failed to convert selector to ABI: %v", err)
continue
}
abistruct, err := abi.JSON(strings.NewReader(string(abistring)))
if err != nil {
t.Errorf("Failed to parse ABI: %v", err)
continue
}
m, err := abistruct.MethodById(common.Hex2Bytes(id))
if err != nil {
t.Errorf("Failed to get method by id (%s): %v", id, err)
continue
}
if m.Sig() != selector {
t.Errorf("Selector mismatch: have %v, want %v", m.Sig(), selector)
}
}
}
// Tests that custom 4byte datasets can be handled too.
func TestCustomDatabase(t *testing.T) {
// Create a new custom 4byte database with no embedded component
tmpdir, err := ioutil.TempDir("", "signer-4byte-test")
if err != nil {
t.Fatal(err)
}
filename := fmt.Sprintf("%s/4byte_custom.json", tmpdir)
db, err := NewWithFile(filename)
if err != nil {
t.Fatal(err)
}
db.embedded = make(map[string]string)
// Ensure the database is empty, insert and verify
calldata := common.Hex2Bytes("a52c101edeadbeef")
if _, err = db.Selector(calldata); err == nil {
t.Fatalf("Should not find a match on empty database")
}
if err = db.AddSelector("send(uint256)", calldata); err != nil {
t.Fatalf("Failed to save file: %v", err)
}
if _, err = db.Selector(calldata); err != nil {
t.Fatalf("Failed to find a match for abi signature: %v", err)
}
// Check that the file as persisted to disk by creating a new instance
db2, err := NewFromFile(filename)
if err != nil {
t.Fatalf("Failed to create new abidb: %v", err)
}
if _, err = db2.Selector(calldata); err != nil {
t.Fatalf("Failed to find a match for persisted abi signature: %v", err)
}
}

View file

@ -0,0 +1,117 @@
// Copyright 2018 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 fourbyte
import (
"bytes"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/signer/core"
)
// ValidateTransaction does a number of checks on the supplied transaction, and
// returns either a list of warnings, or an error (indicating that the transaction
// should be immediately rejected).
func (db *Database) ValidateTransaction(selector *string, tx *core.SendTxArgs) (*core.ValidationMessages, error) {
messages := new(core.ValidationMessages)
// Prevent accidental erroneous usage of both 'input' and 'data' (show stopper)
if tx.Data != nil && tx.Input != nil && !bytes.Equal(*tx.Data, *tx.Input) {
return nil, errors.New(`ambiguous request: both "data" and "input" are set and are not identical`)
}
// Place data on 'data', and nil 'input'
var data []byte
if tx.Input != nil {
tx.Data = tx.Input
tx.Input = nil
}
if tx.Data != nil {
data = *tx.Data
}
// Contract creation doesn't validate call data, handle first
if tx.To == nil {
// Contract creation should contain sufficient data to deploy a contract. A
// typical error is omitting sender due to some quirk in the javascript call
// e.g. https://github.com/ethereum/go-ethereum/issues/16106.
if len(data) == 0 {
// Prevent sending ether into black hole (show stopper)
if tx.Value.ToInt().Cmp(big.NewInt(0)) > 0 {
return nil, errors.New("tx will create contract with value but empty code")
}
// No value submitted at least, critically Warn, but don't blow up
messages.Crit("Transaction will create contract with empty code")
} else if len(data) < 40 { // arbitrary heuristic limit
messages.Warn(fmt.Sprintf("Transaction will will create contract, but payload is suspiciously small (%d bytes)", len(data)))
}
// Method selector should be nil for contract creation
if selector != nil {
messages.Warn("Transaction will create contract, but method selector supplied, indicating intent to call a method")
}
return messages, nil
}
// Not a contract creation, validate as a plain transaction
if !tx.To.ValidChecksum() {
messages.Warn("Invalid checksum on recipient address")
}
if bytes.Equal(tx.To.Address().Bytes(), common.Address{}.Bytes()) {
messages.Crit("Transaction recipient is the zero address")
}
// Semantic fields validated, try to make heads or tails of the call data
db.validateCallData(selector, data, messages)
return messages, nil
}
// validateCallData checks if the ABI call-data + method selector (if given) can
// be parsed and seems to match.
func (db *Database) validateCallData(selector *string, data []byte, messages *core.ValidationMessages) {
// If the data is empty, we have a plain value transfer, nothing more to do
if len(data) == 0 {
return
}
// Validate the call data that it has the 4byte prefix and the rest divisible by 32 bytes
if len(data) < 4 {
messages.Warn("Transaction data is not valid ABI (missing the 4 byte call prefix)")
return
}
if n := len(data) - 4; n%32 != 0 {
messages.Warn(fmt.Sprintf("Transaction data is not valid ABI (length should be a multiple of 32 (was %d))", n))
}
// If a custom method selector was provided, validate with that
if selector != nil {
if info, err := verifySelector(*selector, data); err != nil {
messages.Warn(fmt.Sprintf("Transaction contains data, but provided ABI signature could not be matched: %v", err))
} else {
messages.Info(info.String())
db.AddSelector(*selector, data[:4])
}
return
}
// No method selector was provided, check the database for embedded ones
embedded, err := db.Selector(data[:4])
if err != nil {
messages.Warn(fmt.Sprintf("Transaction contains data, but the ABI signature could not be found: %v", err))
return
}
if info, err := verifySelector(embedded, data); err != nil {
messages.Warn(fmt.Sprintf("Transaction contains data, but provided ABI signature could not be varified: %v", err))
} else {
messages.Info(info.String())
}
}

View file

@ -0,0 +1,137 @@
// Copyright 2018 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 fourbyte
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/signer/core"
)
func mixAddr(a string) (*common.MixedcaseAddress, error) {
return common.NewMixedcaseAddressFromString(a)
}
func toHexBig(h string) hexutil.Big {
b := big.NewInt(0).SetBytes(common.FromHex(h))
return hexutil.Big(*b)
}
func toHexUint(h string) hexutil.Uint64 {
b := big.NewInt(0).SetBytes(common.FromHex(h))
return hexutil.Uint64(b.Uint64())
}
func dummyTxArgs(t txtestcase) *core.SendTxArgs {
to, _ := mixAddr(t.to)
from, _ := mixAddr(t.from)
n := toHexUint(t.n)
gas := toHexUint(t.g)
gasPrice := toHexBig(t.gp)
value := toHexBig(t.value)
var (
data, input *hexutil.Bytes
)
if t.d != "" {
a := hexutil.Bytes(common.FromHex(t.d))
data = &a
}
if t.i != "" {
a := hexutil.Bytes(common.FromHex(t.i))
input = &a
}
return &core.SendTxArgs{
From: *from,
To: to,
Value: value,
Nonce: n,
GasPrice: gasPrice,
Gas: gas,
Data: data,
Input: input,
}
}
type txtestcase struct {
from, to, n, g, gp, value, d, i string
expectErr bool
numMessages int
}
func TestTransactionValidation(t *testing.T) {
var (
// use empty db, there are other tests for the abi-specific stuff
db = newEmpty()
)
testcases := []txtestcase{
// Invalid to checksum
{from: "000000000000000000000000000000000000dead", to: "000000000000000000000000000000000000dead",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1},
// valid 0x000000000000000000000000000000000000dEaD
{from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 0},
// conflicting input and data
{from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", i: "0x02", expectErr: true},
// Data can't be parsed
{from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x0102", numMessages: 1},
// Data (on Input) can't be parsed
{from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", i: "0x0102", numMessages: 1},
// Send to 0
{from: "000000000000000000000000000000000000dead", to: "0x0000000000000000000000000000000000000000",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1},
// Create empty contract (no value)
{from: "000000000000000000000000000000000000dead", to: "",
n: "0x01", g: "0x20", gp: "0x40", value: "0x00", numMessages: 1},
// Create empty contract (with value)
{from: "000000000000000000000000000000000000dead", to: "",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", expectErr: true},
// Small payload for create
{from: "000000000000000000000000000000000000dead", to: "",
n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", numMessages: 1},
}
for i, test := range testcases {
msgs, err := db.ValidateTransaction(nil, dummyTxArgs(test))
if err == nil && test.expectErr {
t.Errorf("Test %d, expected error", i)
for _, msg := range msgs.Messages {
t.Logf("* %s: %s", msg.Typ, msg.Message)
}
}
if err != nil && !test.expectErr {
t.Errorf("Test %d, unexpected error: %v", i, err)
}
if err == nil {
got := len(msgs.Messages)
if got != test.numMessages {
for _, msg := range msgs.Messages {
t.Logf("* %s: %s", msg.Typ, msg.Message)
}
t.Errorf("Test %d, expected %d messages, got %d", i, test.numMessages, got)
} else {
//Debug printout, remove later
for _, msg := range msgs.Messages {
t.Logf("* [%d] %s: %s", i, msg.Typ, msg.Message)
}
t.Log()
}
}
}
}

View file

@ -25,6 +25,8 @@ import (
"sort"
"testing"
"github.com/ethereum/go-ethereum/swarm/testutil"
"github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
@ -43,7 +45,13 @@ func serverFunc(api *api.API) swarmhttp.TestServer {
func TestClientUploadDownloadRaw(t *testing.T) {
testClientUploadDownloadRaw(false, t)
}
func TestClientUploadDownloadRawEncrypted(t *testing.T) {
if testutil.RaceEnabled {
t.Skip("flaky with -race on Travis")
// See: https://github.com/ethersphere/go-ethereum/issues/1254
}
testClientUploadDownloadRaw(true, t)
}

View file

@ -33,44 +33,45 @@ type TestServer interface {
}
func NewTestSwarmServer(t *testing.T, serverFunc func(*api.API) TestServer, resolver api.Resolver) *TestSwarmServer {
dir, err := ioutil.TempDir("", "swarm-storage-test")
swarmDir, err := ioutil.TempDir("", "swarm-storage-test")
if err != nil {
t.Fatal(err)
}
storeparams := storage.NewDefaultLocalStoreParams()
storeparams.DbCapacity = 5000000
storeparams.CacheCapacity = 5000
storeparams.Init(dir)
localStore, err := storage.NewLocalStore(storeparams, nil)
storeParams := storage.NewDefaultLocalStoreParams()
storeParams.DbCapacity = 5000000
storeParams.CacheCapacity = 5000
storeParams.Init(swarmDir)
localStore, err := storage.NewLocalStore(storeParams, nil)
if err != nil {
os.RemoveAll(dir)
os.RemoveAll(swarmDir)
t.Fatal(err)
}
fileStore := storage.NewFileStore(localStore, storage.NewFileStoreParams())
// Swarm feeds test setup
feedsDir, err := ioutil.TempDir("", "swarm-feeds-test")
if err != nil {
t.Fatal(err)
}
rhparams := &feed.HandlerParams{}
rh, err := feed.NewTestHandler(feedsDir, rhparams)
feeds, err := feed.NewTestHandler(feedsDir, &feed.HandlerParams{})
if err != nil {
t.Fatal(err)
}
a := api.NewAPI(fileStore, resolver, rh.Handler, nil)
srv := httptest.NewServer(serverFunc(a))
swarmApi := api.NewAPI(fileStore, resolver, feeds.Handler, nil)
apiServer := httptest.NewServer(serverFunc(swarmApi))
tss := &TestSwarmServer{
Server: srv,
Server: apiServer,
FileStore: fileStore,
dir: dir,
dir: swarmDir,
Hasher: storage.MakeHashFunc(storage.DefaultHash)(),
cleanup: func() {
srv.Close()
rh.Close()
os.RemoveAll(dir)
apiServer.Close()
fileStore.Close()
feeds.Close()
os.RemoveAll(swarmDir)
os.RemoveAll(feedsDir)
},
CurrentTime: 42,

View file

@ -323,6 +323,12 @@ func TestLDBStoreCollectGarbage(t *testing.T) {
initialCap := defaultMaxGCRound / 100
cap := initialCap / 2
t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage)
if testutil.RaceEnabled {
t.Skip("only the simplest case run as others are flaky with race")
// Note: some tests fail consistently and even locally with `-race`
}
t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage)
// at max round