Merge branch 'master' into shed

This commit is contained in:
Janos Guljas 2018-11-19 14:35:21 +01:00
commit 3685c959e0
90 changed files with 3203 additions and 753 deletions

View file

@ -89,6 +89,7 @@ var (
utils.LightKDFFlag, utils.LightKDFFlag,
utils.CacheFlag, utils.CacheFlag,
utils.CacheDatabaseFlag, utils.CacheDatabaseFlag,
utils.CacheTrieFlag,
utils.CacheGCFlag, utils.CacheGCFlag,
utils.TrieCacheGenFlag, utils.TrieCacheGenFlag,
utils.ListenPortFlag, utils.ListenPortFlag,

View file

@ -132,6 +132,7 @@ var AppHelpFlagGroups = []flagGroup{
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.CacheFlag, utils.CacheFlag,
utils.CacheDatabaseFlag, utils.CacheDatabaseFlag,
utils.CacheTrieFlag,
utils.CacheGCFlag, utils.CacheGCFlag,
utils.TrieCacheGenFlag, utils.TrieCacheGenFlag,
}, },

View file

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
swarm "github.com/ethereum/go-ethereum/swarm/api/client" swarm "github.com/ethereum/go-ethereum/swarm/api/client"
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
"github.com/ethereum/go-ethereum/swarm/testutil" "github.com/ethereum/go-ethereum/swarm/testutil"
) )
@ -54,7 +55,7 @@ var DefaultCurve = crypto.S256()
// is then fetched through 2nd node. since the tested code is not key-aware - we can just // is then fetched through 2nd node. since the tested code is not key-aware - we can just
// fetch from the 2nd node using HTTP BasicAuth // fetch from the 2nd node using HTTP BasicAuth
func TestAccessPassword(t *testing.T) { func TestAccessPassword(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
dataFilename := testutil.TempFileWithContent(t, data) dataFilename := testutil.TempFileWithContent(t, data)

View file

@ -19,9 +19,7 @@ package main
import ( import (
"bytes" "bytes"
"crypto/md5" "crypto/md5"
"crypto/rand"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"runtime" "runtime"
@ -29,6 +27,7 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/swarm" "github.com/ethereum/go-ethereum/swarm"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
// TestCLISwarmExportImport perform the following test: // TestCLISwarmExportImport perform the following test:
@ -45,11 +44,12 @@ func TestCLISwarmExportImport(t *testing.T) {
cluster := newTestCluster(t, 1) cluster := newTestCluster(t, 1)
// generate random 10mb file // generate random 10mb file
f, cleanup := generateRandomFile(t, 10000000) content := testutil.RandomBytes(1, 10000000)
defer cleanup() fileName := testutil.TempFileWithContent(t, string(content))
defer os.Remove(fileName)
// upload the file with 'swarm up' and expect a hash // upload the file with 'swarm up' and expect a hash
up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", f.Name()) up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", fileName)
_, matches := up.ExpectRegexp(`[a-f\d]{64}`) _, matches := up.ExpectRegexp(`[a-f\d]{64}`)
up.ExpectExit() up.ExpectExit()
hash := matches[0] hash := matches[0]
@ -96,7 +96,7 @@ func TestCLISwarmExportImport(t *testing.T) {
} }
// compare downloaded file with the generated random file // compare downloaded file with the generated random file
mustEqualFiles(t, f, res.Body) mustEqualFiles(t, bytes.NewReader(content), res.Body)
} }
func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) { func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) {
@ -117,27 +117,3 @@ func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) {
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) 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
}

View file

@ -20,49 +20,37 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
"github.com/ethereum/go-ethereum/swarm/testutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/swarm/storage/feed"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/api"
swarm "github.com/ethereum/go-ethereum/swarm/api/client" swarm "github.com/ethereum/go-ethereum/swarm/api/client"
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http" swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
"github.com/ethereum/go-ethereum/swarm/storage/feed"
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
func TestCLIFeedUpdate(t *testing.T) { func TestCLIFeedUpdate(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, func(api *api.API) testutil.TestServer { srv := swarmhttp.NewTestSwarmServer(t, func(api *api.API) swarmhttp.TestServer {
return swarmhttp.NewServer(api, "") return swarmhttp.NewServer(api, "")
}, nil) }, nil)
log.Info("starting a test swarm server") log.Info("starting a test swarm server")
defer srv.Close() defer srv.Close()
// create a private key file for signing // create a private key file for signing
pkfile, err := ioutil.TempFile("", "swarm-test")
if err != nil {
t.Fatal(err)
}
defer pkfile.Close()
defer os.Remove(pkfile.Name())
privkeyHex := "0000000000000000000000000000000000000000000000000000000000001979" privkeyHex := "0000000000000000000000000000000000000000000000000000000000001979"
privKey, _ := crypto.HexToECDSA(privkeyHex) privKey, _ := crypto.HexToECDSA(privkeyHex)
address := crypto.PubkeyToAddress(privKey.PublicKey) address := crypto.PubkeyToAddress(privKey.PublicKey)
// save the private key to a file pkFileName := testutil.TempFileWithContent(t, privkeyHex)
_, err = io.WriteString(pkfile, privkeyHex) defer os.Remove(pkFileName)
if err != nil {
t.Fatal(err)
}
// compose a topic. We'll be doing quotes about Miguel de Cervantes // compose a topic. We'll be doing quotes about Miguel de Cervantes
var topic feed.Topic var topic feed.Topic
@ -76,7 +64,7 @@ func TestCLIFeedUpdate(t *testing.T) {
flags := []string{ flags := []string{
"--bzzapi", srv.URL, "--bzzapi", srv.URL,
"--bzzaccount", pkfile.Name(), "--bzzaccount", pkFileName,
"feed", "update", "feed", "update",
"--topic", topic.Hex(), "--topic", topic.Hex(),
"--name", name, "--name", name,
@ -89,13 +77,10 @@ func TestCLIFeedUpdate(t *testing.T) {
// now try to get the update using the client // now try to get the update using the client
client := swarm.NewClient(srv.URL) client := swarm.NewClient(srv.URL)
if err != nil {
t.Fatal(err)
}
// build the same topic as before, this time // build the same topic as before, this time
// we use NewTopic to create a topic automatically. // we use NewTopic to create a topic automatically.
topic, err = feed.NewTopic(name, subject) topic, err := feed.NewTopic(name, subject)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -153,7 +138,7 @@ func TestCLIFeedUpdate(t *testing.T) {
// test publishing a manifest // test publishing a manifest
flags = []string{ flags = []string{
"--bzzapi", srv.URL, "--bzzapi", srv.URL,
"--bzzaccount", pkfile.Name(), "--bzzaccount", pkFileName,
"feed", "create", "feed", "create",
"--topic", topic.Hex(), "--topic", topic.Hex(),
} }

View file

@ -26,7 +26,7 @@ import (
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
swarm "github.com/ethereum/go-ethereum/swarm/api/client" swarm "github.com/ethereum/go-ethereum/swarm/api/client"
"github.com/ethereum/go-ethereum/swarm/testutil" swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
) )
// TestManifestChange tests manifest add, update and remove // TestManifestChange tests manifest add, update and remove
@ -58,7 +58,7 @@ func TestManifestChangeEncrypted(t *testing.T) {
// Argument encrypt controls whether to use encryption or not. // Argument encrypt controls whether to use encryption or not.
func testManifestChange(t *testing.T, encrypt bool) { func testManifestChange(t *testing.T, encrypt bool) {
t.Parallel() t.Parallel()
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
tmp, err := ioutil.TempDir("", "swarm-manifest-test") tmp, err := ioutil.TempDir("", "swarm-manifest-test")
@ -430,7 +430,7 @@ func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) {
func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) { func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
t.Parallel() t.Parallel()
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
tmp, err := ioutil.TempDir("", "swarm-manifest-test") tmp, err := ioutil.TempDir("", "swarm-manifest-test")

View file

@ -42,7 +42,6 @@ import (
"github.com/ethereum/go-ethereum/swarm" "github.com/ethereum/go-ethereum/swarm"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http" swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
var loglevel = flag.Int("loglevel", 3, "verbosity of logs") var loglevel = flag.Int("loglevel", 3, "verbosity of logs")
@ -58,7 +57,7 @@ func init() {
}) })
} }
func serverFunc(api *api.API) testutil.TestServer { func serverFunc(api *api.API) swarmhttp.TestServer {
return swarmhttp.NewServer(api, "") return swarmhttp.NewServer(api, "")
} }
func TestMain(m *testing.M) { func TestMain(m *testing.M) {

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
swarm "github.com/ethereum/go-ethereum/swarm/api/client" swarm "github.com/ethereum/go-ethereum/swarm/api/client"
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
"github.com/ethereum/go-ethereum/swarm/testutil" "github.com/ethereum/go-ethereum/swarm/testutil"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
) )
@ -77,33 +78,22 @@ func testCLISwarmUp(toEncrypt bool, t *testing.T) {
cluster := newTestCluster(t, 3) cluster := newTestCluster(t, 3)
defer cluster.Shutdown() defer cluster.Shutdown()
// create a tmp file tmpFileName := testutil.TempFileWithContent(t, data)
tmp, err := ioutil.TempFile("", "swarm-test") defer os.Remove(tmpFileName)
if err != nil {
t.Fatal(err)
}
defer tmp.Close()
defer os.Remove(tmp.Name())
// write data to file // write data to file
data := "notsorandomdata"
_, err = io.WriteString(tmp, data)
if err != nil {
t.Fatal(err)
}
hashRegexp := `[a-f\d]{64}` hashRegexp := `[a-f\d]{64}`
flags := []string{ flags := []string{
"--bzzapi", cluster.Nodes[0].URL, "--bzzapi", cluster.Nodes[0].URL,
"up", "up",
tmp.Name()} tmpFileName}
if toEncrypt { if toEncrypt {
hashRegexp = `[a-f\d]{128}` hashRegexp = `[a-f\d]{128}`
flags = []string{ flags = []string{
"--bzzapi", cluster.Nodes[0].URL, "--bzzapi", cluster.Nodes[0].URL,
"up", "up",
"--encrypt", "--encrypt",
tmp.Name()} tmpFileName}
} }
// upload the file with 'swarm up' and expect a hash // upload the file with 'swarm up' and expect a hash
log.Info(fmt.Sprintf("uploading file with 'swarm up'")) log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
@ -203,7 +193,6 @@ func testCLISwarmUpRecursive(toEncrypt bool, t *testing.T) {
} }
defer os.RemoveAll(tmpUploadDir) defer os.RemoveAll(tmpUploadDir)
// create tmp files // create tmp files
data := "notsorandomdata"
for _, path := range []string{"tmp1", "tmp2"} { for _, path := range []string{"tmp1", "tmp2"} {
if err := ioutil.WriteFile(filepath.Join(tmpUploadDir, path), bytes.NewBufferString(data).Bytes(), 0644); err != nil { if err := ioutil.WriteFile(filepath.Join(tmpUploadDir, path), bytes.NewBufferString(data).Bytes(), 0644); err != nil {
t.Fatal(err) t.Fatal(err)
@ -298,7 +287,7 @@ func TestCLISwarmUpDefaultPath(t *testing.T) {
} }
func testCLISwarmUpDefaultPath(toEncrypt bool, absDefaultPath bool, t *testing.T) { func testCLISwarmUpDefaultPath(toEncrypt bool, absDefaultPath bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
tmp, err := ioutil.TempDir("", "swarm-defaultpath-test") tmp, err := ioutil.TempDir("", "swarm-defaultpath-test")

View file

@ -295,7 +295,12 @@ var (
CacheDatabaseFlag = cli.IntFlag{ CacheDatabaseFlag = cli.IntFlag{
Name: "cache.database", Name: "cache.database",
Usage: "Percentage of cache memory allowance to use for database io", Usage: "Percentage of cache memory allowance to use for database io",
Value: 75, Value: 50,
}
CacheTrieFlag = cli.IntFlag{
Name: "cache.trie",
Usage: "Percentage of cache memory allowance to use for trie caching",
Value: 25,
} }
CacheGCFlag = cli.IntFlag{ CacheGCFlag = cli.IntFlag{
Name: "cache.gc", Name: "cache.gc",
@ -1157,8 +1162,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
} }
cfg.NoPruning = ctx.GlobalString(GCModeFlag.Name) == "archive" cfg.NoPruning = ctx.GlobalString(GCModeFlag.Name) == "archive"
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
cfg.TrieCleanCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100
}
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
cfg.TrieCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 cfg.TrieDirtyCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
} }
if ctx.GlobalIsSet(MinerNotifyFlag.Name) { if ctx.GlobalIsSet(MinerNotifyFlag.Name) {
cfg.MinerNotify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",") cfg.MinerNotify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",")
@ -1394,11 +1402,15 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
} }
cache := &core.CacheConfig{ cache := &core.CacheConfig{
Disabled: ctx.GlobalString(GCModeFlag.Name) == "archive", Disabled: ctx.GlobalString(GCModeFlag.Name) == "archive",
TrieNodeLimit: eth.DefaultConfig.TrieCache, TrieCleanLimit: eth.DefaultConfig.TrieCleanCache,
TrieDirtyLimit: eth.DefaultConfig.TrieDirtyCache,
TrieTimeLimit: eth.DefaultConfig.TrieTimeout, TrieTimeLimit: eth.DefaultConfig.TrieTimeout,
} }
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
cache.TrieCleanLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100
}
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
cache.TrieNodeLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 cache.TrieDirtyLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
} }
vmcfg := vm.Config{EnablePreimageRecording: ctx.GlobalBool(VMEnableDebugFlag.Name)} vmcfg := vm.Config{EnablePreimageRecording: ctx.GlobalBool(VMEnableDebugFlag.Name)}
chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg, nil) chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg, nil)

View file

@ -696,7 +696,7 @@ func (c *Clique) SealHash(header *types.Header) common.Hash {
return sigHash(header) return sigHash(header)
} }
// Close implements consensus.Engine. It's a noop for clique as there is are no background threads. // Close implements consensus.Engine. It's a noop for clique as there are no background threads.
func (c *Clique) Close() error { func (c *Clique) Close() error {
return nil return nil
} }

View file

@ -69,7 +69,8 @@ const (
// that's resident in a blockchain. // that's resident in a blockchain.
type CacheConfig struct { type CacheConfig struct {
Disabled bool // Whether to disable trie write caching (archive node) Disabled bool // Whether to disable trie write caching (archive node)
TrieNodeLimit int // Memory limit (MB) at which to flush the current in-memory trie to disk TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory
TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk
TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
} }
@ -140,7 +141,8 @@ type BlockChain struct {
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool) (*BlockChain, error) { func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool) (*BlockChain, error) {
if cacheConfig == nil { if cacheConfig == nil {
cacheConfig = &CacheConfig{ cacheConfig = &CacheConfig{
TrieNodeLimit: 256 * 1024 * 1024, TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute, TrieTimeLimit: 5 * time.Minute,
} }
} }
@ -156,7 +158,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
cacheConfig: cacheConfig, cacheConfig: cacheConfig,
db: db, db: db,
triegc: prque.New(nil), triegc: prque.New(nil),
stateCache: state.NewDatabase(db), stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
quit: make(chan struct{}), quit: make(chan struct{}),
shouldPreserve: shouldPreserve, shouldPreserve: shouldPreserve,
bodyCache: bodyCache, bodyCache: bodyCache,
@ -393,6 +395,11 @@ func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
return state.New(root, bc.stateCache) return state.New(root, bc.stateCache)
} }
// StateCache returns the caching database underpinning the blockchain instance.
func (bc *BlockChain) StateCache() state.Database {
return bc.stateCache
}
// Reset purges the entire blockchain, restoring it to its genesis state. // Reset purges the entire blockchain, restoring it to its genesis state.
func (bc *BlockChain) Reset() error { func (bc *BlockChain) Reset() error {
return bc.ResetWithGenesisBlock(bc.genesisBlock) return bc.ResetWithGenesisBlock(bc.genesisBlock)
@ -554,6 +561,17 @@ func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool {
return rawdb.HasBody(bc.db, hash, number) return rawdb.HasBody(bc.db, hash, number)
} }
// HasFastBlock checks if a fast block is fully present in the database or not.
func (bc *BlockChain) HasFastBlock(hash common.Hash, number uint64) bool {
if !bc.HasBlock(hash, number) {
return false
}
if bc.receiptsCache.Contains(hash) {
return true
}
return rawdb.HasReceipts(bc.db, hash, number)
}
// HasState checks if state trie is fully present in the database or not. // HasState checks if state trie is fully present in the database or not.
func (bc *BlockChain) HasState(hash common.Hash) bool { func (bc *BlockChain) HasState(hash common.Hash) bool {
_, err := bc.stateCache.OpenTrie(hash) _, err := bc.stateCache.OpenTrie(hash)
@ -611,12 +629,10 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
if receipts, ok := bc.receiptsCache.Get(hash); ok { if receipts, ok := bc.receiptsCache.Get(hash); ok {
return receipts.(types.Receipts) return receipts.(types.Receipts)
} }
number := rawdb.ReadHeaderNumber(bc.db, hash) number := rawdb.ReadHeaderNumber(bc.db, hash)
if number == nil { if number == nil {
return nil return nil
} }
receipts := rawdb.ReadReceipts(bc.db, hash, *number) receipts := rawdb.ReadReceipts(bc.db, hash, *number)
bc.receiptsCache.Add(hash, receipts) bc.receiptsCache.Add(hash, receipts)
return receipts return receipts
@ -938,7 +954,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
// If we exceeded our memory allowance, flush matured singleton nodes to disk // If we exceeded our memory allowance, flush matured singleton nodes to disk
var ( var (
nodes, imgs = triedb.Size() nodes, imgs = triedb.Size()
limit = common.StorageSize(bc.cacheConfig.TrieNodeLimit) * 1024 * 1024 limit = common.StorageSize(bc.cacheConfig.TrieDirtyLimit) * 1024 * 1024
) )
if nodes > limit || imgs > 4*1024*1024 { if nodes > limit || imgs > 4*1024*1024 {
triedb.Cap(limit - ethdb.IdealBatchSize) triedb.Cap(limit - ethdb.IdealBatchSize)

View file

@ -271,6 +271,15 @@ func DeleteTd(db DatabaseDeleter, hash common.Hash, number uint64) {
} }
} }
// HasReceipts verifies the existence of all the transaction receipts belonging
// to a block.
func HasReceipts(db DatabaseReader, hash common.Hash, number uint64) bool {
if has, err := db.Has(blockReceiptsKey(number, hash)); !has || err != nil {
return false
}
return true
}
// ReadReceipts retrieves all the transaction receipts belonging to a block. // ReadReceipts retrieves all the transaction receipts belonging to a block.
func ReadReceipts(db DatabaseReader, hash common.Hash, number uint64) types.Receipts { func ReadReceipts(db DatabaseReader, hash common.Hash, number uint64) types.Receipts {
// Retrieve the flattened receipt slice // Retrieve the flattened receipt slice

View file

@ -72,13 +72,19 @@ type Trie interface {
} }
// NewDatabase creates a backing store for state. The returned database is safe for // NewDatabase creates a backing store for state. The returned database is safe for
// concurrent use and retains cached trie nodes in memory. The pool is an optional // concurrent use and retains a few recent expanded trie nodes in memory. To keep
// intermediate trie-node memory pool between the low level storage layer and the // more historical state in memory, use the NewDatabaseWithCache constructor.
// high level trie abstraction.
func NewDatabase(db ethdb.Database) Database { func NewDatabase(db ethdb.Database) Database {
return NewDatabaseWithCache(db, 0)
}
// NewDatabase creates a backing store for state. The returned database is safe for
// concurrent use and retains both a few recent expanded trie nodes in memory, as
// well as a lot of collapsed RLP trie nodes in a large memory cache.
func NewDatabaseWithCache(db ethdb.Database, cache int) Database {
csc, _ := lru.New(codeSizeCacheSize) csc, _ := lru.New(codeSizeCacheSize)
return &cachingDB{ return &cachingDB{
db: trie.NewDatabase(db), db: trie.NewDatabaseWithCache(db, cache),
codeSizeCache: csc, codeSizeCache: csc,
} }
} }

View file

@ -444,16 +444,16 @@ func (api *PrivateDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Bloc
if startBlock.Number().Uint64() >= endBlock.Number().Uint64() { if startBlock.Number().Uint64() >= endBlock.Number().Uint64() {
return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64())
} }
triedb := api.eth.BlockChain().StateCache().TrieDB()
oldTrie, err := trie.NewSecure(startBlock.Root(), trie.NewDatabase(api.eth.chainDb), 0) oldTrie, err := trie.NewSecure(startBlock.Root(), triedb, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
newTrie, err := trie.NewSecure(endBlock.Root(), trie.NewDatabase(api.eth.chainDb), 0) newTrie, err := trie.NewSecure(endBlock.Root(), triedb, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
diff, _ := trie.NewDifferenceIterator(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{})) diff, _ := trie.NewDifferenceIterator(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}))
iter := trie.NewIterator(diff) iter := trie.NewIterator(diff)

View file

@ -138,7 +138,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
// Ensure we have a valid starting state before doing any work // Ensure we have a valid starting state before doing any work
origin := start.NumberU64() origin := start.NumberU64()
database := state.NewDatabase(api.eth.ChainDb()) database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) // Chain tracing will probably start at genesis
if number := start.NumberU64(); number > 0 { if number := start.NumberU64(); number > 0 {
start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
@ -492,7 +492,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
} }
// Otherwise try to reexec blocks until we find a state or reach our limit // Otherwise try to reexec blocks until we find a state or reach our limit
origin := block.NumberU64() origin := block.NumberU64()
database := state.NewDatabase(api.eth.ChainDb()) database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16)
for i := uint64(0); i < reexec; i++ { for i := uint64(0); i < reexec; i++ {
block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)

View file

@ -154,7 +154,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
EWASMInterpreter: config.EWASMInterpreter, EWASMInterpreter: config.EWASMInterpreter,
EVMInterpreter: config.EVMInterpreter, EVMInterpreter: config.EVMInterpreter,
} }
cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieNodeLimit: config.TrieCache, TrieTimeLimit: config.TrieTimeout} cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieCleanLimit: config.TrieCleanCache, TrieDirtyLimit: config.TrieDirtyCache, TrieTimeLimit: config.TrieTimeout}
) )
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, eth.chainConfig, eth.engine, vmConfig, eth.shouldPreserve) eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, eth.chainConfig, eth.engine, vmConfig, eth.shouldPreserve)
if err != nil { if err != nil {

View file

@ -45,8 +45,9 @@ var DefaultConfig = Config{
}, },
NetworkId: 1, NetworkId: 1,
LightPeers: 100, LightPeers: 100,
DatabaseCache: 768, DatabaseCache: 512,
TrieCache: 256, TrieCleanCache: 256,
TrieDirtyCache: 256,
TrieTimeout: 60 * time.Minute, TrieTimeout: 60 * time.Minute,
MinerGasFloor: 8000000, MinerGasFloor: 8000000,
MinerGasCeil: 8000000, MinerGasCeil: 8000000,
@ -94,7 +95,8 @@ type Config struct {
SkipBcVersionCheck bool `toml:"-"` SkipBcVersionCheck bool `toml:"-"`
DatabaseHandles int `toml:"-"` DatabaseHandles int `toml:"-"`
DatabaseCache int DatabaseCache int
TrieCache int TrieCleanCache int
TrieDirtyCache int
TrieTimeout time.Duration TrieTimeout time.Duration
// Mining-related options // Mining-related options

View file

@ -181,6 +181,9 @@ type BlockChain interface {
// HasBlock verifies a block's presence in the local chain. // HasBlock verifies a block's presence in the local chain.
HasBlock(common.Hash, uint64) bool HasBlock(common.Hash, uint64) bool
// HasFastBlock verifies a fast block's presence in the local chain.
HasFastBlock(common.Hash, uint64) bool
// GetBlockByHash retrieves a block from the local chain. // GetBlockByHash retrieves a block from the local chain.
GetBlockByHash(common.Hash) *types.Block GetBlockByHash(common.Hash) *types.Block
@ -430,7 +433,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
} }
height := latest.Number.Uint64() height := latest.Number.Uint64()
origin, err := d.findAncestor(p, height) origin, err := d.findAncestor(p, latest)
if err != nil { if err != nil {
return err return err
} }
@ -587,41 +590,86 @@ func (d *Downloader) fetchHeight(p *peerConnection) (*types.Header, error) {
} }
} }
// calculateRequestSpan calculates what headers to request from a peer when trying to determine the
// common ancestor.
// It returns parameters to be used for peer.RequestHeadersByNumber:
// from - starting block number
// count - number of headers to request
// skip - number of headers to skip
// and also returns 'max', the last block which is expected to be returned by the remote peers,
// given the (from,count,skip)
func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) {
var (
from int
count int
MaxCount = MaxHeaderFetch / 16
)
// requestHead is the highest block that we will ask for. If requestHead is not offset,
// the highest block that we will get is 16 blocks back from head, which means we
// will fetch 14 or 15 blocks unnecessarily in the case the height difference
// between us and the peer is 1-2 blocks, which is most common
requestHead := int(remoteHeight) - 1
if requestHead < 0 {
requestHead = 0
}
// requestBottom is the lowest block we want included in the query
// Ideally, we want to include just below own head
requestBottom := int(localHeight - 1)
if requestBottom < 0 {
requestBottom = 0
}
totalSpan := requestHead - requestBottom
span := 1 + totalSpan/MaxCount
if span < 2 {
span = 2
}
if span > 16 {
span = 16
}
count = 1 + totalSpan/span
if count > MaxCount {
count = MaxCount
}
if count < 2 {
count = 2
}
from = requestHead - (count-1)*span
if from < 0 {
from = 0
}
max := from + (count-1)*span
return int64(from), count, span - 1, uint64(max)
}
// findAncestor tries to locate the common ancestor link of the local chain and // findAncestor tries to locate the common ancestor link of the local chain and
// a remote peers blockchain. In the general case when our node was in sync and // a remote peers blockchain. In the general case when our node was in sync and
// on the correct chain, checking the top N links should already get us a match. // on the correct chain, checking the top N links should already get us a match.
// In the rare scenario when we ended up on a long reorganisation (i.e. none of // In the rare scenario when we ended up on a long reorganisation (i.e. none of
// the head links match), we do a binary search to find the common ancestor. // the head links match), we do a binary search to find the common ancestor.
func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, error) { func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) (uint64, error) {
// Figure out the valid ancestor range to prevent rewrite attacks // Figure out the valid ancestor range to prevent rewrite attacks
floor, ceil := int64(-1), d.lightchain.CurrentHeader().Number.Uint64() var (
floor = int64(-1)
localHeight uint64
remoteHeight = remoteHeader.Number.Uint64()
)
switch d.mode {
case FullSync:
localHeight = d.blockchain.CurrentBlock().NumberU64()
case FastSync:
localHeight = d.blockchain.CurrentFastBlock().NumberU64()
default:
localHeight = d.lightchain.CurrentHeader().Number.Uint64()
}
p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight)
if localHeight >= MaxForkAncestry {
floor = int64(localHeight - MaxForkAncestry)
}
from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight)
if d.mode == FullSync { p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip)
ceil = d.blockchain.CurrentBlock().NumberU64() go p.peer.RequestHeadersByNumber(uint64(from), count, skip, false)
} else if d.mode == FastSync {
ceil = d.blockchain.CurrentFastBlock().NumberU64()
}
if ceil >= MaxForkAncestry {
floor = int64(ceil - MaxForkAncestry)
}
p.log.Debug("Looking for common ancestor", "local", ceil, "remote", height)
// Request the topmost blocks to short circuit binary ancestor lookup
head := ceil
if head > height {
head = height
}
from := int64(head) - int64(MaxHeaderFetch)
if from < 0 {
from = 0
}
// Span out with 15 block gaps into the future to catch bad head reports
limit := 2 * MaxHeaderFetch / 16
count := 1 + int((int64(ceil)-from)/16)
if count > limit {
count = limit
}
go p.peer.RequestHeadersByNumber(uint64(from), count, 15, false)
// Wait for the remote response to the head fetch // Wait for the remote response to the head fetch
number, hash := uint64(0), common.Hash{} number, hash := uint64(0), common.Hash{}
@ -647,9 +695,10 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
return 0, errEmptyHeaderSet return 0, errEmptyHeaderSet
} }
// Make sure the peer's reply conforms to the request // Make sure the peer's reply conforms to the request
for i := 0; i < len(headers); i++ { for i, header := range headers {
if number := headers[i].Number.Int64(); number != from+int64(i)*16 { expectNumber := from + int64(i)*int64((skip+1))
p.log.Warn("Head headers broke chain ordering", "index", i, "requested", from+int64(i)*16, "received", number) if number := header.Number.Int64(); number != expectNumber {
p.log.Warn("Head headers broke chain ordering", "index", i, "requested", expectNumber, "received", number)
return 0, errInvalidChain return 0, errInvalidChain
} }
} }
@ -657,20 +706,24 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
finished = true finished = true
for i := len(headers) - 1; i >= 0; i-- { for i := len(headers) - 1; i >= 0; i-- {
// Skip any headers that underflow/overflow our requested set // Skip any headers that underflow/overflow our requested set
if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > ceil { if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > max {
continue continue
} }
// Otherwise check if we already know the header or not // Otherwise check if we already know the header or not
h := headers[i].Hash() h := headers[i].Hash()
n := headers[i].Number.Uint64() n := headers[i].Number.Uint64()
if (d.mode == FullSync && d.blockchain.HasBlock(h, n)) || (d.mode != FullSync && d.lightchain.HasHeader(h, n)) {
number, hash = n, h
// If every header is known, even future ones, the peer straight out lied about its head var known bool
if number > height && i == limit-1 { switch d.mode {
p.log.Warn("Lied about chain head", "reported", height, "found", number) case FullSync:
return 0, errStallingPeer known = d.blockchain.HasBlock(h, n)
case FastSync:
known = d.blockchain.HasFastBlock(h, n)
default:
known = d.lightchain.HasHeader(h, n)
} }
if known {
number, hash = n, h
break break
} }
} }
@ -694,10 +747,12 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
return number, nil return number, nil
} }
// Ancestor not found, we need to binary search over our chain // Ancestor not found, we need to binary search over our chain
start, end := uint64(0), head start, end := uint64(0), remoteHeight
if floor > 0 { if floor > 0 {
start = uint64(floor) start = uint64(floor)
} }
p.log.Trace("Binary searching for common ancestor", "start", start, "end", end)
for start+1 < end { for start+1 < end {
// Split our chain interval in two, and request the hash to cross check // Split our chain interval in two, and request the hash to cross check
check := (start + end) / 2 check := (start + end) / 2
@ -730,7 +785,17 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
// Modify the search interval based on the response // Modify the search interval based on the response
h := headers[0].Hash() h := headers[0].Hash()
n := headers[0].Number.Uint64() n := headers[0].Number.Uint64()
if (d.mode == FullSync && !d.blockchain.HasBlock(h, n)) || (d.mode != FullSync && !d.lightchain.HasHeader(h, n)) {
var known bool
switch d.mode {
case FullSync:
known = d.blockchain.HasBlock(h, n)
case FastSync:
known = d.blockchain.HasFastBlock(h, n)
default:
known = d.lightchain.HasHeader(h, n)
}
if !known {
end = check end = check
break break
} }

View file

@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"testing" "testing"
@ -114,6 +115,15 @@ func (dl *downloadTester) HasBlock(hash common.Hash, number uint64) bool {
return dl.GetBlockByHash(hash) != nil return dl.GetBlockByHash(hash) != nil
} }
// HasFastBlock checks if a block is present in the testers canonical chain.
func (dl *downloadTester) HasFastBlock(hash common.Hash, number uint64) bool {
dl.lock.RLock()
defer dl.lock.RUnlock()
_, ok := dl.ownReceipts[hash]
return ok
}
// GetHeader retrieves a header from the testers canonical chain. // GetHeader retrieves a header from the testers canonical chain.
func (dl *downloadTester) GetHeaderByHash(hash common.Hash) *types.Header { func (dl *downloadTester) GetHeaderByHash(hash common.Hash) *types.Header {
dl.lock.RLock() dl.lock.RLock()
@ -234,6 +244,7 @@ func (dl *downloadTester) InsertChain(blocks types.Blocks) (i int, err error) {
dl.ownHeaders[block.Hash()] = block.Header() dl.ownHeaders[block.Hash()] = block.Header()
} }
dl.ownBlocks[block.Hash()] = block dl.ownBlocks[block.Hash()] = block
dl.ownReceipts[block.Hash()] = make(types.Receipts, 0)
dl.stateDb.Put(block.Root().Bytes(), []byte{0x00}) dl.stateDb.Put(block.Root().Bytes(), []byte{0x00})
dl.ownChainTd[block.Hash()] = new(big.Int).Add(dl.ownChainTd[block.ParentHash()], block.Difficulty()) dl.ownChainTd[block.Hash()] = new(big.Int).Add(dl.ownChainTd[block.ParentHash()], block.Difficulty())
} }
@ -374,28 +385,28 @@ func (dlp *downloadTesterPeer) RequestNodeData(hashes []common.Hash) error {
// assertOwnChain checks if the local chain contains the correct number of items // assertOwnChain checks if the local chain contains the correct number of items
// of the various chain components. // of the various chain components.
func assertOwnChain(t *testing.T, tester *downloadTester, length int) { func assertOwnChain(t *testing.T, tester *downloadTester, length int) {
// Mark this method as a helper to report errors at callsite, not in here
t.Helper()
assertOwnForkedChain(t, tester, 1, []int{length}) assertOwnForkedChain(t, tester, 1, []int{length})
} }
// assertOwnForkedChain checks if the local forked chain contains the correct // assertOwnForkedChain checks if the local forked chain contains the correct
// number of items of the various chain components. // number of items of the various chain components.
func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, lengths []int) { func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, lengths []int) {
// Initialize the counters for the first fork // Mark this method as a helper to report errors at callsite, not in here
headers, blocks, receipts := lengths[0], lengths[0], lengths[0]-fsMinFullBlocks t.Helper()
// Initialize the counters for the first fork
headers, blocks, receipts := lengths[0], lengths[0], lengths[0]
if receipts < 0 {
receipts = 1
}
// Update the counters for each subsequent fork // Update the counters for each subsequent fork
for _, length := range lengths[1:] { for _, length := range lengths[1:] {
headers += length - common headers += length - common
blocks += length - common blocks += length - common
receipts += length - common - fsMinFullBlocks receipts += length - common
} }
switch tester.downloader.mode { if tester.downloader.mode == LightSync {
case FullSync:
receipts = 1
case LightSync:
blocks, receipts = 1, 1 blocks, receipts = 1, 1
} }
if hs := len(tester.ownHeaders); hs != headers { if hs := len(tester.ownHeaders); hs != headers {
@ -1149,7 +1160,9 @@ func testSyncProgress(t *testing.T, protocol int, mode SyncMode) {
} }
func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.SyncProgress) { func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.SyncProgress) {
// Mark this method as a helper to report errors at callsite, not in here
t.Helper() t.Helper()
p := d.Progress() p := d.Progress()
p.KnownStates, p.PulledStates = 0, 0 p.KnownStates, p.PulledStates = 0, 0
want.KnownStates, want.PulledStates = 0, 0 want.KnownStates, want.PulledStates = 0, 0
@ -1479,3 +1492,78 @@ func (ftp *floodingTestPeer) RequestHeadersByNumber(from uint64, count, skip int
} }
return nil return nil
} }
func TestRemoteHeaderRequestSpan(t *testing.T) {
testCases := []struct {
remoteHeight uint64
localHeight uint64
expected []int
}{
// Remote is way higher. We should ask for the remote head and go backwards
{1500, 1000,
[]int{1323, 1339, 1355, 1371, 1387, 1403, 1419, 1435, 1451, 1467, 1483, 1499},
},
{15000, 13006,
[]int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999},
},
//Remote is pretty close to us. We don't have to fetch as many
{1200, 1150,
[]int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199},
},
// Remote is equal to us (so on a fork with higher td)
// We should get the closest couple of ancestors
{1500, 1500,
[]int{1497, 1499},
},
// We're higher than the remote! Odd
{1000, 1500,
[]int{997, 999},
},
// Check some weird edgecases that it behaves somewhat rationally
{0, 1500,
[]int{0, 2},
},
{6000000, 0,
[]int{5999823, 5999839, 5999855, 5999871, 5999887, 5999903, 5999919, 5999935, 5999951, 5999967, 5999983, 5999999},
},
{0, 0,
[]int{0, 2},
},
}
reqs := func(from, count, span int) []int {
var r []int
num := from
for len(r) < count {
r = append(r, num)
num += span + 1
}
return r
}
for i, tt := range testCases {
from, count, span, max := calculateRequestSpan(tt.remoteHeight, tt.localHeight)
data := reqs(int(from), count, span)
if max != uint64(data[len(data)-1]) {
t.Errorf("test %d: wrong last value %d != %d", i, data[len(data)-1], max)
}
failed := false
if len(data) != len(tt.expected) {
failed = true
t.Errorf("test %d: length wrong, expected %d got %d", i, len(tt.expected), len(data))
} else {
for j, n := range data {
if n != tt.expected[j] {
failed = true
break
}
}
}
if failed {
res := strings.Replace(fmt.Sprint(data), " ", ",", -1)
exp := strings.Replace(fmt.Sprint(tt.expected), " ", ",", -1)
fmt.Printf("got: %v\n", res)
fmt.Printf("exp: %v\n", exp)
t.Errorf("test %d: wrong values", i)
}
}
}

View file

@ -28,7 +28,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
SkipBcVersionCheck bool `toml:"-"` SkipBcVersionCheck bool `toml:"-"`
DatabaseHandles int `toml:"-"` DatabaseHandles int `toml:"-"`
DatabaseCache int DatabaseCache int
TrieCache int TrieCleanCache int
TrieDirtyCache int
TrieTimeout time.Duration TrieTimeout time.Duration
Etherbase common.Address `toml:",omitempty"` Etherbase common.Address `toml:",omitempty"`
MinerNotify []string `toml:",omitempty"` MinerNotify []string `toml:",omitempty"`
@ -43,6 +44,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
GPO gasprice.Config GPO gasprice.Config
EnablePreimageRecording bool EnablePreimageRecording bool
DocRoot string `toml:"-"` DocRoot string `toml:"-"`
EWASMInterpreter string
EVMInterpreter string
} }
var enc Config var enc Config
enc.Genesis = c.Genesis enc.Genesis = c.Genesis
@ -54,7 +57,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.SkipBcVersionCheck = c.SkipBcVersionCheck enc.SkipBcVersionCheck = c.SkipBcVersionCheck
enc.DatabaseHandles = c.DatabaseHandles enc.DatabaseHandles = c.DatabaseHandles
enc.DatabaseCache = c.DatabaseCache enc.DatabaseCache = c.DatabaseCache
enc.TrieCache = c.TrieCache enc.TrieCleanCache = c.TrieCleanCache
enc.TrieDirtyCache = c.TrieDirtyCache
enc.TrieTimeout = c.TrieTimeout enc.TrieTimeout = c.TrieTimeout
enc.Etherbase = c.Etherbase enc.Etherbase = c.Etherbase
enc.MinerNotify = c.MinerNotify enc.MinerNotify = c.MinerNotify
@ -69,6 +73,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.GPO = c.GPO enc.GPO = c.GPO
enc.EnablePreimageRecording = c.EnablePreimageRecording enc.EnablePreimageRecording = c.EnablePreimageRecording
enc.DocRoot = c.DocRoot enc.DocRoot = c.DocRoot
enc.EWASMInterpreter = c.EWASMInterpreter
enc.EVMInterpreter = c.EVMInterpreter
return &enc, nil return &enc, nil
} }
@ -84,7 +90,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
SkipBcVersionCheck *bool `toml:"-"` SkipBcVersionCheck *bool `toml:"-"`
DatabaseHandles *int `toml:"-"` DatabaseHandles *int `toml:"-"`
DatabaseCache *int DatabaseCache *int
TrieCache *int TrieCleanCache *int
TrieDirtyCache *int
TrieTimeout *time.Duration TrieTimeout *time.Duration
Etherbase *common.Address `toml:",omitempty"` Etherbase *common.Address `toml:",omitempty"`
MinerNotify []string `toml:",omitempty"` MinerNotify []string `toml:",omitempty"`
@ -99,6 +106,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
GPO *gasprice.Config GPO *gasprice.Config
EnablePreimageRecording *bool EnablePreimageRecording *bool
DocRoot *string `toml:"-"` DocRoot *string `toml:"-"`
EWASMInterpreter *string
EVMInterpreter *string
} }
var dec Config var dec Config
if err := unmarshal(&dec); err != nil { if err := unmarshal(&dec); err != nil {
@ -131,8 +140,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.DatabaseCache != nil { if dec.DatabaseCache != nil {
c.DatabaseCache = *dec.DatabaseCache c.DatabaseCache = *dec.DatabaseCache
} }
if dec.TrieCache != nil { if dec.TrieCleanCache != nil {
c.TrieCache = *dec.TrieCache c.TrieCleanCache = *dec.TrieCleanCache
}
if dec.TrieDirtyCache != nil {
c.TrieDirtyCache = *dec.TrieDirtyCache
} }
if dec.TrieTimeout != nil { if dec.TrieTimeout != nil {
c.TrieTimeout = *dec.TrieTimeout c.TrieTimeout = *dec.TrieTimeout
@ -176,5 +188,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.DocRoot != nil { if dec.DocRoot != nil {
c.DocRoot = *dec.DocRoot c.DocRoot = *dec.DocRoot
} }
if dec.EWASMInterpreter != nil {
c.EWASMInterpreter = *dec.EWASMInterpreter
}
if dec.EVMInterpreter != nil {
c.EVMInterpreter = *dec.EVMInterpreter
}
return nil return nil
} }

View file

@ -653,12 +653,12 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
trueHead = request.Block.ParentHash() trueHead = request.Block.ParentHash()
trueTD = new(big.Int).Sub(request.TD, request.Block.Difficulty()) trueTD = new(big.Int).Sub(request.TD, request.Block.Difficulty())
) )
// Update the peers total difficulty if better than the previous // Update the peer's total difficulty if better than the previous
if _, td := p.Head(); trueTD.Cmp(td) > 0 { if _, td := p.Head(); trueTD.Cmp(td) > 0 {
p.SetHead(trueHead, trueTD) p.SetHead(trueHead, trueTD)
// Schedule a sync if above ours. Note, this will not fire a sync for a gap of // Schedule a sync if above ours. Note, this will not fire a sync for a gap of
// a singe block (as the true TD is below the propagated block), however this // a single block (as the true TD is below the propagated block), however this
// scenario should easily be covered by the fetcher. // scenario should easily be covered by the fetcher.
currentBlock := pm.blockchain.CurrentBlock() currentBlock := pm.blockchain.CurrentBlock()
if trueTD.Cmp(pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64())) > 0 { if trueTD.Cmp(pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64())) > 0 {

View file

@ -339,7 +339,7 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
return fetchKeystore(s.am).Lock(addr) == nil return fetchKeystore(s.am).Lock(addr) == nil
} }
// signTransactions sets defaults and signs the given transaction // signTransaction sets defaults and signs the given transaction
// NOTE: the caller needs to ensure that the nonceLock is held, if applicable, // NOTE: the caller needs to ensure that the nonceLock is held, if applicable,
// and release it after the transaction has been submitted to the tx pool // and release it after the transaction has been submitted to the tx pool
func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArgs, passwd string) (*types.Transaction, error) { func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArgs, passwd string) (*types.Transaction, error) {

View file

@ -683,7 +683,7 @@ func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
} }
func encodePubkey64(pub *ecdsa.PublicKey) []byte { func encodePubkey64(pub *ecdsa.PublicKey) []byte {
return crypto.FromECDSAPub(pub)[:1] return crypto.FromECDSAPub(pub)[1:]
} }
func decodePubkey64(b []byte) (*ecdsa.PublicKey, error) { func decodePubkey64(b []byte) (*ecdsa.PublicKey, error) {

View file

@ -159,7 +159,7 @@ func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64) *co
diskdb: db, diskdb: db,
odr: odr, odr: odr,
trieTable: trieTable, trieTable: trieTable,
triedb: trie.NewDatabase(trieTable), triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down
sectionSize: size, sectionSize: size,
} }
return core.NewChainIndexer(db, ethdb.NewTable(db, "chtIndex-"), backend, size, confirms, time.Millisecond*100, "cht") return core.NewChainIndexer(db, ethdb.NewTable(db, "chtIndex-"), backend, size, confirms, time.Millisecond*100, "cht")
@ -281,7 +281,7 @@ func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uin
diskdb: db, diskdb: db,
odr: odr, odr: odr,
trieTable: trieTable, trieTable: trieTable,
triedb: trie.NewDatabase(trieTable), triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down
parentSize: parentSize, parentSize: parentSize,
size: size, size: size,
} }

View file

@ -18,6 +18,7 @@ package simulations
import ( import (
"context" "context"
"flag"
"fmt" "fmt"
"math/rand" "math/rand"
"net/http/httptest" "net/http/httptest"
@ -28,13 +29,26 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"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/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
colorable "github.com/mattn/go-colorable"
) )
var (
loglevel = flag.Int("loglevel", 2, "verbosity of logs")
)
func init() {
flag.Parse()
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
}
// testService implements the node.Service interface and provides protocols // testService implements the node.Service interface and provides protocols
// and APIs which are useful for testing nodes in a simulation network // and APIs which are useful for testing nodes in a simulation network
type testService struct { type testService struct {
@ -584,9 +598,26 @@ func TestHTTPNodeRPC(t *testing.T) {
// TestHTTPSnapshot tests creating and loading network snapshots // TestHTTPSnapshot tests creating and loading network snapshots
func TestHTTPSnapshot(t *testing.T) { func TestHTTPSnapshot(t *testing.T) {
// start the server // start the server
_, s := testHTTPServer(t) network, s := testHTTPServer(t)
defer s.Close() defer s.Close()
var eventsDone = make(chan struct{})
count := 1
eventsDoneChan := make(chan *Event)
eventSub := network.Events().Subscribe(eventsDoneChan)
go func() {
defer eventSub.Unsubscribe()
for event := range eventsDoneChan {
if event.Type == EventTypeConn && !event.Control {
count--
if count == 0 {
eventsDone <- struct{}{}
return
}
}
}
}()
// create a two-node network // create a two-node network
client := NewClient(s.URL) client := NewClient(s.URL)
nodeCount := 2 nodeCount := 2
@ -620,7 +651,7 @@ func TestHTTPSnapshot(t *testing.T) {
} }
states[i] = state states[i] = state
} }
<-eventsDone
// create a snapshot // create a snapshot
snap, err := client.CreateSnapshot() snap, err := client.CreateSnapshot()
if err != nil { if err != nil {
@ -634,9 +665,23 @@ func TestHTTPSnapshot(t *testing.T) {
} }
// create another network // create another network
_, s = testHTTPServer(t) network2, s := testHTTPServer(t)
defer s.Close() defer s.Close()
client = NewClient(s.URL) client = NewClient(s.URL)
count = 1
eventSub = network2.Events().Subscribe(eventsDoneChan)
go func() {
defer eventSub.Unsubscribe()
for event := range eventsDoneChan {
if event.Type == EventTypeConn && !event.Control {
count--
if count == 0 {
eventsDone <- struct{}{}
return
}
}
}
}()
// subscribe to events so we can check them later // subscribe to events so we can check them later
events := make(chan *Event, 100) events := make(chan *Event, 100)
@ -651,6 +696,7 @@ func TestHTTPSnapshot(t *testing.T) {
if err := client.LoadSnapshot(snap); err != nil { if err := client.LoadSnapshot(snap); err != nil {
t.Fatalf("error loading snapshot: %s", err) t.Fatalf("error loading snapshot: %s", err)
} }
<-eventsDone
// check the nodes and connection exists // check the nodes and connection exists
net, err := client.GetNetwork() net, err := client.GetNetwork()
@ -676,6 +722,9 @@ func TestHTTPSnapshot(t *testing.T) {
if conn.Other.String() != nodes[1].ID { if conn.Other.String() != nodes[1].ID {
t.Fatalf("expected connection to have other=%q, got other=%q", nodes[1].ID, conn.Other) t.Fatalf("expected connection to have other=%q, got other=%q", nodes[1].ID, conn.Other)
} }
if !conn.Up {
t.Fatal("should be up")
}
// check the node states were restored // check the node states were restored
for i, node := range nodes { for i, node := range nodes {

View file

@ -644,11 +644,18 @@ type NodeSnapshot struct {
// Snapshot creates a network snapshot // Snapshot creates a network snapshot
func (net *Network) Snapshot() (*Snapshot, error) { func (net *Network) Snapshot() (*Snapshot, error) {
return net.snapshot(nil, nil)
}
func (net *Network) SnapshotWithServices(addServices []string, removeServices []string) (*Snapshot, error) {
return net.snapshot(addServices, removeServices)
}
func (net *Network) snapshot(addServices []string, removeServices []string) (*Snapshot, error) {
net.lock.Lock() net.lock.Lock()
defer net.lock.Unlock() defer net.lock.Unlock()
snap := &Snapshot{ snap := &Snapshot{
Nodes: make([]NodeSnapshot, len(net.Nodes)), Nodes: make([]NodeSnapshot, len(net.Nodes)),
Conns: make([]Conn, len(net.Conns)),
} }
for i, node := range net.Nodes { for i, node := range net.Nodes {
snap.Nodes[i] = NodeSnapshot{Node: *node} snap.Nodes[i] = NodeSnapshot{Node: *node}
@ -660,9 +667,40 @@ func (net *Network) Snapshot() (*Snapshot, error) {
return nil, err return nil, err
} }
snap.Nodes[i].Snapshots = snapshots snap.Nodes[i].Snapshots = snapshots
for _, addSvc := range addServices {
haveSvc := false
for _, svc := range snap.Nodes[i].Node.Config.Services {
if svc == addSvc {
haveSvc = true
break
}
}
if !haveSvc {
snap.Nodes[i].Node.Config.Services = append(snap.Nodes[i].Node.Config.Services, addSvc)
}
}
if len(removeServices) > 0 {
var cleanedServices []string
for _, svc := range snap.Nodes[i].Node.Config.Services {
haveSvc := false
for _, rmSvc := range removeServices {
if rmSvc == svc {
haveSvc = true
break
}
}
if !haveSvc {
cleanedServices = append(cleanedServices, svc)
}
}
snap.Nodes[i].Node.Config.Services = cleanedServices
}
}
for _, conn := range net.Conns {
if conn.Up {
snap.Conns = append(snap.Conns, *conn)
} }
for i, conn := range net.Conns {
snap.Conns[i] = *conn
} }
return snap, nil return snap, nil
} }

View file

@ -49,10 +49,10 @@ var (
// MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network.
MainnetTrustedCheckpoint = &TrustedCheckpoint{ MainnetTrustedCheckpoint = &TrustedCheckpoint{
Name: "mainnet", Name: "mainnet",
SectionIndex: 195, SectionIndex: 203,
SectionHead: common.HexToHash("0x1cdd2a84cf6c1261ffccc88f6bcefb513abd7934a96c1e909fbf74767560f16b"), SectionHead: common.HexToHash("0xc9e05fc67c6a9815adc8072eb18805b53da53a9a6a273e05541e1b7542cf937a"),
CHTRoot: common.HexToHash("0xe453333c20391d16b91b6fe11c104704f62c8dba15f69db73b4cdf7e100105eb"), CHTRoot: common.HexToHash("0xb85f42447d59f7c3e6679b9a37ed983593fd52efd6251b883592662e95769d5b"),
BloomRoot: common.HexToHash("0x47f30069473072e00d2cdca146dce40f0aad243dfc8221bf810822c091674efe"), BloomRoot: common.HexToHash("0xf93d50cb4c49b403c6fd33cd60896d3b36184275be0a51bae4df5e8844ac624c"),
} }
// TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network. // TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network.
@ -73,10 +73,10 @@ var (
// TestnetTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. // TestnetTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network.
TestnetTrustedCheckpoint = &TrustedCheckpoint{ TestnetTrustedCheckpoint = &TrustedCheckpoint{
Name: "testnet", Name: "testnet",
SectionIndex: 126, SectionIndex: 134,
SectionHead: common.HexToHash("0x48f7dd4c9c60be04bf15fd4d0bcac46ddd8caf6b01d6fb8f8e1f7955cdd1337a"), SectionHead: common.HexToHash("0x17053ecbe045bebefaa01e7716cc85a4e22647e181416cc1098ccbb73a088931"),
CHTRoot: common.HexToHash("0x6e54cb80a1884881ea1a114243af9012c95e0296b47f103b5ab124313968508e"), CHTRoot: common.HexToHash("0x4d2b86422e46ed76f0e3f50f06632c409f809c8375e53c8bc0f782bcb93dd49a"),
BloomRoot: common.HexToHash("0xb55accf6dce6455b47db8510d15eff38d0ed7378829f3036d26b48e7d15da3f6"), BloomRoot: common.HexToHash("0xccba62232ee56c2967afc58f136a47ba7dc545ae586e6be666430d94516306c7"),
} }
// RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network. // RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network.
@ -100,10 +100,10 @@ var (
// RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network.
RinkebyTrustedCheckpoint = &TrustedCheckpoint{ RinkebyTrustedCheckpoint = &TrustedCheckpoint{
Name: "rinkeby", Name: "rinkeby",
SectionIndex: 93, SectionIndex: 100,
SectionHead: common.HexToHash("0xdefb94aa217ab38f2919f7318d1d5476bd2aabf1ec9148047fe03e555615e0b4"), SectionHead: common.HexToHash("0xf18f9b43e16f37b12e68818536ffe455ff18d676274ffdd856a8520ed61bb514"),
CHTRoot: common.HexToHash("0x52c98c2fe508a8332c27dc10538f3fead43306e2b22b597587763c2fe6586da6"), CHTRoot: common.HexToHash("0x473f5d603b1fedad75d97fd58692130b9ac9ade1aca01eb9363d79bd1c43c791"),
BloomRoot: common.HexToHash("0x93d83be0c1b12f732b1a027ecdfb16f39b0d020b8c10bfb90e76f3b01adfc5b6"), BloomRoot: common.HexToHash("0xa39ced3ddbb87e909c7531df2afb6414bea9c9a60ab94da9c6b467535f05326e"),
} }
// AllEthashProtocolChanges contains every protocol change (EIPs) introduced // AllEthashProtocolChanges contains every protocol change (EIPs) introduced

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 = 18 // Patch version component of the current release VersionPatch = 19 // 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

@ -25,7 +25,7 @@ import (
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
// In this example, our client whishes to track the latest 'block number' // In this example, our client wishes to track the latest 'block number'
// known to the server. The server supports two methods: // known to the server. The server supports two methods:
// //
// eth_getBlockByNumber("latest", {}) // eth_getBlockByNumber("latest", {})

View file

@ -32,7 +32,7 @@ An example method:
func (s *CalcService) Add(a, b int) (int, error) func (s *CalcService) Add(a, b int) (int, error)
When the returned error isn't nil the returned integer is ignored and the error is When the returned error isn't nil the returned integer is ignored and the error is
send back to the client. Otherwise the returned integer is send back to the client. sent back to the client. Otherwise the returned integer is sent back to the client.
Optional arguments are supported by accepting pointer values as arguments. E.g. Optional arguments are supported by accepting pointer values as arguments. E.g.
if we want to do the addition in an optional finite field we can accept a mod if we want to do the addition in an optional finite field we can accept a mod

View file

@ -33,10 +33,9 @@ import (
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http" swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
"github.com/ethereum/go-ethereum/swarm/multihash" "github.com/ethereum/go-ethereum/swarm/multihash"
"github.com/ethereum/go-ethereum/swarm/storage/feed" "github.com/ethereum/go-ethereum/swarm/storage/feed"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
func serverFunc(api *api.API) testutil.TestServer { func serverFunc(api *api.API) swarmhttp.TestServer {
return swarmhttp.NewServer(api, "") return swarmhttp.NewServer(api, "")
} }
@ -49,7 +48,7 @@ func TestClientUploadDownloadRawEncrypted(t *testing.T) {
} }
func testClientUploadDownloadRaw(toEncrypt bool, t *testing.T) { func testClientUploadDownloadRaw(toEncrypt bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
client := NewClient(srv.URL) client := NewClient(srv.URL)
@ -90,7 +89,7 @@ func TestClientUploadDownloadFilesEncrypted(t *testing.T) {
} }
func testClientUploadDownloadFiles(toEncrypt bool, t *testing.T) { func testClientUploadDownloadFiles(toEncrypt bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
client := NewClient(srv.URL) client := NewClient(srv.URL)
@ -188,7 +187,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, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
dir := newTestDirectory(t) dir := newTestDirectory(t)
@ -254,7 +253,7 @@ func TestClientFileListEncrypted(t *testing.T) {
} }
func testClientFileList(toEncrypt bool, t *testing.T) { func testClientFileList(toEncrypt bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
dir := newTestDirectory(t) dir := newTestDirectory(t)
@ -312,7 +311,7 @@ func testClientFileList(toEncrypt bool, 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, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
// define an uploader which uploads testDirFiles with some data // define an uploader which uploads testDirFiles with some data
@ -378,7 +377,7 @@ func TestClientCreateFeedMultihash(t *testing.T) {
signer, _ := newTestSigner() signer, _ := newTestSigner()
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
client := NewClient(srv.URL) client := NewClient(srv.URL)
defer srv.Close() defer srv.Close()
@ -440,7 +439,7 @@ func TestClientCreateUpdateFeed(t *testing.T) {
signer, _ := newTestSigner() signer, _ := newTestSigner()
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
client := NewClient(srv.URL) client := NewClient(srv.URL)
defer srv.Close() defer srv.Close()

View file

@ -75,7 +75,7 @@ func InitLoggingResponseWriter(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
writer := newLoggingResponseWriter(w) writer := newLoggingResponseWriter(w)
h.ServeHTTP(writer, r) h.ServeHTTP(writer, r)
log.Debug("request served", "ruid", GetRUID(r.Context()), "code", writer.statusCode) log.Info("request served", "ruid", GetRUID(r.Context()), "code", writer.statusCode)
}) })
} }

View file

@ -24,12 +24,10 @@ import (
"testing" "testing"
"golang.org/x/net/html" "golang.org/x/net/html"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
func TestError(t *testing.T) { func TestError(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
var resp *http.Response var resp *http.Response
@ -55,7 +53,7 @@ func TestError(t *testing.T) {
} }
func Test404Page(t *testing.T) { func Test404Page(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
var resp *http.Response var resp *http.Response
@ -81,7 +79,7 @@ func Test404Page(t *testing.T) {
} }
func Test500Page(t *testing.T) { func Test500Page(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
var resp *http.Response var resp *http.Response
@ -106,7 +104,7 @@ func Test500Page(t *testing.T) {
} }
} }
func Test500PageWith0xHashPrefix(t *testing.T) { func Test500PageWith0xHashPrefix(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
var resp *http.Response var resp *http.Response
@ -136,7 +134,7 @@ func Test500PageWith0xHashPrefix(t *testing.T) {
} }
func TestJsonResponse(t *testing.T) { func TestJsonResponse(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
var resp *http.Response var resp *http.Response

View file

@ -20,7 +20,6 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"context" "context"
"crypto/rand"
"encoding/json" "encoding/json"
"errors" "errors"
"flag" "flag"
@ -58,7 +57,7 @@ func init() {
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
} }
func serverFunc(api *api.API) testutil.TestServer { func serverFunc(api *api.API) TestServer {
return NewServer(api, "") return NewServer(api, "")
} }
@ -79,7 +78,7 @@ func TestBzzFeedMultihash(t *testing.T) {
signer, _ := newTestSigner() signer, _ := newTestSigner()
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
// add the data our multihash aliased manifest will point to // add the data our multihash aliased manifest will point to
@ -167,26 +166,19 @@ func TestBzzFeedMultihash(t *testing.T) {
// Test Swarm feeds using the raw update methods // Test Swarm feeds using the raw update methods
func TestBzzFeed(t *testing.T) { func TestBzzFeed(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
signer, _ := newTestSigner() signer, _ := newTestSigner()
defer srv.Close() defer srv.Close()
// data of update 1 // data of update 1
update1Data := make([]byte, 666) update1Data := testutil.RandomBytes(1, 666)
update1Timestamp := srv.CurrentTime update1Timestamp := srv.CurrentTime
_, err := rand.Read(update1Data)
if err != nil {
t.Fatal(err)
}
//data for update 2 //data for update 2
update2Data := []byte("foo") update2Data := []byte("foo")
topic, _ := feed.NewTopic("foo.eth", nil) topic, _ := feed.NewTopic("foo.eth", nil)
updateRequest := feed.NewFirstRequest(topic) updateRequest := feed.NewFirstRequest(topic)
if err != nil {
t.Fatal(err)
}
updateRequest.SetData(update1Data) updateRequest.SetData(update1Data)
if err := updateRequest.Sign(signer); err != nil { if err := updateRequest.Sign(signer); err != nil {
@ -450,7 +442,7 @@ func testBzzGetPath(encrypted bool, t *testing.T) {
addr := [3]storage.Address{} addr := [3]storage.Address{}
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
for i, mf := range testmanifest { for i, mf := range testmanifest {
@ -688,7 +680,7 @@ func TestBzzTar(t *testing.T) {
} }
func testBzzTar(encrypted bool, t *testing.T) { func testBzzTar(encrypted bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
fileNames := []string{"tmp1.txt", "tmp2.lock", "tmp3.rtf"} fileNames := []string{"tmp1.txt", "tmp2.lock", "tmp3.rtf"}
fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"}
@ -823,7 +815,7 @@ func TestBzzRootRedirectEncrypted(t *testing.T) {
} }
func testBzzRootRedirect(toEncrypt bool, t *testing.T) { func testBzzRootRedirect(toEncrypt bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
// create a manifest with some data at the root path // create a manifest with some data at the root path
@ -878,7 +870,7 @@ func testBzzRootRedirect(toEncrypt bool, t *testing.T) {
} }
func TestMethodsNotAllowed(t *testing.T) { func TestMethodsNotAllowed(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
databytes := "bar" databytes := "bar"
for _, c := range []struct { for _, c := range []struct {
@ -937,7 +929,7 @@ func httpDo(httpMethod string, url string, reqBody io.Reader, headers map[string
} }
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
for _, testCase := range []struct { for _, testCase := range []struct {
@ -1020,7 +1012,7 @@ func TestGet(t *testing.T) {
} }
func TestModify(t *testing.T) { func TestModify(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
swarmClient := swarm.NewClient(srv.URL) swarmClient := swarm.NewClient(srv.URL)
@ -1121,7 +1113,7 @@ func TestMultiPartUpload(t *testing.T) {
// POST /bzz:/ Content-Type: multipart/form-data // POST /bzz:/ Content-Type: multipart/form-data
verbose := false verbose := false
// Setup Swarm // Setup Swarm
srv := testutil.NewTestSwarmServer(t, serverFunc, nil) srv := NewTestSwarmServer(t, serverFunc, nil)
defer srv.Close() defer srv.Close()
url := fmt.Sprintf("%s/bzz:/", srv.URL) url := fmt.Sprintf("%s/bzz:/", srv.URL)
@ -1152,7 +1144,7 @@ func TestMultiPartUpload(t *testing.T) {
// TestBzzGetFileWithResolver tests fetching a file using a mocked ENS resolver // TestBzzGetFileWithResolver tests fetching a file using a mocked ENS resolver
func TestBzzGetFileWithResolver(t *testing.T) { func TestBzzGetFileWithResolver(t *testing.T) {
resolver := newTestResolveValidator("") resolver := newTestResolveValidator("")
srv := testutil.NewTestSwarmServer(t, serverFunc, resolver) srv := NewTestSwarmServer(t, serverFunc, resolver)
defer srv.Close() defer srv.Close()
fileNames := []string{"dir1/tmp1.txt", "dir2/tmp2.lock", "dir3/tmp3.rtf"} fileNames := []string{"dir1/tmp1.txt", "dir2/tmp2.lock", "dir3/tmp3.rtf"}
fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"}

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package testutil package http
import ( import (
"io/ioutil" "io/ioutil"

View file

@ -18,10 +18,8 @@ package bmt
import ( import (
"bytes" "bytes"
crand "crypto/rand"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
"math/rand" "math/rand"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -29,6 +27,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
// the actual data length generated (could be longer than max datalength of the BMT) // the actual data length generated (could be longer than max datalength of the BMT)
@ -116,14 +115,11 @@ func TestRefHasher(t *testing.T) {
}) })
// run the tests // run the tests
for _, x := range tests { for i, x := range tests {
for segmentCount := x.from; segmentCount <= x.to; segmentCount++ { for segmentCount := x.from; segmentCount <= x.to; segmentCount++ {
for length := 1; length <= segmentCount*32; length++ { for length := 1; length <= segmentCount*32; length++ {
t.Run(fmt.Sprintf("%d_segments_%d_bytes", segmentCount, length), func(t *testing.T) { t.Run(fmt.Sprintf("%d_segments_%d_bytes", segmentCount, length), func(t *testing.T) {
data := make([]byte, length) data := testutil.RandomBytes(i, length)
if _, err := io.ReadFull(crand.Reader, data); err != nil && err != io.EOF {
t.Fatal(err)
}
expected := x.expected(data) expected := x.expected(data)
actual := NewRefHasher(sha3.NewKeccak256, segmentCount).Hash(data) actual := NewRefHasher(sha3.NewKeccak256, segmentCount).Hash(data)
if !bytes.Equal(actual, expected) { if !bytes.Equal(actual, expected) {
@ -156,7 +152,7 @@ func TestHasherEmptyData(t *testing.T) {
// tests sequential write with entire max size written in one go // tests sequential write with entire max size written in one go
func TestSyncHasherCorrectness(t *testing.T) { func TestSyncHasherCorrectness(t *testing.T) {
data := newData(BufferSize) data := testutil.RandomBytes(1, BufferSize)
hasher := sha3.NewKeccak256 hasher := sha3.NewKeccak256
size := hasher().Size() size := hasher().Size()
@ -182,7 +178,7 @@ func TestSyncHasherCorrectness(t *testing.T) {
// tests order-neutral concurrent writes with entire max size written in one go // tests order-neutral concurrent writes with entire max size written in one go
func TestAsyncCorrectness(t *testing.T) { func TestAsyncCorrectness(t *testing.T) {
data := newData(BufferSize) data := testutil.RandomBytes(1, BufferSize)
hasher := sha3.NewKeccak256 hasher := sha3.NewKeccak256
size := hasher().Size() size := hasher().Size()
whs := []whenHash{first, last, random} whs := []whenHash{first, last, random}
@ -236,7 +232,7 @@ func testHasherReuse(poolsize int, t *testing.T) {
bmt := New(pool) bmt := New(pool)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
data := newData(BufferSize) data := testutil.RandomBytes(1, BufferSize)
n := rand.Intn(bmt.Size()) n := rand.Intn(bmt.Size())
err := testHasherCorrectness(bmt, hasher, data, n, segmentCount) err := testHasherCorrectness(bmt, hasher, data, n, segmentCount)
if err != nil { if err != nil {
@ -256,7 +252,7 @@ func TestBMTConcurrentUse(t *testing.T) {
for i := 0; i < cycles; i++ { for i := 0; i < cycles; i++ {
go func() { go func() {
bmt := New(pool) bmt := New(pool)
data := newData(BufferSize) data := testutil.RandomBytes(1, BufferSize)
n := rand.Intn(bmt.Size()) n := rand.Intn(bmt.Size())
errc <- testHasherCorrectness(bmt, hasher, data, n, 128) errc <- testHasherCorrectness(bmt, hasher, data, n, 128)
}() }()
@ -290,7 +286,7 @@ func TestBMTWriterBuffers(t *testing.T) {
defer pool.Drain(0) defer pool.Drain(0)
n := count * 32 n := count * 32
bmt := New(pool) bmt := New(pool)
data := newData(n) data := testutil.RandomBytes(1, n)
rbmt := NewRefHasher(hasher, count) rbmt := NewRefHasher(hasher, count)
refHash := rbmt.Hash(data) refHash := rbmt.Hash(data)
expHash := syncHash(bmt, nil, data) expHash := syncHash(bmt, nil, data)
@ -413,7 +409,7 @@ func BenchmarkPool(t *testing.B) {
// benchmarks simple sha3 hash on chunks // benchmarks simple sha3 hash on chunks
func benchmarkSHA3(t *testing.B, n int) { func benchmarkSHA3(t *testing.B, n int) {
data := newData(n) data := testutil.RandomBytes(1, n)
hasher := sha3.NewKeccak256 hasher := sha3.NewKeccak256
h := hasher() h := hasher()
@ -432,7 +428,7 @@ func benchmarkSHA3(t *testing.B, n int) {
func benchmarkBMTBaseline(t *testing.B, n int) { func benchmarkBMTBaseline(t *testing.B, n int) {
hasher := sha3.NewKeccak256 hasher := sha3.NewKeccak256
hashSize := hasher().Size() hashSize := hasher().Size()
data := newData(hashSize) data := testutil.RandomBytes(1, hashSize)
t.ReportAllocs() t.ReportAllocs()
t.ResetTimer() t.ResetTimer()
@ -456,7 +452,7 @@ func benchmarkBMTBaseline(t *testing.B, n int) {
// benchmarks BMT Hasher // benchmarks BMT Hasher
func benchmarkBMT(t *testing.B, n int) { func benchmarkBMT(t *testing.B, n int) {
data := newData(n) data := testutil.RandomBytes(1, n)
hasher := sha3.NewKeccak256 hasher := sha3.NewKeccak256
pool := NewTreePool(hasher, segmentCount, PoolSize) pool := NewTreePool(hasher, segmentCount, PoolSize)
bmt := New(pool) bmt := New(pool)
@ -470,12 +466,12 @@ func benchmarkBMT(t *testing.B, n int) {
// benchmarks BMT hasher with asynchronous concurrent segment/section writes // benchmarks BMT hasher with asynchronous concurrent segment/section writes
func benchmarkBMTAsync(t *testing.B, n int, wh whenHash, double bool) { func benchmarkBMTAsync(t *testing.B, n int, wh whenHash, double bool) {
data := newData(n) data := testutil.RandomBytes(1, n)
hasher := sha3.NewKeccak256 hasher := sha3.NewKeccak256
pool := NewTreePool(hasher, segmentCount, PoolSize) pool := NewTreePool(hasher, segmentCount, PoolSize)
bmt := New(pool).NewAsyncWriter(double) bmt := New(pool).NewAsyncWriter(double)
idxs, segments := splitAndShuffle(bmt.SectionSize(), data) idxs, segments := splitAndShuffle(bmt.SectionSize(), data)
shuffle(len(idxs), func(i int, j int) { rand.Shuffle(len(idxs), func(i int, j int) {
idxs[i], idxs[j] = idxs[j], idxs[i] idxs[i], idxs[j] = idxs[j], idxs[i]
}) })
@ -488,7 +484,7 @@ func benchmarkBMTAsync(t *testing.B, n int, wh whenHash, double bool) {
// benchmarks 100 concurrent bmt hashes with pool capacity // benchmarks 100 concurrent bmt hashes with pool capacity
func benchmarkPool(t *testing.B, poolsize, n int) { func benchmarkPool(t *testing.B, poolsize, n int) {
data := newData(n) data := testutil.RandomBytes(1, n)
hasher := sha3.NewKeccak256 hasher := sha3.NewKeccak256
pool := NewTreePool(hasher, segmentCount, poolsize) pool := NewTreePool(hasher, segmentCount, poolsize)
cycles := 100 cycles := 100
@ -511,7 +507,7 @@ func benchmarkPool(t *testing.B, poolsize, n int) {
// benchmarks the reference hasher // benchmarks the reference hasher
func benchmarkRefHasher(t *testing.B, n int) { func benchmarkRefHasher(t *testing.B, n int) {
data := newData(n) data := testutil.RandomBytes(1, n)
hasher := sha3.NewKeccak256 hasher := sha3.NewKeccak256
rbmt := NewRefHasher(hasher, 128) rbmt := NewRefHasher(hasher, 128)
@ -522,15 +518,6 @@ func benchmarkRefHasher(t *testing.B, n int) {
} }
} }
func newData(bufferSize int) []byte {
data := make([]byte, bufferSize)
_, err := io.ReadFull(crand.Reader, data)
if err != nil {
panic(err.Error())
}
return data
}
// Hash hashes the data and the span using the bmt hasher // Hash hashes the data and the span using the bmt hasher
func syncHash(h *Hasher, span, data []byte) []byte { func syncHash(h *Hasher, span, data []byte) []byte {
h.ResetWithLength(span) h.ResetWithLength(span)
@ -553,7 +540,7 @@ func splitAndShuffle(secsize int, data []byte) (idxs []int, segments [][]byte) {
section := data[i*secsize : end] section := data[i*secsize : end]
segments = append(segments, section) segments = append(segments, section)
} }
shuffle(n, func(i int, j int) { rand.Shuffle(n, func(i int, j int) {
idxs[i], idxs[j] = idxs[j], idxs[i] idxs[i], idxs[j] = idxs[j], idxs[i]
}) })
return idxs, segments return idxs, segments
@ -594,29 +581,3 @@ func asyncHash(bmt SectionWriter, span []byte, l int, wh whenHash, idxs []int, s
} }
return <-c return <-c
} }
// this is also in swarm/network_test.go
// shuffle pseudo-randomizes the order of elements.
// n is the number of elements. Shuffle panics if n < 0.
// swap swaps the elements with indexes i and j.
func shuffle(n int, swap func(i, j int)) {
if n < 0 {
panic("invalid argument to Shuffle")
}
// Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
// Shuffle really ought not be called with n that doesn't fit in 32 bits.
// Not only will it take a very long time, but with 2³¹! possible permutations,
// there's no way that any PRNG can have a big enough internal state to
// generate even a minuscule percentage of the possible permutations.
// Nevertheless, the right API signature accepts an int n, so handle it as best we can.
i := n - 1
for ; i > 1<<31-1-1; i-- {
j := int(rand.Int63n(int64(i + 1)))
swap(i, j)
}
for ; i > 0; i-- {
j := int(rand.Int31n(int32(i + 1)))
swap(i, j)
}
}

View file

@ -20,20 +20,19 @@ package fuse
import ( import (
"bytes" "bytes"
"crypto/rand"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/rand"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/swarm/testutil"
"github.com/ethereum/go-ethereum/log"
colorable "github.com/mattn/go-colorable" colorable "github.com/mattn/go-colorable"
) )
@ -229,12 +228,6 @@ func checkFile(t *testing.T, testMountDir, fname string, contents []byte) {
} }
} }
func getRandomBytes(size int) []byte {
contents := make([]byte, size)
rand.Read(contents)
return contents
}
func isDirEmpty(name string) bool { func isDirEmpty(name string) bool {
f, err := os.Open(name) f, err := os.Open(name)
if err != nil { if err != nil {
@ -328,22 +321,22 @@ func (ta *testAPI) mountListAndUnmount(t *testing.T, toEncrypt bool) {
dat.testMountDir = filepath.Join(dat.testDir, "testMountDir") dat.testMountDir = filepath.Join(dat.testDir, "testMountDir")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat.files["2.txt"] = fileInfo{0711, 333, 444, getRandomBytes(10)} dat.files["2.txt"] = fileInfo{0711, 333, 444, testutil.RandomBytes(2, 10)}
dat.files["3.txt"] = fileInfo{0622, 333, 444, getRandomBytes(100)} dat.files["3.txt"] = fileInfo{0622, 333, 444, testutil.RandomBytes(3, 100)}
dat.files["4.txt"] = fileInfo{0533, 333, 444, getRandomBytes(1024)} dat.files["4.txt"] = fileInfo{0533, 333, 444, testutil.RandomBytes(4, 1024)}
dat.files["5.txt"] = fileInfo{0544, 333, 444, getRandomBytes(10)} dat.files["5.txt"] = fileInfo{0544, 333, 444, testutil.RandomBytes(5, 10)}
dat.files["6.txt"] = fileInfo{0555, 333, 444, getRandomBytes(10)} dat.files["6.txt"] = fileInfo{0555, 333, 444, testutil.RandomBytes(6, 10)}
dat.files["7.txt"] = fileInfo{0666, 333, 444, getRandomBytes(10)} dat.files["7.txt"] = fileInfo{0666, 333, 444, testutil.RandomBytes(7, 10)}
dat.files["8.txt"] = fileInfo{0777, 333, 333, getRandomBytes(10)} dat.files["8.txt"] = fileInfo{0777, 333, 333, testutil.RandomBytes(8, 10)}
dat.files["11.txt"] = fileInfo{0777, 333, 444, getRandomBytes(10)} dat.files["11.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(9, 10)}
dat.files["111.txt"] = fileInfo{0777, 333, 444, getRandomBytes(10)} dat.files["111.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(10, 10)}
dat.files["two/2.txt"] = fileInfo{0777, 333, 444, getRandomBytes(10)} dat.files["two/2.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(11, 10)}
dat.files["two/2/2.txt"] = fileInfo{0777, 333, 444, getRandomBytes(10)} dat.files["two/2/2.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(12, 10)}
dat.files["two/2./2.txt"] = fileInfo{0777, 444, 444, getRandomBytes(10)} dat.files["two/2./2.txt"] = fileInfo{0777, 444, 444, testutil.RandomBytes(13, 10)}
dat.files["twice/2.txt"] = fileInfo{0777, 444, 333, getRandomBytes(200)} dat.files["twice/2.txt"] = fileInfo{0777, 444, 333, testutil.RandomBytes(14, 200)}
dat.files["one/two/three/four/five/six/seven/eight/nine/10.txt"] = fileInfo{0777, 333, 444, getRandomBytes(10240)} dat.files["one/two/three/four/five/six/seven/eight/nine/10.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(15, 10240)}
dat.files["one/two/three/four/five/six/six"] = fileInfo{0777, 333, 444, getRandomBytes(10)} dat.files["one/two/three/four/five/six/six"] = fileInfo{0777, 333, 444, testutil.RandomBytes(16, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -386,7 +379,7 @@ func (ta *testAPI) runMaxMounts(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "max-upload1") dat.testUploadDir = filepath.Join(dat.testDir, "max-upload1")
dat.testMountDir = filepath.Join(dat.testDir, "max-mount1") dat.testMountDir = filepath.Join(dat.testDir, "max-mount1")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -396,7 +389,7 @@ func (ta *testAPI) runMaxMounts(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "max-upload2") dat.testUploadDir = filepath.Join(dat.testDir, "max-upload2")
dat.testMountDir = filepath.Join(dat.testDir, "max-mount2") dat.testMountDir = filepath.Join(dat.testDir, "max-mount2")
dat.files["2.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["2.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -405,7 +398,7 @@ func (ta *testAPI) runMaxMounts(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "max-upload3") dat.testUploadDir = filepath.Join(dat.testDir, "max-upload3")
dat.testMountDir = filepath.Join(dat.testDir, "max-mount3") dat.testMountDir = filepath.Join(dat.testDir, "max-mount3")
dat.files["3.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["3.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -414,7 +407,7 @@ func (ta *testAPI) runMaxMounts(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "max-upload4") dat.testUploadDir = filepath.Join(dat.testDir, "max-upload4")
dat.testMountDir = filepath.Join(dat.testDir, "max-mount4") dat.testMountDir = filepath.Join(dat.testDir, "max-mount4")
dat.files["4.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["4.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -423,7 +416,7 @@ func (ta *testAPI) runMaxMounts(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "max-upload5") dat.testUploadDir = filepath.Join(dat.testDir, "max-upload5")
dat.testMountDir = filepath.Join(dat.testDir, "max-mount5") dat.testMountDir = filepath.Join(dat.testDir, "max-mount5")
dat.files["5.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["5.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -436,7 +429,7 @@ func (ta *testAPI) runMaxMounts(t *testing.T, toEncrypt bool) {
if err != nil { if err != nil {
t.Fatalf("Couldn't create upload dir 6: %v", err) t.Fatalf("Couldn't create upload dir 6: %v", err)
} }
dat.files["6.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["6.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
testMountDir6 := filepath.Join(dat.testDir, "max-mount6") testMountDir6 := filepath.Join(dat.testDir, "max-mount6")
err = os.MkdirAll(testMountDir6, 0777) err = os.MkdirAll(testMountDir6, 0777)
if err != nil { if err != nil {
@ -475,7 +468,7 @@ func (ta *testAPI) remount(t *testing.T, toEncrypt bool) {
dat.testMountDir = filepath.Join(dat.testDir, "remount-mount1") dat.testMountDir = filepath.Join(dat.testDir, "remount-mount1")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -494,7 +487,7 @@ func (ta *testAPI) remount(t *testing.T, toEncrypt bool) {
} }
// mount a different hash in already mounted point // mount a different hash in already mounted point
dat.files["2.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["2.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
testUploadDir2, err3 := addDir(dat.testDir, "remount-upload2") testUploadDir2, err3 := addDir(dat.testDir, "remount-upload2")
if err3 != nil { if err3 != nil {
t.Fatalf("Error creating second upload dir: %v", err3) t.Fatalf("Error creating second upload dir: %v", err3)
@ -543,7 +536,7 @@ func (ta *testAPI) unmount(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "ex-upload1") dat.testUploadDir = filepath.Join(dat.testDir, "ex-upload1")
dat.testMountDir = filepath.Join(dat.testDir, "ex-mount1") dat.testMountDir = filepath.Join(dat.testDir, "ex-mount1")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -591,7 +584,7 @@ func (ta *testAPI) unmountWhenResourceBusy(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "ex-upload1") dat.testUploadDir = filepath.Join(dat.testDir, "ex-upload1")
dat.testMountDir = filepath.Join(dat.testDir, "ex-mount1") dat.testMountDir = filepath.Join(dat.testDir, "ex-mount1")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -609,7 +602,7 @@ func (ta *testAPI) unmountWhenResourceBusy(t *testing.T, toEncrypt bool) {
//we need to manually close the file before mount for this test //we need to manually close the file before mount for this test
//but let's defer too in case of errors //but let's defer too in case of errors
defer d.Close() defer d.Close()
_, err = d.Write(getRandomBytes(10)) _, err = d.Write(testutil.RandomBytes(1, 10))
if err != nil { if err != nil {
t.Fatalf("Couldn't write to file: %v", err) t.Fatalf("Couldn't write to file: %v", err)
} }
@ -667,7 +660,7 @@ func (ta *testAPI) seekInMultiChunkFile(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "seek-upload1") dat.testUploadDir = filepath.Join(dat.testDir, "seek-upload1")
dat.testMountDir = filepath.Join(dat.testDir, "seek-mount") dat.testMountDir = filepath.Join(dat.testDir, "seek-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10240)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10240)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -733,9 +726,9 @@ func (ta *testAPI) createNewFile(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "create-upload1") dat.testUploadDir = filepath.Join(dat.testDir, "create-upload1")
dat.testMountDir = filepath.Join(dat.testDir, "create-mount") dat.testMountDir = filepath.Join(dat.testDir, "create-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)}
dat.files["six.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -751,11 +744,7 @@ func (ta *testAPI) createNewFile(t *testing.T, toEncrypt bool) {
} }
defer d.Close() defer d.Close()
log.Debug("Opened file") log.Debug("Opened file")
contents := make([]byte, 11) contents := testutil.RandomBytes(1, 11)
_, err = rand.Read(contents)
if err != nil {
t.Fatalf("Could not rand read contents %v", err)
}
log.Debug("content read") log.Debug("content read")
_, err = d.Write(contents) _, err = d.Write(contents)
if err != nil { if err != nil {
@ -815,7 +804,7 @@ func (ta *testAPI) createNewFileInsideDirectory(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "createinsidedir-upload") dat.testUploadDir = filepath.Join(dat.testDir, "createinsidedir-upload")
dat.testMountDir = filepath.Join(dat.testDir, "createinsidedir-mount") dat.testMountDir = filepath.Join(dat.testDir, "createinsidedir-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["one/1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["one/1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -832,11 +821,7 @@ func (ta *testAPI) createNewFileInsideDirectory(t *testing.T, toEncrypt bool) {
} }
defer d.Close() defer d.Close()
log.Debug("File opened") log.Debug("File opened")
contents := make([]byte, 11) contents := testutil.RandomBytes(1, 11)
_, err = rand.Read(contents)
if err != nil {
t.Fatalf("Error filling random bytes into byte array %v", err)
}
log.Debug("Content read") log.Debug("Content read")
_, err = d.Write(contents) _, err = d.Write(contents)
if err != nil { if err != nil {
@ -896,7 +881,7 @@ func (ta *testAPI) createNewFileInsideNewDirectory(t *testing.T, toEncrypt bool)
dat.testUploadDir = filepath.Join(dat.testDir, "createinsidenewdir-upload") dat.testUploadDir = filepath.Join(dat.testDir, "createinsidenewdir-upload")
dat.testMountDir = filepath.Join(dat.testDir, "createinsidenewdir-mount") dat.testMountDir = filepath.Join(dat.testDir, "createinsidenewdir-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -916,11 +901,7 @@ func (ta *testAPI) createNewFileInsideNewDirectory(t *testing.T, toEncrypt bool)
} }
defer d.Close() defer d.Close()
log.Debug("File opened") log.Debug("File opened")
contents := make([]byte, 11) contents := testutil.RandomBytes(1, 11)
_, err = rand.Read(contents)
if err != nil {
t.Fatalf("Error writing random bytes to byte array: %v", err)
}
log.Debug("content read") log.Debug("content read")
_, err = d.Write(contents) _, err = d.Write(contents)
if err != nil { if err != nil {
@ -976,9 +957,9 @@ func (ta *testAPI) removeExistingFile(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "remove-upload") dat.testUploadDir = filepath.Join(dat.testDir, "remove-upload")
dat.testMountDir = filepath.Join(dat.testDir, "remove-mount") dat.testMountDir = filepath.Join(dat.testDir, "remove-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)}
dat.files["six.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -1036,9 +1017,9 @@ func (ta *testAPI) removeExistingFileInsideDir(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "remove-upload") dat.testUploadDir = filepath.Join(dat.testDir, "remove-upload")
dat.testMountDir = filepath.Join(dat.testDir, "remove-mount") dat.testMountDir = filepath.Join(dat.testDir, "remove-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat.files["one/five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["one/five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)}
dat.files["one/six.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["one/six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -1104,9 +1085,9 @@ func (ta *testAPI) removeNewlyAddedFile(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "removenew-upload") dat.testUploadDir = filepath.Join(dat.testDir, "removenew-upload")
dat.testMountDir = filepath.Join(dat.testDir, "removenew-mount") dat.testMountDir = filepath.Join(dat.testDir, "removenew-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)}
dat.files["six.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -1127,11 +1108,7 @@ func (ta *testAPI) removeNewlyAddedFile(t *testing.T, toEncrypt bool) {
} }
defer d.Close() defer d.Close()
log.Debug("file opened") log.Debug("file opened")
contents := make([]byte, 11) contents := testutil.RandomBytes(1, 11)
_, err = rand.Read(contents)
if err != nil {
t.Fatalf("Error writing random bytes to byte array: %v", err)
}
log.Debug("content read") log.Debug("content read")
_, err = d.Write(contents) _, err = d.Write(contents)
if err != nil { if err != nil {
@ -1201,9 +1178,9 @@ func (ta *testAPI) addNewFileAndModifyContents(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "modifyfile-upload") dat.testUploadDir = filepath.Join(dat.testDir, "modifyfile-upload")
dat.testMountDir = filepath.Join(dat.testDir, "modifyfile-mount") dat.testMountDir = filepath.Join(dat.testDir, "modifyfile-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)}
dat.files["six.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -1357,9 +1334,9 @@ func (ta *testAPI) removeEmptyDir(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "rmdir-upload") dat.testUploadDir = filepath.Join(dat.testDir, "rmdir-upload")
dat.testMountDir = filepath.Join(dat.testDir, "rmdir-mount") dat.testMountDir = filepath.Join(dat.testDir, "rmdir-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)}
dat.files["six.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -1406,9 +1383,9 @@ func (ta *testAPI) removeDirWhichHasFiles(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "rmdir-upload") dat.testUploadDir = filepath.Join(dat.testDir, "rmdir-upload")
dat.testMountDir = filepath.Join(dat.testDir, "rmdir-mount") dat.testMountDir = filepath.Join(dat.testDir, "rmdir-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["one/1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["one/1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat.files["two/five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["two/five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)}
dat.files["two/six.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["two/six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -1480,12 +1457,12 @@ func (ta *testAPI) removeDirWhichHasSubDirs(t *testing.T, toEncrypt bool) {
dat.testUploadDir = filepath.Join(dat.testDir, "rmsubdir-upload") dat.testUploadDir = filepath.Join(dat.testDir, "rmsubdir-upload")
dat.testMountDir = filepath.Join(dat.testDir, "rmsubdir-mount") dat.testMountDir = filepath.Join(dat.testDir, "rmsubdir-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
dat.files["one/1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["one/1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
dat.files["two/three/2.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["two/three/2.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)}
dat.files["two/three/3.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["two/three/3.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
dat.files["two/four/5.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["two/four/5.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(4, 10)}
dat.files["two/four/6.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["two/four/6.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(5, 10)}
dat.files["two/four/six/7.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)} dat.files["two/four/six/7.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(6, 10)}
dat, err = ta.uploadAndMount(dat, t) dat, err = ta.uploadAndMount(dat, t)
if err != nil { if err != nil {
@ -1567,11 +1544,7 @@ func (ta *testAPI) appendFileContentsToEnd(t *testing.T, toEncrypt bool) {
dat.testMountDir = filepath.Join(dat.testDir, "appendlargefile-mount") dat.testMountDir = filepath.Join(dat.testDir, "appendlargefile-mount")
dat.files = make(map[string]fileInfo) dat.files = make(map[string]fileInfo)
line1 := make([]byte, 10) line1 := testutil.RandomBytes(1, 10)
_, err = rand.Read(line1)
if err != nil {
t.Fatalf("Error writing random bytes to byte array: %v", err)
}
dat.files["1.txt"] = fileInfo{0700, 333, 444, line1} dat.files["1.txt"] = fileInfo{0700, 333, 444, line1}
@ -1588,11 +1561,7 @@ func (ta *testAPI) appendFileContentsToEnd(t *testing.T, toEncrypt bool) {
} }
defer fd.Close() defer fd.Close()
log.Debug("file opened") log.Debug("file opened")
line2 := make([]byte, 5) line2 := testutil.RandomBytes(1, 5)
_, err = rand.Read(line2)
if err != nil {
t.Fatalf("Error writing random bytes to byte array: %v", err)
}
log.Debug("line read") log.Debug("line read")
_, err = fd.Seek(int64(len(line1)), 0) _, err = fd.Seek(int64(len(line1)), 0)
if err != nil { if err != nil {

View file

@ -20,16 +20,18 @@ import (
"context" "context"
"sync" "sync"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations"
) )
// PeerEvent is the type of the channel returned by Simulation.PeerEvents. // PeerEvent is the type of the channel returned by Simulation.PeerEvents.
type PeerEvent struct { type PeerEvent struct {
// NodeID is the ID of node that the event is caught on. // NodeID is the ID of node that the event is caught on.
NodeID enode.ID NodeID enode.ID
// PeerID is the ID of the peer node that the event is caught on.
PeerID enode.ID
// Event is the event that is caught. // Event is the event that is caught.
Event *p2p.PeerEvent Event *simulations.Event
// Error is the error that may have happened during event watching. // Error is the error that may have happened during event watching.
Error error Error error
} }
@ -37,7 +39,11 @@ type PeerEvent struct {
// PeerEventsFilter defines a filter on PeerEvents to exclude messages with // PeerEventsFilter defines a filter on PeerEvents to exclude messages with
// defined properties. Use PeerEventsFilter methods to set required options. // defined properties. Use PeerEventsFilter methods to set required options.
type PeerEventsFilter struct { type PeerEventsFilter struct {
t *p2p.PeerEventType eventType simulations.EventType
connUp *bool
msgReceive *bool
protocol *string protocol *string
msgCode *uint64 msgCode *uint64
} }
@ -47,20 +53,48 @@ func NewPeerEventsFilter() *PeerEventsFilter {
return &PeerEventsFilter{} return &PeerEventsFilter{}
} }
// Type sets the filter to only one peer event type. // Connect sets the filter to events when two nodes connect.
func (f *PeerEventsFilter) Type(t p2p.PeerEventType) *PeerEventsFilter { func (f *PeerEventsFilter) Connect() *PeerEventsFilter {
f.t = &t f.eventType = simulations.EventTypeConn
b := true
f.connUp = &b
return f
}
// Drop sets the filter to events when two nodes disconnect.
func (f *PeerEventsFilter) Drop() *PeerEventsFilter {
f.eventType = simulations.EventTypeConn
b := false
f.connUp = &b
return f
}
// ReceivedMessages sets the filter to only messages that are received.
func (f *PeerEventsFilter) ReceivedMessages() *PeerEventsFilter {
f.eventType = simulations.EventTypeMsg
b := true
f.msgReceive = &b
return f
}
// SentMessages sets the filter to only messages that are sent.
func (f *PeerEventsFilter) SentMessages() *PeerEventsFilter {
f.eventType = simulations.EventTypeMsg
b := false
f.msgReceive = &b
return f return f
} }
// Protocol sets the filter to only one message protocol. // Protocol sets the filter to only one message protocol.
func (f *PeerEventsFilter) Protocol(p string) *PeerEventsFilter { func (f *PeerEventsFilter) Protocol(p string) *PeerEventsFilter {
f.eventType = simulations.EventTypeMsg
f.protocol = &p f.protocol = &p
return f return f
} }
// MsgCode sets the filter to only one msg code. // MsgCode sets the filter to only one msg code.
func (f *PeerEventsFilter) MsgCode(c uint64) *PeerEventsFilter { func (f *PeerEventsFilter) MsgCode(c uint64) *PeerEventsFilter {
f.eventType = simulations.EventTypeMsg
f.msgCode = &c f.msgCode = &c
return f return f
} }
@ -80,19 +114,8 @@ func (s *Simulation) PeerEvents(ctx context.Context, ids []enode.ID, filters ...
go func(id enode.ID) { go func(id enode.ID) {
defer s.shutdownWG.Done() defer s.shutdownWG.Done()
client, err := s.Net.GetNode(id).Client() events := make(chan *simulations.Event)
if err != nil { sub := s.Net.Events().Subscribe(events)
subsWG.Done()
eventC <- PeerEvent{NodeID: id, Error: err}
return
}
events := make(chan *p2p.PeerEvent)
sub, err := client.Subscribe(ctx, "admin", events, "peerEvents")
if err != nil {
subsWG.Done()
eventC <- PeerEvent{NodeID: id, Error: err}
return
}
defer sub.Unsubscribe() defer sub.Unsubscribe()
subsWG.Done() subsWG.Done()
@ -110,28 +133,55 @@ func (s *Simulation) PeerEvents(ctx context.Context, ids []enode.ID, filters ...
case <-s.Done(): case <-s.Done():
return return
case e := <-events: case e := <-events:
// ignore control events
if e.Control {
continue
}
match := len(filters) == 0 // if there are no filters match all events match := len(filters) == 0 // if there are no filters match all events
for _, f := range filters { for _, f := range filters {
if f.t != nil && *f.t != e.Type { if f.eventType == simulations.EventTypeConn && e.Conn != nil {
if *f.connUp != e.Conn.Up {
continue continue
} }
if f.protocol != nil && *f.protocol != e.Protocol { // all connection filter parameters matched, break the loop
continue
}
if f.msgCode != nil && e.MsgCode != nil && *f.msgCode != *e.MsgCode {
continue
}
// all filter parameters matched, break the loop
match = true match = true
break break
} }
if f.eventType == simulations.EventTypeMsg && e.Msg != nil {
if f.msgReceive != nil && *f.msgReceive != e.Msg.Received {
continue
}
if f.protocol != nil && *f.protocol != e.Msg.Protocol {
continue
}
if f.msgCode != nil && *f.msgCode != e.Msg.Code {
continue
}
// all message filter parameters matched, break the loop
match = true
break
}
}
var peerID enode.ID
switch e.Type {
case simulations.EventTypeConn:
peerID = e.Conn.One
if peerID == id {
peerID = e.Conn.Other
}
case simulations.EventTypeMsg:
peerID = e.Msg.One
if peerID == id {
peerID = e.Msg.Other
}
}
if match { if match {
select { select {
case eventC <- PeerEvent{NodeID: id, Event: e}: case eventC <- PeerEvent{NodeID: id, PeerID: peerID, Event: e}:
case <-ctx.Done(): case <-ctx.Done():
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
select { select {
case eventC <- PeerEvent{NodeID: id, Error: err}: case eventC <- PeerEvent{NodeID: id, PeerID: peerID, Error: err}:
case <-s.Done(): case <-s.Done():
} }
} }

View file

@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/network"
"github.com/ethereum/go-ethereum/swarm/network/simulation" "github.com/ethereum/go-ethereum/swarm/network/simulation"
@ -87,7 +86,7 @@ func ExampleSimulation_PeerEvents() {
log.Error("peer event", "err", e.Error) log.Error("peer event", "err", e.Error)
continue continue
} }
log.Info("peer event", "node", e.NodeID, "peer", e.Event.Peer, "msgcode", e.Event.MsgCode) log.Info("peer event", "node", e.NodeID, "peer", e.PeerID, "type", e.Event.Type)
} }
}() }()
} }
@ -100,7 +99,7 @@ func ExampleSimulation_PeerEvents_disconnections() {
disconnections := sim.PeerEvents( disconnections := sim.PeerEvents(
context.Background(), context.Background(),
sim.NodeIDs(), sim.NodeIDs(),
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop), simulation.NewPeerEventsFilter().Drop(),
) )
go func() { go func() {
@ -109,7 +108,7 @@ func ExampleSimulation_PeerEvents_disconnections() {
log.Error("peer drop", "err", d.Error) log.Error("peer drop", "err", d.Error)
continue continue
} }
log.Warn("peer drop", "node", d.NodeID, "peer", d.Event.Peer) log.Warn("peer drop", "node", d.NodeID, "peer", d.PeerID)
} }
}() }()
} }
@ -124,8 +123,8 @@ func ExampleSimulation_PeerEvents_multipleFilters() {
context.Background(), context.Background(),
sim.NodeIDs(), sim.NodeIDs(),
// Watch when bzz messages 1 and 4 are received. // Watch when bzz messages 1 and 4 are received.
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeMsgRecv).Protocol("bzz").MsgCode(1), simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("bzz").MsgCode(1),
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeMsgRecv).Protocol("bzz").MsgCode(4), simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("bzz").MsgCode(4),
) )
go func() { go func() {
@ -134,7 +133,7 @@ func ExampleSimulation_PeerEvents_multipleFilters() {
log.Error("bzz message", "err", m.Error) log.Error("bzz message", "err", m.Error)
continue continue
} }
log.Info("bzz message", "node", m.NodeID, "peer", m.Event.Peer) log.Info("bzz message", "node", m.NodeID, "peer", m.PeerID)
} }
}() }()
} }

View file

@ -87,9 +87,10 @@ func getDbStore(nodeID string) (*state.DBStore, error) {
var ( var (
nodeCount = flag.Int("nodes", 10, "number of nodes to create (default 10)") nodeCount = flag.Int("nodes", 10, "number of nodes to create (default 10)")
initCount = flag.Int("conns", 1, "number of originally connected peers (default 1)") initCount = flag.Int("conns", 1, "number of originally connected peers (default 1)")
snapshotFile = flag.String("snapshot", "", "create snapshot") snapshotFile = flag.String("snapshot", "", "path to create snapshot file in")
loglevel = flag.Int("loglevel", 3, "verbosity of logs") loglevel = flag.Int("loglevel", 3, "verbosity of logs")
rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs") rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs")
serviceOverride = flag.String("services", "", "remove or add services to the node snapshot; prefix with \"+\" to add, \"-\" to remove; example: +pss,-discovery")
) )
func init() { func init() {
@ -306,7 +307,25 @@ func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simul
} }
if *snapshotFile != "" { if *snapshotFile != "" {
snap, err := net.Snapshot() var err error
var snap *simulations.Snapshot
if len(*serviceOverride) > 0 {
var addServices []string
var removeServices []string
for _, osvc := range strings.Split(*serviceOverride, ",") {
if strings.Index(osvc, "+") == 0 {
addServices = append(addServices, osvc[1:])
} else if strings.Index(osvc, "-") == 0 {
removeServices = append(removeServices, osvc[1:])
} else {
panic("stick to the rules, you know what they are")
}
}
snap, err = net.SnapshotWithServices(addServices, removeServices)
} else {
snap, err = net.Snapshot()
}
if err != nil { if err != nil {
return nil, errors.New("no shapshot dude") return nil, errors.New("no shapshot dude")
} }

View file

@ -18,7 +18,6 @@ package stream
import ( import (
"context" "context"
crand "crypto/rand"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
@ -40,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/swarm/state" "github.com/ethereum/go-ethereum/swarm/state"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db" mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
"github.com/ethereum/go-ethereum/swarm/testutil"
colorable "github.com/mattn/go-colorable" colorable "github.com/mattn/go-colorable"
) )
@ -114,7 +114,7 @@ func newStreamerTester(t *testing.T, registryOptions *RegistryOptions) (*p2ptest
delivery := NewDelivery(to, netStore) delivery := NewDelivery(to, netStore)
netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, true).New netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, true).New
streamer := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), registryOptions) streamer := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), registryOptions, nil)
teardown := func() { teardown := func() {
streamer.Close() streamer.Close()
removeDataDir() removeDataDir()
@ -230,12 +230,7 @@ func generateRandomFile() (string, error) {
//generate a random file size between minFileSize and maxFileSize //generate a random file size between minFileSize and maxFileSize
fileSize := rand.Intn(maxFileSize-minFileSize) + minFileSize fileSize := rand.Intn(maxFileSize-minFileSize) + minFileSize
log.Debug(fmt.Sprintf("Generated file with filesize %d kB", fileSize)) log.Debug(fmt.Sprintf("Generated file with filesize %d kB", fileSize))
b := make([]byte, fileSize*1024) b := testutil.RandomBytes(1, fileSize*1024)
_, err := crand.Read(b)
if err != nil {
log.Error("Error generating random file.", "err", err)
return "", err
}
return string(b), nil return string(b), nil
} }

View file

@ -19,9 +19,7 @@ package stream
import ( import (
"bytes" "bytes"
"context" "context"
crand "crypto/rand"
"fmt" "fmt"
"io"
"os" "os"
"sync" "sync"
"testing" "testing"
@ -39,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/swarm/network/simulation" "github.com/ethereum/go-ethereum/swarm/network/simulation"
"github.com/ethereum/go-ethereum/swarm/state" "github.com/ethereum/go-ethereum/swarm/state"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
//Tests initializing a retrieve request //Tests initializing a retrieve request
@ -291,7 +290,7 @@ func TestRequestFromPeers(t *testing.T) {
Peer: protocolsPeer, Peer: protocolsPeer,
}, to) }, to)
to.On(peer) to.On(peer)
r := NewRegistry(addr.ID(), delivery, nil, nil, nil) r := NewRegistry(addr.ID(), delivery, nil, nil, nil, nil)
// an empty priorityQueue has to be created to prevent a goroutine being called after the test has finished // an empty priorityQueue has to be created to prevent a goroutine being called after the test has finished
sp := &Peer{ sp := &Peer{
@ -332,7 +331,7 @@ func TestRequestFromPeersWithLightNode(t *testing.T) {
Peer: protocolsPeer, Peer: protocolsPeer,
}, to) }, to)
to.On(peer) to.On(peer)
r := NewRegistry(addr.ID(), delivery, nil, nil, nil) r := NewRegistry(addr.ID(), delivery, nil, nil, nil, nil)
// an empty priorityQueue has to be created to prevent a goroutine being called after the test has finished // an empty priorityQueue has to be created to prevent a goroutine being called after the test has finished
sp := &Peer{ sp := &Peer{
Peer: protocolsPeer, Peer: protocolsPeer,
@ -481,7 +480,7 @@ func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
SkipCheck: skipCheck, SkipCheck: skipCheck,
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
Retrieval: RetrievalEnabled, Retrieval: RetrievalEnabled,
}) }, nil)
bucket.Store(bucketKeyRegistry, r) bucket.Store(bucketKeyRegistry, r)
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams()) fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
@ -530,7 +529,7 @@ func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
//now we can actually upload a (random) file to the round-robin store //now we can actually upload a (random) file to the round-robin store
size := chunkCount * chunkSize size := chunkCount * chunkSize
log.Debug("Storing data to file store") log.Debug("Storing data to file store")
fileHash, wait, err := roundRobinFileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false) fileHash, wait, err := roundRobinFileStore.Store(ctx, testutil.RandomReader(1, size), int64(size), false)
// wait until all chunks stored // wait until all chunks stored
if err != nil { if err != nil {
return err return err
@ -566,13 +565,13 @@ func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
disconnections := sim.PeerEvents( disconnections := sim.PeerEvents(
context.Background(), context.Background(),
sim.NodeIDs(), sim.NodeIDs(),
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop), simulation.NewPeerEventsFilter().Drop(),
) )
go func() { go func() {
for d := range disconnections { for d := range disconnections {
if d.Error != nil { if d.Error != nil {
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer) log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
t.Fatal(d.Error) t.Fatal(d.Error)
} }
} }
@ -656,7 +655,7 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skip
Syncing: SyncingDisabled, Syncing: SyncingDisabled,
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
SyncUpdateDelay: 0, SyncUpdateDelay: 0,
}) }, nil)
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams()) fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
bucket.Store(bucketKeyFileStore, fileStore) bucket.Store(bucketKeyFileStore, fileStore)
@ -698,13 +697,13 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skip
disconnections := sim.PeerEvents( disconnections := sim.PeerEvents(
context.Background(), context.Background(),
sim.NodeIDs(), sim.NodeIDs(),
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop), simulation.NewPeerEventsFilter().Drop(),
) )
go func() { go func() {
for d := range disconnections { for d := range disconnections {
if d.Error != nil { if d.Error != nil {
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer) log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
b.Fatal(d.Error) b.Fatal(d.Error)
} }
} }
@ -719,7 +718,7 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skip
for i := 0; i < chunkCount; i++ { for i := 0; i < chunkCount; i++ {
// create actual size real chunks // create actual size real chunks
ctx := context.TODO() ctx := context.TODO()
hash, wait, err := remoteFileStore.Store(ctx, io.LimitReader(crand.Reader, int64(chunkSize)), int64(chunkSize), false) hash, wait, err := remoteFileStore.Store(ctx, testutil.RandomReader(i, chunkSize), int64(chunkSize), false)
if err != nil { if err != nil {
b.Fatalf("expected no error. got %v", err) b.Fatalf("expected no error. got %v", err)
} }

View file

@ -18,10 +18,8 @@ package stream
import ( import (
"context" "context"
crand "crypto/rand"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
"os" "os"
"sync" "sync"
"testing" "testing"
@ -29,13 +27,13 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/network"
"github.com/ethereum/go-ethereum/swarm/network/simulation" "github.com/ethereum/go-ethereum/swarm/network/simulation"
"github.com/ethereum/go-ethereum/swarm/state" "github.com/ethereum/go-ethereum/swarm/state"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
func TestIntervalsLive(t *testing.T) { func TestIntervalsLive(t *testing.T) {
@ -86,7 +84,7 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
Syncing: SyncingRegisterOnly, Syncing: SyncingRegisterOnly,
SkipCheck: skipCheck, SkipCheck: skipCheck,
}) }, nil)
bucket.Store(bucketKeyRegistry, r) bucket.Store(bucketKeyRegistry, r)
r.RegisterClientFunc(externalStreamName, func(p *Peer, t string, live bool) (Client, error) { r.RegisterClientFunc(externalStreamName, func(p *Peer, t string, live bool) (Client, error) {
@ -130,7 +128,8 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
fileStore := item.(*storage.FileStore) fileStore := item.(*storage.FileStore)
size := chunkCount * chunkSize size := chunkCount * chunkSize
_, wait, err := fileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false)
_, wait, err := fileStore.Store(ctx, testutil.RandomReader(1, size), int64(size), false)
if err != nil { if err != nil {
log.Error("Store error: %v", "err", err) log.Error("Store error: %v", "err", err)
t.Fatal(err) t.Fatal(err)
@ -154,7 +153,7 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
disconnections := sim.PeerEvents( disconnections := sim.PeerEvents(
context.Background(), context.Background(),
sim.NodeIDs(), sim.NodeIDs(),
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop), simulation.NewPeerEventsFilter().Drop(),
) )
err = registry.Subscribe(storer, NewStream(externalStreamName, "", live), history, Top) err = registry.Subscribe(storer, NewStream(externalStreamName, "", live), history, Top)
@ -165,7 +164,7 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
go func() { go func() {
for d := range disconnections { for d := range disconnections {
if d.Error != nil { if d.Error != nil {
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer) log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
t.Fatal(d.Error) t.Fatal(d.Error)
} }
} }

View file

@ -130,7 +130,7 @@ func retrievalStreamerFunc(ctx *adapters.ServiceContext, bucket *sync.Map) (s no
Retrieval: RetrievalEnabled, Retrieval: RetrievalEnabled,
Syncing: SyncingAutoSubscribe, Syncing: SyncingAutoSubscribe,
SyncUpdateDelay: 3 * time.Second, SyncUpdateDelay: 3 * time.Second,
}) }, nil)
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams()) fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
bucket.Store(bucketKeyFileStore, fileStore) bucket.Store(bucketKeyFileStore, fileStore)

View file

@ -17,9 +17,7 @@ package stream
import ( import (
"context" "context"
crand "crypto/rand"
"fmt" "fmt"
"io"
"os" "os"
"runtime" "runtime"
"sync" "sync"
@ -29,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations" "github.com/ethereum/go-ethereum/p2p/simulations"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
@ -39,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/swarm/state" "github.com/ethereum/go-ethereum/swarm/state"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db" mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
const MaxTimeout = 600 const MaxTimeout = 600
@ -168,7 +166,7 @@ func streamerFunc(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Servic
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
Syncing: SyncingAutoSubscribe, Syncing: SyncingAutoSubscribe,
SyncUpdateDelay: 3 * time.Second, SyncUpdateDelay: 3 * time.Second,
}) }, nil)
bucket.Store(bucketKeyRegistry, r) bucket.Store(bucketKeyRegistry, r)
@ -211,12 +209,12 @@ func testSyncingViaGlobalSync(t *testing.T, chunkCount int, nodeCount int) {
disconnections := sim.PeerEvents( disconnections := sim.PeerEvents(
context.Background(), context.Background(),
sim.NodeIDs(), sim.NodeIDs(),
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop), simulation.NewPeerEventsFilter().Drop(),
) )
go func() { go func() {
for d := range disconnections { for d := range disconnections {
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer) log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
t.Fatal("unexpected disconnect") t.Fatal("unexpected disconnect")
cancelSimRun() cancelSimRun()
} }
@ -362,7 +360,7 @@ func testSyncingViaDirectSubscribe(t *testing.T, chunkCount int, nodeCount int)
r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
Syncing: SyncingRegisterOnly, Syncing: SyncingRegisterOnly,
}) }, nil)
bucket.Store(bucketKeyRegistry, r) bucket.Store(bucketKeyRegistry, r)
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams()) fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
@ -403,12 +401,12 @@ func testSyncingViaDirectSubscribe(t *testing.T, chunkCount int, nodeCount int)
disconnections := sim.PeerEvents( disconnections := sim.PeerEvents(
context.Background(), context.Background(),
sim.NodeIDs(), sim.NodeIDs(),
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop), simulation.NewPeerEventsFilter().Drop(),
) )
go func() { go func() {
for d := range disconnections { for d := range disconnections {
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer) log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
t.Fatal("unexpected disconnect") t.Fatal("unexpected disconnect")
cancelSimRun() cancelSimRun()
} }
@ -429,7 +427,7 @@ func testSyncingViaDirectSubscribe(t *testing.T, chunkCount int, nodeCount int)
var subscriptionCount int var subscriptionCount int
filter := simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeMsgRecv).Protocol("stream").MsgCode(4) filter := simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("stream").MsgCode(4)
eventC := sim.PeerEvents(ctx, nodeIDs, filter) eventC := sim.PeerEvents(ctx, nodeIDs, filter)
for j, node := range nodeIDs { for j, node := range nodeIDs {
@ -603,7 +601,7 @@ func uploadFileToSingleNodeStore(id enode.ID, chunkCount int, lstore *storage.Lo
size := chunkSize size := chunkSize
var rootAddrs []storage.Address var rootAddrs []storage.Address
for i := 0; i < chunkCount; i++ { for i := 0; i < chunkCount; i++ {
rk, wait, err := fileStore.Store(context.TODO(), io.LimitReader(crand.Reader, int64(size)), int64(size), false) rk, wait, err := fileStore.Store(context.TODO(), testutil.RandomReader(i, size), int64(size), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"reflect"
"sync" "sync"
"time" "time"
@ -87,6 +88,9 @@ type Registry struct {
intervalsStore state.Store intervalsStore state.Store
autoRetrieval bool //automatically subscribe to retrieve request stream autoRetrieval bool //automatically subscribe to retrieve request stream
maxPeerServers int maxPeerServers int
spec *protocols.Spec //this protocol's spec
balance protocols.Balance //implements protocols.Balance, for accounting
prices protocols.Prices //implements protocols.Prices, provides prices to accounting
} }
// RegistryOptions holds optional values for NewRegistry constructor. // RegistryOptions holds optional values for NewRegistry constructor.
@ -99,7 +103,7 @@ type RegistryOptions struct {
} }
// NewRegistry is Streamer constructor // NewRegistry is Streamer constructor
func NewRegistry(localID enode.ID, delivery *Delivery, syncChunkStore storage.SyncChunkStore, intervalsStore state.Store, options *RegistryOptions) *Registry { func NewRegistry(localID enode.ID, delivery *Delivery, syncChunkStore storage.SyncChunkStore, intervalsStore state.Store, options *RegistryOptions, balance protocols.Balance) *Registry {
if options == nil { if options == nil {
options = &RegistryOptions{} options = &RegistryOptions{}
} }
@ -119,7 +123,10 @@ func NewRegistry(localID enode.ID, delivery *Delivery, syncChunkStore storage.Sy
intervalsStore: intervalsStore, intervalsStore: intervalsStore,
autoRetrieval: retrieval, autoRetrieval: retrieval,
maxPeerServers: options.MaxPeerServers, maxPeerServers: options.MaxPeerServers,
balance: balance,
} }
streamer.setupSpec()
streamer.api = NewAPI(streamer) streamer.api = NewAPI(streamer)
delivery.getPeer = streamer.getPeer delivery.getPeer = streamer.getPeer
@ -228,6 +235,17 @@ func NewRegistry(localID enode.ID, delivery *Delivery, syncChunkStore storage.Sy
return streamer return streamer
} }
//we need to construct a spec instance per node instance
func (r *Registry) setupSpec() {
//first create the "bare" spec
r.createSpec()
//if balance is nil, this node has been started without swap support (swapEnabled flag is false)
if r.balance != nil && !reflect.ValueOf(r.balance).IsNil() {
//swap is enabled, so setup the hook
r.spec.Hook = protocols.NewAccounting(r.balance, r.prices)
}
}
// RegisterClient registers an incoming streamer constructor // RegisterClient registers an incoming streamer constructor
func (r *Registry) RegisterClientFunc(stream string, f func(*Peer, string, bool) (Client, error)) { func (r *Registry) RegisterClientFunc(stream string, f func(*Peer, string, bool) (Client, error)) {
r.clientMu.Lock() r.clientMu.Lock()
@ -492,7 +510,7 @@ func (r *Registry) updateSyncing() {
} }
func (r *Registry) runProtocol(p *p2p.Peer, rw p2p.MsgReadWriter) error { func (r *Registry) runProtocol(p *p2p.Peer, rw p2p.MsgReadWriter) error {
peer := protocols.NewPeer(p, rw, Spec) peer := protocols.NewPeer(p, rw, r.spec)
bp := network.NewBzzPeer(peer) bp := network.NewBzzPeer(peer)
np := network.NewPeer(bp, r.delivery.kad) np := network.NewPeer(bp, r.delivery.kad)
r.delivery.kad.On(np) r.delivery.kad.On(np)
@ -716,8 +734,16 @@ func (c *clientParams) clientCreated() {
close(c.clientCreatedC) close(c.clientCreatedC)
} }
//GetSpec returns the streamer spec to callers
//This used to be a global variable but for simulations with
//multiple nodes its fields (notably the Hook) would be overwritten
func (r *Registry) GetSpec() *protocols.Spec {
return r.spec
}
func (r *Registry) createSpec() {
// Spec is the spec of the streamer protocol // Spec is the spec of the streamer protocol
var Spec = &protocols.Spec{ var spec = &protocols.Spec{
Name: "stream", Name: "stream",
Version: 8, Version: 8,
MaxMsgSize: 10 * 1024 * 1024, MaxMsgSize: 10 * 1024 * 1024,
@ -735,16 +761,16 @@ var Spec = &protocols.Spec{
ChunkDeliveryMsgSyncing{}, ChunkDeliveryMsgSyncing{},
}, },
} }
r.spec = spec
}
func (r *Registry) Protocols() []p2p.Protocol { func (r *Registry) Protocols() []p2p.Protocol {
return []p2p.Protocol{ return []p2p.Protocol{
{ {
Name: Spec.Name, Name: r.spec.Name,
Version: Spec.Version, Version: r.spec.Version,
Length: Spec.Length(), Length: r.spec.Length(),
Run: r.runProtocol, Run: r.runProtocol,
// NodeInfo: ,
// PeerInfo: ,
}, },
} }
} }

View file

@ -18,9 +18,7 @@ package stream
import ( import (
"context" "context"
crand "crypto/rand"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"math" "math"
"os" "os"
@ -30,7 +28,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/swarm/log" "github.com/ethereum/go-ethereum/swarm/log"
@ -39,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/swarm/state" "github.com/ethereum/go-ethereum/swarm/state"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db" mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
const dataChunkCount = 200 const dataChunkCount = 200
@ -120,7 +118,7 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
Retrieval: RetrievalDisabled, Retrieval: RetrievalDisabled,
Syncing: SyncingAutoSubscribe, Syncing: SyncingAutoSubscribe,
SkipCheck: skipCheck, SkipCheck: skipCheck,
}) }, nil)
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams()) fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
bucket.Store(bucketKeyFileStore, fileStore) bucket.Store(bucketKeyFileStore, fileStore)
@ -152,13 +150,13 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
disconnections := sim.PeerEvents( disconnections := sim.PeerEvents(
context.Background(), context.Background(),
sim.NodeIDs(), sim.NodeIDs(),
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop), simulation.NewPeerEventsFilter().Drop(),
) )
go func() { go func() {
for d := range disconnections { for d := range disconnections {
if d.Error != nil { if d.Error != nil {
log.Error("peer drop", "node", d.NodeID, "peer", d.Event.Peer) log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
t.Fatal(d.Error) t.Fatal(d.Error)
} }
} }
@ -183,7 +181,7 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
} }
fileStore := item.(*storage.FileStore) fileStore := item.(*storage.FileStore)
size := chunkCount * chunkSize size := chunkCount * chunkSize
_, wait, err := fileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false) _, wait, err := fileStore.Store(ctx, testutil.RandomReader(j, size), int64(size), false)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
} }

View file

@ -335,7 +335,7 @@ func testSwarmNetwork(t *testing.T, o *testSwarmNetworkOptions, steps ...testSwa
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
nodeIDs := sim.UpNodeIDs() nodeIDs := sim.UpNodeIDs()
shuffle(len(nodeIDs), func(i, j int) { rand.Shuffle(len(nodeIDs), func(i, j int) {
nodeIDs[i], nodeIDs[j] = nodeIDs[j], nodeIDs[i] nodeIDs[i], nodeIDs[j] = nodeIDs[j], nodeIDs[i]
}) })
for _, id := range nodeIDs { for _, id := range nodeIDs {
@ -404,7 +404,7 @@ func retrieve(
nodeStatusM *sync.Map, nodeStatusM *sync.Map,
totalFoundCount *uint64, totalFoundCount *uint64,
) (missing uint64) { ) (missing uint64) {
shuffle(len(files), func(i, j int) { rand.Shuffle(len(files), func(i, j int) {
files[i], files[j] = files[j], files[i] files[i], files[j] = files[j], files[i]
}) })
@ -499,32 +499,3 @@ func retrieve(
return uint64(totalCheckCount) - atomic.LoadUint64(totalFoundCount) return uint64(totalCheckCount) - atomic.LoadUint64(totalFoundCount)
} }
// Backported from stdlib https://golang.org/src/math/rand/rand.go?s=11175:11215#L333
//
// Replace with rand.Shuffle from go 1.10 when go 1.9 support is dropped.
//
// shuffle pseudo-randomizes the order of elements.
// n is the number of elements. Shuffle panics if n < 0.
// swap swaps the elements with indexes i and j.
func shuffle(n int, swap func(i, j int)) {
if n < 0 {
panic("invalid argument to Shuffle")
}
// Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
// Shuffle really ought not be called with n that doesn't fit in 32 bits.
// Not only will it take a very long time, but with 2³¹! possible permutations,
// there's no way that any PRNG can have a big enough internal state to
// generate even a minuscule percentage of the possible permutations.
// Nevertheless, the right API signature accepts an int n, so handle it as best we can.
i := n - 1
for ; i > 1<<31-1-1; i-- {
j := int(rand.Int63n(int64(i + 1)))
swap(i, j)
}
for ; i > 0; i-- {
j := int(rand.Int31n(int32(i + 1)))
swap(i, j)
}
}

View file

@ -19,13 +19,13 @@ package storage
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/rand"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"testing" "testing"
"github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
/* /*
@ -47,7 +47,7 @@ func newTestHasherStore(store ChunkStore, hash string) *hasherStore {
} }
func testRandomBrokenData(n int, tester *chunkerTester) { func testRandomBrokenData(n int, tester *chunkerTester) {
data := io.LimitReader(rand.Reader, int64(n)) data := testutil.RandomReader(1, n)
brokendata := brokenLimitReader(data, n, n/2) brokendata := brokenLimitReader(data, n, n/2)
buf := make([]byte, n) buf := make([]byte, n)
@ -56,7 +56,7 @@ func testRandomBrokenData(n int, tester *chunkerTester) {
tester.t.Fatalf("Broken reader is not broken, hence broken. Returns: %v", err) tester.t.Fatalf("Broken reader is not broken, hence broken. Returns: %v", err)
} }
data = io.LimitReader(rand.Reader, int64(n)) data = testutil.RandomReader(2, n)
brokendata = brokenLimitReader(data, n, n/2) brokendata = brokenLimitReader(data, n, n/2)
putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash) putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash)
@ -77,7 +77,8 @@ func testRandomData(usePyramid bool, hash string, n int, tester *chunkerTester)
input, found := tester.inputs[uint64(n)] input, found := tester.inputs[uint64(n)]
var data io.Reader var data io.Reader
if !found { if !found {
data, input = GenerateRandomData(n) input = testutil.RandomBytes(1, n)
data = bytes.NewReader(input)
tester.inputs[uint64(n)] = input tester.inputs[uint64(n)] = input
} else { } else {
data = io.LimitReader(bytes.NewReader(input), int64(n)) data = io.LimitReader(bytes.NewReader(input), int64(n))
@ -118,14 +119,13 @@ func testRandomData(usePyramid bool, hash string, n int, tester *chunkerTester)
// testing partial read // testing partial read
for i := 1; i < n; i += 10000 { for i := 1; i < n; i += 10000 {
readableLength := n - i readableLength := n - i
output := make([]byte, readableLength)
r, err := reader.ReadAt(output, int64(i)) r, err := reader.ReadAt(output, int64(i))
if r != readableLength || err != io.EOF { if r != readableLength || err != io.EOF {
tester.t.Fatalf("readAt error with offset %v read: %v n = %v err = %v\n", i, r, readableLength, err) tester.t.Fatalf("readAt error with offset %v read: %v n = %v err = %v\n", i, r, readableLength, err)
} }
if input != nil { if input != nil {
if !bytes.Equal(output, input[i:]) { if !bytes.Equal(output[:readableLength], input[i:]) {
tester.t.Fatalf("input and output mismatch\n IN: %v\nOUT: %v\n", input[i:], output) tester.t.Fatalf("input and output mismatch\n IN: %v\nOUT: %v\n", input[i:], output[:readableLength])
} }
} }
} }
@ -177,7 +177,8 @@ func TestDataAppend(t *testing.T) {
input, found := tester.inputs[uint64(n)] input, found := tester.inputs[uint64(n)]
var data io.Reader var data io.Reader
if !found { if !found {
data, input = GenerateRandomData(n) input = testutil.RandomBytes(i, n)
data = bytes.NewReader(input)
tester.inputs[uint64(n)] = input tester.inputs[uint64(n)] = input
} else { } else {
data = io.LimitReader(bytes.NewReader(input), int64(n)) data = io.LimitReader(bytes.NewReader(input), int64(n))
@ -199,7 +200,8 @@ func TestDataAppend(t *testing.T) {
appendInput, found := tester.inputs[uint64(m)] appendInput, found := tester.inputs[uint64(m)]
var appendData io.Reader var appendData io.Reader
if !found { if !found {
appendData, appendInput = GenerateRandomData(m) appendInput = testutil.RandomBytes(i, m)
appendData = bytes.NewReader(appendInput)
tester.inputs[uint64(m)] = appendInput tester.inputs[uint64(m)] = appendInput
} else { } else {
appendData = io.LimitReader(bytes.NewReader(appendInput), int64(m)) appendData = io.LimitReader(bytes.NewReader(appendInput), int64(m))
@ -272,7 +274,7 @@ func benchReadAll(reader LazySectionReader) {
func benchmarkSplitJoin(n int, t *testing.B) { func benchmarkSplitJoin(n int, t *testing.B) {
t.ReportAllocs() t.ReportAllocs()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
data := testDataReader(n) data := testutil.RandomReader(i, n)
putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash) putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash)
ctx := context.TODO() ctx := context.TODO()
@ -292,7 +294,7 @@ func benchmarkSplitJoin(n int, t *testing.B) {
func benchmarkSplitTreeSHA3(n int, t *testing.B) { func benchmarkSplitTreeSHA3(n int, t *testing.B) {
t.ReportAllocs() t.ReportAllocs()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
data := testDataReader(n) data := testutil.RandomReader(i, n)
putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash) putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash)
ctx := context.Background() ctx := context.Background()
@ -311,7 +313,7 @@ func benchmarkSplitTreeSHA3(n int, t *testing.B) {
func benchmarkSplitTreeBMT(n int, t *testing.B) { func benchmarkSplitTreeBMT(n int, t *testing.B) {
t.ReportAllocs() t.ReportAllocs()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
data := testDataReader(n) data := testutil.RandomReader(i, n)
putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash) putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash)
ctx := context.Background() ctx := context.Background()
@ -329,7 +331,7 @@ func benchmarkSplitTreeBMT(n int, t *testing.B) {
func benchmarkSplitPyramidBMT(n int, t *testing.B) { func benchmarkSplitPyramidBMT(n int, t *testing.B) {
t.ReportAllocs() t.ReportAllocs()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
data := testDataReader(n) data := testutil.RandomReader(i, n)
putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash) putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash)
ctx := context.Background() ctx := context.Background()
@ -347,7 +349,7 @@ func benchmarkSplitPyramidBMT(n int, t *testing.B) {
func benchmarkSplitPyramidSHA3(n int, t *testing.B) { func benchmarkSplitPyramidSHA3(n int, t *testing.B) {
t.ReportAllocs() t.ReportAllocs()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
data := testDataReader(n) data := testutil.RandomReader(i, n)
putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash) putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash)
ctx := context.Background() ctx := context.Background()
@ -365,8 +367,8 @@ func benchmarkSplitPyramidSHA3(n int, t *testing.B) {
func benchmarkSplitAppendPyramid(n, m int, t *testing.B) { func benchmarkSplitAppendPyramid(n, m int, t *testing.B) {
t.ReportAllocs() t.ReportAllocs()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
data := testDataReader(n) data := testutil.RandomReader(i, n)
data1 := testDataReader(m) data1 := testutil.RandomReader(t.N+i, m)
store := NewMapChunkStore() store := NewMapChunkStore()
putGetter := newTestHasherStore(store, SHA3Hash) putGetter := newTestHasherStore(store, SHA3Hash)

View file

@ -19,7 +19,6 @@ package storage
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/rand"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -31,7 +30,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
ch "github.com/ethereum/go-ethereum/swarm/chunk" ch "github.com/ethereum/go-ethereum/swarm/chunk"
colorable "github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
) )
var ( var (
@ -151,10 +150,6 @@ func mget(store ChunkStore, hs []Address, f func(h Address, chunk Chunk) error)
return err return err
} }
func testDataReader(l int) (r io.Reader) {
return io.LimitReader(rand.Reader, int64(l))
}
func (r *brokenLimitedReader) Read(buf []byte) (int, error) { func (r *brokenLimitedReader) Read(buf []byte) (int, error) {
if r.off+len(buf) > r.errAt { if r.off+len(buf) > r.errAt {
return 0, fmt.Errorf("Broken reader") return 0, fmt.Errorf("Broken reader")

File diff suppressed because one or more lines are too long

View file

@ -23,6 +23,8 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"github.com/ethereum/go-ethereum/swarm/testutil"
) )
const testDataSize = 0x0001000 const testDataSize = 0x0001000
@ -49,9 +51,9 @@ func testFileStoreRandom(toEncrypt bool, t *testing.T) {
fileStore := NewFileStore(localStore, NewFileStoreParams()) fileStore := NewFileStore(localStore, NewFileStoreParams())
defer os.RemoveAll("/tmp/bzz") defer os.RemoveAll("/tmp/bzz")
reader, slice := GenerateRandomData(testDataSize) slice := testutil.RandomBytes(1, testDataSize)
ctx := context.TODO() ctx := context.TODO()
key, wait, err := fileStore.Store(ctx, reader, testDataSize, toEncrypt) key, wait, err := fileStore.Store(ctx, bytes.NewReader(slice), testDataSize, toEncrypt)
if err != nil { if err != nil {
t.Fatalf("Store error: %v", err) t.Fatalf("Store error: %v", err)
} }
@ -63,13 +65,13 @@ func testFileStoreRandom(toEncrypt bool, t *testing.T) {
if isEncrypted != toEncrypt { if isEncrypted != toEncrypt {
t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted) t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted)
} }
resultSlice := make([]byte, len(slice)) resultSlice := make([]byte, testDataSize)
n, err := resultReader.ReadAt(resultSlice, 0) n, err := resultReader.ReadAt(resultSlice, 0)
if err != io.EOF { if err != io.EOF {
t.Fatalf("Retrieve error: %v", err) t.Fatalf("Retrieve error: %v", err)
} }
if n != len(slice) { if n != testDataSize {
t.Fatalf("Slice size error got %d, expected %d.", n, len(slice)) t.Fatalf("Slice size error got %d, expected %d.", n, testDataSize)
} }
if !bytes.Equal(slice, resultSlice) { if !bytes.Equal(slice, resultSlice) {
t.Fatalf("Comparison error.") t.Fatalf("Comparison error.")
@ -114,9 +116,9 @@ func testFileStoreCapacity(toEncrypt bool, t *testing.T) {
DbStore: db, DbStore: db,
} }
fileStore := NewFileStore(localStore, NewFileStoreParams()) fileStore := NewFileStore(localStore, NewFileStoreParams())
reader, slice := GenerateRandomData(testDataSize) slice := testutil.RandomBytes(1, testDataSize)
ctx := context.TODO() ctx := context.TODO()
key, wait, err := fileStore.Store(ctx, reader, testDataSize, toEncrypt) key, wait, err := fileStore.Store(ctx, bytes.NewReader(slice), testDataSize, toEncrypt)
if err != nil { if err != nil {
t.Errorf("Store error: %v", err) t.Errorf("Store error: %v", err)
} }

View file

@ -57,7 +57,6 @@ var (
var ( var (
keyIndex = byte(0) keyIndex = byte(0)
keyOldData = byte(1)
keyAccessCnt = []byte{2} keyAccessCnt = []byte{2}
keyEntryCnt = []byte{3} keyEntryCnt = []byte{3}
keyDataIdx = []byte{4} keyDataIdx = []byte{4}
@ -186,6 +185,20 @@ func NewLDBStore(params *LDBStoreParams) (s *LDBStore, err error) {
return s, nil return s, nil
} }
// MarkAccessed increments the access counter as a best effort for a chunk, so
// the chunk won't get garbage collected.
func (s *LDBStore) MarkAccessed(addr Address) {
s.lock.Lock()
defer s.lock.Unlock()
if s.closed {
return
}
proximity := s.po(addr)
s.tryAccessIdx(addr, proximity)
}
// initialize and set values for processing of gc round // initialize and set values for processing of gc round
func (s *LDBStore) startGC(c int) { func (s *LDBStore) startGC(c int) {
@ -271,6 +284,10 @@ func getGCIdxValue(index *dpaDBIndex, po uint8, addr Address) []byte {
return val return val
} }
func parseIdxKey(key []byte) (byte, []byte) {
return key[0], key[1:]
}
func parseGCIdxEntry(accessCnt []byte, val []byte) (index *dpaDBIndex, po uint8, addr Address) { func parseGCIdxEntry(accessCnt []byte, val []byte) (index *dpaDBIndex, po uint8, addr Address) {
index = &dpaDBIndex{ index = &dpaDBIndex{
Idx: binary.BigEndian.Uint64(val[1:]), Idx: binary.BigEndian.Uint64(val[1:]),
@ -349,6 +366,7 @@ func (s *LDBStore) collectGarbage() error {
s.delete(s.gc.batch.Batch, index, keyIdx, po) s.delete(s.gc.batch.Batch, index, keyIdx, po)
singleIterationCount++ singleIterationCount++
s.gc.count++ s.gc.count++
log.Trace("garbage collect enqueued chunk for deletion", "key", hash)
// break if target is not on max garbage batch boundary // break if target is not on max garbage batch boundary
if s.gc.count >= s.gc.target { if s.gc.count >= s.gc.target {
@ -554,47 +572,157 @@ func (s *LDBStore) Cleanup(f func(*chunk) bool) {
log.Warn(fmt.Sprintf("Found %v errors out of %v entries. Removed %v chunks.", errorsFound, total, removed)) log.Warn(fmt.Sprintf("Found %v errors out of %v entries. Removed %v chunks.", errorsFound, total, removed))
} }
func (s *LDBStore) ReIndex() { // CleanGCIndex rebuilds the garbage collector index from scratch, while
//Iterates over the database and checks that there are no faulty chunks // removing inconsistent elements, e.g., indices with missing data chunks.
// WARN: it's a pretty heavy, long running function.
func (s *LDBStore) CleanGCIndex() error {
s.lock.Lock()
defer s.lock.Unlock()
batch := leveldb.Batch{}
var okEntryCount uint64
var totalEntryCount uint64
// throw out all gc indices, we will rebuild from cleaned index
it := s.db.NewIterator() it := s.db.NewIterator()
startPosition := []byte{keyOldData} it.Seek([]byte{keyGCIdx})
it.Seek(startPosition) var gcDeletes int
var key []byte
var errorsFound, total int
for it.Valid() { for it.Valid() {
key = it.Key() rowType, _ := parseIdxKey(it.Key())
if (key == nil) || (key[0] != keyOldData) { if rowType != keyGCIdx {
break break
} }
data := it.Value() batch.Delete(it.Key())
hasher := s.hashfunc() gcDeletes++
hasher.Write(data) it.Next()
hash := hasher.Sum(nil) }
log.Debug("gc", "deletes", gcDeletes)
if err := s.db.Write(&batch); err != nil {
return err
}
batch.Reset()
newKey := make([]byte, 10) it.Release()
oldCntKey := make([]byte, 2)
newCntKey := make([]byte, 2)
oldCntKey[0] = keyDistanceCnt
newCntKey[0] = keyDistanceCnt
key[0] = keyData
key[1] = s.po(Address(key[1:]))
oldCntKey[1] = key[1]
newCntKey[1] = s.po(Address(newKey[1:]))
copy(newKey[2:], key[1:])
newValue := append(hash, data...)
batch := new(leveldb.Batch) // corrected po index pointer values
batch.Delete(key) var poPtrs [256]uint64
s.bucketCnt[oldCntKey[1]]--
batch.Put(oldCntKey, U64ToBytes(s.bucketCnt[oldCntKey[1]])) // set to true if chunk count not on 4096 iteration boundary
batch.Put(newKey, newValue) var doneIterating bool
s.bucketCnt[newCntKey[1]]++
batch.Put(newCntKey, U64ToBytes(s.bucketCnt[newCntKey[1]])) // last key index in previous iteration
s.db.Write(batch) lastIdxKey := []byte{keyIndex}
// counter for debug output
var cleanBatchCount int
// go through all key index entries
for !doneIterating {
cleanBatchCount++
var idxs []dpaDBIndex
var chunkHashes [][]byte
var pos []uint8
it := s.db.NewIterator()
it.Seek(lastIdxKey)
// 4096 is just a nice number, don't look for any hidden meaning here...
var i int
for i = 0; i < 4096; i++ {
// this really shouldn't happen unless database is empty
// but let's keep it to be safe
if !it.Valid() {
doneIterating = true
break
}
// if it's not keyindex anymore we're done iterating
rowType, chunkHash := parseIdxKey(it.Key())
if rowType != keyIndex {
doneIterating = true
break
}
// decode the retrieved index
var idx dpaDBIndex
err := decodeIndex(it.Value(), &idx)
if err != nil {
return fmt.Errorf("corrupt index: %v", err)
}
po := s.po(chunkHash)
lastIdxKey = it.Key()
// if we don't find the data key, remove the entry
// if we find it, add to the array of new gc indices to create
dataKey := getDataKey(idx.Idx, po)
_, err = s.db.Get(dataKey)
if err != nil {
log.Warn("deleting inconsistent index (missing data)", "key", chunkHash)
batch.Delete(it.Key())
} else {
idxs = append(idxs, idx)
chunkHashes = append(chunkHashes, chunkHash)
pos = append(pos, po)
okEntryCount++
if idx.Idx > poPtrs[po] {
poPtrs[po] = idx.Idx
}
}
totalEntryCount++
it.Next() it.Next()
} }
it.Release() it.Release()
log.Warn(fmt.Sprintf("Found %v errors out of %v entries", errorsFound, total))
// flush the key index corrections
err := s.db.Write(&batch)
if err != nil {
return err
}
batch.Reset()
// add correct gc indices
for i, okIdx := range idxs {
gcIdxKey := getGCIdxKey(&okIdx)
gcIdxData := getGCIdxValue(&okIdx, pos[i], chunkHashes[i])
batch.Put(gcIdxKey, gcIdxData)
log.Trace("clean ok", "key", chunkHashes[i], "gcKey", gcIdxKey, "gcData", gcIdxData)
}
// flush them
err = s.db.Write(&batch)
if err != nil {
return err
}
batch.Reset()
log.Debug("clean gc index pass", "batch", cleanBatchCount, "checked", i, "kept", len(idxs))
}
log.Debug("gc cleanup entries", "ok", okEntryCount, "total", totalEntryCount, "batchlen", batch.Len())
// lastly add updated entry count
var entryCount [8]byte
binary.BigEndian.PutUint64(entryCount[:], okEntryCount)
batch.Put(keyEntryCnt, entryCount[:])
// and add the new po index pointers
var poKey [2]byte
poKey[0] = keyDistanceCnt
for i, poPtr := range poPtrs {
poKey[1] = uint8(i)
if poPtr == 0 {
batch.Delete(poKey[:])
} else {
var idxCount [8]byte
binary.BigEndian.PutUint64(idxCount[:], poPtr)
batch.Put(poKey[:], idxCount[:])
}
}
// if you made it this far your harddisk has survived. Congratulations
return s.db.Write(&batch)
} }
// Delete is removes a chunk and updates indices. // Delete is removes a chunk and updates indices.
@ -685,12 +813,7 @@ func (s *LDBStore) Put(ctx context.Context, chunk Chunk) error {
idata, err := s.db.Get(ikey) idata, err := s.db.Get(ikey)
if err != nil { if err != nil {
s.doPut(chunk, &index, po) s.doPut(chunk, &index, po)
} else {
log.Debug("ldbstore.put: chunk already exists, only update access", "key", chunk.Address(), "po", po)
decodeIndex(idata, &index)
} }
index.Access = s.accessCnt
s.accessCnt++
idata = encodeIndex(&index) idata = encodeIndex(&index)
s.batch.Put(ikey, idata) s.batch.Put(ikey, idata)
@ -723,7 +846,8 @@ func (s *LDBStore) doPut(chunk Chunk, index *dpaDBIndex, po uint8) {
s.entryCnt++ s.entryCnt++
dbEntryCount.Inc(1) dbEntryCount.Inc(1)
s.dataIdx++ s.dataIdx++
index.Access = s.accessCnt
s.accessCnt++
cntKey := make([]byte, 2) cntKey := make([]byte, 2)
cntKey[0] = keyDistanceCnt cntKey[0] = keyDistanceCnt
cntKey[1] = po cntKey[1] = po
@ -796,28 +920,33 @@ func newMockEncodeDataFunc(mockStore *mock.NodeStore) func(chunk Chunk) []byte {
} }
} }
// try to find index; if found, update access cnt and return true // tryAccessIdx tries to find index entry. If found then increments the access
func (s *LDBStore) tryAccessIdx(ikey []byte, po uint8, index *dpaDBIndex) bool { // count for garbage collection and returns the index entry and true for found,
// otherwise returns nil and false.
func (s *LDBStore) tryAccessIdx(addr Address, po uint8) (*dpaDBIndex, bool) {
ikey := getIndexKey(addr)
idata, err := s.db.Get(ikey) idata, err := s.db.Get(ikey)
if err != nil { if err != nil {
return false return nil, false
} }
index := new(dpaDBIndex)
decodeIndex(idata, index) decodeIndex(idata, index)
oldGCIdxKey := getGCIdxKey(index) oldGCIdxKey := getGCIdxKey(index)
s.batch.Put(keyAccessCnt, U64ToBytes(s.accessCnt)) s.batch.Put(keyAccessCnt, U64ToBytes(s.accessCnt))
s.accessCnt++
index.Access = s.accessCnt index.Access = s.accessCnt
idata = encodeIndex(index) idata = encodeIndex(index)
s.accessCnt++
s.batch.Put(ikey, idata) s.batch.Put(ikey, idata)
newGCIdxKey := getGCIdxKey(index) newGCIdxKey := getGCIdxKey(index)
newGCIdxData := getGCIdxValue(index, po, ikey) newGCIdxData := getGCIdxValue(index, po, ikey[1:])
s.batch.Delete(oldGCIdxKey) s.batch.Delete(oldGCIdxKey)
s.batch.Put(newGCIdxKey, newGCIdxData) s.batch.Put(newGCIdxKey, newGCIdxData)
select { select {
case s.batchesC <- struct{}{}: case s.batchesC <- struct{}{}:
default: default:
} }
return true return index, true
} }
// GetSchema is returning the current named schema of the datastore as read from LevelDB // GetSchema is returning the current named schema of the datastore as read from LevelDB
@ -828,7 +957,7 @@ func (s *LDBStore) GetSchema() (string, error) {
data, err := s.db.Get(keySchema) data, err := s.db.Get(keySchema)
if err != nil { if err != nil {
if err == leveldb.ErrNotFound { if err == leveldb.ErrNotFound {
return "", nil return DbSchemaNone, nil
} }
return "", err return "", err
} }
@ -858,12 +987,12 @@ func (s *LDBStore) Get(_ context.Context, addr Address) (chunk Chunk, err error)
// TODO: To conform with other private methods of this object indices should not be updated // TODO: To conform with other private methods of this object indices should not be updated
func (s *LDBStore) get(addr Address) (chunk *chunk, err error) { func (s *LDBStore) get(addr Address) (chunk *chunk, err error) {
var indx dpaDBIndex
if s.closed { if s.closed {
return nil, ErrDBClosed return nil, ErrDBClosed
} }
proximity := s.po(addr) proximity := s.po(addr)
if s.tryAccessIdx(getIndexKey(addr), proximity, &indx) { index, found := s.tryAccessIdx(addr, proximity)
if found {
var data []byte var data []byte
if s.getDataFunc != nil { if s.getDataFunc != nil {
// if getDataFunc is defined, use it to retrieve the chunk data // if getDataFunc is defined, use it to retrieve the chunk data
@ -874,12 +1003,12 @@ func (s *LDBStore) get(addr Address) (chunk *chunk, err error) {
} }
} else { } else {
// default DbStore functionality to retrieve chunk data // default DbStore functionality to retrieve chunk data
datakey := getDataKey(indx.Idx, proximity) datakey := getDataKey(index.Idx, proximity)
data, err = s.db.Get(datakey) data, err = s.db.Get(datakey)
log.Trace("ldbstore.get retrieve", "key", addr, "indexkey", indx.Idx, "datakey", fmt.Sprintf("%x", datakey), "proximity", proximity) log.Trace("ldbstore.get retrieve", "key", addr, "indexkey", index.Idx, "datakey", fmt.Sprintf("%x", datakey), "proximity", proximity)
if err != nil { if err != nil {
log.Trace("ldbstore.get chunk found but could not be accessed", "key", addr, "err", err) log.Trace("ldbstore.get chunk found but could not be accessed", "key", addr, "err", err)
s.deleteNow(&indx, getIndexKey(addr), s.po(addr)) s.deleteNow(index, getIndexKey(addr), s.po(addr))
return return
} }
} }

View file

@ -19,6 +19,7 @@ package storage
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/binary"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -31,7 +32,6 @@ import (
ch "github.com/ethereum/go-ethereum/swarm/chunk" ch "github.com/ethereum/go-ethereum/swarm/chunk"
"github.com/ethereum/go-ethereum/swarm/log" "github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/storage/mock/mem" "github.com/ethereum/go-ethereum/swarm/storage/mock/mem"
ldberrors "github.com/syndtr/goleveldb/leveldb/errors" ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
) )
@ -105,6 +105,46 @@ func testDbStoreCorrect(n int, chunksize int64, mock bool, t *testing.T) {
testStoreCorrect(db, n, chunksize, t) testStoreCorrect(db, n, chunksize, t)
} }
func TestMarkAccessed(t *testing.T) {
db, cleanup, err := newTestDbStore(false, true)
defer cleanup()
if err != nil {
t.Fatalf("init dbStore failed: %v", err)
}
h := GenerateRandomChunk(ch.DefaultSize)
db.Put(context.Background(), h)
var index dpaDBIndex
addr := h.Address()
idxk := getIndexKey(addr)
idata, err := db.db.Get(idxk)
if err != nil {
t.Fatal(err)
}
decodeIndex(idata, &index)
if index.Access != 0 {
t.Fatalf("Expected the access index to be %d, but it is %d", 0, index.Access)
}
db.MarkAccessed(addr)
db.writeCurrentBatch()
idata, err = db.db.Get(idxk)
if err != nil {
t.Fatal(err)
}
decodeIndex(idata, &index)
if index.Access != 1 {
t.Fatalf("Expected the access index to be %d, but it is %d", 1, index.Access)
}
}
func TestDbStoreRandom_1(t *testing.T) { func TestDbStoreRandom_1(t *testing.T) {
testDbStoreRandom(1, 0, false, t) testDbStoreRandom(1, 0, false, t)
} }
@ -304,17 +344,18 @@ func TestLDBStoreWithoutCollectGarbage(t *testing.T) {
func TestLDBStoreCollectGarbage(t *testing.T) { func TestLDBStoreCollectGarbage(t *testing.T) {
// below max ronud // below max ronud
cap := defaultMaxGCRound / 2 initialCap := defaultMaxGCRound / 100
cap := initialCap / 2
t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage) t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage)
t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage) t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage)
// at max round // at max round
cap = defaultMaxGCRound cap = initialCap
t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage) t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage)
t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage) t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage)
// more than max around, not on threshold // more than max around, not on threshold
cap = defaultMaxGCRound * 1.1 cap = initialCap + 500
t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage) t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage)
t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage) t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage)
@ -538,7 +579,7 @@ func testLDBStoreRemoveThenCollectGarbage(t *testing.T) {
// TestLDBStoreCollectGarbageAccessUnlikeIndex tests garbage collection where accesscount differs from indexcount // TestLDBStoreCollectGarbageAccessUnlikeIndex tests garbage collection where accesscount differs from indexcount
func TestLDBStoreCollectGarbageAccessUnlikeIndex(t *testing.T) { func TestLDBStoreCollectGarbageAccessUnlikeIndex(t *testing.T) {
capacity := defaultMaxGCRound * 2 capacity := defaultMaxGCRound / 100 * 2
n := capacity - 1 n := capacity - 1
ldb, cleanup := newLDBStore(t) ldb, cleanup := newLDBStore(t)
@ -584,6 +625,177 @@ func TestLDBStoreCollectGarbageAccessUnlikeIndex(t *testing.T) {
log.Info("ldbstore", "total", n, "missing", missing, "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt) log.Info("ldbstore", "total", n, "missing", missing, "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt)
} }
func TestCleanIndex(t *testing.T) {
capacity := 5000
n := 3
ldb, cleanup := newLDBStore(t)
ldb.setCapacity(uint64(capacity))
defer cleanup()
chunks, err := mputRandomChunks(ldb, n, 4096)
if err != nil {
t.Fatal(err)
}
// remove the data of the first chunk
po := ldb.po(chunks[0].Address()[:])
dataKey := make([]byte, 10)
dataKey[0] = keyData
dataKey[1] = byte(po)
// dataKey[2:10] = first chunk has storageIdx 0 on [2:10]
if _, err := ldb.db.Get(dataKey); err != nil {
t.Fatal(err)
}
if err := ldb.db.Delete(dataKey); err != nil {
t.Fatal(err)
}
// remove the gc index row for the first chunk
gcFirstCorrectKey := make([]byte, 9)
gcFirstCorrectKey[0] = keyGCIdx
if err := ldb.db.Delete(gcFirstCorrectKey); err != nil {
t.Fatal(err)
}
// warp the gc data of the second chunk
// this data should be correct again after the clean
gcSecondCorrectKey := make([]byte, 9)
gcSecondCorrectKey[0] = keyGCIdx
binary.BigEndian.PutUint64(gcSecondCorrectKey[1:], uint64(1))
gcSecondCorrectVal, err := ldb.db.Get(gcSecondCorrectKey)
if err != nil {
t.Fatal(err)
}
warpedGCVal := make([]byte, len(gcSecondCorrectVal)+1)
copy(warpedGCVal[1:], gcSecondCorrectVal)
if err := ldb.db.Delete(gcSecondCorrectKey); err != nil {
t.Fatal(err)
}
if err := ldb.db.Put(gcSecondCorrectKey, warpedGCVal); err != nil {
t.Fatal(err)
}
if err := ldb.CleanGCIndex(); err != nil {
t.Fatal(err)
}
// the index without corresponding data should have been deleted
idxKey := make([]byte, 33)
idxKey[0] = keyIndex
copy(idxKey[1:], chunks[0].Address())
if _, err := ldb.db.Get(idxKey); err == nil {
t.Fatalf("expected chunk 0 idx to be pruned: %v", idxKey)
}
// the two other indices should be present
copy(idxKey[1:], chunks[1].Address())
if _, err := ldb.db.Get(idxKey); err != nil {
t.Fatalf("expected chunk 1 idx to be present: %v", idxKey)
}
copy(idxKey[1:], chunks[2].Address())
if _, err := ldb.db.Get(idxKey); err != nil {
t.Fatalf("expected chunk 2 idx to be present: %v", idxKey)
}
// first gc index should still be gone
if _, err := ldb.db.Get(gcFirstCorrectKey); err == nil {
t.Fatalf("expected gc 0 idx to be pruned: %v", idxKey)
}
// second gc index should still be fixed
if _, err := ldb.db.Get(gcSecondCorrectKey); err != nil {
t.Fatalf("expected gc 1 idx to be present: %v", idxKey)
}
// third gc index should be unchanged
binary.BigEndian.PutUint64(gcSecondCorrectKey[1:], uint64(2))
if _, err := ldb.db.Get(gcSecondCorrectKey); err != nil {
t.Fatalf("expected gc 2 idx to be present: %v", idxKey)
}
c, err := ldb.db.Get(keyEntryCnt)
if err != nil {
t.Fatalf("expected gc 2 idx to be present: %v", idxKey)
}
// entrycount should now be one less
entryCount := binary.BigEndian.Uint64(c)
if entryCount != 2 {
t.Fatalf("expected entrycnt to be 2, was %d", c)
}
// the chunks might accidentally be in the same bin
// if so that bin counter will now be 2 - the highest added index.
// if not, the total of them will be 3
poBins := []uint8{ldb.po(chunks[1].Address()), ldb.po(chunks[2].Address())}
if poBins[0] == poBins[1] {
poBins = poBins[:1]
}
var binTotal uint64
var currentBin [2]byte
currentBin[0] = keyDistanceCnt
if len(poBins) == 1 {
currentBin[1] = poBins[0]
c, err := ldb.db.Get(currentBin[:])
if err != nil {
t.Fatalf("expected gc 2 idx to be present: %v", idxKey)
}
binCount := binary.BigEndian.Uint64(c)
if binCount != 2 {
t.Fatalf("expected entrycnt to be 2, was %d", binCount)
}
} else {
for _, bin := range poBins {
currentBin[1] = bin
c, err := ldb.db.Get(currentBin[:])
if err != nil {
t.Fatalf("expected gc 2 idx to be present: %v", idxKey)
}
binCount := binary.BigEndian.Uint64(c)
binTotal += binCount
}
if binTotal != 3 {
t.Fatalf("expected sum of bin indices to be 3, was %d", binTotal)
}
}
// check that the iterator quits properly
chunks, err = mputRandomChunks(ldb, 4100, 4096)
if err != nil {
t.Fatal(err)
}
po = ldb.po(chunks[4099].Address()[:])
dataKey = make([]byte, 10)
dataKey[0] = keyData
dataKey[1] = byte(po)
binary.BigEndian.PutUint64(dataKey[2:], 4099+3)
if _, err := ldb.db.Get(dataKey); err != nil {
t.Fatal(err)
}
if err := ldb.db.Delete(dataKey); err != nil {
t.Fatal(err)
}
if err := ldb.CleanGCIndex(); err != nil {
t.Fatal(err)
}
// entrycount should now be one less of added chunks
c, err = ldb.db.Get(keyEntryCnt)
if err != nil {
t.Fatalf("expected gc 2 idx to be present: %v", idxKey)
}
entryCount = binary.BigEndian.Uint64(c)
if entryCount != 4099+2 {
t.Fatalf("expected entrycnt to be 2, was %d", c)
}
}
func waitGc(ctx context.Context, ldb *LDBStore) { func waitGc(ctx context.Context, ldb *LDBStore) {
<-ldb.gc.runC <-ldb.gc.runC
ldb.gc.runC <- struct{}{} ldb.gc.runC <- struct{}{}

View file

@ -153,6 +153,7 @@ func (ls *LocalStore) get(ctx context.Context, addr Address) (chunk Chunk, err e
if err == nil { if err == nil {
metrics.GetOrRegisterCounter("localstore.get.cachehit", nil).Inc(1) metrics.GetOrRegisterCounter("localstore.get.cachehit", nil).Inc(1)
go ls.DbStore.MarkAccessed(addr)
return chunk, nil return chunk, nil
} }
@ -195,31 +196,48 @@ func (ls *LocalStore) Close() {
// Migrate checks the datastore schema vs the runtime schema, and runs migrations if they don't match // Migrate checks the datastore schema vs the runtime schema, and runs migrations if they don't match
func (ls *LocalStore) Migrate() error { func (ls *LocalStore) Migrate() error {
schema, err := ls.DbStore.GetSchema() actualDbSchema, err := ls.DbStore.GetSchema()
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
return err return err
} }
log.Debug("found schema", "schema", schema, "runtime-schema", CurrentDbSchema) log.Debug("running migrations for", "schema", actualDbSchema, "runtime-schema", CurrentDbSchema)
if schema != CurrentDbSchema {
// run migrations
if schema == "" { if actualDbSchema == CurrentDbSchema {
log.Debug("running migrations for", "schema", schema, "runtime-schema", CurrentDbSchema) return nil
}
// delete chunks that are not valid, i.e. chunks that do not pass any of the ls.Validators if actualDbSchema == DbSchemaNone {
ls.migrateFromNoneToPurity()
actualDbSchema = DbSchemaPurity
}
if err := ls.DbStore.PutSchema(actualDbSchema); err != nil {
return err
}
if actualDbSchema == DbSchemaPurity {
if err := ls.migrateFromPurityToHalloween(); err != nil {
return err
}
actualDbSchema = DbSchemaHalloween
}
if err := ls.DbStore.PutSchema(actualDbSchema); err != nil {
return err
}
return nil
}
func (ls *LocalStore) migrateFromNoneToPurity() {
// delete chunks that are not valid, i.e. chunks that do not pass
// any of the ls.Validators
ls.DbStore.Cleanup(func(c *chunk) bool { ls.DbStore.Cleanup(func(c *chunk) bool {
return !ls.isValid(c) return !ls.isValid(c)
}) })
err := ls.DbStore.PutSchema(DbSchemaPurity)
if err != nil {
log.Error(err.Error())
return err
}
}
} }
return nil func (ls *LocalStore) migrateFromPurityToHalloween() error {
return ls.DbStore.CleanGCIndex()
} }

View file

@ -21,6 +21,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"time"
ch "github.com/ethereum/go-ethereum/swarm/chunk" ch "github.com/ethereum/go-ethereum/swarm/chunk"
) )
@ -144,3 +145,67 @@ func put(store *LocalStore, n int, f func(i int64) Chunk) (hs []Address, errs []
} }
return hs, errs return hs, errs
} }
// TestGetFrequentlyAccessedChunkWontGetGarbageCollected tests that the most
// frequently accessed chunk is not garbage collected from LDBStore, i.e.,
// from disk when we are at the capacity and garbage collector runs. For that
// we start putting random chunks into the DB while continuously accessing the
// chunk we care about then check if we can still retrieve it from disk.
func TestGetFrequentlyAccessedChunkWontGetGarbageCollected(t *testing.T) {
ldbCap := defaultGCRatio
store, cleanup := setupLocalStore(t, ldbCap)
defer cleanup()
var chunks []Chunk
for i := 0; i < ldbCap; i++ {
chunks = append(chunks, GenerateRandomChunk(ch.DefaultSize))
}
mostAccessed := chunks[0].Address()
for _, chunk := range chunks {
if err := store.Put(context.Background(), chunk); err != nil {
t.Fatal(err)
}
if _, err := store.Get(context.Background(), mostAccessed); err != nil {
t.Fatal(err)
}
// Add time for MarkAccessed() to be able to finish in a separate Goroutine
time.Sleep(1 * time.Millisecond)
}
store.DbStore.collectGarbage()
if _, err := store.DbStore.Get(context.Background(), mostAccessed); err != nil {
t.Logf("most frequntly accessed chunk not found on disk (key: %v)", mostAccessed)
t.Fatal(err)
}
}
func setupLocalStore(t *testing.T, ldbCap int) (ls *LocalStore, cleanup func()) {
t.Helper()
var err error
datadir, err := ioutil.TempDir("", "storage")
if err != nil {
t.Fatal(err)
}
params := &LocalStoreParams{
StoreParams: NewStoreParams(uint64(ldbCap), uint(ldbCap), nil, nil),
}
params.Init(datadir)
store, err := NewLocalStore(params, nil)
if err != nil {
_ = os.RemoveAll(datadir)
t.Fatal(err)
}
cleanup = func() {
store.Close()
_ = os.RemoveAll(datadir)
}
return store, cleanup
}

View file

@ -37,5 +37,5 @@ func TestRPCStore(t *testing.T) {
store := NewGlobalStore(rpc.DialInProc(server)) store := NewGlobalStore(rpc.DialInProc(server))
defer store.Close() defer store.Close()
test.MockStore(t, store, 100) test.MockStore(t, store, 30)
} }

View file

@ -1,6 +1,17 @@
package storage package storage
// The DB schema we want to use. The actual/current DB schema might differ
// until migrations are run.
const CurrentDbSchema = DbSchemaHalloween
// There was a time when we had no schema at all.
const DbSchemaNone = ""
// "purity" is the first formal schema of LevelDB we release together with Swarm 0.3.5 // "purity" is the first formal schema of LevelDB we release together with Swarm 0.3.5
const DbSchemaPurity = "purity" const DbSchemaPurity = "purity"
const CurrentDbSchema = DbSchemaPurity // "halloween" is here because we had a screw in the garbage collector index.
// Because of that we had to rebuild the GC index to get rid of erroneous
// entries and that takes a long time. This schema is used for bookkeeping,
// so rebuild index will run just once.
const DbSchemaHalloween = "halloween"

View file

@ -25,7 +25,6 @@ import (
"fmt" "fmt"
"hash" "hash"
"io" "io"
"io/ioutil"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/crypto/sha3"
@ -251,16 +250,6 @@ func GenerateRandomChunks(dataSize int64, count int) (chunks []Chunk) {
return chunks return chunks
} }
func GenerateRandomData(l int) (r io.Reader, slice []byte) {
slice, err := ioutil.ReadAll(io.LimitReader(rand.Reader, int64(l)))
if err != nil {
panic("rand error")
}
// log.Warn("generate random data", "len", len(slice), "data", common.Bytes2Hex(slice))
r = io.LimitReader(bytes.NewReader(slice), int64(l))
return r, slice
}
// Size, Seek, Read, ReadAt // Size, Seek, Read, ReadAt
type LazySectionReader interface { type LazySectionReader interface {
Context() context.Context Context() context.Context

93
swarm/swap/swap.go Normal file
View file

@ -0,0 +1,93 @@
// 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 swap
import (
"errors"
"fmt"
"strconv"
"sync"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/protocols"
"github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/state"
)
// SwAP Swarm Accounting Protocol
// a peer to peer micropayment system
// A node maintains an individual balance with every peer
// Only messages which have a price will be accounted for
type Swap struct {
stateStore state.Store //stateStore is needed in order to keep balances across sessions
lock sync.RWMutex //lock the balances
balances map[enode.ID]int64 //map of balances for each peer
}
// New - swap constructor
func New(stateStore state.Store) (swap *Swap) {
swap = &Swap{
stateStore: stateStore,
balances: make(map[enode.ID]int64),
}
return
}
//Swap implements the protocols.Balance interface
//Add is the (sole) accounting function
func (s *Swap) Add(amount int64, peer *protocols.Peer) (err error) {
s.lock.Lock()
defer s.lock.Unlock()
//load existing balances from the state store
err = s.loadState(peer)
if err != nil && err != state.ErrNotFound {
return
}
//adjust the balance
//if amount is negative, it will decrease, otherwise increase
s.balances[peer.ID()] += amount
//save the new balance to the state store
peerBalance := s.balances[peer.ID()]
err = s.stateStore.Put(peer.ID().String(), &peerBalance)
log.Debug(fmt.Sprintf("balance for peer %s: %s", peer.ID().String(), strconv.FormatInt(peerBalance, 10)))
return err
}
//GetPeerBalance returns the balance for a given peer
func (swap *Swap) GetPeerBalance(peer enode.ID) (int64, error) {
swap.lock.RLock()
defer swap.lock.RUnlock()
if p, ok := swap.balances[peer]; ok {
return p, nil
}
return 0, errors.New("Peer not found")
}
//load balances from the state store (persisted)
func (s *Swap) loadState(peer *protocols.Peer) (err error) {
var peerBalance int64
peerID := peer.ID()
//only load if the current instance doesn't already have this peer's
//balance in memory
if _, ok := s.balances[peerID]; !ok {
err = s.stateStore.Get(peerID.String(), &peerBalance)
s.balances[peerID] = peerBalance
}
return
}

184
swarm/swap/swap_test.go Normal file
View file

@ -0,0 +1,184 @@
// 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 swap
import (
"flag"
"fmt"
"io/ioutil"
mrand "math/rand"
"os"
"testing"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/protocols"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/swarm/state"
colorable "github.com/mattn/go-colorable"
)
var (
loglevel = flag.Int("loglevel", 2, "verbosity of logs")
)
func init() {
flag.Parse()
mrand.Seed(time.Now().UnixNano())
log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
}
//Test getting a peer's balance
func TestGetPeerBalance(t *testing.T) {
//create a test swap account
swap, testDir := createTestSwap(t)
defer os.RemoveAll(testDir)
//test for correct value
testPeer := newDummyPeer()
swap.balances[testPeer.ID()] = 888
b, err := swap.GetPeerBalance(testPeer.ID())
if err != nil {
t.Fatal(err)
}
if b != 888 {
t.Fatalf("Expected peer's balance to be %d, but is %d", 888, b)
}
//test for inexistent node
id := adapters.RandomNodeConfig().ID
_, err = swap.GetPeerBalance(id)
if err == nil {
t.Fatal("Expected call to fail, but it didn't!")
}
if err.Error() != "Peer not found" {
t.Fatalf("Expected test to fail with %s, but is %s", "Peer not found", err.Error())
}
}
//Test that repeated bookings do correct accounting
func TestRepeatedBookings(t *testing.T) {
//create a test swap account
swap, testDir := createTestSwap(t)
defer os.RemoveAll(testDir)
testPeer := newDummyPeer()
amount := mrand.Intn(100)
cnt := 1 + mrand.Intn(10)
for i := 0; i < cnt; i++ {
swap.Add(int64(amount), testPeer.Peer)
}
expectedBalance := int64(cnt * amount)
realBalance := swap.balances[testPeer.ID()]
if expectedBalance != realBalance {
t.Fatal(fmt.Sprintf("After %d credits of %d, expected balance to be: %d, but is: %d", cnt, amount, expectedBalance, realBalance))
}
testPeer2 := newDummyPeer()
amount = mrand.Intn(100)
cnt = 1 + mrand.Intn(10)
for i := 0; i < cnt; i++ {
swap.Add(0-int64(amount), testPeer2.Peer)
}
expectedBalance = int64(0 - (cnt * amount))
realBalance = swap.balances[testPeer2.ID()]
if expectedBalance != realBalance {
t.Fatal(fmt.Sprintf("After %d debits of %d, expected balance to be: %d, but is: %d", cnt, amount, expectedBalance, realBalance))
}
//mixed debits and credits
amount1 := mrand.Intn(100)
amount2 := mrand.Intn(55)
amount3 := mrand.Intn(999)
swap.Add(int64(amount1), testPeer2.Peer)
swap.Add(int64(0-amount2), testPeer2.Peer)
swap.Add(int64(0-amount3), testPeer2.Peer)
expectedBalance = expectedBalance + int64(amount1-amount2-amount3)
realBalance = swap.balances[testPeer2.ID()]
if expectedBalance != realBalance {
t.Fatal(fmt.Sprintf("After mixed debits and credits, expected balance to be: %d, but is: %d", expectedBalance, realBalance))
}
}
//try restoring a balance from state store
//this is simulated by creating a node,
//assigning it an arbitrary balance,
//then closing the state store.
//Then we re-open the state store and check that
//the balance is still the same
func TestRestoreBalanceFromStateStore(t *testing.T) {
//create a test swap account
swap, testDir := createTestSwap(t)
defer os.RemoveAll(testDir)
testPeer := newDummyPeer()
swap.balances[testPeer.ID()] = -8888
tmpBalance := swap.balances[testPeer.ID()]
swap.stateStore.Put(testPeer.ID().String(), &tmpBalance)
swap.stateStore.Close()
swap.stateStore = nil
stateStore, err := state.NewDBStore(testDir)
if err != nil {
t.Fatal(err)
}
var newBalance int64
stateStore.Get(testPeer.ID().String(), &newBalance)
//compare the balances
if tmpBalance != newBalance {
t.Fatal(fmt.Sprintf("Unexpected balance value after sending cheap message test. Expected balance: %d, balance is: %d",
tmpBalance, newBalance))
}
}
//create a test swap account
//creates a stateStore for persistence and a Swap account
func createTestSwap(t *testing.T) (*Swap, string) {
dir, err := ioutil.TempDir("", "swap_test_store")
if err != nil {
t.Fatal(err)
}
stateStore, err2 := state.NewDBStore(dir)
if err2 != nil {
t.Fatal(err2)
}
swap := New(stateStore)
return swap, dir
}
type dummyPeer struct {
*protocols.Peer
}
//creates a dummy protocols.Peer with dummy MsgReadWriter
func newDummyPeer() *dummyPeer {
id := adapters.RandomNodeConfig().ID
protoPeer := protocols.NewPeer(p2p.NewPeer(id, "testPeer", nil), nil, nil)
dummy := &dummyPeer{
Peer: protoPeer,
}
return dummy
}

View file

@ -51,6 +51,7 @@ import (
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/swarm/storage/feed" "github.com/ethereum/go-ethereum/swarm/storage/feed"
"github.com/ethereum/go-ethereum/swarm/storage/mock" "github.com/ethereum/go-ethereum/swarm/storage/mock"
"github.com/ethereum/go-ethereum/swarm/swap"
"github.com/ethereum/go-ethereum/swarm/tracing" "github.com/ethereum/go-ethereum/swarm/tracing"
) )
@ -78,6 +79,7 @@ type Swarm struct {
netStore *storage.NetStore netStore *storage.NetStore
sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit
ps *pss.Pss ps *pss.Pss
swap *swap.Swap
tracerClose io.Closer tracerClose io.Closer
} }
@ -171,6 +173,14 @@ func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err e
delivery := stream.NewDelivery(to, self.netStore) delivery := stream.NewDelivery(to, self.netStore)
self.netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, config.DeliverySkipCheck).New self.netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, config.DeliverySkipCheck).New
if config.SwapEnabled {
balancesStore, err := state.NewDBStore(filepath.Join(config.Path, "balances.db"))
if err != nil {
return nil, err
}
self.swap = swap.New(balancesStore)
}
var nodeID enode.ID var nodeID enode.ID
if err := nodeID.UnmarshalText([]byte(config.NodeID)); err != nil { if err := nodeID.UnmarshalText([]byte(config.NodeID)); err != nil {
return nil, err return nil, err
@ -193,7 +203,7 @@ func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err e
SyncUpdateDelay: config.SyncUpdateDelay, SyncUpdateDelay: config.SyncUpdateDelay,
MaxPeerServers: config.MaxStreamPeerServers, MaxPeerServers: config.MaxStreamPeerServers,
} }
self.streamer = stream.NewRegistry(nodeID, delivery, self.netStore, stateStore, registryOptions) self.streamer = stream.NewRegistry(nodeID, delivery, self.netStore, stateStore, registryOptions, self.swap)
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage // Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
self.fileStore = storage.NewFileStore(self.netStore, self.config.FileStoreParams) self.fileStore = storage.NewFileStore(self.netStore, self.config.FileStoreParams)
@ -216,7 +226,7 @@ func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err e
log.Debug("Setup local storage") log.Debug("Setup local storage")
self.bzz = network.NewBzz(bzzconfig, to, stateStore, stream.Spec, self.streamer.Run) self.bzz = network.NewBzz(bzzconfig, to, stateStore, self.streamer.GetSpec(), self.streamer.Run)
// Pss = postal service over swarm (devp2p over bzz) // Pss = postal service over swarm (devp2p over bzz)
self.ps, err = pss.NewPss(to, config.Pss) self.ps, err = pss.NewPss(to, config.Pss)
@ -353,7 +363,9 @@ func (self *Swarm) Start(srv *p2p.Server) error {
newaddr := self.bzz.UpdateLocalAddr([]byte(srv.Self().String())) newaddr := self.bzz.UpdateLocalAddr([]byte(srv.Self().String()))
log.Info("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%s", newaddr.UAddr)) log.Info("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%s", newaddr.UAddr))
// set chequebook // set chequebook
if self.config.SwapEnabled { //TODO: Currently if swap is enabled and no chequebook (or inexistent) contract is provided, the node would crash.
//Once we integrate back the contracts, this check MUST be revisited
if self.config.SwapEnabled && self.config.SwapAPI != "" {
ctx := context.Background() // The initial setup has no deadline. ctx := context.Background() // The initial setup has no deadline.
err := self.SetChequebook(ctx) err := self.SetChequebook(ctx)
if err != nil { if err != nil {

View file

@ -17,8 +17,10 @@
package testutil package testutil
import ( import (
"bytes"
"io" "io"
"io/ioutil" "io/ioutil"
"math/rand"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -42,3 +44,22 @@ func TempFileWithContent(t *testing.T, content string) string {
} }
return tempFile.Name() return tempFile.Name()
} }
// RandomBytes returns pseudo-random deterministic result
// because test fails must be reproducible
func RandomBytes(seed, length int) []byte {
b := make([]byte, length)
reader := rand.New(rand.NewSource(int64(seed)))
for n := 0; n < length; {
read, err := reader.Read(b[n:])
if err != nil {
panic(err)
}
n += read
}
return b
}
func RandomReader(seed, length int) *bytes.Reader {
return bytes.NewReader(RandomBytes(seed, length))
}

View file

@ -23,7 +23,7 @@ import (
const ( const (
VersionMajor = 0 // Major version component of the current release VersionMajor = 0 // Major version component of the current release
VersionMinor = 3 // Minor version component of the current release VersionMinor = 3 // Minor version component of the current release
VersionPatch = 6 // Patch version component of the current release VersionPatch = 7 // 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

@ -118,7 +118,7 @@ func (t *BlockTest) Run() error {
} else { } else {
engine = ethash.NewShared() engine = ethash.NewShared()
} }
chain, err := core.NewBlockChain(db, nil, config, engine, vm.Config{}, nil) chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieCleanLimit: 0}, config, engine, vm.Config{}, nil)
if err != nil { if err != nil {
return err return err
} }

View file

@ -22,6 +22,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/allegro/bigcache"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -30,6 +31,11 @@ import (
) )
var ( var (
memcacheCleanHitMeter = metrics.NewRegisteredMeter("trie/memcache/clean/hit", nil)
memcacheCleanMissMeter = metrics.NewRegisteredMeter("trie/memcache/clean/miss", nil)
memcacheCleanReadMeter = metrics.NewRegisteredMeter("trie/memcache/clean/read", nil)
memcacheCleanWriteMeter = metrics.NewRegisteredMeter("trie/memcache/clean/write", nil)
memcacheFlushTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/flush/time", nil) memcacheFlushTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/flush/time", nil)
memcacheFlushNodesMeter = metrics.NewRegisteredMeter("trie/memcache/flush/nodes", nil) memcacheFlushNodesMeter = metrics.NewRegisteredMeter("trie/memcache/flush/nodes", nil)
memcacheFlushSizeMeter = metrics.NewRegisteredMeter("trie/memcache/flush/size", nil) memcacheFlushSizeMeter = metrics.NewRegisteredMeter("trie/memcache/flush/size", nil)
@ -64,7 +70,8 @@ type DatabaseReader interface {
type Database struct { type Database struct {
diskdb ethdb.Database // Persistent storage for matured trie nodes diskdb ethdb.Database // Persistent storage for matured trie nodes
nodes map[common.Hash]*cachedNode // Data and references relationships of a node cleans *bigcache.BigCache // GC friendly memory cache of clean node RLPs
dirties map[common.Hash]*cachedNode // Data and references relationships of dirty nodes
oldest common.Hash // Oldest tracked node, flush-list head oldest common.Hash // Oldest tracked node, flush-list head
newest common.Hash // Newest tracked node, flush-list tail newest common.Hash // Newest tracked node, flush-list tail
@ -79,7 +86,7 @@ type Database struct {
flushnodes uint64 // Nodes flushed since last commit flushnodes uint64 // Nodes flushed since last commit
flushsize common.StorageSize // Data storage flushed since last commit flushsize common.StorageSize // Data storage flushed since last commit
nodesSize common.StorageSize // Storage size of the nodes cache (exc. flushlist) dirtiesSize common.StorageSize // Storage size of the dirty node cache (exc. flushlist)
preimagesSize common.StorageSize // Storage size of the preimages cache preimagesSize common.StorageSize // Storage size of the preimages cache
lock sync.RWMutex lock sync.RWMutex
@ -262,11 +269,30 @@ func expandNode(hash hashNode, n node, cachegen uint16) node {
} }
// NewDatabase creates a new trie database to store ephemeral trie content before // NewDatabase creates a new trie database to store ephemeral trie content before
// its written out to disk or garbage collected. // its written out to disk or garbage collected. No read cache is created, so all
// data retrievals will hit the underlying disk database.
func NewDatabase(diskdb ethdb.Database) *Database { func NewDatabase(diskdb ethdb.Database) *Database {
return NewDatabaseWithCache(diskdb, 0)
}
// NewDatabaseWithCache creates a new trie database to store ephemeral trie content
// before its written out to disk or garbage collected. It also acts as a read cache
// for nodes loaded from disk.
func NewDatabaseWithCache(diskdb ethdb.Database, cache int) *Database {
var cleans *bigcache.BigCache
if cache > 0 {
cleans, _ = bigcache.NewBigCache(bigcache.Config{
Shards: 1024,
LifeWindow: time.Hour,
MaxEntriesInWindow: cache * 1024,
MaxEntrySize: 512,
HardMaxCacheSize: cache,
})
}
return &Database{ return &Database{
diskdb: diskdb, diskdb: diskdb,
nodes: map[common.Hash]*cachedNode{{}: {}}, cleans: cleans,
dirties: map[common.Hash]*cachedNode{{}: {}},
preimages: make(map[common.Hash][]byte), preimages: make(map[common.Hash][]byte),
} }
} }
@ -293,7 +319,7 @@ func (db *Database) InsertBlob(hash common.Hash, blob []byte) {
// size tracking. // size tracking.
func (db *Database) insert(hash common.Hash, blob []byte, node node) { func (db *Database) insert(hash common.Hash, blob []byte, node node) {
// If the node's already cached, skip // If the node's already cached, skip
if _, ok := db.nodes[hash]; ok { if _, ok := db.dirties[hash]; ok {
return return
} }
// Create the cached entry for this node // Create the cached entry for this node
@ -303,19 +329,19 @@ func (db *Database) insert(hash common.Hash, blob []byte, node node) {
flushPrev: db.newest, flushPrev: db.newest,
} }
for _, child := range entry.childs() { for _, child := range entry.childs() {
if c := db.nodes[child]; c != nil { if c := db.dirties[child]; c != nil {
c.parents++ c.parents++
} }
} }
db.nodes[hash] = entry db.dirties[hash] = entry
// Update the flush-list endpoints // Update the flush-list endpoints
if db.oldest == (common.Hash{}) { if db.oldest == (common.Hash{}) {
db.oldest, db.newest = hash, hash db.oldest, db.newest = hash, hash
} else { } else {
db.nodes[db.newest].flushNext, db.newest = hash, hash db.dirties[db.newest].flushNext, db.newest = hash, hash
} }
db.nodesSize += common.StorageSize(common.HashLength + entry.size) db.dirtiesSize += common.StorageSize(common.HashLength + entry.size)
} }
// insertPreimage writes a new trie node pre-image to the memory database if it's // insertPreimage writes a new trie node pre-image to the memory database if it's
@ -333,35 +359,64 @@ func (db *Database) insertPreimage(hash common.Hash, preimage []byte) {
// node retrieves a cached trie node from memory, or returns nil if none can be // node retrieves a cached trie node from memory, or returns nil if none can be
// found in the memory cache. // found in the memory cache.
func (db *Database) node(hash common.Hash, cachegen uint16) node { func (db *Database) node(hash common.Hash, cachegen uint16) node {
// Retrieve the node from cache if available // Retrieve the node from the clean cache if available
if db.cleans != nil {
if enc, err := db.cleans.Get(string(hash[:])); err == nil && enc != nil {
memcacheCleanHitMeter.Mark(1)
memcacheCleanReadMeter.Mark(int64(len(enc)))
return mustDecodeNode(hash[:], enc, cachegen)
}
}
// Retrieve the node from the dirty cache if available
db.lock.RLock() db.lock.RLock()
node := db.nodes[hash] dirty := db.dirties[hash]
db.lock.RUnlock() db.lock.RUnlock()
if node != nil { if dirty != nil {
return node.obj(hash, cachegen) return dirty.obj(hash, cachegen)
} }
// Content unavailable in memory, attempt to retrieve from disk // Content unavailable in memory, attempt to retrieve from disk
enc, err := db.diskdb.Get(hash[:]) enc, err := db.diskdb.Get(hash[:])
if err != nil || enc == nil { if err != nil || enc == nil {
return nil return nil
} }
if db.cleans != nil {
db.cleans.Set(string(hash[:]), enc)
memcacheCleanMissMeter.Mark(1)
memcacheCleanWriteMeter.Mark(int64(len(enc)))
}
return mustDecodeNode(hash[:], enc, cachegen) return mustDecodeNode(hash[:], enc, cachegen)
} }
// Node retrieves an encoded cached trie node from memory. If it cannot be found // Node retrieves an encoded cached trie node from memory. If it cannot be found
// cached, the method queries the persistent database for the content. // cached, the method queries the persistent database for the content.
func (db *Database) Node(hash common.Hash) ([]byte, error) { func (db *Database) Node(hash common.Hash) ([]byte, error) {
// Retrieve the node from cache if available // Retrieve the node from the clean cache if available
if db.cleans != nil {
if enc, err := db.cleans.Get(string(hash[:])); err == nil && enc != nil {
memcacheCleanHitMeter.Mark(1)
memcacheCleanReadMeter.Mark(int64(len(enc)))
return enc, nil
}
}
// Retrieve the node from the dirty cache if available
db.lock.RLock() db.lock.RLock()
node := db.nodes[hash] dirty := db.dirties[hash]
db.lock.RUnlock() db.lock.RUnlock()
if node != nil { if dirty != nil {
return node.rlp(), nil return dirty.rlp(), nil
} }
// Content unavailable in memory, attempt to retrieve from disk // Content unavailable in memory, attempt to retrieve from disk
return db.diskdb.Get(hash[:]) enc, err := db.diskdb.Get(hash[:])
if err == nil && enc != nil {
if db.cleans != nil {
db.cleans.Set(string(hash[:]), enc)
memcacheCleanMissMeter.Mark(1)
memcacheCleanWriteMeter.Mark(int64(len(enc)))
}
}
return enc, err
} }
// preimage retrieves a cached trie node pre-image from memory. If it cannot be // preimage retrieves a cached trie node pre-image from memory. If it cannot be
@ -395,8 +450,8 @@ func (db *Database) Nodes() []common.Hash {
db.lock.RLock() db.lock.RLock()
defer db.lock.RUnlock() defer db.lock.RUnlock()
var hashes = make([]common.Hash, 0, len(db.nodes)) var hashes = make([]common.Hash, 0, len(db.dirties))
for hash := range db.nodes { for hash := range db.dirties {
if hash != (common.Hash{}) { // Special case for "root" references/nodes if hash != (common.Hash{}) { // Special case for "root" references/nodes
hashes = append(hashes, hash) hashes = append(hashes, hash)
} }
@ -415,18 +470,18 @@ func (db *Database) Reference(child common.Hash, parent common.Hash) {
// reference is the private locked version of Reference. // reference is the private locked version of Reference.
func (db *Database) reference(child common.Hash, parent common.Hash) { func (db *Database) reference(child common.Hash, parent common.Hash) {
// If the node does not exist, it's a node pulled from disk, skip // If the node does not exist, it's a node pulled from disk, skip
node, ok := db.nodes[child] node, ok := db.dirties[child]
if !ok { if !ok {
return return
} }
// If the reference already exists, only duplicate for roots // If the reference already exists, only duplicate for roots
if db.nodes[parent].children == nil { if db.dirties[parent].children == nil {
db.nodes[parent].children = make(map[common.Hash]uint16) db.dirties[parent].children = make(map[common.Hash]uint16)
} else if _, ok = db.nodes[parent].children[child]; ok && parent != (common.Hash{}) { } else if _, ok = db.dirties[parent].children[child]; ok && parent != (common.Hash{}) {
return return
} }
node.parents++ node.parents++
db.nodes[parent].children[child]++ db.dirties[parent].children[child]++
} }
// Dereference removes an existing reference from a root node. // Dereference removes an existing reference from a root node.
@ -439,25 +494,25 @@ func (db *Database) Dereference(root common.Hash) {
db.lock.Lock() db.lock.Lock()
defer db.lock.Unlock() defer db.lock.Unlock()
nodes, storage, start := len(db.nodes), db.nodesSize, time.Now() nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
db.dereference(root, common.Hash{}) db.dereference(root, common.Hash{})
db.gcnodes += uint64(nodes - len(db.nodes)) db.gcnodes += uint64(nodes - len(db.dirties))
db.gcsize += storage - db.nodesSize db.gcsize += storage - db.dirtiesSize
db.gctime += time.Since(start) db.gctime += time.Since(start)
memcacheGCTimeTimer.Update(time.Since(start)) memcacheGCTimeTimer.Update(time.Since(start))
memcacheGCSizeMeter.Mark(int64(storage - db.nodesSize)) memcacheGCSizeMeter.Mark(int64(storage - db.dirtiesSize))
memcacheGCNodesMeter.Mark(int64(nodes - len(db.nodes))) memcacheGCNodesMeter.Mark(int64(nodes - len(db.dirties)))
log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.nodes), "size", storage-db.nodesSize, "time", time.Since(start), log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start),
"gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.nodes), "livesize", db.nodesSize) "gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize)
} }
// dereference is the private locked version of Dereference. // dereference is the private locked version of Dereference.
func (db *Database) dereference(child common.Hash, parent common.Hash) { func (db *Database) dereference(child common.Hash, parent common.Hash) {
// Dereference the parent-child // Dereference the parent-child
node := db.nodes[parent] node := db.dirties[parent]
if node.children != nil && node.children[child] > 0 { if node.children != nil && node.children[child] > 0 {
node.children[child]-- node.children[child]--
@ -466,7 +521,7 @@ func (db *Database) dereference(child common.Hash, parent common.Hash) {
} }
} }
// If the child does not exist, it's a previously committed node. // If the child does not exist, it's a previously committed node.
node, ok := db.nodes[child] node, ok := db.dirties[child]
if !ok { if !ok {
return return
} }
@ -483,20 +538,20 @@ func (db *Database) dereference(child common.Hash, parent common.Hash) {
switch child { switch child {
case db.oldest: case db.oldest:
db.oldest = node.flushNext db.oldest = node.flushNext
db.nodes[node.flushNext].flushPrev = common.Hash{} db.dirties[node.flushNext].flushPrev = common.Hash{}
case db.newest: case db.newest:
db.newest = node.flushPrev db.newest = node.flushPrev
db.nodes[node.flushPrev].flushNext = common.Hash{} db.dirties[node.flushPrev].flushNext = common.Hash{}
default: default:
db.nodes[node.flushPrev].flushNext = node.flushNext db.dirties[node.flushPrev].flushNext = node.flushNext
db.nodes[node.flushNext].flushPrev = node.flushPrev db.dirties[node.flushNext].flushPrev = node.flushPrev
} }
// Dereference all children and delete the node // Dereference all children and delete the node
for _, hash := range node.childs() { for _, hash := range node.childs() {
db.dereference(hash, child) db.dereference(hash, child)
} }
delete(db.nodes, child) delete(db.dirties, child)
db.nodesSize -= common.StorageSize(common.HashLength + int(node.size)) db.dirtiesSize -= common.StorageSize(common.HashLength + int(node.size))
} }
} }
@ -509,13 +564,13 @@ func (db *Database) Cap(limit common.StorageSize) error {
// by only uncaching existing data when the database write finalizes. // by only uncaching existing data when the database write finalizes.
db.lock.RLock() db.lock.RLock()
nodes, storage, start := len(db.nodes), db.nodesSize, time.Now() nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
batch := db.diskdb.NewBatch() batch := db.diskdb.NewBatch()
// db.nodesSize only contains the useful data in the cache, but when reporting // db.dirtiesSize only contains the useful data in the cache, but when reporting
// the total memory consumption, the maintenance metadata is also needed to be // the total memory consumption, the maintenance metadata is also needed to be
// counted. For every useful node, we track 2 extra hashes as the flushlist. // counted. For every useful node, we track 2 extra hashes as the flushlist.
size := db.nodesSize + common.StorageSize((len(db.nodes)-1)*2*common.HashLength) size := db.dirtiesSize + common.StorageSize((len(db.dirties)-1)*2*common.HashLength)
// If the preimage cache got large enough, push to disk. If it's still small // If the preimage cache got large enough, push to disk. If it's still small
// leave for later to deduplicate writes. // leave for later to deduplicate writes.
@ -540,7 +595,7 @@ func (db *Database) Cap(limit common.StorageSize) error {
oldest := db.oldest oldest := db.oldest
for size > limit && oldest != (common.Hash{}) { for size > limit && oldest != (common.Hash{}) {
// Fetch the oldest referenced node and push into the batch // Fetch the oldest referenced node and push into the batch
node := db.nodes[oldest] node := db.dirties[oldest]
if err := batch.Put(oldest[:], node.rlp()); err != nil { if err := batch.Put(oldest[:], node.rlp()); err != nil {
db.lock.RUnlock() db.lock.RUnlock()
return err return err
@ -578,25 +633,25 @@ func (db *Database) Cap(limit common.StorageSize) error {
db.preimagesSize = 0 db.preimagesSize = 0
} }
for db.oldest != oldest { for db.oldest != oldest {
node := db.nodes[db.oldest] node := db.dirties[db.oldest]
delete(db.nodes, db.oldest) delete(db.dirties, db.oldest)
db.oldest = node.flushNext db.oldest = node.flushNext
db.nodesSize -= common.StorageSize(common.HashLength + int(node.size)) db.dirtiesSize -= common.StorageSize(common.HashLength + int(node.size))
} }
if db.oldest != (common.Hash{}) { if db.oldest != (common.Hash{}) {
db.nodes[db.oldest].flushPrev = common.Hash{} db.dirties[db.oldest].flushPrev = common.Hash{}
} }
db.flushnodes += uint64(nodes - len(db.nodes)) db.flushnodes += uint64(nodes - len(db.dirties))
db.flushsize += storage - db.nodesSize db.flushsize += storage - db.dirtiesSize
db.flushtime += time.Since(start) db.flushtime += time.Since(start)
memcacheFlushTimeTimer.Update(time.Since(start)) memcacheFlushTimeTimer.Update(time.Since(start))
memcacheFlushSizeMeter.Mark(int64(storage - db.nodesSize)) memcacheFlushSizeMeter.Mark(int64(storage - db.dirtiesSize))
memcacheFlushNodesMeter.Mark(int64(nodes - len(db.nodes))) memcacheFlushNodesMeter.Mark(int64(nodes - len(db.dirties)))
log.Debug("Persisted nodes from memory database", "nodes", nodes-len(db.nodes), "size", storage-db.nodesSize, "time", time.Since(start), log.Debug("Persisted nodes from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start),
"flushnodes", db.flushnodes, "flushsize", db.flushsize, "flushtime", db.flushtime, "livenodes", len(db.nodes), "livesize", db.nodesSize) "flushnodes", db.flushnodes, "flushsize", db.flushsize, "flushtime", db.flushtime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize)
return nil return nil
} }
@ -630,7 +685,7 @@ func (db *Database) Commit(node common.Hash, report bool) error {
} }
} }
// Move the trie itself into the batch, flushing if enough data is accumulated // Move the trie itself into the batch, flushing if enough data is accumulated
nodes, storage := len(db.nodes), db.nodesSize nodes, storage := len(db.dirties), db.dirtiesSize
if err := db.commit(node, batch); err != nil { if err := db.commit(node, batch); err != nil {
log.Error("Failed to commit trie from trie database", "err", err) log.Error("Failed to commit trie from trie database", "err", err)
db.lock.RUnlock() db.lock.RUnlock()
@ -654,15 +709,15 @@ func (db *Database) Commit(node common.Hash, report bool) error {
db.uncache(node) db.uncache(node)
memcacheCommitTimeTimer.Update(time.Since(start)) memcacheCommitTimeTimer.Update(time.Since(start))
memcacheCommitSizeMeter.Mark(int64(storage - db.nodesSize)) memcacheCommitSizeMeter.Mark(int64(storage - db.dirtiesSize))
memcacheCommitNodesMeter.Mark(int64(nodes - len(db.nodes))) memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties)))
logger := log.Info logger := log.Info
if !report { if !report {
logger = log.Debug logger = log.Debug
} }
logger("Persisted trie from memory database", "nodes", nodes-len(db.nodes)+int(db.flushnodes), "size", storage-db.nodesSize+db.flushsize, "time", time.Since(start)+db.flushtime, logger("Persisted trie from memory database", "nodes", nodes-len(db.dirties)+int(db.flushnodes), "size", storage-db.dirtiesSize+db.flushsize, "time", time.Since(start)+db.flushtime,
"gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.nodes), "livesize", db.nodesSize) "gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize)
// Reset the garbage collection statistics // Reset the garbage collection statistics
db.gcnodes, db.gcsize, db.gctime = 0, 0, 0 db.gcnodes, db.gcsize, db.gctime = 0, 0, 0
@ -674,7 +729,7 @@ func (db *Database) Commit(node common.Hash, report bool) error {
// commit is the private locked version of Commit. // commit is the private locked version of Commit.
func (db *Database) commit(hash common.Hash, batch ethdb.Batch) error { func (db *Database) commit(hash common.Hash, batch ethdb.Batch) error {
// If the node does not exist, it's a previously committed node // If the node does not exist, it's a previously committed node
node, ok := db.nodes[hash] node, ok := db.dirties[hash]
if !ok { if !ok {
return nil return nil
} }
@ -702,7 +757,7 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch) error {
// to disk. // to disk.
func (db *Database) uncache(hash common.Hash) { func (db *Database) uncache(hash common.Hash) {
// If the node does not exist, we're done on this path // If the node does not exist, we're done on this path
node, ok := db.nodes[hash] node, ok := db.dirties[hash]
if !ok { if !ok {
return return
} }
@ -710,20 +765,20 @@ func (db *Database) uncache(hash common.Hash) {
switch hash { switch hash {
case db.oldest: case db.oldest:
db.oldest = node.flushNext db.oldest = node.flushNext
db.nodes[node.flushNext].flushPrev = common.Hash{} db.dirties[node.flushNext].flushPrev = common.Hash{}
case db.newest: case db.newest:
db.newest = node.flushPrev db.newest = node.flushPrev
db.nodes[node.flushPrev].flushNext = common.Hash{} db.dirties[node.flushPrev].flushNext = common.Hash{}
default: default:
db.nodes[node.flushPrev].flushNext = node.flushNext db.dirties[node.flushPrev].flushNext = node.flushNext
db.nodes[node.flushNext].flushPrev = node.flushPrev db.dirties[node.flushNext].flushPrev = node.flushPrev
} }
// Uncache the node's subtries and remove the node itself too // Uncache the node's subtries and remove the node itself too
for _, child := range node.childs() { for _, child := range node.childs() {
db.uncache(child) db.uncache(child)
} }
delete(db.nodes, hash) delete(db.dirties, hash)
db.nodesSize -= common.StorageSize(common.HashLength + int(node.size)) db.dirtiesSize -= common.StorageSize(common.HashLength + int(node.size))
} }
// Size returns the current storage size of the memory cache in front of the // Size returns the current storage size of the memory cache in front of the
@ -732,11 +787,11 @@ func (db *Database) Size() (common.StorageSize, common.StorageSize) {
db.lock.RLock() db.lock.RLock()
defer db.lock.RUnlock() defer db.lock.RUnlock()
// db.nodesSize only contains the useful data in the cache, but when reporting // db.dirtiesSize only contains the useful data in the cache, but when reporting
// the total memory consumption, the maintenance metadata is also needed to be // the total memory consumption, the maintenance metadata is also needed to be
// counted. For every useful node, we track 2 extra hashes as the flushlist. // counted. For every useful node, we track 2 extra hashes as the flushlist.
var flushlistSize = common.StorageSize((len(db.nodes) - 1) * 2 * common.HashLength) var flushlistSize = common.StorageSize((len(db.dirties) - 1) * 2 * common.HashLength)
return db.nodesSize + flushlistSize, db.preimagesSize return db.dirtiesSize + flushlistSize, db.preimagesSize
} }
// verifyIntegrity is a debug method to iterate over the entire trie stored in // verifyIntegrity is a debug method to iterate over the entire trie stored in
@ -749,12 +804,12 @@ func (db *Database) verifyIntegrity() {
// Iterate over all the cached nodes and accumulate them into a set // Iterate over all the cached nodes and accumulate them into a set
reachable := map[common.Hash]struct{}{{}: {}} reachable := map[common.Hash]struct{}{{}: {}}
for child := range db.nodes[common.Hash{}].children { for child := range db.dirties[common.Hash{}].children {
db.accumulate(child, reachable) db.accumulate(child, reachable)
} }
// Find any unreachable but cached nodes // Find any unreachable but cached nodes
unreachable := []string{} unreachable := []string{}
for hash, node := range db.nodes { for hash, node := range db.dirties {
if _, ok := reachable[hash]; !ok { if _, ok := reachable[hash]; !ok {
unreachable = append(unreachable, fmt.Sprintf("%x: {Node: %v, Parents: %d, Prev: %x, Next: %x}", unreachable = append(unreachable, fmt.Sprintf("%x: {Node: %v, Parents: %d, Prev: %x, Next: %x}",
hash, node.node, node.parents, node.flushPrev, node.flushNext)) hash, node.node, node.parents, node.flushPrev, node.flushNext))
@ -769,7 +824,7 @@ func (db *Database) verifyIntegrity() {
// cached children found in memory. // cached children found in memory.
func (db *Database) accumulate(hash common.Hash, reachable map[common.Hash]struct{}) { func (db *Database) accumulate(hash common.Hash, reachable map[common.Hash]struct{}) {
// Mark the node reachable if present in the memory cache // Mark the node reachable if present in the memory cache
node, ok := db.nodes[hash] node, ok := db.dirties[hash]
if !ok { if !ok {
return return
} }

View file

@ -181,6 +181,8 @@ func (it *nodeIterator) LeafProof() [][]byte {
if len(it.stack) > 0 { if len(it.stack) > 0 {
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
hasher := newHasher(0, 0, nil) hasher := newHasher(0, 0, nil)
defer returnHasherToPool(hasher)
proofs := make([][]byte, 0, len(it.stack)) proofs := make([][]byte, 0, len(it.stack))
for i, item := range it.stack[:len(it.stack)-1] { for i, item := range it.stack[:len(it.stack)-1] {

View file

@ -113,7 +113,7 @@ func TestNodeIteratorCoverage(t *testing.T) {
t.Errorf("failed to retrieve reported node %x: %v", hash, err) t.Errorf("failed to retrieve reported node %x: %v", hash, err)
} }
} }
for hash, obj := range db.nodes { for hash, obj := range db.dirties {
if obj != nil && hash != (common.Hash{}) { if obj != nil && hash != (common.Hash{}) {
if _, ok := hashes[hash]; !ok { if _, ok := hashes[hash]; !ok {
t.Errorf("state entry not reported %x", hash) t.Errorf("state entry not reported %x", hash)
@ -333,8 +333,8 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) {
} }
} }
if memonly { if memonly {
robj = triedb.nodes[rkey] robj = triedb.dirties[rkey]
delete(triedb.nodes, rkey) delete(triedb.dirties, rkey)
} else { } else {
rval, _ = diskdb.Get(rkey[:]) rval, _ = diskdb.Get(rkey[:])
diskdb.Delete(rkey[:]) diskdb.Delete(rkey[:])
@ -350,7 +350,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) {
// Add the node back and continue iteration. // Add the node back and continue iteration.
if memonly { if memonly {
triedb.nodes[rkey] = robj triedb.dirties[rkey] = robj
} else { } else {
diskdb.Put(rkey[:], rval) diskdb.Put(rkey[:], rval)
} }
@ -393,8 +393,8 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) {
barNodeObj *cachedNode barNodeObj *cachedNode
) )
if memonly { if memonly {
barNodeObj = triedb.nodes[barNodeHash] barNodeObj = triedb.dirties[barNodeHash]
delete(triedb.nodes, barNodeHash) delete(triedb.dirties, barNodeHash)
} else { } else {
barNodeBlob, _ = diskdb.Get(barNodeHash[:]) barNodeBlob, _ = diskdb.Get(barNodeHash[:])
diskdb.Delete(barNodeHash[:]) diskdb.Delete(barNodeHash[:])
@ -411,7 +411,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) {
} }
// Reinsert the missing node. // Reinsert the missing node.
if memonly { if memonly {
triedb.nodes[barNodeHash] = barNodeObj triedb.dirties[barNodeHash] = barNodeObj
} else { } else {
diskdb.Put(barNodeHash[:], barNodeBlob) diskdb.Put(barNodeHash[:], barNodeBlob)
} }

View file

@ -66,6 +66,8 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.Putter) error {
} }
} }
hasher := newHasher(0, 0, nil) hasher := newHasher(0, 0, nil)
defer returnHasherToPool(hasher)
for i, n := range nodes { for i, n := range nodes {
// Don't bother checking for errors here since hasher panics // Don't bother checking for errors here since hasher panics
// if encoding doesn't work and we're not writing to any database. // if encoding doesn't work and we're not writing to any database.

View file

@ -119,7 +119,7 @@ func testMissingNode(t *testing.T, memonly bool) {
hash := common.HexToHash("0xe1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9") hash := common.HexToHash("0xe1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9")
if memonly { if memonly {
delete(triedb.nodes, hash) delete(triedb.dirties, hash)
} else { } else {
diskdb.Delete(hash[:]) diskdb.Delete(hash[:])
} }
@ -342,15 +342,16 @@ func TestCacheUnload(t *testing.T) {
// Commit the trie repeatedly and access key1. // Commit the trie repeatedly and access key1.
// The branch containing it is loaded from DB exactly two times: // The branch containing it is loaded from DB exactly two times:
// in the 0th and 6th iteration. // in the 0th and 6th iteration.
db := &countingDB{Database: trie.db.diskdb, gets: make(map[string]int)} diskdb := &countingDB{Database: trie.db.diskdb, gets: make(map[string]int)}
trie, _ = New(root, NewDatabase(db)) triedb := NewDatabase(diskdb)
trie, _ = New(root, triedb)
trie.SetCacheLimit(5) trie.SetCacheLimit(5)
for i := 0; i < 12; i++ { for i := 0; i < 12; i++ {
getString(trie, key1) getString(trie, key1)
trie.Commit(nil) trie.Commit(nil)
} }
// Check that it got loaded two times. // Check that it got loaded two times.
for dbkey, count := range db.gets { for dbkey, count := range diskdb.gets {
if count != 2 { if count != 2 {
t.Errorf("db key %x loaded %d times, want %d times", []byte(dbkey), count, 2) t.Errorf("db key %x loaded %d times, want %d times", []byte(dbkey), count, 2)
} }

201
vendor/github.com/allegro/bigcache/LICENSE generated vendored Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

150
vendor/github.com/allegro/bigcache/README.md generated vendored Normal file
View file

@ -0,0 +1,150 @@
# BigCache [![Build Status](https://travis-ci.org/allegro/bigcache.svg?branch=master)](https://travis-ci.org/allegro/bigcache)&nbsp;[![Coverage Status](https://coveralls.io/repos/github/allegro/bigcache/badge.svg?branch=master)](https://coveralls.io/github/allegro/bigcache?branch=master)&nbsp;[![GoDoc](https://godoc.org/github.com/allegro/bigcache?status.svg)](https://godoc.org/github.com/allegro/bigcache)&nbsp;[![Go Report Card](https://goreportcard.com/badge/github.com/allegro/bigcache)](https://goreportcard.com/report/github.com/allegro/bigcache)
Fast, concurrent, evicting in-memory cache written to keep big number of entries without impact on performance.
BigCache keeps entries on heap but omits GC for them. To achieve that operations on bytes arrays take place,
therefore entries (de)serialization in front of the cache will be needed in most use cases.
## Usage
### Simple initialization
```go
import "github.com/allegro/bigcache"
cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Minute))
cache.Set("my-unique-key", []byte("value"))
entry, _ := cache.Get("my-unique-key")
fmt.Println(string(entry))
```
### Custom initialization
When cache load can be predicted in advance then it is better to use custom initialization because additional memory
allocation can be avoided in that way.
```go
import (
"log"
"github.com/allegro/bigcache"
)
config := bigcache.Config {
// number of shards (must be a power of 2)
Shards: 1024,
// time after which entry can be evicted
LifeWindow: 10 * time.Minute,
// rps * lifeWindow, used only in initial memory allocation
MaxEntriesInWindow: 1000 * 10 * 60,
// max entry size in bytes, used only in initial memory allocation
MaxEntrySize: 500,
// prints information about additional memory allocation
Verbose: true,
// cache will not allocate more memory than this limit, value in MB
// if value is reached then the oldest entries can be overridden for the new ones
// 0 value means no size limit
HardMaxCacheSize: 8192,
// callback fired when the oldest entry is removed because of its expiration time or no space left
// for the new entry, or because delete was called. A bitmask representing the reason will be returned.
// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
OnRemove: nil,
// OnRemoveWithReason is a callback fired when the oldest entry is removed because of its expiration time or no space left
// for the new entry, or because delete was called. A constant representing the reason will be passed through.
// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
// Ignored if OnRemove is specified.
OnRemoveWithReason: nil,
}
cache, initErr := bigcache.NewBigCache(config)
if initErr != nil {
log.Fatal(initErr)
}
cache.Set("my-unique-key", []byte("value"))
if entry, err := cache.Get("my-unique-key"); err == nil {
fmt.Println(string(entry))
}
```
## Benchmarks
Three caches were compared: bigcache, [freecache](https://github.com/coocood/freecache) and map.
Benchmark tests were made using an i7-6700K with 32GB of RAM on Windows 10.
### Writes and reads
```bash
cd caches_bench; go test -bench=. -benchtime=10s ./... -timeout 30m
BenchmarkMapSet-8 3000000 569 ns/op 202 B/op 3 allocs/op
BenchmarkConcurrentMapSet-8 1000000 1592 ns/op 347 B/op 8 allocs/op
BenchmarkFreeCacheSet-8 3000000 775 ns/op 355 B/op 2 allocs/op
BenchmarkBigCacheSet-8 3000000 640 ns/op 303 B/op 2 allocs/op
BenchmarkMapGet-8 5000000 407 ns/op 24 B/op 1 allocs/op
BenchmarkConcurrentMapGet-8 3000000 558 ns/op 24 B/op 2 allocs/op
BenchmarkFreeCacheGet-8 2000000 682 ns/op 136 B/op 2 allocs/op
BenchmarkBigCacheGet-8 3000000 512 ns/op 152 B/op 4 allocs/op
BenchmarkBigCacheSetParallel-8 10000000 225 ns/op 313 B/op 3 allocs/op
BenchmarkFreeCacheSetParallel-8 10000000 218 ns/op 341 B/op 3 allocs/op
BenchmarkConcurrentMapSetParallel-8 5000000 318 ns/op 200 B/op 6 allocs/op
BenchmarkBigCacheGetParallel-8 20000000 178 ns/op 152 B/op 4 allocs/op
BenchmarkFreeCacheGetParallel-8 20000000 295 ns/op 136 B/op 3 allocs/op
BenchmarkConcurrentMapGetParallel-8 10000000 237 ns/op 24 B/op 2 allocs/op
```
Writes and reads in bigcache are faster than in freecache.
Writes to map are the slowest.
### GC pause time
```bash
cd caches_bench; go run caches_gc_overhead_comparison.go
Number of entries: 20000000
GC pause for bigcache: 5.8658ms
GC pause for freecache: 32.4341ms
GC pause for map: 52.9661ms
```
Test shows how long are the GC pauses for caches filled with 20mln of entries.
Bigcache and freecache have very similar GC pause time.
It is clear that both reduce GC overhead in contrast to map
which GC pause time took more than 10 seconds.
## How it works
BigCache relies on optimization presented in 1.5 version of Go ([issue-9477](https://github.com/golang/go/issues/9477)).
This optimization states that if map without pointers in keys and values is used then GC will omit its content.
Therefore BigCache uses `map[uint64]uint32` where keys are hashed and values are offsets of entries.
Entries are kept in bytes array, to omit GC again.
Bytes array size can grow to gigabytes without impact on performance
because GC will only see single pointer to it.
## Bigcache vs Freecache
Both caches provide the same core features but they reduce GC overhead in different ways.
Bigcache relies on `map[uint64]uint32`, freecache implements its own mapping built on
slices to reduce number of pointers.
Results from benchmark tests are presented above.
One of the advantage of bigcache over freecache is that you dont need to know
the size of the cache in advance, because when bigcache is full,
it can allocate additional memory for new entries instead of
overwriting existing ones as freecache does currently.
However hard max size in bigcache also can be set, check [HardMaxCacheSize](https://godoc.org/github.com/allegro/bigcache#Config).
## HTTP Server
This package also includes an easily deployable HTTP implementation of BigCache, which can be found in the [server](/server) package.
## More
Bigcache genesis is described in allegro.tech blog post: [writing a very fast cache service in Go](http://allegro.tech/2016/03/writing-fast-cache-service-in-go.html)
## License
BigCache is released under the Apache 2.0 license (see [LICENSE](LICENSE))

202
vendor/github.com/allegro/bigcache/bigcache.go generated vendored Normal file
View file

@ -0,0 +1,202 @@
package bigcache
import (
"fmt"
"time"
)
const (
minimumEntriesInShard = 10 // Minimum number of entries in single shard
)
// BigCache is fast, concurrent, evicting cache created to keep big number of entries without impact on performance.
// It keeps entries on heap but omits GC for them. To achieve that, operations take place on byte arrays,
// therefore entries (de)serialization in front of the cache will be needed in most use cases.
type BigCache struct {
shards []*cacheShard
lifeWindow uint64
clock clock
hash Hasher
config Config
shardMask uint64
maxShardSize uint32
close chan struct{}
}
// RemoveReason is a value used to signal to the user why a particular key was removed in the OnRemove callback.
type RemoveReason uint32
const (
// Expired means the key is past its LifeWindow.
Expired RemoveReason = iota
// NoSpace means the key is the oldest and the cache size was at its maximum when Set was called, or the
// entry exceeded the maximum shard size.
NoSpace
// Deleted means Delete was called and this key was removed as a result.
Deleted
)
// NewBigCache initialize new instance of BigCache
func NewBigCache(config Config) (*BigCache, error) {
return newBigCache(config, &systemClock{})
}
func newBigCache(config Config, clock clock) (*BigCache, error) {
if !isPowerOfTwo(config.Shards) {
return nil, fmt.Errorf("Shards number must be power of two")
}
if config.Hasher == nil {
config.Hasher = newDefaultHasher()
}
cache := &BigCache{
shards: make([]*cacheShard, config.Shards),
lifeWindow: uint64(config.LifeWindow.Seconds()),
clock: clock,
hash: config.Hasher,
config: config,
shardMask: uint64(config.Shards - 1),
maxShardSize: uint32(config.maximumShardSize()),
close: make(chan struct{}),
}
var onRemove func(wrappedEntry []byte, reason RemoveReason)
if config.OnRemove != nil {
onRemove = cache.providedOnRemove
} else if config.OnRemoveWithReason != nil {
onRemove = cache.providedOnRemoveWithReason
} else {
onRemove = cache.notProvidedOnRemove
}
for i := 0; i < config.Shards; i++ {
cache.shards[i] = initNewShard(config, onRemove, clock)
}
if config.CleanWindow > 0 {
go func() {
ticker := time.NewTicker(config.CleanWindow)
defer ticker.Stop()
for {
select {
case t := <-ticker.C:
cache.cleanUp(uint64(t.Unix()))
case <-cache.close:
return
}
}
}()
}
return cache, nil
}
// Close is used to signal a shutdown of the cache when you are done with it.
// This allows the cleaning goroutines to exit and ensures references are not
// kept to the cache preventing GC of the entire cache.
func (c *BigCache) Close() error {
close(c.close)
return nil
}
// Get reads entry for the key.
// It returns an EntryNotFoundError when
// no entry exists for the given key.
func (c *BigCache) Get(key string) ([]byte, error) {
hashedKey := c.hash.Sum64(key)
shard := c.getShard(hashedKey)
return shard.get(key, hashedKey)
}
// Set saves entry under the key
func (c *BigCache) Set(key string, entry []byte) error {
hashedKey := c.hash.Sum64(key)
shard := c.getShard(hashedKey)
return shard.set(key, hashedKey, entry)
}
// Delete removes the key
func (c *BigCache) Delete(key string) error {
hashedKey := c.hash.Sum64(key)
shard := c.getShard(hashedKey)
return shard.del(key, hashedKey)
}
// Reset empties all cache shards
func (c *BigCache) Reset() error {
for _, shard := range c.shards {
shard.reset(c.config)
}
return nil
}
// Len computes number of entries in cache
func (c *BigCache) Len() int {
var len int
for _, shard := range c.shards {
len += shard.len()
}
return len
}
// Capacity returns amount of bytes store in the cache.
func (c *BigCache) Capacity() int {
var len int
for _, shard := range c.shards {
len += shard.capacity()
}
return len
}
// Stats returns cache's statistics
func (c *BigCache) Stats() Stats {
var s Stats
for _, shard := range c.shards {
tmp := shard.getStats()
s.Hits += tmp.Hits
s.Misses += tmp.Misses
s.DelHits += tmp.DelHits
s.DelMisses += tmp.DelMisses
s.Collisions += tmp.Collisions
}
return s
}
// Iterator returns iterator function to iterate over EntryInfo's from whole cache.
func (c *BigCache) Iterator() *EntryInfoIterator {
return newIterator(c)
}
func (c *BigCache) onEvict(oldestEntry []byte, currentTimestamp uint64, evict func(reason RemoveReason) error) bool {
oldestTimestamp := readTimestampFromEntry(oldestEntry)
if currentTimestamp-oldestTimestamp > c.lifeWindow {
evict(Expired)
return true
}
return false
}
func (c *BigCache) cleanUp(currentTimestamp uint64) {
for _, shard := range c.shards {
shard.cleanUp(currentTimestamp)
}
}
func (c *BigCache) getShard(hashedKey uint64) (shard *cacheShard) {
return c.shards[hashedKey&c.shardMask]
}
func (c *BigCache) providedOnRemove(wrappedEntry []byte, reason RemoveReason) {
c.config.OnRemove(readKeyFromEntry(wrappedEntry), readEntry(wrappedEntry))
}
func (c *BigCache) providedOnRemoveWithReason(wrappedEntry []byte, reason RemoveReason) {
if c.config.onRemoveFilter == 0 || (1<<uint(reason))&c.config.onRemoveFilter > 0 {
c.config.OnRemoveWithReason(readKeyFromEntry(wrappedEntry), readEntry(wrappedEntry), reason)
}
}
func (c *BigCache) notProvidedOnRemove(wrappedEntry []byte, reason RemoveReason) {
}

14
vendor/github.com/allegro/bigcache/bytes.go generated vendored Normal file
View file

@ -0,0 +1,14 @@
// +build !appengine
package bigcache
import (
"reflect"
"unsafe"
)
func bytesToString(b []byte) string {
bytesHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
strHeader := reflect.StringHeader{Data: bytesHeader.Data, Len: bytesHeader.Len}
return *(*string)(unsafe.Pointer(&strHeader))
}

View file

@ -0,0 +1,7 @@
// +build appengine
package bigcache
func bytesToString(b []byte) string {
return string(b)
}

14
vendor/github.com/allegro/bigcache/clock.go generated vendored Normal file
View file

@ -0,0 +1,14 @@
package bigcache
import "time"
type clock interface {
epoch() int64
}
type systemClock struct {
}
func (c systemClock) epoch() int64 {
return time.Now().Unix()
}

86
vendor/github.com/allegro/bigcache/config.go generated vendored Normal file
View file

@ -0,0 +1,86 @@
package bigcache
import "time"
// Config for BigCache
type Config struct {
// Number of cache shards, value must be a power of two
Shards int
// Time after which entry can be evicted
LifeWindow time.Duration
// Interval between removing expired entries (clean up).
// If set to <= 0 then no action is performed. Setting to < 1 second is counterproductive — bigcache has a one second resolution.
CleanWindow time.Duration
// Max number of entries in life window. Used only to calculate initial size for cache shards.
// When proper value is set then additional memory allocation does not occur.
MaxEntriesInWindow int
// Max size of entry in bytes. Used only to calculate initial size for cache shards.
MaxEntrySize int
// Verbose mode prints information about new memory allocation
Verbose bool
// Hasher used to map between string keys and unsigned 64bit integers, by default fnv64 hashing is used.
Hasher Hasher
// HardMaxCacheSize is a limit for cache size in MB. Cache will not allocate more memory than this limit.
// It can protect application from consuming all available memory on machine, therefore from running OOM Killer.
// Default value is 0 which means unlimited size. When the limit is higher than 0 and reached then
// the oldest entries are overridden for the new ones.
HardMaxCacheSize int
// OnRemove is a callback fired when the oldest entry is removed because of its expiration time or no space left
// for the new entry, or because delete was called.
// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
OnRemove func(key string, entry []byte)
// OnRemoveWithReason is a callback fired when the oldest entry is removed because of its expiration time or no space left
// for the new entry, or because delete was called. A constant representing the reason will be passed through.
// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
// Ignored if OnRemove is specified.
OnRemoveWithReason func(key string, entry []byte, reason RemoveReason)
onRemoveFilter int
// Logger is a logging interface and used in combination with `Verbose`
// Defaults to `DefaultLogger()`
Logger Logger
}
// DefaultConfig initializes config with default values.
// When load for BigCache can be predicted in advance then it is better to use custom config.
func DefaultConfig(eviction time.Duration) Config {
return Config{
Shards: 1024,
LifeWindow: eviction,
CleanWindow: 0,
MaxEntriesInWindow: 1000 * 10 * 60,
MaxEntrySize: 500,
Verbose: true,
Hasher: newDefaultHasher(),
HardMaxCacheSize: 0,
Logger: DefaultLogger(),
}
}
// initialShardSize computes initial shard size
func (c Config) initialShardSize() int {
return max(c.MaxEntriesInWindow/c.Shards, minimumEntriesInShard)
}
// maximumShardSize computes maximum shard size
func (c Config) maximumShardSize() int {
maxShardSize := 0
if c.HardMaxCacheSize > 0 {
maxShardSize = convertMBToBytes(c.HardMaxCacheSize) / c.Shards
}
return maxShardSize
}
// OnRemoveFilterSet sets which remove reasons will trigger a call to OnRemoveWithReason.
// Filtering out reasons prevents bigcache from unwrapping them, which saves cpu.
func (c Config) OnRemoveFilterSet(reasons ...RemoveReason) Config {
c.onRemoveFilter = 0
for i := range reasons {
c.onRemoveFilter |= 1 << uint(reasons[i])
}
return c
}

62
vendor/github.com/allegro/bigcache/encoding.go generated vendored Normal file
View file

@ -0,0 +1,62 @@
package bigcache
import (
"encoding/binary"
)
const (
timestampSizeInBytes = 8 // Number of bytes used for timestamp
hashSizeInBytes = 8 // Number of bytes used for hash
keySizeInBytes = 2 // Number of bytes used for size of entry key
headersSizeInBytes = timestampSizeInBytes + hashSizeInBytes + keySizeInBytes // Number of bytes used for all headers
)
func wrapEntry(timestamp uint64, hash uint64, key string, entry []byte, buffer *[]byte) []byte {
keyLength := len(key)
blobLength := len(entry) + headersSizeInBytes + keyLength
if blobLength > len(*buffer) {
*buffer = make([]byte, blobLength)
}
blob := *buffer
binary.LittleEndian.PutUint64(blob, timestamp)
binary.LittleEndian.PutUint64(blob[timestampSizeInBytes:], hash)
binary.LittleEndian.PutUint16(blob[timestampSizeInBytes+hashSizeInBytes:], uint16(keyLength))
copy(blob[headersSizeInBytes:], key)
copy(blob[headersSizeInBytes+keyLength:], entry)
return blob[:blobLength]
}
func readEntry(data []byte) []byte {
length := binary.LittleEndian.Uint16(data[timestampSizeInBytes+hashSizeInBytes:])
// copy on read
dst := make([]byte, len(data)-int(headersSizeInBytes+length))
copy(dst, data[headersSizeInBytes+length:])
return dst
}
func readTimestampFromEntry(data []byte) uint64 {
return binary.LittleEndian.Uint64(data)
}
func readKeyFromEntry(data []byte) string {
length := binary.LittleEndian.Uint16(data[timestampSizeInBytes+hashSizeInBytes:])
// copy on read
dst := make([]byte, length)
copy(dst, data[headersSizeInBytes:headersSizeInBytes+length])
return bytesToString(dst)
}
func readHashFromEntry(data []byte) uint64 {
return binary.LittleEndian.Uint64(data[timestampSizeInBytes:])
}
func resetKeyFromEntry(data []byte) {
binary.LittleEndian.PutUint64(data[timestampSizeInBytes:], 0)
}

View file

@ -0,0 +1,17 @@
package bigcache
import "fmt"
// EntryNotFoundError is an error type struct which is returned when entry was not found for provided key
type EntryNotFoundError struct {
key string
}
func notFound(key string) error {
return &EntryNotFoundError{key}
}
// Error returned when entry does not exist.
func (e EntryNotFoundError) Error() string {
return fmt.Sprintf("Entry %q not found", e.key)
}

28
vendor/github.com/allegro/bigcache/fnv.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
package bigcache
// newDefaultHasher returns a new 64-bit FNV-1a Hasher which makes no memory allocations.
// Its Sum64 method will lay the value out in big-endian byte order.
// See https://en.wikipedia.org/wiki/FowlerNollVo_hash_function
func newDefaultHasher() Hasher {
return fnv64a{}
}
type fnv64a struct{}
const (
// offset64 FNVa offset basis. See https://en.wikipedia.org/wiki/FowlerNollVo_hash_function#FNV-1a_hash
offset64 = 14695981039346656037
// prime64 FNVa prime value. See https://en.wikipedia.org/wiki/FowlerNollVo_hash_function#FNV-1a_hash
prime64 = 1099511628211
)
// Sum64 gets the string and returns its uint64 hash value.
func (f fnv64a) Sum64(key string) uint64 {
var hash uint64 = offset64
for i := 0; i < len(key); i++ {
hash ^= uint64(key[i])
hash *= prime64
}
return hash
}

8
vendor/github.com/allegro/bigcache/hash.go generated vendored Normal file
View file

@ -0,0 +1,8 @@
package bigcache
// Hasher is responsible for generating unsigned, 64 bit hash of provided string. Hasher should minimize collisions
// (generating same hash for different strings) and while performance is also important fast functions are preferable (i.e.
// you can use FarmHash family).
type Hasher interface {
Sum64(string) uint64
}

122
vendor/github.com/allegro/bigcache/iterator.go generated vendored Normal file
View file

@ -0,0 +1,122 @@
package bigcache
import "sync"
type iteratorError string
func (e iteratorError) Error() string {
return string(e)
}
// ErrInvalidIteratorState is reported when iterator is in invalid state
const ErrInvalidIteratorState = iteratorError("Iterator is in invalid state. Use SetNext() to move to next position")
// ErrCannotRetrieveEntry is reported when entry cannot be retrieved from underlying
const ErrCannotRetrieveEntry = iteratorError("Could not retrieve entry from cache")
var emptyEntryInfo = EntryInfo{}
// EntryInfo holds informations about entry in the cache
type EntryInfo struct {
timestamp uint64
hash uint64
key string
value []byte
}
// Key returns entry's underlying key
func (e EntryInfo) Key() string {
return e.key
}
// Hash returns entry's hash value
func (e EntryInfo) Hash() uint64 {
return e.hash
}
// Timestamp returns entry's timestamp (time of insertion)
func (e EntryInfo) Timestamp() uint64 {
return e.timestamp
}
// Value returns entry's underlying value
func (e EntryInfo) Value() []byte {
return e.value
}
// EntryInfoIterator allows to iterate over entries in the cache
type EntryInfoIterator struct {
mutex sync.Mutex
cache *BigCache
currentShard int
currentIndex int
elements []uint32
elementsCount int
valid bool
}
// SetNext moves to next element and returns true if it exists.
func (it *EntryInfoIterator) SetNext() bool {
it.mutex.Lock()
it.valid = false
it.currentIndex++
if it.elementsCount > it.currentIndex {
it.valid = true
it.mutex.Unlock()
return true
}
for i := it.currentShard + 1; i < it.cache.config.Shards; i++ {
it.elements, it.elementsCount = it.cache.shards[i].copyKeys()
// Non empty shard - stick with it
if it.elementsCount > 0 {
it.currentIndex = 0
it.currentShard = i
it.valid = true
it.mutex.Unlock()
return true
}
}
it.mutex.Unlock()
return false
}
func newIterator(cache *BigCache) *EntryInfoIterator {
elements, count := cache.shards[0].copyKeys()
return &EntryInfoIterator{
cache: cache,
currentShard: 0,
currentIndex: -1,
elements: elements,
elementsCount: count,
}
}
// Value returns current value from the iterator
func (it *EntryInfoIterator) Value() (EntryInfo, error) {
it.mutex.Lock()
if !it.valid {
it.mutex.Unlock()
return emptyEntryInfo, ErrInvalidIteratorState
}
entry, err := it.cache.shards[it.currentShard].getEntry(int(it.elements[it.currentIndex]))
if err != nil {
it.mutex.Unlock()
return emptyEntryInfo, ErrCannotRetrieveEntry
}
it.mutex.Unlock()
return EntryInfo{
timestamp: readTimestampFromEntry(entry),
hash: readHashFromEntry(entry),
key: readKeyFromEntry(entry),
value: readEntry(entry),
}, nil
}

30
vendor/github.com/allegro/bigcache/logger.go generated vendored Normal file
View file

@ -0,0 +1,30 @@
package bigcache
import (
"log"
"os"
)
// Logger is invoked when `Config.Verbose=true`
type Logger interface {
Printf(format string, v ...interface{})
}
// this is a safeguard, breaking on compile time in case
// `log.Logger` does not adhere to our `Logger` interface.
// see https://golang.org/doc/faq#guarantee_satisfies_interface
var _ Logger = &log.Logger{}
// DefaultLogger returns a `Logger` implementation
// backed by stdlib's log
func DefaultLogger() *log.Logger {
return log.New(os.Stdout, "", log.LstdFlags)
}
func newLogger(custom Logger) Logger {
if custom != nil {
return custom
}
return DefaultLogger()
}

210
vendor/github.com/allegro/bigcache/queue/bytes_queue.go generated vendored Normal file
View file

@ -0,0 +1,210 @@
package queue
import (
"encoding/binary"
"log"
"time"
)
const (
// Number of bytes used to keep information about entry size
headerEntrySize = 4
// Bytes before left margin are not used. Zero index means element does not exist in queue, useful while reading slice from index
leftMarginIndex = 1
// Minimum empty blob size in bytes. Empty blob fills space between tail and head in additional memory allocation.
// It keeps entries indexes unchanged
minimumEmptyBlobSize = 32 + headerEntrySize
)
// BytesQueue is a non-thread safe queue type of fifo based on bytes array.
// For every push operation index of entry is returned. It can be used to read the entry later
type BytesQueue struct {
array []byte
capacity int
maxCapacity int
head int
tail int
count int
rightMargin int
headerBuffer []byte
verbose bool
initialCapacity int
}
type queueError struct {
message string
}
// NewBytesQueue initialize new bytes queue.
// Initial capacity is used in bytes array allocation
// When verbose flag is set then information about memory allocation are printed
func NewBytesQueue(initialCapacity int, maxCapacity int, verbose bool) *BytesQueue {
return &BytesQueue{
array: make([]byte, initialCapacity),
capacity: initialCapacity,
maxCapacity: maxCapacity,
headerBuffer: make([]byte, headerEntrySize),
tail: leftMarginIndex,
head: leftMarginIndex,
rightMargin: leftMarginIndex,
verbose: verbose,
initialCapacity: initialCapacity,
}
}
// Reset removes all entries from queue
func (q *BytesQueue) Reset() {
// Just reset indexes
q.tail = leftMarginIndex
q.head = leftMarginIndex
q.rightMargin = leftMarginIndex
q.count = 0
}
// Push copies entry at the end of queue and moves tail pointer. Allocates more space if needed.
// Returns index for pushed data or error if maximum size queue limit is reached.
func (q *BytesQueue) Push(data []byte) (int, error) {
dataLen := len(data)
if q.availableSpaceAfterTail() < dataLen+headerEntrySize {
if q.availableSpaceBeforeHead() >= dataLen+headerEntrySize {
q.tail = leftMarginIndex
} else if q.capacity+headerEntrySize+dataLen >= q.maxCapacity && q.maxCapacity > 0 {
return -1, &queueError{"Full queue. Maximum size limit reached."}
} else {
q.allocateAdditionalMemory(dataLen + headerEntrySize)
}
}
index := q.tail
q.push(data, dataLen)
return index, nil
}
func (q *BytesQueue) allocateAdditionalMemory(minimum int) {
start := time.Now()
if q.capacity < minimum {
q.capacity += minimum
}
q.capacity = q.capacity * 2
if q.capacity > q.maxCapacity && q.maxCapacity > 0 {
q.capacity = q.maxCapacity
}
oldArray := q.array
q.array = make([]byte, q.capacity)
if leftMarginIndex != q.rightMargin {
copy(q.array, oldArray[:q.rightMargin])
if q.tail < q.head {
emptyBlobLen := q.head - q.tail - headerEntrySize
q.push(make([]byte, emptyBlobLen), emptyBlobLen)
q.head = leftMarginIndex
q.tail = q.rightMargin
}
}
if q.verbose {
log.Printf("Allocated new queue in %s; Capacity: %d \n", time.Since(start), q.capacity)
}
}
func (q *BytesQueue) push(data []byte, len int) {
binary.LittleEndian.PutUint32(q.headerBuffer, uint32(len))
q.copy(q.headerBuffer, headerEntrySize)
q.copy(data, len)
if q.tail > q.head {
q.rightMargin = q.tail
}
q.count++
}
func (q *BytesQueue) copy(data []byte, len int) {
q.tail += copy(q.array[q.tail:], data[:len])
}
// Pop reads the oldest entry from queue and moves head pointer to the next one
func (q *BytesQueue) Pop() ([]byte, error) {
data, size, err := q.peek(q.head)
if err != nil {
return nil, err
}
q.head += headerEntrySize + size
q.count--
if q.head == q.rightMargin {
q.head = leftMarginIndex
if q.tail == q.rightMargin {
q.tail = leftMarginIndex
}
q.rightMargin = q.tail
}
return data, nil
}
// Peek reads the oldest entry from list without moving head pointer
func (q *BytesQueue) Peek() ([]byte, error) {
data, _, err := q.peek(q.head)
return data, err
}
// Get reads entry from index
func (q *BytesQueue) Get(index int) ([]byte, error) {
data, _, err := q.peek(index)
return data, err
}
// Capacity returns number of allocated bytes for queue
func (q *BytesQueue) Capacity() int {
return q.capacity
}
// Len returns number of entries kept in queue
func (q *BytesQueue) Len() int {
return q.count
}
// Error returns error message
func (e *queueError) Error() string {
return e.message
}
func (q *BytesQueue) peek(index int) ([]byte, int, error) {
if q.count == 0 {
return nil, 0, &queueError{"Empty queue"}
}
if index <= 0 {
return nil, 0, &queueError{"Index must be grater than zero. Invalid index."}
}
if index+headerEntrySize >= len(q.array) {
return nil, 0, &queueError{"Index out of range"}
}
blockSize := int(binary.LittleEndian.Uint32(q.array[index : index+headerEntrySize]))
return q.array[index+headerEntrySize : index+headerEntrySize+blockSize], blockSize, nil
}
func (q *BytesQueue) availableSpaceAfterTail() int {
if q.tail >= q.head {
return q.capacity - q.tail
}
return q.head - q.tail - minimumEmptyBlobSize
}
func (q *BytesQueue) availableSpaceBeforeHead() int {
if q.tail >= q.head {
return q.head - leftMarginIndex - minimumEmptyBlobSize
}
return q.head - q.tail - minimumEmptyBlobSize
}

236
vendor/github.com/allegro/bigcache/shard.go generated vendored Normal file
View file

@ -0,0 +1,236 @@
package bigcache
import (
"fmt"
"sync"
"sync/atomic"
"github.com/allegro/bigcache/queue"
)
type onRemoveCallback func(wrappedEntry []byte, reason RemoveReason)
type cacheShard struct {
hashmap map[uint64]uint32
entries queue.BytesQueue
lock sync.RWMutex
entryBuffer []byte
onRemove onRemoveCallback
isVerbose bool
logger Logger
clock clock
lifeWindow uint64
stats Stats
}
func (s *cacheShard) get(key string, hashedKey uint64) ([]byte, error) {
s.lock.RLock()
itemIndex := s.hashmap[hashedKey]
if itemIndex == 0 {
s.lock.RUnlock()
s.miss()
return nil, notFound(key)
}
wrappedEntry, err := s.entries.Get(int(itemIndex))
if err != nil {
s.lock.RUnlock()
s.miss()
return nil, err
}
if entryKey := readKeyFromEntry(wrappedEntry); key != entryKey {
if s.isVerbose {
s.logger.Printf("Collision detected. Both %q and %q have the same hash %x", key, entryKey, hashedKey)
}
s.lock.RUnlock()
s.collision()
return nil, notFound(key)
}
s.lock.RUnlock()
s.hit()
return readEntry(wrappedEntry), nil
}
func (s *cacheShard) set(key string, hashedKey uint64, entry []byte) error {
currentTimestamp := uint64(s.clock.epoch())
s.lock.Lock()
if previousIndex := s.hashmap[hashedKey]; previousIndex != 0 {
if previousEntry, err := s.entries.Get(int(previousIndex)); err == nil {
resetKeyFromEntry(previousEntry)
}
}
if oldestEntry, err := s.entries.Peek(); err == nil {
s.onEvict(oldestEntry, currentTimestamp, s.removeOldestEntry)
}
w := wrapEntry(currentTimestamp, hashedKey, key, entry, &s.entryBuffer)
for {
if index, err := s.entries.Push(w); err == nil {
s.hashmap[hashedKey] = uint32(index)
s.lock.Unlock()
return nil
}
if s.removeOldestEntry(NoSpace) != nil {
s.lock.Unlock()
return fmt.Errorf("entry is bigger than max shard size")
}
}
}
func (s *cacheShard) del(key string, hashedKey uint64) error {
s.lock.RLock()
itemIndex := s.hashmap[hashedKey]
if itemIndex == 0 {
s.lock.RUnlock()
s.delmiss()
return notFound(key)
}
wrappedEntry, err := s.entries.Get(int(itemIndex))
if err != nil {
s.lock.RUnlock()
s.delmiss()
return err
}
s.lock.RUnlock()
s.lock.Lock()
{
delete(s.hashmap, hashedKey)
s.onRemove(wrappedEntry, Deleted)
resetKeyFromEntry(wrappedEntry)
}
s.lock.Unlock()
s.delhit()
return nil
}
func (s *cacheShard) onEvict(oldestEntry []byte, currentTimestamp uint64, evict func(reason RemoveReason) error) bool {
oldestTimestamp := readTimestampFromEntry(oldestEntry)
if currentTimestamp-oldestTimestamp > s.lifeWindow {
evict(Expired)
return true
}
return false
}
func (s *cacheShard) cleanUp(currentTimestamp uint64) {
s.lock.Lock()
for {
if oldestEntry, err := s.entries.Peek(); err != nil {
break
} else if evicted := s.onEvict(oldestEntry, currentTimestamp, s.removeOldestEntry); !evicted {
break
}
}
s.lock.Unlock()
}
func (s *cacheShard) getOldestEntry() ([]byte, error) {
return s.entries.Peek()
}
func (s *cacheShard) getEntry(index int) ([]byte, error) {
return s.entries.Get(index)
}
func (s *cacheShard) copyKeys() (keys []uint32, next int) {
keys = make([]uint32, len(s.hashmap))
s.lock.RLock()
for _, index := range s.hashmap {
keys[next] = index
next++
}
s.lock.RUnlock()
return keys, next
}
func (s *cacheShard) removeOldestEntry(reason RemoveReason) error {
oldest, err := s.entries.Pop()
if err == nil {
hash := readHashFromEntry(oldest)
delete(s.hashmap, hash)
s.onRemove(oldest, reason)
return nil
}
return err
}
func (s *cacheShard) reset(config Config) {
s.lock.Lock()
s.hashmap = make(map[uint64]uint32, config.initialShardSize())
s.entryBuffer = make([]byte, config.MaxEntrySize+headersSizeInBytes)
s.entries.Reset()
s.lock.Unlock()
}
func (s *cacheShard) len() int {
s.lock.RLock()
res := len(s.hashmap)
s.lock.RUnlock()
return res
}
func (s *cacheShard) capacity() int {
s.lock.RLock()
res := s.entries.Capacity()
s.lock.RUnlock()
return res
}
func (s *cacheShard) getStats() Stats {
var stats = Stats{
Hits: atomic.LoadInt64(&s.stats.Hits),
Misses: atomic.LoadInt64(&s.stats.Misses),
DelHits: atomic.LoadInt64(&s.stats.DelHits),
DelMisses: atomic.LoadInt64(&s.stats.DelMisses),
Collisions: atomic.LoadInt64(&s.stats.Collisions),
}
return stats
}
func (s *cacheShard) hit() {
atomic.AddInt64(&s.stats.Hits, 1)
}
func (s *cacheShard) miss() {
atomic.AddInt64(&s.stats.Misses, 1)
}
func (s *cacheShard) delhit() {
atomic.AddInt64(&s.stats.DelHits, 1)
}
func (s *cacheShard) delmiss() {
atomic.AddInt64(&s.stats.DelMisses, 1)
}
func (s *cacheShard) collision() {
atomic.AddInt64(&s.stats.Collisions, 1)
}
func initNewShard(config Config, callback onRemoveCallback, clock clock) *cacheShard {
return &cacheShard{
hashmap: make(map[uint64]uint32, config.initialShardSize()),
entries: *queue.NewBytesQueue(config.initialShardSize()*config.MaxEntrySize, config.maximumShardSize(), config.Verbose),
entryBuffer: make([]byte, config.MaxEntrySize+headersSizeInBytes),
onRemove: callback,
isVerbose: config.Verbose,
logger: newLogger(config.Logger),
clock: clock,
lifeWindow: uint64(config.LifeWindow.Seconds()),
}
}

15
vendor/github.com/allegro/bigcache/stats.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
package bigcache
// Stats stores cache statistics
type Stats struct {
// Hits is a number of successfully found keys
Hits int64 `json:"hits"`
// Misses is a number of not found keys
Misses int64 `json:"misses"`
// DelHits is a number of successfully deleted keys
DelHits int64 `json:"delete_hits"`
// DelMisses is a number of not deleted keys
DelMisses int64 `json:"delete_misses"`
// Collisions is a number of happened key-collisions
Collisions int64 `json:"collisions"`
}

16
vendor/github.com/allegro/bigcache/utils.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
package bigcache
func max(a, b int) int {
if a > b {
return a
}
return b
}
func convertMBToBytes(value int) int {
return value * 1024 * 1024
}
func isPowerOfTwo(number int) bool {
return (number & (number - 1)) == 0
}

12
vendor/vendor.json vendored
View file

@ -38,6 +38,18 @@
"revision": "5d049714c4a64225c3c79a7cf7d02f7fb5b96338", "revision": "5d049714c4a64225c3c79a7cf7d02f7fb5b96338",
"revisionTime": "2018-01-16T20:38:02Z" "revisionTime": "2018-01-16T20:38:02Z"
}, },
{
"checksumSHA1": "9Niiu1GNhWUrXnGZrl8AU4EzbVE=",
"path": "github.com/allegro/bigcache",
"revision": "bff00e20c68d9f136477d62d182a7dc917bae0ca",
"revisionTime": "2018-10-22T20:06:25Z"
},
{
"checksumSHA1": "zqToN+R6KybEskp1D4G/lAOKXU4=",
"path": "github.com/allegro/bigcache/queue",
"revision": "bff00e20c68d9f136477d62d182a7dc917bae0ca",
"revisionTime": "2018-10-22T20:06:25Z"
},
{ {
"checksumSHA1": "USkefO0g1U9mr+8hagv3fpSkrxg=", "checksumSHA1": "USkefO0g1U9mr+8hagv3fpSkrxg=",
"path": "github.com/aristanetworks/goarista/monotime", "path": "github.com/aristanetworks/goarista/monotime",