Merge pull request #1 from ethereum/master

Pull latest changes
This commit is contained in:
ledgerwatch 2018-07-10 20:38:41 +01:00 committed by GitHub
commit b9f1dc852c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
281 changed files with 42156 additions and 10752 deletions

20
.github/CODEOWNERS vendored
View file

@ -9,4 +9,24 @@ les/ @zsfelfoldi
light/ @zsfelfoldi light/ @zsfelfoldi
mobile/ @karalabe mobile/ @karalabe
p2p/ @fjl @zsfelfoldi p2p/ @fjl @zsfelfoldi
swarm/bmt @zelig
swarm/dev @lmars
swarm/fuse @jmozah @holisticode
swarm/grafana_dashboards @nonsense
swarm/metrics @nonsense @holisticode
swarm/multihash @nolash
swarm/network/bitvector @zelig @janos @gbalint
swarm/network/priorityqueue @zelig @janos @gbalint
swarm/network/simulations @zelig
swarm/network/stream @janos @zelig @gbalint @holisticode @justelad
swarm/network/stream/intervals @janos
swarm/network/stream/testing @zelig
swarm/pot @zelig
swarm/pss @nolash @zelig @nonsense
swarm/services @zelig
swarm/state @justelad
swarm/storage/encryption @gbalint @zelig @nagydani
swarm/storage/mock @janos
swarm/storage/mru @nolash
swarm/testutil @lmars
whisper/ @gballet @gluk256 whisper/ @gballet @gluk256

View file

@ -152,10 +152,10 @@ matrix:
- export GOPATH=$HOME/go - export GOPATH=$HOME/go
script: script:
# Build the Android archive and upload it to Maven Central and Azure # Build the Android archive and upload it to Maven Central and Azure
- curl https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip -o android-ndk-r16b.zip - curl https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip -o android-ndk-r17b.zip
- unzip -q android-ndk-r16b.zip && rm android-ndk-r16b.zip - unzip -q android-ndk-r17b.zip && rm android-ndk-r17b.zip
- mv android-ndk-r16b $HOME - mv android-ndk-r17b $HOME
- export ANDROID_NDK=$HOME/android-ndk-r16b - export ANDROID_NDK=$HOME/android-ndk-r17b
- mkdir -p $GOPATH/src/github.com/ethereum - mkdir -p $GOPATH/src/github.com/ethereum
- ln -s `pwd` $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum

View file

@ -1 +1 @@
1.8.12 1.8.13

View file

@ -31,29 +31,14 @@ var (
uint16T = reflect.TypeOf(uint16(0)) uint16T = reflect.TypeOf(uint16(0))
uint32T = reflect.TypeOf(uint32(0)) uint32T = reflect.TypeOf(uint32(0))
uint64T = reflect.TypeOf(uint64(0)) uint64T = reflect.TypeOf(uint64(0))
intT = reflect.TypeOf(int(0))
int8T = reflect.TypeOf(int8(0)) int8T = reflect.TypeOf(int8(0))
int16T = reflect.TypeOf(int16(0)) int16T = reflect.TypeOf(int16(0))
int32T = reflect.TypeOf(int32(0)) int32T = reflect.TypeOf(int32(0))
int64T = reflect.TypeOf(int64(0)) int64T = reflect.TypeOf(int64(0))
addressT = reflect.TypeOf(common.Address{}) addressT = reflect.TypeOf(common.Address{})
intTS = reflect.TypeOf([]int(nil))
int8TS = reflect.TypeOf([]int8(nil))
int16TS = reflect.TypeOf([]int16(nil))
int32TS = reflect.TypeOf([]int32(nil))
int64TS = reflect.TypeOf([]int64(nil))
) )
// U256 converts a big Int into a 256bit EVM number. // U256 converts a big Int into a 256bit EVM number.
func U256(n *big.Int) []byte { func U256(n *big.Int) []byte {
return math.PaddedBigBytes(math.U256(n), 32) return math.PaddedBigBytes(math.U256(n), 32)
} }
// checks whether the given reflect value is signed. This also works for slices with a number type
func isSigned(v reflect.Value) bool {
switch v.Type() {
case intTS, int8TS, int16TS, int32TS, int64TS, intT, int8T, int16T, int32T, int64T:
return true
}
return false
}

View file

@ -19,7 +19,6 @@ package abi
import ( import (
"bytes" "bytes"
"math/big" "math/big"
"reflect"
"testing" "testing"
) )
@ -32,13 +31,3 @@ func TestNumberTypes(t *testing.T) {
t.Errorf("expected %x got %x", ubytes, unsigned) t.Errorf("expected %x got %x", ubytes, unsigned)
} }
} }
func TestSigned(t *testing.T) {
if isSigned(reflect.ValueOf(uint(10))) {
t.Error("signed")
}
if !isSigned(reflect.ValueOf(int(10))) {
t.Error("not signed")
}
}

View file

@ -36,7 +36,7 @@ func Type(msg proto.Message) uint16 {
} }
// Name returns the friendly message type name of a specific protocol buffer // Name returns the friendly message type name of a specific protocol buffer
// type numbers. // type number.
func Name(kind uint16) string { func Name(kind uint16) string {
name := MessageType_name[int32(kind)] name := MessageType_name[int32(kind)]
if len(name) < 12 { if len(name) < 12 {

View file

@ -302,7 +302,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
for i, component := range derivationPath { for i, component := range derivationPath {
binary.BigEndian.PutUint32(path[1+4*i:], component) binary.BigEndian.PutUint32(path[1+4*i:], component)
} }
// Create the transaction RLP based on whether legacy or EIP155 signing was requeste // Create the transaction RLP based on whether legacy or EIP155 signing was requested
var ( var (
txrlp []byte txrlp []byte
err error err error

View file

@ -1,560 +0,0 @@
// Copyright 2017 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 bmt provides a binary merkle tree implementation
package bmt
import (
"fmt"
"hash"
"io"
"strings"
"sync"
"sync/atomic"
)
/*
Binary Merkle Tree Hash is a hash function over arbitrary datachunks of limited size
It is defined as the root hash of the binary merkle tree built over fixed size segments
of the underlying chunk using any base hash function (e.g keccak 256 SHA3)
It is used as the chunk hash function in swarm which in turn is the basis for the
128 branching swarm hash http://swarm-guide.readthedocs.io/en/latest/architecture.html#swarm-hash
The BMT is optimal for providing compact inclusion proofs, i.e. prove that a
segment is a substring of a chunk starting at a particular offset
The size of the underlying segments is fixed at 32 bytes (called the resolution
of the BMT hash), the EVM word size to optimize for on-chain BMT verification
as well as the hash size optimal for inclusion proofs in the merkle tree of the swarm hash.
Two implementations are provided:
* RefHasher is optimized for code simplicity and meant as a reference implementation
* Hasher is optimized for speed taking advantage of concurrency with minimalistic
control structure to coordinate the concurrent routines
It implements the ChunkHash interface as well as the go standard hash.Hash interface
*/
const (
// DefaultSegmentCount is the maximum number of segments of the underlying chunk
DefaultSegmentCount = 128 // Should be equal to storage.DefaultBranches
// DefaultPoolSize is the maximum number of bmt trees used by the hashers, i.e,
// the maximum number of concurrent BMT hashing operations performed by the same hasher
DefaultPoolSize = 8
)
// BaseHasher is a hash.Hash constructor function used for the base hash of the BMT.
type BaseHasher func() hash.Hash
// Hasher a reusable hasher for fixed maximum size chunks representing a BMT
// implements the hash.Hash interface
// reuse pool of Tree-s for amortised memory allocation and resource control
// supports order-agnostic concurrent segment writes
// as well as sequential read and write
// can not be called concurrently on more than one chunk
// can be further appended after Sum
// Reset gives back the Tree to the pool and guaranteed to leave
// the tree and itself in a state reusable for hashing a new chunk
type Hasher struct {
pool *TreePool // BMT resource pool
bmt *Tree // prebuilt BMT resource for flowcontrol and proofs
blocksize int // segment size (size of hash) also for hash.Hash
count int // segment count
size int // for hash.Hash same as hashsize
cur int // cursor position for rightmost currently open chunk
segment []byte // the rightmost open segment (not complete)
depth int // index of last level
result chan []byte // result channel
hash []byte // to record the result
max int32 // max segments for SegmentWriter interface
blockLength []byte // The block length that needes to be added in Sum
}
// New creates a reusable Hasher
// implements the hash.Hash interface
// pulls a new Tree from a resource pool for hashing each chunk
func New(p *TreePool) *Hasher {
return &Hasher{
pool: p,
depth: depth(p.SegmentCount),
size: p.SegmentSize,
blocksize: p.SegmentSize,
count: p.SegmentCount,
result: make(chan []byte),
}
}
// Node is a reuseable segment hasher representing a node in a BMT
// it allows for continued writes after a Sum
// and is left in completely reusable state after Reset
type Node struct {
level, index int // position of node for information/logging only
initial bool // first and last node
root bool // whether the node is root to a smaller BMT
isLeft bool // whether it is left side of the parent double segment
unbalanced bool // indicates if a node has only the left segment
parent *Node // BMT connections
state int32 // atomic increment impl concurrent boolean toggle
left, right []byte
}
// NewNode constructor for segment hasher nodes in the BMT
func NewNode(level, index int, parent *Node) *Node {
return &Node{
parent: parent,
level: level,
index: index,
initial: index == 0,
isLeft: index%2 == 0,
}
}
// TreePool provides a pool of Trees used as resources by Hasher
// a Tree popped from the pool is guaranteed to have clean state
// for hashing a new chunk
// Hasher Reset releases the Tree to the pool
type TreePool struct {
lock sync.Mutex
c chan *Tree
hasher BaseHasher
SegmentSize int
SegmentCount int
Capacity int
count int
}
// NewTreePool creates a Tree pool with hasher, segment size, segment count and capacity
// on GetTree it reuses free Trees or creates a new one if size is not reached
func NewTreePool(hasher BaseHasher, segmentCount, capacity int) *TreePool {
return &TreePool{
c: make(chan *Tree, capacity),
hasher: hasher,
SegmentSize: hasher().Size(),
SegmentCount: segmentCount,
Capacity: capacity,
}
}
// Drain drains the pool until it has no more than n resources
func (p *TreePool) Drain(n int) {
p.lock.Lock()
defer p.lock.Unlock()
for len(p.c) > n {
<-p.c
p.count--
}
}
// Reserve is blocking until it returns an available Tree
// it reuses free Trees or creates a new one if size is not reached
func (p *TreePool) Reserve() *Tree {
p.lock.Lock()
defer p.lock.Unlock()
var t *Tree
if p.count == p.Capacity {
return <-p.c
}
select {
case t = <-p.c:
default:
t = NewTree(p.hasher, p.SegmentSize, p.SegmentCount)
p.count++
}
return t
}
// Release gives back a Tree to the pool.
// This Tree is guaranteed to be in reusable state
// does not need locking
func (p *TreePool) Release(t *Tree) {
p.c <- t // can never fail but...
}
// Tree is a reusable control structure representing a BMT
// organised in a binary tree
// Hasher uses a TreePool to pick one for each chunk hash
// the Tree is 'locked' while not in the pool
type Tree struct {
leaves []*Node
}
// Draw draws the BMT (badly)
func (t *Tree) Draw(hash []byte, d int) string {
var left, right []string
var anc []*Node
for i, n := range t.leaves {
left = append(left, fmt.Sprintf("%v", hashstr(n.left)))
if i%2 == 0 {
anc = append(anc, n.parent)
}
right = append(right, fmt.Sprintf("%v", hashstr(n.right)))
}
anc = t.leaves
var hashes [][]string
for l := 0; len(anc) > 0; l++ {
var nodes []*Node
hash := []string{""}
for i, n := range anc {
hash = append(hash, fmt.Sprintf("%v|%v", hashstr(n.left), hashstr(n.right)))
if i%2 == 0 && n.parent != nil {
nodes = append(nodes, n.parent)
}
}
hash = append(hash, "")
hashes = append(hashes, hash)
anc = nodes
}
hashes = append(hashes, []string{"", fmt.Sprintf("%v", hashstr(hash)), ""})
total := 60
del := " "
var rows []string
for i := len(hashes) - 1; i >= 0; i-- {
var textlen int
hash := hashes[i]
for _, s := range hash {
textlen += len(s)
}
if total < textlen {
total = textlen + len(hash)
}
delsize := (total - textlen) / (len(hash) - 1)
if delsize > len(del) {
delsize = len(del)
}
row := fmt.Sprintf("%v: %v", len(hashes)-i-1, strings.Join(hash, del[:delsize]))
rows = append(rows, row)
}
rows = append(rows, strings.Join(left, " "))
rows = append(rows, strings.Join(right, " "))
return strings.Join(rows, "\n") + "\n"
}
// NewTree initialises the Tree by building up the nodes of a BMT
// segment size is stipulated to be the size of the hash
// segmentCount needs to be positive integer and does not need to be
// a power of two and can even be an odd number
// segmentSize * segmentCount determines the maximum chunk size
// hashed using the tree
func NewTree(hasher BaseHasher, segmentSize, segmentCount int) *Tree {
n := NewNode(0, 0, nil)
n.root = true
prevlevel := []*Node{n}
// iterate over levels and creates 2^level nodes
level := 1
count := 2
for d := 1; d <= depth(segmentCount); d++ {
nodes := make([]*Node, count)
for i := 0; i < len(nodes); i++ {
parent := prevlevel[i/2]
t := NewNode(level, i, parent)
nodes[i] = t
}
prevlevel = nodes
level++
count *= 2
}
// the datanode level is the nodes on the last level where
return &Tree{
leaves: prevlevel,
}
}
// methods needed by hash.Hash
// Size returns the size
func (h *Hasher) Size() int {
return h.size
}
// BlockSize returns the block size
func (h *Hasher) BlockSize() int {
return h.blocksize
}
// Sum returns the hash of the buffer
// hash.Hash interface Sum method appends the byte slice to the underlying
// data before it calculates and returns the hash of the chunk
func (h *Hasher) Sum(b []byte) (r []byte) {
t := h.bmt
i := h.cur
n := t.leaves[i]
j := i
// must run strictly before all nodes calculate
// datanodes are guaranteed to have a parent
if len(h.segment) > h.size && i > 0 && n.parent != nil {
n = n.parent
} else {
i *= 2
}
d := h.finalise(n, i)
h.writeSegment(j, h.segment, d)
c := <-h.result
h.releaseTree()
// sha3(length + BMT(pure_chunk))
if h.blockLength == nil {
return c
}
res := h.pool.hasher()
res.Reset()
res.Write(h.blockLength)
res.Write(c)
return res.Sum(nil)
}
// Hasher implements the SwarmHash interface
// Hash waits for the hasher result and returns it
// caller must call this on a BMT Hasher being written to
func (h *Hasher) Hash() []byte {
return <-h.result
}
// Hasher implements the io.Writer interface
// Write fills the buffer to hash
// with every full segment complete launches a hasher go routine
// that shoots up the BMT
func (h *Hasher) Write(b []byte) (int, error) {
l := len(b)
if l <= 0 {
return 0, nil
}
s := h.segment
i := h.cur
count := (h.count + 1) / 2
need := h.count*h.size - h.cur*2*h.size
size := h.size
if need > size {
size *= 2
}
if l < need {
need = l
}
// calculate missing bit to complete current open segment
rest := size - len(s)
if need < rest {
rest = need
}
s = append(s, b[:rest]...)
need -= rest
// read full segments and the last possibly partial segment
for need > 0 && i < count-1 {
// push all finished chunks we read
h.writeSegment(i, s, h.depth)
need -= size
if need < 0 {
size += need
}
s = b[rest : rest+size]
rest += size
i++
}
h.segment = s
h.cur = i
// otherwise, we can assume len(s) == 0, so all buffer is read and chunk is not yet full
return l, nil
}
// Hasher implements the io.ReaderFrom interface
// ReadFrom reads from io.Reader and appends to the data to hash using Write
// it reads so that chunk to hash is maximum length or reader reaches EOF
// caller must Reset the hasher prior to call
func (h *Hasher) ReadFrom(r io.Reader) (m int64, err error) {
bufsize := h.size*h.count - h.size*h.cur - len(h.segment)
buf := make([]byte, bufsize)
var read int
for {
var n int
n, err = r.Read(buf)
read += n
if err == io.EOF || read == len(buf) {
hash := h.Sum(buf[:n])
if read == len(buf) {
err = NewEOC(hash)
}
break
}
if err != nil {
break
}
n, err = h.Write(buf[:n])
if err != nil {
break
}
}
return int64(read), err
}
// Reset needs to be called before writing to the hasher
func (h *Hasher) Reset() {
h.getTree()
h.blockLength = nil
}
// Hasher implements the SwarmHash interface
// ResetWithLength needs to be called before writing to the hasher
// the argument is supposed to be the byte slice binary representation of
// the length of the data subsumed under the hash
func (h *Hasher) ResetWithLength(l []byte) {
h.Reset()
h.blockLength = l
}
// Release gives back the Tree to the pool whereby it unlocks
// it resets tree, segment and index
func (h *Hasher) releaseTree() {
if h.bmt != nil {
n := h.bmt.leaves[h.cur]
for ; n != nil; n = n.parent {
n.unbalanced = false
if n.parent != nil {
n.root = false
}
}
h.pool.Release(h.bmt)
h.bmt = nil
}
h.cur = 0
h.segment = nil
}
func (h *Hasher) writeSegment(i int, s []byte, d int) {
hash := h.pool.hasher()
n := h.bmt.leaves[i]
if len(s) > h.size && n.parent != nil {
go func() {
hash.Reset()
hash.Write(s)
s = hash.Sum(nil)
if n.root {
h.result <- s
return
}
h.run(n.parent, hash, d, n.index, s)
}()
return
}
go h.run(n, hash, d, i*2, s)
}
func (h *Hasher) run(n *Node, hash hash.Hash, d int, i int, s []byte) {
isLeft := i%2 == 0
for {
if isLeft {
n.left = s
} else {
n.right = s
}
if !n.unbalanced && n.toggle() {
return
}
if !n.unbalanced || !isLeft || i == 0 && d == 0 {
hash.Reset()
hash.Write(n.left)
hash.Write(n.right)
s = hash.Sum(nil)
} else {
s = append(n.left, n.right...)
}
h.hash = s
if n.root {
h.result <- s
return
}
isLeft = n.isLeft
n = n.parent
i++
}
}
// getTree obtains a BMT resource by reserving one from the pool
func (h *Hasher) getTree() *Tree {
if h.bmt != nil {
return h.bmt
}
t := h.pool.Reserve()
h.bmt = t
return t
}
// atomic bool toggle implementing a concurrent reusable 2-state object
// atomic addint with %2 implements atomic bool toggle
// it returns true if the toggler just put it in the active/waiting state
func (n *Node) toggle() bool {
return atomic.AddInt32(&n.state, 1)%2 == 1
}
func hashstr(b []byte) string {
end := len(b)
if end > 4 {
end = 4
}
return fmt.Sprintf("%x", b[:end])
}
func depth(n int) (d int) {
for l := (n - 1) / 2; l > 0; l /= 2 {
d++
}
return d
}
// finalise is following the zigzags on the tree belonging
// to the final datasegment
func (h *Hasher) finalise(n *Node, i int) (d int) {
isLeft := i%2 == 0
for {
// when the final segment's path is going via left segments
// the incoming data is pushed to the parent upon pulling the left
// we do not need toggle the state since this condition is
// detectable
n.unbalanced = isLeft
n.right = nil
if n.initial {
n.root = true
return d
}
isLeft = n.isLeft
n = n.parent
d++
}
}
// EOC (end of chunk) implements the error interface
type EOC struct {
Hash []byte // read the hash of the chunk off the error
}
// Error returns the error string
func (e *EOC) Error() string {
return fmt.Sprintf("hasher limit reached, chunk hash: %x", e.Hash)
}
// NewEOC creates new end of chunk error with the hash
func NewEOC(hash []byte) *EOC {
return &EOC{hash}
}

View file

@ -1,85 +0,0 @@
// Copyright 2017 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 bmt is a simple nonconcurrent reference implementation for hashsize segment based
// Binary Merkle tree hash on arbitrary but fixed maximum chunksize
//
// This implementation does not take advantage of any paralellisms and uses
// far more memory than necessary, but it is easy to see that it is correct.
// It can be used for generating test cases for optimized implementations.
// see testBMTHasherCorrectness function in bmt_test.go
package bmt
import (
"hash"
)
// RefHasher is the non-optimized easy to read reference implementation of BMT
type RefHasher struct {
span int
section int
cap int
h hash.Hash
}
// NewRefHasher returns a new RefHasher
func NewRefHasher(hasher BaseHasher, count int) *RefHasher {
h := hasher()
hashsize := h.Size()
maxsize := hashsize * count
c := 2
for ; c < count; c *= 2 {
}
if c > 2 {
c /= 2
}
return &RefHasher{
section: 2 * hashsize,
span: c * hashsize,
cap: maxsize,
h: h,
}
}
// Hash returns the BMT hash of the byte slice
// implements the SwarmHash interface
func (rh *RefHasher) Hash(d []byte) []byte {
if len(d) > rh.cap {
d = d[:rh.cap]
}
return rh.hash(d, rh.span)
}
func (rh *RefHasher) hash(d []byte, s int) []byte {
l := len(d)
left := d
var right []byte
if l > rh.section {
for ; s >= l; s /= 2 {
}
left = rh.hash(d[:s], s)
right = d[s:]
if l-s > rh.section/2 {
right = rh.hash(right, s)
}
}
defer rh.h.Reset()
rh.h.Write(left)
rh.h.Write(right)
h := rh.h.Sum(nil)
return h
}

View file

@ -1,481 +0,0 @@
// Copyright 2017 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 bmt
import (
"bytes"
crand "crypto/rand"
"fmt"
"hash"
"io"
"math/rand"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/ethereum/go-ethereum/crypto/sha3"
)
const (
maxproccnt = 8
)
// TestRefHasher tests that the RefHasher computes the expected BMT hash for
// all data lengths between 0 and 256 bytes
func TestRefHasher(t *testing.T) {
hashFunc := sha3.NewKeccak256
sha3 := func(data ...[]byte) []byte {
h := hashFunc()
for _, v := range data {
h.Write(v)
}
return h.Sum(nil)
}
// the test struct is used to specify the expected BMT hash for data
// lengths between "from" and "to"
type test struct {
from int64
to int64
expected func([]byte) []byte
}
var tests []*test
// all lengths in [0,64] should be:
//
// sha3(data)
//
tests = append(tests, &test{
from: 0,
to: 64,
expected: func(data []byte) []byte {
return sha3(data)
},
})
// all lengths in [65,96] should be:
//
// sha3(
// sha3(data[:64])
// data[64:]
// )
//
tests = append(tests, &test{
from: 65,
to: 96,
expected: func(data []byte) []byte {
return sha3(sha3(data[:64]), data[64:])
},
})
// all lengths in [97,128] should be:
//
// sha3(
// sha3(data[:64])
// sha3(data[64:])
// )
//
tests = append(tests, &test{
from: 97,
to: 128,
expected: func(data []byte) []byte {
return sha3(sha3(data[:64]), sha3(data[64:]))
},
})
// all lengths in [129,160] should be:
//
// sha3(
// sha3(
// sha3(data[:64])
// sha3(data[64:128])
// )
// data[128:]
// )
//
tests = append(tests, &test{
from: 129,
to: 160,
expected: func(data []byte) []byte {
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), data[128:])
},
})
// all lengths in [161,192] should be:
//
// sha3(
// sha3(
// sha3(data[:64])
// sha3(data[64:128])
// )
// sha3(data[128:])
// )
//
tests = append(tests, &test{
from: 161,
to: 192,
expected: func(data []byte) []byte {
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(data[128:]))
},
})
// all lengths in [193,224] should be:
//
// sha3(
// sha3(
// sha3(data[:64])
// sha3(data[64:128])
// )
// sha3(
// sha3(data[128:192])
// data[192:]
// )
// )
//
tests = append(tests, &test{
from: 193,
to: 224,
expected: func(data []byte) []byte {
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(sha3(data[128:192]), data[192:]))
},
})
// all lengths in [225,256] should be:
//
// sha3(
// sha3(
// sha3(data[:64])
// sha3(data[64:128])
// )
// sha3(
// sha3(data[128:192])
// sha3(data[192:])
// )
// )
//
tests = append(tests, &test{
from: 225,
to: 256,
expected: func(data []byte) []byte {
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(sha3(data[128:192]), sha3(data[192:])))
},
})
// run the tests
for _, x := range tests {
for length := x.from; length <= x.to; length++ {
t.Run(fmt.Sprintf("%d_bytes", length), func(t *testing.T) {
data := make([]byte, length)
if _, err := io.ReadFull(crand.Reader, data); err != nil && err != io.EOF {
t.Fatal(err)
}
expected := x.expected(data)
actual := NewRefHasher(hashFunc, 128).Hash(data)
if !bytes.Equal(actual, expected) {
t.Fatalf("expected %x, got %x", expected, actual)
}
})
}
}
}
func testDataReader(l int) (r io.Reader) {
return io.LimitReader(crand.Reader, int64(l))
}
func TestHasherCorrectness(t *testing.T) {
err := testHasher(testBaseHasher)
if err != nil {
t.Fatal(err)
}
}
func testHasher(f func(BaseHasher, []byte, int, int) error) error {
tdata := testDataReader(4128)
data := make([]byte, 4128)
tdata.Read(data)
hasher := sha3.NewKeccak256
size := hasher().Size()
counts := []int{1, 2, 3, 4, 5, 8, 16, 32, 64, 128}
var err error
for _, count := range counts {
max := count * size
incr := 1
for n := 0; n <= max+incr; n += incr {
err = f(hasher, data, n, count)
if err != nil {
return err
}
}
}
return nil
}
func TestHasherReuseWithoutRelease(t *testing.T) {
testHasherReuse(1, t)
}
func TestHasherReuseWithRelease(t *testing.T) {
testHasherReuse(maxproccnt, t)
}
func testHasherReuse(i int, t *testing.T) {
hasher := sha3.NewKeccak256
pool := NewTreePool(hasher, 128, i)
defer pool.Drain(0)
bmt := New(pool)
for i := 0; i < 500; i++ {
n := rand.Intn(4096)
tdata := testDataReader(n)
data := make([]byte, n)
tdata.Read(data)
err := testHasherCorrectness(bmt, hasher, data, n, 128)
if err != nil {
t.Fatal(err)
}
}
}
func TestHasherConcurrency(t *testing.T) {
hasher := sha3.NewKeccak256
pool := NewTreePool(hasher, 128, maxproccnt)
defer pool.Drain(0)
wg := sync.WaitGroup{}
cycles := 100
wg.Add(maxproccnt * cycles)
errc := make(chan error)
for p := 0; p < maxproccnt; p++ {
for i := 0; i < cycles; i++ {
go func() {
bmt := New(pool)
n := rand.Intn(4096)
tdata := testDataReader(n)
data := make([]byte, n)
tdata.Read(data)
err := testHasherCorrectness(bmt, hasher, data, n, 128)
wg.Done()
if err != nil {
errc <- err
}
}()
}
}
go func() {
wg.Wait()
close(errc)
}()
var err error
select {
case <-time.NewTimer(5 * time.Second).C:
err = fmt.Errorf("timed out")
case err = <-errc:
}
if err != nil {
t.Fatal(err)
}
}
func testBaseHasher(hasher BaseHasher, d []byte, n, count int) error {
pool := NewTreePool(hasher, count, 1)
defer pool.Drain(0)
bmt := New(pool)
return testHasherCorrectness(bmt, hasher, d, n, count)
}
func testHasherCorrectness(bmt hash.Hash, hasher BaseHasher, d []byte, n, count int) (err error) {
data := d[:n]
rbmt := NewRefHasher(hasher, count)
exp := rbmt.Hash(data)
timeout := time.NewTimer(time.Second)
c := make(chan error)
go func() {
bmt.Reset()
bmt.Write(data)
got := bmt.Sum(nil)
if !bytes.Equal(got, exp) {
c <- fmt.Errorf("wrong hash: expected %x, got %x", exp, got)
}
close(c)
}()
select {
case <-timeout.C:
err = fmt.Errorf("BMT hash calculation timed out")
case err = <-c:
}
return err
}
func BenchmarkSHA3_4k(t *testing.B) { benchmarkSHA3(4096, t) }
func BenchmarkSHA3_2k(t *testing.B) { benchmarkSHA3(4096/2, t) }
func BenchmarkSHA3_1k(t *testing.B) { benchmarkSHA3(4096/4, t) }
func BenchmarkSHA3_512b(t *testing.B) { benchmarkSHA3(4096/8, t) }
func BenchmarkSHA3_256b(t *testing.B) { benchmarkSHA3(4096/16, t) }
func BenchmarkSHA3_128b(t *testing.B) { benchmarkSHA3(4096/32, t) }
func BenchmarkBMTBaseline_4k(t *testing.B) { benchmarkBMTBaseline(4096, t) }
func BenchmarkBMTBaseline_2k(t *testing.B) { benchmarkBMTBaseline(4096/2, t) }
func BenchmarkBMTBaseline_1k(t *testing.B) { benchmarkBMTBaseline(4096/4, t) }
func BenchmarkBMTBaseline_512b(t *testing.B) { benchmarkBMTBaseline(4096/8, t) }
func BenchmarkBMTBaseline_256b(t *testing.B) { benchmarkBMTBaseline(4096/16, t) }
func BenchmarkBMTBaseline_128b(t *testing.B) { benchmarkBMTBaseline(4096/32, t) }
func BenchmarkRefHasher_4k(t *testing.B) { benchmarkRefHasher(4096, t) }
func BenchmarkRefHasher_2k(t *testing.B) { benchmarkRefHasher(4096/2, t) }
func BenchmarkRefHasher_1k(t *testing.B) { benchmarkRefHasher(4096/4, t) }
func BenchmarkRefHasher_512b(t *testing.B) { benchmarkRefHasher(4096/8, t) }
func BenchmarkRefHasher_256b(t *testing.B) { benchmarkRefHasher(4096/16, t) }
func BenchmarkRefHasher_128b(t *testing.B) { benchmarkRefHasher(4096/32, t) }
func BenchmarkHasher_4k(t *testing.B) { benchmarkHasher(4096, t) }
func BenchmarkHasher_2k(t *testing.B) { benchmarkHasher(4096/2, t) }
func BenchmarkHasher_1k(t *testing.B) { benchmarkHasher(4096/4, t) }
func BenchmarkHasher_512b(t *testing.B) { benchmarkHasher(4096/8, t) }
func BenchmarkHasher_256b(t *testing.B) { benchmarkHasher(4096/16, t) }
func BenchmarkHasher_128b(t *testing.B) { benchmarkHasher(4096/32, t) }
func BenchmarkHasherNoReuse_4k(t *testing.B) { benchmarkHasherReuse(1, 4096, t) }
func BenchmarkHasherNoReuse_2k(t *testing.B) { benchmarkHasherReuse(1, 4096/2, t) }
func BenchmarkHasherNoReuse_1k(t *testing.B) { benchmarkHasherReuse(1, 4096/4, t) }
func BenchmarkHasherNoReuse_512b(t *testing.B) { benchmarkHasherReuse(1, 4096/8, t) }
func BenchmarkHasherNoReuse_256b(t *testing.B) { benchmarkHasherReuse(1, 4096/16, t) }
func BenchmarkHasherNoReuse_128b(t *testing.B) { benchmarkHasherReuse(1, 4096/32, t) }
func BenchmarkHasherReuse_4k(t *testing.B) { benchmarkHasherReuse(16, 4096, t) }
func BenchmarkHasherReuse_2k(t *testing.B) { benchmarkHasherReuse(16, 4096/2, t) }
func BenchmarkHasherReuse_1k(t *testing.B) { benchmarkHasherReuse(16, 4096/4, t) }
func BenchmarkHasherReuse_512b(t *testing.B) { benchmarkHasherReuse(16, 4096/8, t) }
func BenchmarkHasherReuse_256b(t *testing.B) { benchmarkHasherReuse(16, 4096/16, t) }
func BenchmarkHasherReuse_128b(t *testing.B) { benchmarkHasherReuse(16, 4096/32, t) }
// benchmarks the minimum hashing time for a balanced (for simplicity) BMT
// by doing count/segmentsize parallel hashings of 2*segmentsize bytes
// doing it on n maxproccnt each reusing the base hasher
// the premise is that this is the minimum computation needed for a BMT
// therefore this serves as a theoretical optimum for concurrent implementations
func benchmarkBMTBaseline(n int, t *testing.B) {
tdata := testDataReader(64)
data := make([]byte, 64)
tdata.Read(data)
hasher := sha3.NewKeccak256
t.ReportAllocs()
t.ResetTimer()
for i := 0; i < t.N; i++ {
count := int32((n-1)/hasher().Size() + 1)
wg := sync.WaitGroup{}
wg.Add(maxproccnt)
var i int32
for j := 0; j < maxproccnt; j++ {
go func() {
defer wg.Done()
h := hasher()
for atomic.AddInt32(&i, 1) < count {
h.Reset()
h.Write(data)
h.Sum(nil)
}
}()
}
wg.Wait()
}
}
func benchmarkHasher(n int, t *testing.B) {
tdata := testDataReader(n)
data := make([]byte, n)
tdata.Read(data)
size := 1
hasher := sha3.NewKeccak256
segmentCount := 128
pool := NewTreePool(hasher, segmentCount, size)
bmt := New(pool)
t.ReportAllocs()
t.ResetTimer()
for i := 0; i < t.N; i++ {
bmt.Reset()
bmt.Write(data)
bmt.Sum(nil)
}
}
func benchmarkHasherReuse(poolsize, n int, t *testing.B) {
tdata := testDataReader(n)
data := make([]byte, n)
tdata.Read(data)
hasher := sha3.NewKeccak256
segmentCount := 128
pool := NewTreePool(hasher, segmentCount, poolsize)
cycles := 200
t.ReportAllocs()
t.ResetTimer()
for i := 0; i < t.N; i++ {
wg := sync.WaitGroup{}
wg.Add(cycles)
for j := 0; j < cycles; j++ {
bmt := New(pool)
go func() {
defer wg.Done()
bmt.Reset()
bmt.Write(data)
bmt.Sum(nil)
}()
}
wg.Wait()
}
}
func benchmarkSHA3(n int, t *testing.B) {
data := make([]byte, n)
tdata := testDataReader(n)
tdata.Read(data)
hasher := sha3.NewKeccak256
h := hasher()
t.ReportAllocs()
t.ResetTimer()
for i := 0; i < t.N; i++ {
h.Reset()
h.Write(data)
h.Sum(nil)
}
}
func benchmarkRefHasher(n int, t *testing.B) {
data := make([]byte, n)
tdata := testDataReader(n)
tdata.Read(data)
hasher := sha3.NewKeccak256
rbmt := NewRefHasher(hasher, 128)
t.ReportAllocs()
t.ResetTimer()
for i := 0; i < t.N; i++ {
rbmt.Hash(data)
}
}

View file

@ -1,18 +1,18 @@
#!/usr/bin/env bash #!/bin/sh
find_files() { find_files() {
find . -not \( \ find . ! \( \
\( \ \( \
-wholename '.github' \ -path '.github' \
-o -wholename './build/_workspace' \ -o -path './build/_workspace' \
-o -wholename './build/bin' \ -o -path './build/bin' \
-o -wholename './crypto/bn256' \ -o -path './crypto/bn256' \
-o -wholename '*/vendor/*' \ -o -path '*/vendor/*' \
\) -prune \ \) -prune \
\) -name '*.go' \) -name '*.go'
} }
GOFMT="gofmt -s -w"; GOFMT="gofmt -s -w"
GOIMPORTS="goimports -w"; GOIMPORTS="goimports -w"
find_files | xargs $GOFMT; find_files | xargs $GOFMT
find_files | xargs $GOIMPORTS; find_files | xargs $GOIMPORTS

View file

@ -77,9 +77,6 @@ var (
accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with") accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with")
accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds") accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds")
githubUser = flag.String("github.user", "", "GitHub user to authenticate with for Gist access")
githubToken = flag.String("github.token", "", "GitHub personal token to access Gists with")
captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side") captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side")
captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side") captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side")
@ -638,59 +635,6 @@ func sendSuccess(conn *websocket.Conn, msg string) error {
return send(conn, map[string]string{"success": msg}, time.Second) return send(conn, map[string]string{"success": msg}, time.Second)
} }
// authGitHub tries to authenticate a faucet request using GitHub gists, returning
// the username, avatar URL and Ethereum address to fund on success.
func authGitHub(url string) (string, string, common.Address, error) {
// Retrieve the gist from the GitHub Gist APIs
parts := strings.Split(url, "/")
req, _ := http.NewRequest("GET", "https://api.github.com/gists/"+parts[len(parts)-1], nil)
if *githubUser != "" {
req.SetBasicAuth(*githubUser, *githubToken)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return "", "", common.Address{}, err
}
var gist struct {
Owner struct {
Login string `json:"login"`
} `json:"owner"`
Files map[string]struct {
Content string `json:"content"`
} `json:"files"`
}
err = json.NewDecoder(res.Body).Decode(&gist)
res.Body.Close()
if err != nil {
return "", "", common.Address{}, err
}
if gist.Owner.Login == "" {
return "", "", common.Address{}, errors.New("Anonymous Gists not allowed")
}
// Iterate over all the files and look for Ethereum addresses
var address common.Address
for _, file := range gist.Files {
content := strings.TrimSpace(file.Content)
if len(content) == 2+common.AddressLength*2 {
address = common.HexToAddress(content)
}
}
if address == (common.Address{}) {
return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
}
// Validate the user's existence since the API is unhelpful here
if res, err = http.Head("https://github.com/" + gist.Owner.Login); err != nil {
return "", "", common.Address{}, err
}
res.Body.Close()
if res.StatusCode != 200 {
return "", "", common.Address{}, errors.New("Invalid user... boom!")
}
// Everything passed validation, return the gathered infos
return gist.Owner.Login + "@github", fmt.Sprintf("https://github.com/%s.png?size=64", gist.Owner.Login), address, nil
}
// authTwitter tries to authenticate a faucet request using Twitter posts, returning // authTwitter tries to authenticate a faucet request using Twitter posts, returning
// the username, avatar URL and Ethereum address to fund on success. // the username, avatar URL and Ethereum address to fund on success.
func authTwitter(url string) (string, string, common.Address, error) { func authTwitter(url string) (string, string, common.Address, error) {

View file

@ -77,7 +77,7 @@ var customGenesisTests = []struct {
"homesteadBlock" : 314, "homesteadBlock" : 314,
"daoForkBlock" : 141, "daoForkBlock" : 141,
"daoForkSupport" : true "daoForkSupport" : true
}, }
}`, }`,
query: "eth.getBlock(0).nonce", query: "eth.getBlock(0).nonce",
result: "0x0000000000000042", result: "0x0000000000000042",

View file

@ -144,6 +144,15 @@ var (
utils.WhisperMaxMessageSizeFlag, utils.WhisperMaxMessageSizeFlag,
utils.WhisperMinPOWFlag, utils.WhisperMinPOWFlag,
} }
metricsFlags = []cli.Flag{
utils.MetricsEnableInfluxDBFlag,
utils.MetricsInfluxDBEndpointFlag,
utils.MetricsInfluxDBDatabaseFlag,
utils.MetricsInfluxDBUsernameFlag,
utils.MetricsInfluxDBPasswordFlag,
utils.MetricsInfluxDBHostTagFlag,
}
) )
func init() { func init() {
@ -186,13 +195,14 @@ func init() {
app.Flags = append(app.Flags, consoleFlags...) app.Flags = append(app.Flags, consoleFlags...)
app.Flags = append(app.Flags, debug.Flags...) app.Flags = append(app.Flags, debug.Flags...)
app.Flags = append(app.Flags, whisperFlags...) app.Flags = append(app.Flags, whisperFlags...)
app.Flags = append(app.Flags, metricsFlags...)
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
if err := debug.Setup(ctx); err != nil { if err := debug.Setup(ctx); err != nil {
return err return err
} }
// Cap the cache allowance and tune the garbage colelctor // Cap the cache allowance and tune the garbage collector
var mem gosigar.Mem var mem gosigar.Mem
if err := mem.Get(); err == nil { if err := mem.Get(); err == nil {
allowance := int(mem.Total / 1024 / 1024 / 3) allowance := int(mem.Total / 1024 / 1024 / 3)
@ -208,6 +218,9 @@ func init() {
log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
godebug.SetGCPercent(int(gogc)) godebug.SetGCPercent(int(gogc))
// Start metrics export if enabled
utils.SetupMetrics(ctx)
// Start system runtime metrics collection // Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second) go metrics.CollectProcessMetrics(3 * time.Second)

View file

@ -83,7 +83,8 @@ var AppHelpFlagGroups = []flagGroup{
utils.LightKDFFlag, utils.LightKDFFlag,
}, },
}, },
{Name: "DEVELOPER CHAIN", {
Name: "DEVELOPER CHAIN",
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.DeveloperFlag, utils.DeveloperFlag,
utils.DeveloperPeriodFlag, utils.DeveloperPeriodFlag,
@ -206,11 +207,22 @@ var AppHelpFlagGroups = []flagGroup{
{ {
Name: "LOGGING AND DEBUGGING", Name: "LOGGING AND DEBUGGING",
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
utils.MetricsEnabledFlag,
utils.FakePoWFlag, utils.FakePoWFlag,
utils.NoCompactionFlag, utils.NoCompactionFlag,
}, debug.Flags...), }, debug.Flags...),
}, },
{
Name: "METRICS AND STATS",
Flags: []cli.Flag{
utils.MetricsEnabledFlag,
utils.MetricsEnableInfluxDBFlag,
utils.MetricsInfluxDBEndpointFlag,
utils.MetricsInfluxDBDatabaseFlag,
utils.MetricsInfluxDBUsernameFlag,
utils.MetricsInfluxDBPasswordFlag,
utils.MetricsInfluxDBHostTagFlag,
},
},
{ {
Name: "WHISPER (EXPERIMENTAL)", Name: "WHISPER (EXPERIMENTAL)",
Flags: whisperFlags, Flags: whisperFlags,

View file

@ -180,7 +180,10 @@ func main() {
}, },
}, },
} }
app.Run(os.Args) if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
} }
func showNetwork(ctx *cli.Context) error { func showNetwork(ctx *cli.Context) error {
@ -275,9 +278,8 @@ func createNode(ctx *cli.Context) error {
if len(ctx.Args()) != 0 { if len(ctx.Args()) != 0 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name) return cli.ShowCommandHelp(ctx, ctx.Command.Name)
} }
config := &adapters.NodeConfig{ config := adapters.RandomNodeConfig()
Name: ctx.String("name"), config.Name = ctx.String("name")
}
if key := ctx.String("key"); key != "" { if key := ctx.String("key"); key != "" {
privKey, err := crypto.HexToECDSA(key) privKey, err := crypto.HexToECDSA(key)
if err != nil { if err != nil {

View file

@ -276,13 +276,3 @@ func (stats serverStats) render() {
} }
table.Render() table.Render()
} }
// protips contains a collection of network infos to report pro-tips
// based on.
type protips struct {
genesis string
network int64
bootFull []string
bootLight []string
ethstats string
}

View file

@ -24,6 +24,7 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"time"
"unicode" "unicode"
cli "gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
@ -37,6 +38,8 @@ import (
bzzapi "github.com/ethereum/go-ethereum/swarm/api" bzzapi "github.com/ethereum/go-ethereum/swarm/api"
) )
const SWARM_VERSION = "0.3"
var ( var (
//flag definition for the dumpconfig command //flag definition for the dumpconfig command
DumpConfigCommand = cli.Command{ DumpConfigCommand = cli.Command{
@ -65,11 +68,17 @@ const (
SWARM_ENV_NETWORK_ID = "SWARM_NETWORK_ID" SWARM_ENV_NETWORK_ID = "SWARM_NETWORK_ID"
SWARM_ENV_SWAP_ENABLE = "SWARM_SWAP_ENABLE" SWARM_ENV_SWAP_ENABLE = "SWARM_SWAP_ENABLE"
SWARM_ENV_SWAP_API = "SWARM_SWAP_API" SWARM_ENV_SWAP_API = "SWARM_SWAP_API"
SWARM_ENV_SYNC_ENABLE = "SWARM_SYNC_ENABLE" SWARM_ENV_SYNC_DISABLE = "SWARM_SYNC_DISABLE"
SWARM_ENV_SYNC_UPDATE_DELAY = "SWARM_ENV_SYNC_UPDATE_DELAY"
SWARM_ENV_DELIVERY_SKIP_CHECK = "SWARM_DELIVERY_SKIP_CHECK"
SWARM_ENV_ENS_API = "SWARM_ENS_API" SWARM_ENV_ENS_API = "SWARM_ENS_API"
SWARM_ENV_ENS_ADDR = "SWARM_ENS_ADDR" SWARM_ENV_ENS_ADDR = "SWARM_ENS_ADDR"
SWARM_ENV_CORS = "SWARM_CORS" SWARM_ENV_CORS = "SWARM_CORS"
SWARM_ENV_BOOTNODES = "SWARM_BOOTNODES" SWARM_ENV_BOOTNODES = "SWARM_BOOTNODES"
SWARM_ENV_PSS_ENABLE = "SWARM_PSS_ENABLE"
SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH"
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
GETH_ENV_DATADIR = "GETH_DATADIR" GETH_ENV_DATADIR = "GETH_DATADIR"
) )
@ -92,10 +101,8 @@ var tomlSettings = toml.Config{
//before booting the swarm node, build the configuration //before booting the swarm node, build the configuration
func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) { func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
//check for deprecated flags
checkDeprecated(ctx)
//start by creating a default config //start by creating a default config
config = bzzapi.NewDefaultConfig() config = bzzapi.NewConfig()
//first load settings from config file (if provided) //first load settings from config file (if provided)
config, err = configFileOverride(config, ctx) config, err = configFileOverride(config, ctx)
if err != nil { if err != nil {
@ -168,7 +175,7 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" { if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" {
if id, _ := strconv.Atoi(networkid); id != 0 { if id, _ := strconv.Atoi(networkid); id != 0 {
currentConfig.NetworkId = uint64(id) currentConfig.NetworkID = uint64(id)
} }
} }
@ -191,12 +198,20 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
currentConfig.SwapEnabled = true currentConfig.SwapEnabled = true
} }
if ctx.GlobalIsSet(SwarmSyncEnabledFlag.Name) { if ctx.GlobalIsSet(SwarmSyncDisabledFlag.Name) {
currentConfig.SyncEnabled = true currentConfig.SyncEnabled = false
} }
currentConfig.SwapApi = ctx.GlobalString(SwarmSwapAPIFlag.Name) if d := ctx.GlobalDuration(SwarmSyncUpdateDelay.Name); d > 0 {
if currentConfig.SwapEnabled && currentConfig.SwapApi == "" { currentConfig.SyncUpdateDelay = d
}
if ctx.GlobalIsSet(SwarmDeliverySkipCheckFlag.Name) {
currentConfig.DeliverySkipCheck = true
}
currentConfig.SwapAPI = ctx.GlobalString(SwarmSwapAPIFlag.Name)
if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API) utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
} }
@ -209,10 +224,6 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
currentConfig.EnsAPIs = ensAPIs currentConfig.EnsAPIs = ensAPIs
} }
if ensaddr := ctx.GlobalString(DeprecatedEnsAddrFlag.Name); ensaddr != "" {
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
}
if cors := ctx.GlobalString(CorsStringFlag.Name); cors != "" { if cors := ctx.GlobalString(CorsStringFlag.Name); cors != "" {
currentConfig.Cors = cors currentConfig.Cors = cors
} }
@ -221,6 +232,18 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
currentConfig.BootNodes = ctx.GlobalString(utils.BootnodesFlag.Name) currentConfig.BootNodes = ctx.GlobalString(utils.BootnodesFlag.Name)
} }
if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" {
currentConfig.LocalStoreParams.ChunkDbPath = storePath
}
if storeCapacity := ctx.GlobalUint64(SwarmStoreCapacity.Name); storeCapacity != 0 {
currentConfig.LocalStoreParams.DbCapacity = storeCapacity
}
if storeCacheCapacity := ctx.GlobalUint(SwarmStoreCacheCapacity.Name); storeCacheCapacity != 0 {
currentConfig.LocalStoreParams.CacheCapacity = storeCacheCapacity
}
return currentConfig return currentConfig
} }
@ -239,7 +262,7 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
if networkid := os.Getenv(SWARM_ENV_NETWORK_ID); networkid != "" { if networkid := os.Getenv(SWARM_ENV_NETWORK_ID); networkid != "" {
if id, _ := strconv.Atoi(networkid); id != 0 { if id, _ := strconv.Atoi(networkid); id != 0 {
currentConfig.NetworkId = uint64(id) currentConfig.NetworkID = uint64(id)
} }
} }
@ -262,17 +285,29 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
} }
} }
if syncenable := os.Getenv(SWARM_ENV_SYNC_ENABLE); syncenable != "" { if syncdisable := os.Getenv(SWARM_ENV_SYNC_DISABLE); syncdisable != "" {
if sync, err := strconv.ParseBool(syncenable); err != nil { if sync, err := strconv.ParseBool(syncdisable); err != nil {
currentConfig.SyncEnabled = sync currentConfig.SyncEnabled = !sync
}
}
if v := os.Getenv(SWARM_ENV_DELIVERY_SKIP_CHECK); v != "" {
if skipCheck, err := strconv.ParseBool(v); err != nil {
currentConfig.DeliverySkipCheck = skipCheck
}
}
if v := os.Getenv(SWARM_ENV_SYNC_UPDATE_DELAY); v != "" {
if d, err := time.ParseDuration(v); err != nil {
currentConfig.SyncUpdateDelay = d
} }
} }
if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" { if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" {
currentConfig.SwapApi = swapapi currentConfig.SwapAPI = swapapi
} }
if currentConfig.SwapEnabled && currentConfig.SwapApi == "" { if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API) utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
} }
@ -312,18 +347,6 @@ func dumpConfig(ctx *cli.Context) error {
return nil return nil
} }
//deprecated flags checked here
func checkDeprecated(ctx *cli.Context) {
// exit if the deprecated --ethapi flag is set
if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" {
utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.")
}
// warn if --ens-api flag is set
if ctx.GlobalString(DeprecatedEnsAddrFlag.Name) != "" {
log.Warn("--ens-addr is no longer a valid command line flag, please use --ens-api to specify contract address.")
}
}
//validate configuration parameters //validate configuration parameters
func validateConfig(cfg *bzzapi.Config) (err error) { func validateConfig(cfg *bzzapi.Config) (err error) {
for _, ensAPI := range cfg.EnsAPIs { for _, ensAPI := range cfg.EnsAPIs {

View file

@ -34,7 +34,7 @@ import (
func TestDumpConfig(t *testing.T) { func TestDumpConfig(t *testing.T) {
swarm := runSwarm(t, "dumpconfig") swarm := runSwarm(t, "dumpconfig")
defaultConf := api.NewDefaultConfig() defaultConf := api.NewConfig()
out, err := tomlSettings.Marshal(&defaultConf) out, err := tomlSettings.Marshal(&defaultConf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -43,7 +43,7 @@ func TestDumpConfig(t *testing.T) {
swarm.ExpectExit() swarm.ExpectExit()
} }
func TestFailsSwapEnabledNoSwapApi(t *testing.T) { func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
flags := []string{ flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42", fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545", fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
@ -55,7 +55,7 @@ func TestFailsSwapEnabledNoSwapApi(t *testing.T) {
swarm.ExpectExit() swarm.ExpectExit()
} }
func TestFailsNoBzzAccount(t *testing.T) { func TestConfigFailsNoBzzAccount(t *testing.T) {
flags := []string{ flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42", fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545", fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
@ -66,7 +66,7 @@ func TestFailsNoBzzAccount(t *testing.T) {
swarm.ExpectExit() swarm.ExpectExit()
} }
func TestCmdLineOverrides(t *testing.T) { func TestConfigCmdLineOverrides(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest") dir, err := ioutil.TempDir("", "bzztest")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -85,9 +85,10 @@ func TestCmdLineOverrides(t *testing.T) {
flags := []string{ flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42", fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort, fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
fmt.Sprintf("--%s", SwarmSyncEnabledFlag.Name), fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
fmt.Sprintf("--%s", CorsStringFlag.Name), "*", fmt.Sprintf("--%s", CorsStringFlag.Name), "*",
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(), fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
fmt.Sprintf("--%s", SwarmDeliverySkipCheckFlag.Name),
fmt.Sprintf("--%s", EnsAPIFlag.Name), "", fmt.Sprintf("--%s", EnsAPIFlag.Name), "",
"--datadir", dir, "--datadir", dir,
"--ipcpath", conf.IPCPath, "--ipcpath", conf.IPCPath,
@ -120,12 +121,16 @@ func TestCmdLineOverrides(t *testing.T) {
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port) t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
} }
if info.NetworkId != 42 { if info.NetworkID != 42 {
t.Fatalf("Expected network ID to be %d, got %d", 42, info.NetworkId) t.Fatalf("Expected network ID to be %d, got %d", 42, info.NetworkID)
} }
if !info.SyncEnabled { if info.SyncEnabled {
t.Fatal("Expected Sync to be enabled, but is false") t.Fatal("Expected Sync to be disabled, but is true")
}
if !info.DeliverySkipCheck {
t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not")
} }
if info.Cors != "*" { if info.Cors != "*" {
@ -135,7 +140,7 @@ func TestCmdLineOverrides(t *testing.T) {
node.Shutdown() node.Shutdown()
} }
func TestFileOverrides(t *testing.T) { func TestConfigFileOverrides(t *testing.T) {
// assign ports // assign ports
httpPort, err := assignTCPPort() httpPort, err := assignTCPPort()
@ -145,16 +150,16 @@ func TestFileOverrides(t *testing.T) {
//create a config file //create a config file
//first, create a default conf //first, create a default conf
defaultConf := api.NewDefaultConfig() defaultConf := api.NewConfig()
//change some values in order to test if they have been loaded //change some values in order to test if they have been loaded
defaultConf.SyncEnabled = true defaultConf.SyncEnabled = false
defaultConf.NetworkId = 54 defaultConf.DeliverySkipCheck = true
defaultConf.NetworkID = 54
defaultConf.Port = httpPort defaultConf.Port = httpPort
defaultConf.StoreParams.DbCapacity = 9000000 defaultConf.DbCapacity = 9000000
defaultConf.ChunkerParams.Branches = 64 defaultConf.HiveParams.KeepAliveInterval = 6000000000
defaultConf.HiveParams.CallInterval = 6000000000
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
defaultConf.SyncParams.KeyBufferSize = 512 //defaultConf.SyncParams.KeyBufferSize = 512
//create a TOML string //create a TOML string
out, err := tomlSettings.Marshal(&defaultConf) out, err := tomlSettings.Marshal(&defaultConf)
if err != nil { if err != nil {
@ -215,38 +220,38 @@ func TestFileOverrides(t *testing.T) {
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port) t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
} }
if info.NetworkId != 54 { if info.NetworkID != 54 {
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId) t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkID)
} }
if !info.SyncEnabled { if info.SyncEnabled {
t.Fatal("Expected Sync to be enabled, but is false") t.Fatal("Expected Sync to be disabled, but is true")
} }
if info.StoreParams.DbCapacity != 9000000 { if !info.DeliverySkipCheck {
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId) t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not")
} }
if info.ChunkerParams.Branches != 64 { if info.DbCapacity != 9000000 {
t.Fatalf("Expected chunker params branches to be %d, got %d", 64, info.ChunkerParams.Branches) t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkID)
} }
if info.HiveParams.CallInterval != 6000000000 { if info.HiveParams.KeepAliveInterval != 6000000000 {
t.Fatalf("Expected HiveParams CallInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.CallInterval)) t.Fatalf("Expected HiveParams KeepAliveInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.KeepAliveInterval))
} }
if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second { if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval) t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
} }
if info.SyncParams.KeyBufferSize != 512 { // if info.SyncParams.KeyBufferSize != 512 {
t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize) // t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
} // }
node.Shutdown() node.Shutdown()
} }
func TestEnvVars(t *testing.T) { func TestConfigEnvVars(t *testing.T) {
// assign ports // assign ports
httpPort, err := assignTCPPort() httpPort, err := assignTCPPort()
if err != nil { if err != nil {
@ -257,7 +262,8 @@ func TestEnvVars(t *testing.T) {
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmPortFlag.EnvVar, httpPort)) envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmPortFlag.EnvVar, httpPort))
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmNetworkIdFlag.EnvVar, "999")) envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmNetworkIdFlag.EnvVar, "999"))
envVars = append(envVars, fmt.Sprintf("%s=%s", CorsStringFlag.EnvVar, "*")) envVars = append(envVars, fmt.Sprintf("%s=%s", CorsStringFlag.EnvVar, "*"))
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmSyncEnabledFlag.EnvVar, "true")) envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmSyncDisabledFlag.EnvVar, "true"))
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmDeliverySkipCheckFlag.EnvVar, "true"))
dir, err := ioutil.TempDir("", "bzztest") dir, err := ioutil.TempDir("", "bzztest")
if err != nil { if err != nil {
@ -326,23 +332,27 @@ func TestEnvVars(t *testing.T) {
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port) t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
} }
if info.NetworkId != 999 { if info.NetworkID != 999 {
t.Fatalf("Expected network ID to be %d, got %d", 999, info.NetworkId) t.Fatalf("Expected network ID to be %d, got %d", 999, info.NetworkID)
} }
if info.Cors != "*" { if info.Cors != "*" {
t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors) t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors)
} }
if !info.SyncEnabled { if info.SyncEnabled {
t.Fatal("Expected Sync to be enabled, but is false") t.Fatal("Expected Sync to be disabled, but is true")
}
if !info.DeliverySkipCheck {
t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not")
} }
node.Shutdown() node.Shutdown()
cmd.Process.Kill() cmd.Process.Kill()
} }
func TestCmdLineOverridesFile(t *testing.T) { func TestConfigCmdLineOverridesFile(t *testing.T) {
// assign ports // assign ports
httpPort, err := assignTCPPort() httpPort, err := assignTCPPort()
@ -352,26 +362,27 @@ func TestCmdLineOverridesFile(t *testing.T) {
//create a config file //create a config file
//first, create a default conf //first, create a default conf
defaultConf := api.NewDefaultConfig() defaultConf := api.NewConfig()
//change some values in order to test if they have been loaded //change some values in order to test if they have been loaded
defaultConf.SyncEnabled = false defaultConf.SyncEnabled = true
defaultConf.NetworkId = 54 defaultConf.NetworkID = 54
defaultConf.Port = "8588" defaultConf.Port = "8588"
defaultConf.StoreParams.DbCapacity = 9000000 defaultConf.DbCapacity = 9000000
defaultConf.ChunkerParams.Branches = 64 defaultConf.HiveParams.KeepAliveInterval = 6000000000
defaultConf.HiveParams.CallInterval = 6000000000
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
defaultConf.SyncParams.KeyBufferSize = 512 //defaultConf.SyncParams.KeyBufferSize = 512
//create a TOML file //create a TOML file
out, err := tomlSettings.Marshal(&defaultConf) out, err := tomlSettings.Marshal(&defaultConf)
if err != nil { if err != nil {
t.Fatalf("Error creating TOML file in TestFileOverride: %v", err) t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
} }
//write file //write file
f, err := ioutil.TempFile("", "testconfig.toml") fname := "testconfig.toml"
f, err := ioutil.TempFile("", fname)
if err != nil { if err != nil {
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err) t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
} }
defer os.Remove(fname)
//write file //write file
_, err = f.WriteString(string(out)) _, err = f.WriteString(string(out))
if err != nil { if err != nil {
@ -392,7 +403,7 @@ func TestCmdLineOverridesFile(t *testing.T) {
flags := []string{ flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "77", fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "77",
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort, fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
fmt.Sprintf("--%s", SwarmSyncEnabledFlag.Name), fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(), fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(),
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(), fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
"--ens-api", "", "--ens-api", "",
@ -427,33 +438,29 @@ func TestCmdLineOverridesFile(t *testing.T) {
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port) t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
} }
if info.NetworkId != expectNetworkId { if info.NetworkID != expectNetworkId {
t.Fatalf("Expected network ID to be %d, got %d", expectNetworkId, info.NetworkId) t.Fatalf("Expected network ID to be %d, got %d", expectNetworkId, info.NetworkID)
} }
if !info.SyncEnabled { if info.SyncEnabled {
t.Fatal("Expected Sync to be enabled, but is false") t.Fatal("Expected Sync to be disabled, but is true")
} }
if info.StoreParams.DbCapacity != 9000000 { if info.LocalStoreParams.DbCapacity != 9000000 {
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId) t.Fatalf("Expected Capacity to be %d, got %d", 9000000, info.LocalStoreParams.DbCapacity)
} }
if info.ChunkerParams.Branches != 64 { if info.HiveParams.KeepAliveInterval != 6000000000 {
t.Fatalf("Expected chunker params branches to be %d, got %d", 64, info.ChunkerParams.Branches) t.Fatalf("Expected HiveParams KeepAliveInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.KeepAliveInterval))
}
if info.HiveParams.CallInterval != 6000000000 {
t.Fatalf("Expected HiveParams CallInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.CallInterval))
} }
if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second { if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval) t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
} }
if info.SyncParams.KeyBufferSize != 512 { // if info.SyncParams.KeyBufferSize != 512 {
t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize) // t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
} // }
node.Shutdown() node.Shutdown()
} }

View file

@ -23,6 +23,7 @@ import (
"path/filepath" "path/filepath"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
@ -30,11 +31,11 @@ import (
func dbExport(ctx *cli.Context) { func dbExport(ctx *cli.Context) {
args := ctx.Args() args := ctx.Args()
if len(args) != 2 { if len(args) != 3 {
utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database) and <file> (path to write the tar archive to, - for stdout)") utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database), <file> (path to write the tar archive to, - for stdout) and the base key")
} }
store, err := openDbStore(args[0]) store, err := openLDBStore(args[0], common.Hex2Bytes(args[2]))
if err != nil { if err != nil {
utils.Fatalf("error opening local chunk database: %s", err) utils.Fatalf("error opening local chunk database: %s", err)
} }
@ -62,11 +63,11 @@ func dbExport(ctx *cli.Context) {
func dbImport(ctx *cli.Context) { func dbImport(ctx *cli.Context) {
args := ctx.Args() args := ctx.Args()
if len(args) != 2 { if len(args) != 3 {
utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database) and <file> (path to read the tar archive from, - for stdin)") utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database), <file> (path to read the tar archive from, - for stdin) and the base key")
} }
store, err := openDbStore(args[0]) store, err := openLDBStore(args[0], common.Hex2Bytes(args[2]))
if err != nil { if err != nil {
utils.Fatalf("error opening local chunk database: %s", err) utils.Fatalf("error opening local chunk database: %s", err)
} }
@ -94,11 +95,11 @@ func dbImport(ctx *cli.Context) {
func dbClean(ctx *cli.Context) { func dbClean(ctx *cli.Context) {
args := ctx.Args() args := ctx.Args()
if len(args) != 1 { if len(args) != 2 {
utils.Fatalf("invalid arguments, please specify <chunkdb> (path to a local chunk database)") utils.Fatalf("invalid arguments, please specify <chunkdb> (path to a local chunk database) and the base key")
} }
store, err := openDbStore(args[0]) store, err := openLDBStore(args[0], common.Hex2Bytes(args[1]))
if err != nil { if err != nil {
utils.Fatalf("error opening local chunk database: %s", err) utils.Fatalf("error opening local chunk database: %s", err)
} }
@ -107,10 +108,13 @@ func dbClean(ctx *cli.Context) {
store.Cleanup() store.Cleanup()
} }
func openDbStore(path string) (*storage.DbStore, error) { func openLDBStore(path string, basekey []byte) (*storage.LDBStore, error) {
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil { if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
return nil, fmt.Errorf("invalid chunkdb path: %s", err) return nil, fmt.Errorf("invalid chunkdb path: %s", err)
} }
hash := storage.MakeHashFunc("SHA3")
return storage.NewDbStore(path, hash, 10000000, 0) storeparams := storage.NewDefaultStoreParams()
ldbparams := storage.NewLDBStoreParams(storeparams, path)
ldbparams.BaseKey = basekey
return storage.NewLDBStore(ldbparams)
} }

85
cmd/swarm/download.go Normal file
View file

@ -0,0 +1,85 @@
// 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 main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/api"
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
"gopkg.in/urfave/cli.v1"
)
func download(ctx *cli.Context) {
log.Debug("downloading content using swarm down")
args := ctx.Args()
dest := "."
switch len(args) {
case 0:
utils.Fatalf("Usage: swarm down [options] <bzz locator> [<destination path>]")
case 1:
log.Trace(fmt.Sprintf("swarm down: no destination path - assuming working dir"))
default:
log.Trace(fmt.Sprintf("destination path arg: %s", args[1]))
if absDest, err := filepath.Abs(args[1]); err == nil {
dest = absDest
} else {
utils.Fatalf("could not get download path: %v", err)
}
}
var (
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
isRecursive = ctx.Bool(SwarmRecursiveFlag.Name)
client = swarm.NewClient(bzzapi)
)
if fi, err := os.Stat(dest); err == nil {
if isRecursive && !fi.Mode().IsDir() {
utils.Fatalf("destination path is not a directory!")
}
} else {
if !os.IsNotExist(err) {
utils.Fatalf("could not stat path: %v", err)
}
}
uri, err := api.Parse(args[0])
if err != nil {
utils.Fatalf("could not parse uri argument: %v", err)
}
// assume behaviour according to --recursive switch
if isRecursive {
if err := client.DownloadDirectory(uri.Addr, uri.Path, dest); err != nil {
utils.Fatalf("encoutered an error while downloading directory: %v", err)
}
} else {
// we are downloading a file
log.Debug(fmt.Sprintf("downloading file/path from a manifest. hash: %s, path:%s", uri.Addr, uri.Path))
err := client.DownloadFile(uri.Addr, uri.Path, dest)
if err != nil {
utils.Fatalf("could not download %s from given address: %s. error: %v", uri.Path, uri.Addr, err)
}
}
}

139
cmd/swarm/export_test.go Normal file
View file

@ -0,0 +1,139 @@
// 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 main
import (
"bytes"
"crypto/md5"
"crypto/rand"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"testing"
"github.com/ethereum/go-ethereum/swarm"
)
// TestCLISwarmExportImport perform the following test:
// 1. runs swarm node
// 2. uploads a random file
// 3. runs an export of the local datastore
// 4. runs a second swarm node
// 5. imports the exported datastore
// 6. fetches the uploaded random file from the second node
func TestCLISwarmExportImport(t *testing.T) {
cluster := newTestCluster(t, 1)
// generate random 10mb file
f, cleanup := generateRandomFile(t, 10000000)
defer cleanup()
// upload the file with 'swarm up' and expect a hash
up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", f.Name())
_, matches := up.ExpectRegexp(`[a-f\d]{64}`)
up.ExpectExit()
hash := matches[0]
var info swarm.Info
if err := cluster.Nodes[0].Client.Call(&info, "bzz_info"); err != nil {
t.Fatal(err)
}
cluster.Stop()
defer cluster.Cleanup()
// generate an export.tar
exportCmd := runSwarm(t, "db", "export", info.Path+"/chunks", info.Path+"/export.tar", strings.TrimPrefix(info.BzzKey, "0x"))
exportCmd.ExpectExit()
// start second cluster
cluster2 := newTestCluster(t, 1)
var info2 swarm.Info
if err := cluster2.Nodes[0].Client.Call(&info2, "bzz_info"); err != nil {
t.Fatal(err)
}
// stop second cluster, so that we close LevelDB
cluster2.Stop()
defer cluster2.Cleanup()
// import the export.tar
importCmd := runSwarm(t, "db", "import", info2.Path+"/chunks", info.Path+"/export.tar", strings.TrimPrefix(info2.BzzKey, "0x"))
importCmd.ExpectExit()
// spin second cluster back up
cluster2.StartExistingNodes(t, 1, strings.TrimPrefix(info2.BzzAccount, "0x"))
// try to fetch imported file
res, err := http.Get(cluster2.Nodes[0].URL + "/bzz:/" + hash)
if err != nil {
t.Fatal(err)
}
if res.StatusCode != 200 {
t.Fatalf("expected HTTP status %d, got %s", 200, res.Status)
}
// compare downloaded file with the generated random file
mustEqualFiles(t, f, res.Body)
}
func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) {
h := md5.New()
upLen, err := io.Copy(h, up)
if err != nil {
t.Fatal(err)
}
upHash := h.Sum(nil)
h.Reset()
downLen, err := io.Copy(h, down)
if err != nil {
t.Fatal(err)
}
downHash := h.Sum(nil)
if !bytes.Equal(upHash, downHash) || upLen != downLen {
t.Fatalf("downloaded imported file md5=%x (length %v) is not the same as the generated one mp5=%x (length %v)", downHash, downLen, upHash, upLen)
}
}
func generateRandomFile(t *testing.T, size int) (f *os.File, teardown func()) {
// create a tmp file
tmp, err := ioutil.TempFile("", "swarm-test")
if err != nil {
t.Fatal(err)
}
// callback for tmp file cleanup
teardown = func() {
tmp.Close()
os.Remove(tmp.Name())
}
// write 10mb random data to file
buf := make([]byte, 10000000)
_, err = rand.Read(buf)
if err != nil {
t.Fatal(err)
}
ioutil.WriteFile(tmp.Name(), buf, 0755)
return tmp, teardown
}

127
cmd/swarm/fs.go Normal file
View file

@ -0,0 +1,127 @@
// 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 main
import (
"context"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm/fuse"
"gopkg.in/urfave/cli.v1"
)
func mount(cliContext *cli.Context) {
args := cliContext.Args()
if len(args) < 2 {
utils.Fatalf("Usage: swarm fs mount --ipcpath <path to bzzd.ipc> <manifestHash> <file name>")
}
client, err := dialRPC(cliContext)
if err != nil {
utils.Fatalf("had an error dailing to RPC endpoint: %v", err)
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
mf := &fuse.MountInfo{}
mountPoint, err := filepath.Abs(filepath.Clean(args[1]))
if err != nil {
utils.Fatalf("error expanding path for mount point: %v", err)
}
err = client.CallContext(ctx, mf, "swarmfs_mount", args[0], mountPoint)
if err != nil {
utils.Fatalf("had an error calling the RPC endpoint while mounting: %v", err)
}
}
func unmount(cliContext *cli.Context) {
args := cliContext.Args()
if len(args) < 1 {
utils.Fatalf("Usage: swarm fs unmount --ipcpath <path to bzzd.ipc> <mount path>")
}
client, err := dialRPC(cliContext)
if err != nil {
utils.Fatalf("had an error dailing to RPC endpoint: %v", err)
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
mf := fuse.MountInfo{}
err = client.CallContext(ctx, &mf, "swarmfs_unmount", args[0])
if err != nil {
utils.Fatalf("encountered an error calling the RPC endpoint while unmounting: %v", err)
}
fmt.Printf("%s\n", mf.LatestManifest) //print the latest manifest hash for user reference
}
func listMounts(cliContext *cli.Context) {
client, err := dialRPC(cliContext)
if err != nil {
utils.Fatalf("had an error dailing to RPC endpoint: %v", err)
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
mf := []fuse.MountInfo{}
err = client.CallContext(ctx, &mf, "swarmfs_listmounts")
if err != nil {
utils.Fatalf("encountered an error calling the RPC endpoint while unmounting: %v", err)
}
if len(mf) == 0 {
fmt.Print("Could not found any swarmfs mounts. Please make sure you've specified the correct RPC endpoint\n")
} else {
fmt.Printf("Found %d swarmfs mount(s):\n", len(mf))
for i, mountInfo := range mf {
fmt.Printf("%d:\n", i)
fmt.Printf("\tMount point: %s\n", mountInfo.MountPoint)
fmt.Printf("\tLatest Manifest: %s\n", mountInfo.LatestManifest)
fmt.Printf("\tStart Manifest: %s\n", mountInfo.StartManifest)
}
}
}
func dialRPC(ctx *cli.Context) (*rpc.Client, error) {
var endpoint string
if ctx.IsSet(utils.IPCPathFlag.Name) {
endpoint = ctx.String(utils.IPCPathFlag.Name)
} else {
utils.Fatalf("swarm ipc endpoint not specified")
}
if endpoint == "" {
endpoint = node.DefaultIPCEndpoint(clientIdentifier)
} else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
// Backwards compatibility with geth < 1.5 which required
// these prefixes.
endpoint = endpoint[4:]
}
return rpc.Dial(endpoint)
}

236
cmd/swarm/fs_test.go Normal file
View file

@ -0,0 +1,236 @@
// 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/>.
// +build linux darwin freebsd
package main
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/log"
colorable "github.com/mattn/go-colorable"
)
func init() {
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
}
type testFile struct {
filePath string
content string
}
// TestCLISwarmFs is a high-level test of swarmfs
func TestCLISwarmFs(t *testing.T) {
cluster := newTestCluster(t, 3)
defer cluster.Shutdown()
// create a tmp dir
mountPoint, err := ioutil.TempDir("", "swarm-test")
log.Debug("swarmfs cli test", "1st mount", mountPoint)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(mountPoint)
handlingNode := cluster.Nodes[0]
mhash := doUploadEmptyDir(t, handlingNode)
log.Debug("swarmfs cli test: mounting first run", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
mount := runSwarm(t, []string{
"fs",
"mount",
"--ipcpath", filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
mhash,
mountPoint,
}...)
mount.ExpectExit()
filesToAssert := []*testFile{}
dirPath, err := createDirInDir(mountPoint, "testSubDir")
if err != nil {
t.Fatal(err)
}
dirPath2, err := createDirInDir(dirPath, "AnotherTestSubDir")
dummyContent := "somerandomtestcontentthatshouldbeasserted"
dirs := []string{
mountPoint,
dirPath,
dirPath2,
}
files := []string{"f1.tmp", "f2.tmp"}
for _, d := range dirs {
for _, entry := range files {
tFile, err := createTestFileInPath(d, entry, dummyContent)
if err != nil {
t.Fatal(err)
}
filesToAssert = append(filesToAssert, tFile)
}
}
if len(filesToAssert) != len(dirs)*len(files) {
t.Fatalf("should have %d files to assert now, got %d", len(dirs)*len(files), len(filesToAssert))
}
hashRegexp := `[a-f\d]{64}`
log.Debug("swarmfs cli test: unmounting first run...", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
unmount := runSwarm(t, []string{
"fs",
"unmount",
"--ipcpath", filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
mountPoint,
}...)
_, matches := unmount.ExpectRegexp(hashRegexp)
unmount.ExpectExit()
hash := matches[0]
if hash == mhash {
t.Fatal("this should not be equal")
}
log.Debug("swarmfs cli test: asserting no files in mount point")
//check that there's nothing in the mount folder
filesInDir, err := ioutil.ReadDir(mountPoint)
if err != nil {
t.Fatalf("had an error reading the directory: %v", err)
}
if len(filesInDir) != 0 {
t.Fatal("there shouldn't be anything here")
}
secondMountPoint, err := ioutil.TempDir("", "swarm-test")
log.Debug("swarmfs cli test", "2nd mount point at", secondMountPoint)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(secondMountPoint)
log.Debug("swarmfs cli test: remounting at second mount point", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
//remount, check files
newMount := runSwarm(t, []string{
"fs",
"mount",
"--ipcpath", filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
hash, // the latest hash
secondMountPoint,
}...)
newMount.ExpectExit()
time.Sleep(1 * time.Second)
filesInDir, err = ioutil.ReadDir(secondMountPoint)
if err != nil {
t.Fatal(err)
}
if len(filesInDir) == 0 {
t.Fatal("there should be something here")
}
log.Debug("swarmfs cli test: traversing file tree to see it matches previous mount")
for _, file := range filesToAssert {
file.filePath = strings.Replace(file.filePath, mountPoint, secondMountPoint, -1)
fileBytes, err := ioutil.ReadFile(file.filePath)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(fileBytes, bytes.NewBufferString(file.content).Bytes()) {
t.Fatal("this should be equal")
}
}
log.Debug("swarmfs cli test: unmounting second run", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
unmountSec := runSwarm(t, []string{
"fs",
"unmount",
"--ipcpath", filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
secondMountPoint,
}...)
_, matches = unmountSec.ExpectRegexp(hashRegexp)
unmountSec.ExpectExit()
if matches[0] != hash {
t.Fatal("these should be equal - no changes made")
}
}
func doUploadEmptyDir(t *testing.T, node *testNode) string {
// create a tmp dir
tmpDir, err := ioutil.TempDir("", "swarm-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
hashRegexp := `[a-f\d]{64}`
flags := []string{
"--bzzapi", node.URL,
"--recursive",
"up",
tmpDir}
log.Info("swarmfs cli test: uploading dir with 'swarm up'")
up := runSwarm(t, flags...)
_, matches := up.ExpectRegexp(hashRegexp)
up.ExpectExit()
hash := matches[0]
log.Info("swarmfs cli test: dir uploaded", "hash", hash)
return hash
}
func createDirInDir(createInDir string, dirToCreate string) (string, error) {
fullpath := filepath.Join(createInDir, dirToCreate)
err := os.MkdirAll(fullpath, 0777)
if err != nil {
return "", err
}
return fullpath, nil
}
func createTestFileInPath(dir, filename, content string) (*testFile, error) {
tFile := &testFile{}
filePath := filepath.Join(dir, filename)
if file, err := os.Create(filePath); err == nil {
tFile.content = content
tFile.filePath = filePath
_, err = io.WriteString(file, content)
if err != nil {
return nil, err
}
file.Close()
}
return tFile, nil
}

View file

@ -18,6 +18,7 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"os" "os"
@ -38,11 +39,11 @@ func hash(ctx *cli.Context) {
defer f.Close() defer f.Close()
stat, _ := f.Stat() stat, _ := f.Stat()
chunker := storage.NewTreeChunker(storage.NewChunkerParams()) fileStore := storage.NewFileStore(storage.NewMapChunkStore(), storage.NewFileStoreParams())
key, err := chunker.Split(f, stat.Size(), nil, nil, nil) addr, _, err := fileStore.Store(context.TODO(), f, stat.Size(), false)
if err != nil { if err != nil {
utils.Fatalf("%v\n", err) utils.Fatalf("%v\n", err)
} else { } else {
fmt.Printf("%v\n", key) fmt.Printf("%v\n", addr)
} }
} }

View file

@ -34,7 +34,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
@ -49,6 +48,22 @@ import (
) )
const clientIdentifier = "swarm" const clientIdentifier = "swarm"
const helpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
CATEGORY:
{{.Category}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
var ( var (
gitCommit string // Git SHA1 commit hash of the release (set via linker flags) gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
@ -87,10 +102,6 @@ var (
Usage: "Network identifier (integer, default 3=swarm testnet)", Usage: "Network identifier (integer, default 3=swarm testnet)",
EnvVar: SWARM_ENV_NETWORK_ID, EnvVar: SWARM_ENV_NETWORK_ID,
} }
SwarmConfigPathFlag = cli.StringFlag{
Name: "bzzconfig",
Usage: "DEPRECATED: please use --config path/to/TOML-file",
}
SwarmSwapEnabledFlag = cli.BoolFlag{ SwarmSwapEnabledFlag = cli.BoolFlag{
Name: "swap", Name: "swap",
Usage: "Swarm SWAP enabled (default false)", Usage: "Swarm SWAP enabled (default false)",
@ -101,10 +112,20 @@ var (
Usage: "URL of the Ethereum API provider to use to settle SWAP payments", Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
EnvVar: SWARM_ENV_SWAP_API, EnvVar: SWARM_ENV_SWAP_API,
} }
SwarmSyncEnabledFlag = cli.BoolTFlag{ SwarmSyncDisabledFlag = cli.BoolTFlag{
Name: "sync", Name: "nosync",
Usage: "Swarm Syncing enabled (default true)", Usage: "Disable swarm syncing",
EnvVar: SWARM_ENV_SYNC_ENABLE, EnvVar: SWARM_ENV_SYNC_DISABLE,
}
SwarmSyncUpdateDelay = cli.DurationFlag{
Name: "sync-update-delay",
Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)",
EnvVar: SWARM_ENV_SYNC_UPDATE_DELAY,
}
SwarmDeliverySkipCheckFlag = cli.BoolFlag{
Name: "delivery-skip-check",
Usage: "Skip chunk delivery check (default false)",
EnvVar: SWARM_ENV_DELIVERY_SKIP_CHECK,
} }
EnsAPIFlag = cli.StringSliceFlag{ EnsAPIFlag = cli.StringSliceFlag{
Name: "ens-api", Name: "ens-api",
@ -116,13 +137,13 @@ var (
Usage: "Swarm HTTP endpoint", Usage: "Swarm HTTP endpoint",
Value: "http://127.0.0.1:8500", Value: "http://127.0.0.1:8500",
} }
SwarmRecursiveUploadFlag = cli.BoolFlag{ SwarmRecursiveFlag = cli.BoolFlag{
Name: "recursive", Name: "recursive",
Usage: "Upload directories recursively", Usage: "Upload directories recursively",
} }
SwarmWantManifestFlag = cli.BoolTFlag{ SwarmWantManifestFlag = cli.BoolTFlag{
Name: "manifest", Name: "manifest",
Usage: "Automatic manifest upload", Usage: "Automatic manifest upload (default true)",
} }
SwarmUploadDefaultPath = cli.StringFlag{ SwarmUploadDefaultPath = cli.StringFlag{
Name: "defaultpath", Name: "defaultpath",
@ -134,22 +155,31 @@ var (
} }
SwarmUploadMimeType = cli.StringFlag{ SwarmUploadMimeType = cli.StringFlag{
Name: "mime", Name: "mime",
Usage: "force mime type", Usage: "Manually specify MIME type",
}
SwarmEncryptedFlag = cli.BoolFlag{
Name: "encrypt",
Usage: "use encrypted upload",
} }
CorsStringFlag = cli.StringFlag{ CorsStringFlag = cli.StringFlag{
Name: "corsdomain", Name: "corsdomain",
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')", Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
EnvVar: SWARM_ENV_CORS, EnvVar: SWARM_ENV_CORS,
} }
SwarmStorePath = cli.StringFlag{
// the following flags are deprecated and should be removed in the future Name: "store.path",
DeprecatedEthAPIFlag = cli.StringFlag{ Usage: "Path to leveldb chunk DB (default <$GETH_ENV_DIR>/swarm/bzz-<$BZZ_KEY>/chunks)",
Name: "ethapi", EnvVar: SWARM_ENV_STORE_PATH,
Usage: "DEPRECATED: please use --ens-api and --swap-api",
} }
DeprecatedEnsAddrFlag = cli.StringFlag{ SwarmStoreCapacity = cli.Uint64Flag{
Name: "ens-addr", Name: "store.size",
Usage: "DEPRECATED: ENS contract address, please use --ens-api with contract address according to its format", Usage: "Number of chunks (5M is roughly 20-25GB) (default 5000000)",
EnvVar: SWARM_ENV_STORE_CAPACITY,
}
SwarmStoreCacheCapacity = cli.UintFlag{
Name: "store.cache.size",
Usage: "Number of recent chunks cached in memory (default 5000)",
EnvVar: SWARM_ENV_STORE_CACHE_CAPACITY,
} }
) )
@ -181,87 +211,126 @@ func init() {
app.Commands = []cli.Command{ app.Commands = []cli.Command{
{ {
Action: version, Action: version,
CustomHelpTemplate: helpTemplate,
Name: "version", Name: "version",
Usage: "Print version numbers", Usage: "Print version numbers",
ArgsUsage: " ", Description: "The output of this command is supposed to be machine-readable",
Description: `
The output of this command is supposed to be machine-readable.
`,
}, },
{ {
Action: upload, Action: upload,
CustomHelpTemplate: helpTemplate,
Name: "up", Name: "up",
Usage: "upload a file or directory to swarm using the HTTP API", Usage: "uploads a file or directory to swarm using the HTTP API",
ArgsUsage: " <file>", ArgsUsage: "<file>",
Description: ` Flags: []cli.Flag{SwarmEncryptedFlag},
"upload a file or directory to swarm using the HTTP API and prints the root hash", Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
`,
}, },
{ {
Action: list, Action: list,
CustomHelpTemplate: helpTemplate,
Name: "ls", Name: "ls",
Usage: "list files and directories contained in a manifest", Usage: "list files and directories contained in a manifest",
ArgsUsage: " <manifest> [<prefix>]", ArgsUsage: "<manifest> [<prefix>]",
Description: ` Description: "Lists files and directories contained in a manifest",
Lists files and directories contained in a manifest.
`,
}, },
{ {
Action: hash, Action: hash,
CustomHelpTemplate: helpTemplate,
Name: "hash", Name: "hash",
Usage: "print the swarm hash of a file or directory", Usage: "print the swarm hash of a file or directory",
ArgsUsage: " <file>", ArgsUsage: "<file>",
Description: ` Description: "Prints the swarm hash of file or directory",
Prints the swarm hash of file or directory.
`,
}, },
{ {
Name: "manifest", Action: download,
Usage: "update a MANIFEST", Name: "down",
ArgsUsage: "manifest COMMAND", Flags: []cli.Flag{SwarmRecursiveFlag},
Usage: "downloads a swarm manifest or a file inside a manifest",
ArgsUsage: " <uri> [<dir>]",
Description: ` Description: `
Updates a MANIFEST by adding/removing/updating the hash of a path. Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.
`, `,
},
{
Name: "manifest",
CustomHelpTemplate: helpTemplate,
Usage: "perform operations on swarm manifests",
ArgsUsage: "COMMAND",
Description: "Updates a MANIFEST by adding/removing/updating the hash of a path.\nCOMMAND could be: add, update, remove",
Subcommands: []cli.Command{ Subcommands: []cli.Command{
{ {
Action: add, Action: add,
CustomHelpTemplate: helpTemplate,
Name: "add", Name: "add",
Usage: "add a new path to the manifest", Usage: "add a new path to the manifest",
ArgsUsage: "<MANIFEST> <path> <hash> [<content-type>]", ArgsUsage: "<MANIFEST> <path> <hash> [<content-type>]",
Description: ` Description: "Adds a new path to the manifest",
Adds a new path to the manifest
`,
}, },
{ {
Action: update, Action: update,
CustomHelpTemplate: helpTemplate,
Name: "update", Name: "update",
Usage: "update the hash for an already existing path in the manifest", Usage: "update the hash for an already existing path in the manifest",
ArgsUsage: "<MANIFEST> <path> <newhash> [<newcontent-type>]", ArgsUsage: "<MANIFEST> <path> <newhash> [<newcontent-type>]",
Description: ` Description: "Update the hash for an already existing path in the manifest",
Update the hash for an already existing path in the manifest
`,
}, },
{ {
Action: remove, Action: remove,
CustomHelpTemplate: helpTemplate,
Name: "remove", Name: "remove",
Usage: "removes a path from the manifest", Usage: "removes a path from the manifest",
ArgsUsage: "<MANIFEST> <path>", ArgsUsage: "<MANIFEST> <path>",
Description: ` Description: "Removes a path from the manifest",
Removes a path from the manifest },
`, },
},
{
Name: "fs",
CustomHelpTemplate: helpTemplate,
Usage: "perform FUSE operations",
ArgsUsage: "fs COMMAND",
Description: "Performs FUSE operations by mounting/unmounting/listing mount points. This assumes you already have a Swarm node running locally. For all operation you must reference the correct path to bzzd.ipc in order to communicate with the node",
Subcommands: []cli.Command{
{
Action: mount,
CustomHelpTemplate: helpTemplate,
Name: "mount",
Flags: []cli.Flag{utils.IPCPathFlag},
Usage: "mount a swarm hash to a mount point",
ArgsUsage: "swarm fs mount --ipcpath <path to bzzd.ipc> <manifest hash> <mount point>",
Description: "Mounts a Swarm manifest hash to a given mount point. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
},
{
Action: unmount,
CustomHelpTemplate: helpTemplate,
Name: "unmount",
Flags: []cli.Flag{utils.IPCPathFlag},
Usage: "unmount a swarmfs mount",
ArgsUsage: "swarm fs unmount --ipcpath <path to bzzd.ipc> <mount point>",
Description: "Unmounts a swarmfs mount residing at <mount point>. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
},
{
Action: listMounts,
CustomHelpTemplate: helpTemplate,
Name: "list",
Flags: []cli.Flag{utils.IPCPathFlag},
Usage: "list swarmfs mounts",
ArgsUsage: "swarm fs list --ipcpath <path to bzzd.ipc>",
Description: "Lists all mounted swarmfs volumes. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
}, },
}, },
}, },
{ {
Name: "db", Name: "db",
CustomHelpTemplate: helpTemplate,
Usage: "manage the local chunk database", Usage: "manage the local chunk database",
ArgsUsage: "db COMMAND", ArgsUsage: "db COMMAND",
Description: ` Description: "Manage the local chunk database",
Manage the local chunk database.
`,
Subcommands: []cli.Command{ Subcommands: []cli.Command{
{ {
Action: dbExport, Action: dbExport,
CustomHelpTemplate: helpTemplate,
Name: "export", Name: "export",
Usage: "export a local chunk database as a tar archive (use - to send to stdout)", Usage: "export a local chunk database as a tar archive (use - to send to stdout)",
ArgsUsage: "<chunkdb> <file>", ArgsUsage: "<chunkdb> <file>",
@ -278,6 +347,7 @@ pv(1) tool to get a progress bar:
}, },
{ {
Action: dbImport, Action: dbImport,
CustomHelpTemplate: helpTemplate,
Name: "import", Name: "import",
Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)", Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
ArgsUsage: "<chunkdb> <file>", ArgsUsage: "<chunkdb> <file>",
@ -294,26 +364,15 @@ pv(1) tool to get a progress bar:
}, },
{ {
Action: dbClean, Action: dbClean,
CustomHelpTemplate: helpTemplate,
Name: "clean", Name: "clean",
Usage: "remove corrupt entries from a local chunk database", Usage: "remove corrupt entries from a local chunk database",
ArgsUsage: "<chunkdb>", ArgsUsage: "<chunkdb>",
Description: ` Description: "Remove corrupt entries from a local chunk database",
Remove corrupt entries from a local chunk database.
`,
}, },
}, },
}, },
{
Action: func(ctx *cli.Context) {
utils.Fatalf("ERROR: 'swarm cleandb' has been removed, please use 'swarm db clean'.")
},
Name: "cleandb",
Usage: "DEPRECATED: use 'swarm db clean'",
ArgsUsage: " ",
Description: `
DEPRECATED: use 'swarm db clean'.
`,
},
// See config.go // See config.go
DumpConfigCommand, DumpConfigCommand,
} }
@ -339,10 +398,11 @@ DEPRECATED: use 'swarm db clean'.
CorsStringFlag, CorsStringFlag,
EnsAPIFlag, EnsAPIFlag,
SwarmTomlConfigPathFlag, SwarmTomlConfigPathFlag,
SwarmConfigPathFlag,
SwarmSwapEnabledFlag, SwarmSwapEnabledFlag,
SwarmSwapAPIFlag, SwarmSwapAPIFlag,
SwarmSyncEnabledFlag, SwarmSyncDisabledFlag,
SwarmSyncUpdateDelay,
SwarmDeliverySkipCheckFlag,
SwarmListenAddrFlag, SwarmListenAddrFlag,
SwarmPortFlag, SwarmPortFlag,
SwarmAccountFlag, SwarmAccountFlag,
@ -350,15 +410,24 @@ DEPRECATED: use 'swarm db clean'.
ChequebookAddrFlag, ChequebookAddrFlag,
// upload flags // upload flags
SwarmApiFlag, SwarmApiFlag,
SwarmRecursiveUploadFlag, SwarmRecursiveFlag,
SwarmWantManifestFlag, SwarmWantManifestFlag,
SwarmUploadDefaultPath, SwarmUploadDefaultPath,
SwarmUpFromStdinFlag, SwarmUpFromStdinFlag,
SwarmUploadMimeType, SwarmUploadMimeType,
//deprecated flags // storage flags
DeprecatedEthAPIFlag, SwarmStorePath,
DeprecatedEnsAddrFlag, SwarmStoreCapacity,
SwarmStoreCacheCapacity,
} }
rpcFlags := []cli.Flag{
utils.WSEnabledFlag,
utils.WSListenAddrFlag,
utils.WSPortFlag,
utils.WSApiFlag,
utils.WSAllowedOriginsFlag,
}
app.Flags = append(app.Flags, rpcFlags...)
app.Flags = append(app.Flags, debug.Flags...) app.Flags = append(app.Flags, debug.Flags...)
app.Flags = append(app.Flags, swarmmetrics.Flags...) app.Flags = append(app.Flags, swarmmetrics.Flags...)
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {
@ -383,16 +452,12 @@ func main() {
} }
func version(ctx *cli.Context) error { func version(ctx *cli.Context) error {
fmt.Println(strings.Title(clientIdentifier)) fmt.Println("Version:", SWARM_VERSION)
fmt.Println("Version:", params.Version)
if gitCommit != "" { if gitCommit != "" {
fmt.Println("Git Commit:", gitCommit) fmt.Println("Git Commit:", gitCommit)
} }
fmt.Println("Network Id:", ctx.GlobalInt(utils.NetworkIdFlag.Name))
fmt.Println("Go Version:", runtime.Version()) fmt.Println("Go Version:", runtime.Version())
fmt.Println("OS:", runtime.GOOS) fmt.Println("OS:", runtime.GOOS)
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
return nil return nil
} }
@ -405,6 +470,10 @@ func bzzd(ctx *cli.Context) error {
} }
cfg := defaultNodeConfig cfg := defaultNodeConfig
//pss operates on ws
cfg.WSModules = append(cfg.WSModules, "pss")
//geth only supports --datadir via command line //geth only supports --datadir via command line
//in order to be consistent within swarm, if we pass --datadir via environment variable //in order to be consistent within swarm, if we pass --datadir via environment variable
//or via config file, we get the same directory for geth and swarm //or via config file, we get the same directory for geth and swarm
@ -421,7 +490,7 @@ func bzzd(ctx *cli.Context) error {
//due to overriding behavior //due to overriding behavior
initSwarmNode(bzzconfig, stack, ctx) initSwarmNode(bzzconfig, stack, ctx)
//register BZZ as node.Service in the ethereum node //register BZZ as node.Service in the ethereum node
registerBzzService(bzzconfig, ctx, stack) registerBzzService(bzzconfig, stack)
//start the node //start the node
utils.StartNode(stack) utils.StartNode(stack)
@ -439,7 +508,7 @@ func bzzd(ctx *cli.Context) error {
bootnodes := strings.Split(bzzconfig.BootNodes, ",") bootnodes := strings.Split(bzzconfig.BootNodes, ",")
injectBootnodes(stack.Server(), bootnodes) injectBootnodes(stack.Server(), bootnodes)
} else { } else {
if bzzconfig.NetworkId == 3 { if bzzconfig.NetworkID == 3 {
injectBootnodes(stack.Server(), testbetBootNodes) injectBootnodes(stack.Server(), testbetBootNodes)
} }
} }
@ -448,21 +517,11 @@ func bzzd(ctx *cli.Context) error {
return nil return nil
} }
func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) { func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) {
//define the swarm service boot function //define the swarm service boot function
boot := func(ctx *node.ServiceContext) (node.Service, error) { boot := func(_ *node.ServiceContext) (node.Service, error) {
var swapClient *ethclient.Client // In production, mockStore must be always nil.
var err error return swarm.NewSwarm(bzzconfig, nil)
if bzzconfig.SwapApi != "" {
log.Info("connecting to SWAP API", "url", bzzconfig.SwapApi)
swapClient, err = ethclient.Dial(bzzconfig.SwapApi)
if err != nil {
return nil, fmt.Errorf("error connecting to SWAP API %s: %s", bzzconfig.SwapApi, err)
}
}
return swarm.NewSwarm(ctx, swapClient, bzzconfig)
} }
//register within the ethereum node //register within the ethereum node
if err := stack.Register(boot); err != nil { if err := stack.Register(boot); err != nil {

View file

@ -131,13 +131,13 @@ func addEntryToManifest(ctx *cli.Context, mhash, path, hash, ctype string) strin
longestPathEntry = api.ManifestEntry{} longestPathEntry = api.ManifestEntry{}
) )
mroot, err := client.DownloadManifest(mhash) mroot, isEncrypted, err := client.DownloadManifest(mhash)
if err != nil { if err != nil {
utils.Fatalf("Manifest download failed: %v", err) utils.Fatalf("Manifest download failed: %v", err)
} }
//TODO: check if the "hash" to add is valid and present in swarm //TODO: check if the "hash" to add is valid and present in swarm
_, err = client.DownloadManifest(hash) _, _, err = client.DownloadManifest(hash)
if err != nil { if err != nil {
utils.Fatalf("Hash to add is not present: %v", err) utils.Fatalf("Hash to add is not present: %v", err)
} }
@ -180,7 +180,7 @@ func addEntryToManifest(ctx *cli.Context, mhash, path, hash, ctype string) strin
mroot.Entries = append(mroot.Entries, newEntry) mroot.Entries = append(mroot.Entries, newEntry)
} }
newManifestHash, err := client.UploadManifest(mroot) newManifestHash, err := client.UploadManifest(mroot, isEncrypted)
if err != nil { if err != nil {
utils.Fatalf("Manifest upload failed: %v", err) utils.Fatalf("Manifest upload failed: %v", err)
} }
@ -197,7 +197,7 @@ func updateEntryInManifest(ctx *cli.Context, mhash, path, hash, ctype string) st
longestPathEntry = api.ManifestEntry{} longestPathEntry = api.ManifestEntry{}
) )
mroot, err := client.DownloadManifest(mhash) mroot, isEncrypted, err := client.DownloadManifest(mhash)
if err != nil { if err != nil {
utils.Fatalf("Manifest download failed: %v", err) utils.Fatalf("Manifest download failed: %v", err)
} }
@ -257,7 +257,7 @@ func updateEntryInManifest(ctx *cli.Context, mhash, path, hash, ctype string) st
mroot = newMRoot mroot = newMRoot
} }
newManifestHash, err := client.UploadManifest(mroot) newManifestHash, err := client.UploadManifest(mroot, isEncrypted)
if err != nil { if err != nil {
utils.Fatalf("Manifest upload failed: %v", err) utils.Fatalf("Manifest upload failed: %v", err)
} }
@ -273,7 +273,7 @@ func removeEntryFromManifest(ctx *cli.Context, mhash, path string) string {
longestPathEntry = api.ManifestEntry{} longestPathEntry = api.ManifestEntry{}
) )
mroot, err := client.DownloadManifest(mhash) mroot, isEncrypted, err := client.DownloadManifest(mhash)
if err != nil { if err != nil {
utils.Fatalf("Manifest download failed: %v", err) utils.Fatalf("Manifest download failed: %v", err)
} }
@ -323,7 +323,7 @@ func removeEntryFromManifest(ctx *cli.Context, mhash, path string) string {
mroot = newMRoot mroot = newMRoot
} }
newManifestHash, err := client.UploadManifest(mroot) newManifestHash, err := client.UploadManifest(mroot, isEncrypted)
if err != nil { if err != nil {
utils.Fatalf("Manifest upload failed: %v", err) utils.Fatalf("Manifest upload failed: %v", err)
} }

View file

@ -81,6 +81,7 @@ type testCluster struct {
// //
// When starting more than one node, they are connected together using the // When starting more than one node, they are connected together using the
// admin SetPeer RPC method. // admin SetPeer RPC method.
func newTestCluster(t *testing.T, size int) *testCluster { func newTestCluster(t *testing.T, size int) *testCluster {
cluster := &testCluster{} cluster := &testCluster{}
defer func() { defer func() {
@ -96,18 +97,7 @@ func newTestCluster(t *testing.T, size int) *testCluster {
cluster.TmpDir = tmpdir cluster.TmpDir = tmpdir
// start the nodes // start the nodes
cluster.Nodes = make([]*testNode, 0, size) cluster.StartNewNodes(t, size)
for i := 0; i < size; i++ {
dir := filepath.Join(cluster.TmpDir, fmt.Sprintf("swarm%02d", i))
if err := os.Mkdir(dir, 0700); err != nil {
t.Fatal(err)
}
node := newTestNode(t, dir)
node.Name = fmt.Sprintf("swarm%02d", i)
cluster.Nodes = append(cluster.Nodes, node)
}
if size == 1 { if size == 1 {
return cluster return cluster
@ -145,12 +135,49 @@ func (c *testCluster) Shutdown() {
os.RemoveAll(c.TmpDir) os.RemoveAll(c.TmpDir)
} }
func (c *testCluster) Stop() {
for _, node := range c.Nodes {
node.Shutdown()
}
}
func (c *testCluster) StartNewNodes(t *testing.T, size int) {
c.Nodes = make([]*testNode, 0, 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)
}
node := newTestNode(t, dir)
node.Name = fmt.Sprintf("swarm%02d", i)
c.Nodes = append(c.Nodes, node)
}
}
func (c *testCluster) StartExistingNodes(t *testing.T, size int, bzzaccount string) {
c.Nodes = make([]*testNode, 0, size)
for i := 0; i < size; i++ {
dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i))
node := existingTestNode(t, dir, bzzaccount)
node.Name = fmt.Sprintf("swarm%02d", i)
c.Nodes = append(c.Nodes, node)
}
}
func (c *testCluster) Cleanup() {
os.RemoveAll(c.TmpDir)
}
type testNode struct { type testNode struct {
Name string Name string
Addr string Addr string
URL string URL string
Enode string Enode string
Dir string Dir string
IpcPath string
Client *rpc.Client Client *rpc.Client
Cmd *cmdtest.TestCmd Cmd *cmdtest.TestCmd
} }
@ -181,6 +208,72 @@ func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accoun
return conf, account return conf, account
} }
func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
conf, _ := getTestAccount(t, dir)
node := &testNode{Dir: dir}
// use a unique IPCPath when running tests on Windows
if runtime.GOOS == "windows" {
conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", bzzaccount)
}
// assign ports
httpPort, err := assignTCPPort()
if err != nil {
t.Fatal(err)
}
p2pPort, err := assignTCPPort()
if err != nil {
t.Fatal(err)
}
// start the node
node.Cmd = runSwarm(t,
"--port", p2pPort,
"--nodiscover",
"--datadir", dir,
"--ipcpath", conf.IPCPath,
"--ens-api", "",
"--bzzaccount", bzzaccount,
"--bzznetworkid", "321",
"--bzzport", httpPort,
"--verbosity", "6",
)
node.Cmd.InputLine(testPassphrase)
defer func() {
if t.Failed() {
node.Shutdown()
}
}()
// wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint())
if err == nil {
break
}
}
if node.Client == nil {
t.Fatal(err)
}
// load info
var info swarm.Info
if err := node.Client.Call(&info, "bzz_info"); err != nil {
t.Fatal(err)
}
node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
node.URL = "http://" + node.Addr
var nodeInfo p2p.NodeInfo
if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
t.Fatal(err)
}
node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort)
return node
}
func newTestNode(t *testing.T, dir string) *testNode { func newTestNode(t *testing.T, dir string) *testNode {
conf, account := getTestAccount(t, dir) conf, account := getTestAccount(t, dir)
@ -239,6 +332,7 @@ func newTestNode(t *testing.T, dir string) *testNode {
t.Fatal(err) t.Fatal(err)
} }
node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort) node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort)
node.IpcPath = conf.IPCPath
return node return node
} }

View file

@ -0,0 +1,101 @@
// 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 main
import (
"os"
"sort"
"github.com/ethereum/go-ethereum/log"
colorable "github.com/mattn/go-colorable"
cli "gopkg.in/urfave/cli.v1"
)
var (
endpoints []string
includeLocalhost bool
cluster string
scheme string
filesize int
from int
to int
)
func main() {
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
app := cli.NewApp()
app.Name = "smoke-test"
app.Usage = ""
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "cluster-endpoint",
Value: "testing",
Usage: "cluster to point to (open, or testing)",
Destination: &cluster,
},
cli.IntFlag{
Name: "cluster-from",
Value: 8501,
Usage: "swarm node (from)",
Destination: &from,
},
cli.IntFlag{
Name: "cluster-to",
Value: 8512,
Usage: "swarm node (to)",
Destination: &to,
},
cli.StringFlag{
Name: "cluster-scheme",
Value: "http",
Usage: "http or https",
Destination: &scheme,
},
cli.BoolFlag{
Name: "include-localhost",
Usage: "whether to include localhost:8500 as an endpoint",
Destination: &includeLocalhost,
},
cli.IntFlag{
Name: "filesize",
Value: 1,
Usage: "file size for generated random file in MB",
Destination: &filesize,
},
}
app.Commands = []cli.Command{
{
Name: "upload_and_sync",
Aliases: []string{"c"},
Usage: "upload and sync",
Action: cliUploadAndSync,
},
}
sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
err := app.Run(os.Args)
if err != nil {
log.Error(err.Error())
}
}

View file

@ -0,0 +1,190 @@
// 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 main
import (
"bytes"
"crypto/md5"
"crypto/rand"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/pborman/uuid"
cli "gopkg.in/urfave/cli.v1"
)
func generateEndpoints(scheme string, cluster string, from int, to int) {
if cluster == "prod" {
cluster = ""
} else {
cluster = cluster + "."
}
for port := from; port <= to; port++ {
endpoints = append(endpoints, fmt.Sprintf("%s://%v.%sswarm-gateways.net", scheme, port, cluster))
}
if includeLocalhost {
endpoints = append(endpoints, "http://localhost:8500")
}
}
func cliUploadAndSync(c *cli.Context) error {
defer func(now time.Time) { log.Info("total time", "time", time.Since(now), "size", filesize) }(time.Now())
generateEndpoints(scheme, cluster, from, to)
log.Info("uploading to " + endpoints[0] + " and syncing")
f, cleanup := generateRandomFile(filesize * 1000000)
defer cleanup()
hash, err := upload(f, endpoints[0])
if err != nil {
log.Error(err.Error())
return err
}
fhash, err := digest(f)
if err != nil {
log.Error(err.Error())
return err
}
log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash))
if filesize < 10 {
time.Sleep(15 * time.Second)
} else {
time.Sleep(2 * time.Duration(filesize) * time.Second)
}
wg := sync.WaitGroup{}
for _, endpoint := range endpoints {
endpoint := endpoint
ruid := uuid.New()[:8]
wg.Add(1)
go func(endpoint string, ruid string) {
for {
err := fetch(hash, endpoint, fhash, ruid)
if err != nil {
continue
}
wg.Done()
return
}
}(endpoint, ruid)
}
wg.Wait()
log.Info("all endpoints synced random file successfully")
return nil
}
// fetch is getting the requested `hash` from the `endpoint` and compares it with the `original` file
func fetch(hash string, endpoint string, original []byte, ruid string) error {
log.Trace("sleeping", "ruid", ruid)
time.Sleep(1 * time.Second)
log.Trace("http get request", "ruid", ruid, "api", endpoint, "hash", hash)
res, err := http.Get(endpoint + "/bzz:/" + hash + "/")
if err != nil {
log.Warn(err.Error(), "ruid", ruid)
return err
}
log.Trace("http get response", "ruid", ruid, "api", endpoint, "hash", hash, "code", res.StatusCode, "len", res.ContentLength)
if res.StatusCode != 200 {
err := fmt.Errorf("expected status code %d, got %v", 200, res.StatusCode)
log.Warn(err.Error(), "ruid", ruid)
return err
}
defer res.Body.Close()
rdigest, err := digest(res.Body)
if err != nil {
log.Warn(err.Error(), "ruid", ruid)
return err
}
if !bytes.Equal(rdigest, original) {
err := fmt.Errorf("downloaded imported file md5=%x is not the same as the generated one=%x", rdigest, original)
log.Warn(err.Error(), "ruid", ruid)
return err
}
log.Trace("downloaded file matches random file", "ruid", ruid, "len", res.ContentLength)
return nil
}
// upload is uploading a file `f` to `endpoint` via the `swarm up` cmd
func upload(f *os.File, endpoint string) (string, error) {
var out bytes.Buffer
cmd := exec.Command("swarm", "--bzzapi", endpoint, "up", f.Name())
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
hash := strings.TrimRight(out.String(), "\r\n")
return hash, nil
}
func digest(r io.Reader) ([]byte, error) {
h := md5.New()
_, err := io.Copy(h, r)
if err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// generateRandomFile is creating a temporary file with the requested byte size
func generateRandomFile(size int) (f *os.File, teardown func()) {
// create a tmp file
tmp, err := ioutil.TempFile("", "swarm-test")
if err != nil {
panic(err)
}
// callback for tmp file cleanup
teardown = func() {
tmp.Close()
os.Remove(tmp.Name())
}
buf := make([]byte, size)
_, err = rand.Read(buf)
if err != nil {
panic(err)
}
ioutil.WriteFile(tmp.Name(), buf, 0755)
return tmp, teardown
}

View file

@ -40,12 +40,13 @@ func upload(ctx *cli.Context) {
args := ctx.Args() args := ctx.Args()
var ( var (
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
recursive = ctx.GlobalBool(SwarmRecursiveUploadFlag.Name) recursive = ctx.GlobalBool(SwarmRecursiveFlag.Name)
wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name) wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name)
defaultPath = ctx.GlobalString(SwarmUploadDefaultPath.Name) defaultPath = ctx.GlobalString(SwarmUploadDefaultPath.Name)
fromStdin = ctx.GlobalBool(SwarmUpFromStdinFlag.Name) fromStdin = ctx.GlobalBool(SwarmUpFromStdinFlag.Name)
mimeType = ctx.GlobalString(SwarmUploadMimeType.Name) mimeType = ctx.GlobalString(SwarmUploadMimeType.Name)
client = swarm.NewClient(bzzapi) client = swarm.NewClient(bzzapi)
toEncrypt = ctx.Bool(SwarmEncryptedFlag.Name)
file string file string
) )
@ -76,7 +77,7 @@ func upload(ctx *cli.Context) {
utils.Fatalf("Error opening file: %s", err) utils.Fatalf("Error opening file: %s", err)
} }
defer f.Close() defer f.Close()
hash, err := client.UploadRaw(f, f.Size) hash, err := client.UploadRaw(f, f.Size, toEncrypt)
if err != nil { if err != nil {
utils.Fatalf("Upload failed: %s", err) utils.Fatalf("Upload failed: %s", err)
} }
@ -97,7 +98,7 @@ func upload(ctx *cli.Context) {
if !recursive { if !recursive {
return "", errors.New("Argument is a directory and recursive upload is disabled") return "", errors.New("Argument is a directory and recursive upload is disabled")
} }
return client.UploadDirectory(file, defaultPath, "") return client.UploadDirectory(file, defaultPath, "", toEncrypt)
} }
} else { } else {
doUpload = func() (string, error) { doUpload = func() (string, error) {
@ -110,7 +111,7 @@ func upload(ctx *cli.Context) {
mimeType = detectMimeType(file) mimeType = detectMimeType(file)
} }
f.ContentType = mimeType f.ContentType = mimeType
return client.Upload(f, "") return client.Upload(f, "", toEncrypt)
} }
} }
hash, err := doUpload() hash, err := doUpload()

View file

@ -17,60 +17,259 @@
package main package main
import ( import (
"bytes"
"flag"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"path"
"path/filepath"
"strings"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/log"
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
colorable "github.com/mattn/go-colorable"
) )
var loglevel = flag.Int("loglevel", 3, "verbosity of logs")
func init() {
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
}
// TestCLISwarmUp tests that running 'swarm up' makes the resulting file // TestCLISwarmUp tests that running 'swarm up' makes the resulting file
// available from all nodes via the HTTP API // available from all nodes via the HTTP API
func TestCLISwarmUp(t *testing.T) { func TestCLISwarmUp(t *testing.T) {
// start 3 node cluster testCLISwarmUp(false, t)
t.Log("starting 3 node cluster") }
func TestCLISwarmUpRecursive(t *testing.T) {
testCLISwarmUpRecursive(false, t)
}
// TestCLISwarmUpEncrypted tests that running 'swarm encrypted-up' makes the resulting file
// available from all nodes via the HTTP API
func TestCLISwarmUpEncrypted(t *testing.T) {
testCLISwarmUp(true, t)
}
func TestCLISwarmUpEncryptedRecursive(t *testing.T) {
testCLISwarmUpRecursive(true, t)
}
func testCLISwarmUp(toEncrypt bool, t *testing.T) {
log.Info("starting 3 node cluster")
cluster := newTestCluster(t, 3) cluster := newTestCluster(t, 3)
defer cluster.Shutdown() defer cluster.Shutdown()
// create a tmp file // create a tmp file
tmp, err := ioutil.TempFile("", "swarm-test") tmp, err := ioutil.TempFile("", "swarm-test")
assertNil(t, err)
defer tmp.Close()
defer os.Remove(tmp.Name())
_, err = io.WriteString(tmp, "data")
assertNil(t, err)
// upload the file with 'swarm up' and expect a hash
t.Log("uploading file with 'swarm up'")
up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", tmp.Name())
_, matches := up.ExpectRegexp(`[a-f\d]{64}`)
up.ExpectExit()
hash := matches[0]
t.Logf("file uploaded with hash %s", hash)
// get the file from the HTTP API of each node
for _, node := range cluster.Nodes {
t.Logf("getting file from %s", node.Name)
res, err := http.Get(node.URL + "/bzz:/" + hash)
assertNil(t, err)
assertHTTPResponse(t, res, http.StatusOK, "data")
}
}
func assertNil(t *testing.T, err error) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer tmp.Close()
defer os.Remove(tmp.Name())
// write data to file
data := "notsorandomdata"
_, err = io.WriteString(tmp, data)
if err != nil {
t.Fatal(err)
}
hashRegexp := `[a-f\d]{64}`
flags := []string{
"--bzzapi", cluster.Nodes[0].URL,
"up",
tmp.Name()}
if toEncrypt {
hashRegexp = `[a-f\d]{128}`
flags = []string{
"--bzzapi", cluster.Nodes[0].URL,
"up",
"--encrypt",
tmp.Name()}
}
// upload the file with 'swarm up' and expect a hash
log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
up := runSwarm(t, flags...)
_, matches := up.ExpectRegexp(hashRegexp)
up.ExpectExit()
hash := matches[0]
log.Info("file uploaded", "hash", hash)
// get the file from the HTTP API of each node
for _, node := range cluster.Nodes {
log.Info("getting file from node", "node", node.Name)
res, err := http.Get(node.URL + "/bzz:/" + hash)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
reply, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
if res.StatusCode != 200 {
t.Fatalf("expected HTTP status 200, got %s", res.Status)
}
if string(reply) != data {
t.Fatalf("expected HTTP body %q, got %q", data, reply)
}
log.Debug("verifying uploaded file using `swarm down`")
//try to get the content with `swarm down`
tmpDownload, err := ioutil.TempDir("", "swarm-test")
tmpDownload = path.Join(tmpDownload, "tmpfile.tmp")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDownload)
bzzLocator := "bzz:/" + hash
flags = []string{
"--bzzapi", cluster.Nodes[0].URL,
"down",
bzzLocator,
tmpDownload,
}
down := runSwarm(t, flags...)
down.ExpectExit()
fi, err := os.Stat(tmpDownload)
if err != nil {
t.Fatalf("could not stat path: %v", err)
}
switch mode := fi.Mode(); {
case mode.IsRegular():
downloadedBytes, err := ioutil.ReadFile(tmpDownload)
if err != nil {
t.Fatalf("had an error reading the downloaded file: %v", err)
}
if !bytes.Equal(downloadedBytes, bytes.NewBufferString(data).Bytes()) {
t.Fatalf("retrieved data and posted data not equal!")
}
default:
t.Fatalf("expected to download regular file, got %s", fi.Mode())
}
}
timeout := time.Duration(2 * time.Second)
httpClient := http.Client{
Timeout: timeout,
}
// try to squeeze a timeout by getting an non-existent hash from each node
for _, node := range cluster.Nodes {
_, err := httpClient.Get(node.URL + "/bzz:/1023e8bae0f70be7d7b5f74343088ba408a218254391490c85ae16278e230340")
// we're speeding up the timeout here since netstore has a 60 seconds timeout on a request
if err != nil && !strings.Contains(err.Error(), "Client.Timeout exceeded while awaiting headers") {
t.Fatal(err)
}
// this is disabled since it takes 60s due to netstore timeout
// if res.StatusCode != 404 {
// t.Fatalf("expected HTTP status 404, got %s", res.Status)
// }
}
} }
func assertHTTPResponse(t *testing.T, res *http.Response, expectedStatus int, expectedBody string) { func testCLISwarmUpRecursive(toEncrypt bool, t *testing.T) {
defer res.Body.Close() fmt.Println("starting 3 node cluster")
if res.StatusCode != expectedStatus { cluster := newTestCluster(t, 3)
t.Fatalf("expected HTTP status %d, got %s", expectedStatus, res.Status) defer cluster.Shutdown()
tmpUploadDir, err := ioutil.TempDir("", "swarm-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpUploadDir)
// create tmp files
data := "notsorandomdata"
for _, path := range []string{"tmp1", "tmp2"} {
if err := ioutil.WriteFile(filepath.Join(tmpUploadDir, path), bytes.NewBufferString(data).Bytes(), 0644); err != nil {
t.Fatal(err)
}
}
hashRegexp := `[a-f\d]{64}`
flags := []string{
"--bzzapi", cluster.Nodes[0].URL,
"--recursive",
"up",
tmpUploadDir}
if toEncrypt {
hashRegexp = `[a-f\d]{128}`
flags = []string{
"--bzzapi", cluster.Nodes[0].URL,
"--recursive",
"up",
"--encrypt",
tmpUploadDir}
}
// upload the file with 'swarm up' and expect a hash
log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
up := runSwarm(t, flags...)
_, matches := up.ExpectRegexp(hashRegexp)
up.ExpectExit()
hash := matches[0]
log.Info("dir uploaded", "hash", hash)
// get the file from the HTTP API of each node
for _, node := range cluster.Nodes {
log.Info("getting file from node", "node", node.Name)
//try to get the content with `swarm down`
tmpDownload, err := ioutil.TempDir("", "swarm-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDownload)
bzzLocator := "bzz:/" + hash
flagss := []string{}
flagss = []string{
"--bzzapi", cluster.Nodes[0].URL,
"down",
"--recursive",
bzzLocator,
tmpDownload,
}
fmt.Println("downloading from swarm with recursive")
down := runSwarm(t, flagss...)
down.ExpectExit()
files, err := ioutil.ReadDir(tmpDownload)
for _, v := range files {
fi, err := os.Stat(path.Join(tmpDownload, v.Name()))
if err != nil {
t.Fatalf("got an error: %v", err)
}
switch mode := fi.Mode(); {
case mode.IsRegular():
if file, err := swarm.Open(path.Join(tmpDownload, v.Name())); err != nil {
t.Fatalf("encountered an error opening the file returned from the CLI: %v", err)
} else {
ff := make([]byte, len(data))
io.ReadFull(file, ff)
buf := bytes.NewBufferString(data)
if !bytes.Equal(ff, buf.Bytes()) {
t.Fatalf("retrieved data and posted data not equal!")
}
}
default:
t.Fatalf("this shouldnt happen")
}
}
if err != nil {
t.Fatalf("could not list files at: %v", files)
} }
data, err := ioutil.ReadAll(res.Body)
assertNil(t, err)
if string(data) != expectedBody {
t.Fatalf("expected HTTP body %q, got %q", expectedBody, data)
} }
} }

View file

@ -27,6 +27,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
@ -48,6 +49,7 @@ import (
"github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/metrics/influxdb"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
@ -360,10 +362,6 @@ var (
Name: "ethstats", Name: "ethstats",
Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)", Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)",
} }
MetricsEnabledFlag = cli.BoolFlag{
Name: metrics.MetricsEnabledFlag,
Usage: "Enable metrics collection and reporting",
}
FakePoWFlag = cli.BoolFlag{ FakePoWFlag = cli.BoolFlag{
Name: "fakepow", Name: "fakepow",
Usage: "Disables proof-of-work verification", Usage: "Disables proof-of-work verification",
@ -532,6 +530,45 @@ var (
Usage: "Minimum POW accepted", Usage: "Minimum POW accepted",
Value: whisper.DefaultMinimumPoW, Value: whisper.DefaultMinimumPoW,
} }
// Metrics flags
MetricsEnabledFlag = cli.BoolFlag{
Name: metrics.MetricsEnabledFlag,
Usage: "Enable metrics collection and reporting",
}
MetricsEnableInfluxDBFlag = cli.BoolFlag{
Name: "metrics.influxdb",
Usage: "Enable metrics export/push to an external InfluxDB database",
}
MetricsInfluxDBEndpointFlag = cli.StringFlag{
Name: "metrics.influxdb.endpoint",
Usage: "InfluxDB API endpoint to report metrics to",
Value: "http://localhost:8086",
}
MetricsInfluxDBDatabaseFlag = cli.StringFlag{
Name: "metrics.influxdb.database",
Usage: "InfluxDB database name to push reported metrics to",
Value: "geth",
}
MetricsInfluxDBUsernameFlag = cli.StringFlag{
Name: "metrics.influxdb.username",
Usage: "Username to authorize access to the database",
Value: "test",
}
MetricsInfluxDBPasswordFlag = cli.StringFlag{
Name: "metrics.influxdb.password",
Usage: "Password to authorize access to the database",
Value: "test",
}
// The `host` tag is part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB.
// It is used so that we can group all nodes and average a measurement across all of them, but also so
// that we can select a specific node and inspect its measurements.
// https://docs.influxdata.com/influxdb/v1.4/concepts/key_concepts/#tag-key
MetricsInfluxDBHostTagFlag = cli.StringFlag{
Name: "metrics.influxdb.host.tag",
Usage: "InfluxDB `host` tag attached to all measurements",
Value: "localhost",
}
) )
// MakeDataDir retrieves the currently requested data directory, terminating // MakeDataDir retrieves the currently requested data directory, terminating
@ -961,7 +998,7 @@ func setEthash(ctx *cli.Context, cfg *eth.Config) {
} }
} }
// checkExclusive verifies that only a single isntance of the provided flags was // checkExclusive verifies that only a single instance of the provided flags was
// set by the user. Each flag might optionally be followed by a string type to // set by the user. Each flag might optionally be followed by a string type to
// specialize it further. // specialize it further.
func checkExclusive(ctx *cli.Context, args ...interface{}) { func checkExclusive(ctx *cli.Context, args ...interface{}) {
@ -1184,6 +1221,27 @@ func SetupNetwork(ctx *cli.Context) {
params.TargetGasLimit = ctx.GlobalUint64(TargetGasLimitFlag.Name) params.TargetGasLimit = ctx.GlobalUint64(TargetGasLimitFlag.Name)
} }
func SetupMetrics(ctx *cli.Context) {
if metrics.Enabled {
log.Info("Enabling metrics collection")
var (
enableExport = ctx.GlobalBool(MetricsEnableInfluxDBFlag.Name)
endpoint = ctx.GlobalString(MetricsInfluxDBEndpointFlag.Name)
database = ctx.GlobalString(MetricsInfluxDBDatabaseFlag.Name)
username = ctx.GlobalString(MetricsInfluxDBUsernameFlag.Name)
password = ctx.GlobalString(MetricsInfluxDBPasswordFlag.Name)
hosttag = ctx.GlobalString(MetricsInfluxDBHostTagFlag.Name)
)
if enableExport {
log.Info("Enabling metrics export to InfluxDB")
go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", map[string]string{
"host": hosttag,
})
}
}
}
// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.
func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
var ( var (

View file

@ -75,7 +75,7 @@ func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) {
return snap.signers(), nil return snap.signers(), nil
} }
// GetSignersAtHash retrieves the state snapshot at a given block. // GetSignersAtHash retrieves the list of authorized signers at the specified block.
func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) { func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) {
header := api.chain.GetHeaderByHash(hash) header := api.chain.GetHeaderByHash(hash)
if header == nil { if header == nil {

View file

@ -389,7 +389,7 @@ type Config struct {
PowMode Mode PowMode Mode
} }
// Ethash is a consensus engine based on proot-of-work implementing the ethash // Ethash is a consensus engine based on proof-of-work implementing the ethash
// algorithm. // algorithm.
type Ethash struct { type Ethash struct {
config Config config Config

View file

@ -269,8 +269,8 @@ func (bc *BlockChain) SetHead(head uint64) error {
defer bc.mu.Unlock() defer bc.mu.Unlock()
// Rewind the header chain, deleting all block bodies until then // Rewind the header chain, deleting all block bodies until then
delFn := func(hash common.Hash, num uint64) { delFn := func(db rawdb.DatabaseDeleter, hash common.Hash, num uint64) {
rawdb.DeleteBody(bc.db, hash, num) rawdb.DeleteBody(db, hash, num)
} }
bc.hc.SetHead(head, delFn) bc.hc.SetHead(head, delFn)
currentHeader := bc.hc.CurrentHeader() currentHeader := bc.hc.CurrentHeader()
@ -672,7 +672,7 @@ func (bc *BlockChain) Stop() {
} }
} }
for !bc.triegc.Empty() { for !bc.triegc.Empty() {
triedb.Dereference(bc.triegc.PopItem().(common.Hash), common.Hash{}) triedb.Dereference(bc.triegc.PopItem().(common.Hash))
} }
if size, _ := triedb.Size(); size != 0 { if size, _ := triedb.Size(); size != 0 {
log.Error("Dangling trie nodes after full cleanup") log.Error("Dangling trie nodes after full cleanup")
@ -947,7 +947,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
bc.triegc.Push(root, number) bc.triegc.Push(root, number)
break break
} }
triedb.Dereference(root.(common.Hash), common.Hash{}) triedb.Dereference(root.(common.Hash))
} }
} }
} }
@ -1340,9 +1340,12 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
diff := types.TxDifference(deletedTxs, addedTxs) diff := types.TxDifference(deletedTxs, addedTxs)
// When transactions get deleted from the database that means the // When transactions get deleted from the database that means the
// receipts that were created in the fork must also be deleted // receipts that were created in the fork must also be deleted
batch := bc.db.NewBatch()
for _, tx := range diff { for _, tx := range diff {
rawdb.DeleteTxLookupEntry(bc.db, tx.Hash()) rawdb.DeleteTxLookupEntry(batch, tx.Hash())
} }
batch.Write()
if len(deletedLogs) > 0 { if len(deletedLogs) > 0 {
go bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs}) go bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs})
} }

View file

@ -25,6 +25,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
@ -35,6 +36,39 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
// So we can deterministically seed different blockchains
var (
canonicalSeed = 1
forkSeed = 2
)
// newCanonical creates a chain database, and injects a deterministic canonical
// chain. Depending on the full flag, if creates either a full block chain or a
// header only chain.
func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *BlockChain, error) {
var (
db = ethdb.NewMemDatabase()
genesis = new(Genesis).MustCommit(db)
)
// Initialize a fresh chain with only a genesis block
blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{})
// Create and inject the requested chain
if n == 0 {
return db, blockchain, nil
}
if full {
// Full block-chain requested
blocks := makeBlockChain(genesis, n, engine, db, canonicalSeed)
_, err := blockchain.InsertChain(blocks)
return db, blockchain, err
}
// Header-only chain requested
headers := makeHeaderChain(genesis.Header(), n, engine, db, canonicalSeed)
_, err := blockchain.InsertHeaderChain(headers, 1)
return db, blockchain, err
}
// Test fork of length N starting from block i // Test fork of length N starting from block i
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) { func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
// Copy old chain up to #i into a new db // Copy old chain up to #i into a new db
@ -1279,8 +1313,8 @@ func TestTrieForkGC(t *testing.T) {
} }
// Dereference all the recent tries and ensure no past trie is left in // Dereference all the recent tries and ensure no past trie is left in
for i := 0; i < triesInMemory; i++ { for i := 0; i < triesInMemory; i++ {
chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root(), common.Hash{}) chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root())
chain.stateCache.TrieDB().Dereference(forks[len(blocks)-1-i].Root(), common.Hash{}) chain.stateCache.TrieDB().Dereference(forks[len(blocks)-1-i].Root())
} }
if len(chain.stateCache.TrieDB().Nodes()) > 0 { if len(chain.stateCache.TrieDB().Nodes()) > 0 {
t.Fatalf("stale tries still alive after garbase collection") t.Fatalf("stale tries still alive after garbase collection")

View file

@ -30,12 +30,6 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
// So we can deterministically seed different blockchains
var (
canonicalSeed = 1
forkSeed = 2
)
// BlockGen creates blocks for testing. // BlockGen creates blocks for testing.
// See GenerateChain for a detailed explanation. // See GenerateChain for a detailed explanation.
type BlockGen struct { type BlockGen struct {
@ -252,33 +246,6 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
} }
} }
// newCanonical creates a chain database, and injects a deterministic canonical
// chain. Depending on the full flag, if creates either a full block chain or a
// header only chain.
func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *BlockChain, error) {
var (
db = ethdb.NewMemDatabase()
genesis = new(Genesis).MustCommit(db)
)
// Initialize a fresh chain with only a genesis block
blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{})
// Create and inject the requested chain
if n == 0 {
return db, blockchain, nil
}
if full {
// Full block-chain requested
blocks := makeBlockChain(genesis, n, engine, db, canonicalSeed)
_, err := blockchain.InsertChain(blocks)
return db, blockchain, err
}
// Header-only chain requested
headers := makeHeaderChain(genesis.Header(), n, engine, db, canonicalSeed)
_, err := blockchain.InsertHeaderChain(headers, 1)
return db, blockchain, err
}
// makeHeaderChain creates a deterministic chain of headers rooted at parent. // makeHeaderChain creates a deterministic chain of headers rooted at parent.
func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header { func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header {
blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed) blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed)

View file

@ -156,13 +156,16 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf // Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
if externTd.Cmp(localTd) > 0 || (externTd.Cmp(localTd) == 0 && mrand.Float64() < 0.5) { if externTd.Cmp(localTd) > 0 || (externTd.Cmp(localTd) == 0 && mrand.Float64() < 0.5) {
// Delete any canonical number assignments above the new head // Delete any canonical number assignments above the new head
batch := hc.chainDb.NewBatch()
for i := number + 1; ; i++ { for i := number + 1; ; i++ {
hash := rawdb.ReadCanonicalHash(hc.chainDb, i) hash := rawdb.ReadCanonicalHash(hc.chainDb, i)
if hash == (common.Hash{}) { if hash == (common.Hash{}) {
break break
} }
rawdb.DeleteCanonicalHash(hc.chainDb, i) rawdb.DeleteCanonicalHash(batch, i)
} }
batch.Write()
// Overwrite any stale canonical number assignments // Overwrite any stale canonical number assignments
var ( var (
headHash = header.ParentHash headHash = header.ParentHash
@ -438,7 +441,7 @@ func (hc *HeaderChain) SetCurrentHeader(head *types.Header) {
// DeleteCallback is a callback function that is called by SetHead before // DeleteCallback is a callback function that is called by SetHead before
// each header is deleted. // each header is deleted.
type DeleteCallback func(common.Hash, uint64) type DeleteCallback func(rawdb.DatabaseDeleter, common.Hash, uint64)
// SetHead rewinds the local chain to a new head. Everything above the new head // SetHead rewinds the local chain to a new head. Everything above the new head
// will be deleted and the new one set. // will be deleted and the new one set.
@ -448,22 +451,24 @@ func (hc *HeaderChain) SetHead(head uint64, delFn DeleteCallback) {
if hdr := hc.CurrentHeader(); hdr != nil { if hdr := hc.CurrentHeader(); hdr != nil {
height = hdr.Number.Uint64() height = hdr.Number.Uint64()
} }
batch := hc.chainDb.NewBatch()
for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() { for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() {
hash := hdr.Hash() hash := hdr.Hash()
num := hdr.Number.Uint64() num := hdr.Number.Uint64()
if delFn != nil { if delFn != nil {
delFn(hash, num) delFn(batch, hash, num)
} }
rawdb.DeleteHeader(hc.chainDb, hash, num) rawdb.DeleteHeader(batch, hash, num)
rawdb.DeleteTd(hc.chainDb, hash, num) rawdb.DeleteTd(batch, hash, num)
hc.currentHeader.Store(hc.GetHeader(hdr.ParentHash, hdr.Number.Uint64()-1)) hc.currentHeader.Store(hc.GetHeader(hdr.ParentHash, hdr.Number.Uint64()-1))
} }
// Roll back the canonical chain numbering // Roll back the canonical chain numbering
for i := height; i > head; i-- { for i := height; i > head; i-- {
rawdb.DeleteCanonicalHash(hc.chainDb, i) rawdb.DeleteCanonicalHash(batch, i)
} }
batch.Write()
// Clear out any stale content from the caches // Clear out any stale content from the caches
hc.headerCache.Purge() hc.headerCache.Purge()
hc.tdCache.Purge() hc.tdCache.Purge()

View file

@ -596,7 +596,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error)
case isDirty: case isDirty:
// Write any contract code associated with the state object // Write any contract code associated with the state object
if stateObject.code != nil && stateObject.dirtyCode { if stateObject.code != nil && stateObject.dirtyCode {
s.db.TrieDB().Insert(common.BytesToHash(stateObject.CodeHash()), stateObject.code) s.db.TrieDB().InsertBlob(common.BytesToHash(stateObject.CodeHash()), stateObject.code)
stateObject.dirtyCode = false stateObject.dirtyCode = false
} }
// Write any storage changes in the state object to its storage trie. // Write any storage changes in the state object to its storage trie.

View file

@ -35,15 +35,6 @@ var (
ErrInvalidSig = errors.New("invalid transaction v, r, s values") ErrInvalidSig = errors.New("invalid transaction v, r, s values")
) )
// deriveSigner makes a *best* guess about which signer to use.
func deriveSigner(V *big.Int) Signer {
if V.Sign() != 0 && isProtectedV(V) {
return NewEIP155Signer(deriveChainId(V))
} else {
return HomesteadSigner{}
}
}
type Transaction struct { type Transaction struct {
data txdata data txdata
// caches // caches
@ -275,9 +266,9 @@ func (s Transactions) GetRlp(i int) []byte {
return enc return enc
} }
// TxDifference returns a new set t which is the difference between a to b. // TxDifference returns a new set which is the difference between a and b.
func TxDifference(a, b Transactions) (keep Transactions) { func TxDifference(a, b Transactions) Transactions {
keep = make(Transactions, 0, len(a)) keep := make(Transactions, 0, len(a))
remove := make(map[common.Hash]struct{}) remove := make(map[common.Hash]struct{})
for _, tx := range b { for _, tx := range b {

View file

@ -18,6 +18,7 @@ package vm
import "errors" import "errors"
// List execution errors
var ( var (
ErrOutOfGas = errors.New("out of gas") ErrOutOfGas = errors.New("out of gas")
ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas")

View file

@ -31,7 +31,9 @@ import (
var emptyCodeHash = crypto.Keccak256Hash(nil) var emptyCodeHash = crypto.Keccak256Hash(nil)
type ( type (
// CanTransferFunc is the signature of a transfer guard function
CanTransferFunc func(StateDB, common.Address, *big.Int) bool CanTransferFunc func(StateDB, common.Address, *big.Int) bool
// TransferFunc is the signature of a transfer function
TransferFunc func(StateDB, common.Address, common.Address, *big.Int) TransferFunc func(StateDB, common.Address, common.Address, *big.Int)
// GetHashFunc returns the nth block hash in the blockchain // GetHashFunc returns the nth block hash in the blockchain
// and is used by the BLOCKHASH EVM op code. // and is used by the BLOCKHASH EVM op code.

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
// Gas costs
const ( const (
GasQuickStep uint64 = 2 GasQuickStep uint64 = 2
GasFastestStep uint64 = 3 GasFastestStep uint64 = 3

View file

@ -861,7 +861,7 @@ func makeDup(size int64) executionFunc {
// make swap instruction function // make swap instruction function
func makeSwap(size int64) executionFunc { func makeSwap(size int64) executionFunc {
// switch n + 1 otherwise n would be swapped with n // switch n + 1 otherwise n would be swapped with n
size += 1 size++
return func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { return func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.swap(int(size)) stack.swap(int(size))
return nil, nil return nil, nil

View file

@ -36,6 +36,7 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
stack = newstack() stack = newstack()
pc = uint64(0) pc = uint64(0)
) )
env.interpreter.intPool = poolOfIntPools.get()
for i, test := range tests { for i, test := range tests {
x := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) x := new(big.Int).SetBytes(common.Hex2Bytes(test.x))
shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y)) shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y))
@ -64,6 +65,7 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
} }
} }
} }
poolOfIntPools.put(env.interpreter.intPool)
} }
func TestByteOp(t *testing.T) { func TestByteOp(t *testing.T) {
@ -71,6 +73,7 @@ func TestByteOp(t *testing.T) {
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
) )
env.interpreter.intPool = poolOfIntPools.get()
tests := []struct { tests := []struct {
v string v string
th uint64 th uint64
@ -97,6 +100,7 @@ func TestByteOp(t *testing.T) {
t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual) t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual)
} }
} }
poolOfIntPools.put(env.interpreter.intPool)
} }
func TestSHL(t *testing.T) { func TestSHL(t *testing.T) {
@ -432,6 +436,7 @@ func TestOpMstore(t *testing.T) {
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
) )
env.interpreter.intPool = poolOfIntPools.get()
mem.Resize(64) mem.Resize(64)
pc := uint64(0) pc := uint64(0)
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
@ -445,6 +450,7 @@ func TestOpMstore(t *testing.T) {
if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
t.Fatalf("Mstore failed to overwrite previous value") t.Fatalf("Mstore failed to overwrite previous value")
} }
poolOfIntPools.put(env.interpreter.intPool)
} }
func BenchmarkOpMstore(bench *testing.B) { func BenchmarkOpMstore(bench *testing.B) {

View file

@ -77,7 +77,6 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
evm: evm, evm: evm,
cfg: cfg, cfg: cfg,
gasTable: evm.ChainConfig().GasTable(evm.BlockNumber), gasTable: evm.ChainConfig().GasTable(evm.BlockNumber),
intPool: newIntPool(),
} }
} }
@ -104,6 +103,14 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
// considered a revert-and-consume-all-gas operation except for // considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left. // errExecutionReverted which means revert-and-keep-gas-left.
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) { func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
if in.intPool == nil {
in.intPool = poolOfIntPools.get()
defer func() {
poolOfIntPools.put(in.intPool)
in.intPool = nil
}()
}
// Increment the call depth which is restricted to 1024 // Increment the call depth which is restricted to 1024
in.evm.depth++ in.evm.depth++
defer func() { in.evm.depth-- }() defer func() { in.evm.depth-- }()
@ -133,6 +140,9 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
) )
contract.Input = input contract.Input = input
// Reclaim the stack as an int pool when the execution stops
defer func() { in.intPool.put(stack.data...) }()
if in.cfg.Debug { if in.cfg.Debug {
defer func() { defer func() {
if err != nil { if err != nil {

View file

@ -16,7 +16,10 @@
package vm package vm
import "math/big" import (
"math/big"
"sync"
)
var checkVal = big.NewInt(-42) var checkVal = big.NewInt(-42)
@ -65,3 +68,39 @@ func (p *intPool) put(is ...*big.Int) {
p.pool.push(i) p.pool.push(i)
} }
} }
// The intPool pool's default capacity
const poolDefaultCap = 25
// intPoolPool manages a pool of intPools.
type intPoolPool struct {
pools []*intPool
lock sync.Mutex
}
var poolOfIntPools = &intPoolPool{
pools: make([]*intPool, 0, poolDefaultCap),
}
// get is looking for an available pool to return.
func (ipp *intPoolPool) get() *intPool {
ipp.lock.Lock()
defer ipp.lock.Unlock()
if len(poolOfIntPools.pools) > 0 {
ip := ipp.pools[len(ipp.pools)-1]
ipp.pools = ipp.pools[:len(ipp.pools)-1]
return ip
}
return newIntPool()
}
// put a pool that has been allocated with get.
func (ipp *intPoolPool) put(ip *intPool) {
ipp.lock.Lock()
defer ipp.lock.Unlock()
if len(ipp.pools) < cap(ipp.pools) {
ipp.pools = append(ipp.pools, ip)
}
}

55
core/vm/intpool_test.go Normal file
View file

@ -0,0 +1,55 @@
// 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 vm
import (
"testing"
)
func TestIntPoolPoolGet(t *testing.T) {
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
nip := poolOfIntPools.get()
if nip == nil {
t.Fatalf("Invalid pool allocation")
}
}
func TestIntPoolPoolPut(t *testing.T) {
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
nip := poolOfIntPools.get()
if len(poolOfIntPools.pools) != 0 {
t.Fatalf("Pool got added to list when none should have been")
}
poolOfIntPools.put(nip)
if len(poolOfIntPools.pools) == 0 {
t.Fatalf("Pool did not get added to list when one should have been")
}
}
func TestIntPoolPoolReUse(t *testing.T) {
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
nip := poolOfIntPools.get()
poolOfIntPools.put(nip)
poolOfIntPools.get()
if len(poolOfIntPools.pools) != 0 {
t.Fatalf("Invalid number of pools. Got %d, expected %d", len(poolOfIntPools.pools), 0)
}
}

View file

@ -51,17 +51,17 @@ type operation struct {
} }
var ( var (
frontierInstructionSet = NewFrontierInstructionSet() frontierInstructionSet = newFrontierInstructionSet()
homesteadInstructionSet = NewHomesteadInstructionSet() homesteadInstructionSet = newHomesteadInstructionSet()
byzantiumInstructionSet = NewByzantiumInstructionSet() byzantiumInstructionSet = newByzantiumInstructionSet()
constantinopleInstructionSet = NewConstantinopleInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet()
) )
// NewConstantinopleInstructionSet returns the frontier, homestead // NewConstantinopleInstructionSet returns the frontier, homestead
// byzantium and contantinople instructions. // byzantium and contantinople instructions.
func NewConstantinopleInstructionSet() [256]operation { func newConstantinopleInstructionSet() [256]operation {
// instructions that can be executed during the byzantium phase. // instructions that can be executed during the byzantium phase.
instructionSet := NewByzantiumInstructionSet() instructionSet := newByzantiumInstructionSet()
instructionSet[SHL] = operation{ instructionSet[SHL] = operation{
execute: opSHL, execute: opSHL,
gasCost: constGasFunc(GasFastestStep), gasCost: constGasFunc(GasFastestStep),
@ -85,9 +85,9 @@ func NewConstantinopleInstructionSet() [256]operation {
// NewByzantiumInstructionSet returns the frontier, homestead and // NewByzantiumInstructionSet returns the frontier, homestead and
// byzantium instructions. // byzantium instructions.
func NewByzantiumInstructionSet() [256]operation { func newByzantiumInstructionSet() [256]operation {
// instructions that can be executed during the homestead phase. // instructions that can be executed during the homestead phase.
instructionSet := NewHomesteadInstructionSet() instructionSet := newHomesteadInstructionSet()
instructionSet[STATICCALL] = operation{ instructionSet[STATICCALL] = operation{
execute: opStaticCall, execute: opStaticCall,
gasCost: gasStaticCall, gasCost: gasStaticCall,
@ -123,8 +123,8 @@ func NewByzantiumInstructionSet() [256]operation {
// NewHomesteadInstructionSet returns the frontier and homestead // NewHomesteadInstructionSet returns the frontier and homestead
// instructions that can be executed during the homestead phase. // instructions that can be executed during the homestead phase.
func NewHomesteadInstructionSet() [256]operation { func newHomesteadInstructionSet() [256]operation {
instructionSet := NewFrontierInstructionSet() instructionSet := newFrontierInstructionSet()
instructionSet[DELEGATECALL] = operation{ instructionSet[DELEGATECALL] = operation{
execute: opDelegateCall, execute: opDelegateCall,
gasCost: gasDelegateCall, gasCost: gasDelegateCall,
@ -138,7 +138,7 @@ func NewHomesteadInstructionSet() [256]operation {
// NewFrontierInstructionSet returns the frontier instructions // NewFrontierInstructionSet returns the frontier instructions
// that can be executed during the frontier phase. // that can be executed during the frontier phase.
func NewFrontierInstructionSet() [256]operation { func newFrontierInstructionSet() [256]operation {
return [256]operation{ return [256]operation{
STOP: { STOP: {
execute: opStop, execute: opStop,

View file

@ -29,8 +29,10 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
// Storage represents a contract's storage.
type Storage map[common.Hash]common.Hash type Storage map[common.Hash]common.Hash
// Copy duplicates the current storage.
func (s Storage) Copy() Storage { func (s Storage) Copy() Storage {
cpy := make(Storage) cpy := make(Storage)
for key, value := range s { for key, value := range s {
@ -76,10 +78,12 @@ type structLogMarshaling struct {
ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON
} }
// OpName formats the operand name in a human-readable format.
func (s *StructLog) OpName() string { func (s *StructLog) OpName() string {
return s.Op.String() return s.Op.String()
} }
// ErrorString formats the log's error as a string.
func (s *StructLog) ErrorString() string { func (s *StructLog) ErrorString() string {
if s.Err != nil { if s.Err != nil {
return s.Err.Error() return s.Err.Error()
@ -124,6 +128,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
return logger return logger
} }
// CaptureStart implements the Tracer interface to initialize the tracing operation.
func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
return nil return nil
} }
@ -178,10 +183,13 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
return nil return nil
} }
// CaptureFault implements the Tracer interface to trace an execution fault
// while running an opcode.
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
return nil return nil
} }
// CaptureEnd is called after the call finishes to finalize the tracing.
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
l.output = output l.output = output
l.err = err l.err = err

View file

@ -29,6 +29,7 @@ type Memory struct {
lastGasCost uint64 lastGasCost uint64
} }
// NewMemory returns a new memory memory model.
func NewMemory() *Memory { func NewMemory() *Memory {
return &Memory{} return &Memory{}
} }
@ -107,6 +108,7 @@ func (m *Memory) Data() []byte {
return m.store return m.store
} }
// Print dumps the content of the memory.
func (m *Memory) Print() { func (m *Memory) Print() {
fmt.Printf("### mem %d bytes ###\n", len(m.store)) fmt.Printf("### mem %d bytes ###\n", len(m.store))
if len(m.store) > 0 { if len(m.store) > 0 {

View file

@ -65,12 +65,6 @@ func memoryCall(stack *Stack) *big.Int {
return math.BigMax(x, y) return math.BigMax(x, y)
} }
func memoryCallCode(stack *Stack) *big.Int {
x := calcMemSize(stack.Back(5), stack.Back(6))
y := calcMemSize(stack.Back(3), stack.Back(4))
return math.BigMax(x, y)
}
func memoryDelegateCall(stack *Stack) *big.Int { func memoryDelegateCall(stack *Stack) *big.Int {
x := calcMemSize(stack.Back(4), stack.Back(5)) x := calcMemSize(stack.Back(4), stack.Back(5))
y := calcMemSize(stack.Back(2), stack.Back(3)) y := calcMemSize(stack.Back(2), stack.Back(3))

View file

@ -23,6 +23,7 @@ import (
// OpCode is an EVM opcode // OpCode is an EVM opcode
type OpCode byte type OpCode byte
// IsPush specifies if an opcode is a PUSH opcode.
func (op OpCode) IsPush() bool { func (op OpCode) IsPush() bool {
switch op { switch op {
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
@ -31,12 +32,13 @@ func (op OpCode) IsPush() bool {
return false return false
} }
// IsStaticJump specifies if an opcode is JUMP.
func (op OpCode) IsStaticJump() bool { func (op OpCode) IsStaticJump() bool {
return op == JUMP return op == JUMP
} }
// 0x0 range - arithmetic ops.
const ( const (
// 0x0 range - arithmetic ops
STOP OpCode = iota STOP OpCode = iota
ADD ADD
MUL MUL
@ -51,6 +53,7 @@ const (
SIGNEXTEND SIGNEXTEND
) )
// 0x10 range - comparison ops.
const ( const (
LT OpCode = iota + 0x10 LT OpCode = iota + 0x10
GT GT
@ -70,8 +73,8 @@ const (
SHA3 = 0x20 SHA3 = 0x20
) )
// 0x30 range - closure state.
const ( const (
// 0x30 range - closure state
ADDRESS OpCode = 0x30 + iota ADDRESS OpCode = 0x30 + iota
BALANCE BALANCE
ORIGIN ORIGIN
@ -89,8 +92,8 @@ const (
RETURNDATACOPY RETURNDATACOPY
) )
// 0x40 range - block operations.
const ( const (
// 0x40 range - block operations
BLOCKHASH OpCode = 0x40 + iota BLOCKHASH OpCode = 0x40 + iota
COINBASE COINBASE
TIMESTAMP TIMESTAMP
@ -99,8 +102,8 @@ const (
GASLIMIT GASLIMIT
) )
// 0x50 range - 'storage' and execution.
const ( const (
// 0x50 range - 'storage' and execution
POP OpCode = 0x50 + iota POP OpCode = 0x50 + iota
MLOAD MLOAD
MSTORE MSTORE
@ -115,8 +118,8 @@ const (
JUMPDEST JUMPDEST
) )
// 0x60 range.
const ( const (
// 0x60 range
PUSH1 OpCode = 0x60 + iota PUSH1 OpCode = 0x60 + iota
PUSH2 PUSH2
PUSH3 PUSH3
@ -183,6 +186,7 @@ const (
SWAP16 SWAP16
) )
// 0xa0 range - logging ops.
const ( const (
LOG0 OpCode = 0xa0 + iota LOG0 OpCode = 0xa0 + iota
LOG1 LOG1
@ -191,15 +195,15 @@ const (
LOG4 LOG4
) )
// unofficial opcodes used for parsing // unofficial opcodes used for parsing.
const ( const (
PUSH OpCode = 0xb0 + iota PUSH OpCode = 0xb0 + iota
DUP DUP
SWAP SWAP
) )
// 0xf0 range - closures.
const ( const (
// 0xf0 range - closures
CREATE OpCode = 0xf0 + iota CREATE OpCode = 0xf0 + iota
CALL CALL
CALLCODE CALLCODE
@ -211,9 +215,9 @@ const (
SELFDESTRUCT = 0xff SELFDESTRUCT = 0xff
) )
// Since the opcodes aren't all in order we can't use a regular slice // Since the opcodes aren't all in order we can't use a regular slice.
var opCodeToString = map[OpCode]string{ var opCodeToString = map[OpCode]string{
// 0x0 range - arithmetic ops // 0x0 range - arithmetic ops.
STOP: "STOP", STOP: "STOP",
ADD: "ADD", ADD: "ADD",
MUL: "MUL", MUL: "MUL",
@ -232,7 +236,7 @@ var opCodeToString = map[OpCode]string{
ISZERO: "ISZERO", ISZERO: "ISZERO",
SIGNEXTEND: "SIGNEXTEND", SIGNEXTEND: "SIGNEXTEND",
// 0x10 range - bit ops // 0x10 range - bit ops.
AND: "AND", AND: "AND",
OR: "OR", OR: "OR",
XOR: "XOR", XOR: "XOR",
@ -243,10 +247,10 @@ var opCodeToString = map[OpCode]string{
ADDMOD: "ADDMOD", ADDMOD: "ADDMOD",
MULMOD: "MULMOD", MULMOD: "MULMOD",
// 0x20 range - crypto // 0x20 range - crypto.
SHA3: "SHA3", SHA3: "SHA3",
// 0x30 range - closure state // 0x30 range - closure state.
ADDRESS: "ADDRESS", ADDRESS: "ADDRESS",
BALANCE: "BALANCE", BALANCE: "BALANCE",
ORIGIN: "ORIGIN", ORIGIN: "ORIGIN",
@ -263,7 +267,7 @@ var opCodeToString = map[OpCode]string{
RETURNDATASIZE: "RETURNDATASIZE", RETURNDATASIZE: "RETURNDATASIZE",
RETURNDATACOPY: "RETURNDATACOPY", RETURNDATACOPY: "RETURNDATACOPY",
// 0x40 range - block operations // 0x40 range - block operations.
BLOCKHASH: "BLOCKHASH", BLOCKHASH: "BLOCKHASH",
COINBASE: "COINBASE", COINBASE: "COINBASE",
TIMESTAMP: "TIMESTAMP", TIMESTAMP: "TIMESTAMP",
@ -271,7 +275,7 @@ var opCodeToString = map[OpCode]string{
DIFFICULTY: "DIFFICULTY", DIFFICULTY: "DIFFICULTY",
GASLIMIT: "GASLIMIT", GASLIMIT: "GASLIMIT",
// 0x50 range - 'storage' and execution // 0x50 range - 'storage' and execution.
POP: "POP", POP: "POP",
//DUP: "DUP", //DUP: "DUP",
//SWAP: "SWAP", //SWAP: "SWAP",
@ -287,7 +291,7 @@ var opCodeToString = map[OpCode]string{
GAS: "GAS", GAS: "GAS",
JUMPDEST: "JUMPDEST", JUMPDEST: "JUMPDEST",
// 0x60 range - push // 0x60 range - push.
PUSH1: "PUSH1", PUSH1: "PUSH1",
PUSH2: "PUSH2", PUSH2: "PUSH2",
PUSH3: "PUSH3", PUSH3: "PUSH3",
@ -360,7 +364,7 @@ var opCodeToString = map[OpCode]string{
LOG3: "LOG3", LOG3: "LOG3",
LOG4: "LOG4", LOG4: "LOG4",
// 0xf0 range // 0xf0 range.
CREATE: "CREATE", CREATE: "CREATE",
CALL: "CALL", CALL: "CALL",
RETURN: "RETURN", RETURN: "RETURN",
@ -524,6 +528,7 @@ var stringToOp = map[string]OpCode{
"SELFDESTRUCT": SELFDESTRUCT, "SELFDESTRUCT": SELFDESTRUCT,
} }
// StringToOp finds the opcode whose name is stored in `str`.
func StringToOp(str string) OpCode { func StringToOp(str string) OpCode {
return stringToOp[str] return stringToOp[str]
} }

View file

@ -21,7 +21,7 @@ import (
"math/big" "math/big"
) )
// stack is an object for basic stack operations. Items popped to the stack are // Stack is an object for basic stack operations. Items popped to the stack are
// expected to be changed and modified. stack does not take care of adding newly // expected to be changed and modified. stack does not take care of adding newly
// initialised objects. // initialised objects.
type Stack struct { type Stack struct {
@ -32,6 +32,7 @@ func newstack() *Stack {
return &Stack{data: make([]*big.Int, 0, 1024)} return &Stack{data: make([]*big.Int, 0, 1024)}
} }
// Data returns the underlying big.Int array.
func (st *Stack) Data() []*big.Int { func (st *Stack) Data() []*big.Int {
return st.data return st.data
} }
@ -80,6 +81,7 @@ func (st *Stack) require(n int) error {
return nil return nil
} }
// Print dumps the content of the stack
func (st *Stack) Print() { func (st *Stack) Print() {
fmt.Println("### stack ###") fmt.Println("### stack ###")
if len(st.data) > 0 { if len(st.data) > 0 {

View file

@ -297,7 +297,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
database.TrieDB().Reference(root, common.Hash{}) database.TrieDB().Reference(root, common.Hash{})
} }
// Dereference all past tries we ourselves are done working with // Dereference all past tries we ourselves are done working with
database.TrieDB().Dereference(proot, common.Hash{}) database.TrieDB().Dereference(proot)
proot = root proot = root
// TODO(karalabe): Do we need the preimages? Won't they accumulate too much? // TODO(karalabe): Do we need the preimages? Won't they accumulate too much?
@ -320,7 +320,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
done[uint64(result.Block)] = result done[uint64(result.Block)] = result
// Dereference any paret tries held in memory by this task // Dereference any paret tries held in memory by this task
database.TrieDB().Dereference(res.rootref, common.Hash{}) database.TrieDB().Dereference(res.rootref)
// Stream completed traces to the user, aborting on the first error // Stream completed traces to the user, aborting on the first error
for result, ok := done[next]; ok; result, ok = done[next] { for result, ok := done[next]; ok; result, ok = done[next] {
@ -526,7 +526,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
return nil, err return nil, err
} }
database.TrieDB().Reference(root, common.Hash{}) database.TrieDB().Reference(root, common.Hash{})
database.TrieDB().Dereference(proot, common.Hash{}) database.TrieDB().Dereference(proot)
proot = root proot = root
} }
nodes, imgs := database.TrieDB().Size() nodes, imgs := database.TrieDB().Size()

View file

@ -60,7 +60,7 @@
return; return;
} }
// Skip any pre-compile invocations, those are just fancy opcodes // Skip any pre-compile invocations, those are just fancy opcodes
if (isPrecompiled(toAddress(log.stack.peek(1)))) { if (isPrecompiled(toAddress(log.stack.peek(1).toString(16)))) {
return; return;
} }
// Gather internal call details // Gather internal call details

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,47 @@
// 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/>.
{
// hist is the counters of opcode bigrams
hist: {},
// lastOp is last operation
lastOp: '',
// execution depth of last op
lastDepth: 0,
// step is invoked for every opcode that the VM executes.
step: function(log, db) {
var op = log.op.toString();
var depth = log.getDepth();
if (depth == this.lastDepth){
var key = this.lastOp+'-'+op;
if (this.hist[key]){
this.hist[key]++;
}
else {
this.hist[key] = 1;
}
}
this.lastOp = op;
this.lastDepth = depth;
},
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) {},
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx) {
return this.hist;
},
}

View file

@ -0,0 +1,49 @@
// 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/>.
{
// hist is the map of trigram counters
hist: {},
// lastOp is last operation
lastOps: ['',''],
lastDepth: 0,
// step is invoked for every opcode that the VM executes.
step: function(log, db) {
var depth = log.getDepth();
if (depth != this.lastDepth){
this.lastOps = ['',''];
this.lastDepth = depth;
return;
}
var op = log.op.toString();
var key = this.lastOps[0]+'-'+this.lastOps[1]+'-'+op;
if (this.hist[key]){
this.hist[key]++;
}
else {
this.hist[key] = 1;
}
this.lastOps[0] = this.lastOps[1];
this.lastOps[1] = op;
},
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) {},
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx) {
return this.hist;
},
}

View file

@ -0,0 +1,43 @@
// 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/>.
{
// hist is the map of opcodes to counters
hist: {},
// nops counts number of ops
nops: 0,
// step is invoked for every opcode that the VM executes.
step: function(log, db) {
var op = log.op.toString();
if (this.hist[op]){
this.hist[op]++;
}
else {
this.hist[op] = 1;
}
this.nops++;
},
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) {},
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx) {
if(this.nops > 0){
return this.hist;
}
},
}

View file

@ -388,6 +388,12 @@ func (b *ldbBatch) Put(key, value []byte) error {
return nil return nil
} }
func (b *ldbBatch) Delete(key []byte) error {
b.b.Delete(key)
b.size += 1
return nil
}
func (b *ldbBatch) Write() error { func (b *ldbBatch) Write() error {
return b.db.Write(b.b, nil) return b.db.Write(b.b, nil)
} }
@ -453,6 +459,10 @@ func (tb *tableBatch) Put(key, value []byte) error {
return tb.batch.Put(append([]byte(tb.prefix), key...), value) return tb.batch.Put(append([]byte(tb.prefix), key...), value)
} }
func (tb *tableBatch) Delete(key []byte) error {
return tb.batch.Delete(append([]byte(tb.prefix), key...))
}
func (tb *tableBatch) Write() error { func (tb *tableBatch) Write() error {
return tb.batch.Write() return tb.batch.Write()
} }

View file

@ -25,12 +25,17 @@ type Putter interface {
Put(key []byte, value []byte) error Put(key []byte, value []byte) error
} }
// Deleter wraps the database delete operation supported by both batches and regular databases.
type Deleter interface {
Delete(key []byte) error
}
// Database wraps all database operations. All methods are safe for concurrent use. // Database wraps all database operations. All methods are safe for concurrent use.
type Database interface { type Database interface {
Putter Putter
Deleter
Get(key []byte) ([]byte, error) Get(key []byte) ([]byte, error)
Has(key []byte) (bool, error) Has(key []byte) (bool, error)
Delete(key []byte) error
Close() Close()
NewBatch() Batch NewBatch() Batch
} }
@ -39,6 +44,7 @@ type Database interface {
// when Write is called. Batch cannot be used concurrently. // when Write is called. Batch cannot be used concurrently.
type Batch interface { type Batch interface {
Putter Putter
Deleter
ValueSize() int // amount of data in the batch ValueSize() int // amount of data in the batch
Write() error Write() error
// Reset resets the batch for reuse // Reset resets the batch for reuse

View file

@ -110,11 +110,20 @@ func (b *memBatch) Put(key, value []byte) error {
return nil return nil
} }
func (b *memBatch) Delete(key []byte) error {
b.writes = append(b.writes, kv{common.CopyBytes(key), nil})
return nil
}
func (b *memBatch) Write() error { func (b *memBatch) Write() error {
b.db.lock.Lock() b.db.lock.Lock()
defer b.db.lock.Unlock() defer b.db.lock.Unlock()
for _, kv := range b.writes { for _, kv := range b.writes {
if kv.v == nil {
delete(b.db.db, string(kv.k))
continue
}
b.db.db[string(kv.k)] = kv.v b.db.db[string(kv.k)] = kv.v
} }
return nil return nil

View file

@ -499,7 +499,7 @@ func (s uncleStats) MarshalJSON() ([]byte, error) {
return []byte("[]"), nil return []byte("[]"), nil
} }
// reportBlock retrieves the current chain head and repors it to the stats server. // reportBlock retrieves the current chain head and reports it to the stats server.
func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error { func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error {
// Gather the block details from the header or block chain // Gather the block details from the header or block chain
details := s.assembleBlockStats(block) details := s.assembleBlockStats(block)

View file

@ -267,9 +267,16 @@ func (f *lightFetcher) announce(p *peer, head *announceData) {
} }
n = n.parent n = n.parent
} }
if n != nil {
// n is now the reorg common ancestor, add a new branch of nodes // n is now the reorg common ancestor, add a new branch of nodes
// check if the node count is too high to add new nodes if n != nil && (head.Number >= n.number+maxNodeCount || head.Number <= n.number) {
// if announced head block height is lower or same as n or too far from it to add
// intermediate nodes then discard previous announcement info and trigger a resync
n = nil
fp.nodeCnt = 0
fp.nodeByHash = make(map[common.Hash]*fetcherTreeNode)
}
if n != nil {
// check if the node count is too high to add new nodes, discard oldest ones if necessary
locked := false locked := false
for uint64(fp.nodeCnt)+head.Number-n.number > maxNodeCount && fp.root != nil { for uint64(fp.nodeCnt)+head.Number-n.number > maxNodeCount && fp.root != nil {
if !locked { if !locked {

View file

@ -87,6 +87,27 @@ const (
initStatsWeight = 1 initStatsWeight = 1
) )
// connReq represents a request for peer connection.
type connReq struct {
p *peer
ip net.IP
port uint16
result chan *poolEntry
}
// disconnReq represents a request for peer disconnection.
type disconnReq struct {
entry *poolEntry
stopped bool
done chan struct{}
}
// registerReq represents a request for peer registration.
type registerReq struct {
entry *poolEntry
done chan struct{}
}
// serverPool implements a pool for storing and selecting newly discovered and already // serverPool implements a pool for storing and selecting newly discovered and already
// known light server nodes. It received discovered nodes, stores statistics about // known light server nodes. It received discovered nodes, stores statistics about
// known nodes and takes care of always having enough good quality servers connected. // known nodes and takes care of always having enough good quality servers connected.
@ -105,10 +126,13 @@ type serverPool struct {
discLookups chan bool discLookups chan bool
entries map[discover.NodeID]*poolEntry entries map[discover.NodeID]*poolEntry
lock sync.Mutex
timeout, enableRetry chan *poolEntry timeout, enableRetry chan *poolEntry
adjustStats chan poolStatAdjust adjustStats chan poolStatAdjust
connCh chan *connReq
disconnCh chan *disconnReq
registerCh chan *registerReq
knownQueue, newQueue poolEntryQueue knownQueue, newQueue poolEntryQueue
knownSelect, newSelect *weightedRandomSelect knownSelect, newSelect *weightedRandomSelect
knownSelected, newSelected int knownSelected, newSelected int
@ -125,6 +149,9 @@ func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *s
timeout: make(chan *poolEntry, 1), timeout: make(chan *poolEntry, 1),
adjustStats: make(chan poolStatAdjust, 100), adjustStats: make(chan poolStatAdjust, 100),
enableRetry: make(chan *poolEntry, 1), enableRetry: make(chan *poolEntry, 1),
connCh: make(chan *connReq),
disconnCh: make(chan *disconnReq),
registerCh: make(chan *registerReq),
knownSelect: newWeightedRandomSelect(), knownSelect: newWeightedRandomSelect(),
newSelect: newWeightedRandomSelect(), newSelect: newWeightedRandomSelect(),
fastDiscover: true, fastDiscover: true,
@ -147,9 +174,8 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
pool.discLookups = make(chan bool, 100) pool.discLookups = make(chan bool, 100)
go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups) go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups)
} }
go pool.eventLoop()
pool.checkDial() pool.checkDial()
go pool.eventLoop()
} }
// connect should be called upon any incoming connection. If the connection has been // connect should be called upon any incoming connection. If the connection has been
@ -158,83 +184,44 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
// Note that whenever a connection has been accepted and a pool entry has been returned, // Note that whenever a connection has been accepted and a pool entry has been returned,
// disconnect should also always be called. // disconnect should also always be called.
func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry { func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry {
pool.lock.Lock() log.Debug("Connect new entry", "enode", p.id)
defer pool.lock.Unlock() req := &connReq{p: p, ip: ip, port: port, result: make(chan *poolEntry, 1)}
entry := pool.entries[p.ID()] select {
if entry == nil { case pool.connCh <- req:
entry = pool.findOrNewNode(p.ID(), ip, port) case <-pool.quit:
}
p.Log().Debug("Connecting to new peer", "state", entry.state)
if entry.state == psConnected || entry.state == psRegistered {
return nil return nil
} }
pool.connWg.Add(1) return <-req.result
entry.peer = p
entry.state = psConnected
addr := &poolEntryAddress{
ip: ip,
port: port,
lastSeen: mclock.Now(),
}
entry.lastConnected = addr
entry.addr = make(map[string]*poolEntryAddress)
entry.addr[addr.strKey()] = addr
entry.addrSelect = *newWeightedRandomSelect()
entry.addrSelect.update(addr)
return entry
} }
// registered should be called after a successful handshake // registered should be called after a successful handshake
func (pool *serverPool) registered(entry *poolEntry) { func (pool *serverPool) registered(entry *poolEntry) {
log.Debug("Registered new entry", "enode", entry.id) log.Debug("Registered new entry", "enode", entry.id)
pool.lock.Lock() req := &registerReq{entry: entry, done: make(chan struct{})}
defer pool.lock.Unlock() select {
case pool.registerCh <- req:
entry.state = psRegistered case <-pool.quit:
entry.regTime = mclock.Now() return
if !entry.known {
pool.newQueue.remove(entry)
entry.known = true
} }
pool.knownQueue.setLatest(entry) <-req.done
entry.shortRetry = shortRetryCnt
} }
// disconnect should be called when ending a connection. Service quality statistics // disconnect should be called when ending a connection. Service quality statistics
// can be updated optionally (not updated if no registration happened, in this case // can be updated optionally (not updated if no registration happened, in this case
// only connection statistics are updated, just like in case of timeout) // only connection statistics are updated, just like in case of timeout)
func (pool *serverPool) disconnect(entry *poolEntry) { func (pool *serverPool) disconnect(entry *poolEntry) {
log.Debug("Disconnected old entry", "enode", entry.id)
pool.lock.Lock()
defer pool.lock.Unlock()
if entry.state == psRegistered {
connTime := mclock.Now() - entry.regTime
connAdjust := float64(connTime) / float64(targetConnTime)
if connAdjust > 1 {
connAdjust = 1
}
stopped := false stopped := false
select { select {
case <-pool.quit: case <-pool.quit:
stopped = true stopped = true
default: default:
} }
if stopped { log.Debug("Disconnected old entry", "enode", entry.id)
entry.connectStats.add(1, connAdjust) req := &disconnReq{entry: entry, stopped: stopped, done: make(chan struct{})}
} else {
entry.connectStats.add(connAdjust, 1)
}
}
entry.state = psNotConnected // Block until disconnection request is served.
if entry.knownSelected { pool.disconnCh <- req
pool.knownSelected-- <-req.done
} else {
pool.newSelected--
}
pool.setRetryDial(entry)
pool.connWg.Done()
} }
const ( const (
@ -277,25 +264,51 @@ func (pool *serverPool) eventLoop() {
if pool.discSetPeriod != nil { if pool.discSetPeriod != nil {
pool.discSetPeriod <- time.Millisecond * 100 pool.discSetPeriod <- time.Millisecond * 100
} }
// disconnect updates service quality statistics depending on the connection time
// and disconnection initiator.
disconnect := func(req *disconnReq, stopped bool) {
// Handle peer disconnection requests.
entry := req.entry
if entry.state == psRegistered {
connAdjust := float64(mclock.Now()-entry.regTime) / float64(targetConnTime)
if connAdjust > 1 {
connAdjust = 1
}
if stopped {
// disconnect requested by ourselves.
entry.connectStats.add(1, connAdjust)
} else {
// disconnect requested by server side.
entry.connectStats.add(connAdjust, 1)
}
}
entry.state = psNotConnected
if entry.knownSelected {
pool.knownSelected--
} else {
pool.newSelected--
}
pool.setRetryDial(entry)
pool.connWg.Done()
close(req.done)
}
for { for {
select { select {
case entry := <-pool.timeout: case entry := <-pool.timeout:
pool.lock.Lock()
if !entry.removed { if !entry.removed {
pool.checkDialTimeout(entry) pool.checkDialTimeout(entry)
} }
pool.lock.Unlock()
case entry := <-pool.enableRetry: case entry := <-pool.enableRetry:
pool.lock.Lock()
if !entry.removed { if !entry.removed {
entry.delayedRetry = false entry.delayedRetry = false
pool.updateCheckDial(entry) pool.updateCheckDial(entry)
} }
pool.lock.Unlock()
case adj := <-pool.adjustStats: case adj := <-pool.adjustStats:
pool.lock.Lock()
switch adj.adjustType { switch adj.adjustType {
case pseBlockDelay: case pseBlockDelay:
adj.entry.delayStats.add(float64(adj.time), 1) adj.entry.delayStats.add(float64(adj.time), 1)
@ -305,13 +318,10 @@ func (pool *serverPool) eventLoop() {
case pseResponseTimeout: case pseResponseTimeout:
adj.entry.timeoutStats.add(1, 1) adj.entry.timeoutStats.add(1, 1)
} }
pool.lock.Unlock()
case node := <-pool.discNodes: case node := <-pool.discNodes:
pool.lock.Lock()
entry := pool.findOrNewNode(discover.NodeID(node.ID), node.IP, node.TCP) entry := pool.findOrNewNode(discover.NodeID(node.ID), node.IP, node.TCP)
pool.updateCheckDial(entry) pool.updateCheckDial(entry)
pool.lock.Unlock()
case conv := <-pool.discLookups: case conv := <-pool.discLookups:
if conv { if conv {
@ -327,15 +337,66 @@ func (pool *serverPool) eventLoop() {
} }
} }
case req := <-pool.connCh:
// Handle peer connection requests.
entry := pool.entries[req.p.ID()]
if entry == nil {
entry = pool.findOrNewNode(req.p.ID(), req.ip, req.port)
}
if entry.state == psConnected || entry.state == psRegistered {
req.result <- nil
continue
}
pool.connWg.Add(1)
entry.peer = req.p
entry.state = psConnected
addr := &poolEntryAddress{
ip: req.ip,
port: req.port,
lastSeen: mclock.Now(),
}
entry.lastConnected = addr
entry.addr = make(map[string]*poolEntryAddress)
entry.addr[addr.strKey()] = addr
entry.addrSelect = *newWeightedRandomSelect()
entry.addrSelect.update(addr)
req.result <- entry
case req := <-pool.registerCh:
// Handle peer registration requests.
entry := req.entry
entry.state = psRegistered
entry.regTime = mclock.Now()
if !entry.known {
pool.newQueue.remove(entry)
entry.known = true
}
pool.knownQueue.setLatest(entry)
entry.shortRetry = shortRetryCnt
close(req.done)
case req := <-pool.disconnCh:
// Handle peer disconnection requests.
disconnect(req, req.stopped)
case <-pool.quit: case <-pool.quit:
if pool.discSetPeriod != nil { if pool.discSetPeriod != nil {
close(pool.discSetPeriod) close(pool.discSetPeriod)
} }
// Spawn a goroutine to close the disconnCh after all connections are disconnected.
go func() {
pool.connWg.Wait() pool.connWg.Wait()
close(pool.disconnCh)
}()
// Handle all remaining disconnection requests before exit.
for req := range pool.disconnCh {
disconnect(req, true)
}
pool.saveNodes() pool.saveNodes()
pool.wg.Done() pool.wg.Done()
return return
} }
} }
} }

View file

@ -59,18 +59,18 @@ type trustedCheckpoint struct {
var ( var (
mainnetCheckpoint = trustedCheckpoint{ mainnetCheckpoint = trustedCheckpoint{
name: "mainnet", name: "mainnet",
sectionIdx: 174, sectionIdx: 179,
sectionHead: common.HexToHash("a3ef48cd8f1c3a08419f0237fc7763491fe89497b3144b17adf87c1c43664613"), sectionHead: common.HexToHash("ae778e455492db1183e566fa0c67f954d256fdd08618f6d5a393b0e24576d0ea"),
chtRoot: common.HexToHash("dcbeed9f4dea1b3cb75601bb27c51b9960c28e5850275402ac49a150a667296e"), chtRoot: common.HexToHash("646b338f9ca74d936225338916be53710ec84020b89946004a8605f04c817f16"),
bloomTrieRoot: common.HexToHash("6b7497a4a03e33870a2383cb6f5e70570f12b1bf5699063baf8c71d02ca90b02"), bloomTrieRoot: common.HexToHash("d0f978f5dbc86e5bf931d8dd5b2ecbebbda6dc78f8896af6a27b46a3ced0ac25"),
} }
ropstenCheckpoint = trustedCheckpoint{ ropstenCheckpoint = trustedCheckpoint{
name: "ropsten", name: "ropsten",
sectionIdx: 102, sectionIdx: 107,
sectionHead: common.HexToHash("9017ab08465cb2b2dee035ee5b817bbd7fa28e2c8d2cd903e0aed1cccb249e89"), sectionHead: common.HexToHash("e1988f95399debf45b873e065e5cd61b416ef2e2e5deec5a6f87c3127086e1ce"),
chtRoot: common.HexToHash("f61c10a7a787a5ef15f0ae1ae6c13c64331e57e79d0466d2bd9b0c06833fe956"), chtRoot: common.HexToHash("15cba18e4de0ab1e95e202625199ba30147aec8b0b70384b66ebea31ba6a18e0"),
bloomTrieRoot: common.HexToHash("69f2ad19aa46d5213a90137b3d2c9bff8a7c9483f7170f0125096ff450c9a873"), bloomTrieRoot: common.HexToHash("e00fa6389b2e597d9df52172cd8e936879eed0fca4fa59db99e2c8ed682562f2"),
} }
) )

View file

@ -199,15 +199,17 @@ func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number
// rollbackTxs marks the transactions contained in recently rolled back blocks // rollbackTxs marks the transactions contained in recently rolled back blocks
// as rolled back. It also removes any positional lookup entries. // as rolled back. It also removes any positional lookup entries.
func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) { func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) {
batch := pool.chainDb.NewBatch()
if list, ok := pool.mined[hash]; ok { if list, ok := pool.mined[hash]; ok {
for _, tx := range list { for _, tx := range list {
txHash := tx.Hash() txHash := tx.Hash()
rawdb.DeleteTxLookupEntry(pool.chainDb, txHash) rawdb.DeleteTxLookupEntry(batch, txHash)
pool.pending[txHash] = tx pool.pending[txHash] = tx
txc.setState(txHash, false) txc.setState(txHash, false)
} }
delete(pool.mined, hash) delete(pool.mined, hash)
} }
batch.Write()
} }
// reorgOnNewHead sets a new head header, processing (and rolling back if necessary) // reorgOnNewHead sets a new head header, processing (and rolling back if necessary)
@ -504,14 +506,16 @@ func (self *TxPool) Content() (map[common.Address]types.Transactions, map[common
func (self *TxPool) RemoveTransactions(txs types.Transactions) { func (self *TxPool) RemoveTransactions(txs types.Transactions) {
self.mu.Lock() self.mu.Lock()
defer self.mu.Unlock() defer self.mu.Unlock()
var hashes []common.Hash var hashes []common.Hash
batch := self.chainDb.NewBatch()
for _, tx := range txs { for _, tx := range txs {
//self.RemoveTx(tx.Hash())
hash := tx.Hash() hash := tx.Hash()
delete(self.pending, hash) delete(self.pending, hash)
self.chainDb.Delete(hash[:]) batch.Delete(hash.Bytes())
hashes = append(hashes, hash) hashes = append(hashes, hash)
} }
batch.Write()
self.relay.Discard(hashes) self.relay.Discard(hashes)
} }

View file

@ -15,7 +15,7 @@ import (
const ( const (
timeFormat = "2006-01-02T15:04:05-0700" timeFormat = "2006-01-02T15:04:05-0700"
termTimeFormat = "01-02|15:04:05.999999" termTimeFormat = "01-02|15:04:05.000"
floatFormat = 'f' floatFormat = 'f'
termMsgJust = 40 termMsgJust = 40
) )

View file

@ -2,10 +2,10 @@ package influxdb
import ( import (
"fmt" "fmt"
"log"
uurl "net/url" uurl "net/url"
"time" "time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/influxdata/influxdb/client" "github.com/influxdata/influxdb/client"
) )
@ -35,7 +35,7 @@ func InfluxDB(r metrics.Registry, d time.Duration, url, database, username, pass
func InfluxDBWithTags(r metrics.Registry, d time.Duration, url, database, username, password, namespace string, tags map[string]string) { func InfluxDBWithTags(r metrics.Registry, d time.Duration, url, database, username, password, namespace string, tags map[string]string) {
u, err := uurl.Parse(url) u, err := uurl.Parse(url)
if err != nil { if err != nil {
log.Printf("unable to parse InfluxDB url %s. err=%v", url, err) log.Warn("Unable to parse InfluxDB", "url", url, "err", err)
return return
} }
@ -51,7 +51,7 @@ func InfluxDBWithTags(r metrics.Registry, d time.Duration, url, database, userna
cache: make(map[string]int64), cache: make(map[string]int64),
} }
if err := rep.makeClient(); err != nil { if err := rep.makeClient(); err != nil {
log.Printf("unable to make InfluxDB client. err=%v", err) log.Warn("Unable to make InfluxDB client", "err", err)
return return
} }
@ -76,15 +76,15 @@ func (r *reporter) run() {
select { select {
case <-intervalTicker: case <-intervalTicker:
if err := r.send(); err != nil { if err := r.send(); err != nil {
log.Printf("unable to send to InfluxDB. err=%v", err) log.Warn("Unable to send to InfluxDB", "err", err)
} }
case <-pingTicker: case <-pingTicker:
_, _, err := r.client.Ping() _, _, err := r.client.Ping()
if err != nil { if err != nil {
log.Printf("got error while sending a ping to InfluxDB, trying to recreate client. err=%v", err) log.Warn("Got error while sending a ping to InfluxDB, trying to recreate client", "err", err)
if err = r.makeClient(); err != nil { if err = r.makeClient(); err != nil {
log.Printf("unable to make InfluxDB client. err=%v", err) log.Warn("Unable to make InfluxDB client", "err", err)
} }
} }
} }

View file

@ -58,11 +58,14 @@ func CollectProcessMetrics(refresh time.Duration) {
memPauses := GetOrRegisterMeter("system/memory/pauses", DefaultRegistry) memPauses := GetOrRegisterMeter("system/memory/pauses", DefaultRegistry)
var diskReads, diskReadBytes, diskWrites, diskWriteBytes Meter var diskReads, diskReadBytes, diskWrites, diskWriteBytes Meter
var diskReadBytesCounter, diskWriteBytesCounter Counter
if err := ReadDiskStats(diskstats[0]); err == nil { if err := ReadDiskStats(diskstats[0]); err == nil {
diskReads = GetOrRegisterMeter("system/disk/readcount", DefaultRegistry) diskReads = GetOrRegisterMeter("system/disk/readcount", DefaultRegistry)
diskReadBytes = GetOrRegisterMeter("system/disk/readdata", DefaultRegistry) diskReadBytes = GetOrRegisterMeter("system/disk/readdata", DefaultRegistry)
diskReadBytesCounter = GetOrRegisterCounter("system/disk/readbytes", DefaultRegistry)
diskWrites = GetOrRegisterMeter("system/disk/writecount", DefaultRegistry) diskWrites = GetOrRegisterMeter("system/disk/writecount", DefaultRegistry)
diskWriteBytes = GetOrRegisterMeter("system/disk/writedata", DefaultRegistry) diskWriteBytes = GetOrRegisterMeter("system/disk/writedata", DefaultRegistry)
diskWriteBytesCounter = GetOrRegisterCounter("system/disk/writebytes", DefaultRegistry)
} else { } else {
log.Debug("Failed to read disk metrics", "err", err) log.Debug("Failed to read disk metrics", "err", err)
} }
@ -82,6 +85,9 @@ func CollectProcessMetrics(refresh time.Duration) {
diskReadBytes.Mark(diskstats[location1].ReadBytes - diskstats[location2].ReadBytes) diskReadBytes.Mark(diskstats[location1].ReadBytes - diskstats[location2].ReadBytes)
diskWrites.Mark(diskstats[location1].WriteCount - diskstats[location2].WriteCount) diskWrites.Mark(diskstats[location1].WriteCount - diskstats[location2].WriteCount)
diskWriteBytes.Mark(diskstats[location1].WriteBytes - diskstats[location2].WriteBytes) diskWriteBytes.Mark(diskstats[location1].WriteBytes - diskstats[location2].WriteBytes)
diskReadBytesCounter.Inc(diskstats[location1].ReadBytes - diskstats[location2].ReadBytes)
diskWriteBytesCounter.Inc(diskstats[location1].WriteBytes - diskstats[location2].WriteBytes)
} }
time.Sleep(refresh) time.Sleep(refresh)
} }

View file

@ -125,12 +125,12 @@ func (t *Topics) Append(topics *Hashes) {
t.topics = append(t.topics, topics.hashes) t.topics = append(t.topics, topics.hashes)
} }
// FilterQuery contains options for contact log filtering. // FilterQuery contains options for contract log filtering.
type FilterQuery struct { type FilterQuery struct {
query ethereum.FilterQuery query ethereum.FilterQuery
} }
// NewFilterQuery creates an empty filter query for contact log filtering. // NewFilterQuery creates an empty filter query for contract log filtering.
func NewFilterQuery() *FilterQuery { func NewFilterQuery() *FilterQuery {
return new(FilterQuery) return new(FilterQuery)
} }

View file

@ -193,7 +193,7 @@ func (n *Node) Start() error {
return n.node.Start() return n.node.Start()
} }
// Stop terminates a running node along with all it's services. In the node was // Stop terminates a running node along with all it's services. If the node was
// not started, an error is returned. // not started, an error is returned.
func (n *Node) Stop() error { func (n *Node) Stop() error {
return n.node.Stop() return n.node.Stop()

View file

@ -343,13 +343,13 @@ func (api *PublicDebugAPI) Metrics(raw bool) (map[string]interface{}, error) {
ps := t.Percentiles([]float64{5, 20, 50, 80, 95}) ps := t.Percentiles([]float64{5, 20, 50, 80, 95})
root[name] = map[string]interface{}{ root[name] = map[string]interface{}{
"Measurements": len(t.Values()), "Measurements": len(t.Values()),
"Mean": time.Duration(t.Mean()).String(), "Mean": t.Mean(),
"Percentiles": map[string]interface{}{ "Percentiles": map[string]interface{}{
"5": time.Duration(ps[0]).String(), "5": ps[0],
"20": time.Duration(ps[1]).String(), "20": ps[1],
"50": time.Duration(ps[2]).String(), "50": ps[2],
"80": time.Duration(ps[3]).String(), "80": ps[3],
"95": time.Duration(ps[4]).String(), "95": ps[4],
}, },
} }

View file

@ -69,7 +69,7 @@ unless its location is changed through the KeyStoreDir configuration option.
Data Directory Sharing Example Data Directory Sharing Example
In this example, two node instances named A and B are started with the same data In this example, two node instances named A and B are started with the same data
directory. Mode instance A opens the database "db", node instance B opens the databases directory. Node instance A opens the database "db", node instance B opens the databases
"db" and "db-2". The following files will be created in the data directory: "db" and "db-2". The following files will be created in the data directory:
data-directory/ data-directory/

View file

@ -42,6 +42,7 @@ var (
nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element. nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element.
nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
nodeDBCleanupCycle = time.Hour // Time period for running the expiration task. nodeDBCleanupCycle = time.Hour // Time period for running the expiration task.
nodeDBVersion = 5
) )
// nodeDB stores all nodes we know about. // nodeDB stores all nodes we know about.
@ -257,7 +258,7 @@ func (db *nodeDB) expireNodes() error {
} }
// Skip the node if not expired yet (and not self) // Skip the node if not expired yet (and not self)
if !bytes.Equal(id[:], db.self[:]) { if !bytes.Equal(id[:], db.self[:]) {
if seen := db.bondTime(id); seen.After(threshold) { if seen := db.lastPongReceived(id); seen.After(threshold) {
continue continue
} }
} }
@ -267,29 +268,28 @@ func (db *nodeDB) expireNodes() error {
return nil return nil
} }
// lastPing retrieves the time of the last ping packet send to a remote node, // lastPingReceived retrieves the time of the last ping packet sent by the remote node.
// requesting binding. func (db *nodeDB) lastPingReceived(id NodeID) time.Time {
func (db *nodeDB) lastPing(id NodeID) time.Time {
return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0) return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0)
} }
// updateLastPing updates the last time we tried contacting a remote node. // updateLastPing updates the last time remote node pinged us.
func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error { func (db *nodeDB) updateLastPingReceived(id NodeID, instance time.Time) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix()) return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix())
} }
// bondTime retrieves the time of the last successful pong from remote node. // lastPongReceived retrieves the time of the last successful pong from remote node.
func (db *nodeDB) bondTime(id NodeID) time.Time { func (db *nodeDB) lastPongReceived(id NodeID) time.Time {
return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0) return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0)
} }
// hasBond reports whether the given node is considered bonded. // hasBond reports whether the given node is considered bonded.
func (db *nodeDB) hasBond(id NodeID) bool { func (db *nodeDB) hasBond(id NodeID) bool {
return time.Since(db.bondTime(id)) < nodeDBNodeExpiration return time.Since(db.lastPongReceived(id)) < nodeDBNodeExpiration
} }
// updateBondTime updates the last pong time of a node. // updateLastPongReceived updates the last pong time of a node.
func (db *nodeDB) updateBondTime(id NodeID, instance time.Time) error { func (db *nodeDB) updateLastPongReceived(id NodeID, instance time.Time) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix()) return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
} }
@ -332,7 +332,7 @@ seek:
if n.ID == db.self { if n.ID == db.self {
continue seek continue seek
} }
if now.Sub(db.bondTime(n.ID)) > maxAge { if now.Sub(db.lastPongReceived(n.ID)) > maxAge {
continue seek continue seek
} }
for i := range nodes { for i := range nodes {

View file

@ -79,7 +79,7 @@ var nodeDBInt64Tests = []struct {
} }
func TestNodeDBInt64(t *testing.T) { func TestNodeDBInt64(t *testing.T) {
db, _ := newNodeDB("", Version, NodeID{}) db, _ := newNodeDB("", nodeDBVersion, NodeID{})
defer db.close() defer db.close()
tests := nodeDBInt64Tests tests := nodeDBInt64Tests
@ -111,27 +111,27 @@ func TestNodeDBFetchStore(t *testing.T) {
inst := time.Now() inst := time.Now()
num := 314 num := 314
db, _ := newNodeDB("", Version, NodeID{}) db, _ := newNodeDB("", nodeDBVersion, NodeID{})
defer db.close() defer db.close()
// Check fetch/store operations on a node ping object // Check fetch/store operations on a node ping object
if stored := db.lastPing(node.ID); stored.Unix() != 0 { if stored := db.lastPingReceived(node.ID); stored.Unix() != 0 {
t.Errorf("ping: non-existing object: %v", stored) t.Errorf("ping: non-existing object: %v", stored)
} }
if err := db.updateLastPing(node.ID, inst); err != nil { if err := db.updateLastPingReceived(node.ID, inst); err != nil {
t.Errorf("ping: failed to update: %v", err) t.Errorf("ping: failed to update: %v", err)
} }
if stored := db.lastPing(node.ID); stored.Unix() != inst.Unix() { if stored := db.lastPingReceived(node.ID); stored.Unix() != inst.Unix() {
t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) t.Errorf("ping: value mismatch: have %v, want %v", stored, inst)
} }
// Check fetch/store operations on a node pong object // Check fetch/store operations on a node pong object
if stored := db.bondTime(node.ID); stored.Unix() != 0 { if stored := db.lastPongReceived(node.ID); stored.Unix() != 0 {
t.Errorf("pong: non-existing object: %v", stored) t.Errorf("pong: non-existing object: %v", stored)
} }
if err := db.updateBondTime(node.ID, inst); err != nil { if err := db.updateLastPongReceived(node.ID, inst); err != nil {
t.Errorf("pong: failed to update: %v", err) t.Errorf("pong: failed to update: %v", err)
} }
if stored := db.bondTime(node.ID); stored.Unix() != inst.Unix() { if stored := db.lastPongReceived(node.ID); stored.Unix() != inst.Unix() {
t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) t.Errorf("pong: value mismatch: have %v, want %v", stored, inst)
} }
// Check fetch/store operations on a node findnode-failure object // Check fetch/store operations on a node findnode-failure object
@ -216,7 +216,7 @@ var nodeDBSeedQueryNodes = []struct {
} }
func TestNodeDBSeedQuery(t *testing.T) { func TestNodeDBSeedQuery(t *testing.T) {
db, _ := newNodeDB("", Version, nodeDBSeedQueryNodes[1].node.ID) db, _ := newNodeDB("", nodeDBVersion, nodeDBSeedQueryNodes[1].node.ID)
defer db.close() defer db.close()
// Insert a batch of nodes for querying // Insert a batch of nodes for querying
@ -224,7 +224,7 @@ func TestNodeDBSeedQuery(t *testing.T) {
if err := db.updateNode(seed.node); err != nil { if err := db.updateNode(seed.node); err != nil {
t.Fatalf("node %d: failed to insert: %v", i, err) t.Fatalf("node %d: failed to insert: %v", i, err)
} }
if err := db.updateBondTime(seed.node.ID, seed.pong); err != nil { if err := db.updateLastPongReceived(seed.node.ID, seed.pong); err != nil {
t.Fatalf("node %d: failed to insert bondTime: %v", i, err) t.Fatalf("node %d: failed to insert bondTime: %v", i, err)
} }
} }
@ -267,7 +267,7 @@ func TestNodeDBPersistency(t *testing.T) {
) )
// Create a persistent database and store some values // Create a persistent database and store some values
db, err := newNodeDB(filepath.Join(root, "database"), Version, NodeID{}) db, err := newNodeDB(filepath.Join(root, "database"), nodeDBVersion, NodeID{})
if err != nil { if err != nil {
t.Fatalf("failed to create persistent database: %v", err) t.Fatalf("failed to create persistent database: %v", err)
} }
@ -277,7 +277,7 @@ func TestNodeDBPersistency(t *testing.T) {
db.close() db.close()
// Reopen the database and check the value // Reopen the database and check the value
db, err = newNodeDB(filepath.Join(root, "database"), Version, NodeID{}) db, err = newNodeDB(filepath.Join(root, "database"), nodeDBVersion, NodeID{})
if err != nil { if err != nil {
t.Fatalf("failed to open persistent database: %v", err) t.Fatalf("failed to open persistent database: %v", err)
} }
@ -287,7 +287,7 @@ func TestNodeDBPersistency(t *testing.T) {
db.close() db.close()
// Change the database version and check flush // Change the database version and check flush
db, err = newNodeDB(filepath.Join(root, "database"), Version+1, NodeID{}) db, err = newNodeDB(filepath.Join(root, "database"), nodeDBVersion+1, NodeID{})
if err != nil { if err != nil {
t.Fatalf("failed to open persistent database: %v", err) t.Fatalf("failed to open persistent database: %v", err)
} }
@ -324,7 +324,7 @@ var nodeDBExpirationNodes = []struct {
} }
func TestNodeDBExpiration(t *testing.T) { func TestNodeDBExpiration(t *testing.T) {
db, _ := newNodeDB("", Version, NodeID{}) db, _ := newNodeDB("", nodeDBVersion, NodeID{})
defer db.close() defer db.close()
// Add all the test nodes and set their last pong time // Add all the test nodes and set their last pong time
@ -332,7 +332,7 @@ func TestNodeDBExpiration(t *testing.T) {
if err := db.updateNode(seed.node); err != nil { if err := db.updateNode(seed.node); err != nil {
t.Fatalf("node %d: failed to insert: %v", i, err) t.Fatalf("node %d: failed to insert: %v", i, err)
} }
if err := db.updateBondTime(seed.node.ID, seed.pong); err != nil { if err := db.updateLastPongReceived(seed.node.ID, seed.pong); err != nil {
t.Fatalf("node %d: failed to update bondTime: %v", i, err) t.Fatalf("node %d: failed to update bondTime: %v", i, err)
} }
} }
@ -357,7 +357,7 @@ func TestNodeDBSelfExpiration(t *testing.T) {
break break
} }
} }
db, _ := newNodeDB("", Version, self) db, _ := newNodeDB("", nodeDBVersion, self)
defer db.close() defer db.close()
// Add all the test nodes and set their last pong time // Add all the test nodes and set their last pong time
@ -365,7 +365,7 @@ func TestNodeDBSelfExpiration(t *testing.T) {
if err := db.updateNode(seed.node); err != nil { if err := db.updateNode(seed.node); err != nil {
t.Fatalf("node %d: failed to insert: %v", i, err) t.Fatalf("node %d: failed to insert: %v", i, err)
} }
if err := db.updateBondTime(seed.node.ID, seed.pong); err != nil { if err := db.updateLastPongReceived(seed.node.ID, seed.pong); err != nil {
t.Fatalf("node %d: failed to update bondTime: %v", i, err) t.Fatalf("node %d: failed to update bondTime: %v", i, err)
} }
} }

View file

@ -25,7 +25,6 @@ package discover
import ( import (
crand "crypto/rand" crand "crypto/rand"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
mrand "math/rand" mrand "math/rand"
"net" "net"
@ -54,9 +53,7 @@ const (
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24 bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
tableIPLimit, tableSubnet = 10, 24 tableIPLimit, tableSubnet = 10, 24
maxBondingPingPongs = 16 // Limit on the number of concurrent ping/pong interactions
maxFindnodeFailures = 5 // Nodes exceeding this limit are dropped maxFindnodeFailures = 5 // Nodes exceeding this limit are dropped
refreshInterval = 30 * time.Minute refreshInterval = 30 * time.Minute
revalidateInterval = 10 * time.Second revalidateInterval = 10 * time.Second
copyNodesInterval = 30 * time.Second copyNodesInterval = 30 * time.Second
@ -78,28 +75,17 @@ type Table struct {
closeReq chan struct{} closeReq chan struct{}
closed chan struct{} closed chan struct{}
bondmu sync.Mutex
bonding map[NodeID]*bondproc
bondslots chan struct{} // limits total number of active bonding processes
nodeAddedHook func(*Node) // for testing nodeAddedHook func(*Node) // for testing
net transport net transport
self *Node // metadata of the local node self *Node // metadata of the local node
} }
type bondproc struct {
err error
n *Node
done chan struct{}
}
// transport is implemented by the UDP transport. // transport is implemented by the UDP transport.
// it is an interface so we can test without opening lots of UDP // it is an interface so we can test without opening lots of UDP
// sockets and without generating a private key. // sockets and without generating a private key.
type transport interface { type transport interface {
ping(NodeID, *net.UDPAddr) error ping(NodeID, *net.UDPAddr) error
waitping(NodeID) error
findnode(toid NodeID, addr *net.UDPAddr, target NodeID) ([]*Node, error) findnode(toid NodeID, addr *net.UDPAddr, target NodeID) ([]*Node, error)
close() close()
} }
@ -114,7 +100,7 @@ type bucket struct {
func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string, bootnodes []*Node) (*Table, error) { func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string, bootnodes []*Node) (*Table, error) {
// If no node database was given, use an in-memory one // If no node database was given, use an in-memory one
db, err := newNodeDB(nodeDBPath, Version, ourID) db, err := newNodeDB(nodeDBPath, nodeDBVersion, ourID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -122,8 +108,6 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string
net: t, net: t,
db: db, db: db,
self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)), self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)),
bonding: make(map[NodeID]*bondproc),
bondslots: make(chan struct{}, maxBondingPingPongs),
refreshReq: make(chan chan struct{}), refreshReq: make(chan chan struct{}),
initDone: make(chan struct{}), initDone: make(chan struct{}),
closeReq: make(chan struct{}), closeReq: make(chan struct{}),
@ -134,16 +118,13 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string
if err := tab.setFallbackNodes(bootnodes); err != nil { if err := tab.setFallbackNodes(bootnodes); err != nil {
return nil, err return nil, err
} }
for i := 0; i < cap(tab.bondslots); i++ {
tab.bondslots <- struct{}{}
}
for i := range tab.buckets { for i := range tab.buckets {
tab.buckets[i] = &bucket{ tab.buckets[i] = &bucket{
ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit}, ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
} }
} }
tab.seedRand() tab.seedRand()
tab.loadSeedNodes(false) tab.loadSeedNodes()
// Start the background expiration goroutine after loading seeds so that the search for // Start the background expiration goroutine after loading seeds so that the search for
// seed nodes also considers older nodes that would otherwise be removed by the // seed nodes also considers older nodes that would otherwise be removed by the
// expiration. // expiration.
@ -315,22 +296,7 @@ func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node {
if !asked[n.ID] { if !asked[n.ID] {
asked[n.ID] = true asked[n.ID] = true
pendingQueries++ pendingQueries++
go func() { go tab.findnode(n, targetID, reply)
// Find potential neighbors to bond with
r, err := tab.net.findnode(n.ID, n.addr(), targetID)
if err != nil {
// Bump the failure counter to detect and evacuate non-bonded entries
fails := tab.db.findFails(n.ID) + 1
tab.db.updateFindFails(n.ID, fails)
log.Trace("Bumping findnode failure counter", "id", n.ID, "failcount", fails)
if fails >= maxFindnodeFailures {
log.Trace("Too many findnode failures, dropping", "id", n.ID, "failcount", fails)
tab.delete(n)
}
}
reply <- tab.bondall(r)
}()
} }
} }
if pendingQueries == 0 { if pendingQueries == 0 {
@ -349,6 +315,29 @@ func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node {
return result.entries return result.entries
} }
func (tab *Table) findnode(n *Node, targetID NodeID, reply chan<- []*Node) {
fails := tab.db.findFails(n.ID)
r, err := tab.net.findnode(n.ID, n.addr(), targetID)
if err != nil || len(r) == 0 {
fails++
tab.db.updateFindFails(n.ID, fails)
log.Trace("Findnode failed", "id", n.ID, "failcount", fails, "err", err)
if fails >= maxFindnodeFailures {
log.Trace("Too many findnode failures, dropping", "id", n.ID, "failcount", fails)
tab.delete(n)
}
} else if fails > 0 {
tab.db.updateFindFails(n.ID, fails-1)
}
// Grab as many nodes as possible. Some of them might not be alive anymore, but we'll
// just remove those again during revalidation.
for _, n := range r {
tab.add(n)
}
reply <- r
}
func (tab *Table) refresh() <-chan struct{} { func (tab *Table) refresh() <-chan struct{} {
done := make(chan struct{}) done := make(chan struct{})
select { select {
@ -401,7 +390,7 @@ loop:
case <-revalidateDone: case <-revalidateDone:
revalidate.Reset(tab.nextRevalidateTime()) revalidate.Reset(tab.nextRevalidateTime())
case <-copyNodes.C: case <-copyNodes.C:
go tab.copyBondedNodes() go tab.copyLiveNodes()
case <-tab.closeReq: case <-tab.closeReq:
break loop break loop
} }
@ -429,7 +418,7 @@ func (tab *Table) doRefresh(done chan struct{}) {
// Load nodes from the database and insert // Load nodes from the database and insert
// them. This should yield a few previously seen nodes that are // them. This should yield a few previously seen nodes that are
// (hopefully) still alive. // (hopefully) still alive.
tab.loadSeedNodes(true) tab.loadSeedNodes()
// Run self lookup to discover new neighbor nodes. // Run self lookup to discover new neighbor nodes.
tab.lookup(tab.self.ID, false) tab.lookup(tab.self.ID, false)
@ -447,15 +436,12 @@ func (tab *Table) doRefresh(done chan struct{}) {
} }
} }
func (tab *Table) loadSeedNodes(bond bool) { func (tab *Table) loadSeedNodes() {
seeds := tab.db.querySeeds(seedCount, seedMaxAge) seeds := tab.db.querySeeds(seedCount, seedMaxAge)
seeds = append(seeds, tab.nursery...) seeds = append(seeds, tab.nursery...)
if bond {
seeds = tab.bondall(seeds)
}
for i := range seeds { for i := range seeds {
seed := seeds[i] seed := seeds[i]
age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.bondTime(seed.ID)) }} age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.lastPongReceived(seed.ID)) }}
log.Debug("Found seed node in database", "id", seed.ID, "addr", seed.addr(), "age", age) log.Debug("Found seed node in database", "id", seed.ID, "addr", seed.addr(), "age", age)
tab.add(seed) tab.add(seed)
} }
@ -473,7 +459,7 @@ func (tab *Table) doRevalidate(done chan<- struct{}) {
} }
// Ping the selected node and wait for a pong. // Ping the selected node and wait for a pong.
err := tab.ping(last.ID, last.addr()) err := tab.net.ping(last.ID, last.addr())
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
@ -515,9 +501,9 @@ func (tab *Table) nextRevalidateTime() time.Duration {
return time.Duration(tab.rand.Int63n(int64(revalidateInterval))) return time.Duration(tab.rand.Int63n(int64(revalidateInterval)))
} }
// copyBondedNodes adds nodes from the table to the database if they have been in the table // copyLiveNodes adds nodes from the table to the database if they have been in the table
// longer then minTableTime. // longer then minTableTime.
func (tab *Table) copyBondedNodes() { func (tab *Table) copyLiveNodes() {
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
@ -553,120 +539,6 @@ func (tab *Table) len() (n int) {
return n return n
} }
// bondall bonds with all given nodes concurrently and returns
// those nodes for which bonding has probably succeeded.
func (tab *Table) bondall(nodes []*Node) (result []*Node) {
rc := make(chan *Node, len(nodes))
for i := range nodes {
go func(n *Node) {
nn, _ := tab.bond(false, n.ID, n.addr(), n.TCP)
rc <- nn
}(nodes[i])
}
for range nodes {
if n := <-rc; n != nil {
result = append(result, n)
}
}
return result
}
// bond ensures the local node has a bond with the given remote node.
// It also attempts to insert the node into the table if bonding succeeds.
// The caller must not hold tab.mutex.
//
// A bond is must be established before sending findnode requests.
// Both sides must have completed a ping/pong exchange for a bond to
// exist. The total number of active bonding processes is limited in
// order to restrain network use.
//
// bond is meant to operate idempotently in that bonding with a remote
// node which still remembers a previously established bond will work.
// The remote node will simply not send a ping back, causing waitping
// to time out.
//
// If pinged is true, the remote node has just pinged us and one half
// of the process can be skipped.
func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) (*Node, error) {
if id == tab.self.ID {
return nil, errors.New("is self")
}
if pinged && !tab.isInitDone() {
return nil, errors.New("still initializing")
}
// Start bonding if we haven't seen this node for a while or if it failed findnode too often.
node, fails := tab.db.node(id), tab.db.findFails(id)
age := time.Since(tab.db.bondTime(id))
var result error
if fails > 0 || age > nodeDBNodeExpiration {
log.Trace("Starting bonding ping/pong", "id", id, "known", node != nil, "failcount", fails, "age", age)
tab.bondmu.Lock()
w := tab.bonding[id]
if w != nil {
// Wait for an existing bonding process to complete.
tab.bondmu.Unlock()
<-w.done
} else {
// Register a new bonding process.
w = &bondproc{done: make(chan struct{})}
tab.bonding[id] = w
tab.bondmu.Unlock()
// Do the ping/pong. The result goes into w.
tab.pingpong(w, pinged, id, addr, tcpPort)
// Unregister the process after it's done.
tab.bondmu.Lock()
delete(tab.bonding, id)
tab.bondmu.Unlock()
}
// Retrieve the bonding results
result = w.err
if result == nil {
node = w.n
}
}
// Add the node to the table even if the bonding ping/pong
// fails. It will be relaced quickly if it continues to be
// unresponsive.
if node != nil {
tab.add(node)
tab.db.updateFindFails(id, 0)
}
return node, result
}
func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) {
// Request a bonding slot to limit network usage
<-tab.bondslots
defer func() { tab.bondslots <- struct{}{} }()
// Ping the remote side and wait for a pong.
if w.err = tab.ping(id, addr); w.err != nil {
close(w.done)
return
}
if !pinged {
// Give the remote node a chance to ping us before we start
// sending findnode requests. If they still remember us,
// waitping will simply time out.
tab.net.waitping(id)
}
// Bonding succeeded, update the node database.
w.n = NewNode(id, addr.IP, uint16(addr.Port), tcpPort)
close(w.done)
}
// ping a remote endpoint and wait for a reply, also updating the node
// database accordingly.
func (tab *Table) ping(id NodeID, addr *net.UDPAddr) error {
tab.db.updateLastPing(id, time.Now())
if err := tab.net.ping(id, addr); err != nil {
return err
}
tab.db.updateBondTime(id, time.Now())
return nil
}
// bucket returns the bucket for the given node ID hash. // bucket returns the bucket for the given node ID hash.
func (tab *Table) bucket(sha common.Hash) *bucket { func (tab *Table) bucket(sha common.Hash) *bucket {
d := logdist(tab.self.sha, sha) d := logdist(tab.self.sha, sha)
@ -676,23 +548,35 @@ func (tab *Table) bucket(sha common.Hash) *bucket {
return tab.buckets[d-bucketMinDistance-1] return tab.buckets[d-bucketMinDistance-1]
} }
// add attempts to add the given node its corresponding bucket. If the // add attempts to add the given node to its corresponding bucket. If the bucket has space
// bucket has space available, adding the node succeeds immediately. // available, adding the node succeeds immediately. Otherwise, the node is added if the
// Otherwise, the node is added if the least recently active node in // least recently active node in the bucket does not respond to a ping packet.
// the bucket does not respond to a ping packet.
// //
// The caller must not hold tab.mutex. // The caller must not hold tab.mutex.
func (tab *Table) add(new *Node) { func (tab *Table) add(n *Node) {
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
b := tab.bucket(new.sha) b := tab.bucket(n.sha)
if !tab.bumpOrAdd(b, new) { if !tab.bumpOrAdd(b, n) {
// Node is not in table. Add it to the replacement list. // Node is not in table. Add it to the replacement list.
tab.addReplacement(b, new) tab.addReplacement(b, n)
} }
} }
// addThroughPing adds the given node to the table. Compared to plain
// 'add' there is an additional safety measure: if the table is still
// initializing the node is not added. This prevents an attack where the
// table could be filled by just sending ping repeatedly.
//
// The caller must not hold tab.mutex.
func (tab *Table) addThroughPing(n *Node) {
if !tab.isInitDone() {
return
}
tab.add(n)
}
// stuff adds nodes the table to the end of their corresponding bucket // stuff adds nodes the table to the end of their corresponding bucket
// if the bucket is not full. The caller must not hold tab.mutex. // if the bucket is not full. The caller must not hold tab.mutex.
func (tab *Table) stuff(nodes []*Node) { func (tab *Table) stuff(nodes []*Node) {
@ -710,8 +594,7 @@ func (tab *Table) stuff(nodes []*Node) {
} }
} }
// delete removes an entry from the node table (used to evacuate // delete removes an entry from the node table. It is used to evacuate dead nodes.
// failed/non-bonded discovery peers).
func (tab *Table) delete(node *Node) { func (tab *Table) delete(node *Node) {
tab.mutex.Lock() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()

View file

@ -52,27 +52,22 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil) tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil)
defer tab.Close() defer tab.Close()
// Wait for init so bond is accepted.
<-tab.initDone <-tab.initDone
// fill up the sender's bucket. // Fill up the sender's bucket.
pingSender := NewNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99) pingSender := NewNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99)
last := fillBucket(tab, pingSender) last := fillBucket(tab, pingSender)
// this call to bond should replace the last node // Add the sender as if it just pinged us. Revalidate should replace the last node in
// in its bucket if the node is not responding. // its bucket if it is unresponsive. Revalidate again to ensure that
transport.dead[last.ID] = !lastInBucketIsResponding transport.dead[last.ID] = !lastInBucketIsResponding
transport.dead[pingSender.ID] = !newNodeIsResponding transport.dead[pingSender.ID] = !newNodeIsResponding
tab.bond(true, pingSender.ID, &net.UDPAddr{}, 0) tab.add(pingSender)
tab.doRevalidate(make(chan struct{}, 1))
tab.doRevalidate(make(chan struct{}, 1)) tab.doRevalidate(make(chan struct{}, 1))
// first ping goes to sender (bonding pingback)
if !transport.pinged[pingSender.ID] {
t.Error("table did not ping back sender")
}
if !transport.pinged[last.ID] { if !transport.pinged[last.ID] {
// second ping goes to oldest node in bucket // Oldest node in bucket is pinged to see whether it is still alive.
// to see whether it is still alive.
t.Error("table did not ping last node in bucket") t.Error("table did not ping last node in bucket")
} }
@ -83,7 +78,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
wantSize-- wantSize--
} }
if l := len(tab.bucket(pingSender.sha).entries); l != wantSize { if l := len(tab.bucket(pingSender.sha).entries); l != wantSize {
t.Errorf("wrong bucket size after bond: got %d, want %d", l, wantSize) t.Errorf("wrong bucket size after add: got %d, want %d", l, wantSize)
} }
if found := contains(tab.bucket(pingSender.sha).entries, last.ID); found != lastInBucketIsResponding { if found := contains(tab.bucket(pingSender.sha).entries, last.ID); found != lastInBucketIsResponding {
t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding) t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding)
@ -206,10 +201,7 @@ func newPingRecorder() *pingRecorder {
func (t *pingRecorder) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) { func (t *pingRecorder) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) {
return nil, nil return nil, nil
} }
func (t *pingRecorder) close() {}
func (t *pingRecorder) waitping(from NodeID) error {
return nil // remote always pings
}
func (t *pingRecorder) ping(toid NodeID, toaddr *net.UDPAddr) error { func (t *pingRecorder) ping(toid NodeID, toaddr *net.UDPAddr) error {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
@ -222,6 +214,8 @@ func (t *pingRecorder) ping(toid NodeID, toaddr *net.UDPAddr) error {
} }
} }
func (t *pingRecorder) close() {}
func TestTable_closest(t *testing.T) { func TestTable_closest(t *testing.T) {
t.Parallel() t.Parallel()

View file

@ -32,8 +32,6 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
const Version = 4
// Errors // Errors
var ( var (
errPacketTooSmall = errors.New("too small") errPacketTooSmall = errors.New("too small")
@ -272,21 +270,33 @@ func (t *udp) close() {
// ping sends a ping message to the given node and waits for a reply. // ping sends a ping message to the given node and waits for a reply.
func (t *udp) ping(toid NodeID, toaddr *net.UDPAddr) error { func (t *udp) ping(toid NodeID, toaddr *net.UDPAddr) error {
return <-t.sendPing(toid, toaddr, nil)
}
// sendPing sends a ping message to the given node and invokes the callback
// when the reply arrives.
func (t *udp) sendPing(toid NodeID, toaddr *net.UDPAddr, callback func()) <-chan error {
req := &ping{ req := &ping{
Version: Version, Version: 4,
From: t.ourEndpoint, From: t.ourEndpoint,
To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB
Expiration: uint64(time.Now().Add(expiration).Unix()), Expiration: uint64(time.Now().Add(expiration).Unix()),
} }
packet, hash, err := encodePacket(t.priv, pingPacket, req) packet, hash, err := encodePacket(t.priv, pingPacket, req)
if err != nil { if err != nil {
return err errc := make(chan error, 1)
errc <- err
return errc
} }
errc := t.pending(toid, pongPacket, func(p interface{}) bool { errc := t.pending(toid, pongPacket, func(p interface{}) bool {
return bytes.Equal(p.(*pong).ReplyTok, hash) ok := bytes.Equal(p.(*pong).ReplyTok, hash)
if ok && callback != nil {
callback()
}
return ok
}) })
t.write(toaddr, req.name(), packet) t.write(toaddr, req.name(), packet)
return <-errc return errc
} }
func (t *udp) waitping(from NodeID) error { func (t *udp) waitping(from NodeID) error {
@ -296,6 +306,13 @@ func (t *udp) waitping(from NodeID) error {
// findnode sends a findnode request to the given node and waits until // findnode sends a findnode request to the given node and waits until
// the node has sent up to k neighbors. // the node has sent up to k neighbors.
func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) { func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) {
// If we haven't seen a ping from the destination node for a while, it won't remember
// our endpoint proof and reject findnode. Solicit a ping first.
if time.Since(t.db.lastPingReceived(toid)) > nodeDBNodeExpiration {
t.ping(toid, toaddr)
t.waitping(toid)
}
nodes := make([]*Node, 0, bucketSize) nodes := make([]*Node, 0, bucketSize)
nreceived := 0 nreceived := 0
errc := t.pending(toid, neighborsPacket, func(r interface{}) bool { errc := t.pending(toid, neighborsPacket, func(r interface{}) bool {
@ -315,8 +332,7 @@ func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node
Target: target, Target: target,
Expiration: uint64(time.Now().Add(expiration).Unix()), Expiration: uint64(time.Now().Add(expiration).Unix()),
}) })
err := <-errc return nodes, <-errc
return nodes, err
} }
// pending adds a reply callback to the pending reply queue. // pending adds a reply callback to the pending reply queue.
@ -587,10 +603,17 @@ func (req *ping) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) er
ReplyTok: mac, ReplyTok: mac,
Expiration: uint64(time.Now().Add(expiration).Unix()), Expiration: uint64(time.Now().Add(expiration).Unix()),
}) })
if !t.handleReply(fromID, pingPacket, req) { t.handleReply(fromID, pingPacket, req)
// Note: we're ignoring the provided IP address right now
go t.bond(true, fromID, from, req.From.TCP) // Add the node to the table. Before doing so, ensure that we have a recent enough pong
// recorded in the database so their findnode requests will be accepted later.
n := NewNode(fromID, from.IP, uint16(from.Port), req.From.TCP)
if time.Since(t.db.lastPongReceived(fromID)) > nodeDBNodeExpiration {
t.sendPing(fromID, from, func() { t.addThroughPing(n) })
} else {
t.addThroughPing(n)
} }
t.db.updateLastPingReceived(fromID, time.Now())
return nil return nil
} }
@ -603,6 +626,7 @@ func (req *pong) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) er
if !t.handleReply(fromID, pongPacket, req) { if !t.handleReply(fromID, pongPacket, req) {
return errUnsolicitedReply return errUnsolicitedReply
} }
t.db.updateLastPongReceived(fromID, time.Now())
return nil return nil
} }
@ -613,13 +637,12 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte
return errExpired return errExpired
} }
if !t.db.hasBond(fromID) { if !t.db.hasBond(fromID) {
// No bond exists, we don't process the packet. This prevents // No endpoint proof pong exists, we don't process the packet. This prevents an
// an attack vector where the discovery protocol could be used // attack vector where the discovery protocol could be used to amplify traffic in a
// to amplify traffic in a DDOS attack. A malicious actor // DDOS attack. A malicious actor would send a findnode request with the IP address
// would send a findnode request with the IP address and UDP // and UDP port of the target as the source address. The recipient of the findnode
// port of the target as the source address. The recipient of // packet would then send a neighbors packet (which is a much bigger packet than
// the findnode packet would then send a neighbors packet // findnode) to the victim.
// (which is a much bigger packet than findnode) to the victim.
return errUnknownNode return errUnknownNode
} }
target := crypto.Keccak256Hash(req.Target[:]) target := crypto.Keccak256Hash(req.Target[:])

View file

@ -124,7 +124,7 @@ func TestUDP_packetErrors(t *testing.T) {
test := newUDPTest(t) test := newUDPTest(t)
defer test.table.Close() defer test.table.Close()
test.packetIn(errExpired, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: Version}) test.packetIn(errExpired, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4})
test.packetIn(errUnsolicitedReply, pongPacket, &pong{ReplyTok: []byte{}, Expiration: futureExp}) test.packetIn(errUnsolicitedReply, pongPacket, &pong{ReplyTok: []byte{}, Expiration: futureExp})
test.packetIn(errUnknownNode, findnodePacket, &findnode{Expiration: futureExp}) test.packetIn(errUnknownNode, findnodePacket, &findnode{Expiration: futureExp})
test.packetIn(errUnsolicitedReply, neighborsPacket, &neighbors{Expiration: futureExp}) test.packetIn(errUnsolicitedReply, neighborsPacket, &neighbors{Expiration: futureExp})
@ -247,7 +247,7 @@ func TestUDP_findnode(t *testing.T) {
// ensure there's a bond with the test node, // ensure there's a bond with the test node,
// findnode won't be accepted otherwise. // findnode won't be accepted otherwise.
test.table.db.updateBondTime(PubkeyID(&test.remotekey.PublicKey), time.Now()) test.table.db.updateLastPongReceived(PubkeyID(&test.remotekey.PublicKey), time.Now())
// check that closest neighbors are returned. // check that closest neighbors are returned.
test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp}) test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp})
@ -273,10 +273,12 @@ func TestUDP_findnodeMultiReply(t *testing.T) {
test := newUDPTest(t) test := newUDPTest(t)
defer test.table.Close() defer test.table.Close()
rid := PubkeyID(&test.remotekey.PublicKey)
test.table.db.updateLastPingReceived(rid, time.Now())
// queue a pending findnode request // queue a pending findnode request
resultc, errc := make(chan []*Node), make(chan error) resultc, errc := make(chan []*Node), make(chan error)
go func() { go func() {
rid := PubkeyID(&test.remotekey.PublicKey)
ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget) ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget)
if err != nil && len(ns) == 0 { if err != nil && len(ns) == 0 {
errc <- err errc <- err
@ -328,7 +330,7 @@ func TestUDP_successfulPing(t *testing.T) {
defer test.table.Close() defer test.table.Close()
// The remote side sends a ping packet to initiate the exchange. // The remote side sends a ping packet to initiate the exchange.
go test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: Version, Expiration: futureExp}) go test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
// the ping is replied to. // the ping is replied to.
test.waitPacketOut(func(p *pong) { test.waitPacketOut(func(p *pong) {

View file

@ -31,10 +31,10 @@ var (
egressTrafficMeter = metrics.NewRegisteredMeter("p2p/OutboundTraffic", nil) egressTrafficMeter = metrics.NewRegisteredMeter("p2p/OutboundTraffic", nil)
) )
// meteredConn is a wrapper around a network TCP connection that meters both the // meteredConn is a wrapper around a net.Conn that meters both the
// inbound and outbound network traffic. // inbound and outbound network traffic.
type meteredConn struct { type meteredConn struct {
*net.TCPConn // Network connection to wrap with metering net.Conn // Network connection to wrap with metering
} }
// newMeteredConn creates a new metered connection, also bumping the ingress or // newMeteredConn creates a new metered connection, also bumping the ingress or
@ -51,13 +51,13 @@ func newMeteredConn(conn net.Conn, ingress bool) net.Conn {
} else { } else {
egressConnectMeter.Mark(1) egressConnectMeter.Mark(1)
} }
return &meteredConn{conn.(*net.TCPConn)} return &meteredConn{Conn: conn}
} }
// Read delegates a network read to the underlying connection, bumping the ingress // Read delegates a network read to the underlying connection, bumping the ingress
// traffic meter along the way. // traffic meter along the way.
func (c *meteredConn) Read(b []byte) (n int, err error) { func (c *meteredConn) Read(b []byte) (n int, err error) {
n, err = c.TCPConn.Read(b) n, err = c.Conn.Read(b)
ingressTrafficMeter.Mark(int64(n)) ingressTrafficMeter.Mark(int64(n))
return return
} }
@ -65,7 +65,7 @@ func (c *meteredConn) Read(b []byte) (n int, err error) {
// Write delegates a network write to the underlying connection, bumping the // Write delegates a network write to the underlying connection, bumping the
// egress traffic meter along the way. // egress traffic meter along the way.
func (c *meteredConn) Write(b []byte) (n int, err error) { func (c *meteredConn) Write(b []byte) (n int, err error) {
n, err = c.TCPConn.Write(b) n, err = c.Conn.Write(b)
egressTrafficMeter.Mark(int64(n)) egressTrafficMeter.Mark(int64(n))
return return
} }

View file

@ -17,6 +17,7 @@
package p2p package p2p
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -31,6 +32,10 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
var (
ErrShuttingDown = errors.New("shutting down")
)
const ( const (
baseProtocolVersion = 5 baseProtocolVersion = 5
baseProtocolLength = uint64(16) baseProtocolLength = uint64(16)
@ -393,7 +398,7 @@ func (rw *protoRW) WriteMsg(msg Msg) (err error) {
// as well but we don't want to rely on that. // as well but we don't want to rely on that.
rw.werr <- err rw.werr <- err
case <-rw.closed: case <-rw.closed:
err = fmt.Errorf("shutting down") err = ErrShuttingDown
} }
return err return err
} }

View file

@ -31,10 +31,12 @@ package protocols
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"reflect" "reflect"
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
) )
@ -202,6 +204,11 @@ func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, spec *Spec) *Peer {
func (p *Peer) Run(handler func(msg interface{}) error) error { func (p *Peer) Run(handler func(msg interface{}) error) error {
for { for {
if err := p.handleIncoming(handler); err != nil { if err := p.handleIncoming(handler); err != nil {
if err != io.EOF {
metrics.GetOrRegisterCounter("peer.handleincoming.error", nil).Inc(1)
log.Error("peer.handleIncoming", "err", err)
}
return err return err
} }
} }

View file

@ -31,7 +31,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
) )
var dialBanTimeout = 200 * time.Millisecond var DialBanTimeout = 200 * time.Millisecond
// NetworkConfig defines configuration options for starting a Network // NetworkConfig defines configuration options for starting a Network
type NetworkConfig struct { type NetworkConfig struct {
@ -78,41 +78,25 @@ func (net *Network) Events() *event.Feed {
return &net.events return &net.events
} }
// NewNode adds a new node to the network with a random ID
func (net *Network) NewNode() (*Node, error) {
conf := adapters.RandomNodeConfig()
conf.Services = []string{net.DefaultService}
return net.NewNodeWithConfig(conf)
}
// NewNodeWithConfig adds a new node to the network with the given config, // NewNodeWithConfig adds a new node to the network with the given config,
// returning an error if a node with the same ID or name already exists // returning an error if a node with the same ID or name already exists
func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) { func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) {
net.lock.Lock() net.lock.Lock()
defer net.lock.Unlock() defer net.lock.Unlock()
// create a random ID and PrivateKey if not set
if conf.ID == (discover.NodeID{}) {
c := adapters.RandomNodeConfig()
conf.ID = c.ID
conf.PrivateKey = c.PrivateKey
}
id := conf.ID
if conf.Reachable == nil { if conf.Reachable == nil {
conf.Reachable = func(otherID discover.NodeID) bool { conf.Reachable = func(otherID discover.NodeID) bool {
_, err := net.InitConn(conf.ID, otherID) _, err := net.InitConn(conf.ID, otherID)
return err == nil if err != nil && bytes.Compare(conf.ID.Bytes(), otherID.Bytes()) < 0 {
return false
} }
return true
} }
// assign a name to the node if not set
if conf.Name == "" {
conf.Name = fmt.Sprintf("node%02d", len(net.Nodes)+1)
} }
// check the node doesn't already exist // check the node doesn't already exist
if node := net.getNode(id); node != nil { if node := net.getNode(conf.ID); node != nil {
return nil, fmt.Errorf("node with ID %q already exists", id) return nil, fmt.Errorf("node with ID %q already exists", conf.ID)
} }
if node := net.getNodeByName(conf.Name); node != nil { if node := net.getNodeByName(conf.Name); node != nil {
return nil, fmt.Errorf("node with name %q already exists", conf.Name) return nil, fmt.Errorf("node with name %q already exists", conf.Name)
@ -132,8 +116,8 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error)
Node: adapterNode, Node: adapterNode,
Config: conf, Config: conf,
} }
log.Trace(fmt.Sprintf("node %v created", id)) log.Trace(fmt.Sprintf("node %v created", conf.ID))
net.nodeMap[id] = len(net.Nodes) net.nodeMap[conf.ID] = len(net.Nodes)
net.Nodes = append(net.Nodes, node) net.Nodes = append(net.Nodes, node)
// emit a "control" event // emit a "control" event
@ -181,7 +165,9 @@ func (net *Network) Start(id discover.NodeID) error {
// startWithSnapshots starts the node with the given ID using the give // startWithSnapshots starts the node with the given ID using the give
// snapshots // snapshots
func (net *Network) startWithSnapshots(id discover.NodeID, snapshots map[string][]byte) error { func (net *Network) startWithSnapshots(id discover.NodeID, snapshots map[string][]byte) error {
node := net.GetNode(id) net.lock.Lock()
defer net.lock.Unlock()
node := net.getNode(id)
if node == nil { if node == nil {
return fmt.Errorf("node %v does not exist", id) return fmt.Errorf("node %v does not exist", id)
} }
@ -220,9 +206,13 @@ func (net *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEve
// assume the node is now down // assume the node is now down
net.lock.Lock() net.lock.Lock()
defer net.lock.Unlock()
node := net.getNode(id) node := net.getNode(id)
if node == nil {
log.Error("Can not find node for id", "id", id)
return
}
node.Up = false node.Up = false
net.lock.Unlock()
net.events.Send(NewEvent(node)) net.events.Send(NewEvent(node))
}() }()
for { for {
@ -259,7 +249,9 @@ func (net *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEve
// Stop stops the node with the given ID // Stop stops the node with the given ID
func (net *Network) Stop(id discover.NodeID) error { func (net *Network) Stop(id discover.NodeID) error {
node := net.GetNode(id) net.lock.Lock()
defer net.lock.Unlock()
node := net.getNode(id)
if node == nil { if node == nil {
return fmt.Errorf("node %v does not exist", id) return fmt.Errorf("node %v does not exist", id)
} }
@ -312,7 +304,9 @@ func (net *Network) Disconnect(oneID, otherID discover.NodeID) error {
// DidConnect tracks the fact that the "one" node connected to the "other" node // DidConnect tracks the fact that the "one" node connected to the "other" node
func (net *Network) DidConnect(one, other discover.NodeID) error { func (net *Network) DidConnect(one, other discover.NodeID) error {
conn, err := net.GetOrCreateConn(one, other) net.lock.Lock()
defer net.lock.Unlock()
conn, err := net.getOrCreateConn(one, other)
if err != nil { if err != nil {
return fmt.Errorf("connection between %v and %v does not exist", one, other) return fmt.Errorf("connection between %v and %v does not exist", one, other)
} }
@ -327,7 +321,9 @@ func (net *Network) DidConnect(one, other discover.NodeID) error {
// DidDisconnect tracks the fact that the "one" node disconnected from the // DidDisconnect tracks the fact that the "one" node disconnected from the
// "other" node // "other" node
func (net *Network) DidDisconnect(one, other discover.NodeID) error { func (net *Network) DidDisconnect(one, other discover.NodeID) error {
conn := net.GetConn(one, other) net.lock.Lock()
defer net.lock.Unlock()
conn := net.getConn(one, other)
if conn == nil { if conn == nil {
return fmt.Errorf("connection between %v and %v does not exist", one, other) return fmt.Errorf("connection between %v and %v does not exist", one, other)
} }
@ -335,7 +331,7 @@ func (net *Network) DidDisconnect(one, other discover.NodeID) error {
return fmt.Errorf("%v and %v already disconnected", one, other) return fmt.Errorf("%v and %v already disconnected", one, other)
} }
conn.Up = false conn.Up = false
conn.initiated = time.Now().Add(-dialBanTimeout) conn.initiated = time.Now().Add(-DialBanTimeout)
net.events.Send(NewEvent(conn)) net.events.Send(NewEvent(conn))
return nil return nil
} }
@ -476,16 +472,19 @@ func (net *Network) InitConn(oneID, otherID discover.NodeID) (*Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if time.Since(conn.initiated) < dialBanTimeout {
return nil, fmt.Errorf("connection between %v and %v recently attempted", oneID, otherID)
}
if conn.Up { if conn.Up {
return nil, fmt.Errorf("%v and %v already connected", oneID, otherID) return nil, fmt.Errorf("%v and %v already connected", oneID, otherID)
} }
if time.Since(conn.initiated) < DialBanTimeout {
return nil, fmt.Errorf("connection between %v and %v recently attempted", oneID, otherID)
}
err = conn.nodesUp() err = conn.nodesUp()
if err != nil { if err != nil {
log.Trace(fmt.Sprintf("nodes not up: %v", err))
return nil, fmt.Errorf("nodes not up: %v", err) return nil, fmt.Errorf("nodes not up: %v", err)
} }
log.Debug("InitConn - connection initiated")
conn.initiated = time.Now() conn.initiated = time.Now()
return conn, nil return conn, nil
} }

View file

@ -91,7 +91,9 @@ func (s *ProtocolSession) trigger(trig Trigger) error {
errc := make(chan error) errc := make(chan error)
go func() { go func() {
log.Trace(fmt.Sprintf("trigger %v (%v)....", trig.Msg, trig.Code))
errc <- mockNode.Trigger(&trig) errc <- mockNode.Trigger(&trig)
log.Trace(fmt.Sprintf("triggered %v (%v)", trig.Msg, trig.Code))
}() }()
t := trig.Timeout t := trig.Timeout

View file

@ -23,7 +23,7 @@ import (
const ( const (
VersionMajor = 1 // Major version component of the current release VersionMajor = 1 // Major version component of the current release
VersionMinor = 8 // Minor version component of the current release VersionMinor = 8 // Minor version component of the current release
VersionPatch = 12 // Patch version component of the current release VersionPatch = 13 // Patch version component of the current release
VersionMeta = "unstable" // Version metadata to append to the version string VersionMeta = "unstable" // Version metadata to append to the version string
) )

View file

@ -304,7 +304,7 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str
return err return err
} }
// dispatch has accepted the request and will close the channel it when it quits. // dispatch has accepted the request and will close the channel when it quits.
switch resp, err := op.wait(ctx); { switch resp, err := op.wait(ctx); {
case err != nil: case err != nil:
return err return err

View file

@ -189,15 +189,6 @@ type (
var ErrRequestDenied = errors.New("Request denied") var ErrRequestDenied = errors.New("Request denied")
type errorWrapper struct {
msg string
err error
}
func (ew errorWrapper) String() string {
return fmt.Sprintf("%s\n%s", ew.msg, ew.err)
}
// NewSignerAPI creates a new API that can be used for Account management. // NewSignerAPI creates a new API that can be used for Account management.
// ksLocation specifies the directory where to store the password protected private // ksLocation specifies the directory where to store the password protected private
// key that is generated when a new Account is created. // key that is generated when a new Account is created.

35
swarm/AUTHORS Normal file
View file

@ -0,0 +1,35 @@
# Core team members
Viktor Trón - @zelig
Louis Holbrook - @nolash
Lewis Marshall - @lmars
Anton Evangelatov - @nonsense
Janoš Guljaš - @janos
Balint Gabor - @gbalint
Elad Nachmias - @justelad
Daniel A. Nagy - @nagydani
Aron Fischer - @homotopycolimit
Fabio Barone - @holisticode
Zahoor Mohamed - @jmozah
Zsolt Felföldi - @zsfelfoldi
# External contributors
Kiel Barry
Gary Rong
Jared Wasinger
Leon Stanko
Javier Peletier [epiclabs.io]
Bartek Borkowski [tungsten-labs.com]
Shane Howley [mainframe.com]
Doug Leonard [mainframe.com]
Ivan Daniluk [status.im]
Felix Lange [EF]
Martin Holst Swende [EF]
Guillaume Ballet [EF]
ligi [EF]
Christopher Dro [blick-labs.com]
Sergii Bomko [ledgerleopard.com]
Domino Valdano
Rafael Matias
Coogan Brennan

26
swarm/OWNERS Normal file
View file

@ -0,0 +1,26 @@
# Ownership by go packages
swarm
├── api ─────────────────── ethersphere
├── bmt ─────────────────── @zelig
├── dev ─────────────────── @lmars
├── fuse ────────────────── @jmozah, @holisticode
├── grafana_dashboards ──── @nonsense
├── metrics ─────────────── @nonsense, @holisticode
├── multihash ───────────── @nolash
├── network ─────────────── ethersphere
│ ├── bitvector ───────── @zelig, @janos, @gbalint
│ ├── priorityqueue ───── @zelig, @janos, @gbalint
│ ├── simulations ─────── @zelig
│ └── stream ──────────── @janos, @zelig, @gbalint, @holisticode, @justelad
│ ├── intervals ───── @janos
│ └── testing ─────── @zelig
├── pot ─────────────────── @zelig
├── pss ─────────────────── @nolash, @zelig, @nonsense
├── services ────────────── @zelig
├── state ───────────────── @justelad
├── storage ─────────────── ethersphere
│ ├── encryption ──────── @gbalint, @zelig, @nagydani
│ ├── mock ────────────── @janos
│ └── mru ─────────────── @nolash
└── testutil ────────────── @lmars

View file

@ -17,13 +17,13 @@
package api package api
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"math/big"
"net/http" "net/http"
"path" "path"
"regexp"
"strings" "strings"
"sync"
"bytes" "bytes"
"mime" "mime"
@ -31,14 +31,15 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/contracts/ens"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/multihash"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/swarm/storage/mru"
) )
var hashMatcher = regexp.MustCompile("^[0-9A-Fa-f]{64}")
//setup metrics
var ( var (
apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil) apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil)
apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil) apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil)
@ -46,7 +47,7 @@ var (
apiPutFail = metrics.NewRegisteredCounter("api.put.fail", nil) apiPutFail = metrics.NewRegisteredCounter("api.put.fail", nil)
apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil) apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil)
apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil) apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil)
apiGetHttp300 = metrics.NewRegisteredCounter("api.get.http.300", nil) apiGetHTTP300 = metrics.NewRegisteredCounter("api.get.http.300", nil)
apiModifyCount = metrics.NewRegisteredCounter("api.modify.count", nil) apiModifyCount = metrics.NewRegisteredCounter("api.modify.count", nil)
apiModifyFail = metrics.NewRegisteredCounter("api.modify.fail", nil) apiModifyFail = metrics.NewRegisteredCounter("api.modify.fail", nil)
apiAddFileCount = metrics.NewRegisteredCounter("api.addfile.count", nil) apiAddFileCount = metrics.NewRegisteredCounter("api.addfile.count", nil)
@ -55,22 +56,33 @@ var (
apiRmFileFail = metrics.NewRegisteredCounter("api.removefile.fail", nil) apiRmFileFail = metrics.NewRegisteredCounter("api.removefile.fail", nil)
apiAppendFileCount = metrics.NewRegisteredCounter("api.appendfile.count", nil) apiAppendFileCount = metrics.NewRegisteredCounter("api.appendfile.count", nil)
apiAppendFileFail = metrics.NewRegisteredCounter("api.appendfile.fail", nil) apiAppendFileFail = metrics.NewRegisteredCounter("api.appendfile.fail", nil)
apiGetInvalid = metrics.NewRegisteredCounter("api.get.invalid", nil)
) )
// Resolver interface resolve a domain name to a hash using ENS
type Resolver interface { type Resolver interface {
Resolve(string) (common.Hash, error) Resolve(string) (common.Hash, error)
} }
// ResolveValidator is used to validate the contained Resolver
type ResolveValidator interface {
Resolver
Owner(node [32]byte) (common.Address, error)
HeaderByNumber(context.Context, *big.Int) (*types.Header, error)
}
// NoResolverError is returned by MultiResolver.Resolve if no resolver // NoResolverError is returned by MultiResolver.Resolve if no resolver
// can be found for the address. // can be found for the address.
type NoResolverError struct { type NoResolverError struct {
TLD string TLD string
} }
// NewNoResolverError creates a NoResolverError for the given top level domain
func NewNoResolverError(tld string) *NoResolverError { func NewNoResolverError(tld string) *NoResolverError {
return &NoResolverError{TLD: tld} return &NoResolverError{TLD: tld}
} }
// Error NoResolverError implements error
func (e *NoResolverError) Error() string { func (e *NoResolverError) Error() string {
if e.TLD == "" { if e.TLD == "" {
return "no ENS resolver" return "no ENS resolver"
@ -82,7 +94,8 @@ func (e *NoResolverError) Error() string {
// Each TLD can have multiple resolvers, and the resoluton from the // Each TLD can have multiple resolvers, and the resoluton from the
// first one in the sequence will be returned. // first one in the sequence will be returned.
type MultiResolver struct { type MultiResolver struct {
resolvers map[string][]Resolver resolvers map[string][]ResolveValidator
nameHash func(string) common.Hash
} }
// MultiResolverOption sets options for MultiResolver and is used as // MultiResolverOption sets options for MultiResolver and is used as
@ -93,16 +106,24 @@ type MultiResolverOption func(*MultiResolver)
// for a specific TLD. If TLD is an empty string, the resolver will be added // for a specific TLD. If TLD is an empty string, the resolver will be added
// to the list of default resolver, the ones that will be used for resolution // to the list of default resolver, the ones that will be used for resolution
// of addresses which do not have their TLD resolver specified. // of addresses which do not have their TLD resolver specified.
func MultiResolverOptionWithResolver(r Resolver, tld string) MultiResolverOption { func MultiResolverOptionWithResolver(r ResolveValidator, tld string) MultiResolverOption {
return func(m *MultiResolver) { return func(m *MultiResolver) {
m.resolvers[tld] = append(m.resolvers[tld], r) m.resolvers[tld] = append(m.resolvers[tld], r)
} }
} }
// MultiResolverOptionWithNameHash is unused at the time of this writing
func MultiResolverOptionWithNameHash(nameHash func(string) common.Hash) MultiResolverOption {
return func(m *MultiResolver) {
m.nameHash = nameHash
}
}
// NewMultiResolver creates a new instance of MultiResolver. // NewMultiResolver creates a new instance of MultiResolver.
func NewMultiResolver(opts ...MultiResolverOption) (m *MultiResolver) { func NewMultiResolver(opts ...MultiResolverOption) (m *MultiResolver) {
m = &MultiResolver{ m = &MultiResolver{
resolvers: make(map[string][]Resolver), resolvers: make(map[string][]ResolveValidator),
nameHash: ens.EnsNode,
} }
for _, o := range opts { for _, o := range opts {
o(m) o(m)
@ -114,18 +135,10 @@ func NewMultiResolver(opts ...MultiResolverOption) (m *MultiResolver) {
// If there are more default Resolvers, or for a specific TLD, // If there are more default Resolvers, or for a specific TLD,
// the Hash from the the first one which does not return error // the Hash from the the first one which does not return error
// will be returned. // will be returned.
func (m MultiResolver) Resolve(addr string) (h common.Hash, err error) { func (m *MultiResolver) Resolve(addr string) (h common.Hash, err error) {
rs := m.resolvers[""] rs, err := m.getResolveValidator(addr)
tld := path.Ext(addr) if err != nil {
if tld != "" { return h, err
tld = tld[1:]
rstld, ok := m.resolvers[tld]
if ok {
rs = rstld
}
}
if rs == nil {
return h, NewNoResolverError(tld)
} }
for _, r := range rs { for _, r := range rs {
h, err = r.Resolve(addr) h, err = r.Resolve(addr)
@ -136,104 +149,174 @@ func (m MultiResolver) Resolve(addr string) (h common.Hash, err error) {
return return
} }
// ValidateOwner checks the ENS to validate that the owner of the given domain is the given eth address
func (m *MultiResolver) ValidateOwner(name string, address common.Address) (bool, error) {
rs, err := m.getResolveValidator(name)
if err != nil {
return false, err
}
var addr common.Address
for _, r := range rs {
addr, err = r.Owner(m.nameHash(name))
// we hide the error if it is not for the last resolver we check
if err == nil {
return addr == address, nil
}
}
return false, err
}
// HeaderByNumber uses the validator of the given domainname and retrieves the header for the given block number
func (m *MultiResolver) HeaderByNumber(ctx context.Context, name string, blockNr *big.Int) (*types.Header, error) {
rs, err := m.getResolveValidator(name)
if err != nil {
return nil, err
}
for _, r := range rs {
var header *types.Header
header, err = r.HeaderByNumber(ctx, blockNr)
// we hide the error if it is not for the last resolver we check
if err == nil {
return header, nil
}
}
return nil, err
}
// getResolveValidator uses the hostname to retrieve the resolver associated with the top level domain
func (m *MultiResolver) getResolveValidator(name string) ([]ResolveValidator, error) {
rs := m.resolvers[""]
tld := path.Ext(name)
if tld != "" {
tld = tld[1:]
rstld, ok := m.resolvers[tld]
if ok {
return rstld, nil
}
}
if len(rs) == 0 {
return rs, NewNoResolverError(tld)
}
return rs, nil
}
// SetNameHash sets the hasher function that hashes the domain into a name hash that ENS uses
func (m *MultiResolver) SetNameHash(nameHash func(string) common.Hash) {
m.nameHash = nameHash
}
/* /*
Api implements webserver/file system related content storage and retrieval API implements webserver/file system related content storage and retrieval
on top of the dpa on top of the FileStore
it is the public interface of the dpa which is included in the ethereum stack it is the public interface of the FileStore which is included in the ethereum stack
*/ */
type Api struct { type API struct {
dpa *storage.DPA resource *mru.Handler
fileStore *storage.FileStore
dns Resolver dns Resolver
} }
//the api constructor initialises // NewAPI the api constructor initialises a new API instance.
func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) { func NewAPI(fileStore *storage.FileStore, dns Resolver, resourceHandler *mru.Handler) (self *API) {
self = &Api{ self = &API{
dpa: dpa, fileStore: fileStore,
dns: dns, dns: dns,
resource: resourceHandler,
} }
return return
} }
// to be used only in TEST // Upload to be used only in TEST
func (self *Api) Upload(uploadDir, index string) (hash string, err error) { func (a *API) Upload(ctx context.Context, uploadDir, index string, toEncrypt bool) (hash string, err error) {
fs := NewFileSystem(self) fs := NewFileSystem(a)
hash, err = fs.Upload(uploadDir, index) hash, err = fs.Upload(uploadDir, index, toEncrypt)
return hash, err return hash, err
} }
// DPA reader API // Retrieve FileStore reader API
func (self *Api) Retrieve(key storage.Key) storage.LazySectionReader { func (a *API) Retrieve(ctx context.Context, addr storage.Address) (reader storage.LazySectionReader, isEncrypted bool) {
return self.dpa.Retrieve(key) return a.fileStore.Retrieve(ctx, addr)
} }
func (self *Api) Store(data io.Reader, size int64, wg *sync.WaitGroup) (key storage.Key, err error) { // Store wraps the Store API call of the embedded FileStore
return self.dpa.Store(data, size, wg, nil) func (a *API) Store(ctx context.Context, data io.Reader, size int64, toEncrypt bool) (addr storage.Address, wait func(ctx context.Context) error, err error) {
log.Debug("api.store", "size", size)
return a.fileStore.Store(ctx, data, size, toEncrypt)
} }
// ErrResolve is returned when an URI cannot be resolved from ENS.
type ErrResolve error type ErrResolve error
// DNS Resolver // Resolve resolves a URI to an Address using the MultiResolver.
func (self *Api) Resolve(uri *URI) (storage.Key, error) { func (a *API) Resolve(ctx context.Context, uri *URI) (storage.Address, error) {
apiResolveCount.Inc(1) apiResolveCount.Inc(1)
log.Trace(fmt.Sprintf("Resolving : %v", uri.Addr)) log.Trace("resolving", "uri", uri.Addr)
// if the URI is immutable, check if the address is a hash // if the URI is immutable, check if the address looks like a hash
isHash := hashMatcher.MatchString(uri.Addr) if uri.Immutable() {
if uri.Immutable() || uri.DeprecatedImmutable() { key := uri.Address()
if !isHash { if key == nil {
return nil, fmt.Errorf("immutable address not a content hash: %q", uri.Addr) return nil, fmt.Errorf("immutable address not a content hash: %q", uri.Addr)
} }
return common.Hex2Bytes(uri.Addr), nil return key, nil
} }
// if DNS is not configured, check if the address is a hash // if DNS is not configured, check if the address is a hash
if self.dns == nil { if a.dns == nil {
if !isHash { key := uri.Address()
if key == nil {
apiResolveFail.Inc(1) apiResolveFail.Inc(1)
return nil, fmt.Errorf("no DNS to resolve name: %q", uri.Addr) return nil, fmt.Errorf("no DNS to resolve name: %q", uri.Addr)
} }
return common.Hex2Bytes(uri.Addr), nil return key, nil
} }
// try and resolve the address // try and resolve the address
resolved, err := self.dns.Resolve(uri.Addr) resolved, err := a.dns.Resolve(uri.Addr)
if err == nil { if err == nil {
return resolved[:], nil return resolved[:], nil
} else if !isHash { }
key := uri.Address()
if key == nil {
apiResolveFail.Inc(1) apiResolveFail.Inc(1)
return nil, err return nil, err
} }
return common.Hex2Bytes(uri.Addr), nil
}
// Put provides singleton manifest creation on top of dpa store
func (self *Api) Put(content, contentType string) (storage.Key, error) {
apiPutCount.Inc(1)
r := strings.NewReader(content)
wg := &sync.WaitGroup{}
key, err := self.dpa.Store(r, int64(len(content)), wg, nil)
if err != nil {
apiPutFail.Inc(1)
return nil, err
}
manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType)
r = strings.NewReader(manifest)
key, err = self.dpa.Store(r, int64(len(manifest)), wg, nil)
if err != nil {
apiPutFail.Inc(1)
return nil, err
}
wg.Wait()
return key, nil return key, nil
} }
// Put provides singleton manifest creation on top of FileStore store
func (a *API) Put(ctx context.Context, content string, contentType string, toEncrypt bool) (k storage.Address, wait func(context.Context) error, err error) {
apiPutCount.Inc(1)
r := strings.NewReader(content)
key, waitContent, err := a.fileStore.Store(ctx, r, int64(len(content)), toEncrypt)
if err != nil {
apiPutFail.Inc(1)
return nil, nil, err
}
manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType)
r = strings.NewReader(manifest)
key, waitManifest, err := a.fileStore.Store(ctx, r, int64(len(manifest)), toEncrypt)
if err != nil {
apiPutFail.Inc(1)
return nil, nil, err
}
return key, func(ctx context.Context) error {
err := waitContent(ctx)
if err != nil {
return err
}
return waitManifest(ctx)
}, nil
}
// Get uses iterative manifest retrieval and prefix matching // Get uses iterative manifest retrieval and prefix matching
// to resolve basePath to content using dpa retrieve // to resolve basePath to content using FileStore retrieve
// it returns a section reader, mimeType, status and an error // it returns a section reader, mimeType, status, the key of the actual content and an error
func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionReader, mimeType string, status int, err error) { func (a *API) Get(ctx context.Context, manifestAddr storage.Address, path string) (reader storage.LazySectionReader, mimeType string, status int, contentAddr storage.Address, err error) {
log.Debug("api.get", "key", manifestAddr, "path", path)
apiGetCount.Inc(1) apiGetCount.Inc(1)
trie, err := loadManifest(self.dpa, key, nil) trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil)
if err != nil { if err != nil {
apiGetNotFound.Inc(1) apiGetNotFound.Inc(1)
status = http.StatusNotFound status = http.StatusNotFound
@ -241,34 +324,111 @@ func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionRe
return return
} }
log.Trace(fmt.Sprintf("getEntry(%s)", path)) log.Debug("trie getting entry", "key", manifestAddr, "path", path)
entry, _ := trie.getEntry(path) entry, _ := trie.getEntry(path)
if entry != nil { if entry != nil {
key = common.Hex2Bytes(entry.Hash) log.Debug("trie got entry", "key", manifestAddr, "path", path, "entry.Hash", entry.Hash)
// we need to do some extra work if this is a mutable resource manifest
if entry.ContentType == ResourceContentType {
// get the resource root chunk key
log.Trace("resource type", "key", manifestAddr, "hash", entry.Hash)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rsrc, err := a.resource.Load(storage.Address(common.FromHex(entry.Hash)))
if err != nil {
apiGetNotFound.Inc(1)
status = http.StatusNotFound
log.Debug(fmt.Sprintf("get resource content error: %v", err))
return reader, mimeType, status, nil, err
}
// use this key to retrieve the latest update
rsrc, err = a.resource.LookupLatest(ctx, rsrc.NameHash(), true, &mru.LookupParams{})
if err != nil {
apiGetNotFound.Inc(1)
status = http.StatusNotFound
log.Debug(fmt.Sprintf("get resource content error: %v", err))
return reader, mimeType, status, nil, err
}
// if it's multihash, we will transparently serve the content this multihash points to
// \TODO this resolve is rather expensive all in all, review to see if it can be achieved cheaper
if rsrc.Multihash {
// get the data of the update
_, rsrcData, err := a.resource.GetContent(rsrc.NameHash().Hex())
if err != nil {
apiGetNotFound.Inc(1)
status = http.StatusNotFound
log.Warn(fmt.Sprintf("get resource content error: %v", err))
return reader, mimeType, status, nil, err
}
// validate that data as multihash
decodedMultihash, err := multihash.FromMultihash(rsrcData)
if err != nil {
apiGetInvalid.Inc(1)
status = http.StatusUnprocessableEntity
log.Warn("invalid resource multihash", "err", err)
return reader, mimeType, status, nil, err
}
manifestAddr = storage.Address(decodedMultihash)
log.Trace("resource is multihash", "key", manifestAddr)
// get the manifest the multihash digest points to
trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil)
if err != nil {
apiGetNotFound.Inc(1)
status = http.StatusNotFound
log.Warn(fmt.Sprintf("loadManifestTrie (resource multihash) error: %v", err))
return reader, mimeType, status, nil, err
}
// finally, get the manifest entry
// it will always be the entry on path ""
entry, _ = trie.getEntry(path)
if entry == nil {
status = http.StatusNotFound
apiGetNotFound.Inc(1)
err = fmt.Errorf("manifest (resource multihash) entry for '%s' not found", path)
log.Trace("manifest (resource multihash) entry not found", "key", manifestAddr, "path", path)
return reader, mimeType, status, nil, err
}
} else {
// data is returned verbatim since it's not a multihash
return rsrc, "application/octet-stream", http.StatusOK, nil, nil
}
}
// regardless of resource update manifests or normal manifests we will converge at this point
// get the key the manifest entry points to and serve it if it's unambiguous
contentAddr = common.Hex2Bytes(entry.Hash)
status = entry.Status status = entry.Status
if status == http.StatusMultipleChoices { if status == http.StatusMultipleChoices {
apiGetHttp300.Inc(1) apiGetHTTP300.Inc(1)
return return nil, entry.ContentType, status, contentAddr, err
} else {
mimeType = entry.ContentType
log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", key, mimeType))
reader = self.dpa.Retrieve(key)
} }
mimeType = entry.ContentType
log.Debug("content lookup key", "key", contentAddr, "mimetype", mimeType)
reader, _ = a.fileStore.Retrieve(ctx, contentAddr)
} else { } else {
// no entry found
status = http.StatusNotFound status = http.StatusNotFound
apiGetNotFound.Inc(1) apiGetNotFound.Inc(1)
err = fmt.Errorf("manifest entry for '%s' not found", path) err = fmt.Errorf("manifest entry for '%s' not found", path)
log.Warn(fmt.Sprintf("%v", err)) log.Trace("manifest entry not found", "key", contentAddr, "path", path)
} }
return return
} }
func (self *Api) Modify(key storage.Key, path, contentHash, contentType string) (storage.Key, error) { // Modify loads manifest and checks the content hash before recalculating and storing the manifest.
func (a *API) Modify(ctx context.Context, addr storage.Address, path, contentHash, contentType string) (storage.Address, error) {
apiModifyCount.Inc(1) apiModifyCount.Inc(1)
quitC := make(chan bool) quitC := make(chan bool)
trie, err := loadManifest(self.dpa, key, quitC) trie, err := loadManifest(ctx, a.fileStore, addr, quitC)
if err != nil { if err != nil {
apiModifyFail.Inc(1) apiModifyFail.Inc(1)
return nil, err return nil, err
@ -288,10 +448,11 @@ func (self *Api) Modify(key storage.Key, path, contentHash, contentType string)
apiModifyFail.Inc(1) apiModifyFail.Inc(1)
return nil, err return nil, err
} }
return trie.hash, nil return trie.ref, nil
} }
func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver bool) (storage.Key, string, error) { // AddFile creates a new manifest entry, adds it to swarm, then adds a file to swarm.
func (a *API) AddFile(ctx context.Context, mhash, path, fname string, content []byte, nameresolver bool) (storage.Address, string, error) {
apiAddFileCount.Inc(1) apiAddFileCount.Inc(1)
uri, err := Parse("bzz:/" + mhash) uri, err := Parse("bzz:/" + mhash)
@ -299,7 +460,7 @@ func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver
apiAddFileFail.Inc(1) apiAddFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
mkey, err := self.Resolve(uri) mkey, err := a.Resolve(ctx, uri)
if err != nil { if err != nil {
apiAddFileFail.Inc(1) apiAddFileFail.Inc(1)
return nil, "", err return nil, "", err
@ -318,13 +479,13 @@ func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver
ModTime: time.Now(), ModTime: time.Now(),
} }
mw, err := self.NewManifestWriter(mkey, nil) mw, err := a.NewManifestWriter(ctx, mkey, nil)
if err != nil { if err != nil {
apiAddFileFail.Inc(1) apiAddFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
fkey, err := mw.AddEntry(bytes.NewReader(content), entry) fkey, err := mw.AddEntry(ctx, bytes.NewReader(content), entry)
if err != nil { if err != nil {
apiAddFileFail.Inc(1) apiAddFileFail.Inc(1)
return nil, "", err return nil, "", err
@ -338,10 +499,10 @@ func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver
} }
return fkey, newMkey.String(), nil return fkey, newMkey.String(), nil
} }
func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (string, error) { // RemoveFile removes a file entry in a manifest.
func (a *API) RemoveFile(ctx context.Context, mhash string, path string, fname string, nameresolver bool) (string, error) {
apiRmFileCount.Inc(1) apiRmFileCount.Inc(1)
uri, err := Parse("bzz:/" + mhash) uri, err := Parse("bzz:/" + mhash)
@ -349,7 +510,7 @@ func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (strin
apiRmFileFail.Inc(1) apiRmFileFail.Inc(1)
return "", err return "", err
} }
mkey, err := self.Resolve(uri) mkey, err := a.Resolve(ctx, uri)
if err != nil { if err != nil {
apiRmFileFail.Inc(1) apiRmFileFail.Inc(1)
return "", err return "", err
@ -360,7 +521,7 @@ func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (strin
path = path[1:] path = path[1:]
} }
mw, err := self.NewManifestWriter(mkey, nil) mw, err := a.NewManifestWriter(ctx, mkey, nil)
if err != nil { if err != nil {
apiRmFileFail.Inc(1) apiRmFileFail.Inc(1)
return "", err return "", err
@ -382,7 +543,8 @@ func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (strin
return newMkey.String(), nil return newMkey.String(), nil
} }
func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, content []byte, oldKey storage.Key, offset int64, addSize int64, nameresolver bool) (storage.Key, string, error) { // AppendFile removes old manifest, appends file entry to new manifest and adds it to Swarm.
func (a *API) AppendFile(ctx context.Context, mhash, path, fname string, existingSize int64, content []byte, oldAddr storage.Address, offset int64, addSize int64, nameresolver bool) (storage.Address, string, error) {
apiAppendFileCount.Inc(1) apiAppendFileCount.Inc(1)
buffSize := offset + addSize buffSize := offset + addSize
@ -392,7 +554,7 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
buf := make([]byte, buffSize) buf := make([]byte, buffSize)
oldReader := self.Retrieve(oldKey) oldReader, _ := a.Retrieve(ctx, oldAddr)
io.ReadAtLeast(oldReader, buf, int(offset)) io.ReadAtLeast(oldReader, buf, int(offset))
newReader := bytes.NewReader(content) newReader := bytes.NewReader(content)
@ -406,7 +568,7 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
totalSize := int64(len(buf)) totalSize := int64(len(buf))
// TODO(jmozah): to append using pyramid chunker when it is ready // TODO(jmozah): to append using pyramid chunker when it is ready
//oldReader := self.Retrieve(oldKey) //oldReader := a.Retrieve(oldKey)
//newReader := bytes.NewReader(content) //newReader := bytes.NewReader(content)
//combinedReader := io.MultiReader(oldReader, newReader) //combinedReader := io.MultiReader(oldReader, newReader)
@ -415,7 +577,7 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
apiAppendFileFail.Inc(1) apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
} }
mkey, err := self.Resolve(uri) mkey, err := a.Resolve(ctx, uri)
if err != nil { if err != nil {
apiAppendFileFail.Inc(1) apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
@ -426,7 +588,7 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
path = path[1:] path = path[1:]
} }
mw, err := self.NewManifestWriter(mkey, nil) mw, err := a.NewManifestWriter(ctx, mkey, nil)
if err != nil { if err != nil {
apiAppendFileFail.Inc(1) apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
@ -446,7 +608,7 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
ModTime: time.Now(), ModTime: time.Now(),
} }
fkey, err := mw.AddEntry(io.Reader(combinedReader), entry) fkey, err := mw.AddEntry(ctx, io.Reader(combinedReader), entry)
if err != nil { if err != nil {
apiAppendFileFail.Inc(1) apiAppendFileFail.Inc(1)
return nil, "", err return nil, "", err
@ -460,24 +622,24 @@ func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, conte
} }
return fkey, newMkey.String(), nil return fkey, newMkey.String(), nil
} }
func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storage.Key, manifestEntryMap map[string]*manifestTrieEntry, err error) { // BuildDirectoryTree used by swarmfs_unix
func (a *API) BuildDirectoryTree(ctx context.Context, mhash string, nameresolver bool) (addr storage.Address, manifestEntryMap map[string]*manifestTrieEntry, err error) {
uri, err := Parse("bzz:/" + mhash) uri, err := Parse("bzz:/" + mhash)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
key, err = self.Resolve(uri) addr, err = a.Resolve(ctx, uri)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
quitC := make(chan bool) quitC := make(chan bool)
rootTrie, err := loadManifest(self.dpa, key, quitC) rootTrie, err := loadManifest(ctx, a.fileStore, addr, quitC)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("can't load manifest %v: %v", key.String(), err) return nil, nil, fmt.Errorf("can't load manifest %v: %v", addr.String(), err)
} }
manifestEntryMap = map[string]*manifestTrieEntry{} manifestEntryMap = map[string]*manifestTrieEntry{}
@ -486,7 +648,94 @@ func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storag
}) })
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("list with prefix failed %v: %v", key.String(), err) return nil, nil, fmt.Errorf("list with prefix failed %v: %v", addr.String(), err)
} }
return key, manifestEntryMap, nil return addr, manifestEntryMap, nil
}
// ResourceLookup Looks up mutable resource updates at specific periods and versions
func (a *API) ResourceLookup(ctx context.Context, addr storage.Address, period uint32, version uint32, maxLookup *mru.LookupParams) (string, []byte, error) {
var err error
rsrc, err := a.resource.Load(addr)
if err != nil {
return "", nil, err
}
if version != 0 {
if period == 0 {
return "", nil, mru.NewError(mru.ErrInvalidValue, "Period can't be 0")
}
_, err = a.resource.LookupVersion(ctx, rsrc.NameHash(), period, version, true, maxLookup)
} else if period != 0 {
_, err = a.resource.LookupHistorical(ctx, rsrc.NameHash(), period, true, maxLookup)
} else {
_, err = a.resource.LookupLatest(ctx, rsrc.NameHash(), true, maxLookup)
}
if err != nil {
return "", nil, err
}
var data []byte
_, data, err = a.resource.GetContent(rsrc.NameHash().Hex())
if err != nil {
return "", nil, err
}
return rsrc.Name(), data, nil
}
// ResourceCreate creates Resource and returns its key
func (a *API) ResourceCreate(ctx context.Context, name string, frequency uint64) (storage.Address, error) {
key, _, err := a.resource.New(ctx, name, frequency)
if err != nil {
return nil, err
}
return key, nil
}
// ResourceUpdateMultihash updates a Mutable Resource and marks the update's content to be of multihash type, which will be recognized upon retrieval.
// It will fail if the data is not a valid multihash.
func (a *API) ResourceUpdateMultihash(ctx context.Context, name string, data []byte) (storage.Address, uint32, uint32, error) {
return a.resourceUpdate(ctx, name, data, true)
}
// ResourceUpdate updates a Mutable Resource with arbitrary data.
// Upon retrieval the update will be retrieved verbatim as bytes.
func (a *API) ResourceUpdate(ctx context.Context, name string, data []byte) (storage.Address, uint32, uint32, error) {
return a.resourceUpdate(ctx, name, data, false)
}
func (a *API) resourceUpdate(ctx context.Context, name string, data []byte, multihash bool) (storage.Address, uint32, uint32, error) {
var addr storage.Address
var err error
if multihash {
addr, err = a.resource.UpdateMultihash(ctx, name, data)
} else {
addr, err = a.resource.Update(ctx, name, data)
}
period, _ := a.resource.GetLastPeriod(name)
version, _ := a.resource.GetVersion(name)
return addr, period, version, err
}
// ResourceHashSize returned the size of the digest produced by the Mutable Resource hashing function
func (a *API) ResourceHashSize() int {
return a.resource.HashSize
}
// ResourceIsValidated checks if the Mutable Resource has an active content validator.
func (a *API) ResourceIsValidated() bool {
return a.resource.IsValidated()
}
// ResolveResourceManifest retrieves the Mutable Resource manifest for the given address, and returns the address of the metadata chunk.
func (a *API) ResolveResourceManifest(ctx context.Context, addr storage.Address) (storage.Address, error) {
trie, err := loadManifest(ctx, a.fileStore, addr, nil)
if err != nil {
return nil, fmt.Errorf("cannot load resource manifest: %v", err)
}
entry, _ := trie.getEntry("")
if entry.ContentType != ResourceContentType {
return nil, fmt.Errorf("not a resource manifest: %s", addr)
}
return storage.Address(common.FromHex(entry.Hash)), nil
} }

View file

@ -17,33 +17,34 @@
package api package api
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/big"
"os" "os"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
func testApi(t *testing.T, f func(*Api)) { func testAPI(t *testing.T, f func(*API, bool)) {
datadir, err := ioutil.TempDir("", "bzz-test") datadir, err := ioutil.TempDir("", "bzz-test")
if err != nil { if err != nil {
t.Fatalf("unable to create temp dir: %v", err) t.Fatalf("unable to create temp dir: %v", err)
} }
os.RemoveAll(datadir)
defer os.RemoveAll(datadir) defer os.RemoveAll(datadir)
dpa, err := storage.NewLocalDPA(datadir) fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32))
if err != nil { if err != nil {
return return
} }
api := NewApi(dpa, nil) api := NewAPI(fileStore, nil, nil)
dpa.Start() f(api, false)
f(api) f(api, true)
dpa.Stop()
} }
type testResponse struct { type testResponse struct {
@ -82,10 +83,9 @@ func expResponse(content string, mimeType string, status int) *Response {
return &Response{mimeType, status, int64(len(content)), content} return &Response{mimeType, status, int64(len(content)), content}
} }
// func testGet(t *testing.T, api *Api, bzzhash string) *testResponse { func testGet(t *testing.T, api *API, bzzhash, path string) *testResponse {
func testGet(t *testing.T, api *Api, bzzhash, path string) *testResponse { addr := storage.Address(common.Hex2Bytes(bzzhash))
key := storage.Key(common.Hex2Bytes(bzzhash)) reader, mimeType, status, _, err := api.Get(context.TODO(), addr, path)
reader, mimeType, status, err := api.Get(key, path)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -106,27 +106,31 @@ func testGet(t *testing.T, api *Api, bzzhash, path string) *testResponse {
} }
func TestApiPut(t *testing.T) { func TestApiPut(t *testing.T) {
testApi(t, func(api *Api) { testAPI(t, func(api *API, toEncrypt bool) {
content := "hello" content := "hello"
exp := expResponse(content, "text/plain", 0) exp := expResponse(content, "text/plain", 0)
// exp := expResponse([]byte(content), "text/plain", 0) ctx := context.TODO()
key, err := api.Put(content, exp.MimeType) addr, wait, err := api.Put(ctx, content, exp.MimeType, toEncrypt)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
resp := testGet(t, api, key.String(), "") err = wait(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
resp := testGet(t, api, addr.Hex(), "")
checkResponse(t, resp, exp) checkResponse(t, resp, exp)
}) })
} }
// testResolver implements the Resolver interface and either returns the given // testResolver implements the Resolver interface and either returns the given
// hash if it is set, or returns a "name not found" error // hash if it is set, or returns a "name not found" error
type testResolver struct { type testResolveValidator struct {
hash *common.Hash hash *common.Hash
} }
func newTestResolver(addr string) *testResolver { func newTestResolveValidator(addr string) *testResolveValidator {
r := &testResolver{} r := &testResolveValidator{}
if addr != "" { if addr != "" {
hash := common.HexToHash(addr) hash := common.HexToHash(addr)
r.hash = &hash r.hash = &hash
@ -134,21 +138,28 @@ func newTestResolver(addr string) *testResolver {
return r return r
} }
func (t *testResolver) Resolve(addr string) (common.Hash, error) { func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) {
if t.hash == nil { if t.hash == nil {
return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr) return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr)
} }
return *t.hash, nil return *t.hash, nil
} }
func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) {
return
}
func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) {
return
}
// TestAPIResolve tests resolving URIs which can either contain content hashes // TestAPIResolve tests resolving URIs which can either contain content hashes
// or ENS names // or ENS names
func TestAPIResolve(t *testing.T) { func TestAPIResolve(t *testing.T) {
ensAddr := "swarm.eth" ensAddr := "swarm.eth"
hashAddr := "1111111111111111111111111111111111111111111111111111111111111111" hashAddr := "1111111111111111111111111111111111111111111111111111111111111111"
resolvedAddr := "2222222222222222222222222222222222222222222222222222222222222222" resolvedAddr := "2222222222222222222222222222222222222222222222222222222222222222"
doesResolve := newTestResolver(resolvedAddr) doesResolve := newTestResolveValidator(resolvedAddr)
doesntResolve := newTestResolver("") doesntResolve := newTestResolveValidator("")
type test struct { type test struct {
desc string desc string
@ -213,12 +224,12 @@ func TestAPIResolve(t *testing.T) {
} }
for _, x := range tests { for _, x := range tests {
t.Run(x.desc, func(t *testing.T) { t.Run(x.desc, func(t *testing.T) {
api := &Api{dns: x.dns} api := &API{dns: x.dns}
uri := &URI{Addr: x.addr, Scheme: "bzz"} uri := &URI{Addr: x.addr, Scheme: "bzz"}
if x.immutable { if x.immutable {
uri.Scheme = "bzz-immutable" uri.Scheme = "bzz-immutable"
} }
res, err := api.Resolve(uri) res, err := api.Resolve(context.TODO(), uri)
if err == nil { if err == nil {
if x.expectErr != nil { if x.expectErr != nil {
t.Fatalf("expected error %q, got result %q", x.expectErr, res) t.Fatalf("expected error %q, got result %q", x.expectErr, res)
@ -239,15 +250,15 @@ func TestAPIResolve(t *testing.T) {
} }
func TestMultiResolver(t *testing.T) { func TestMultiResolver(t *testing.T) {
doesntResolve := newTestResolver("") doesntResolve := newTestResolveValidator("")
ethAddr := "swarm.eth" ethAddr := "swarm.eth"
ethHash := "0x2222222222222222222222222222222222222222222222222222222222222222" ethHash := "0x2222222222222222222222222222222222222222222222222222222222222222"
ethResolve := newTestResolver(ethHash) ethResolve := newTestResolveValidator(ethHash)
testAddr := "swarm.test" testAddr := "swarm.test"
testHash := "0x1111111111111111111111111111111111111111111111111111111111111111" testHash := "0x1111111111111111111111111111111111111111111111111111111111111111"
testResolve := newTestResolver(testHash) testResolve := newTestResolveValidator(testHash)
tests := []struct { tests := []struct {
desc string desc string

View file

@ -30,6 +30,7 @@ import (
"net/textproto" "net/textproto"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -52,12 +53,17 @@ type Client struct {
Gateway string Gateway string
} }
// UploadRaw uploads raw data to swarm and returns the resulting hash // UploadRaw uploads raw data to swarm and returns the resulting hash. If toEncrypt is true it
func (c *Client) UploadRaw(r io.Reader, size int64) (string, error) { // uploads encrypted data
func (c *Client) UploadRaw(r io.Reader, size int64, toEncrypt bool) (string, error) {
if size <= 0 { if size <= 0 {
return "", errors.New("data size must be greater than zero") return "", errors.New("data size must be greater than zero")
} }
req, err := http.NewRequest("POST", c.Gateway+"/bzz-raw:/", r) addr := ""
if toEncrypt {
addr = "encrypt"
}
req, err := http.NewRequest("POST", c.Gateway+"/bzz-raw:/"+addr, r)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -77,18 +83,20 @@ func (c *Client) UploadRaw(r io.Reader, size int64) (string, error) {
return string(data), nil return string(data), nil
} }
// DownloadRaw downloads raw data from swarm // DownloadRaw downloads raw data from swarm and it returns a ReadCloser and a bool whether the
func (c *Client) DownloadRaw(hash string) (io.ReadCloser, error) { // content was encrypted
func (c *Client) DownloadRaw(hash string) (io.ReadCloser, bool, error) {
uri := c.Gateway + "/bzz-raw:/" + hash uri := c.Gateway + "/bzz-raw:/" + hash
res, err := http.DefaultClient.Get(uri) res, err := http.DefaultClient.Get(uri)
if err != nil { if err != nil {
return nil, err return nil, false, err
} }
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
res.Body.Close() res.Body.Close()
return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status) return nil, false, fmt.Errorf("unexpected HTTP status: %s", res.Status)
} }
return res.Body, nil isEncrypted := (res.Header.Get("X-Decrypted") == "true")
return res.Body, isEncrypted, nil
} }
// File represents a file in a swarm manifest and is used for uploading and // File represents a file in a swarm manifest and is used for uploading and
@ -125,11 +133,11 @@ func Open(path string) (*File, error) {
// (if the manifest argument is non-empty) or creates a new manifest containing // (if the manifest argument is non-empty) or creates a new manifest containing
// the file, returning the resulting manifest hash (the file will then be // the file, returning the resulting manifest hash (the file will then be
// available at bzz:/<hash>/<path>) // available at bzz:/<hash>/<path>)
func (c *Client) Upload(file *File, manifest string) (string, error) { func (c *Client) Upload(file *File, manifest string, toEncrypt bool) (string, error) {
if file.Size <= 0 { if file.Size <= 0 {
return "", errors.New("file size must be greater than zero") return "", errors.New("file size must be greater than zero")
} }
return c.TarUpload(manifest, &FileUploader{file}) return c.TarUpload(manifest, &FileUploader{file}, toEncrypt)
} }
// Download downloads a file with the given path from the swarm manifest with // Download downloads a file with the given path from the swarm manifest with
@ -159,14 +167,14 @@ func (c *Client) Download(hash, path string) (*File, error) {
// directory will then be available at bzz:/<hash>/path/to/file), with // directory will then be available at bzz:/<hash>/path/to/file), with
// the file specified in defaultPath being uploaded to the root of the manifest // the file specified in defaultPath being uploaded to the root of the manifest
// (i.e. bzz:/<hash>/) // (i.e. bzz:/<hash>/)
func (c *Client) UploadDirectory(dir, defaultPath, manifest string) (string, error) { func (c *Client) UploadDirectory(dir, defaultPath, manifest string, toEncrypt bool) (string, error) {
stat, err := os.Stat(dir) stat, err := os.Stat(dir)
if err != nil { if err != nil {
return "", err return "", err
} else if !stat.IsDir() { } else if !stat.IsDir() {
return "", fmt.Errorf("not a directory: %s", dir) return "", fmt.Errorf("not a directory: %s", dir)
} }
return c.TarUpload(manifest, &DirectoryUploader{dir, defaultPath}) return c.TarUpload(manifest, &DirectoryUploader{dir, defaultPath}, toEncrypt)
} }
// DownloadDirectory downloads the files contained in a swarm manifest under // DownloadDirectory downloads the files contained in a swarm manifest under
@ -228,27 +236,109 @@ func (c *Client) DownloadDirectory(hash, path, destDir string) error {
} }
} }
// DownloadFile downloads a single file into the destination directory
// if the manifest entry does not specify a file name - it will fallback
// to the hash of the file as a filename
func (c *Client) DownloadFile(hash, path, dest string) error {
hasDestinationFilename := false
if stat, err := os.Stat(dest); err == nil {
hasDestinationFilename = !stat.IsDir()
} else {
if os.IsNotExist(err) {
// does not exist - should be created
hasDestinationFilename = true
} else {
return fmt.Errorf("could not stat path: %v", err)
}
}
manifestList, err := c.List(hash, path)
if err != nil {
return fmt.Errorf("could not list manifest: %v", err)
}
switch len(manifestList.Entries) {
case 0:
return fmt.Errorf("could not find path requested at manifest address. make sure the path you've specified is correct")
case 1:
//continue
default:
return fmt.Errorf("got too many matches for this path")
}
uri := c.Gateway + "/bzz:/" + hash + "/" + path
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode)
}
filename := ""
if hasDestinationFilename {
filename = dest
} else {
// try to assert
re := regexp.MustCompile("[^/]+$") //everything after last slash
if results := re.FindAllString(path, -1); len(results) > 0 {
filename = results[len(results)-1]
} else {
if entry := manifestList.Entries[0]; entry.Path != "" && entry.Path != "/" {
filename = entry.Path
} else {
// assume hash as name if there's nothing from the command line
filename = hash
}
}
filename = filepath.Join(dest, filename)
}
filePath, err := filepath.Abs(filename)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(filePath), 0777); err != nil {
return err
}
dst, err := os.Create(filename)
if err != nil {
return err
}
defer dst.Close()
_, err = io.Copy(dst, res.Body)
return err
}
// UploadManifest uploads the given manifest to swarm // UploadManifest uploads the given manifest to swarm
func (c *Client) UploadManifest(m *api.Manifest) (string, error) { func (c *Client) UploadManifest(m *api.Manifest, toEncrypt bool) (string, error) {
data, err := json.Marshal(m) data, err := json.Marshal(m)
if err != nil { if err != nil {
return "", err return "", err
} }
return c.UploadRaw(bytes.NewReader(data), int64(len(data))) return c.UploadRaw(bytes.NewReader(data), int64(len(data)), toEncrypt)
} }
// DownloadManifest downloads a swarm manifest // DownloadManifest downloads a swarm manifest
func (c *Client) DownloadManifest(hash string) (*api.Manifest, error) { func (c *Client) DownloadManifest(hash string) (*api.Manifest, bool, error) {
res, err := c.DownloadRaw(hash) res, isEncrypted, err := c.DownloadRaw(hash)
if err != nil { if err != nil {
return nil, err return nil, isEncrypted, err
} }
defer res.Close() defer res.Close()
var manifest api.Manifest var manifest api.Manifest
if err := json.NewDecoder(res).Decode(&manifest); err != nil { if err := json.NewDecoder(res).Decode(&manifest); err != nil {
return nil, err return nil, isEncrypted, err
} }
return &manifest, nil return &manifest, isEncrypted, nil
} }
// List list files in a swarm manifest which have the given prefix, grouping // List list files in a swarm manifest which have the given prefix, grouping
@ -350,10 +440,19 @@ type UploadFn func(file *File) error
// TarUpload uses the given Uploader to upload files to swarm as a tar stream, // TarUpload uses the given Uploader to upload files to swarm as a tar stream,
// returning the resulting manifest hash // returning the resulting manifest hash
func (c *Client) TarUpload(hash string, uploader Uploader) (string, error) { func (c *Client) TarUpload(hash string, uploader Uploader, toEncrypt bool) (string, error) {
reqR, reqW := io.Pipe() reqR, reqW := io.Pipe()
defer reqR.Close() defer reqR.Close()
req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+hash, reqR) addr := hash
// If there is a hash already (a manifest), then that manifest will determine if the upload has
// to be encrypted or not. If there is no manifest then the toEncrypt parameter decides if
// there is encryption or not.
if hash == "" && toEncrypt {
// This is the built-in address for the encrypted upload endpoint
addr = "encrypt"
}
req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+addr, reqR)
if err != nil { if err != nil {
return "", err return "", err
} }

View file

@ -26,28 +26,43 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
"github.com/ethereum/go-ethereum/swarm/testutil" "github.com/ethereum/go-ethereum/swarm/testutil"
) )
func serverFunc(api *api.API) testutil.TestServer {
return swarmhttp.NewServer(api)
}
// TestClientUploadDownloadRaw test uploading and downloading raw data to swarm // TestClientUploadDownloadRaw test uploading and downloading raw data to swarm
func TestClientUploadDownloadRaw(t *testing.T) { func TestClientUploadDownloadRaw(t *testing.T) {
srv := testutil.NewTestSwarmServer(t) testClientUploadDownloadRaw(false, t)
}
func TestClientUploadDownloadRawEncrypted(t *testing.T) {
testClientUploadDownloadRaw(true, t)
}
func testClientUploadDownloadRaw(toEncrypt bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc)
defer srv.Close() defer srv.Close()
client := NewClient(srv.URL) client := NewClient(srv.URL)
// upload some raw data // upload some raw data
data := []byte("foo123") data := []byte("foo123")
hash, err := client.UploadRaw(bytes.NewReader(data), int64(len(data))) hash, err := client.UploadRaw(bytes.NewReader(data), int64(len(data)), toEncrypt)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// check we can download the same data // check we can download the same data
res, err := client.DownloadRaw(hash) res, isEncrypted, err := client.DownloadRaw(hash)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if isEncrypted != toEncrypt {
t.Fatalf("Expected encyption status %v got %v", toEncrypt, isEncrypted)
}
defer res.Close() defer res.Close()
gotData, err := ioutil.ReadAll(res) gotData, err := ioutil.ReadAll(res)
if err != nil { if err != nil {
@ -61,7 +76,15 @@ func TestClientUploadDownloadRaw(t *testing.T) {
// TestClientUploadDownloadFiles test uploading and downloading files to swarm // TestClientUploadDownloadFiles test uploading and downloading files to swarm
// manifests // manifests
func TestClientUploadDownloadFiles(t *testing.T) { func TestClientUploadDownloadFiles(t *testing.T) {
srv := testutil.NewTestSwarmServer(t) testClientUploadDownloadFiles(false, t)
}
func TestClientUploadDownloadFilesEncrypted(t *testing.T) {
testClientUploadDownloadFiles(true, t)
}
func testClientUploadDownloadFiles(toEncrypt bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc)
defer srv.Close() defer srv.Close()
client := NewClient(srv.URL) client := NewClient(srv.URL)
@ -74,7 +97,7 @@ func TestClientUploadDownloadFiles(t *testing.T) {
Size: int64(len(data)), Size: int64(len(data)),
}, },
} }
hash, err := client.Upload(file, manifest) hash, err := client.Upload(file, manifest, toEncrypt)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -159,7 +182,7 @@ func newTestDirectory(t *testing.T) string {
// TestClientUploadDownloadDirectory tests uploading and downloading a // TestClientUploadDownloadDirectory tests uploading and downloading a
// directory of files to a swarm manifest // directory of files to a swarm manifest
func TestClientUploadDownloadDirectory(t *testing.T) { func TestClientUploadDownloadDirectory(t *testing.T) {
srv := testutil.NewTestSwarmServer(t) srv := testutil.NewTestSwarmServer(t, serverFunc)
defer srv.Close() defer srv.Close()
dir := newTestDirectory(t) dir := newTestDirectory(t)
@ -168,7 +191,7 @@ func TestClientUploadDownloadDirectory(t *testing.T) {
// upload the directory // upload the directory
client := NewClient(srv.URL) client := NewClient(srv.URL)
defaultPath := filepath.Join(dir, testDirFiles[0]) defaultPath := filepath.Join(dir, testDirFiles[0])
hash, err := client.UploadDirectory(dir, defaultPath, "") hash, err := client.UploadDirectory(dir, defaultPath, "", false)
if err != nil { if err != nil {
t.Fatalf("error uploading directory: %s", err) t.Fatalf("error uploading directory: %s", err)
} }
@ -217,14 +240,22 @@ func TestClientUploadDownloadDirectory(t *testing.T) {
// TestClientFileList tests listing files in a swarm manifest // TestClientFileList tests listing files in a swarm manifest
func TestClientFileList(t *testing.T) { func TestClientFileList(t *testing.T) {
srv := testutil.NewTestSwarmServer(t) testClientFileList(false, t)
}
func TestClientFileListEncrypted(t *testing.T) {
testClientFileList(true, t)
}
func testClientFileList(toEncrypt bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc)
defer srv.Close() defer srv.Close()
dir := newTestDirectory(t) dir := newTestDirectory(t)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
client := NewClient(srv.URL) client := NewClient(srv.URL)
hash, err := client.UploadDirectory(dir, "", "") hash, err := client.UploadDirectory(dir, "", "", toEncrypt)
if err != nil { if err != nil {
t.Fatalf("error uploading directory: %s", err) t.Fatalf("error uploading directory: %s", err)
} }
@ -275,7 +306,7 @@ func TestClientFileList(t *testing.T) {
// TestClientMultipartUpload tests uploading files to swarm using a multipart // TestClientMultipartUpload tests uploading files to swarm using a multipart
// upload // upload
func TestClientMultipartUpload(t *testing.T) { func TestClientMultipartUpload(t *testing.T) {
srv := testutil.NewTestSwarmServer(t) srv := testutil.NewTestSwarmServer(t, serverFunc)
defer srv.Close() defer srv.Close()
// define an uploader which uploads testDirFiles with some data // define an uploader which uploads testDirFiles with some data

View file

@ -21,13 +21,16 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/contracts/ens" "github.com/ethereum/go-ethereum/contracts/ens"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/network"
"github.com/ethereum/go-ethereum/swarm/pss"
"github.com/ethereum/go-ethereum/swarm/services/swap" "github.com/ethereum/go-ethereum/swarm/services/swap"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
@ -41,11 +44,12 @@ const (
// allow several bzz nodes running in parallel // allow several bzz nodes running in parallel
type Config struct { type Config struct {
// serialised/persisted fields // serialised/persisted fields
*storage.StoreParams *storage.FileStoreParams
*storage.ChunkerParams *storage.LocalStoreParams
*network.HiveParams *network.HiveParams
Swap *swap.SwapParams Swap *swap.LocalProfile
*network.SyncParams Pss *pss.PssParams
//*network.SyncParams
Contract common.Address Contract common.Address
EnsRoot common.Address EnsRoot common.Address
EnsAPIs []string EnsAPIs []string
@ -54,33 +58,40 @@ type Config struct {
Port string Port string
PublicKey string PublicKey string
BzzKey string BzzKey string
NetworkId uint64 NodeID string
NetworkID uint64
SwapEnabled bool SwapEnabled bool
SyncEnabled bool SyncEnabled bool
SwapApi string DeliverySkipCheck bool
SyncUpdateDelay time.Duration
SwapAPI string
Cors string Cors string
BzzAccount string BzzAccount string
BootNodes string BootNodes string
privateKey *ecdsa.PrivateKey
} }
//create a default config with all parameters to set to defaults //create a default config with all parameters to set to defaults
func NewDefaultConfig() (self *Config) { func NewConfig() (c *Config) {
self = &Config{ c = &Config{
StoreParams: storage.NewDefaultStoreParams(), LocalStoreParams: storage.NewDefaultLocalStoreParams(),
ChunkerParams: storage.NewChunkerParams(), FileStoreParams: storage.NewFileStoreParams(),
HiveParams: network.NewDefaultHiveParams(), HiveParams: network.NewHiveParams(),
SyncParams: network.NewDefaultSyncParams(), //SyncParams: network.NewDefaultSyncParams(),
Swap: swap.NewDefaultSwapParams(), Swap: swap.NewDefaultSwapParams(),
Pss: pss.NewPssParams(),
ListenAddr: DefaultHTTPListenAddr, ListenAddr: DefaultHTTPListenAddr,
Port: DefaultHTTPPort, Port: DefaultHTTPPort,
Path: node.DefaultDataDir(), Path: node.DefaultDataDir(),
EnsAPIs: nil, EnsAPIs: nil,
EnsRoot: ens.TestNetAddress, EnsRoot: ens.TestNetAddress,
NetworkId: network.NetworkId, NetworkID: network.DefaultNetworkID,
SwapEnabled: false, SwapEnabled: false,
SyncEnabled: true, SyncEnabled: true,
SwapApi: "", DeliverySkipCheck: false,
SyncUpdateDelay: 15 * time.Second,
SwapAPI: "",
BootNodes: "", BootNodes: "",
} }
@ -89,11 +100,11 @@ func NewDefaultConfig() (self *Config) {
//some config params need to be initialized after the complete //some config params need to be initialized after the complete
//config building phase is completed (e.g. due to overriding flags) //config building phase is completed (e.g. due to overriding flags)
func (self *Config) Init(prvKey *ecdsa.PrivateKey) { func (c *Config) Init(prvKey *ecdsa.PrivateKey) {
address := crypto.PubkeyToAddress(prvKey.PublicKey) address := crypto.PubkeyToAddress(prvKey.PublicKey)
self.Path = filepath.Join(self.Path, "bzz-"+common.Bytes2Hex(address.Bytes())) c.Path = filepath.Join(c.Path, "bzz-"+common.Bytes2Hex(address.Bytes()))
err := os.MkdirAll(self.Path, os.ModePerm) err := os.MkdirAll(c.Path, os.ModePerm)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("Error creating root swarm data directory: %v", err)) log.Error(fmt.Sprintf("Error creating root swarm data directory: %v", err))
return return
@ -103,11 +114,25 @@ func (self *Config) Init(prvKey *ecdsa.PrivateKey) {
pubkeyhex := common.ToHex(pubkey) pubkeyhex := common.ToHex(pubkey)
keyhex := crypto.Keccak256Hash(pubkey).Hex() keyhex := crypto.Keccak256Hash(pubkey).Hex()
self.PublicKey = pubkeyhex c.PublicKey = pubkeyhex
self.BzzKey = keyhex c.BzzKey = keyhex
c.NodeID = discover.PubkeyID(&prvKey.PublicKey).String()
self.Swap.Init(self.Contract, prvKey) if c.SwapEnabled {
self.SyncParams.Init(self.Path) c.Swap.Init(c.Contract, prvKey)
self.HiveParams.Init(self.Path) }
self.StoreParams.Init(self.Path)
c.privateKey = prvKey
c.LocalStoreParams.Init(c.Path)
c.LocalStoreParams.BaseKey = common.FromHex(keyhex)
c.Pss = c.Pss.WithPrivateKey(c.privateKey)
}
func (c *Config) ShiftPrivateKey() (privKey *ecdsa.PrivateKey) {
if c.privateKey != nil {
privKey = c.privateKey
c.privateKey = nil
}
return privKey
} }

View file

@ -33,9 +33,10 @@ func TestConfig(t *testing.T) {
t.Fatalf("failed to load private key: %v", err) t.Fatalf("failed to load private key: %v", err)
} }
one := NewDefaultConfig() one := NewConfig()
two := NewDefaultConfig() two := NewConfig()
one.LocalStoreParams = two.LocalStoreParams
if equal := reflect.DeepEqual(one, two); !equal { if equal := reflect.DeepEqual(one, two); !equal {
t.Fatal("Two default configs are not equal") t.Fatal("Two default configs are not equal")
} }
@ -49,21 +50,10 @@ func TestConfig(t *testing.T) {
if one.PublicKey == "" { if one.PublicKey == "" {
t.Fatal("Expected PublicKey to be set") t.Fatal("Expected PublicKey to be set")
} }
if one.Swap.PayProfile.Beneficiary == (common.Address{}) && one.SwapEnabled {
//the Init function should append subdirs to the given path
if one.Swap.PayProfile.Beneficiary == (common.Address{}) {
t.Fatal("Failed to correctly initialize SwapParams") t.Fatal("Failed to correctly initialize SwapParams")
} }
if one.ChunkDbPath == one.Path {
if one.SyncParams.RequestDbPath == one.Path {
t.Fatal("Failed to correctly initialize SyncParams")
}
if one.HiveParams.KadDbPath == one.Path {
t.Fatal("Failed to correctly initialize HiveParams")
}
if one.StoreParams.ChunkDbPath == one.Path {
t.Fatal("Failed to correctly initialize StoreParams") t.Fatal("Failed to correctly initialize StoreParams")
} }
} }

View file

@ -18,6 +18,7 @@ package api
import ( import (
"bufio" "bufio"
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -27,26 +28,27 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
const maxParallelFiles = 5 const maxParallelFiles = 5
type FileSystem struct { type FileSystem struct {
api *Api api *API
} }
func NewFileSystem(api *Api) *FileSystem { func NewFileSystem(api *API) *FileSystem {
return &FileSystem{api} return &FileSystem{api}
} }
// Upload replicates a local directory as a manifest file and uploads it // Upload replicates a local directory as a manifest file and uploads it
// using dpa store // using FileStore store
// This function waits the chunks to be stored.
// TODO: localpath should point to a manifest // TODO: localpath should point to a manifest
// //
// DEPRECATED: Use the HTTP API instead // DEPRECATED: Use the HTTP API instead
func (self *FileSystem) Upload(lpath, index string) (string, error) { func (fs *FileSystem) Upload(lpath, index string, toEncrypt bool) (string, error) {
var list []*manifestTrieEntry var list []*manifestTrieEntry
localpath, err := filepath.Abs(filepath.Clean(lpath)) localpath, err := filepath.Abs(filepath.Clean(lpath))
if err != nil { if err != nil {
@ -111,13 +113,14 @@ func (self *FileSystem) Upload(lpath, index string) (string, error) {
f, err := os.Open(entry.Path) f, err := os.Open(entry.Path)
if err == nil { if err == nil {
stat, _ := f.Stat() stat, _ := f.Stat()
var hash storage.Key var hash storage.Address
wg := &sync.WaitGroup{} var wait func(context.Context) error
hash, err = self.api.dpa.Store(f, stat.Size(), wg, nil) ctx := context.TODO()
hash, wait, err = fs.api.fileStore.Store(ctx, f, stat.Size(), toEncrypt)
if hash != nil { if hash != nil {
list[i].Hash = hash.String() list[i].Hash = hash.Hex()
} }
wg.Wait() err = wait(ctx)
awg.Done() awg.Done()
if err == nil { if err == nil {
first512 := make([]byte, 512) first512 := make([]byte, 512)
@ -142,7 +145,7 @@ func (self *FileSystem) Upload(lpath, index string) (string, error) {
} }
trie := &manifestTrie{ trie := &manifestTrie{
dpa: self.api.dpa, fileStore: fs.api.fileStore,
} }
quitC := make(chan bool) quitC := make(chan bool)
for i, entry := range list { for i, entry := range list {
@ -163,7 +166,7 @@ func (self *FileSystem) Upload(lpath, index string) (string, error) {
err2 := trie.recalcAndStore() err2 := trie.recalcAndStore()
var hs string var hs string
if err2 == nil { if err2 == nil {
hs = trie.hash.String() hs = trie.ref.Hex()
} }
awg.Wait() awg.Wait()
return hs, err2 return hs, err2
@ -173,7 +176,7 @@ func (self *FileSystem) Upload(lpath, index string) (string, error) {
// under localpath // under localpath
// //
// DEPRECATED: Use the HTTP API instead // DEPRECATED: Use the HTTP API instead
func (self *FileSystem) Download(bzzpath, localpath string) error { func (fs *FileSystem) Download(bzzpath, localpath string) error {
lpath, err := filepath.Abs(filepath.Clean(localpath)) lpath, err := filepath.Abs(filepath.Clean(localpath))
if err != nil { if err != nil {
return err return err
@ -188,7 +191,7 @@ func (self *FileSystem) Download(bzzpath, localpath string) error {
if err != nil { if err != nil {
return err return err
} }
key, err := self.api.Resolve(uri) addr, err := fs.api.Resolve(context.TODO(), uri)
if err != nil { if err != nil {
return err return err
} }
@ -199,14 +202,14 @@ func (self *FileSystem) Download(bzzpath, localpath string) error {
} }
quitC := make(chan bool) quitC := make(chan bool)
trie, err := loadManifest(self.api.dpa, key, quitC) trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC)
if err != nil { if err != nil {
log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)) log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err))
return err return err
} }
type downloadListEntry struct { type downloadListEntry struct {
key storage.Key addr storage.Address
path string path string
} }
@ -217,7 +220,7 @@ func (self *FileSystem) Download(bzzpath, localpath string) error {
err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) { err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) {
log.Trace(fmt.Sprintf("fs.Download: %#v", entry)) log.Trace(fmt.Sprintf("fs.Download: %#v", entry))
key = common.Hex2Bytes(entry.Hash) addr = common.Hex2Bytes(entry.Hash)
path := lpath + "/" + suffix path := lpath + "/" + suffix
dir := filepath.Dir(path) dir := filepath.Dir(path)
if dir != prevPath { if dir != prevPath {
@ -225,7 +228,7 @@ func (self *FileSystem) Download(bzzpath, localpath string) error {
prevPath = dir prevPath = dir
} }
if (mde == nil) && (path != dir+"/") { if (mde == nil) && (path != dir+"/") {
list = append(list, &downloadListEntry{key: key, path: path}) list = append(list, &downloadListEntry{addr: addr, path: path})
} }
}) })
if err != nil { if err != nil {
@ -244,7 +247,7 @@ func (self *FileSystem) Download(bzzpath, localpath string) error {
} }
go func(i int, entry *downloadListEntry) { go func(i int, entry *downloadListEntry) {
defer wg.Done() defer wg.Done()
err := retrieveToFile(quitC, self.api.dpa, entry.key, entry.path) err := retrieveToFile(quitC, fs.api.fileStore, entry.addr, entry.path)
if err != nil { if err != nil {
select { select {
case errC <- err: case errC <- err:
@ -267,12 +270,12 @@ func (self *FileSystem) Download(bzzpath, localpath string) error {
} }
} }
func retrieveToFile(quitC chan bool, dpa *storage.DPA, key storage.Key, path string) error { func retrieveToFile(quitC chan bool, fileStore *storage.FileStore, addr storage.Address, path string) error {
f, err := os.Create(path) // TODO: basePath separators f, err := os.Create(path) // TODO: basePath separators
if err != nil { if err != nil {
return err return err
} }
reader := dpa.Retrieve(key) reader, _ := fileStore.Retrieve(context.TODO(), addr)
writer := bufio.NewWriter(f) writer := bufio.NewWriter(f)
size, err := reader.Size(quitC) size, err := reader.Size(quitC)
if err != nil { if err != nil {

View file

@ -18,10 +18,10 @@ package api
import ( import (
"bytes" "bytes"
"context"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -30,9 +30,9 @@ import (
var testDownloadDir, _ = ioutil.TempDir(os.TempDir(), "bzz-test") var testDownloadDir, _ = ioutil.TempDir(os.TempDir(), "bzz-test")
func testFileSystem(t *testing.T, f func(*FileSystem)) { func testFileSystem(t *testing.T, f func(*FileSystem, bool)) {
testApi(t, func(api *Api) { testAPI(t, func(api *API, toEncrypt bool) {
f(NewFileSystem(api)) f(NewFileSystem(api), toEncrypt)
}) })
} }
@ -47,9 +47,9 @@ func readPath(t *testing.T, parts ...string) string {
} }
func TestApiDirUpload0(t *testing.T) { func TestApiDirUpload0(t *testing.T) {
testFileSystem(t, func(fs *FileSystem) { testFileSystem(t, func(fs *FileSystem, toEncrypt bool) {
api := fs.api api := fs.api
bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "") bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "", toEncrypt)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -63,8 +63,8 @@ func TestApiDirUpload0(t *testing.T) {
exp = expResponse(content, "text/css", 0) exp = expResponse(content, "text/css", 0)
checkResponse(t, resp, exp) checkResponse(t, resp, exp)
key := storage.Key(common.Hex2Bytes(bzzhash)) addr := storage.Address(common.Hex2Bytes(bzzhash))
_, _, _, err = api.Get(key, "") _, _, _, _, err = api.Get(context.TODO(), addr, "")
if err == nil { if err == nil {
t.Fatalf("expected error: %v", err) t.Fatalf("expected error: %v", err)
} }
@ -75,27 +75,28 @@ func TestApiDirUpload0(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
newbzzhash, err := fs.Upload(downloadDir, "") newbzzhash, err := fs.Upload(downloadDir, "", toEncrypt)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if bzzhash != newbzzhash { // TODO: currently the hash is not deterministic in the encrypted case
if !toEncrypt && bzzhash != newbzzhash {
t.Fatalf("download %v reuploaded has incorrect hash, expected %v, got %v", downloadDir, bzzhash, newbzzhash) t.Fatalf("download %v reuploaded has incorrect hash, expected %v, got %v", downloadDir, bzzhash, newbzzhash)
} }
}) })
} }
func TestApiDirUploadModify(t *testing.T) { func TestApiDirUploadModify(t *testing.T) {
testFileSystem(t, func(fs *FileSystem) { testFileSystem(t, func(fs *FileSystem, toEncrypt bool) {
api := fs.api api := fs.api
bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "") bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "", toEncrypt)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
return return
} }
key := storage.Key(common.Hex2Bytes(bzzhash)) addr := storage.Address(common.Hex2Bytes(bzzhash))
key, err = api.Modify(key, "index.html", "", "") addr, err = api.Modify(context.TODO(), addr, "index.html", "", "")
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
return return
@ -105,24 +106,28 @@ func TestApiDirUploadModify(t *testing.T) {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
return return
} }
wg := &sync.WaitGroup{} ctx := context.TODO()
hash, err := api.Store(bytes.NewReader(index), int64(len(index)), wg) hash, wait, err := api.Store(ctx, bytes.NewReader(index), int64(len(index)), toEncrypt)
wg.Wait()
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
return return
} }
key, err = api.Modify(key, "index2.html", hash.Hex(), "text/html; charset=utf-8") err = wait(ctx)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
return return
} }
key, err = api.Modify(key, "img/logo.png", hash.Hex(), "text/html; charset=utf-8") addr, err = api.Modify(context.TODO(), addr, "index2.html", hash.Hex(), "text/html; charset=utf-8")
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
return return
} }
bzzhash = key.String() addr, err = api.Modify(context.TODO(), addr, "img/logo.png", hash.Hex(), "text/html; charset=utf-8")
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
bzzhash = addr.Hex()
content := readPath(t, "testdata", "test0", "index.html") content := readPath(t, "testdata", "test0", "index.html")
resp := testGet(t, api, bzzhash, "index2.html") resp := testGet(t, api, bzzhash, "index2.html")
@ -138,7 +143,7 @@ func TestApiDirUploadModify(t *testing.T) {
exp = expResponse(content, "text/css", 0) exp = expResponse(content, "text/css", 0)
checkResponse(t, resp, exp) checkResponse(t, resp, exp)
_, _, _, err = api.Get(key, "") _, _, _, _, err = api.Get(context.TODO(), addr, "")
if err == nil { if err == nil {
t.Errorf("expected error: %v", err) t.Errorf("expected error: %v", err)
} }
@ -146,9 +151,9 @@ func TestApiDirUploadModify(t *testing.T) {
} }
func TestApiDirUploadWithRootFile(t *testing.T) { func TestApiDirUploadWithRootFile(t *testing.T) {
testFileSystem(t, func(fs *FileSystem) { testFileSystem(t, func(fs *FileSystem, toEncrypt bool) {
api := fs.api api := fs.api
bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "index.html") bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "index.html", toEncrypt)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
return return
@ -162,9 +167,9 @@ func TestApiDirUploadWithRootFile(t *testing.T) {
} }
func TestApiFileUpload(t *testing.T) { func TestApiFileUpload(t *testing.T) {
testFileSystem(t, func(fs *FileSystem) { testFileSystem(t, func(fs *FileSystem, toEncrypt bool) {
api := fs.api api := fs.api
bzzhash, err := fs.Upload(filepath.Join("testdata", "test0", "index.html"), "") bzzhash, err := fs.Upload(filepath.Join("testdata", "test0", "index.html"), "", toEncrypt)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
return return
@ -178,9 +183,9 @@ func TestApiFileUpload(t *testing.T) {
} }
func TestApiFileUploadWithRootFile(t *testing.T) { func TestApiFileUploadWithRootFile(t *testing.T) {
testFileSystem(t, func(fs *FileSystem) { testFileSystem(t, func(fs *FileSystem, toEncrypt bool) {
api := fs.api api := fs.api
bzzhash, err := fs.Upload(filepath.Join("testdata", "test0", "index.html"), "index.html") bzzhash, err := fs.Upload(filepath.Join("testdata", "test0", "index.html"), "index.html", toEncrypt)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
return return

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