mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
Merge branch 'master' into shed
This commit is contained in:
commit
3685c959e0
90 changed files with 3203 additions and 753 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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", {})
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: ,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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{}{}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
93
swarm/swap/swap.go
Normal 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
184
swarm/swap/swap_test.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
201
trie/database.go
201
trie/database.go
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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] {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
201
vendor/github.com/allegro/bigcache/LICENSE
generated
vendored
Normal 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
150
vendor/github.com/allegro/bigcache/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
# BigCache [](https://travis-ci.org/allegro/bigcache) [](https://coveralls.io/github/allegro/bigcache?branch=master) [](https://godoc.org/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 don’t 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
202
vendor/github.com/allegro/bigcache/bigcache.go
generated
vendored
Normal 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
14
vendor/github.com/allegro/bigcache/bytes.go
generated
vendored
Normal 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))
|
||||||
|
}
|
||||||
7
vendor/github.com/allegro/bigcache/bytes_appengine.go
generated
vendored
Normal file
7
vendor/github.com/allegro/bigcache/bytes_appengine.go
generated
vendored
Normal 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
14
vendor/github.com/allegro/bigcache/clock.go
generated
vendored
Normal 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
86
vendor/github.com/allegro/bigcache/config.go
generated
vendored
Normal 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
62
vendor/github.com/allegro/bigcache/encoding.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
17
vendor/github.com/allegro/bigcache/entry_not_found_error.go
generated
vendored
Normal file
17
vendor/github.com/allegro/bigcache/entry_not_found_error.go
generated
vendored
Normal 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
28
vendor/github.com/allegro/bigcache/fnv.go
generated
vendored
Normal 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/Fowler–Noll–Vo_hash_function
|
||||||
|
func newDefaultHasher() Hasher {
|
||||||
|
return fnv64a{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fnv64a struct{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// offset64 FNVa offset basis. See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function#FNV-1a_hash
|
||||||
|
offset64 = 14695981039346656037
|
||||||
|
// prime64 FNVa prime value. See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_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
8
vendor/github.com/allegro/bigcache/hash.go
generated
vendored
Normal 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
122
vendor/github.com/allegro/bigcache/iterator.go
generated
vendored
Normal 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
30
vendor/github.com/allegro/bigcache/logger.go
generated
vendored
Normal 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
210
vendor/github.com/allegro/bigcache/queue/bytes_queue.go
generated
vendored
Normal 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
236
vendor/github.com/allegro/bigcache/shard.go
generated
vendored
Normal 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
15
vendor/github.com/allegro/bigcache/stats.go
generated
vendored
Normal 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
16
vendor/github.com/allegro/bigcache/utils.go
generated
vendored
Normal 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
12
vendor/vendor.json
vendored
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue