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
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
|
|
@ -9,9 +9,9 @@ les/ @zsfelfoldi
|
|||
light/ @zsfelfoldi
|
||||
mobile/ @karalabe
|
||||
p2p/ @fjl @zsfelfoldi
|
||||
p2p/simulations @lmars
|
||||
p2p/protocols @zelig
|
||||
swarm/api/http @justelad
|
||||
p2p/simulations @lmars
|
||||
p2p/protocols @zelig
|
||||
swarm/api/http @justelad
|
||||
swarm/bmt @zelig
|
||||
swarm/dev @lmars
|
||||
swarm/fuse @jmozah @holisticode
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ var (
|
|||
utils.LightKDFFlag,
|
||||
utils.CacheFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
utils.CacheTrieFlag,
|
||||
utils.CacheGCFlag,
|
||||
utils.TrieCacheGenFlag,
|
||||
utils.ListenPortFlag,
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ var AppHelpFlagGroups = []flagGroup{
|
|||
Flags: []cli.Flag{
|
||||
utils.CacheFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
utils.CacheTrieFlag,
|
||||
utils.CacheGCFlag,
|
||||
utils.TrieCacheGenFlag,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
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"
|
||||
)
|
||||
|
||||
|
|
@ -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
|
||||
// fetch from the 2nd node using HTTP BasicAuth
|
||||
func TestAccessPassword(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
dataFilename := testutil.TempFileWithContent(t, data)
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
|
@ -29,6 +27,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
// TestCLISwarmExportImport perform the following test:
|
||||
|
|
@ -45,11 +44,12 @@ func TestCLISwarmExportImport(t *testing.T) {
|
|||
cluster := newTestCluster(t, 1)
|
||||
|
||||
// generate random 10mb file
|
||||
f, cleanup := generateRandomFile(t, 10000000)
|
||||
defer cleanup()
|
||||
content := testutil.RandomBytes(1, 10000000)
|
||||
fileName := testutil.TempFileWithContent(t, string(content))
|
||||
defer os.Remove(fileName)
|
||||
|
||||
// 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}`)
|
||||
up.ExpectExit()
|
||||
hash := matches[0]
|
||||
|
|
@ -96,7 +96,7 @@ func TestCLISwarmExportImport(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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) {
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"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/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
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) {
|
||||
|
||||
srv := testutil.NewTestSwarmServer(t, func(api *api.API) testutil.TestServer {
|
||||
srv := swarmhttp.NewTestSwarmServer(t, func(api *api.API) swarmhttp.TestServer {
|
||||
return swarmhttp.NewServer(api, "")
|
||||
}, nil)
|
||||
log.Info("starting a test swarm server")
|
||||
defer srv.Close()
|
||||
|
||||
// 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"
|
||||
privKey, _ := crypto.HexToECDSA(privkeyHex)
|
||||
address := crypto.PubkeyToAddress(privKey.PublicKey)
|
||||
|
||||
// save the private key to a file
|
||||
_, err = io.WriteString(pkfile, privkeyHex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pkFileName := testutil.TempFileWithContent(t, privkeyHex)
|
||||
defer os.Remove(pkFileName)
|
||||
|
||||
// compose a topic. We'll be doing quotes about Miguel de Cervantes
|
||||
var topic feed.Topic
|
||||
|
|
@ -76,7 +64,7 @@ func TestCLIFeedUpdate(t *testing.T) {
|
|||
|
||||
flags := []string{
|
||||
"--bzzapi", srv.URL,
|
||||
"--bzzaccount", pkfile.Name(),
|
||||
"--bzzaccount", pkFileName,
|
||||
"feed", "update",
|
||||
"--topic", topic.Hex(),
|
||||
"--name", name,
|
||||
|
|
@ -89,13 +77,10 @@ func TestCLIFeedUpdate(t *testing.T) {
|
|||
|
||||
// now try to get the update using the client
|
||||
client := swarm.NewClient(srv.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// build the same topic as before, this time
|
||||
// we use NewTopic to create a topic automatically.
|
||||
topic, err = feed.NewTopic(name, subject)
|
||||
topic, err := feed.NewTopic(name, subject)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -153,7 +138,7 @@ func TestCLIFeedUpdate(t *testing.T) {
|
|||
// test publishing a manifest
|
||||
flags = []string{
|
||||
"--bzzapi", srv.URL,
|
||||
"--bzzaccount", pkfile.Name(),
|
||||
"--bzzaccount", pkFileName,
|
||||
"feed", "create",
|
||||
"--topic", topic.Hex(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
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
|
||||
|
|
@ -58,7 +58,7 @@ func TestManifestChangeEncrypted(t *testing.T) {
|
|||
// Argument encrypt controls whether to use encryption or not.
|
||||
func testManifestChange(t *testing.T, encrypt bool) {
|
||||
t.Parallel()
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
|
||||
|
|
@ -430,7 +430,7 @@ func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) {
|
|||
|
||||
func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
|
||||
t.Parallel()
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/swarm"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
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")
|
||||
|
|
@ -58,7 +57,7 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
func serverFunc(api *api.API) testutil.TestServer {
|
||||
func serverFunc(api *api.API) swarmhttp.TestServer {
|
||||
return swarmhttp.NewServer(api, "")
|
||||
}
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
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/mattn/go-colorable"
|
||||
)
|
||||
|
|
@ -77,33 +78,22 @@ func testCLISwarmUp(toEncrypt bool, t *testing.T) {
|
|||
cluster := newTestCluster(t, 3)
|
||||
defer cluster.Shutdown()
|
||||
|
||||
// create a tmp file
|
||||
tmp, err := ioutil.TempFile("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tmp.Close()
|
||||
defer os.Remove(tmp.Name())
|
||||
tmpFileName := testutil.TempFileWithContent(t, data)
|
||||
defer os.Remove(tmpFileName)
|
||||
|
||||
// write data to file
|
||||
data := "notsorandomdata"
|
||||
_, err = io.WriteString(tmp, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hashRegexp := `[a-f\d]{64}`
|
||||
flags := []string{
|
||||
"--bzzapi", cluster.Nodes[0].URL,
|
||||
"up",
|
||||
tmp.Name()}
|
||||
tmpFileName}
|
||||
if toEncrypt {
|
||||
hashRegexp = `[a-f\d]{128}`
|
||||
flags = []string{
|
||||
"--bzzapi", cluster.Nodes[0].URL,
|
||||
"up",
|
||||
"--encrypt",
|
||||
tmp.Name()}
|
||||
tmpFileName}
|
||||
}
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
|
||||
|
|
@ -203,7 +193,6 @@ func testCLISwarmUpRecursive(toEncrypt bool, t *testing.T) {
|
|||
}
|
||||
defer os.RemoveAll(tmpUploadDir)
|
||||
// create tmp files
|
||||
data := "notsorandomdata"
|
||||
for _, path := range []string{"tmp1", "tmp2"} {
|
||||
if err := ioutil.WriteFile(filepath.Join(tmpUploadDir, path), bytes.NewBufferString(data).Bytes(), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -298,7 +287,7 @@ func TestCLISwarmUpDefaultPath(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()
|
||||
|
||||
tmp, err := ioutil.TempDir("", "swarm-defaultpath-test")
|
||||
|
|
|
|||
|
|
@ -295,7 +295,12 @@ var (
|
|||
CacheDatabaseFlag = cli.IntFlag{
|
||||
Name: "cache.database",
|
||||
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{
|
||||
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"
|
||||
|
||||
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) {
|
||||
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) {
|
||||
cfg.MinerNotify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",")
|
||||
|
|
@ -1393,12 +1401,16 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
|
|||
Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
|
||||
}
|
||||
cache := &core.CacheConfig{
|
||||
Disabled: ctx.GlobalString(GCModeFlag.Name) == "archive",
|
||||
TrieNodeLimit: eth.DefaultConfig.TrieCache,
|
||||
TrieTimeLimit: eth.DefaultConfig.TrieTimeout,
|
||||
Disabled: ctx.GlobalString(GCModeFlag.Name) == "archive",
|
||||
TrieCleanLimit: eth.DefaultConfig.TrieCleanCache,
|
||||
TrieDirtyLimit: eth.DefaultConfig.TrieDirtyCache,
|
||||
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) {
|
||||
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)}
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,10 @@ const (
|
|||
// CacheConfig contains the configuration values for the trie caching/pruning
|
||||
// that's resident in a blockchain.
|
||||
type CacheConfig struct {
|
||||
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
|
||||
TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
|
||||
Disabled bool // Whether to disable trie write caching (archive node)
|
||||
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
|
||||
}
|
||||
|
||||
// BlockChain represents the canonical chain given a database with a genesis
|
||||
|
|
@ -140,8 +141,9 @@ 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) {
|
||||
if cacheConfig == nil {
|
||||
cacheConfig = &CacheConfig{
|
||||
TrieNodeLimit: 256 * 1024 * 1024,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
}
|
||||
}
|
||||
bodyCache, _ := lru.New(bodyCacheLimit)
|
||||
|
|
@ -156,7 +158,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
|||
cacheConfig: cacheConfig,
|
||||
db: db,
|
||||
triegc: prque.New(nil),
|
||||
stateCache: state.NewDatabase(db),
|
||||
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
|
||||
quit: make(chan struct{}),
|
||||
shouldPreserve: shouldPreserve,
|
||||
bodyCache: bodyCache,
|
||||
|
|
@ -393,6 +395,11 @@ func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
|
|||
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.
|
||||
func (bc *BlockChain) Reset() error {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (bc *BlockChain) HasState(hash common.Hash) bool {
|
||||
_, 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 {
|
||||
return receipts.(types.Receipts)
|
||||
}
|
||||
|
||||
number := rawdb.ReadHeaderNumber(bc.db, hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
receipts := rawdb.ReadReceipts(bc.db, hash, *number)
|
||||
bc.receiptsCache.Add(hash, 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
|
||||
var (
|
||||
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 {
|
||||
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.
|
||||
func ReadReceipts(db DatabaseReader, hash common.Hash, number uint64) types.Receipts {
|
||||
// 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
|
||||
// concurrent use and retains cached trie nodes in memory. The pool is an optional
|
||||
// intermediate trie-node memory pool between the low level storage layer and the
|
||||
// high level trie abstraction.
|
||||
// concurrent use and retains a few recent expanded trie nodes in memory. To keep
|
||||
// more historical state in memory, use the NewDatabaseWithCache constructor.
|
||||
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)
|
||||
return &cachingDB{
|
||||
db: trie.NewDatabase(db),
|
||||
db: trie.NewDatabaseWithCache(db, cache),
|
||||
codeSizeCache: csc,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -444,16 +444,16 @@ func (api *PrivateDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Bloc
|
|||
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())
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diff, _ := trie.NewDifferenceIterator(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}))
|
||||
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
|
||||
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 {
|
||||
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
|
||||
origin := block.NumberU64()
|
||||
database := state.NewDatabase(api.eth.ChainDb())
|
||||
database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16)
|
||||
|
||||
for i := uint64(0); i < reexec; i++ {
|
||||
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,
|
||||
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)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -43,15 +43,16 @@ var DefaultConfig = Config{
|
|||
DatasetsInMem: 1,
|
||||
DatasetsOnDisk: 2,
|
||||
},
|
||||
NetworkId: 1,
|
||||
LightPeers: 100,
|
||||
DatabaseCache: 768,
|
||||
TrieCache: 256,
|
||||
TrieTimeout: 60 * time.Minute,
|
||||
MinerGasFloor: 8000000,
|
||||
MinerGasCeil: 8000000,
|
||||
MinerGasPrice: big.NewInt(params.GWei),
|
||||
MinerRecommit: 3 * time.Second,
|
||||
NetworkId: 1,
|
||||
LightPeers: 100,
|
||||
DatabaseCache: 512,
|
||||
TrieCleanCache: 256,
|
||||
TrieDirtyCache: 256,
|
||||
TrieTimeout: 60 * time.Minute,
|
||||
MinerGasFloor: 8000000,
|
||||
MinerGasCeil: 8000000,
|
||||
MinerGasPrice: big.NewInt(params.GWei),
|
||||
MinerRecommit: 3 * time.Second,
|
||||
|
||||
TxPool: core.DefaultTxPoolConfig,
|
||||
GPO: gasprice.Config{
|
||||
|
|
@ -94,7 +95,8 @@ type Config struct {
|
|||
SkipBcVersionCheck bool `toml:"-"`
|
||||
DatabaseHandles int `toml:"-"`
|
||||
DatabaseCache int
|
||||
TrieCache int
|
||||
TrieCleanCache int
|
||||
TrieDirtyCache int
|
||||
TrieTimeout time.Duration
|
||||
|
||||
// Mining-related options
|
||||
|
|
|
|||
|
|
@ -181,6 +181,9 @@ type BlockChain interface {
|
|||
// HasBlock verifies a block's presence in the local chain.
|
||||
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(common.Hash) *types.Block
|
||||
|
||||
|
|
@ -430,7 +433,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
|
|||
}
|
||||
height := latest.Number.Uint64()
|
||||
|
||||
origin, err := d.findAncestor(p, height)
|
||||
origin, err := d.findAncestor(p, latest)
|
||||
if err != nil {
|
||||
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
|
||||
// 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.
|
||||
// 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.
|
||||
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
|
||||
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 {
|
||||
ceil = d.blockchain.CurrentBlock().NumberU64()
|
||||
} 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)
|
||||
p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip)
|
||||
go p.peer.RequestHeadersByNumber(uint64(from), count, skip, false)
|
||||
|
||||
// Wait for the remote response to the head fetch
|
||||
number, hash := uint64(0), common.Hash{}
|
||||
|
|
@ -647,9 +695,10 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
|||
return 0, errEmptyHeaderSet
|
||||
}
|
||||
// Make sure the peer's reply conforms to the request
|
||||
for i := 0; i < len(headers); i++ {
|
||||
if number := headers[i].Number.Int64(); number != from+int64(i)*16 {
|
||||
p.log.Warn("Head headers broke chain ordering", "index", i, "requested", from+int64(i)*16, "received", number)
|
||||
for i, header := range headers {
|
||||
expectNumber := from + int64(i)*int64((skip+1))
|
||||
if number := header.Number.Int64(); number != expectNumber {
|
||||
p.log.Warn("Head headers broke chain ordering", "index", i, "requested", expectNumber, "received", number)
|
||||
return 0, errInvalidChain
|
||||
}
|
||||
}
|
||||
|
|
@ -657,20 +706,24 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
|||
finished = true
|
||||
for i := len(headers) - 1; i >= 0; i-- {
|
||||
// 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
|
||||
}
|
||||
// Otherwise check if we already know the header or not
|
||||
h := headers[i].Hash()
|
||||
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
|
||||
if number > height && i == limit-1 {
|
||||
p.log.Warn("Lied about chain head", "reported", height, "found", number)
|
||||
return 0, errStallingPeer
|
||||
}
|
||||
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 {
|
||||
number, hash = n, h
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -694,10 +747,12 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
|||
return number, nil
|
||||
}
|
||||
// Ancestor not found, we need to binary search over our chain
|
||||
start, end := uint64(0), head
|
||||
start, end := uint64(0), remoteHeight
|
||||
if floor > 0 {
|
||||
start = uint64(floor)
|
||||
}
|
||||
p.log.Trace("Binary searching for common ancestor", "start", start, "end", end)
|
||||
|
||||
for start+1 < end {
|
||||
// Split our chain interval in two, and request the hash to cross check
|
||||
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
|
||||
h := headers[0].Hash()
|
||||
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
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
|
@ -114,6 +115,15 @@ func (dl *downloadTester) HasBlock(hash common.Hash, number uint64) bool {
|
|||
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.
|
||||
func (dl *downloadTester) GetHeaderByHash(hash common.Hash) *types.Header {
|
||||
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.ownBlocks[block.Hash()] = block
|
||||
dl.ownReceipts[block.Hash()] = make(types.Receipts, 0)
|
||||
dl.stateDb.Put(block.Root().Bytes(), []byte{0x00})
|
||||
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
|
||||
// of the various chain components.
|
||||
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 checks if the local forked chain contains the correct
|
||||
// number of items of the various chain components.
|
||||
func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, lengths []int) {
|
||||
// Initialize the counters for the first fork
|
||||
headers, blocks, receipts := lengths[0], lengths[0], lengths[0]-fsMinFullBlocks
|
||||
// Mark this method as a helper to report errors at callsite, not in here
|
||||
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
|
||||
for _, length := range lengths[1:] {
|
||||
headers += length - common
|
||||
blocks += length - common
|
||||
receipts += length - common - fsMinFullBlocks
|
||||
receipts += length - common
|
||||
}
|
||||
switch tester.downloader.mode {
|
||||
case FullSync:
|
||||
receipts = 1
|
||||
case LightSync:
|
||||
if tester.downloader.mode == LightSync {
|
||||
blocks, receipts = 1, 1
|
||||
}
|
||||
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) {
|
||||
// Mark this method as a helper to report errors at callsite, not in here
|
||||
t.Helper()
|
||||
|
||||
p := d.Progress()
|
||||
p.KnownStates, p.PulledStates = 0, 0
|
||||
want.KnownStates, want.PulledStates = 0, 0
|
||||
|
|
@ -1479,3 +1492,78 @@ func (ftp *floodingTestPeer) RequestHeadersByNumber(from uint64, count, skip int
|
|||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header {
|
|||
}
|
||||
// Make sure no duplicate requests are executed
|
||||
if _, ok := q.blockTaskPool[hash]; ok {
|
||||
log.Warn("Header already scheduled for block fetch", "number", header.Number, "hash", hash)
|
||||
log.Warn("Header already scheduled for block fetch", "number", header.Number, "hash", hash)
|
||||
continue
|
||||
}
|
||||
if _, ok := q.receiptTaskPool[hash]; ok {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
SkipBcVersionCheck bool `toml:"-"`
|
||||
DatabaseHandles int `toml:"-"`
|
||||
DatabaseCache int
|
||||
TrieCache int
|
||||
TrieCleanCache int
|
||||
TrieDirtyCache int
|
||||
TrieTimeout time.Duration
|
||||
Etherbase common.Address `toml:",omitempty"`
|
||||
MinerNotify []string `toml:",omitempty"`
|
||||
|
|
@ -43,6 +44,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
GPO gasprice.Config
|
||||
EnablePreimageRecording bool
|
||||
DocRoot string `toml:"-"`
|
||||
EWASMInterpreter string
|
||||
EVMInterpreter string
|
||||
}
|
||||
var enc Config
|
||||
enc.Genesis = c.Genesis
|
||||
|
|
@ -54,7 +57,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.SkipBcVersionCheck = c.SkipBcVersionCheck
|
||||
enc.DatabaseHandles = c.DatabaseHandles
|
||||
enc.DatabaseCache = c.DatabaseCache
|
||||
enc.TrieCache = c.TrieCache
|
||||
enc.TrieCleanCache = c.TrieCleanCache
|
||||
enc.TrieDirtyCache = c.TrieDirtyCache
|
||||
enc.TrieTimeout = c.TrieTimeout
|
||||
enc.Etherbase = c.Etherbase
|
||||
enc.MinerNotify = c.MinerNotify
|
||||
|
|
@ -69,6 +73,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.GPO = c.GPO
|
||||
enc.EnablePreimageRecording = c.EnablePreimageRecording
|
||||
enc.DocRoot = c.DocRoot
|
||||
enc.EWASMInterpreter = c.EWASMInterpreter
|
||||
enc.EVMInterpreter = c.EVMInterpreter
|
||||
return &enc, nil
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +90,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
SkipBcVersionCheck *bool `toml:"-"`
|
||||
DatabaseHandles *int `toml:"-"`
|
||||
DatabaseCache *int
|
||||
TrieCache *int
|
||||
TrieCleanCache *int
|
||||
TrieDirtyCache *int
|
||||
TrieTimeout *time.Duration
|
||||
Etherbase *common.Address `toml:",omitempty"`
|
||||
MinerNotify []string `toml:",omitempty"`
|
||||
|
|
@ -99,6 +106,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
GPO *gasprice.Config
|
||||
EnablePreimageRecording *bool
|
||||
DocRoot *string `toml:"-"`
|
||||
EWASMInterpreter *string
|
||||
EVMInterpreter *string
|
||||
}
|
||||
var dec Config
|
||||
if err := unmarshal(&dec); err != nil {
|
||||
|
|
@ -131,8 +140,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.DatabaseCache != nil {
|
||||
c.DatabaseCache = *dec.DatabaseCache
|
||||
}
|
||||
if dec.TrieCache != nil {
|
||||
c.TrieCache = *dec.TrieCache
|
||||
if dec.TrieCleanCache != nil {
|
||||
c.TrieCleanCache = *dec.TrieCleanCache
|
||||
}
|
||||
if dec.TrieDirtyCache != nil {
|
||||
c.TrieDirtyCache = *dec.TrieDirtyCache
|
||||
}
|
||||
if dec.TrieTimeout != nil {
|
||||
c.TrieTimeout = *dec.TrieTimeout
|
||||
|
|
@ -176,5 +188,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.DocRoot != nil {
|
||||
c.DocRoot = *dec.DocRoot
|
||||
}
|
||||
if dec.EWASMInterpreter != nil {
|
||||
c.EWASMInterpreter = *dec.EWASMInterpreter
|
||||
}
|
||||
if dec.EVMInterpreter != nil {
|
||||
c.EVMInterpreter = *dec.EVMInterpreter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -653,12 +653,12 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
|||
trueHead = request.Block.ParentHash()
|
||||
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 {
|
||||
p.SetHead(trueHead, trueTD)
|
||||
|
||||
// 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.
|
||||
currentBlock := pm.blockchain.CurrentBlock()
|
||||
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
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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) {
|
||||
|
|
|
|||
|
|
@ -683,7 +683,7 @@ func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
|
|||
}
|
||||
|
||||
func encodePubkey64(pub *ecdsa.PublicKey) []byte {
|
||||
return crypto.FromECDSAPub(pub)[:1]
|
||||
return crypto.FromECDSAPub(pub)[1:]
|
||||
}
|
||||
|
||||
func decodePubkey64(b []byte) (*ecdsa.PublicKey, error) {
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64) *co
|
|||
diskdb: db,
|
||||
odr: odr,
|
||||
trieTable: trieTable,
|
||||
triedb: trie.NewDatabase(trieTable),
|
||||
triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down
|
||||
sectionSize: size,
|
||||
}
|
||||
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,
|
||||
odr: odr,
|
||||
trieTable: trieTable,
|
||||
triedb: trie.NewDatabase(trieTable),
|
||||
triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down
|
||||
parentSize: parentSize,
|
||||
size: size,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package simulations
|
|||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http/httptest"
|
||||
|
|
@ -28,13 +29,26 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"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/simulations/adapters"
|
||||
"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
|
||||
// and APIs which are useful for testing nodes in a simulation network
|
||||
type testService struct {
|
||||
|
|
@ -584,9 +598,26 @@ func TestHTTPNodeRPC(t *testing.T) {
|
|||
// TestHTTPSnapshot tests creating and loading network snapshots
|
||||
func TestHTTPSnapshot(t *testing.T) {
|
||||
// start the server
|
||||
_, s := testHTTPServer(t)
|
||||
network, s := testHTTPServer(t)
|
||||
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
|
||||
client := NewClient(s.URL)
|
||||
nodeCount := 2
|
||||
|
|
@ -620,7 +651,7 @@ func TestHTTPSnapshot(t *testing.T) {
|
|||
}
|
||||
states[i] = state
|
||||
}
|
||||
|
||||
<-eventsDone
|
||||
// create a snapshot
|
||||
snap, err := client.CreateSnapshot()
|
||||
if err != nil {
|
||||
|
|
@ -634,9 +665,23 @@ func TestHTTPSnapshot(t *testing.T) {
|
|||
}
|
||||
|
||||
// create another network
|
||||
_, s = testHTTPServer(t)
|
||||
network2, s := testHTTPServer(t)
|
||||
defer s.Close()
|
||||
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
|
||||
events := make(chan *Event, 100)
|
||||
|
|
@ -651,6 +696,7 @@ func TestHTTPSnapshot(t *testing.T) {
|
|||
if err := client.LoadSnapshot(snap); err != nil {
|
||||
t.Fatalf("error loading snapshot: %s", err)
|
||||
}
|
||||
<-eventsDone
|
||||
|
||||
// check the nodes and connection exists
|
||||
net, err := client.GetNetwork()
|
||||
|
|
@ -676,6 +722,9 @@ func TestHTTPSnapshot(t *testing.T) {
|
|||
if conn.Other.String() != nodes[1].ID {
|
||||
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
|
||||
for i, node := range nodes {
|
||||
|
|
|
|||
|
|
@ -644,11 +644,18 @@ type NodeSnapshot struct {
|
|||
|
||||
// Snapshot creates a network snapshot
|
||||
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()
|
||||
defer net.lock.Unlock()
|
||||
snap := &Snapshot{
|
||||
Nodes: make([]NodeSnapshot, len(net.Nodes)),
|
||||
Conns: make([]Conn, len(net.Conns)),
|
||||
}
|
||||
for i, node := range net.Nodes {
|
||||
snap.Nodes[i] = NodeSnapshot{Node: *node}
|
||||
|
|
@ -660,9 +667,40 @@ func (net *Network) Snapshot() (*Snapshot, error) {
|
|||
return nil, err
|
||||
}
|
||||
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 i, conn := range net.Conns {
|
||||
snap.Conns[i] = *conn
|
||||
for _, conn := range net.Conns {
|
||||
if conn.Up {
|
||||
snap.Conns = append(snap.Conns, *conn)
|
||||
}
|
||||
}
|
||||
return snap, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ var (
|
|||
// MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network.
|
||||
MainnetTrustedCheckpoint = &TrustedCheckpoint{
|
||||
Name: "mainnet",
|
||||
SectionIndex: 195,
|
||||
SectionHead: common.HexToHash("0x1cdd2a84cf6c1261ffccc88f6bcefb513abd7934a96c1e909fbf74767560f16b"),
|
||||
CHTRoot: common.HexToHash("0xe453333c20391d16b91b6fe11c104704f62c8dba15f69db73b4cdf7e100105eb"),
|
||||
BloomRoot: common.HexToHash("0x47f30069473072e00d2cdca146dce40f0aad243dfc8221bf810822c091674efe"),
|
||||
SectionIndex: 203,
|
||||
SectionHead: common.HexToHash("0xc9e05fc67c6a9815adc8072eb18805b53da53a9a6a273e05541e1b7542cf937a"),
|
||||
CHTRoot: common.HexToHash("0xb85f42447d59f7c3e6679b9a37ed983593fd52efd6251b883592662e95769d5b"),
|
||||
BloomRoot: common.HexToHash("0xf93d50cb4c49b403c6fd33cd60896d3b36184275be0a51bae4df5e8844ac624c"),
|
||||
}
|
||||
|
||||
// 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 = &TrustedCheckpoint{
|
||||
Name: "testnet",
|
||||
SectionIndex: 126,
|
||||
SectionHead: common.HexToHash("0x48f7dd4c9c60be04bf15fd4d0bcac46ddd8caf6b01d6fb8f8e1f7955cdd1337a"),
|
||||
CHTRoot: common.HexToHash("0x6e54cb80a1884881ea1a114243af9012c95e0296b47f103b5ab124313968508e"),
|
||||
BloomRoot: common.HexToHash("0xb55accf6dce6455b47db8510d15eff38d0ed7378829f3036d26b48e7d15da3f6"),
|
||||
SectionIndex: 134,
|
||||
SectionHead: common.HexToHash("0x17053ecbe045bebefaa01e7716cc85a4e22647e181416cc1098ccbb73a088931"),
|
||||
CHTRoot: common.HexToHash("0x4d2b86422e46ed76f0e3f50f06632c409f809c8375e53c8bc0f782bcb93dd49a"),
|
||||
BloomRoot: common.HexToHash("0xccba62232ee56c2967afc58f136a47ba7dc545ae586e6be666430d94516306c7"),
|
||||
}
|
||||
|
||||
// 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 = &TrustedCheckpoint{
|
||||
Name: "rinkeby",
|
||||
SectionIndex: 93,
|
||||
SectionHead: common.HexToHash("0xdefb94aa217ab38f2919f7318d1d5476bd2aabf1ec9148047fe03e555615e0b4"),
|
||||
CHTRoot: common.HexToHash("0x52c98c2fe508a8332c27dc10538f3fead43306e2b22b597587763c2fe6586da6"),
|
||||
BloomRoot: common.HexToHash("0x93d83be0c1b12f732b1a027ecdfb16f39b0d020b8c10bfb90e76f3b01adfc5b6"),
|
||||
SectionIndex: 100,
|
||||
SectionHead: common.HexToHash("0xf18f9b43e16f37b12e68818536ffe455ff18d676274ffdd856a8520ed61bb514"),
|
||||
CHTRoot: common.HexToHash("0x473f5d603b1fedad75d97fd58692130b9ac9ade1aca01eb9363d79bd1c43c791"),
|
||||
BloomRoot: common.HexToHash("0xa39ced3ddbb87e909c7531df2afb6414bea9c9a60ab94da9c6b467535f05326e"),
|
||||
}
|
||||
|
||||
// AllEthashProtocolChanges contains every protocol change (EIPs) introduced
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import (
|
|||
const (
|
||||
VersionMajor = 1 // Major 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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
"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:
|
||||
//
|
||||
// eth_getBlockByNumber("latest", {})
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ An example method:
|
|||
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
|
||||
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.
|
||||
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"
|
||||
"github.com/ethereum/go-ethereum/swarm/multihash"
|
||||
"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, "")
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +48,7 @@ func TestClientUploadDownloadRawEncrypted(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()
|
||||
|
||||
client := NewClient(srv.URL)
|
||||
|
|
@ -90,7 +89,7 @@ func TestClientUploadDownloadFilesEncrypted(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()
|
||||
|
||||
client := NewClient(srv.URL)
|
||||
|
|
@ -188,7 +187,7 @@ func newTestDirectory(t *testing.T) string {
|
|||
// TestClientUploadDownloadDirectory tests uploading and downloading a
|
||||
// directory of files to a swarm manifest
|
||||
func TestClientUploadDownloadDirectory(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
dir := newTestDirectory(t)
|
||||
|
|
@ -254,7 +253,7 @@ func TestClientFileListEncrypted(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()
|
||||
|
||||
dir := newTestDirectory(t)
|
||||
|
|
@ -312,7 +311,7 @@ func testClientFileList(toEncrypt bool, t *testing.T) {
|
|||
// TestClientMultipartUpload tests uploading files to swarm using a multipart
|
||||
// upload
|
||||
func TestClientMultipartUpload(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
// define an uploader which uploads testDirFiles with some data
|
||||
|
|
@ -378,7 +377,7 @@ func TestClientCreateFeedMultihash(t *testing.T) {
|
|||
|
||||
signer, _ := newTestSigner()
|
||||
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||
client := NewClient(srv.URL)
|
||||
defer srv.Close()
|
||||
|
||||
|
|
@ -440,7 +439,7 @@ func TestClientCreateUpdateFeed(t *testing.T) {
|
|||
|
||||
signer, _ := newTestSigner()
|
||||
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||
client := NewClient(srv.URL)
|
||||
defer srv.Close()
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func InitLoggingResponseWriter(h http.Handler) http.Handler {
|
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writer := newLoggingResponseWriter(w)
|
||||
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"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
var resp *http.Response
|
||||
|
|
@ -55,7 +53,7 @@ func TestError(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test404Page(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
var resp *http.Response
|
||||
|
|
@ -81,7 +79,7 @@ func Test404Page(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test500Page(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
var resp *http.Response
|
||||
|
|
@ -106,7 +104,7 @@ func Test500Page(t *testing.T) {
|
|||
}
|
||||
}
|
||||
func Test500PageWith0xHashPrefix(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
var resp *http.Response
|
||||
|
|
@ -136,7 +134,7 @@ func Test500PageWith0xHashPrefix(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestJsonResponse(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
var resp *http.Response
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
|
|
@ -58,7 +57,7 @@ func init() {
|
|||
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, "")
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +78,7 @@ func TestBzzFeedMultihash(t *testing.T) {
|
|||
|
||||
signer, _ := newTestSigner()
|
||||
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
// 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
|
||||
func TestBzzFeed(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
signer, _ := newTestSigner()
|
||||
|
||||
defer srv.Close()
|
||||
|
||||
// data of update 1
|
||||
update1Data := make([]byte, 666)
|
||||
update1Data := testutil.RandomBytes(1, 666)
|
||||
update1Timestamp := srv.CurrentTime
|
||||
_, err := rand.Read(update1Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
//data for update 2
|
||||
update2Data := []byte("foo")
|
||||
|
||||
topic, _ := feed.NewTopic("foo.eth", nil)
|
||||
updateRequest := feed.NewFirstRequest(topic)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateRequest.SetData(update1Data)
|
||||
|
||||
if err := updateRequest.Sign(signer); err != nil {
|
||||
|
|
@ -450,7 +442,7 @@ func testBzzGetPath(encrypted bool, t *testing.T) {
|
|||
|
||||
addr := [3]storage.Address{}
|
||||
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
for i, mf := range testmanifest {
|
||||
|
|
@ -688,7 +680,7 @@ func TestBzzTar(t *testing.T) {
|
|||
}
|
||||
|
||||
func testBzzTar(encrypted bool, t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
fileNames := []string{"tmp1.txt", "tmp2.lock", "tmp3.rtf"}
|
||||
fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"}
|
||||
|
|
@ -823,7 +815,7 @@ func TestBzzRootRedirectEncrypted(t *testing.T) {
|
|||
}
|
||||
|
||||
func testBzzRootRedirect(toEncrypt bool, t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
// 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) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
databytes := "bar"
|
||||
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) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
|
|
@ -1020,7 +1012,7 @@ func TestGet(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestModify(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
swarmClient := swarm.NewClient(srv.URL)
|
||||
|
|
@ -1121,7 +1113,7 @@ func TestMultiPartUpload(t *testing.T) {
|
|||
// POST /bzz:/ Content-Type: multipart/form-data
|
||||
verbose := false
|
||||
// Setup Swarm
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
|
||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
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
|
||||
func TestBzzGetFileWithResolver(t *testing.T) {
|
||||
resolver := newTestResolveValidator("")
|
||||
srv := testutil.NewTestSwarmServer(t, serverFunc, resolver)
|
||||
srv := NewTestSwarmServer(t, serverFunc, resolver)
|
||||
defer srv.Close()
|
||||
fileNames := []string{"dir1/tmp1.txt", "dir2/tmp2.lock", "dir3/tmp3.rtf"}
|
||||
fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
// 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 testutil
|
||||
package http
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
|
@ -18,10 +18,8 @@ package bmt
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
|
@ -29,6 +27,7 @@ import (
|
|||
"time"
|
||||
|
||||
"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)
|
||||
|
|
@ -116,14 +115,11 @@ func TestRefHasher(t *testing.T) {
|
|||
})
|
||||
|
||||
// run the tests
|
||||
for _, x := range tests {
|
||||
for i, x := range tests {
|
||||
for segmentCount := x.from; segmentCount <= x.to; segmentCount++ {
|
||||
for length := 1; length <= segmentCount*32; length++ {
|
||||
t.Run(fmt.Sprintf("%d_segments_%d_bytes", segmentCount, length), func(t *testing.T) {
|
||||
data := make([]byte, length)
|
||||
if _, err := io.ReadFull(crand.Reader, data); err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data := testutil.RandomBytes(i, length)
|
||||
expected := x.expected(data)
|
||||
actual := NewRefHasher(sha3.NewKeccak256, segmentCount).Hash(data)
|
||||
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
|
||||
func TestSyncHasherCorrectness(t *testing.T) {
|
||||
data := newData(BufferSize)
|
||||
data := testutil.RandomBytes(1, BufferSize)
|
||||
hasher := sha3.NewKeccak256
|
||||
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
|
||||
func TestAsyncCorrectness(t *testing.T) {
|
||||
data := newData(BufferSize)
|
||||
data := testutil.RandomBytes(1, BufferSize)
|
||||
hasher := sha3.NewKeccak256
|
||||
size := hasher().Size()
|
||||
whs := []whenHash{first, last, random}
|
||||
|
|
@ -236,7 +232,7 @@ func testHasherReuse(poolsize int, t *testing.T) {
|
|||
bmt := New(pool)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
data := newData(BufferSize)
|
||||
data := testutil.RandomBytes(1, BufferSize)
|
||||
n := rand.Intn(bmt.Size())
|
||||
err := testHasherCorrectness(bmt, hasher, data, n, segmentCount)
|
||||
if err != nil {
|
||||
|
|
@ -256,7 +252,7 @@ func TestBMTConcurrentUse(t *testing.T) {
|
|||
for i := 0; i < cycles; i++ {
|
||||
go func() {
|
||||
bmt := New(pool)
|
||||
data := newData(BufferSize)
|
||||
data := testutil.RandomBytes(1, BufferSize)
|
||||
n := rand.Intn(bmt.Size())
|
||||
errc <- testHasherCorrectness(bmt, hasher, data, n, 128)
|
||||
}()
|
||||
|
|
@ -290,7 +286,7 @@ func TestBMTWriterBuffers(t *testing.T) {
|
|||
defer pool.Drain(0)
|
||||
n := count * 32
|
||||
bmt := New(pool)
|
||||
data := newData(n)
|
||||
data := testutil.RandomBytes(1, n)
|
||||
rbmt := NewRefHasher(hasher, count)
|
||||
refHash := rbmt.Hash(data)
|
||||
expHash := syncHash(bmt, nil, data)
|
||||
|
|
@ -413,7 +409,7 @@ func BenchmarkPool(t *testing.B) {
|
|||
|
||||
// benchmarks simple sha3 hash on chunks
|
||||
func benchmarkSHA3(t *testing.B, n int) {
|
||||
data := newData(n)
|
||||
data := testutil.RandomBytes(1, n)
|
||||
hasher := sha3.NewKeccak256
|
||||
h := hasher()
|
||||
|
||||
|
|
@ -432,7 +428,7 @@ func benchmarkSHA3(t *testing.B, n int) {
|
|||
func benchmarkBMTBaseline(t *testing.B, n int) {
|
||||
hasher := sha3.NewKeccak256
|
||||
hashSize := hasher().Size()
|
||||
data := newData(hashSize)
|
||||
data := testutil.RandomBytes(1, hashSize)
|
||||
|
||||
t.ReportAllocs()
|
||||
t.ResetTimer()
|
||||
|
|
@ -456,7 +452,7 @@ func benchmarkBMTBaseline(t *testing.B, n int) {
|
|||
|
||||
// benchmarks BMT Hasher
|
||||
func benchmarkBMT(t *testing.B, n int) {
|
||||
data := newData(n)
|
||||
data := testutil.RandomBytes(1, n)
|
||||
hasher := sha3.NewKeccak256
|
||||
pool := NewTreePool(hasher, segmentCount, PoolSize)
|
||||
bmt := New(pool)
|
||||
|
|
@ -470,12 +466,12 @@ func benchmarkBMT(t *testing.B, n int) {
|
|||
|
||||
// benchmarks BMT hasher with asynchronous concurrent segment/section writes
|
||||
func benchmarkBMTAsync(t *testing.B, n int, wh whenHash, double bool) {
|
||||
data := newData(n)
|
||||
data := testutil.RandomBytes(1, n)
|
||||
hasher := sha3.NewKeccak256
|
||||
pool := NewTreePool(hasher, segmentCount, PoolSize)
|
||||
bmt := New(pool).NewAsyncWriter(double)
|
||||
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]
|
||||
})
|
||||
|
||||
|
|
@ -488,7 +484,7 @@ func benchmarkBMTAsync(t *testing.B, n int, wh whenHash, double bool) {
|
|||
|
||||
// benchmarks 100 concurrent bmt hashes with pool capacity
|
||||
func benchmarkPool(t *testing.B, poolsize, n int) {
|
||||
data := newData(n)
|
||||
data := testutil.RandomBytes(1, n)
|
||||
hasher := sha3.NewKeccak256
|
||||
pool := NewTreePool(hasher, segmentCount, poolsize)
|
||||
cycles := 100
|
||||
|
|
@ -511,7 +507,7 @@ func benchmarkPool(t *testing.B, poolsize, n int) {
|
|||
|
||||
// benchmarks the reference hasher
|
||||
func benchmarkRefHasher(t *testing.B, n int) {
|
||||
data := newData(n)
|
||||
data := testutil.RandomBytes(1, n)
|
||||
hasher := sha3.NewKeccak256
|
||||
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
|
||||
func syncHash(h *Hasher, span, data []byte) []byte {
|
||||
h.ResetWithLength(span)
|
||||
|
|
@ -553,7 +540,7 @@ func splitAndShuffle(secsize int, data []byte) (idxs []int, segments [][]byte) {
|
|||
section := data[i*secsize : end]
|
||||
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]
|
||||
})
|
||||
return idxs, segments
|
||||
|
|
@ -594,29 +581,3 @@ func asyncHash(bmt SectionWriter, span []byte, l int, wh whenHash, idxs []int, s
|
|||
}
|
||||
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 (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
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 {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
|
|
@ -328,22 +321,22 @@ func (ta *testAPI) mountListAndUnmount(t *testing.T, toEncrypt bool) {
|
|||
dat.testMountDir = filepath.Join(dat.testDir, "testMountDir")
|
||||
dat.files = make(map[string]fileInfo)
|
||||
|
||||
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["2.txt"] = fileInfo{0711, 333, 444, getRandomBytes(10)}
|
||||
dat.files["3.txt"] = fileInfo{0622, 333, 444, getRandomBytes(100)}
|
||||
dat.files["4.txt"] = fileInfo{0533, 333, 444, getRandomBytes(1024)}
|
||||
dat.files["5.txt"] = fileInfo{0544, 333, 444, getRandomBytes(10)}
|
||||
dat.files["6.txt"] = fileInfo{0555, 333, 444, getRandomBytes(10)}
|
||||
dat.files["7.txt"] = fileInfo{0666, 333, 444, getRandomBytes(10)}
|
||||
dat.files["8.txt"] = fileInfo{0777, 333, 333, getRandomBytes(10)}
|
||||
dat.files["11.txt"] = fileInfo{0777, 333, 444, getRandomBytes(10)}
|
||||
dat.files["111.txt"] = fileInfo{0777, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/2.txt"] = fileInfo{0777, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/2/2.txt"] = fileInfo{0777, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/2./2.txt"] = fileInfo{0777, 444, 444, getRandomBytes(10)}
|
||||
dat.files["twice/2.txt"] = fileInfo{0777, 444, 333, getRandomBytes(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/six"] = fileInfo{0777, 333, 444, getRandomBytes(10)}
|
||||
dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)}
|
||||
dat.files["2.txt"] = fileInfo{0711, 333, 444, testutil.RandomBytes(2, 10)}
|
||||
dat.files["3.txt"] = fileInfo{0622, 333, 444, testutil.RandomBytes(3, 100)}
|
||||
dat.files["4.txt"] = fileInfo{0533, 333, 444, testutil.RandomBytes(4, 1024)}
|
||||
dat.files["5.txt"] = fileInfo{0544, 333, 444, testutil.RandomBytes(5, 10)}
|
||||
dat.files["6.txt"] = fileInfo{0555, 333, 444, testutil.RandomBytes(6, 10)}
|
||||
dat.files["7.txt"] = fileInfo{0666, 333, 444, testutil.RandomBytes(7, 10)}
|
||||
dat.files["8.txt"] = fileInfo{0777, 333, 333, testutil.RandomBytes(8, 10)}
|
||||
dat.files["11.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(9, 10)}
|
||||
dat.files["111.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(10, 10)}
|
||||
dat.files["two/2.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(11, 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, testutil.RandomBytes(13, 10)}
|
||||
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, testutil.RandomBytes(15, 10240)}
|
||||
dat.files["one/two/three/four/five/six/six"] = fileInfo{0777, 333, 444, testutil.RandomBytes(16, 10)}
|
||||
|
||||
dat, err = ta.uploadAndMount(dat, t)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "max-mount1")
|
||||
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)
|
||||
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.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)
|
||||
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.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)
|
||||
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.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)
|
||||
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.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)
|
||||
if err != nil {
|
||||
|
|
@ -436,7 +429,7 @@ func (ta *testAPI) runMaxMounts(t *testing.T, toEncrypt bool) {
|
|||
if err != nil {
|
||||
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")
|
||||
err = os.MkdirAll(testMountDir6, 0777)
|
||||
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.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)
|
||||
if err != nil {
|
||||
|
|
@ -494,7 +487,7 @@ func (ta *testAPI) remount(t *testing.T, toEncrypt bool) {
|
|||
}
|
||||
|
||||
// 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")
|
||||
if err3 != nil {
|
||||
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.testMountDir = filepath.Join(dat.testDir, "ex-mount1")
|
||||
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)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "ex-mount1")
|
||||
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)
|
||||
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
|
||||
//but let's defer too in case of errors
|
||||
defer d.Close()
|
||||
_, err = d.Write(getRandomBytes(10))
|
||||
_, err = d.Write(testutil.RandomBytes(1, 10))
|
||||
if err != nil {
|
||||
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.testMountDir = filepath.Join(dat.testDir, "seek-mount")
|
||||
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)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "create-mount")
|
||||
dat.files = make(map[string]fileInfo)
|
||||
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["six.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, testutil.RandomBytes(2, 10)}
|
||||
dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
|
||||
|
||||
dat, err = ta.uploadAndMount(dat, t)
|
||||
if err != nil {
|
||||
|
|
@ -751,11 +744,7 @@ func (ta *testAPI) createNewFile(t *testing.T, toEncrypt bool) {
|
|||
}
|
||||
defer d.Close()
|
||||
log.Debug("Opened file")
|
||||
contents := make([]byte, 11)
|
||||
_, err = rand.Read(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not rand read contents %v", err)
|
||||
}
|
||||
contents := testutil.RandomBytes(1, 11)
|
||||
log.Debug("content read")
|
||||
_, err = d.Write(contents)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "createinsidedir-mount")
|
||||
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)
|
||||
if err != nil {
|
||||
|
|
@ -832,11 +821,7 @@ func (ta *testAPI) createNewFileInsideDirectory(t *testing.T, toEncrypt bool) {
|
|||
}
|
||||
defer d.Close()
|
||||
log.Debug("File opened")
|
||||
contents := make([]byte, 11)
|
||||
_, err = rand.Read(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Error filling random bytes into byte array %v", err)
|
||||
}
|
||||
contents := testutil.RandomBytes(1, 11)
|
||||
log.Debug("Content read")
|
||||
_, err = d.Write(contents)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "createinsidenewdir-mount")
|
||||
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)
|
||||
if err != nil {
|
||||
|
|
@ -916,11 +901,7 @@ func (ta *testAPI) createNewFileInsideNewDirectory(t *testing.T, toEncrypt bool)
|
|||
}
|
||||
defer d.Close()
|
||||
log.Debug("File opened")
|
||||
contents := make([]byte, 11)
|
||||
_, err = rand.Read(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing random bytes to byte array: %v", err)
|
||||
}
|
||||
contents := testutil.RandomBytes(1, 11)
|
||||
log.Debug("content read")
|
||||
_, err = d.Write(contents)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "remove-mount")
|
||||
dat.files = make(map[string]fileInfo)
|
||||
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["six.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, testutil.RandomBytes(2, 10)}
|
||||
dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
|
||||
|
||||
dat, err = ta.uploadAndMount(dat, t)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "remove-mount")
|
||||
dat.files = make(map[string]fileInfo)
|
||||
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["one/five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["one/six.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, testutil.RandomBytes(2, 10)}
|
||||
dat.files["one/six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
|
||||
|
||||
dat, err = ta.uploadAndMount(dat, t)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "removenew-mount")
|
||||
dat.files = make(map[string]fileInfo)
|
||||
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["six.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, testutil.RandomBytes(2, 10)}
|
||||
dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
|
||||
|
||||
dat, err = ta.uploadAndMount(dat, t)
|
||||
if err != nil {
|
||||
|
|
@ -1127,11 +1108,7 @@ func (ta *testAPI) removeNewlyAddedFile(t *testing.T, toEncrypt bool) {
|
|||
}
|
||||
defer d.Close()
|
||||
log.Debug("file opened")
|
||||
contents := make([]byte, 11)
|
||||
_, err = rand.Read(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing random bytes to byte array: %v", err)
|
||||
}
|
||||
contents := testutil.RandomBytes(1, 11)
|
||||
log.Debug("content read")
|
||||
_, err = d.Write(contents)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "modifyfile-mount")
|
||||
dat.files = make(map[string]fileInfo)
|
||||
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["six.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, testutil.RandomBytes(2, 10)}
|
||||
dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
|
||||
|
||||
dat, err = ta.uploadAndMount(dat, t)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "rmdir-mount")
|
||||
dat.files = make(map[string]fileInfo)
|
||||
dat.files["1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["six.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, testutil.RandomBytes(2, 10)}
|
||||
dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
|
||||
|
||||
dat, err = ta.uploadAndMount(dat, t)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "rmdir-mount")
|
||||
dat.files = make(map[string]fileInfo)
|
||||
dat.files["one/1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/five.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/six.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, testutil.RandomBytes(2, 10)}
|
||||
dat.files["two/six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)}
|
||||
|
||||
dat, err = ta.uploadAndMount(dat, t)
|
||||
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.testMountDir = filepath.Join(dat.testDir, "rmsubdir-mount")
|
||||
dat.files = make(map[string]fileInfo)
|
||||
dat.files["one/1.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/three/2.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/three/3.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/four/5.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/four/6.txt"] = fileInfo{0700, 333, 444, getRandomBytes(10)}
|
||||
dat.files["two/four/six/7.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, testutil.RandomBytes(2, 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, testutil.RandomBytes(4, 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, testutil.RandomBytes(6, 10)}
|
||||
|
||||
dat, err = ta.uploadAndMount(dat, t)
|
||||
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.files = make(map[string]fileInfo)
|
||||
|
||||
line1 := make([]byte, 10)
|
||||
_, err = rand.Read(line1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing random bytes to byte array: %v", err)
|
||||
}
|
||||
line1 := testutil.RandomBytes(1, 10)
|
||||
|
||||
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()
|
||||
log.Debug("file opened")
|
||||
line2 := make([]byte, 5)
|
||||
_, err = rand.Read(line2)
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing random bytes to byte array: %v", err)
|
||||
}
|
||||
line2 := testutil.RandomBytes(1, 5)
|
||||
log.Debug("line read")
|
||||
_, err = fd.Seek(int64(len(line1)), 0)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -20,16 +20,18 @@ import (
|
|||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"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.
|
||||
type PeerEvent struct {
|
||||
// NodeID is the ID of node that the event is caught on.
|
||||
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 *p2p.PeerEvent
|
||||
Event *simulations.Event
|
||||
// Error is the error that may have happened during event watching.
|
||||
Error error
|
||||
}
|
||||
|
|
@ -37,9 +39,13 @@ type PeerEvent struct {
|
|||
// PeerEventsFilter defines a filter on PeerEvents to exclude messages with
|
||||
// defined properties. Use PeerEventsFilter methods to set required options.
|
||||
type PeerEventsFilter struct {
|
||||
t *p2p.PeerEventType
|
||||
protocol *string
|
||||
msgCode *uint64
|
||||
eventType simulations.EventType
|
||||
|
||||
connUp *bool
|
||||
|
||||
msgReceive *bool
|
||||
protocol *string
|
||||
msgCode *uint64
|
||||
}
|
||||
|
||||
// NewPeerEventsFilter returns a new PeerEventsFilter instance.
|
||||
|
|
@ -47,20 +53,48 @@ func NewPeerEventsFilter() *PeerEventsFilter {
|
|||
return &PeerEventsFilter{}
|
||||
}
|
||||
|
||||
// Type sets the filter to only one peer event type.
|
||||
func (f *PeerEventsFilter) Type(t p2p.PeerEventType) *PeerEventsFilter {
|
||||
f.t = &t
|
||||
// Connect sets the filter to events when two nodes connect.
|
||||
func (f *PeerEventsFilter) Connect() *PeerEventsFilter {
|
||||
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
|
||||
}
|
||||
|
||||
// Protocol sets the filter to only one message protocol.
|
||||
func (f *PeerEventsFilter) Protocol(p string) *PeerEventsFilter {
|
||||
f.eventType = simulations.EventTypeMsg
|
||||
f.protocol = &p
|
||||
return f
|
||||
}
|
||||
|
||||
// MsgCode sets the filter to only one msg code.
|
||||
func (f *PeerEventsFilter) MsgCode(c uint64) *PeerEventsFilter {
|
||||
f.eventType = simulations.EventTypeMsg
|
||||
f.msgCode = &c
|
||||
return f
|
||||
}
|
||||
|
|
@ -80,19 +114,8 @@ func (s *Simulation) PeerEvents(ctx context.Context, ids []enode.ID, filters ...
|
|||
go func(id enode.ID) {
|
||||
defer s.shutdownWG.Done()
|
||||
|
||||
client, err := s.Net.GetNode(id).Client()
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
events := make(chan *simulations.Event)
|
||||
sub := s.Net.Events().Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
subsWG.Done()
|
||||
|
|
@ -110,28 +133,55 @@ func (s *Simulation) PeerEvents(ctx context.Context, ids []enode.ID, filters ...
|
|||
case <-s.Done():
|
||||
return
|
||||
case e := <-events:
|
||||
// ignore control events
|
||||
if e.Control {
|
||||
continue
|
||||
}
|
||||
match := len(filters) == 0 // if there are no filters match all events
|
||||
for _, f := range filters {
|
||||
if f.t != nil && *f.t != e.Type {
|
||||
continue
|
||||
if f.eventType == simulations.EventTypeConn && e.Conn != nil {
|
||||
if *f.connUp != e.Conn.Up {
|
||||
continue
|
||||
}
|
||||
// all connection filter parameters matched, break the loop
|
||||
match = true
|
||||
break
|
||||
}
|
||||
if f.protocol != nil && *f.protocol != e.Protocol {
|
||||
continue
|
||||
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
|
||||
}
|
||||
if f.msgCode != nil && e.MsgCode != nil && *f.msgCode != *e.MsgCode {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
// all filter parameters matched, break the loop
|
||||
match = true
|
||||
break
|
||||
}
|
||||
if match {
|
||||
select {
|
||||
case eventC <- PeerEvent{NodeID: id, Event: e}:
|
||||
case eventC <- PeerEvent{NodeID: id, PeerID: peerID, Event: e}:
|
||||
case <-ctx.Done():
|
||||
if err := ctx.Err(); err != nil {
|
||||
select {
|
||||
case eventC <- PeerEvent{NodeID: id, Error: err}:
|
||||
case eventC <- PeerEvent{NodeID: id, PeerID: peerID, Error: err}:
|
||||
case <-s.Done():
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"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/swarm/network"
|
||||
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||
|
|
@ -87,7 +86,7 @@ func ExampleSimulation_PeerEvents() {
|
|||
log.Error("peer event", "err", e.Error)
|
||||
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(
|
||||
context.Background(),
|
||||
sim.NodeIDs(),
|
||||
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||
simulation.NewPeerEventsFilter().Drop(),
|
||||
)
|
||||
|
||||
go func() {
|
||||
|
|
@ -109,7 +108,7 @@ func ExampleSimulation_PeerEvents_disconnections() {
|
|||
log.Error("peer drop", "err", d.Error)
|
||||
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(),
|
||||
sim.NodeIDs(),
|
||||
// Watch when bzz messages 1 and 4 are received.
|
||||
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeMsgRecv).Protocol("bzz").MsgCode(1),
|
||||
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeMsgRecv).Protocol("bzz").MsgCode(4),
|
||||
simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("bzz").MsgCode(1),
|
||||
simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("bzz").MsgCode(4),
|
||||
)
|
||||
|
||||
go func() {
|
||||
|
|
@ -134,7 +133,7 @@ func ExampleSimulation_PeerEvents_multipleFilters() {
|
|||
log.Error("bzz message", "err", m.Error)
|
||||
continue
|
||||
}
|
||||
log.Info("bzz message", "node", m.NodeID, "peer", m.Event.Peer)
|
||||
log.Info("bzz message", "node", m.NodeID, "peer", m.PeerID)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,11 +85,12 @@ func getDbStore(nodeID string) (*state.DBStore, error) {
|
|||
}
|
||||
|
||||
var (
|
||||
nodeCount = flag.Int("nodes", 10, "number of nodes to create (default 10)")
|
||||
initCount = flag.Int("conns", 1, "number of originally connected peers (default 1)")
|
||||
snapshotFile = flag.String("snapshot", "", "create snapshot")
|
||||
loglevel = flag.Int("loglevel", 3, "verbosity of logs")
|
||||
rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs")
|
||||
nodeCount = flag.Int("nodes", 10, "number of nodes to create (default 10)")
|
||||
initCount = flag.Int("conns", 1, "number of originally connected peers (default 1)")
|
||||
snapshotFile = flag.String("snapshot", "", "path to create snapshot file in")
|
||||
loglevel = flag.Int("loglevel", 3, "verbosity of 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() {
|
||||
|
|
@ -306,7 +307,25 @@ func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simul
|
|||
}
|
||||
|
||||
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 {
|
||||
return nil, errors.New("no shapshot dude")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package stream
|
|||
|
||||
import (
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
|
@ -40,6 +39,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/swarm/state"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ func newStreamerTester(t *testing.T, registryOptions *RegistryOptions) (*p2ptest
|
|||
|
||||
delivery := NewDelivery(to, netStore)
|
||||
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() {
|
||||
streamer.Close()
|
||||
removeDataDir()
|
||||
|
|
@ -230,12 +230,7 @@ func generateRandomFile() (string, error) {
|
|||
//generate a random file size between minFileSize and maxFileSize
|
||||
fileSize := rand.Intn(maxFileSize-minFileSize) + minFileSize
|
||||
log.Debug(fmt.Sprintf("Generated file with filesize %d kB", fileSize))
|
||||
b := make([]byte, fileSize*1024)
|
||||
_, err := crand.Read(b)
|
||||
if err != nil {
|
||||
log.Error("Error generating random file.", "err", err)
|
||||
return "", err
|
||||
}
|
||||
b := testutil.RandomBytes(1, fileSize*1024)
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ package stream
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
|
@ -39,6 +37,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||
"github.com/ethereum/go-ethereum/swarm/state"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
//Tests initializing a retrieve request
|
||||
|
|
@ -291,7 +290,7 @@ func TestRequestFromPeers(t *testing.T) {
|
|||
Peer: protocolsPeer,
|
||||
}, to)
|
||||
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
|
||||
sp := &Peer{
|
||||
|
|
@ -332,7 +331,7 @@ func TestRequestFromPeersWithLightNode(t *testing.T) {
|
|||
Peer: protocolsPeer,
|
||||
}, to)
|
||||
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
|
||||
sp := &Peer{
|
||||
Peer: protocolsPeer,
|
||||
|
|
@ -481,7 +480,7 @@ func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
|
|||
SkipCheck: skipCheck,
|
||||
Syncing: SyncingDisabled,
|
||||
Retrieval: RetrievalEnabled,
|
||||
})
|
||||
}, nil)
|
||||
bucket.Store(bucketKeyRegistry, r)
|
||||
|
||||
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
|
||||
size := chunkCount * chunkSize
|
||||
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
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -566,13 +565,13 @@ func testDeliveryFromNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
|
|||
disconnections := sim.PeerEvents(
|
||||
context.Background(),
|
||||
sim.NodeIDs(),
|
||||
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||
simulation.NewPeerEventsFilter().Drop(),
|
||||
)
|
||||
|
||||
go func() {
|
||||
for d := range disconnections {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -656,7 +655,7 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skip
|
|||
Syncing: SyncingDisabled,
|
||||
Retrieval: RetrievalDisabled,
|
||||
SyncUpdateDelay: 0,
|
||||
})
|
||||
}, nil)
|
||||
|
||||
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
|
||||
bucket.Store(bucketKeyFileStore, fileStore)
|
||||
|
|
@ -698,13 +697,13 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skip
|
|||
disconnections := sim.PeerEvents(
|
||||
context.Background(),
|
||||
sim.NodeIDs(),
|
||||
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||
simulation.NewPeerEventsFilter().Drop(),
|
||||
)
|
||||
|
||||
go func() {
|
||||
for d := range disconnections {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -719,7 +718,7 @@ func benchmarkDeliveryFromNodes(b *testing.B, nodes, conns, chunkCount int, skip
|
|||
for i := 0; i < chunkCount; i++ {
|
||||
// create actual size real chunks
|
||||
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 {
|
||||
b.Fatalf("expected no error. got %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,8 @@ package stream
|
|||
|
||||
import (
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
|
@ -29,13 +27,13 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"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/simulations/adapters"
|
||||
"github.com/ethereum/go-ethereum/swarm/network"
|
||||
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||
"github.com/ethereum/go-ethereum/swarm/state"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
func TestIntervalsLive(t *testing.T) {
|
||||
|
|
@ -86,7 +84,7 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
|||
Retrieval: RetrievalDisabled,
|
||||
Syncing: SyncingRegisterOnly,
|
||||
SkipCheck: skipCheck,
|
||||
})
|
||||
}, nil)
|
||||
bucket.Store(bucketKeyRegistry, r)
|
||||
|
||||
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)
|
||||
|
||||
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 {
|
||||
log.Error("Store error: %v", "err", err)
|
||||
t.Fatal(err)
|
||||
|
|
@ -154,7 +153,7 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
|||
disconnections := sim.PeerEvents(
|
||||
context.Background(),
|
||||
sim.NodeIDs(),
|
||||
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||
simulation.NewPeerEventsFilter().Drop(),
|
||||
)
|
||||
|
||||
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() {
|
||||
for d := range disconnections {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ func retrievalStreamerFunc(ctx *adapters.ServiceContext, bucket *sync.Map) (s no
|
|||
Retrieval: RetrievalEnabled,
|
||||
Syncing: SyncingAutoSubscribe,
|
||||
SyncUpdateDelay: 3 * time.Second,
|
||||
})
|
||||
}, nil)
|
||||
|
||||
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
|
||||
bucket.Store(bucketKeyFileStore, fileStore)
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@ package stream
|
|||
|
||||
import (
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
|
@ -29,7 +27,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"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/simulations"
|
||||
"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/storage"
|
||||
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
const MaxTimeout = 600
|
||||
|
|
@ -168,7 +166,7 @@ func streamerFunc(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Servic
|
|||
Retrieval: RetrievalDisabled,
|
||||
Syncing: SyncingAutoSubscribe,
|
||||
SyncUpdateDelay: 3 * time.Second,
|
||||
})
|
||||
}, nil)
|
||||
|
||||
bucket.Store(bucketKeyRegistry, r)
|
||||
|
||||
|
|
@ -211,12 +209,12 @@ func testSyncingViaGlobalSync(t *testing.T, chunkCount int, nodeCount int) {
|
|||
disconnections := sim.PeerEvents(
|
||||
context.Background(),
|
||||
sim.NodeIDs(),
|
||||
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||
simulation.NewPeerEventsFilter().Drop(),
|
||||
)
|
||||
|
||||
go func() {
|
||||
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")
|
||||
cancelSimRun()
|
||||
}
|
||||
|
|
@ -362,7 +360,7 @@ func testSyncingViaDirectSubscribe(t *testing.T, chunkCount int, nodeCount int)
|
|||
r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{
|
||||
Retrieval: RetrievalDisabled,
|
||||
Syncing: SyncingRegisterOnly,
|
||||
})
|
||||
}, nil)
|
||||
bucket.Store(bucketKeyRegistry, r)
|
||||
|
||||
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
|
||||
|
|
@ -403,12 +401,12 @@ func testSyncingViaDirectSubscribe(t *testing.T, chunkCount int, nodeCount int)
|
|||
disconnections := sim.PeerEvents(
|
||||
context.Background(),
|
||||
sim.NodeIDs(),
|
||||
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||
simulation.NewPeerEventsFilter().Drop(),
|
||||
)
|
||||
|
||||
go func() {
|
||||
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")
|
||||
cancelSimRun()
|
||||
}
|
||||
|
|
@ -429,7 +427,7 @@ func testSyncingViaDirectSubscribe(t *testing.T, chunkCount int, nodeCount 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)
|
||||
|
||||
for j, node := range nodeIDs {
|
||||
|
|
@ -603,7 +601,7 @@ func uploadFileToSingleNodeStore(id enode.ID, chunkCount int, lstore *storage.Lo
|
|||
size := chunkSize
|
||||
var rootAddrs []storage.Address
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -87,6 +88,9 @@ type Registry struct {
|
|||
intervalsStore state.Store
|
||||
autoRetrieval bool //automatically subscribe to retrieve request stream
|
||||
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.
|
||||
|
|
@ -99,7 +103,7 @@ type RegistryOptions struct {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
options = &RegistryOptions{}
|
||||
}
|
||||
|
|
@ -119,7 +123,10 @@ func NewRegistry(localID enode.ID, delivery *Delivery, syncChunkStore storage.Sy
|
|||
intervalsStore: intervalsStore,
|
||||
autoRetrieval: retrieval,
|
||||
maxPeerServers: options.MaxPeerServers,
|
||||
balance: balance,
|
||||
}
|
||||
streamer.setupSpec()
|
||||
|
||||
streamer.api = NewAPI(streamer)
|
||||
delivery.getPeer = streamer.getPeer
|
||||
|
||||
|
|
@ -228,6 +235,17 @@ func NewRegistry(localID enode.ID, delivery *Delivery, syncChunkStore storage.Sy
|
|||
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
|
||||
func (r *Registry) RegisterClientFunc(stream string, f func(*Peer, string, bool) (Client, error)) {
|
||||
r.clientMu.Lock()
|
||||
|
|
@ -492,7 +510,7 @@ func (r *Registry) updateSyncing() {
|
|||
}
|
||||
|
||||
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)
|
||||
np := network.NewPeer(bp, r.delivery.kad)
|
||||
r.delivery.kad.On(np)
|
||||
|
|
@ -716,35 +734,43 @@ func (c *clientParams) clientCreated() {
|
|||
close(c.clientCreatedC)
|
||||
}
|
||||
|
||||
// Spec is the spec of the streamer protocol
|
||||
var Spec = &protocols.Spec{
|
||||
Name: "stream",
|
||||
Version: 8,
|
||||
MaxMsgSize: 10 * 1024 * 1024,
|
||||
Messages: []interface{}{
|
||||
UnsubscribeMsg{},
|
||||
OfferedHashesMsg{},
|
||||
WantedHashesMsg{},
|
||||
TakeoverProofMsg{},
|
||||
SubscribeMsg{},
|
||||
RetrieveRequestMsg{},
|
||||
ChunkDeliveryMsgRetrieval{},
|
||||
SubscribeErrorMsg{},
|
||||
RequestSubscriptionMsg{},
|
||||
QuitMsg{},
|
||||
ChunkDeliveryMsgSyncing{},
|
||||
},
|
||||
//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
|
||||
var spec = &protocols.Spec{
|
||||
Name: "stream",
|
||||
Version: 8,
|
||||
MaxMsgSize: 10 * 1024 * 1024,
|
||||
Messages: []interface{}{
|
||||
UnsubscribeMsg{},
|
||||
OfferedHashesMsg{},
|
||||
WantedHashesMsg{},
|
||||
TakeoverProofMsg{},
|
||||
SubscribeMsg{},
|
||||
RetrieveRequestMsg{},
|
||||
ChunkDeliveryMsgRetrieval{},
|
||||
SubscribeErrorMsg{},
|
||||
RequestSubscriptionMsg{},
|
||||
QuitMsg{},
|
||||
ChunkDeliveryMsgSyncing{},
|
||||
},
|
||||
}
|
||||
r.spec = spec
|
||||
}
|
||||
|
||||
func (r *Registry) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{
|
||||
{
|
||||
Name: Spec.Name,
|
||||
Version: Spec.Version,
|
||||
Length: Spec.Length(),
|
||||
Name: r.spec.Name,
|
||||
Version: r.spec.Version,
|
||||
Length: r.spec.Length(),
|
||||
Run: r.runProtocol,
|
||||
// NodeInfo: ,
|
||||
// PeerInfo: ,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ package stream
|
|||
|
||||
import (
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
|
|
@ -30,7 +28,6 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/simulations/adapters"
|
||||
"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/storage"
|
||||
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
const dataChunkCount = 200
|
||||
|
|
@ -120,7 +118,7 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
|
|||
Retrieval: RetrievalDisabled,
|
||||
Syncing: SyncingAutoSubscribe,
|
||||
SkipCheck: skipCheck,
|
||||
})
|
||||
}, nil)
|
||||
|
||||
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
|
||||
bucket.Store(bucketKeyFileStore, fileStore)
|
||||
|
|
@ -152,13 +150,13 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
|
|||
disconnections := sim.PeerEvents(
|
||||
context.Background(),
|
||||
sim.NodeIDs(),
|
||||
simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeDrop),
|
||||
simulation.NewPeerEventsFilter().Drop(),
|
||||
)
|
||||
|
||||
go func() {
|
||||
for d := range disconnections {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -183,7 +181,7 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
|
|||
}
|
||||
fileStore := item.(*storage.FileStore)
|
||||
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 {
|
||||
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 {
|
||||
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]
|
||||
})
|
||||
for _, id := range nodeIDs {
|
||||
|
|
@ -404,7 +404,7 @@ func retrieve(
|
|||
nodeStatusM *sync.Map,
|
||||
totalFoundCount *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]
|
||||
})
|
||||
|
||||
|
|
@ -499,32 +499,3 @@ func retrieve(
|
|||
|
||||
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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"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) {
|
||||
data := io.LimitReader(rand.Reader, int64(n))
|
||||
data := testutil.RandomReader(1, n)
|
||||
brokendata := brokenLimitReader(data, n, n/2)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
data = io.LimitReader(rand.Reader, int64(n))
|
||||
data = testutil.RandomReader(2, n)
|
||||
brokendata = brokenLimitReader(data, n, n/2)
|
||||
|
||||
putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash)
|
||||
|
|
@ -77,7 +77,8 @@ func testRandomData(usePyramid bool, hash string, n int, tester *chunkerTester)
|
|||
input, found := tester.inputs[uint64(n)]
|
||||
var data io.Reader
|
||||
if !found {
|
||||
data, input = GenerateRandomData(n)
|
||||
input = testutil.RandomBytes(1, n)
|
||||
data = bytes.NewReader(input)
|
||||
tester.inputs[uint64(n)] = input
|
||||
} else {
|
||||
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
|
||||
for i := 1; i < n; i += 10000 {
|
||||
readableLength := n - i
|
||||
output := make([]byte, readableLength)
|
||||
r, err := reader.ReadAt(output, int64(i))
|
||||
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)
|
||||
}
|
||||
if input != nil {
|
||||
if !bytes.Equal(output, input[i:]) {
|
||||
tester.t.Fatalf("input and output mismatch\n IN: %v\nOUT: %v\n", input[i:], output)
|
||||
if !bytes.Equal(output[:readableLength], input[i:]) {
|
||||
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)]
|
||||
var data io.Reader
|
||||
if !found {
|
||||
data, input = GenerateRandomData(n)
|
||||
input = testutil.RandomBytes(i, n)
|
||||
data = bytes.NewReader(input)
|
||||
tester.inputs[uint64(n)] = input
|
||||
} else {
|
||||
data = io.LimitReader(bytes.NewReader(input), int64(n))
|
||||
|
|
@ -199,7 +200,8 @@ func TestDataAppend(t *testing.T) {
|
|||
appendInput, found := tester.inputs[uint64(m)]
|
||||
var appendData io.Reader
|
||||
if !found {
|
||||
appendData, appendInput = GenerateRandomData(m)
|
||||
appendInput = testutil.RandomBytes(i, m)
|
||||
appendData = bytes.NewReader(appendInput)
|
||||
tester.inputs[uint64(m)] = appendInput
|
||||
} else {
|
||||
appendData = io.LimitReader(bytes.NewReader(appendInput), int64(m))
|
||||
|
|
@ -272,7 +274,7 @@ func benchReadAll(reader LazySectionReader) {
|
|||
func benchmarkSplitJoin(n int, t *testing.B) {
|
||||
t.ReportAllocs()
|
||||
for i := 0; i < t.N; i++ {
|
||||
data := testDataReader(n)
|
||||
data := testutil.RandomReader(i, n)
|
||||
|
||||
putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash)
|
||||
ctx := context.TODO()
|
||||
|
|
@ -292,7 +294,7 @@ func benchmarkSplitJoin(n int, t *testing.B) {
|
|||
func benchmarkSplitTreeSHA3(n int, t *testing.B) {
|
||||
t.ReportAllocs()
|
||||
for i := 0; i < t.N; i++ {
|
||||
data := testDataReader(n)
|
||||
data := testutil.RandomReader(i, n)
|
||||
putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash)
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -311,7 +313,7 @@ func benchmarkSplitTreeSHA3(n int, t *testing.B) {
|
|||
func benchmarkSplitTreeBMT(n int, t *testing.B) {
|
||||
t.ReportAllocs()
|
||||
for i := 0; i < t.N; i++ {
|
||||
data := testDataReader(n)
|
||||
data := testutil.RandomReader(i, n)
|
||||
putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash)
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -329,7 +331,7 @@ func benchmarkSplitTreeBMT(n int, t *testing.B) {
|
|||
func benchmarkSplitPyramidBMT(n int, t *testing.B) {
|
||||
t.ReportAllocs()
|
||||
for i := 0; i < t.N; i++ {
|
||||
data := testDataReader(n)
|
||||
data := testutil.RandomReader(i, n)
|
||||
putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash)
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -347,7 +349,7 @@ func benchmarkSplitPyramidBMT(n int, t *testing.B) {
|
|||
func benchmarkSplitPyramidSHA3(n int, t *testing.B) {
|
||||
t.ReportAllocs()
|
||||
for i := 0; i < t.N; i++ {
|
||||
data := testDataReader(n)
|
||||
data := testutil.RandomReader(i, n)
|
||||
putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash)
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -365,8 +367,8 @@ func benchmarkSplitPyramidSHA3(n int, t *testing.B) {
|
|||
func benchmarkSplitAppendPyramid(n, m int, t *testing.B) {
|
||||
t.ReportAllocs()
|
||||
for i := 0; i < t.N; i++ {
|
||||
data := testDataReader(n)
|
||||
data1 := testDataReader(m)
|
||||
data := testutil.RandomReader(i, n)
|
||||
data1 := testutil.RandomReader(t.N+i, m)
|
||||
|
||||
store := NewMapChunkStore()
|
||||
putGetter := newTestHasherStore(store, SHA3Hash)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package storage
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -31,7 +30,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
ch "github.com/ethereum/go-ethereum/swarm/chunk"
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -151,10 +150,6 @@ func mget(store ChunkStore, hs []Address, f func(h Address, chunk Chunk) error)
|
|||
return err
|
||||
}
|
||||
|
||||
func testDataReader(l int) (r io.Reader) {
|
||||
return io.LimitReader(rand.Reader, int64(l))
|
||||
}
|
||||
|
||||
func (r *brokenLimitedReader) Read(buf []byte) (int, error) {
|
||||
if r.off+len(buf) > r.errAt {
|
||||
return 0, fmt.Errorf("Broken reader")
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -23,6 +23,8 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
const testDataSize = 0x0001000
|
||||
|
|
@ -49,9 +51,9 @@ func testFileStoreRandom(toEncrypt bool, t *testing.T) {
|
|||
fileStore := NewFileStore(localStore, NewFileStoreParams())
|
||||
defer os.RemoveAll("/tmp/bzz")
|
||||
|
||||
reader, slice := GenerateRandomData(testDataSize)
|
||||
slice := testutil.RandomBytes(1, testDataSize)
|
||||
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 {
|
||||
t.Fatalf("Store error: %v", err)
|
||||
}
|
||||
|
|
@ -63,13 +65,13 @@ func testFileStoreRandom(toEncrypt bool, t *testing.T) {
|
|||
if isEncrypted != toEncrypt {
|
||||
t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted)
|
||||
}
|
||||
resultSlice := make([]byte, len(slice))
|
||||
resultSlice := make([]byte, testDataSize)
|
||||
n, err := resultReader.ReadAt(resultSlice, 0)
|
||||
if err != io.EOF {
|
||||
t.Fatalf("Retrieve error: %v", err)
|
||||
}
|
||||
if n != len(slice) {
|
||||
t.Fatalf("Slice size error got %d, expected %d.", n, len(slice))
|
||||
if n != testDataSize {
|
||||
t.Fatalf("Slice size error got %d, expected %d.", n, testDataSize)
|
||||
}
|
||||
if !bytes.Equal(slice, resultSlice) {
|
||||
t.Fatalf("Comparison error.")
|
||||
|
|
@ -114,9 +116,9 @@ func testFileStoreCapacity(toEncrypt bool, t *testing.T) {
|
|||
DbStore: db,
|
||||
}
|
||||
fileStore := NewFileStore(localStore, NewFileStoreParams())
|
||||
reader, slice := GenerateRandomData(testDataSize)
|
||||
slice := testutil.RandomBytes(1, testDataSize)
|
||||
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 {
|
||||
t.Errorf("Store error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ var (
|
|||
|
||||
var (
|
||||
keyIndex = byte(0)
|
||||
keyOldData = byte(1)
|
||||
keyAccessCnt = []byte{2}
|
||||
keyEntryCnt = []byte{3}
|
||||
keyDataIdx = []byte{4}
|
||||
|
|
@ -186,6 +185,20 @@ func NewLDBStore(params *LDBStoreParams) (s *LDBStore, err error) {
|
|||
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
|
||||
func (s *LDBStore) startGC(c int) {
|
||||
|
||||
|
|
@ -271,6 +284,10 @@ func getGCIdxValue(index *dpaDBIndex, po uint8, addr Address) []byte {
|
|||
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) {
|
||||
index = &dpaDBIndex{
|
||||
Idx: binary.BigEndian.Uint64(val[1:]),
|
||||
|
|
@ -349,6 +366,7 @@ func (s *LDBStore) collectGarbage() error {
|
|||
s.delete(s.gc.batch.Batch, index, keyIdx, po)
|
||||
singleIterationCount++
|
||||
s.gc.count++
|
||||
log.Trace("garbage collect enqueued chunk for deletion", "key", hash)
|
||||
|
||||
// break if target is not on max garbage batch boundary
|
||||
if s.gc.count >= s.gc.target {
|
||||
|
|
@ -489,7 +507,7 @@ func (s *LDBStore) Import(in io.Reader) (int64, error) {
|
|||
}
|
||||
}
|
||||
|
||||
//Cleanup iterates over the database and deletes chunks if they pass the `f` condition
|
||||
// Cleanup iterates over the database and deletes chunks if they pass the `f` condition
|
||||
func (s *LDBStore) Cleanup(f func(*chunk) bool) {
|
||||
var errorsFound, removed, total int
|
||||
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
||||
func (s *LDBStore) ReIndex() {
|
||||
//Iterates over the database and checks that there are no faulty chunks
|
||||
// CleanGCIndex rebuilds the garbage collector index from scratch, while
|
||||
// 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()
|
||||
startPosition := []byte{keyOldData}
|
||||
it.Seek(startPosition)
|
||||
var key []byte
|
||||
var errorsFound, total int
|
||||
it.Seek([]byte{keyGCIdx})
|
||||
var gcDeletes int
|
||||
for it.Valid() {
|
||||
key = it.Key()
|
||||
if (key == nil) || (key[0] != keyOldData) {
|
||||
rowType, _ := parseIdxKey(it.Key())
|
||||
if rowType != keyGCIdx {
|
||||
break
|
||||
}
|
||||
data := it.Value()
|
||||
hasher := s.hashfunc()
|
||||
hasher.Write(data)
|
||||
hash := hasher.Sum(nil)
|
||||
|
||||
newKey := make([]byte, 10)
|
||||
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)
|
||||
batch.Delete(key)
|
||||
s.bucketCnt[oldCntKey[1]]--
|
||||
batch.Put(oldCntKey, U64ToBytes(s.bucketCnt[oldCntKey[1]]))
|
||||
batch.Put(newKey, newValue)
|
||||
s.bucketCnt[newCntKey[1]]++
|
||||
batch.Put(newCntKey, U64ToBytes(s.bucketCnt[newCntKey[1]]))
|
||||
s.db.Write(batch)
|
||||
batch.Delete(it.Key())
|
||||
gcDeletes++
|
||||
it.Next()
|
||||
}
|
||||
log.Debug("gc", "deletes", gcDeletes)
|
||||
if err := s.db.Write(&batch); err != nil {
|
||||
return err
|
||||
}
|
||||
batch.Reset()
|
||||
|
||||
it.Release()
|
||||
log.Warn(fmt.Sprintf("Found %v errors out of %v entries", errorsFound, total))
|
||||
|
||||
// corrected po index pointer values
|
||||
var poPtrs [256]uint64
|
||||
|
||||
// set to true if chunk count not on 4096 iteration boundary
|
||||
var doneIterating bool
|
||||
|
||||
// last key index in previous iteration
|
||||
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.Release()
|
||||
|
||||
// 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.
|
||||
|
|
@ -685,12 +813,7 @@ func (s *LDBStore) Put(ctx context.Context, chunk Chunk) error {
|
|||
idata, err := s.db.Get(ikey)
|
||||
if err != nil {
|
||||
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)
|
||||
s.batch.Put(ikey, idata)
|
||||
|
||||
|
|
@ -723,7 +846,8 @@ func (s *LDBStore) doPut(chunk Chunk, index *dpaDBIndex, po uint8) {
|
|||
s.entryCnt++
|
||||
dbEntryCount.Inc(1)
|
||||
s.dataIdx++
|
||||
|
||||
index.Access = s.accessCnt
|
||||
s.accessCnt++
|
||||
cntKey := make([]byte, 2)
|
||||
cntKey[0] = keyDistanceCnt
|
||||
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
|
||||
func (s *LDBStore) tryAccessIdx(ikey []byte, po uint8, index *dpaDBIndex) bool {
|
||||
// tryAccessIdx tries to find index entry. If found then increments the access
|
||||
// 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)
|
||||
if err != nil {
|
||||
return false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
index := new(dpaDBIndex)
|
||||
decodeIndex(idata, index)
|
||||
oldGCIdxKey := getGCIdxKey(index)
|
||||
s.batch.Put(keyAccessCnt, U64ToBytes(s.accessCnt))
|
||||
s.accessCnt++
|
||||
index.Access = s.accessCnt
|
||||
idata = encodeIndex(index)
|
||||
s.accessCnt++
|
||||
s.batch.Put(ikey, idata)
|
||||
newGCIdxKey := getGCIdxKey(index)
|
||||
newGCIdxData := getGCIdxValue(index, po, ikey)
|
||||
newGCIdxData := getGCIdxValue(index, po, ikey[1:])
|
||||
s.batch.Delete(oldGCIdxKey)
|
||||
s.batch.Put(newGCIdxKey, newGCIdxData)
|
||||
select {
|
||||
case s.batchesC <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return true
|
||||
return index, true
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
if err == leveldb.ErrNotFound {
|
||||
return "", nil
|
||||
return DbSchemaNone, nil
|
||||
}
|
||||
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
|
||||
func (s *LDBStore) get(addr Address) (chunk *chunk, err error) {
|
||||
var indx dpaDBIndex
|
||||
if s.closed {
|
||||
return nil, ErrDBClosed
|
||||
}
|
||||
proximity := s.po(addr)
|
||||
if s.tryAccessIdx(getIndexKey(addr), proximity, &indx) {
|
||||
index, found := s.tryAccessIdx(addr, proximity)
|
||||
if found {
|
||||
var data []byte
|
||||
if s.getDataFunc != nil {
|
||||
// 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 {
|
||||
// default DbStore functionality to retrieve chunk data
|
||||
datakey := getDataKey(indx.Idx, proximity)
|
||||
datakey := getDataKey(index.Idx, proximity)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package storage
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
|
@ -31,7 +32,6 @@ import (
|
|||
ch "github.com/ethereum/go-ethereum/swarm/chunk"
|
||||
"github.com/ethereum/go-ethereum/swarm/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/mock/mem"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
testDbStoreRandom(1, 0, false, t)
|
||||
}
|
||||
|
|
@ -304,17 +344,18 @@ func TestLDBStoreWithoutCollectGarbage(t *testing.T) {
|
|||
func TestLDBStoreCollectGarbage(t *testing.T) {
|
||||
|
||||
// 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("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage)
|
||||
|
||||
// at max round
|
||||
cap = defaultMaxGCRound
|
||||
cap = initialCap
|
||||
t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage)
|
||||
t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage)
|
||||
|
||||
// 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("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
|
||||
func TestLDBStoreCollectGarbageAccessUnlikeIndex(t *testing.T) {
|
||||
|
||||
capacity := defaultMaxGCRound * 2
|
||||
capacity := defaultMaxGCRound / 100 * 2
|
||||
n := capacity - 1
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
<-ldb.gc.runC
|
||||
ldb.gc.runC <- struct{}{}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ func (ls *LocalStore) get(ctx context.Context, addr Address) (chunk Chunk, err e
|
|||
|
||||
if err == nil {
|
||||
metrics.GetOrRegisterCounter("localstore.get.cachehit", nil).Inc(1)
|
||||
go ls.DbStore.MarkAccessed(addr)
|
||||
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
|
||||
func (ls *LocalStore) Migrate() error {
|
||||
schema, err := ls.DbStore.GetSchema()
|
||||
actualDbSchema, err := ls.DbStore.GetSchema()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("found schema", "schema", schema, "runtime-schema", CurrentDbSchema)
|
||||
if schema != CurrentDbSchema {
|
||||
// run migrations
|
||||
log.Debug("running migrations for", "schema", actualDbSchema, "runtime-schema", CurrentDbSchema)
|
||||
|
||||
if schema == "" {
|
||||
log.Debug("running migrations for", "schema", schema, "runtime-schema", CurrentDbSchema)
|
||||
|
||||
// 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 {
|
||||
return !ls.isValid(c)
|
||||
})
|
||||
|
||||
err := ls.DbStore.PutSchema(DbSchemaPurity)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
if actualDbSchema == CurrentDbSchema {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return !ls.isValid(c)
|
||||
})
|
||||
}
|
||||
|
||||
func (ls *LocalStore) migrateFromPurityToHalloween() error {
|
||||
return ls.DbStore.CleanGCIndex()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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))
|
||||
defer store.Close()
|
||||
|
||||
test.MockStore(t, store, 100)
|
||||
test.MockStore(t, store, 30)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
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
|
||||
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"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
|
|
@ -251,16 +250,6 @@ func GenerateRandomChunks(dataSize int64, count int) (chunks []Chunk) {
|
|||
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
|
||||
type LazySectionReader interface {
|
||||
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/feed"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/mock"
|
||||
"github.com/ethereum/go-ethereum/swarm/swap"
|
||||
"github.com/ethereum/go-ethereum/swarm/tracing"
|
||||
)
|
||||
|
||||
|
|
@ -78,6 +79,7 @@ type Swarm struct {
|
|||
netStore *storage.NetStore
|
||||
sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit
|
||||
ps *pss.Pss
|
||||
swap *swap.Swap
|
||||
|
||||
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)
|
||||
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
|
||||
if err := nodeID.UnmarshalText([]byte(config.NodeID)); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -193,7 +203,7 @@ func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err e
|
|||
SyncUpdateDelay: config.SyncUpdateDelay,
|
||||
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
|
||||
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")
|
||||
|
||||
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)
|
||||
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()))
|
||||
log.Info("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%s", newaddr.UAddr))
|
||||
// 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.
|
||||
err := self.SetChequebook(ctx)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@
|
|||
package testutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -42,3 +44,22 @@ func TempFileWithContent(t *testing.T, content string) string {
|
|||
}
|
||||
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 (
|
||||
VersionMajor = 0 // Major 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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ func (t *BlockTest) Run() error {
|
|||
} else {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
205
trie/database.go
205
trie/database.go
|
|
@ -22,6 +22,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/allegro/bigcache"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -30,6 +31,11 @@ import (
|
|||
)
|
||||
|
||||
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)
|
||||
memcacheFlushNodesMeter = metrics.NewRegisteredMeter("trie/memcache/flush/nodes", nil)
|
||||
memcacheFlushSizeMeter = metrics.NewRegisteredMeter("trie/memcache/flush/size", nil)
|
||||
|
|
@ -64,9 +70,10 @@ type DatabaseReader interface {
|
|||
type Database struct {
|
||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||
|
||||
nodes map[common.Hash]*cachedNode // Data and references relationships of a node
|
||||
oldest common.Hash // Oldest tracked node, flush-list head
|
||||
newest common.Hash // Newest tracked node, flush-list tail
|
||||
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
|
||||
newest common.Hash // Newest tracked node, flush-list tail
|
||||
|
||||
preimages map[common.Hash][]byte // Preimages of nodes from the secure trie
|
||||
seckeybuf [secureKeyLength]byte // Ephemeral buffer for calculating preimage keys
|
||||
|
|
@ -79,7 +86,7 @@ type Database struct {
|
|||
flushnodes uint64 // Nodes 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
|
||||
|
||||
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
|
||||
// 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 {
|
||||
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{
|
||||
diskdb: diskdb,
|
||||
nodes: map[common.Hash]*cachedNode{{}: {}},
|
||||
cleans: cleans,
|
||||
dirties: map[common.Hash]*cachedNode{{}: {}},
|
||||
preimages: make(map[common.Hash][]byte),
|
||||
}
|
||||
}
|
||||
|
|
@ -293,7 +319,7 @@ func (db *Database) InsertBlob(hash common.Hash, blob []byte) {
|
|||
// size tracking.
|
||||
func (db *Database) insert(hash common.Hash, blob []byte, node node) {
|
||||
// If the node's already cached, skip
|
||||
if _, ok := db.nodes[hash]; ok {
|
||||
if _, ok := db.dirties[hash]; ok {
|
||||
return
|
||||
}
|
||||
// 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,
|
||||
}
|
||||
for _, child := range entry.childs() {
|
||||
if c := db.nodes[child]; c != nil {
|
||||
if c := db.dirties[child]; c != nil {
|
||||
c.parents++
|
||||
}
|
||||
}
|
||||
db.nodes[hash] = entry
|
||||
db.dirties[hash] = entry
|
||||
|
||||
// Update the flush-list endpoints
|
||||
if db.oldest == (common.Hash{}) {
|
||||
db.oldest, db.newest = hash, hash
|
||||
} 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
|
||||
|
|
@ -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
|
||||
// found in the memory cache.
|
||||
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()
|
||||
node := db.nodes[hash]
|
||||
dirty := db.dirties[hash]
|
||||
db.lock.RUnlock()
|
||||
|
||||
if node != nil {
|
||||
return node.obj(hash, cachegen)
|
||||
if dirty != nil {
|
||||
return dirty.obj(hash, cachegen)
|
||||
}
|
||||
// Content unavailable in memory, attempt to retrieve from disk
|
||||
enc, err := db.diskdb.Get(hash[:])
|
||||
if err != nil || enc == 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)
|
||||
}
|
||||
|
||||
// Node retrieves an encoded cached trie node from memory. If it cannot be found
|
||||
// cached, the method queries the persistent database for the content.
|
||||
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()
|
||||
node := db.nodes[hash]
|
||||
dirty := db.dirties[hash]
|
||||
db.lock.RUnlock()
|
||||
|
||||
if node != nil {
|
||||
return node.rlp(), nil
|
||||
if dirty != nil {
|
||||
return dirty.rlp(), nil
|
||||
}
|
||||
// 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
|
||||
|
|
@ -395,8 +450,8 @@ func (db *Database) Nodes() []common.Hash {
|
|||
db.lock.RLock()
|
||||
defer db.lock.RUnlock()
|
||||
|
||||
var hashes = make([]common.Hash, 0, len(db.nodes))
|
||||
for hash := range db.nodes {
|
||||
var hashes = make([]common.Hash, 0, len(db.dirties))
|
||||
for hash := range db.dirties {
|
||||
if hash != (common.Hash{}) { // Special case for "root" references/nodes
|
||||
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.
|
||||
func (db *Database) reference(child common.Hash, parent common.Hash) {
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
// If the reference already exists, only duplicate for roots
|
||||
if db.nodes[parent].children == nil {
|
||||
db.nodes[parent].children = make(map[common.Hash]uint16)
|
||||
} else if _, ok = db.nodes[parent].children[child]; ok && parent != (common.Hash{}) {
|
||||
if db.dirties[parent].children == nil {
|
||||
db.dirties[parent].children = make(map[common.Hash]uint16)
|
||||
} else if _, ok = db.dirties[parent].children[child]; ok && parent != (common.Hash{}) {
|
||||
return
|
||||
}
|
||||
node.parents++
|
||||
db.nodes[parent].children[child]++
|
||||
db.dirties[parent].children[child]++
|
||||
}
|
||||
|
||||
// Dereference removes an existing reference from a root node.
|
||||
|
|
@ -439,25 +494,25 @@ func (db *Database) Dereference(root common.Hash) {
|
|||
db.lock.Lock()
|
||||
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.gcnodes += uint64(nodes - len(db.nodes))
|
||||
db.gcsize += storage - db.nodesSize
|
||||
db.gcnodes += uint64(nodes - len(db.dirties))
|
||||
db.gcsize += storage - db.dirtiesSize
|
||||
db.gctime += time.Since(start)
|
||||
|
||||
memcacheGCTimeTimer.Update(time.Since(start))
|
||||
memcacheGCSizeMeter.Mark(int64(storage - db.nodesSize))
|
||||
memcacheGCNodesMeter.Mark(int64(nodes - len(db.nodes)))
|
||||
memcacheGCSizeMeter.Mark(int64(storage - db.dirtiesSize))
|
||||
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),
|
||||
"gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.nodes), "livesize", db.nodesSize)
|
||||
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.dirties), "livesize", db.dirtiesSize)
|
||||
}
|
||||
|
||||
// dereference is the private locked version of Dereference.
|
||||
func (db *Database) dereference(child common.Hash, parent common.Hash) {
|
||||
// Dereference the parent-child
|
||||
node := db.nodes[parent]
|
||||
node := db.dirties[parent]
|
||||
|
||||
if node.children != nil && node.children[child] > 0 {
|
||||
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.
|
||||
node, ok := db.nodes[child]
|
||||
node, ok := db.dirties[child]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
|
@ -483,20 +538,20 @@ func (db *Database) dereference(child common.Hash, parent common.Hash) {
|
|||
switch child {
|
||||
case db.oldest:
|
||||
db.oldest = node.flushNext
|
||||
db.nodes[node.flushNext].flushPrev = common.Hash{}
|
||||
db.dirties[node.flushNext].flushPrev = common.Hash{}
|
||||
case db.newest:
|
||||
db.newest = node.flushPrev
|
||||
db.nodes[node.flushPrev].flushNext = common.Hash{}
|
||||
db.dirties[node.flushPrev].flushNext = common.Hash{}
|
||||
default:
|
||||
db.nodes[node.flushPrev].flushNext = node.flushNext
|
||||
db.nodes[node.flushNext].flushPrev = node.flushPrev
|
||||
db.dirties[node.flushPrev].flushNext = node.flushNext
|
||||
db.dirties[node.flushNext].flushPrev = node.flushPrev
|
||||
}
|
||||
// Dereference all children and delete the node
|
||||
for _, hash := range node.childs() {
|
||||
db.dereference(hash, child)
|
||||
}
|
||||
delete(db.nodes, child)
|
||||
db.nodesSize -= common.StorageSize(common.HashLength + int(node.size))
|
||||
delete(db.dirties, child)
|
||||
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.
|
||||
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()
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// leave for later to deduplicate writes.
|
||||
|
|
@ -540,7 +595,7 @@ func (db *Database) Cap(limit common.StorageSize) error {
|
|||
oldest := db.oldest
|
||||
for size > limit && oldest != (common.Hash{}) {
|
||||
// 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 {
|
||||
db.lock.RUnlock()
|
||||
return err
|
||||
|
|
@ -578,25 +633,25 @@ func (db *Database) Cap(limit common.StorageSize) error {
|
|||
db.preimagesSize = 0
|
||||
}
|
||||
for db.oldest != oldest {
|
||||
node := db.nodes[db.oldest]
|
||||
delete(db.nodes, db.oldest)
|
||||
node := db.dirties[db.oldest]
|
||||
delete(db.dirties, db.oldest)
|
||||
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{}) {
|
||||
db.nodes[db.oldest].flushPrev = common.Hash{}
|
||||
db.dirties[db.oldest].flushPrev = common.Hash{}
|
||||
}
|
||||
db.flushnodes += uint64(nodes - len(db.nodes))
|
||||
db.flushsize += storage - db.nodesSize
|
||||
db.flushnodes += uint64(nodes - len(db.dirties))
|
||||
db.flushsize += storage - db.dirtiesSize
|
||||
db.flushtime += time.Since(start)
|
||||
|
||||
memcacheFlushTimeTimer.Update(time.Since(start))
|
||||
memcacheFlushSizeMeter.Mark(int64(storage - db.nodesSize))
|
||||
memcacheFlushNodesMeter.Mark(int64(nodes - len(db.nodes)))
|
||||
memcacheFlushSizeMeter.Mark(int64(storage - db.dirtiesSize))
|
||||
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),
|
||||
"flushnodes", db.flushnodes, "flushsize", db.flushsize, "flushtime", db.flushtime, "livenodes", len(db.nodes), "livesize", db.nodesSize)
|
||||
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.dirties), "livesize", db.dirtiesSize)
|
||||
|
||||
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
|
||||
nodes, storage := len(db.nodes), db.nodesSize
|
||||
nodes, storage := len(db.dirties), db.dirtiesSize
|
||||
if err := db.commit(node, batch); err != nil {
|
||||
log.Error("Failed to commit trie from trie database", "err", err)
|
||||
db.lock.RUnlock()
|
||||
|
|
@ -654,15 +709,15 @@ func (db *Database) Commit(node common.Hash, report bool) error {
|
|||
db.uncache(node)
|
||||
|
||||
memcacheCommitTimeTimer.Update(time.Since(start))
|
||||
memcacheCommitSizeMeter.Mark(int64(storage - db.nodesSize))
|
||||
memcacheCommitNodesMeter.Mark(int64(nodes - len(db.nodes)))
|
||||
memcacheCommitSizeMeter.Mark(int64(storage - db.dirtiesSize))
|
||||
memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties)))
|
||||
|
||||
logger := log.Info
|
||||
if !report {
|
||||
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,
|
||||
"gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.nodes), "livesize", db.nodesSize)
|
||||
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.dirties), "livesize", db.dirtiesSize)
|
||||
|
||||
// Reset the garbage collection statistics
|
||||
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.
|
||||
func (db *Database) commit(hash common.Hash, batch ethdb.Batch) error {
|
||||
// If the node does not exist, it's a previously committed node
|
||||
node, ok := db.nodes[hash]
|
||||
node, ok := db.dirties[hash]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -702,7 +757,7 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch) error {
|
|||
// to disk.
|
||||
func (db *Database) uncache(hash common.Hash) {
|
||||
// If the node does not exist, we're done on this path
|
||||
node, ok := db.nodes[hash]
|
||||
node, ok := db.dirties[hash]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
|
@ -710,20 +765,20 @@ func (db *Database) uncache(hash common.Hash) {
|
|||
switch hash {
|
||||
case db.oldest:
|
||||
db.oldest = node.flushNext
|
||||
db.nodes[node.flushNext].flushPrev = common.Hash{}
|
||||
db.dirties[node.flushNext].flushPrev = common.Hash{}
|
||||
case db.newest:
|
||||
db.newest = node.flushPrev
|
||||
db.nodes[node.flushPrev].flushNext = common.Hash{}
|
||||
db.dirties[node.flushPrev].flushNext = common.Hash{}
|
||||
default:
|
||||
db.nodes[node.flushPrev].flushNext = node.flushNext
|
||||
db.nodes[node.flushNext].flushPrev = node.flushPrev
|
||||
db.dirties[node.flushPrev].flushNext = node.flushNext
|
||||
db.dirties[node.flushNext].flushPrev = node.flushPrev
|
||||
}
|
||||
// Uncache the node's subtries and remove the node itself too
|
||||
for _, child := range node.childs() {
|
||||
db.uncache(child)
|
||||
}
|
||||
delete(db.nodes, hash)
|
||||
db.nodesSize -= common.StorageSize(common.HashLength + int(node.size))
|
||||
delete(db.dirties, hash)
|
||||
db.dirtiesSize -= common.StorageSize(common.HashLength + int(node.size))
|
||||
}
|
||||
|
||||
// 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()
|
||||
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
|
||||
// counted. For every useful node, we track 2 extra hashes as the flushlist.
|
||||
var flushlistSize = common.StorageSize((len(db.nodes) - 1) * 2 * common.HashLength)
|
||||
return db.nodesSize + flushlistSize, db.preimagesSize
|
||||
var flushlistSize = common.StorageSize((len(db.dirties) - 1) * 2 * common.HashLength)
|
||||
return db.dirtiesSize + flushlistSize, db.preimagesSize
|
||||
}
|
||||
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
// Find any unreachable but cached nodes
|
||||
unreachable := []string{}
|
||||
for hash, node := range db.nodes {
|
||||
for hash, node := range db.dirties {
|
||||
if _, ok := reachable[hash]; !ok {
|
||||
unreachable = append(unreachable, fmt.Sprintf("%x: {Node: %v, Parents: %d, Prev: %x, Next: %x}",
|
||||
hash, node.node, node.parents, node.flushPrev, node.flushNext))
|
||||
|
|
@ -769,7 +824,7 @@ func (db *Database) verifyIntegrity() {
|
|||
// cached children found in memory.
|
||||
func (db *Database) accumulate(hash common.Hash, reachable map[common.Hash]struct{}) {
|
||||
// Mark the node reachable if present in the memory cache
|
||||
node, ok := db.nodes[hash]
|
||||
node, ok := db.dirties[hash]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,6 +181,8 @@ func (it *nodeIterator) LeafProof() [][]byte {
|
|||
if len(it.stack) > 0 {
|
||||
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
|
||||
hasher := newHasher(0, 0, nil)
|
||||
defer returnHasherToPool(hasher)
|
||||
|
||||
proofs := make([][]byte, 0, len(it.stack))
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
for hash, obj := range db.nodes {
|
||||
for hash, obj := range db.dirties {
|
||||
if obj != nil && hash != (common.Hash{}) {
|
||||
if _, ok := hashes[hash]; !ok {
|
||||
t.Errorf("state entry not reported %x", hash)
|
||||
|
|
@ -333,8 +333,8 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) {
|
|||
}
|
||||
}
|
||||
if memonly {
|
||||
robj = triedb.nodes[rkey]
|
||||
delete(triedb.nodes, rkey)
|
||||
robj = triedb.dirties[rkey]
|
||||
delete(triedb.dirties, rkey)
|
||||
} else {
|
||||
rval, _ = diskdb.Get(rkey[:])
|
||||
diskdb.Delete(rkey[:])
|
||||
|
|
@ -350,7 +350,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) {
|
|||
|
||||
// Add the node back and continue iteration.
|
||||
if memonly {
|
||||
triedb.nodes[rkey] = robj
|
||||
triedb.dirties[rkey] = robj
|
||||
} else {
|
||||
diskdb.Put(rkey[:], rval)
|
||||
}
|
||||
|
|
@ -393,8 +393,8 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) {
|
|||
barNodeObj *cachedNode
|
||||
)
|
||||
if memonly {
|
||||
barNodeObj = triedb.nodes[barNodeHash]
|
||||
delete(triedb.nodes, barNodeHash)
|
||||
barNodeObj = triedb.dirties[barNodeHash]
|
||||
delete(triedb.dirties, barNodeHash)
|
||||
} else {
|
||||
barNodeBlob, _ = diskdb.Get(barNodeHash[:])
|
||||
diskdb.Delete(barNodeHash[:])
|
||||
|
|
@ -411,7 +411,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) {
|
|||
}
|
||||
// Reinsert the missing node.
|
||||
if memonly {
|
||||
triedb.nodes[barNodeHash] = barNodeObj
|
||||
triedb.dirties[barNodeHash] = barNodeObj
|
||||
} else {
|
||||
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)
|
||||
defer returnHasherToPool(hasher)
|
||||
|
||||
for i, n := range nodes {
|
||||
// Don't bother checking for errors here since hasher panics
|
||||
// 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")
|
||||
if memonly {
|
||||
delete(triedb.nodes, hash)
|
||||
delete(triedb.dirties, hash)
|
||||
} else {
|
||||
diskdb.Delete(hash[:])
|
||||
}
|
||||
|
|
@ -342,15 +342,16 @@ func TestCacheUnload(t *testing.T) {
|
|||
// Commit the trie repeatedly and access key1.
|
||||
// The branch containing it is loaded from DB exactly two times:
|
||||
// in the 0th and 6th iteration.
|
||||
db := &countingDB{Database: trie.db.diskdb, gets: make(map[string]int)}
|
||||
trie, _ = New(root, NewDatabase(db))
|
||||
diskdb := &countingDB{Database: trie.db.diskdb, gets: make(map[string]int)}
|
||||
triedb := NewDatabase(diskdb)
|
||||
trie, _ = New(root, triedb)
|
||||
trie.SetCacheLimit(5)
|
||||
for i := 0; i < 12; i++ {
|
||||
getString(trie, key1)
|
||||
trie.Commit(nil)
|
||||
}
|
||||
// Check that it got loaded two times.
|
||||
for dbkey, count := range db.gets {
|
||||
for dbkey, count := range diskdb.gets {
|
||||
if 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",
|
||||
"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=",
|
||||
"path": "github.com/aristanetworks/goarista/monotime",
|
||||
|
|
|
|||
Loading…
Reference in a new issue