mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 18:02:24 +00:00
resolved merge conflicts
This commit is contained in:
commit
7c6b751209
39 changed files with 1522 additions and 1255 deletions
|
|
@ -76,7 +76,6 @@ const (
|
||||||
SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH"
|
SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH"
|
||||||
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
|
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
|
||||||
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
|
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
|
||||||
SWARM_ENV_STORE_RADIUS = "SWARM_STORE_RADIUS"
|
|
||||||
GETH_ENV_DATADIR = "GETH_DATADIR"
|
GETH_ENV_DATADIR = "GETH_DATADIR"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -237,19 +236,15 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
||||||
}
|
}
|
||||||
|
|
||||||
if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" {
|
if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" {
|
||||||
currentConfig.StoreParams.ChunkDbPath = storePath
|
currentConfig.LocalStoreParams.ChunkDbPath = storePath
|
||||||
}
|
}
|
||||||
|
|
||||||
if storeCapacity := ctx.GlobalUint64(SwarmStoreCapacity.Name); storeCapacity != 0 {
|
if storeCapacity := ctx.GlobalUint64(SwarmStoreCapacity.Name); storeCapacity != 0 {
|
||||||
currentConfig.StoreParams.DbCapacity = storeCapacity
|
currentConfig.LocalStoreParams.DbCapacity = storeCapacity
|
||||||
}
|
}
|
||||||
|
|
||||||
if storeCacheCapacity := ctx.GlobalUint(SwarmStoreCacheCapacity.Name); storeCacheCapacity != 0 {
|
if storeCacheCapacity := ctx.GlobalUint(SwarmStoreCacheCapacity.Name); storeCacheCapacity != 0 {
|
||||||
currentConfig.StoreParams.CacheCapacity = storeCacheCapacity
|
currentConfig.LocalStoreParams.CacheCapacity = storeCacheCapacity
|
||||||
}
|
|
||||||
|
|
||||||
if storeRadius := ctx.GlobalInt(SwarmStoreRadius.Name); storeRadius != 0 {
|
|
||||||
currentConfig.StoreParams.Radius = storeRadius
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentConfig
|
return currentConfig
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ func TestConfigFileOverrides(t *testing.T) {
|
||||||
defaultConf.PssEnabled = true
|
defaultConf.PssEnabled = true
|
||||||
defaultConf.NetworkId = 54
|
defaultConf.NetworkId = 54
|
||||||
defaultConf.Port = httpPort
|
defaultConf.Port = httpPort
|
||||||
defaultConf.StoreParams.DbCapacity = 9000000
|
defaultConf.DbCapacity = 9000000
|
||||||
defaultConf.HiveParams.KeepAliveInterval = 6000000000
|
defaultConf.HiveParams.KeepAliveInterval = 6000000000
|
||||||
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
||||||
//defaultConf.SyncParams.KeyBufferSize = 512
|
//defaultConf.SyncParams.KeyBufferSize = 512
|
||||||
|
|
@ -232,7 +232,7 @@ func TestConfigFileOverrides(t *testing.T) {
|
||||||
t.Fatal("Expected Pss to be enabled, but is false")
|
t.Fatal("Expected Pss to be enabled, but is false")
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.StoreParams.DbCapacity != 9000000 {
|
if info.DbCapacity != 9000000 {
|
||||||
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId)
|
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,7 +368,7 @@ func TestConfigCmdLineOverridesFile(t *testing.T) {
|
||||||
defaultConf.PssEnabled = false
|
defaultConf.PssEnabled = false
|
||||||
defaultConf.NetworkId = 54
|
defaultConf.NetworkId = 54
|
||||||
defaultConf.Port = "8588"
|
defaultConf.Port = "8588"
|
||||||
defaultConf.StoreParams.DbCapacity = 9000000
|
defaultConf.DbCapacity = 9000000
|
||||||
defaultConf.HiveParams.KeepAliveInterval = 6000000000
|
defaultConf.HiveParams.KeepAliveInterval = 6000000000
|
||||||
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
||||||
//defaultConf.SyncParams.KeyBufferSize = 512
|
//defaultConf.SyncParams.KeyBufferSize = 512
|
||||||
|
|
@ -378,10 +378,12 @@ func TestConfigCmdLineOverridesFile(t *testing.T) {
|
||||||
t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
|
t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
|
||||||
}
|
}
|
||||||
//write file
|
//write file
|
||||||
f, err := ioutil.TempFile("", "testconfig.toml")
|
fname := "testconfig.toml"
|
||||||
|
f, err := ioutil.TempFile("", fname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||||
}
|
}
|
||||||
|
defer os.Remove(fname)
|
||||||
//write file
|
//write file
|
||||||
_, err = f.WriteString(string(out))
|
_, err = f.WriteString(string(out))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -446,8 +448,8 @@ func TestConfigCmdLineOverridesFile(t *testing.T) {
|
||||||
t.Fatal("Expected Sync to be enabled, but is false")
|
t.Fatal("Expected Sync to be enabled, but is false")
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.StoreParams.DbCapacity != 9000000 {
|
if info.LocalStoreParams.DbCapacity != 9000000 {
|
||||||
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId)
|
t.Fatalf("Expected Capacity to be %d, got %d", 9000000, info.LocalStoreParams.DbCapacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.HiveParams.KeepAliveInterval != 6000000000 {
|
if info.HiveParams.KeepAliveInterval != 6000000000 {
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,8 @@ func openLDBStore(path string, basekey []byte) (*storage.LDBStore, error) {
|
||||||
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
|
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
|
||||||
return nil, fmt.Errorf("invalid chunkdb path: %s", err)
|
return nil, fmt.Errorf("invalid chunkdb path: %s", err)
|
||||||
}
|
}
|
||||||
hash := storage.MakeHashFunc("SHA3")
|
|
||||||
return storage.NewLDBStore(path, hash, 10000000, func(k storage.Key) (ret uint8) { return uint8(storage.Proximity(basekey[:], k[:])) })
|
storeparams := storage.NewDefaultStoreParams()
|
||||||
|
ldbparams := storage.NewLDBStoreParams(storeparams, path)
|
||||||
|
return storage.NewLDBStore(ldbparams)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,11 +169,6 @@ var (
|
||||||
Usage: "Number of recent chunks cached in memory (default 5000)",
|
Usage: "Number of recent chunks cached in memory (default 5000)",
|
||||||
EnvVar: SWARM_ENV_STORE_CACHE_CAPACITY,
|
EnvVar: SWARM_ENV_STORE_CACHE_CAPACITY,
|
||||||
}
|
}
|
||||||
SwarmStoreRadius = cli.IntFlag{
|
|
||||||
Name: "store.radius",
|
|
||||||
Usage: "Minimum proximity order (number of identical prefix bits of address key) for chunks to warrant storage (default 0)",
|
|
||||||
EnvVar: SWARM_ENV_STORE_RADIUS,
|
|
||||||
}
|
|
||||||
|
|
||||||
// the following flags are deprecated and should be removed in the future
|
// the following flags are deprecated and should be removed in the future
|
||||||
DeprecatedEthAPIFlag = cli.StringFlag{
|
DeprecatedEthAPIFlag = cli.StringFlag{
|
||||||
|
|
@ -385,7 +380,6 @@ Remove corrupt entries from a local chunk database.
|
||||||
SwarmStorePath,
|
SwarmStorePath,
|
||||||
SwarmStoreCapacity,
|
SwarmStoreCapacity,
|
||||||
SwarmStoreCacheCapacity,
|
SwarmStoreCacheCapacity,
|
||||||
SwarmStoreRadius,
|
|
||||||
//deprecated flags
|
//deprecated flags
|
||||||
DeprecatedEthAPIFlag,
|
DeprecatedEthAPIFlag,
|
||||||
DeprecatedEnsAddrFlag,
|
DeprecatedEnsAddrFlag,
|
||||||
|
|
|
||||||
|
|
@ -617,7 +617,7 @@ func (self *Api) ResourceUpdate(ctx context.Context, name string, data []byte) (
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Api) ResourceHashSize() int {
|
func (self *Api) ResourceHashSize() int {
|
||||||
return self.resource.HashSize()
|
return self.resource.HashSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Api) ResourceIsValidated() bool {
|
func (self *Api) ResourceIsValidated() bool {
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ const (
|
||||||
// allow several bzz nodes running in parallel
|
// allow several bzz nodes running in parallel
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// serialised/persisted fields
|
// serialised/persisted fields
|
||||||
*storage.StoreParams
|
|
||||||
*storage.DPAParams
|
*storage.DPAParams
|
||||||
|
*storage.LocalStoreParams
|
||||||
*network.HiveParams
|
*network.HiveParams
|
||||||
Swap *swap.SwapParams
|
Swap *swap.SwapParams
|
||||||
//*network.SyncParams
|
//*network.SyncParams
|
||||||
|
|
@ -72,9 +72,9 @@ type Config struct {
|
||||||
func NewConfig() (self *Config) {
|
func NewConfig() (self *Config) {
|
||||||
|
|
||||||
self = &Config{
|
self = &Config{
|
||||||
StoreParams: storage.NewDefaultStoreParams(),
|
LocalStoreParams: storage.NewDefaultLocalStoreParams(),
|
||||||
DPAParams: storage.NewDPAParams(),
|
DPAParams: storage.NewDPAParams(),
|
||||||
HiveParams: network.NewHiveParams(),
|
HiveParams: network.NewHiveParams(),
|
||||||
//SyncParams: network.NewDefaultSyncParams(),
|
//SyncParams: network.NewDefaultSyncParams(),
|
||||||
Swap: swap.NewDefaultSwapParams(),
|
Swap: swap.NewDefaultSwapParams(),
|
||||||
ListenAddr: DefaultHTTPListenAddr,
|
ListenAddr: DefaultHTTPListenAddr,
|
||||||
|
|
@ -117,8 +117,9 @@ func (self *Config) Init(prvKey *ecdsa.PrivateKey) {
|
||||||
if self.SwapEnabled {
|
if self.SwapEnabled {
|
||||||
self.Swap.Init(self.Contract, prvKey)
|
self.Swap.Init(self.Contract, prvKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.privateKey = prvKey
|
self.privateKey = prvKey
|
||||||
self.StoreParams.Init(self.Path)
|
self.LocalStoreParams.Init(self.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Config) ShiftPrivateKey() (privKey *ecdsa.PrivateKey) {
|
func (self *Config) ShiftPrivateKey() (privKey *ecdsa.PrivateKey) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ func TestConfig(t *testing.T) {
|
||||||
one := NewConfig()
|
one := NewConfig()
|
||||||
two := NewConfig()
|
two := NewConfig()
|
||||||
|
|
||||||
|
one.LocalStoreParams = two.LocalStoreParams
|
||||||
if equal := reflect.DeepEqual(one, two); !equal {
|
if equal := reflect.DeepEqual(one, two); !equal {
|
||||||
t.Fatal("Two default configs are not equal")
|
t.Fatal("Two default configs are not equal")
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +53,7 @@ func TestConfig(t *testing.T) {
|
||||||
if one.Swap.PayProfile.Beneficiary == (common.Address{}) && one.SwapEnabled {
|
if one.Swap.PayProfile.Beneficiary == (common.Address{}) && one.SwapEnabled {
|
||||||
t.Fatal("Failed to correctly initialize SwapParams")
|
t.Fatal("Failed to correctly initialize SwapParams")
|
||||||
}
|
}
|
||||||
if one.StoreParams.ChunkDbPath == one.Path {
|
if one.ChunkDbPath == one.Path {
|
||||||
t.Fatal("Failed to correctly initialize StoreParams")
|
t.Fatal("Failed to correctly initialize StoreParams")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -481,13 +481,13 @@ func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supEr
|
||||||
code := 0
|
code := 0
|
||||||
defaultErr := fmt.Errorf("%s: %v", supErr, err)
|
defaultErr := fmt.Errorf("%s: %v", supErr, err)
|
||||||
rsrcErr, ok := err.(*storage.ResourceError)
|
rsrcErr, ok := err.(*storage.ResourceError)
|
||||||
if !ok {
|
if !ok && rsrcErr != nil {
|
||||||
code = rsrcErr.Code()
|
code = rsrcErr.Code()
|
||||||
}
|
}
|
||||||
switch code {
|
switch code {
|
||||||
case storage.ErrInvalidValue:
|
case storage.ErrInvalidValue:
|
||||||
return http.StatusBadRequest, defaultErr
|
return http.StatusBadRequest, defaultErr
|
||||||
case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn:
|
case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn, storage.ErrInit:
|
||||||
return http.StatusNotFound, defaultErr
|
return http.StatusNotFound, defaultErr
|
||||||
case storage.ErrUnauthorized, storage.ErrInvalidSignature:
|
case storage.ErrUnauthorized, storage.ErrInvalidSignature:
|
||||||
return http.StatusUnauthorized, defaultErr
|
return http.StatusUnauthorized, defaultErr
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||||
|
|
@ -37,11 +38,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
verbose := flag.Bool("v", false, "verbose")
|
loglevel := flag.Int("loglevel", 2, "loglevel")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *verbose {
|
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
||||||
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type resourceResponse struct {
|
type resourceResponse struct {
|
||||||
|
|
@ -55,10 +54,8 @@ func TestBzzResource(t *testing.T) {
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
// our mutable resource "name"
|
// our mutable resource "name"
|
||||||
keybytes := []byte("foo")
|
keybytes := "foo"
|
||||||
srv.Hasher.Reset()
|
keybyteshash := ens.EnsNode(keybytes)
|
||||||
srv.Hasher.Write([]byte(fmt.Sprintf("%x", keybytes)))
|
|
||||||
keybyteshash := fmt.Sprintf("%x", srv.Hasher.Sum(nil))
|
|
||||||
|
|
||||||
// data of update 1
|
// data of update 1
|
||||||
databytes := make([]byte, 666)
|
databytes := make([]byte, 666)
|
||||||
|
|
@ -68,7 +65,7 @@ func TestBzzResource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates resource and sets update 1
|
// creates resource and sets update 1
|
||||||
url := fmt.Sprintf("%s/bzz-resource:/%x/13", srv.URL, keybytes)
|
url := fmt.Sprintf("%s/bzz-resource:/%s/13", srv.URL, []byte(keybytes))
|
||||||
resp, err := http.Post(url, "application/octet-stream", bytes.NewReader(databytes))
|
resp, err := http.Post(url, "application/octet-stream", bytes.NewReader(databytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -86,8 +83,8 @@ func TestBzzResource(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("data %s could not be unmarshaled: %v", b, err)
|
t.Fatalf("data %s could not be unmarshaled: %v", b, err)
|
||||||
}
|
}
|
||||||
if rsrcResp.Update.Hex() != keybyteshash {
|
if !bytes.Equal(rsrcResp.Update, keybyteshash.Bytes()) {
|
||||||
t.Fatalf("Response resource key mismatch, expected '%s', got '%s'", keybyteshash, rsrcResp.Resource)
|
t.Fatalf("Response resource key mismatch, expected '%s', got '%s'", keybyteshash.Hex(), rsrcResp.Update.Hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
// get manifest
|
// get manifest
|
||||||
|
|
@ -131,8 +128,16 @@ func TestBzzResource(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get non-existent name
|
||||||
|
url = fmt.Sprintf("%s/bzz-resource:/bar", srv.URL)
|
||||||
|
resp, err = http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
// get latest update (1.1) through resource directly
|
// get latest update (1.1) through resource directly
|
||||||
url = fmt.Sprintf("%s/bzz-resource:/%x", srv.URL, keybytes)
|
url = fmt.Sprintf("%s/bzz-resource:/%s", srv.URL, keybytes)
|
||||||
resp, err = http.Get(url)
|
resp, err = http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -150,7 +155,7 @@ func TestBzzResource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update 2
|
// update 2
|
||||||
url = fmt.Sprintf("%s/bzz-resource:/%x", srv.URL, keybytes)
|
url = fmt.Sprintf("%s/bzz-resource:/%s", srv.URL, keybytes)
|
||||||
data := []byte("foo")
|
data := []byte("foo")
|
||||||
resp, err = http.Post(url, "application/octet-stream", bytes.NewReader(data))
|
resp, err = http.Post(url, "application/octet-stream", bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -162,7 +167,7 @@ func TestBzzResource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get latest update (1.2) through resource directly
|
// get latest update (1.2) through resource directly
|
||||||
url = fmt.Sprintf("%s/bzz-resource:/%x", srv.URL, keybytes)
|
url = fmt.Sprintf("%s/bzz-resource:/%s", srv.URL, keybytes)
|
||||||
resp, err = http.Get(url)
|
resp, err = http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -180,7 +185,7 @@ func TestBzzResource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get latest update (1.2) with specified period
|
// get latest update (1.2) with specified period
|
||||||
url = fmt.Sprintf("%s/bzz-resource:/%x/1", srv.URL, keybytes)
|
url = fmt.Sprintf("%s/bzz-resource:/%s/1", srv.URL, keybytes)
|
||||||
resp, err = http.Get(url)
|
resp, err = http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -198,7 +203,7 @@ func TestBzzResource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get first update (1.1) with specified period and version
|
// get first update (1.1) with specified period and version
|
||||||
url = fmt.Sprintf("%s/bzz-resource:/%x/1/1", srv.URL, keybytes)
|
url = fmt.Sprintf("%s/bzz-resource:/%s/1/1", srv.URL, keybytes)
|
||||||
resp, err = http.Get(url)
|
resp, err = http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ package fuse
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -29,8 +30,23 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
||||||
|
colorable "github.com/mattn/go-colorable"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loglevel = flag.Int("loglevel", 4, "verbosity of logs")
|
||||||
|
rawlog = flag.Bool("rawlog", false, "turn off terminal formatting in logs")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
log.PrintOrigins(true)
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(!*rawlog))))
|
||||||
|
}
|
||||||
|
|
||||||
type fileInfo struct {
|
type fileInfo struct {
|
||||||
perm uint64
|
perm uint64
|
||||||
uid int
|
uid int
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,11 @@ func newStreamerTester(t *testing.T) (*p2ptest.ProtocolTester, *Registry, *stora
|
||||||
os.RemoveAll(datadir)
|
os.RemoveAll(datadir)
|
||||||
}
|
}
|
||||||
|
|
||||||
localStore, err := storage.NewTestLocalStoreForAddr(datadir, addr.Over())
|
params := storage.NewDefaultLocalStoreParams()
|
||||||
|
params.Init(datadir)
|
||||||
|
params.BaseKey = addr.Over()
|
||||||
|
|
||||||
|
localStore, err := storage.NewTestLocalStoreForAddr(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, removeDataDir, err
|
return nil, nil, nil, removeDataDir, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ func (d *Delivery) handleRetrieveRequestMsg(sp *Peer, req *RetrieveRequestMsg) e
|
||||||
if created {
|
if created {
|
||||||
if err := d.RequestFromPeers(chunk.Key[:], false, sp.ID()); err != nil {
|
if err := d.RequestFromPeers(chunk.Key[:], false, sp.ID()); err != nil {
|
||||||
log.Warn("unable to forward chunk request", "peer", sp.ID(), "key", chunk.Key, "err", err)
|
log.Warn("unable to forward chunk request", "peer", sp.ID(), "key", chunk.Key, "err", err)
|
||||||
chunk.SetErrored(true)
|
chunk.SetErrored(storage.ErrChunkForward)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,10 +149,10 @@ func (d *Delivery) handleRetrieveRequestMsg(sp *Peer, req *RetrieveRequestMsg) e
|
||||||
select {
|
select {
|
||||||
case <-chunk.ReqC:
|
case <-chunk.ReqC:
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
chunk.SetErrored(true)
|
chunk.SetErrored(storage.ErrChunkTimeout)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
chunk.SetErrored(false)
|
chunk.SetErrored(nil)
|
||||||
|
|
||||||
if req.SkipCheck {
|
if req.SkipCheck {
|
||||||
err := sp.Deliver(chunk, s.priority)
|
err := sp.Deliver(chunk, s.priority)
|
||||||
|
|
@ -209,6 +209,12 @@ R:
|
||||||
chunk.SData = req.SData
|
chunk.SData = req.SData
|
||||||
d.db.Put(chunk)
|
d.db.Put(chunk)
|
||||||
chunk.WaitToStore()
|
chunk.WaitToStore()
|
||||||
|
err = chunk.GetErrored()
|
||||||
|
if err != nil {
|
||||||
|
if err == storage.ErrChunkInvalid {
|
||||||
|
req.peer.Drop(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
close(chunk.ReqC)
|
close(chunk.ReqC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -662,7 +662,10 @@ func createTestLocalStorageForId(id discover.NodeID, addr *network.BzzAddr) (sto
|
||||||
}
|
}
|
||||||
datadirs[id] = datadir
|
datadirs[id] = datadir
|
||||||
var store storage.ChunkStore
|
var store storage.ChunkStore
|
||||||
store, err = storage.NewTestLocalStoreForAddr(datadir, addr.Over())
|
params := storage.NewDefaultLocalStoreParams()
|
||||||
|
params.ChunkDbPath = datadir
|
||||||
|
params.BaseKey = addr.Over()
|
||||||
|
store, err = storage.NewTestLocalStoreForAddr(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,10 @@ func SetStores(addrs ...network.Addr) ([]storage.ChunkStore, func(), error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
var store storage.ChunkStore
|
var store storage.ChunkStore
|
||||||
store, err = storage.NewTestLocalStoreForAddr(datadir, addr.Over())
|
params := storage.NewDefaultLocalStoreParams()
|
||||||
|
params.Init(datadir)
|
||||||
|
params.BaseKey = addr.Over()
|
||||||
|
store, err = storage.NewTestLocalStoreForAddr(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -41,8 +40,6 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrChunkNotFound = errors.New("chunk not found")
|
|
||||||
ErrFetching = errors.New("chunk still fetching")
|
|
||||||
// timeout interval before retrieval is timed out
|
// timeout interval before retrieval is timed out
|
||||||
searchTimeout = 3 * time.Second
|
searchTimeout = 3 * time.Second
|
||||||
)
|
)
|
||||||
|
|
@ -64,18 +61,14 @@ func NewDPAParams() *DPAParams {
|
||||||
|
|
||||||
// for testing locally
|
// for testing locally
|
||||||
func NewLocalDPA(datadir string, basekey []byte) (*DPA, error) {
|
func NewLocalDPA(datadir string, basekey []byte) (*DPA, error) {
|
||||||
|
params := NewDefaultLocalStoreParams()
|
||||||
hash := MakeHashFunc("SHA3")
|
params.Init(datadir)
|
||||||
|
localStore, err := NewLocalStore(params, nil)
|
||||||
dbStore, err := NewLDBStore(datadir, hash, defaultLDBCapacity, func(k Key) (ret uint8) { return uint8(Proximity(basekey[:], k[:])) })
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
localStore.Validators = append(localStore.Validators, NewContentAddressValidator(MakeHashFunc(SHA3Hash)))
|
||||||
return NewDPA(&LocalStore{
|
return NewDPA(localStore, NewDPAParams()), nil
|
||||||
memStore: NewMemStore(dbStore, defaultCacheCapacity, defaultChunkRequestsCacheCapacity),
|
|
||||||
DbStore: dbStore,
|
|
||||||
}, NewDPAParams()), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDPA(store ChunkStore, params *DPAParams) *DPA {
|
func NewDPA(store ChunkStore, params *DPAParams) *DPA {
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,14 @@ func TestDPArandom(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDpaRandom(toEncrypt bool, t *testing.T) {
|
func testDpaRandom(toEncrypt bool, t *testing.T) {
|
||||||
tdb, err := newTestDbStore(false)
|
tdb, err := newTestDbStore(false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("init dbStore failed: %v", err)
|
t.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
defer tdb.close()
|
defer tdb.close()
|
||||||
db := tdb.LDBStore
|
db := tdb.LDBStore
|
||||||
db.setCapacity(50000)
|
db.setCapacity(50000)
|
||||||
memStore := NewMemStore(db, defaultCacheCapacity, defaultChunkRequestsCacheCapacity)
|
memStore := NewMemStore(NewDefaultStoreParams(), db)
|
||||||
localStore := &LocalStore{
|
localStore := &LocalStore{
|
||||||
memStore: memStore,
|
memStore: memStore,
|
||||||
DbStore: db,
|
DbStore: db,
|
||||||
|
|
@ -71,7 +71,7 @@ func testDpaRandom(toEncrypt bool, t *testing.T) {
|
||||||
}
|
}
|
||||||
ioutil.WriteFile("/tmp/slice.bzz.16M", slice, 0666)
|
ioutil.WriteFile("/tmp/slice.bzz.16M", slice, 0666)
|
||||||
ioutil.WriteFile("/tmp/result.bzz.16M", resultSlice, 0666)
|
ioutil.WriteFile("/tmp/result.bzz.16M", resultSlice, 0666)
|
||||||
localStore.memStore = NewMemStore(db, defaultCacheCapacity, defaultChunkRequestsCacheCapacity)
|
localStore.memStore = NewMemStore(NewDefaultStoreParams(), db)
|
||||||
resultReader, isEncrypted = dpa.Retrieve(key)
|
resultReader, isEncrypted = dpa.Retrieve(key)
|
||||||
if isEncrypted != toEncrypt {
|
if isEncrypted != toEncrypt {
|
||||||
t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted)
|
t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted)
|
||||||
|
|
@ -97,13 +97,13 @@ func TestDPA_capacity(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDPA_capacity(toEncrypt bool, t *testing.T) {
|
func testDPA_capacity(toEncrypt bool, t *testing.T) {
|
||||||
tdb, err := newTestDbStore(false)
|
tdb, err := newTestDbStore(false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("init dbStore failed: %v", err)
|
t.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
defer tdb.close()
|
defer tdb.close()
|
||||||
db := tdb.LDBStore
|
db := tdb.LDBStore
|
||||||
memStore := NewMemStore(db, 0, 0)
|
memStore := NewMemStore(NewDefaultStoreParams(), db)
|
||||||
localStore := &LocalStore{
|
localStore := &LocalStore{
|
||||||
memStore: memStore,
|
memStore: memStore,
|
||||||
DbStore: db,
|
DbStore: db,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ErrNotFound = iota
|
ErrInit = iota
|
||||||
|
ErrNotFound
|
||||||
ErrIO
|
ErrIO
|
||||||
ErrUnauthorized
|
ErrUnauthorized
|
||||||
ErrInvalidValue
|
ErrInvalidValue
|
||||||
|
|
@ -12,3 +17,12 @@ const (
|
||||||
ErrPeriodDepth
|
ErrPeriodDepth
|
||||||
ErrCnt
|
ErrCnt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrChunkNotFound = errors.New("chunk not found")
|
||||||
|
ErrFetching = errors.New("chunk still fetching")
|
||||||
|
ErrChunkInvalid = errors.New("invalid chunk")
|
||||||
|
ErrChunkForward = errors.New("cannot forward")
|
||||||
|
ErrChunkUnavailable = errors.New("chunk unavailable")
|
||||||
|
ErrChunkTimeout = errors.New("timeout")
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,21 @@ type gcItem struct {
|
||||||
po uint8
|
po uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LDBStoreParams struct {
|
||||||
|
*StoreParams
|
||||||
|
Path string
|
||||||
|
Po func(Key) uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLDBStoreParams constructs LDBStoreParams with the specified values.
|
||||||
|
func NewLDBStoreParams(storeparams *StoreParams, path string) *LDBStoreParams {
|
||||||
|
return &LDBStoreParams{
|
||||||
|
StoreParams: storeparams,
|
||||||
|
Path: path,
|
||||||
|
Po: func(k Key) (ret uint8) { return uint8(Proximity(storeparams.BaseKey[:], k[:])) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type LDBStore struct {
|
type LDBStore struct {
|
||||||
db *LDBDatabase
|
db *LDBDatabase
|
||||||
|
|
||||||
|
|
@ -87,7 +102,6 @@ type LDBStore struct {
|
||||||
batchesC chan struct{}
|
batchesC chan struct{}
|
||||||
batch *leveldb.Batch
|
batch *leveldb.Batch
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
trusted bool // if hash integity check is to be performed (for testing only)
|
|
||||||
|
|
||||||
// Functions encodeDataFunc is used to bypass
|
// Functions encodeDataFunc is used to bypass
|
||||||
// the default functionality of DbStore with
|
// the default functionality of DbStore with
|
||||||
|
|
@ -102,9 +116,9 @@ type LDBStore struct {
|
||||||
// TODO: Instead of passing the distance function, just pass the address from which distances are calculated
|
// TODO: Instead of passing the distance function, just pass the address from which distances are calculated
|
||||||
// to avoid the appearance of a pluggable distance metric and opportunities of bugs associated with providing
|
// to avoid the appearance of a pluggable distance metric and opportunities of bugs associated with providing
|
||||||
// a function different from the one that is actually used.
|
// a function different from the one that is actually used.
|
||||||
func NewLDBStore(path string, hash SwarmHasher, capacity uint64, po func(Key) uint8) (s *LDBStore, err error) {
|
func NewLDBStore(params *LDBStoreParams) (s *LDBStore, err error) {
|
||||||
s = new(LDBStore)
|
s = new(LDBStore)
|
||||||
s.hashfunc = hash
|
s.hashfunc = params.Hash
|
||||||
|
|
||||||
s.batchC = make(chan bool)
|
s.batchC = make(chan bool)
|
||||||
s.batchesC = make(chan struct{}, 1)
|
s.batchesC = make(chan struct{}, 1)
|
||||||
|
|
@ -113,13 +127,13 @@ func NewLDBStore(path string, hash SwarmHasher, capacity uint64, po func(Key) ui
|
||||||
// associate encodeData with default functionality
|
// associate encodeData with default functionality
|
||||||
s.encodeDataFunc = encodeData
|
s.encodeDataFunc = encodeData
|
||||||
|
|
||||||
s.db, err = NewLDBDatabase(path)
|
s.db, err = NewLDBDatabase(params.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.po = po
|
s.po = params.Po
|
||||||
s.setCapacity(capacity)
|
s.setCapacity(params.DbCapacity)
|
||||||
|
|
||||||
s.bucketCnt = make([]uint64, 0x100)
|
s.bucketCnt = make([]uint64, 0x100)
|
||||||
for i := 0; i < 0x100; i++ {
|
for i := 0; i < 0x100; i++ {
|
||||||
|
|
@ -143,15 +157,11 @@ func NewLDBStore(path string, hash SwarmHasher, capacity uint64, po func(Key) ui
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *LDBStore) SetTrusted() {
|
|
||||||
self.trusted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockDbStore creates a new instance of DbStore with
|
// NewMockDbStore creates a new instance of DbStore with
|
||||||
// mockStore set to a provided value. If mockStore argument is nil,
|
// mockStore set to a provided value. If mockStore argument is nil,
|
||||||
// this function behaves exactly as NewDbStore.
|
// this function behaves exactly as NewDbStore.
|
||||||
func NewMockDbStore(path string, hash SwarmHasher, capacity uint64, po func(Key) uint8, mockStore *mock.NodeStore) (s *LDBStore, err error) {
|
func NewMockDbStore(params *LDBStoreParams, mockStore *mock.NodeStore) (s *LDBStore, err error) {
|
||||||
s, err = NewLDBStore(path, hash, capacity, po)
|
s, err = NewLDBStore(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -344,7 +354,7 @@ func (s *LDBStore) Import(in io.Reader) (int64, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := hex.DecodeString(hdr.Name)
|
keybytes, err := hex.DecodeString(hdr.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("ignoring invalid chunk file", "name", hdr.Name, "err", err)
|
log.Warn("ignoring invalid chunk file", "name", hdr.Name, "err", err)
|
||||||
continue
|
continue
|
||||||
|
|
@ -354,6 +364,7 @@ func (s *LDBStore) Import(in io.Reader) (int64, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
key := Key(keybytes)
|
||||||
chunk := NewChunk(key, nil)
|
chunk := NewChunk(key, nil)
|
||||||
chunk.SData = data[32:]
|
chunk.SData = data[32:]
|
||||||
s.Put(chunk)
|
s.Put(chunk)
|
||||||
|
|
@ -631,19 +642,6 @@ func (s *LDBStore) get(key Key) (chunk *Chunk, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.trusted {
|
|
||||||
data_mod := data[32:]
|
|
||||||
hasher := s.hashfunc()
|
|
||||||
hasher.Write(data_mod)
|
|
||||||
hash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
if !bytes.Equal(hash, key) {
|
|
||||||
log.Error("apparent key/hash mismatch", "hash", hash, "key", key[:])
|
|
||||||
s.delete(indx.Idx, getIndexKey(key), s.po(key))
|
|
||||||
log.Error("invalid chunk in database.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chunk = NewChunk(key, nil)
|
chunk = NewChunk(key, nil)
|
||||||
chunk.markAsStored()
|
chunk.markAsStored()
|
||||||
decodeData(data, chunk)
|
decodeData(data, chunk)
|
||||||
|
|
|
||||||
|
|
@ -37,21 +37,25 @@ type testDbStore struct {
|
||||||
dir string
|
dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestDbStore(mock bool) (*testDbStore, error) {
|
func newTestDbStore(mock bool, trusted bool) (*testDbStore, error) {
|
||||||
dir, err := ioutil.TempDir("", "bzz-storage-test")
|
dir, err := ioutil.TempDir("", "bzz-storage-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var db *LDBStore
|
var db *LDBStore
|
||||||
|
storeparams := NewDefaultStoreParams()
|
||||||
|
params := NewLDBStoreParams(storeparams, dir)
|
||||||
|
params.Po = testPoFunc
|
||||||
|
|
||||||
if mock {
|
if mock {
|
||||||
globalStore := mem.NewGlobalStore()
|
globalStore := mem.NewGlobalStore()
|
||||||
addr := common.HexToAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
|
addr := common.HexToAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
|
||||||
mockStore := globalStore.NewNodeStore(addr)
|
mockStore := globalStore.NewNodeStore(addr)
|
||||||
|
|
||||||
db, err = NewMockDbStore(dir, MakeHashFunc(SHA3Hash), defaultLDBCapacity, testPoFunc, mockStore)
|
db, err = NewMockDbStore(params, mockStore)
|
||||||
} else {
|
} else {
|
||||||
db, err = NewLDBStore(dir, MakeHashFunc(SHA3Hash), defaultLDBCapacity, testPoFunc)
|
db, err = NewLDBStore(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &testDbStore{db, dir}, err
|
return &testDbStore{db, dir}, err
|
||||||
|
|
@ -71,17 +75,16 @@ func (db *testDbStore) close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDbStoreRandom(n int, processors int, chunksize int, mock bool, t *testing.T) {
|
func testDbStoreRandom(n int, processors int, chunksize int, mock bool, t *testing.T) {
|
||||||
db, err := newTestDbStore(mock)
|
db, err := newTestDbStore(mock, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("init dbStore failed: %v", err)
|
t.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
defer db.close()
|
defer db.close()
|
||||||
db.trusted = true
|
|
||||||
testStoreRandom(db, processors, n, chunksize, t)
|
testStoreRandom(db, processors, n, chunksize, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDbStoreCorrect(n int, processors int, chunksize int, mock bool, t *testing.T) {
|
func testDbStoreCorrect(n int, processors int, chunksize int, mock bool, t *testing.T) {
|
||||||
db, err := newTestDbStore(mock)
|
db, err := newTestDbStore(mock, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("init dbStore failed: %v", err)
|
t.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +141,7 @@ func TestMockDbStoreCorrect_8_5k(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDbStoreNotFound(t *testing.T, mock bool) {
|
func testDbStoreNotFound(t *testing.T, mock bool) {
|
||||||
db, err := newTestDbStore(mock)
|
db, err := newTestDbStore(mock, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("init dbStore failed: %v", err)
|
t.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +172,7 @@ func testIterator(t *testing.T, mock bool) {
|
||||||
chunks = append(chunks, NewChunk(nil, nil))
|
chunks = append(chunks, NewChunk(nil, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := newTestDbStore(mock)
|
db, err := newTestDbStore(mock, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("init dbStore failed: %v", err)
|
t.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -224,22 +227,20 @@ func TestMockIterator(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchmarkDbStorePut(n int, processors int, chunksize int, mock bool, b *testing.B) {
|
func benchmarkDbStorePut(n int, processors int, chunksize int, mock bool, b *testing.B) {
|
||||||
db, err := newTestDbStore(mock)
|
db, err := newTestDbStore(mock, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("init dbStore failed: %v", err)
|
b.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
defer db.close()
|
defer db.close()
|
||||||
db.trusted = true
|
|
||||||
benchmarkStorePut(db, processors, n, chunksize, b)
|
benchmarkStorePut(db, processors, n, chunksize, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchmarkDbStoreGet(n int, processors int, chunksize int, mock bool, b *testing.B) {
|
func benchmarkDbStoreGet(n int, processors int, chunksize int, mock bool, b *testing.B) {
|
||||||
db, err := newTestDbStore(mock)
|
db, err := newTestDbStore(mock, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("init dbStore failed: %v", err)
|
b.Fatalf("init dbStore failed: %v", err)
|
||||||
}
|
}
|
||||||
defer db.close()
|
defer db.close()
|
||||||
db.trusted = true
|
|
||||||
benchmarkStoreGet(db, processors, n, chunksize, b)
|
benchmarkStoreGet(db, processors, n, chunksize, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package storage
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -30,57 +31,75 @@ var (
|
||||||
dbStorePutCounter = metrics.NewRegisteredCounter("storage.db.dbstore.put.count", nil)
|
dbStorePutCounter = metrics.NewRegisteredCounter("storage.db.dbstore.put.count", nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type StoreParams struct {
|
type LocalStoreParams struct {
|
||||||
ChunkDbPath string
|
*StoreParams
|
||||||
DbCapacity uint64
|
ChunkDbPath string
|
||||||
CacheCapacity uint
|
Validators []ChunkValidator `toml:"-"`
|
||||||
Radius int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//create params with default values
|
func NewDefaultLocalStoreParams() *LocalStoreParams {
|
||||||
func NewDefaultStoreParams() (self *StoreParams) {
|
return &LocalStoreParams{
|
||||||
return &StoreParams{
|
StoreParams: NewDefaultStoreParams(),
|
||||||
DbCapacity: defaultLDBCapacity,
|
}
|
||||||
CacheCapacity: defaultCacheCapacity,
|
}
|
||||||
|
|
||||||
|
//this can only finally be set after all config options (file, cmd line, env vars)
|
||||||
|
//have been evaluated
|
||||||
|
func (self *LocalStoreParams) Init(path string) {
|
||||||
|
if self.ChunkDbPath == "" {
|
||||||
|
self.ChunkDbPath = filepath.Join(path, "chunks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalStore is a combination of inmemory db over a disk persisted db
|
// LocalStore is a combination of inmemory db over a disk persisted db
|
||||||
// implements a Get/Put with fallback (caching) logic using any 2 ChunkStores
|
// implements a Get/Put with fallback (caching) logic using any 2 ChunkStores
|
||||||
type LocalStore struct {
|
type LocalStore struct {
|
||||||
memStore *MemStore
|
Validators []ChunkValidator
|
||||||
DbStore *LDBStore
|
memStore *MemStore
|
||||||
mu sync.Mutex
|
DbStore *LDBStore
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// This constructor uses MemStore and DbStore as components
|
// This constructor uses MemStore and DbStore as components
|
||||||
func NewLocalStore(hash SwarmHasher, params *StoreParams, basekey []byte, mockStore *mock.NodeStore) (*LocalStore, error) {
|
func NewLocalStore(params *LocalStoreParams, mockStore *mock.NodeStore) (*LocalStore, error) {
|
||||||
dbStore, err := NewMockDbStore(params.ChunkDbPath, hash, params.DbCapacity, func(k Key) (ret uint8) { return uint8(Proximity(basekey[:], k[:])) }, mockStore)
|
ldbparams := NewLDBStoreParams(params.StoreParams, params.ChunkDbPath)
|
||||||
|
dbStore, err := NewMockDbStore(ldbparams, mockStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &LocalStore{
|
return &LocalStore{
|
||||||
memStore: NewMemStore(dbStore, params.CacheCapacity, defaultChunkRequestsCacheCapacity),
|
memStore: NewMemStore(params.StoreParams, dbStore),
|
||||||
DbStore: dbStore,
|
DbStore: dbStore,
|
||||||
|
Validators: params.Validators,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestLocalStoreForAddr(path string, basekey []byte) (*LocalStore, error) {
|
func NewTestLocalStoreForAddr(params *LocalStoreParams) (*LocalStore, error) {
|
||||||
hasher := MakeHashFunc("SHA3")
|
ldbparams := NewLDBStoreParams(params.StoreParams, params.ChunkDbPath)
|
||||||
dbStore, err := NewLDBStore(path, hasher, defaultLDBCapacity, func(k Key) (ret uint8) { return uint8(Proximity(basekey[:], k[:])) })
|
dbStore, err := NewLDBStore(ldbparams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
localStore := &LocalStore{
|
localStore := &LocalStore{
|
||||||
memStore: NewMemStore(dbStore, defaultCacheCapacity, defaultChunkRequestsCacheCapacity),
|
memStore: NewMemStore(params.StoreParams, dbStore),
|
||||||
DbStore: dbStore,
|
DbStore: dbStore,
|
||||||
|
Validators: params.Validators,
|
||||||
}
|
}
|
||||||
return localStore, nil
|
return localStore, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalStore is itself a chunk store
|
|
||||||
// unsafe, in that the data is not integrity checked
|
|
||||||
func (self *LocalStore) Put(chunk *Chunk) {
|
func (self *LocalStore) Put(chunk *Chunk) {
|
||||||
|
valid := true
|
||||||
|
for _, v := range self.Validators {
|
||||||
|
if valid = v.Validate(chunk.Key, chunk.SData); valid {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
chunk.SetErrored(ErrChunkInvalid)
|
||||||
|
chunk.dbStoredC <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
log.Trace("localstore.put", "key", chunk.Key)
|
log.Trace("localstore.put", "key", chunk.Key)
|
||||||
self.mu.Lock()
|
self.mu.Lock()
|
||||||
defer self.mu.Unlock()
|
defer self.mu.Unlock()
|
||||||
|
|
@ -139,11 +158,11 @@ func (self *LocalStore) GetOrCreateRequest(key Key) (chunk *Chunk, created bool)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
chunk, err = self.get(key)
|
chunk, err = self.get(key)
|
||||||
if err == nil && !chunk.GetErrored() {
|
if err == nil && chunk.GetErrored() == nil {
|
||||||
log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v found locally", key))
|
log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v found locally", key))
|
||||||
return chunk, false
|
return chunk, false
|
||||||
}
|
}
|
||||||
if err == ErrFetching && !chunk.GetErrored() {
|
if err == ErrFetching && chunk.GetErrored() == nil {
|
||||||
log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v hit on an existing request %v", key, chunk.ReqC))
|
log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v hit on an existing request %v", key, chunk.ReqC))
|
||||||
return chunk, false
|
return chunk, false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
153
swarm/storage/localstore_test.go
Normal file
153
swarm/storage/localstore_test.go
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
hashfunc = MakeHashFunc("SHA3")
|
||||||
|
)
|
||||||
|
|
||||||
|
// convenience generator for unique chunks
|
||||||
|
type randomChunkGenerator struct {
|
||||||
|
data []byte
|
||||||
|
hasher SwarmHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRandomChunkGenerator(stem []byte) *randomChunkGenerator {
|
||||||
|
gen := &randomChunkGenerator{
|
||||||
|
data: make([]byte, 8),
|
||||||
|
hasher: hashfunc(),
|
||||||
|
}
|
||||||
|
gen.data = append(gen.data, stem...)
|
||||||
|
return gen
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *randomChunkGenerator) newChunk() *Chunk {
|
||||||
|
self.hasher.Reset()
|
||||||
|
self.hasher.Write(self.data)
|
||||||
|
chunk := NewChunk(self.hasher.Sum(nil), nil)
|
||||||
|
chunk.SData = make([]byte, len(self.data))
|
||||||
|
copy(chunk.SData, self.data)
|
||||||
|
self.data[0]++
|
||||||
|
return chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
// put to localstore and wait for stored channel
|
||||||
|
// does not check delivery error state
|
||||||
|
func putChunks(store *LocalStore, chunks ...*Chunk) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(chunks))
|
||||||
|
go func() {
|
||||||
|
for _, c := range chunks {
|
||||||
|
<-c.dbStoredC
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, c := range chunks {
|
||||||
|
go store.Put(c)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// tests that the content address validator correctly checks the data
|
||||||
|
// tests that resource update chunks are passed through content address validator
|
||||||
|
// the test checking the resouce update validator internal correctness is found in resource_test.go
|
||||||
|
func TestValidator(t *testing.T) {
|
||||||
|
bogusData := []byte("00000000bar")
|
||||||
|
|
||||||
|
// set up localstore
|
||||||
|
datadir, err := ioutil.TempDir("", "storage-testvalidator")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(datadir)
|
||||||
|
|
||||||
|
params := NewDefaultLocalStoreParams()
|
||||||
|
params.Init(datadir)
|
||||||
|
store, err := NewLocalStore(params, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check puts with no validators, both succeed
|
||||||
|
gen := newRandomChunkGenerator([]byte("foo"))
|
||||||
|
goodChunk := gen.newChunk()
|
||||||
|
badChunk := gen.newChunk()
|
||||||
|
badChunk.SData = bogusData
|
||||||
|
|
||||||
|
putChunks(store, goodChunk, badChunk)
|
||||||
|
if err := goodChunk.GetErrored(); err != nil {
|
||||||
|
t.Fatalf("expected no error on good content address chunk in spite of no validation, but got: %s", err)
|
||||||
|
}
|
||||||
|
if err := badChunk.GetErrored(); err != nil {
|
||||||
|
t.Fatalf("expected no error on bad content address chunk in spite of no validation, but got: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add content address validator and check puts
|
||||||
|
// bad should fail, good should pass
|
||||||
|
store.Validators = append(store.Validators, NewContentAddressValidator(hashfunc))
|
||||||
|
goodChunk = gen.newChunk()
|
||||||
|
badChunk = gen.newChunk()
|
||||||
|
badChunk.SData = bogusData
|
||||||
|
|
||||||
|
putChunks(store, goodChunk, badChunk)
|
||||||
|
if err := goodChunk.GetErrored(); err != nil {
|
||||||
|
t.Fatalf("expected no error on good content address chunk with content address validator only, but got: %s", err)
|
||||||
|
}
|
||||||
|
if err := badChunk.GetErrored(); err == nil {
|
||||||
|
t.Fatal("expected error on bad content address chunk with content address validator only, but got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// append resource validator to validators and check puts
|
||||||
|
// bad should fail, good should pass, resource should pass
|
||||||
|
rhParams := &ResourceHandlerParams{}
|
||||||
|
rh, err := NewResourceHandler(rhParams)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
store.Validators = append(store.Validators, rh)
|
||||||
|
|
||||||
|
goodChunk = gen.newChunk()
|
||||||
|
key := rh.resourceHash(42, 1, ens.EnsNode("xyzzy.eth"))
|
||||||
|
data := []byte("bar")
|
||||||
|
uglyChunk := newUpdateChunk(key, nil, 42, 1, "xyzzy.eth", data, len(data))
|
||||||
|
|
||||||
|
putChunks(store, goodChunk, badChunk, uglyChunk)
|
||||||
|
if err := goodChunk.GetErrored(); err != nil {
|
||||||
|
t.Fatalf("expected no error on good content address chunk with both validators, but got: %s", err)
|
||||||
|
}
|
||||||
|
if err := badChunk.GetErrored(); err == nil {
|
||||||
|
t.Fatal("expected error on bad chunk address with both validators, but got nil")
|
||||||
|
}
|
||||||
|
if err := uglyChunk.GetErrored(); err != nil {
|
||||||
|
t.Fatalf("expected no error on resource update chunk with both validators, but got: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// (redundant check)
|
||||||
|
// use only resource validator, and check puts
|
||||||
|
// bad should fail, good should fail, resource should pass
|
||||||
|
store.Validators[0] = store.Validators[1]
|
||||||
|
store.Validators = store.Validators[:1]
|
||||||
|
|
||||||
|
goodChunk = gen.newChunk()
|
||||||
|
key = rh.resourceHash(42, 2, ens.EnsNode("xyzzy.eth"))
|
||||||
|
data = []byte("baz")
|
||||||
|
uglyChunk = newUpdateChunk(key, nil, 42, 2, "xyzzy.eth", data, len(data))
|
||||||
|
|
||||||
|
putChunks(store, goodChunk, badChunk, uglyChunk)
|
||||||
|
if goodChunk.GetErrored() == nil {
|
||||||
|
t.Fatal("expected error on good content address chunk with resource validator only, but got nil")
|
||||||
|
}
|
||||||
|
if badChunk.GetErrored() == nil {
|
||||||
|
t.Fatal("expected error on bad content address chunk with resource validator only, but got nil")
|
||||||
|
}
|
||||||
|
if err := uglyChunk.GetErrored(); err != nil {
|
||||||
|
t.Fatalf("expected no error on resource update chunk with resource validator only, but got: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,8 +33,8 @@ type MemStore struct {
|
||||||
disabled bool
|
disabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemStore(_ *LDBStore, cacheCapacity uint, requestsCapacity uint) (m *MemStore) {
|
func NewMemStore(params *StoreParams, _ *LDBStore) (m *MemStore) {
|
||||||
if cacheCapacity == 0 {
|
if params.CacheCapacity == 0 {
|
||||||
return &MemStore{
|
return &MemStore{
|
||||||
disabled: true,
|
disabled: true,
|
||||||
}
|
}
|
||||||
|
|
@ -44,12 +44,12 @@ func NewMemStore(_ *LDBStore, cacheCapacity uint, requestsCapacity uint) (m *Mem
|
||||||
v := value.(*Chunk)
|
v := value.(*Chunk)
|
||||||
<-v.dbStoredC
|
<-v.dbStoredC
|
||||||
}
|
}
|
||||||
c, err := lru.NewWithEvict(int(cacheCapacity), onEvicted)
|
c, err := lru.NewWithEvict(int(params.CacheCapacity), onEvicted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := lru.New(int(requestsCapacity))
|
r, err := lru.New(int(params.ChunkRequestsCacheCapacity))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -98,8 +98,7 @@ func (m *MemStore) Put(c *Chunk) {
|
||||||
if c.ReqC != nil {
|
if c.ReqC != nil {
|
||||||
select {
|
select {
|
||||||
case <-c.ReqC:
|
case <-c.ReqC:
|
||||||
ok := c.GetErrored()
|
if c.GetErrored() != nil {
|
||||||
if !ok {
|
|
||||||
m.requests.Remove(string(c.Key))
|
m.requests.Remove(string(c.Key))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +119,24 @@ func (m *MemStore) setCapacity(n int) {
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
m.disabled = true
|
m.disabled = true
|
||||||
} else {
|
} else {
|
||||||
m = NewMemStore(nil, uint(n), defaultChunkRequestsCacheCapacity)
|
onEvicted := func(key interface{}, value interface{}) {
|
||||||
|
v := value.(*Chunk)
|
||||||
|
<-v.dbStoredC
|
||||||
|
}
|
||||||
|
c, err := lru.NewWithEvict(n, onEvicted)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := lru.New(defaultChunkRequestsCacheCapacity)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m = &MemStore{
|
||||||
|
cache: c,
|
||||||
|
requests: r,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@ func newLDBStore(t *testing.T) (*LDBStore, func()) {
|
||||||
}
|
}
|
||||||
log.Trace("memstore.tempdir", "dir", dir)
|
log.Trace("memstore.tempdir", "dir", dir)
|
||||||
|
|
||||||
db, err := NewLDBStore(dir, MakeHashFunc(SHA3Hash), defaultLDBCapacity, testPoFunc)
|
ldbparams := NewLDBStoreParams(NewDefaultStoreParams(), dir)
|
||||||
|
db, err := NewLDBStore(ldbparams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +58,7 @@ func TestMemStoreAndLDBStore(t *testing.T) {
|
||||||
|
|
||||||
cacheCap := 200
|
cacheCap := 200
|
||||||
requestsCap := 200
|
requestsCap := 200
|
||||||
memStore := NewMemStore(ldb, uint(cacheCap), uint(requestsCap))
|
memStore := NewMemStore(NewStoreParams(4000, 200, 200, nil, nil), nil)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
n int // number of chunks to push to memStore
|
n int // number of chunks to push to memStore
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ package storage
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func newTestMemStore() *MemStore {
|
func newTestMemStore() *MemStore {
|
||||||
return NewMemStore(nil, defaultCacheCapacity, defaultChunkRequestsCacheCapacity)
|
storeparams := NewDefaultStoreParams()
|
||||||
|
return NewMemStore(storeparams, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMemStoreRandom(n int, processors int, chunksize int, t *testing.T) {
|
func testMemStoreRandom(n int, processors int, chunksize int, t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,9 @@ a hash prefix subtree containing subtrees or one storage entry (but never both)
|
||||||
(access[] is a binary tree inside the multi-bit leveled hash tree)
|
(access[] is a binary tree inside the multi-bit leveled hash tree)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func NewMemStore(d *LDBStore, capacity uint) (m *MemStore) {
|
func NewMemStore(params *StoreParams, d *LDBStore) (m *MemStore) {
|
||||||
|
|
||||||
|
capacity := params.CacheCapacity
|
||||||
m = &MemStore{}
|
m = &MemStore{}
|
||||||
m.memtree = newMemTree(memTreeFLW, nil, 0)
|
m.memtree = newMemTree(memTreeFLW, nil, 0)
|
||||||
m.ldbStore = d
|
m.ldbStore = d
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -57,7 +56,7 @@ func (self *NetStore) Get(key Key) (chunk *Chunk, err error) {
|
||||||
err := self.retrieve(chunk)
|
err := self.retrieve(chunk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// mark chunk request as failed so that we can retry it later
|
// mark chunk request as failed so that we can retry it later
|
||||||
chunk.SetErrored(true)
|
chunk.SetErrored(ErrChunkUnavailable)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,22 +68,14 @@ func (self *NetStore) Get(key Key) (chunk *Chunk, err error) {
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
// mark chunk request as failed so that we can retry
|
// mark chunk request as failed so that we can retry
|
||||||
chunk.SetErrored(true)
|
chunk.SetErrored(ErrChunkNotFound)
|
||||||
return nil, ErrChunkNotFound
|
return nil, ErrChunkNotFound
|
||||||
case <-chunk.ReqC:
|
case <-chunk.ReqC:
|
||||||
}
|
}
|
||||||
chunk.SetErrored(false)
|
chunk.SetErrored(nil)
|
||||||
return chunk, nil
|
return chunk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//this can only finally be set after all config options (file, cmd line, env vars)
|
|
||||||
//have been evaluated
|
|
||||||
func (self *StoreParams) Init(path string) {
|
|
||||||
if self.ChunkDbPath == "" {
|
|
||||||
self.ChunkDbPath = filepath.Join(path, "chunks")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put is the entrypoint for local store requests coming from storeLoop
|
// Put is the entrypoint for local store requests coming from storeLoop
|
||||||
func (self *NetStore) Put(chunk *Chunk) {
|
func (self *NetStore) Put(chunk *Chunk) {
|
||||||
self.localStore.Put(chunk)
|
self.localStore.Put(chunk)
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,10 @@ func TestNetstoreFailedRequest(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
params := NewDefaultLocalStoreParams()
|
||||||
localStore, err := NewTestLocalStoreForAddr(datadir, addr.Over())
|
params.Init(datadir)
|
||||||
|
params.BaseKey = addr.Over()
|
||||||
|
localStore, err := NewTestLocalStoreForAddr(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -13,6 +14,7 @@ import (
|
||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -20,11 +22,12 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
signatureLength = 65
|
signatureLength = 65
|
||||||
indexSize = 16
|
indexSize = 18
|
||||||
DbDirName = "resource"
|
DbDirName = "resource"
|
||||||
chunkSize = 4096 // temporary until we implement DPA in the resourcehandler
|
chunkSize = 4096 // temporary until we implement DPA in the resourcehandler
|
||||||
defaultStoreTimeout = 4000 * time.Millisecond
|
defaultStoreTimeout = 4000 * time.Millisecond
|
||||||
hasherCount = 8
|
hasherCount = 8
|
||||||
|
resourceHash = SHA3Hash
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResourceError struct {
|
type ResourceError struct {
|
||||||
|
|
@ -61,10 +64,6 @@ type ResourceLookupParams struct {
|
||||||
Max uint32
|
Max uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignFunc func(common.Hash) (Signature, error)
|
|
||||||
|
|
||||||
type nameHashFunc func(string) common.Hash
|
|
||||||
|
|
||||||
// Encapsulates an specific resource update. When synced it contains the most recent
|
// Encapsulates an specific resource update. When synced it contains the most recent
|
||||||
// version of the resource update data.
|
// version of the resource update data.
|
||||||
type resource struct {
|
type resource struct {
|
||||||
|
|
@ -88,15 +87,6 @@ func (self *resource) NameHash() common.Hash {
|
||||||
return self.nameHash
|
return self.nameHash
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement to activate validation of resource updates
|
|
||||||
// Specifically signing data and verification of signatures
|
|
||||||
type ResourceValidator interface {
|
|
||||||
hashSize() int
|
|
||||||
checkAccess(string, common.Address) (bool, error)
|
|
||||||
NameHash(string) common.Hash // nameHashFunc
|
|
||||||
sign(common.Hash) (Signature, error) // SignFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
type headerGetter interface {
|
type headerGetter interface {
|
||||||
HeaderByNumber(context.Context, string, *big.Int) (*types.Header, error)
|
HeaderByNumber(context.Context, string, *big.Int) (*types.Header, error)
|
||||||
}
|
}
|
||||||
|
|
@ -146,90 +136,123 @@ type headerGetter interface {
|
||||||
// headerlength|period|version|identifier|data
|
// headerlength|period|version|identifier|data
|
||||||
//
|
//
|
||||||
// if a validator is active, the chunk data is:
|
// if a validator is active, the chunk data is:
|
||||||
// sign(resourcedata)|resourcedata
|
// resourcedata|sign(resourcedata)
|
||||||
// otherwise, the chunk data is the same as the resourcedata
|
// otherwise, the chunk data is the same as the resourcedata
|
||||||
//
|
//
|
||||||
// headerlength is a 16 bit value containing the byte length of period|version|name
|
// headerlength is a 16 bit value containing the byte length of period|version|name
|
||||||
// period and version are both 32 bit values. name can have arbitrary length
|
|
||||||
//
|
|
||||||
// NOTE: the following is yet to be implemented
|
|
||||||
// The resource update chunks will be stored in the swarm, but receive special
|
|
||||||
// treatment as their keys do not validate as hashes of their data. They are also
|
|
||||||
// stored using a separate store, and forwarding/syncing protocols carry per-chunk
|
|
||||||
// flags to tell whether the chunk can be validated or not; if not it is to be
|
|
||||||
// treated as a resource update chunk.
|
|
||||||
//
|
//
|
||||||
// TODO: Include modtime in chunk data + signature
|
// TODO: Include modtime in chunk data + signature
|
||||||
type ResourceHandler struct {
|
type ResourceHandler struct {
|
||||||
ChunkStore
|
chunkStore ChunkStore
|
||||||
validator ResourceValidator
|
HashSize int
|
||||||
|
signer ResourceSigner
|
||||||
ethClient headerGetter
|
ethClient headerGetter
|
||||||
|
ensClient *ens.ENS
|
||||||
resources map[string]*resource
|
resources map[string]*resource
|
||||||
hashPool sync.Pool
|
hashPool sync.Pool
|
||||||
resourceLock sync.RWMutex
|
resourceLock sync.RWMutex
|
||||||
nameHash nameHashFunc
|
|
||||||
storeTimeout time.Duration
|
storeTimeout time.Duration
|
||||||
queryMaxPeriods *ResourceLookupParams
|
queryMaxPeriods *ResourceLookupParams
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceHandlerParams struct {
|
type ResourceHandlerParams struct {
|
||||||
Validator ResourceValidator
|
|
||||||
QueryMaxPeriods *ResourceLookupParams
|
QueryMaxPeriods *ResourceLookupParams
|
||||||
|
Signer ResourceSigner
|
||||||
|
EthClient headerGetter
|
||||||
|
EnsClient *ens.ENS
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or open resource update chunk store
|
// Create or open resource update chunk store
|
||||||
func NewResourceHandler(hasher SwarmHasher, chunkStore ChunkStore, ethClient headerGetter, params *ResourceHandlerParams) (*ResourceHandler, error) {
|
func NewResourceHandler(params *ResourceHandlerParams) (*ResourceHandler, error) {
|
||||||
if params.QueryMaxPeriods == nil {
|
if params.QueryMaxPeriods == nil {
|
||||||
params.QueryMaxPeriods = &ResourceLookupParams{
|
params.QueryMaxPeriods = &ResourceLookupParams{
|
||||||
Limit: false,
|
Limit: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rh := &ResourceHandler{
|
rh := &ResourceHandler{
|
||||||
ChunkStore: chunkStore,
|
ethClient: params.EthClient,
|
||||||
ethClient: ethClient,
|
ensClient: params.EnsClient,
|
||||||
resources: make(map[string]*resource),
|
resources: make(map[string]*resource),
|
||||||
validator: params.Validator,
|
|
||||||
storeTimeout: defaultStoreTimeout,
|
storeTimeout: defaultStoreTimeout,
|
||||||
|
signer: params.Signer,
|
||||||
hashPool: sync.Pool{
|
hashPool: sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
return MakeHashFunc(SHA3Hash)()
|
return MakeHashFunc(resourceHash)()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
queryMaxPeriods: params.QueryMaxPeriods,
|
queryMaxPeriods: params.QueryMaxPeriods,
|
||||||
}
|
}
|
||||||
|
|
||||||
if rh.validator != nil {
|
|
||||||
rh.nameHash = rh.validator.NameHash
|
|
||||||
} else {
|
|
||||||
rh.nameHash = func(name string) common.Hash {
|
|
||||||
hasher := rh.hashPool.Get().(SwarmHash)
|
|
||||||
defer rh.hashPool.Put(hasher)
|
|
||||||
hasher.Reset()
|
|
||||||
hasher.Write([]byte(name))
|
|
||||||
hashval := common.BytesToHash(hasher.Sum(nil))
|
|
||||||
log.Debug("generic namehasher", "name", name, "hash", hashval)
|
|
||||||
return hashval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < hasherCount; i++ {
|
for i := 0; i < hasherCount; i++ {
|
||||||
hashfunc := MakeHashFunc(SHA3Hash)()
|
hashfunc := MakeHashFunc(resourceHash)()
|
||||||
|
if rh.HashSize == 0 {
|
||||||
|
rh.HashSize = hashfunc.Size()
|
||||||
|
}
|
||||||
rh.hashPool.Put(hashfunc)
|
rh.hashPool.Put(hashfunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rh, nil
|
return rh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) IsValidated() bool {
|
// Sets the store backend for resource updates
|
||||||
return self.validator == nil
|
func (self *ResourceHandler) SetStore(store ChunkStore) {
|
||||||
|
self.chunkStore = store
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) HashSize() int {
|
// Chunk Validation method (matches ChunkValidatorFunc signature)
|
||||||
return self.validator.hashSize()
|
//
|
||||||
|
// If resource update, owner is checked against ENS record of resource name inferred from chunk data
|
||||||
|
// If parsed signature is nil, validates automatically
|
||||||
|
// If not resource update, it validates are root chunk if length is indexSize and first two bytes are 0
|
||||||
|
func (self *ResourceHandler) Validate(key Key, data []byte) bool {
|
||||||
|
signature, period, version, name, parseddata, err := self.parseUpdate(data)
|
||||||
|
if err != nil {
|
||||||
|
if len(data) == indexSize {
|
||||||
|
if bytes.Equal(data[:2], []byte{0, 0}) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else if signature == nil {
|
||||||
|
if !bytes.Equal(self.resourceHash(period, version, ens.EnsNode(name)), key) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
digest := self.keyDataHash(key, parseddata)
|
||||||
|
addr, err := getAddressFromDataSig(digest, *signature)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ok, _ := self.checkAccess(name, addr)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no ens client is supplied, resource updates are not validated
|
||||||
|
func (self *ResourceHandler) IsValidated() bool {
|
||||||
|
return self.ensClient != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the resource update digest used in signatures
|
||||||
|
func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash {
|
||||||
|
hasher := self.hashPool.Get().(SwarmHash)
|
||||||
|
defer self.hashPool.Put(hasher)
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(key[:])
|
||||||
|
hasher.Write(data)
|
||||||
|
return common.BytesToHash(hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if current address matches owner address of ENS
|
||||||
|
func (self *ResourceHandler) checkAccess(name string, address common.Address) (bool, error) {
|
||||||
|
if self.ensClient == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
owneraddress, err := self.ensClient.Owner(ens.EnsNode(name))
|
||||||
|
return (address == owneraddress), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// get data from current resource
|
// get data from current resource
|
||||||
|
|
||||||
func (self *ResourceHandler) GetContent(name string) (Key, []byte, error) {
|
func (self *ResourceHandler) GetContent(name string) (Key, []byte, error) {
|
||||||
rsrc := self.getResource(name)
|
rsrc := self.getResource(name)
|
||||||
if rsrc == nil || !rsrc.isSynced() {
|
if rsrc == nil || !rsrc.isSynced() {
|
||||||
|
|
@ -278,10 +301,10 @@ func (self *ResourceHandler) NewResource(ctx context.Context, name string, frequ
|
||||||
return nil, NewResourceError(ErrInvalidValue, fmt.Sprintf("Invalid name: '%s'", name))
|
return nil, NewResourceError(ErrInvalidValue, fmt.Sprintf("Invalid name: '%s'", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
nameHash := self.nameHash(name)
|
nameHash := ens.EnsNode(name)
|
||||||
|
|
||||||
if self.validator != nil {
|
if self.signer != nil {
|
||||||
signature, err := self.validator.sign(nameHash)
|
signature, err := self.signer.Sign(nameHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewResourceError(ErrInvalidSignature, fmt.Sprintf("Sign fail: %v", err))
|
return nil, NewResourceError(ErrInvalidSignature, fmt.Sprintf("Sign fail: %v", err))
|
||||||
}
|
}
|
||||||
|
|
@ -289,7 +312,7 @@ func (self *ResourceHandler) NewResource(ctx context.Context, name string, frequ
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewResourceError(ErrInvalidSignature, fmt.Sprintf("Retrieve address from signature fail: %v", err))
|
return nil, NewResourceError(ErrInvalidSignature, fmt.Sprintf("Retrieve address from signature fail: %v", err))
|
||||||
}
|
}
|
||||||
ok, err := self.validator.checkAccess(name, addr)
|
ok, err := self.checkAccess(name, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
|
|
@ -305,15 +328,17 @@ func (self *ResourceHandler) NewResource(ctx context.Context, name string, frequ
|
||||||
|
|
||||||
// chunk with key equal to namehash points to data of first blockheight + update frequency
|
// chunk with key equal to namehash points to data of first blockheight + update frequency
|
||||||
// from this we know from what blockheight we should look for updates, and how often
|
// from this we know from what blockheight we should look for updates, and how often
|
||||||
chunk := NewChunk(Key(nameHash.Bytes()), nil)
|
key := Key(nameHash.Bytes())
|
||||||
|
chunk := NewChunk(key, nil)
|
||||||
chunk.SData = make([]byte, indexSize)
|
chunk.SData = make([]byte, indexSize)
|
||||||
|
|
||||||
|
// root block has first two bytes both, which distinguishes from update bytes
|
||||||
val := make([]byte, 8)
|
val := make([]byte, 8)
|
||||||
binary.LittleEndian.PutUint64(val, currentblock)
|
binary.LittleEndian.PutUint64(val, currentblock)
|
||||||
copy(chunk.SData[:8], val)
|
copy(chunk.SData[2:10], val)
|
||||||
binary.LittleEndian.PutUint64(val, frequency)
|
binary.LittleEndian.PutUint64(val, frequency)
|
||||||
copy(chunk.SData[8:], val)
|
copy(chunk.SData[10:], val)
|
||||||
self.Put(chunk)
|
self.chunkStore.Put(chunk)
|
||||||
log.Debug("new resource", "name", name, "key", nameHash, "startBlock", currentblock, "frequency", frequency)
|
log.Debug("new resource", "name", name, "key", nameHash, "startBlock", currentblock, "frequency", frequency)
|
||||||
|
|
||||||
rsrc := &resource{
|
rsrc := &resource{
|
||||||
|
|
@ -336,12 +361,8 @@ func (self *ResourceHandler) NewResource(ctx context.Context, name string, frequ
|
||||||
// root chunk.
|
// root chunk.
|
||||||
// It is the callers responsibility to make sure that this chunk exists (if the resource
|
// It is the callers responsibility to make sure that this chunk exists (if the resource
|
||||||
// update root data was retrieved externally, it typically doesn't)
|
// update root data was retrieved externally, it typically doesn't)
|
||||||
//
|
|
||||||
// If maxPeriod is -1, the default QueryMaxPeriod from ResourceHandlerParams will be used
|
|
||||||
// if maxPeriod is 0, there will be no limit on period hops
|
|
||||||
// if maxPeriod > 0, the given value will be the limit of period hops
|
|
||||||
func (self *ResourceHandler) LookupVersionByName(ctx context.Context, name string, period uint32, version uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
func (self *ResourceHandler) LookupVersionByName(ctx context.Context, name string, period uint32, version uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
||||||
return self.LookupVersion(ctx, self.nameHash(name), name, period, version, refresh, maxLookup)
|
return self.LookupVersion(ctx, ens.EnsNode(name), name, period, version, refresh, maxLookup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) LookupVersion(ctx context.Context, nameHash common.Hash, name string, period uint32, version uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
func (self *ResourceHandler) LookupVersion(ctx context.Context, nameHash common.Hash, name string, period uint32, version uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
||||||
|
|
@ -361,7 +382,7 @@ func (self *ResourceHandler) LookupVersion(ctx context.Context, nameHash common.
|
||||||
//
|
//
|
||||||
// See also (*ResourceHandler).LookupVersion
|
// See also (*ResourceHandler).LookupVersion
|
||||||
func (self *ResourceHandler) LookupHistoricalByName(ctx context.Context, name string, period uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
func (self *ResourceHandler) LookupHistoricalByName(ctx context.Context, name string, period uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
||||||
return self.LookupHistorical(ctx, self.nameHash(name), name, period, refresh, maxLookup)
|
return self.LookupHistorical(ctx, ens.EnsNode(name), name, period, refresh, maxLookup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) LookupHistorical(ctx context.Context, nameHash common.Hash, name string, period uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
func (self *ResourceHandler) LookupHistorical(ctx context.Context, nameHash common.Hash, name string, period uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
||||||
|
|
@ -383,7 +404,7 @@ func (self *ResourceHandler) LookupHistorical(ctx context.Context, nameHash comm
|
||||||
//
|
//
|
||||||
// See also (*ResourceHandler).LookupHistorical
|
// See also (*ResourceHandler).LookupHistorical
|
||||||
func (self *ResourceHandler) LookupLatestByName(ctx context.Context, name string, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
func (self *ResourceHandler) LookupLatestByName(ctx context.Context, name string, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
||||||
return self.LookupLatest(ctx, self.nameHash(name), name, refresh, maxLookup)
|
return self.LookupLatest(ctx, ens.EnsNode(name), name, refresh, maxLookup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.Hash, name string, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.Hash, name string, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
||||||
|
|
@ -404,6 +425,10 @@ func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.H
|
||||||
// base code for public lookup methods
|
// base code for public lookup methods
|
||||||
func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) {
|
||||||
|
|
||||||
|
if self.chunkStore == nil {
|
||||||
|
return nil, NewResourceError(ErrInit, "Call ResourceHandler.SetStore() before performing lookups")
|
||||||
|
}
|
||||||
|
|
||||||
if period == 0 {
|
if period == 0 {
|
||||||
return nil, NewResourceError(ErrInvalidValue, "period must be >0")
|
return nil, NewResourceError(ErrInvalidValue, "period must be >0")
|
||||||
}
|
}
|
||||||
|
|
@ -426,7 +451,7 @@ func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint3
|
||||||
return nil, NewResourceError(ErrPeriodDepth, fmt.Sprintf("Lookup exceeded max period hops (%d)", maxLookup.Max))
|
return nil, NewResourceError(ErrPeriodDepth, fmt.Sprintf("Lookup exceeded max period hops (%d)", maxLookup.Max))
|
||||||
}
|
}
|
||||||
key := self.resourceHash(period, version, rsrc.nameHash)
|
key := self.resourceHash(period, version, rsrc.nameHash)
|
||||||
chunk, err := self.Get(key)
|
chunk, err := self.chunkStore.Get(key)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if specificversion {
|
if specificversion {
|
||||||
return self.updateResourceIndex(rsrc, chunk)
|
return self.updateResourceIndex(rsrc, chunk)
|
||||||
|
|
@ -436,7 +461,7 @@ func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint3
|
||||||
for {
|
for {
|
||||||
newversion := version + 1
|
newversion := version + 1
|
||||||
key := self.resourceHash(period, newversion, rsrc.nameHash)
|
key := self.resourceHash(period, newversion, rsrc.nameHash)
|
||||||
newchunk, err := self.Get(key)
|
newchunk, err := self.chunkStore.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return self.updateResourceIndex(rsrc, chunk)
|
return self.updateResourceIndex(rsrc, chunk)
|
||||||
}
|
}
|
||||||
|
|
@ -472,7 +497,7 @@ func (self *ResourceHandler) loadResource(nameHash common.Hash, name string, ref
|
||||||
rsrc.nameHash = nameHash
|
rsrc.nameHash = nameHash
|
||||||
|
|
||||||
// get the root info chunk and update the cached value
|
// get the root info chunk and update the cached value
|
||||||
chunk, err := self.Get(Key(rsrc.nameHash[:]))
|
chunk, err := self.chunkStore.Get(Key(rsrc.nameHash[:]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -481,8 +506,8 @@ func (self *ResourceHandler) loadResource(nameHash common.Hash, name string, ref
|
||||||
if len(chunk.SData) != indexSize {
|
if len(chunk.SData) != indexSize {
|
||||||
return nil, NewResourceError(ErrNothingToReturn, fmt.Sprintf("Invalid chunk length %d, should be %d", len(chunk.SData), indexSize))
|
return nil, NewResourceError(ErrNothingToReturn, fmt.Sprintf("Invalid chunk length %d, should be %d", len(chunk.SData), indexSize))
|
||||||
}
|
}
|
||||||
rsrc.startBlock = binary.LittleEndian.Uint64(chunk.SData[:8])
|
rsrc.startBlock = binary.LittleEndian.Uint64(chunk.SData[2:10])
|
||||||
rsrc.frequency = binary.LittleEndian.Uint64(chunk.SData[8:])
|
rsrc.frequency = binary.LittleEndian.Uint64(chunk.SData[10:])
|
||||||
} else {
|
} else {
|
||||||
rsrc.name = self.resources[name].name
|
rsrc.name = self.resources[name].name
|
||||||
rsrc.nameHash = self.resources[name].nameHash
|
rsrc.nameHash = self.resources[name].nameHash
|
||||||
|
|
@ -501,8 +526,8 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk) (
|
||||||
return nil, NewResourceError(ErrNothingToReturn, fmt.Sprintf("Update belongs to '%s', but have '%s'", name, *rsrc.name))
|
return nil, NewResourceError(ErrNothingToReturn, fmt.Sprintf("Update belongs to '%s', but have '%s'", name, *rsrc.name))
|
||||||
}
|
}
|
||||||
log.Trace("update", "name", *rsrc.name, "rootkey", rsrc.nameHash, "updatekey", chunk.Key, "period", period, "version", version)
|
log.Trace("update", "name", *rsrc.name, "rootkey", rsrc.nameHash, "updatekey", chunk.Key, "period", period, "version", version)
|
||||||
// only check signature if validator is present
|
// check signature (if signer algorithm is present)
|
||||||
if self.validator != nil {
|
if signature != nil {
|
||||||
digest := self.keyDataHash(chunk.Key, data)
|
digest := self.keyDataHash(chunk.Key, data)
|
||||||
_, err = getAddressFromDataSig(digest, *signature)
|
_, err = getAddressFromDataSig(digest, *signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -525,11 +550,16 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk) (
|
||||||
// retrieve update metadata from chunk data
|
// retrieve update metadata from chunk data
|
||||||
// mirrors newUpdateChunk()
|
// mirrors newUpdateChunk()
|
||||||
func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32, uint32, string, []byte, error) {
|
func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32, uint32, string, []byte, error) {
|
||||||
|
// 14 = header + one byte of name + one byte of data
|
||||||
|
if len(chunkdata) < 14 {
|
||||||
|
return nil, 0, 0, "", nil, NewResourceError(ErrNothingToReturn, "chunk less than 13 bytes cannot be a resource update chunk")
|
||||||
|
}
|
||||||
cursor := 0
|
cursor := 0
|
||||||
headerlength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
|
headerlength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
|
||||||
cursor += 2
|
cursor += 2
|
||||||
datalength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
|
datalength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
|
||||||
if int(headerlength+datalength+4) > len(chunkdata) {
|
exclsignlength := int(headerlength + datalength + 4)
|
||||||
|
if exclsignlength > len(chunkdata) || exclsignlength < 14 {
|
||||||
return nil, 0, 0, "", nil, NewResourceError(ErrNothingToReturn, fmt.Sprintf("Reported headerlength %d + datalength %d longer than actual chunk data length %d", headerlength, datalength, len(chunkdata)))
|
return nil, 0, 0, "", nil, NewResourceError(ErrNothingToReturn, fmt.Sprintf("Reported headerlength %d + datalength %d longer than actual chunk data length %d", headerlength, datalength, len(chunkdata)))
|
||||||
}
|
}
|
||||||
var period uint32
|
var period uint32
|
||||||
|
|
@ -560,10 +590,13 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32,
|
||||||
|
|
||||||
// omit signatures if we have no validator
|
// omit signatures if we have no validator
|
||||||
var signature *Signature
|
var signature *Signature
|
||||||
if self.validator != nil {
|
cursor += intdatalength
|
||||||
cursor += intdatalength
|
if self.signer != nil {
|
||||||
signature = &Signature{}
|
sigdata := chunkdata[cursor : cursor+signatureLength]
|
||||||
copy(signature[:], chunkdata[cursor:cursor+signatureLength])
|
if len(sigdata) > 0 {
|
||||||
|
signature = &Signature{}
|
||||||
|
copy(signature[:], sigdata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return signature, period, version, name, data, nil
|
return signature, period, version, name, data, nil
|
||||||
|
|
@ -589,8 +622,11 @@ func (self *ResourceHandler) Update(ctx context.Context, name string, data []byt
|
||||||
|
|
||||||
func (self *ResourceHandler) update(ctx context.Context, name string, data []byte, multihash bool) (Key, error) {
|
func (self *ResourceHandler) update(ctx context.Context, name string, data []byte, multihash bool) (Key, error) {
|
||||||
|
|
||||||
|
if self.chunkStore == nil {
|
||||||
|
return nil, NewResourceError(ErrInit, "Call ResourceHandler.SetStore() before updating")
|
||||||
|
}
|
||||||
var signaturelength int
|
var signaturelength int
|
||||||
if self.validator != nil {
|
if self.signer != nil {
|
||||||
signaturelength = signatureLength
|
signaturelength = signatureLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -628,10 +664,10 @@ func (self *ResourceHandler) update(ctx context.Context, name string, data []byt
|
||||||
key := self.resourceHash(nextperiod, version, rsrc.nameHash)
|
key := self.resourceHash(nextperiod, version, rsrc.nameHash)
|
||||||
|
|
||||||
var signature *Signature
|
var signature *Signature
|
||||||
if self.validator != nil {
|
if self.signer != nil {
|
||||||
// sign the data hash with the key
|
// sign the data hash with the key
|
||||||
digest := self.keyDataHash(key, data)
|
digest := self.keyDataHash(key, data)
|
||||||
sig, err := self.validator.sign(digest)
|
sig, err := self.signer.Sign(digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewResourceError(ErrInvalidSignature, fmt.Sprintf("Sign fail: %v", err))
|
return nil, NewResourceError(ErrInvalidSignature, fmt.Sprintf("Sign fail: %v", err))
|
||||||
}
|
}
|
||||||
|
|
@ -642,13 +678,14 @@ func (self *ResourceHandler) update(ctx context.Context, name string, data []byt
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewResourceError(ErrInvalidSignature, fmt.Sprintf("Invalid data/signature: %v", err))
|
return nil, NewResourceError(ErrInvalidSignature, fmt.Sprintf("Invalid data/signature: %v", err))
|
||||||
}
|
}
|
||||||
|
if self.signer != nil {
|
||||||
// check if the signer has access to update
|
// check if the signer has access to update
|
||||||
ok, err := self.validator.checkAccess(name, addr)
|
ok, err := self.checkAccess(name, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewResourceError(ErrIO, fmt.Sprintf("Access check fail: %v", err))
|
return nil, NewResourceError(ErrIO, fmt.Sprintf("Access check fail: %v", err))
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
return nil, NewResourceError(ErrUnauthorized, fmt.Sprintf("Address %x does not have access to update %s", addr, name))
|
return nil, NewResourceError(ErrUnauthorized, fmt.Sprintf("Address %x does not have access to update %s", addr, name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -659,7 +696,7 @@ func (self *ResourceHandler) update(ctx context.Context, name string, data []byt
|
||||||
chunk := newUpdateChunk(key, signature, nextperiod, version, name, data, datalength)
|
chunk := newUpdateChunk(key, signature, nextperiod, version, name, data, datalength)
|
||||||
|
|
||||||
// send the chunk
|
// send the chunk
|
||||||
self.Put(chunk)
|
self.chunkStore.Put(chunk)
|
||||||
timeout := time.NewTimer(self.storeTimeout)
|
timeout := time.NewTimer(self.storeTimeout)
|
||||||
select {
|
select {
|
||||||
case <-chunk.dbStoredC:
|
case <-chunk.dbStoredC:
|
||||||
|
|
@ -679,7 +716,7 @@ func (self *ResourceHandler) update(ctx context.Context, name string, data []byt
|
||||||
// Closes the datastore.
|
// Closes the datastore.
|
||||||
// Always call this at shutdown to avoid data corruption.
|
// Always call this at shutdown to avoid data corruption.
|
||||||
func (self *ResourceHandler) Close() {
|
func (self *ResourceHandler) Close() {
|
||||||
self.ChunkStore.Close()
|
self.chunkStore.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) getBlock(ctx context.Context, name string) (uint64, error) {
|
func (self *ResourceHandler) getBlock(ctx context.Context, name string) (uint64, error) {
|
||||||
|
|
@ -789,52 +826,6 @@ func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32
|
||||||
return chunk
|
return chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
// \TODO chunkSize is a workaround until the ChunkStore interface exports a method to get the chunk size directly
|
|
||||||
type resourceChunkStore struct {
|
|
||||||
localStore ChunkStore
|
|
||||||
netStore ChunkStore
|
|
||||||
chunkSize int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewResourceChunkStore(localStore *LocalStore, request func(*Chunk) error) ChunkStore {
|
|
||||||
return &resourceChunkStore{
|
|
||||||
localStore: localStore,
|
|
||||||
netStore: NewNetStore(localStore, request),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resourceChunkStore) Get(key Key) (*Chunk, error) {
|
|
||||||
chunk, err := r.netStore.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// if the chunk has to be remotely retrieved, we define a timeout of how long to wait for it before failing.
|
|
||||||
// sadly due to the nature of swarm, the error will never be conclusive as to whether it was a network issue
|
|
||||||
// that caused the failure or that the chunk doesn't exist.
|
|
||||||
if chunk.ReqC == nil {
|
|
||||||
return chunk, nil
|
|
||||||
}
|
|
||||||
t := time.NewTimer(time.Second * 1)
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
log.Trace("Timeout on resource chunk store")
|
|
||||||
return nil, fmt.Errorf("timeout")
|
|
||||||
case <-chunk.C:
|
|
||||||
log.Trace("Received resource update chunk")
|
|
||||||
}
|
|
||||||
return chunk, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resourceChunkStore) Put(chunk *Chunk) {
|
|
||||||
r.netStore.Put(chunk)
|
|
||||||
chunk.WaitToStore()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resourceChunkStore) Close() {
|
|
||||||
r.netStore.Close()
|
|
||||||
r.localStore.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNextPeriod(start uint64, current uint64, frequency uint64) uint32 {
|
func getNextPeriod(start uint64, current uint64, frequency uint64) uint32 {
|
||||||
blockdiff := current - start
|
blockdiff := current - start
|
||||||
period := blockdiff / frequency
|
period := blockdiff / frequency
|
||||||
|
|
@ -857,16 +848,6 @@ func isSafeName(name string) bool {
|
||||||
return validname == name
|
return validname == name
|
||||||
}
|
}
|
||||||
|
|
||||||
// convenience for creating signature hashes of update data
|
|
||||||
func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash {
|
|
||||||
hasher := self.hashPool.Get().(SwarmHash)
|
|
||||||
defer self.hashPool.Put(hasher)
|
|
||||||
hasher.Reset()
|
|
||||||
hasher.Write(key[:])
|
|
||||||
hasher.Write(data)
|
|
||||||
return common.BytesToHash(hasher.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// if first byte is the start of a multihash this function will try to parse it
|
// if first byte is the start of a multihash this function will try to parse it
|
||||||
// if successful it returns the length of multihash data, 0 otherwise
|
// if successful it returns the length of multihash data, 0 otherwise
|
||||||
func isMultihash(data []byte) int {
|
func isMultihash(data []byte) int {
|
||||||
|
|
@ -892,29 +873,20 @@ func isMultihash(data []byte) int {
|
||||||
return cursor + inthashlength
|
return cursor + inthashlength
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this should not be part of production code, but currently swarm/testutil/http.go needs it
|
// TODO: this should not be exposed, but swarm/testutil/http.go needs it
|
||||||
func NewTestResourceHandler(datadir string, ethClient headerGetter, validator ResourceValidator, maxLimit *ResourceLookupParams) (*ResourceHandler, error) {
|
func NewTestResourceHandler(datadir string, params *ResourceHandlerParams) (*ResourceHandler, error) {
|
||||||
path := filepath.Join(datadir, DbDirName)
|
path := filepath.Join(datadir, DbDirName)
|
||||||
basekey := make([]byte, 32)
|
rh, err := NewResourceHandler(params)
|
||||||
hasher := MakeHashFunc(SHA3Hash)
|
|
||||||
dbStore, err := NewLDBStore(path, hasher, defaultLDBCapacity, func(k Key) (ret uint8) { return uint8(Proximity(basekey[:], k[:])) })
|
|
||||||
dbStore.SetTrusted()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
localStore := &LocalStore{
|
localstoreparams := NewDefaultLocalStoreParams()
|
||||||
memStore: NewMemStore(dbStore, defaultCacheCapacity, defaultChunkRequestsCacheCapacity),
|
localstoreparams.Init(path)
|
||||||
DbStore: dbStore,
|
localStore, err := NewLocalStore(localstoreparams, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
resourceChunkStore := NewResourceChunkStore(localStore, nil)
|
localStore.Validators = append(localStore.Validators, rh)
|
||||||
if maxLimit == nil {
|
rh.SetStore(localStore)
|
||||||
maxLimit = &ResourceLookupParams{
|
return rh, nil
|
||||||
Limit: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
params := &ResourceHandlerParams{
|
|
||||||
Validator: validator,
|
|
||||||
QueryMaxPeriods: maxLimit,
|
|
||||||
}
|
|
||||||
return NewResourceHandler(hasher, resourceChunkStore, ethClient, params)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/contracts/ens"
|
|
||||||
)
|
|
||||||
|
|
||||||
type baseValidator struct {
|
|
||||||
signFunc SignFunc
|
|
||||||
hashsize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *baseValidator) sign(datahash common.Hash) (signature Signature, err error) {
|
|
||||||
if b.signFunc == nil {
|
|
||||||
return signature, errors.New("No signature function")
|
|
||||||
}
|
|
||||||
return b.signFunc(datahash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *baseValidator) hashSize() int {
|
|
||||||
return b.hashsize
|
|
||||||
}
|
|
||||||
|
|
||||||
type OwnerValidator interface {
|
|
||||||
ValidateOwner(name string, address common.Address) (bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ENS validation of mutable resource owners
|
|
||||||
type ENSValidator struct {
|
|
||||||
*baseValidator
|
|
||||||
api OwnerValidator
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewENSValidator(contractaddress common.Address, ownerValidator OwnerValidator, signFunc SignFunc) *ENSValidator {
|
|
||||||
return &ENSValidator{
|
|
||||||
baseValidator: &baseValidator{
|
|
||||||
signFunc: signFunc,
|
|
||||||
hashsize: common.HashLength,
|
|
||||||
},
|
|
||||||
api: ownerValidator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ENSValidator) checkAccess(name string, address common.Address) (bool, error) {
|
|
||||||
return self.api.ValidateOwner(name, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ENSValidator) NameHash(name string) common.Hash {
|
|
||||||
return ens.EnsNode(name)
|
|
||||||
}
|
|
||||||
|
|
@ -7,14 +7,20 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// matches the SignFunc type
|
// Signs resource updates
|
||||||
func NewGenericResourceSigner(privKey *ecdsa.PrivateKey) SignFunc {
|
type ResourceSigner interface {
|
||||||
return func(data common.Hash) (signature Signature, err error) {
|
Sign(common.Hash) (Signature, error)
|
||||||
signaturebytes, err := crypto.Sign(data.Bytes(), privKey)
|
}
|
||||||
if err != nil {
|
|
||||||
return
|
type GenericResourceSigner struct {
|
||||||
}
|
PrivKey *ecdsa.PrivateKey
|
||||||
copy(signature[:], signaturebytes)
|
}
|
||||||
|
|
||||||
|
func (self *GenericResourceSigner) Sign(data common.Hash) (signature Signature, err error) {
|
||||||
|
signaturebytes, err := crypto.Sign(data.Bytes(), self.PrivKey)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
copy(signature[:], signaturebytes)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package storage
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"flag"
|
"flag"
|
||||||
|
|
@ -83,14 +82,14 @@ func TestResourceReverse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up rpc and create resourcehandler
|
// set up rpc and create resourcehandler
|
||||||
rh, _, _, teardownTest, err := setupTest(nil, newTestValidator(signer.signContent))
|
rh, _, teardownTest, err := setupTest(nil, nil, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer teardownTest()
|
defer teardownTest()
|
||||||
|
|
||||||
// generate a hash for block 4200 version 1
|
// generate a hash for block 4200 version 1
|
||||||
key := rh.resourceHash(period, version, rh.nameHash(safeName))
|
key := rh.resourceHash(period, version, ens.EnsNode(safeName))
|
||||||
|
|
||||||
// generate some bogus data for the chunk and sign it
|
// generate some bogus data for the chunk and sign it
|
||||||
data := make([]byte, 8)
|
data := make([]byte, 8)
|
||||||
|
|
@ -101,7 +100,7 @@ func TestResourceReverse(t *testing.T) {
|
||||||
testHasher.Reset()
|
testHasher.Reset()
|
||||||
testHasher.Write(data)
|
testHasher.Write(data)
|
||||||
digest := rh.keyDataHash(key, data)
|
digest := rh.keyDataHash(key, data)
|
||||||
sig, err := rh.validator.sign(digest)
|
sig, err := rh.signer.Sign(digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +117,7 @@ func TestResourceReverse(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Retrieve address from signature fail: %v", err)
|
t.Fatalf("Retrieve address from signature fail: %v", err)
|
||||||
}
|
}
|
||||||
originaladdress := crypto.PubkeyToAddress(signer.privKey.PublicKey)
|
originaladdress := crypto.PubkeyToAddress(signer.PrivKey.PublicKey)
|
||||||
|
|
||||||
// check that the metadata retrieved from the chunk matches what we gave it
|
// check that the metadata retrieved from the chunk matches what we gave it
|
||||||
if recoveredaddress != originaladdress {
|
if recoveredaddress != originaladdress {
|
||||||
|
|
@ -149,7 +148,7 @@ func TestResourceHandler(t *testing.T) {
|
||||||
backend := &fakeBackend{
|
backend := &fakeBackend{
|
||||||
blocknumber: int64(startBlock),
|
blocknumber: int64(startBlock),
|
||||||
}
|
}
|
||||||
rh, datadir, _, teardownTest, err := setupTest(backend, nil)
|
rh, datadir, teardownTest, err := setupTest(backend, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -164,15 +163,15 @@ func TestResourceHandler(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that the new resource is stored correctly
|
// check that the new resource is stored correctly
|
||||||
namehash := rh.nameHash(safeName)
|
namehash := ens.EnsNode(safeName)
|
||||||
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(namehash[:]))
|
chunk, err := rh.chunkStore.(*LocalStore).memStore.Get(Key(namehash[:]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if len(chunk.SData) < 16 {
|
} else if len(chunk.SData) < 16 {
|
||||||
t.Fatalf("chunk data must be minimum 16 bytes, is %d", len(chunk.SData))
|
t.Fatalf("chunk data must be minimum 16 bytes, is %d", len(chunk.SData))
|
||||||
}
|
}
|
||||||
startblocknumber := binary.LittleEndian.Uint64(chunk.SData[:8])
|
startblocknumber := binary.LittleEndian.Uint64(chunk.SData[2:10])
|
||||||
chunkfrequency := binary.LittleEndian.Uint64(chunk.SData[8:])
|
chunkfrequency := binary.LittleEndian.Uint64(chunk.SData[10:])
|
||||||
if startblocknumber != uint64(backend.blocknumber) {
|
if startblocknumber != uint64(backend.blocknumber) {
|
||||||
t.Fatalf("stored block number %d does not match provided block number %d", startblocknumber, backend.blocknumber)
|
t.Fatalf("stored block number %d does not match provided block number %d", startblocknumber, backend.blocknumber)
|
||||||
}
|
}
|
||||||
|
|
@ -219,10 +218,14 @@ func TestResourceHandler(t *testing.T) {
|
||||||
// it will match on second iteration startblocknumber + (resourceFrequency * 3)
|
// it will match on second iteration startblocknumber + (resourceFrequency * 3)
|
||||||
fwdBlocks(int(resourceFrequency*2)-1, backend)
|
fwdBlocks(int(resourceFrequency*2)-1, backend)
|
||||||
|
|
||||||
lookupParams := &ResourceLookupParams{
|
rhparams := &ResourceHandlerParams{
|
||||||
Limit: false,
|
QueryMaxPeriods: &ResourceLookupParams{
|
||||||
|
Limit: false,
|
||||||
|
},
|
||||||
|
Signer: nil,
|
||||||
|
EthClient: rh.ethClient,
|
||||||
}
|
}
|
||||||
rh2, err := NewTestResourceHandler(datadir, rh.ethClient, nil, lookupParams)
|
rh2, err := NewTestResourceHandler(datadir, rhparams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +247,7 @@ func TestResourceHandler(t *testing.T) {
|
||||||
log.Debug("Latest lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
|
log.Debug("Latest lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
|
||||||
|
|
||||||
// specific block, latest version
|
// specific block, latest version
|
||||||
rsrc, err := rh2.LookupHistoricalByName(ctx, safeName, 3, true, lookupParams)
|
rsrc, err := rh2.LookupHistoricalByName(ctx, safeName, 3, true, rh2.queryMaxPeriods)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +258,7 @@ func TestResourceHandler(t *testing.T) {
|
||||||
log.Debug("Historical lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
|
log.Debug("Historical lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
|
||||||
|
|
||||||
// specific block, specific version
|
// specific block, specific version
|
||||||
rsrc, err = rh2.LookupVersionByName(ctx, safeName, 3, 1, true, lookupParams)
|
rsrc, err = rh2.LookupVersionByName(ctx, safeName, 3, 1, true, rh2.queryMaxPeriods)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -267,6 +270,65 @@ func TestResourceHandler(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create ENS enabled resource update, with and without valid owner
|
||||||
|
func TestResourceENSOwner(t *testing.T) {
|
||||||
|
|
||||||
|
// signer containing private key
|
||||||
|
signer, err := newTestSigner()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ens address and transact options
|
||||||
|
addr := crypto.PubkeyToAddress(signer.PrivKey.PublicKey)
|
||||||
|
transactOpts := bind.NewKeyedTransactor(signer.PrivKey)
|
||||||
|
|
||||||
|
// set up ENS sim
|
||||||
|
domainparts := strings.Split(safeName, ".")
|
||||||
|
contractAddr, contractbackend, err := setupENS(addr, transactOpts, domainparts[0], domainparts[1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ensClient, err := ens.NewENS(transactOpts, contractAddr, contractbackend)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up rpc and create resourcehandler with ENS sim backend
|
||||||
|
rh, _, teardownTest, err := setupTest(contractbackend, ensClient, signer)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer teardownTest()
|
||||||
|
|
||||||
|
// create new resource when we are owner = ok
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
_, err = rh.NewResource(ctx, safeName, resourceFrequency)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Create resource fail: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []byte("foo")
|
||||||
|
// update resource when we are owner = ok
|
||||||
|
_, err = rh.Update(ctx, safeName, data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Update resource fail: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update resource when we are not owner = !ok
|
||||||
|
signertwo, err := newTestSigner()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rh.signer = signertwo
|
||||||
|
_, err = rh.Update(ctx, safeName, data)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected resource update fail due to owner mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResourceMultihash(t *testing.T) {
|
func TestResourceMultihash(t *testing.T) {
|
||||||
|
|
||||||
// signer containing private key
|
// signer containing private key
|
||||||
|
|
@ -274,7 +336,6 @@ func TestResourceMultihash(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
validator := newTestValidator(signer.signContent)
|
|
||||||
|
|
||||||
// make fake backend, set up rpc and create resourcehandler
|
// make fake backend, set up rpc and create resourcehandler
|
||||||
backend := &fakeBackend{
|
backend := &fakeBackend{
|
||||||
|
|
@ -282,7 +343,7 @@ func TestResourceMultihash(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up rpc and create resourcehandler
|
// set up rpc and create resourcehandler
|
||||||
rh, datadir, _, teardownTest, err := setupTest(backend, nil)
|
rh, datadir, teardownTest, err := setupTest(backend, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +359,7 @@ func TestResourceMultihash(t *testing.T) {
|
||||||
|
|
||||||
// we're naïvely assuming keccak256 for swarm hashes
|
// we're naïvely assuming keccak256 for swarm hashes
|
||||||
// if it ever changes this test should also change
|
// if it ever changes this test should also change
|
||||||
swarmhashbytes := rh.nameHash("foo")
|
swarmhashbytes := ens.EnsNode("foo")
|
||||||
swarmhashmulti, err := multihash.Encode(swarmhashbytes.Bytes(), multihash.KECCAK_256)
|
swarmhashmulti, err := multihash.Encode(swarmhashbytes.Bytes(), multihash.KECCAK_256)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -352,8 +413,16 @@ func TestResourceMultihash(t *testing.T) {
|
||||||
}
|
}
|
||||||
rh.Close()
|
rh.Close()
|
||||||
|
|
||||||
|
rhparams := &ResourceHandlerParams{
|
||||||
|
QueryMaxPeriods: &ResourceLookupParams{
|
||||||
|
Limit: false,
|
||||||
|
},
|
||||||
|
Signer: signer,
|
||||||
|
EthClient: rh.ethClient,
|
||||||
|
EnsClient: rh.ensClient,
|
||||||
|
}
|
||||||
// test with signed data
|
// test with signed data
|
||||||
rh2, err := NewTestResourceHandler(datadir, rh.ethClient, validator, nil)
|
rh2, err := NewTestResourceHandler(datadir, rhparams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -394,9 +463,7 @@ func TestResourceMultihash(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create ENS enabled resource update, with and without valid owner
|
func TestResourceChunkValidator(t *testing.T) {
|
||||||
func TestResourceENSOwner(t *testing.T) {
|
|
||||||
|
|
||||||
// signer containing private key
|
// signer containing private key
|
||||||
signer, err := newTestSigner()
|
signer, err := newTestSigner()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -404,8 +471,8 @@ func TestResourceENSOwner(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ens address and transact options
|
// ens address and transact options
|
||||||
addr := crypto.PubkeyToAddress(signer.privKey.PublicKey)
|
addr := crypto.PubkeyToAddress(signer.PrivKey.PublicKey)
|
||||||
transactOpts := bind.NewKeyedTransactor(signer.privKey)
|
transactOpts := bind.NewKeyedTransactor(signer.PrivKey)
|
||||||
|
|
||||||
// set up ENS sim
|
// set up ENS sim
|
||||||
domainparts := strings.Split(safeName, ".")
|
domainparts := strings.Split(safeName, ".")
|
||||||
|
|
@ -418,10 +485,9 @@ func TestResourceENSOwner(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
validator := NewENSValidator(contractAddr, newTestResolver(ensClient), signer.signContent)
|
|
||||||
|
|
||||||
// set up rpc and create resourcehandler with ENS sim backend
|
// set up rpc and create resourcehandler with ENS sim backend
|
||||||
rh, _, _, teardownTest, err := setupTest(contractbackend, validator)
|
rh, _, teardownTest, err := setupTest(contractbackend, ensClient, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -430,27 +496,21 @@ func TestResourceENSOwner(t *testing.T) {
|
||||||
// create new resource when we are owner = ok
|
// create new resource when we are owner = ok
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
_, err = rh.NewResource(ctx, safeName, resourceFrequency)
|
rsrc, err := rh.NewResource(ctx, safeName, resourceFrequency)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create resource fail: %v", err)
|
t.Fatalf("Create resource fail: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := []byte("foo")
|
data := []byte("foo")
|
||||||
// update resource when we are owner = ok
|
key := rh.resourceHash(1, 1, rsrc.nameHash)
|
||||||
_, err = rh.Update(ctx, safeName, data)
|
digest := rh.keyDataHash(key, data)
|
||||||
|
sig, err := rh.signer.Sign(digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Update resource fail: %v", err)
|
t.Fatalf("sign fail: %v", err)
|
||||||
}
|
}
|
||||||
|
chunk := newUpdateChunk(key, &sig, 1, 1, safeName, data, len(data))
|
||||||
// update resource when we are owner = ok
|
if !rh.Validate(chunk.Key, chunk.SData) {
|
||||||
signertwo, err := newTestSigner()
|
t.Fatal("Chunk validator fail")
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
rh.validator.(*ENSValidator).signFunc = signertwo.signContent
|
|
||||||
_, err = rh.Update(ctx, safeName, data)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected resource update fail due to owner mismatch")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -462,7 +522,7 @@ func fwdBlocks(count int, backend *fakeBackend) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create rpc and resourcehandler
|
// create rpc and resourcehandler
|
||||||
func setupTest(backend headerGetter, validator ResourceValidator) (rh *ResourceHandler, datadir string, signer *testSigner, teardown func(), err error) {
|
func setupTest(backend headerGetter, ensBackend *ens.ENS, signer ResourceSigner) (rh *ResourceHandler, datadir string, teardown func(), err error) {
|
||||||
|
|
||||||
var fsClean func()
|
var fsClean func()
|
||||||
var rpcClean func()
|
var rpcClean func()
|
||||||
|
|
@ -478,14 +538,22 @@ func setupTest(backend headerGetter, validator ResourceValidator) (rh *ResourceH
|
||||||
// temp datadir
|
// temp datadir
|
||||||
datadir, err = ioutil.TempDir("", "rh")
|
datadir, err = ioutil.TempDir("", "rh")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", nil, nil, err
|
return nil, "", nil, err
|
||||||
}
|
}
|
||||||
fsClean = func() {
|
fsClean = func() {
|
||||||
os.RemoveAll(datadir)
|
os.RemoveAll(datadir)
|
||||||
}
|
}
|
||||||
|
|
||||||
rh, err = NewTestResourceHandler(datadir, backend, validator, &ResourceLookupParams{Limit: false})
|
rhparams := &ResourceHandlerParams{
|
||||||
return rh, datadir, signer, cleanF, nil
|
QueryMaxPeriods: &ResourceLookupParams{
|
||||||
|
Limit: false,
|
||||||
|
},
|
||||||
|
Signer: signer,
|
||||||
|
EthClient: backend,
|
||||||
|
EnsClient: ensBackend,
|
||||||
|
}
|
||||||
|
rh, err = NewTestResourceHandler(datadir, rhparams)
|
||||||
|
return rh, datadir, cleanF, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up simulated ENS backend for use with ENSResourceHandler tests
|
// Set up simulated ENS backend for use with ENSResourceHandler tests
|
||||||
|
|
@ -531,55 +599,18 @@ func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string,
|
||||||
return contractAddress, contractBackend, nil
|
return contractAddress, contractBackend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// implementation of an external signer to pass to validator
|
func newTestSigner() (*GenericResourceSigner, error) {
|
||||||
type testSigner struct {
|
|
||||||
privKey *ecdsa.PrivateKey
|
|
||||||
hasher SwarmHash
|
|
||||||
signContent SignFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestSigner() (*testSigner, error) {
|
|
||||||
privKey, err := crypto.GenerateKey()
|
privKey, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &testSigner{
|
return &GenericResourceSigner{
|
||||||
privKey: privKey,
|
PrivKey: privKey,
|
||||||
hasher: testHasher,
|
|
||||||
signContent: NewGenericResourceSigner(privKey),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default fallthrough validation of mutable resource ownership
|
|
||||||
type testValidator struct {
|
|
||||||
*baseValidator
|
|
||||||
hashFunc func(string) common.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestValidator(signFunc SignFunc) *testValidator {
|
|
||||||
return &testValidator{
|
|
||||||
baseValidator: &baseValidator{
|
|
||||||
signFunc: signFunc,
|
|
||||||
},
|
|
||||||
hashFunc: func(name string) common.Hash {
|
|
||||||
testHasher.Reset()
|
|
||||||
testHasher.Write([]byte(name))
|
|
||||||
return common.BytesToHash(testHasher.Sum(nil))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *testValidator) checkAccess(name string, address common.Address) (bool, error) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *testValidator) NameHash(name string) common.Hash {
|
|
||||||
return self.hashFunc(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUpdateDirect(rh *ResourceHandler, key Key) ([]byte, error) {
|
func getUpdateDirect(rh *ResourceHandler, key Key) ([]byte, error) {
|
||||||
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(key)
|
chunk, err := rh.chunkStore.(*LocalStore).memStore.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -589,18 +620,3 @@ func getUpdateDirect(rh *ResourceHandler, key Key) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type testResolver struct {
|
|
||||||
ens *ens.ENS
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestResolver(ens *ens.ENS) *testResolver {
|
|
||||||
return &testResolver{
|
|
||||||
ens: ens,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *testResolver) ValidateOwner(name string, address common.Address) (bool, error) {
|
|
||||||
addr, err := r.ens.Owner(ens.EnsNode(name))
|
|
||||||
return addr == address, err
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/bmt"
|
"github.com/ethereum/go-ethereum/bmt"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const MaxPO = 7
|
const MaxPO = 7
|
||||||
|
|
@ -180,18 +181,18 @@ type Chunk struct {
|
||||||
dbStoredC chan bool // never remove a chunk from memStore before it is written to dbStore
|
dbStoredC chan bool // never remove a chunk from memStore before it is written to dbStore
|
||||||
dbStored bool
|
dbStored bool
|
||||||
dbStoredMu *sync.Mutex
|
dbStoredMu *sync.Mutex
|
||||||
errored bool // flag which is set when the chunk request has errored or timeouted
|
errored error // flag which is set when the chunk request has errored or timeouted
|
||||||
erroredMu sync.Mutex
|
erroredMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunk) SetErrored(val bool) {
|
func (c *Chunk) SetErrored(err error) {
|
||||||
c.erroredMu.Lock()
|
c.erroredMu.Lock()
|
||||||
defer c.erroredMu.Unlock()
|
defer c.erroredMu.Unlock()
|
||||||
|
|
||||||
c.errored = val
|
c.errored = err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunk) GetErrored() bool {
|
func (c *Chunk) GetErrored() error {
|
||||||
c.erroredMu.Lock()
|
c.erroredMu.Lock()
|
||||||
defer c.erroredMu.Unlock()
|
defer c.erroredMu.Unlock()
|
||||||
|
|
||||||
|
|
@ -257,6 +258,34 @@ func (self *LazyTestSectionReader) Size(chan bool) (int64, error) {
|
||||||
return self.SectionReader.Size(), nil
|
return self.SectionReader.Size(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StoreParams struct {
|
||||||
|
Hash SwarmHasher `toml:"-"`
|
||||||
|
DbCapacity uint64
|
||||||
|
CacheCapacity uint
|
||||||
|
ChunkRequestsCacheCapacity uint
|
||||||
|
BaseKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultStoreParams() *StoreParams {
|
||||||
|
return NewStoreParams(defaultLDBCapacity, defaultCacheCapacity, defaultChunkRequestsCacheCapacity, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStoreParams(ldbCap uint64, cacheCap uint, requestsCap uint, hash SwarmHasher, basekey []byte) *StoreParams {
|
||||||
|
if basekey == nil {
|
||||||
|
basekey = make([]byte, 32)
|
||||||
|
}
|
||||||
|
if hash == nil {
|
||||||
|
hash = MakeHashFunc("SHA3")
|
||||||
|
}
|
||||||
|
return &StoreParams{
|
||||||
|
Hash: hash,
|
||||||
|
DbCapacity: ldbCap,
|
||||||
|
CacheCapacity: cacheCap,
|
||||||
|
ChunkRequestsCacheCapacity: requestsCap,
|
||||||
|
BaseKey: basekey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ChunkData []byte
|
type ChunkData []byte
|
||||||
|
|
||||||
type Reference []byte
|
type Reference []byte
|
||||||
|
|
@ -285,3 +314,34 @@ func (c ChunkData) Size() int64 {
|
||||||
func (c ChunkData) Data() []byte {
|
func (c ChunkData) Data() []byte {
|
||||||
return c[8:]
|
return c[8:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChunkValidator interface {
|
||||||
|
Validate(key Key, data []byte) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provides method for validation of content address in chunks
|
||||||
|
// Holds the corresponding hasher to create the address
|
||||||
|
type ContentAddressValidator struct {
|
||||||
|
Hasher SwarmHasher
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
func NewContentAddressValidator(hasher SwarmHasher) *ContentAddressValidator {
|
||||||
|
return &ContentAddressValidator{
|
||||||
|
Hasher: hasher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the given key is a valid content address for the given data
|
||||||
|
func (self *ContentAddressValidator) Validate(key Key, data []byte) bool {
|
||||||
|
hasher := self.Hasher()
|
||||||
|
hasher.ResetWithLength(data[:8])
|
||||||
|
hasher.Write(data[8:])
|
||||||
|
hash := hasher.Sum(nil)
|
||||||
|
|
||||||
|
if !bytes.Equal(hash, key[:]) {
|
||||||
|
log.Error("invalid content address", "expected", fmt.Sprintf("%x", hash), "have", key)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,15 +117,6 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
|
||||||
}
|
}
|
||||||
log.Debug(fmt.Sprintf("Setting up Swarm service components"))
|
log.Debug(fmt.Sprintf("Setting up Swarm service components"))
|
||||||
|
|
||||||
hash := storage.MakeHashFunc(config.DPAParams.Hash)
|
|
||||||
self.lstore, err = storage.NewLocalStore(hash, config.StoreParams, common.FromHex(config.BzzKey), mockStore)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup local store
|
|
||||||
log.Debug(fmt.Sprintf("Set up local storage"))
|
|
||||||
|
|
||||||
kp := network.NewKadParams()
|
kp := network.NewKadParams()
|
||||||
to := network.NewKademlia(
|
to := network.NewKademlia(
|
||||||
common.FromHex(config.BzzKey),
|
common.FromHex(config.BzzKey),
|
||||||
|
|
@ -146,40 +137,16 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
|
||||||
HiveParams: config.HiveParams,
|
HiveParams: config.HiveParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
db := storage.NewDBAPI(self.lstore)
|
|
||||||
delivery := stream.NewDelivery(to, db)
|
|
||||||
// TODO: decide on intervals store file location
|
// TODO: decide on intervals store file location
|
||||||
stateStore, err := state.NewDBStore(filepath.Join(config.Path, "state-store.db"))
|
stateStore, err := state.NewDBStore(filepath.Join(config.Path, "state-store.db"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.streamer = stream.NewRegistry(addr, delivery, db, stateStore, &stream.RegistryOptions{
|
|
||||||
DoSync: true,
|
|
||||||
DoRetrieve: true,
|
|
||||||
SyncUpdateDelay: config.SyncUpdateDelay,
|
|
||||||
})
|
|
||||||
|
|
||||||
self.bzz = network.NewBzz(bzzconfig, to, stateStore, stream.Spec, self.streamer.Run)
|
|
||||||
|
|
||||||
// set up DPA, the cloud storage local access layer
|
|
||||||
dpaChunkStore := storage.NewNetStore(self.lstore, self.streamer.Retrieve)
|
|
||||||
log.Debug(fmt.Sprintf("-> Local Access to Swarm"))
|
|
||||||
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
|
|
||||||
self.dpa = storage.NewDPA(dpaChunkStore, self.config.DPAParams)
|
|
||||||
log.Debug(fmt.Sprintf("-> Content Store API"))
|
|
||||||
|
|
||||||
// Pss = postal service over swarm (devp2p over bzz)
|
|
||||||
if self.config.PssEnabled {
|
|
||||||
pssparams := pss.NewPssParams(self.privateKey)
|
|
||||||
self.ps = pss.NewPss(to, pssparams)
|
|
||||||
if pss.IsActiveHandshake {
|
|
||||||
pss.SetHandshakeController(self.ps, pss.NewHandshakeParams())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up high level api
|
// set up high level api
|
||||||
//transactOpts := bind.NewKeyedTransactor(self.privateKey)
|
//transactOpts := bind.NewKeyedTransactor(self.privateKey)
|
||||||
var resolver *api.MultiResolver
|
var resolver *api.MultiResolver
|
||||||
|
var ensresolver *ens.ENS
|
||||||
if len(config.EnsAPIs) > 0 {
|
if len(config.EnsAPIs) > 0 {
|
||||||
opts := []api.MultiResolverOption{}
|
opts := []api.MultiResolverOption{}
|
||||||
for _, c := range config.EnsAPIs {
|
for _, c := range config.EnsAPIs {
|
||||||
|
|
@ -188,6 +155,7 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ensresolver = r.ENS
|
||||||
opts = append(opts, api.MultiResolverOptionWithResolver(r, tld))
|
opts = append(opts, api.MultiResolverOptionWithResolver(r, tld))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -195,19 +163,67 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
|
||||||
self.dns = resolver
|
self.dns = resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.lstore, err = storage.NewLocalStore(config.LocalStoreParams, mockStore)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var resourceHandler *storage.ResourceHandler
|
var resourceHandler *storage.ResourceHandler
|
||||||
// if use resource updates
|
// if use resource updates
|
||||||
|
|
||||||
if self.config.ResourceEnabled && resolver != nil {
|
if self.config.ResourceEnabled && resolver != nil {
|
||||||
resourceparams := &storage.ResourceHandlerParams{
|
resolver.SetNameHash(ens.EnsNode)
|
||||||
Validator: storage.NewENSValidator(config.EnsRoot, resolver, storage.NewGenericResourceSigner(self.privateKey)),
|
rhparams := &storage.ResourceHandlerParams{
|
||||||
|
// TODO: config parameter to set limits
|
||||||
|
QueryMaxPeriods: &storage.ResourceLookupParams{
|
||||||
|
Limit: false,
|
||||||
|
},
|
||||||
|
Signer: &storage.GenericResourceSigner{
|
||||||
|
PrivKey: self.privateKey,
|
||||||
|
},
|
||||||
|
EthClient: resolver,
|
||||||
|
EnsClient: ensresolver,
|
||||||
}
|
}
|
||||||
resolver.SetNameHash(resourceparams.Validator.NameHash)
|
resourceHandler, err = storage.NewResourceHandler(rhparams)
|
||||||
hashfunc := storage.MakeHashFunc(storage.SHA3Hash)
|
|
||||||
chunkStore := storage.NewResourceChunkStore(self.lstore, func(*storage.Chunk) error { return nil })
|
|
||||||
resourceHandler, err = storage.NewResourceHandler(hashfunc, chunkStore, resolver, resourceparams)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
resourceHandler.SetStore(self.lstore)
|
||||||
|
}
|
||||||
|
|
||||||
|
var validators []storage.ChunkValidator
|
||||||
|
validators = append(validators, storage.NewContentAddressValidator(storage.MakeHashFunc(storage.SHA3Hash)))
|
||||||
|
if resourceHandler != nil {
|
||||||
|
validators = append(validators, resourceHandler)
|
||||||
|
}
|
||||||
|
self.lstore.Validators = validators
|
||||||
|
|
||||||
|
// setup local store
|
||||||
|
log.Debug(fmt.Sprintf("Set up local storage"))
|
||||||
|
|
||||||
|
db := storage.NewDBAPI(self.lstore)
|
||||||
|
delivery := stream.NewDelivery(to, db)
|
||||||
|
|
||||||
|
self.streamer = stream.NewRegistry(addr, delivery, db, stateStore, &stream.RegistryOptions{
|
||||||
|
DoSync: true,
|
||||||
|
DoRetrieve: true,
|
||||||
|
SyncUpdateDelay: config.SyncUpdateDelay,
|
||||||
|
})
|
||||||
|
|
||||||
|
// set up DPA, the cloud storage local access layer
|
||||||
|
dpaChunkStore := storage.NewNetStore(self.lstore, self.streamer.Retrieve)
|
||||||
|
log.Debug(fmt.Sprintf("-> Local Access to Swarm"))
|
||||||
|
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
|
||||||
|
self.dpa = storage.NewDPA(dpaChunkStore, self.config.DPAParams)
|
||||||
|
log.Debug(fmt.Sprintf("-> Content Store API"))
|
||||||
|
|
||||||
|
self.bzz = network.NewBzz(bzzconfig, to, stateStore, stream.Spec, self.streamer.Run)
|
||||||
|
|
||||||
|
// Pss = postal service over swarm (devp2p over bzz)
|
||||||
|
pssparams := pss.NewPssParams(self.privateKey)
|
||||||
|
self.ps = pss.NewPss(to, pssparams)
|
||||||
|
if pss.IsActiveHandshake {
|
||||||
|
pss.SetHandshakeController(self.ps, pss.NewHandshakeParams())
|
||||||
}
|
}
|
||||||
|
|
||||||
self.api = api.NewApi(self.dpa, self.dns, resourceHandler)
|
self.api = api.NewApi(self.dpa, self.dns, resourceHandler)
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,11 @@ func NewTestSwarmServer(t *testing.T) *TestSwarmServer {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
storeparams := &storage.StoreParams{
|
storeparams := storage.NewDefaultLocalStoreParams()
|
||||||
ChunkDbPath: dir,
|
storeparams.DbCapacity = 5000000
|
||||||
DbCapacity: 5000000,
|
storeparams.CacheCapacity = 5000
|
||||||
CacheCapacity: 5000,
|
storeparams.Init(dir)
|
||||||
}
|
localStore, err := storage.NewLocalStore(storeparams, nil)
|
||||||
localStore, err := storage.NewLocalStore(storage.MakeHashFunc(storage.SHA3Hash), storeparams, make([]byte, 32), nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.RemoveAll(dir)
|
os.RemoveAll(dir)
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -64,8 +63,11 @@ func NewTestSwarmServer(t *testing.T) *TestSwarmServer {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
rhparams := &storage.ResourceHandlerParams{
|
||||||
rh, err := storage.NewTestResourceHandler(resourceDir, &fakeBackend{}, nil, &storage.ResourceLookupParams{Limit: false})
|
QueryMaxPeriods: &storage.ResourceLookupParams{},
|
||||||
|
EthClient: &fakeBackend{},
|
||||||
|
}
|
||||||
|
rh, err := storage.NewTestResourceHandler(resourceDir, rhparams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
vendor/github.com/naoina/toml/encode.go
generated
vendored
12
vendor/github.com/naoina/toml/encode.go
generated
vendored
|
|
@ -61,20 +61,26 @@ func (cfg *Config) NewEncoder(w io.Writer) *Encoder {
|
||||||
// Encode writes the TOML of v to the stream.
|
// Encode writes the TOML of v to the stream.
|
||||||
// See the documentation for Marshal for details about the conversion of Go values to TOML.
|
// See the documentation for Marshal for details about the conversion of Go values to TOML.
|
||||||
func (e *Encoder) Encode(v interface{}) error {
|
func (e *Encoder) Encode(v interface{}) error {
|
||||||
rv := reflect.ValueOf(v)
|
var (
|
||||||
|
buf = &tableBuf{typ: ast.TableTypeNormal}
|
||||||
|
rv = reflect.ValueOf(v)
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
for rv.Kind() == reflect.Ptr {
|
for rv.Kind() == reflect.Ptr {
|
||||||
if rv.IsNil() {
|
if rv.IsNil() {
|
||||||
return &marshalNilError{rv.Type()}
|
return &marshalNilError{rv.Type()}
|
||||||
}
|
}
|
||||||
rv = rv.Elem()
|
rv = rv.Elem()
|
||||||
}
|
}
|
||||||
buf := &tableBuf{typ: ast.TableTypeNormal}
|
|
||||||
var err error
|
|
||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
err = buf.structFields(e.cfg, rv)
|
err = buf.structFields(e.cfg, rv)
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
err = buf.mapFields(e.cfg, rv)
|
err = buf.mapFields(e.cfg, rv)
|
||||||
|
case reflect.Interface:
|
||||||
|
return e.Encode(rv.Interface())
|
||||||
default:
|
default:
|
||||||
err = &marshalTableError{rv.Type()}
|
err = &marshalTableError{rv.Type()}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
52
vendor/github.com/naoina/toml/parse.go
generated
vendored
52
vendor/github.com/naoina/toml/parse.go
generated
vendored
|
|
@ -97,6 +97,7 @@ type toml struct {
|
||||||
currentTable *ast.Table
|
currentTable *ast.Table
|
||||||
s string
|
s string
|
||||||
key string
|
key string
|
||||||
|
tableKeys []string
|
||||||
val ast.Value
|
val ast.Value
|
||||||
arr *array
|
arr *array
|
||||||
stack []*stack
|
stack []*stack
|
||||||
|
|
@ -180,12 +181,12 @@ func (p *tomlParser) SetArray(begin, end int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *toml) SetTable(buf []rune, begin, end int) {
|
func (p *toml) SetTable(buf []rune, begin, end int) {
|
||||||
p.setTable(p.table, buf, begin, end)
|
rawName := string(buf[begin:end])
|
||||||
|
p.setTable(p.table, rawName, p.tableKeys)
|
||||||
|
p.tableKeys = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *toml) setTable(parent *ast.Table, buf []rune, begin, end int) {
|
func (p *toml) setTable(parent *ast.Table, name string, names []string) {
|
||||||
name := string(buf[begin:end])
|
|
||||||
names := splitTableKey(name)
|
|
||||||
parent, err := p.lookupTable(parent, names[:len(names)-1])
|
parent, err := p.lookupTable(parent, names[:len(names)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error(err)
|
p.Error(err)
|
||||||
|
|
@ -230,12 +231,12 @@ func (p *tomlParser) SetTableString(begin, end int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *toml) SetArrayTable(buf []rune, begin, end int) {
|
func (p *toml) SetArrayTable(buf []rune, begin, end int) {
|
||||||
p.setArrayTable(p.table, buf, begin, end)
|
rawName := string(buf[begin:end])
|
||||||
|
p.setArrayTable(p.table, rawName, p.tableKeys)
|
||||||
|
p.tableKeys = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *toml) setArrayTable(parent *ast.Table, buf []rune, begin, end int) {
|
func (p *toml) setArrayTable(parent *ast.Table, name string, names []string) {
|
||||||
name := string(buf[begin:end])
|
|
||||||
names := splitTableKey(name)
|
|
||||||
parent, err := p.lookupTable(parent, names[:len(names)-1])
|
parent, err := p.lookupTable(parent, names[:len(names)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error(err)
|
p.Error(err)
|
||||||
|
|
@ -260,11 +261,11 @@ func (p *toml) setArrayTable(parent *ast.Table, buf []rune, begin, end int) {
|
||||||
func (p *toml) StartInlineTable() {
|
func (p *toml) StartInlineTable() {
|
||||||
p.skip = false
|
p.skip = false
|
||||||
p.stack = append(p.stack, &stack{p.key, p.currentTable})
|
p.stack = append(p.stack, &stack{p.key, p.currentTable})
|
||||||
buf := []rune(p.key)
|
names := []string{p.key}
|
||||||
if p.arr == nil {
|
if p.arr == nil {
|
||||||
p.setTable(p.currentTable, buf, 0, len(buf))
|
p.setTable(p.currentTable, names[0], names)
|
||||||
} else {
|
} else {
|
||||||
p.setArrayTable(p.currentTable, buf, 0, len(buf))
|
p.setArrayTable(p.currentTable, names[0], names)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,6 +283,13 @@ func (p *toml) AddLineCount(i int) {
|
||||||
|
|
||||||
func (p *toml) SetKey(buf []rune, begin, end int) {
|
func (p *toml) SetKey(buf []rune, begin, end int) {
|
||||||
p.key = string(buf[begin:end])
|
p.key = string(buf[begin:end])
|
||||||
|
if len(p.key) > 0 && p.key[0] == '"' {
|
||||||
|
p.key = p.unquote(p.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *toml) AddTableKey() {
|
||||||
|
p.tableKeys = append(p.tableKeys, p.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *toml) AddKeyValue() {
|
func (p *toml) AddKeyValue() {
|
||||||
|
|
@ -352,25 +360,3 @@ func (p *toml) lookupTable(t *ast.Table, keys []string) (*ast.Table, error) {
|
||||||
}
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitTableKey(tk string) []string {
|
|
||||||
key := make([]byte, 0, 1)
|
|
||||||
keys := make([]string, 0, 1)
|
|
||||||
inQuote := false
|
|
||||||
for i := 0; i < len(tk); i++ {
|
|
||||||
k := tk[i]
|
|
||||||
switch {
|
|
||||||
case k == tableSeparator && !inQuote:
|
|
||||||
keys = append(keys, string(key))
|
|
||||||
key = key[:0] // reuse buffer.
|
|
||||||
case k == '"':
|
|
||||||
inQuote = !inQuote
|
|
||||||
case (k == ' ' || k == '\t') && !inQuote:
|
|
||||||
// skip.
|
|
||||||
default:
|
|
||||||
key = append(key, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keys = append(keys, string(key))
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
|
||||||
8
vendor/github.com/naoina/toml/parse.peg
generated
vendored
8
vendor/github.com/naoina/toml/parse.peg
generated
vendored
|
|
@ -29,7 +29,7 @@ key <- bareKey / quotedKey
|
||||||
|
|
||||||
bareKey <- <[0-9A-Za-z\-_]+> { p.SetKey(p.buffer, begin, end) }
|
bareKey <- <[0-9A-Za-z\-_]+> { p.SetKey(p.buffer, begin, end) }
|
||||||
|
|
||||||
quotedKey <- '"' <basicChar+> '"' { p.SetKey(p.buffer, begin-1, end+1) }
|
quotedKey <- < '"' basicChar* '"' > { p.SetKey(p.buffer, begin, end) }
|
||||||
|
|
||||||
val <- (
|
val <- (
|
||||||
<datetime> { p.SetTime(begin, end) }
|
<datetime> { p.SetTime(begin, end) }
|
||||||
|
|
@ -55,7 +55,9 @@ inlineTable <- (
|
||||||
|
|
||||||
inlineTableKeyValues <- (keyval inlineTableValSep?)*
|
inlineTableKeyValues <- (keyval inlineTableValSep?)*
|
||||||
|
|
||||||
tableKey <- key (tableKeySep key)*
|
tableKey <- tableKeyComp (tableKeySep tableKeyComp)*
|
||||||
|
|
||||||
|
tableKeyComp <- key { p.AddTableKey() }
|
||||||
|
|
||||||
tableKeySep <- ws '.' ws
|
tableKeySep <- ws '.' ws
|
||||||
|
|
||||||
|
|
@ -117,7 +119,7 @@ timeNumoffset <- [\-+] timeHour ':' timeMinute
|
||||||
timeOffset <- 'Z' / timeNumoffset
|
timeOffset <- 'Z' / timeNumoffset
|
||||||
partialTime <- timeHour ':' timeMinute ':' timeSecond timeSecfrac?
|
partialTime <- timeHour ':' timeMinute ':' timeSecond timeSecfrac?
|
||||||
fullDate <- dateFullYear '-' dateMonth '-' dateMDay
|
fullDate <- dateFullYear '-' dateMonth '-' dateMDay
|
||||||
fullTime <- partialTime timeOffset
|
fullTime <- partialTime timeOffset?
|
||||||
datetime <- (fullDate ('T' fullTime)?) / partialTime
|
datetime <- (fullDate ('T' fullTime)?) / partialTime
|
||||||
|
|
||||||
digit <- [0-9]
|
digit <- [0-9]
|
||||||
|
|
|
||||||
1378
vendor/github.com/naoina/toml/parse.peg.go
generated
vendored
1378
vendor/github.com/naoina/toml/parse.peg.go
generated
vendored
File diff suppressed because it is too large
Load diff
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
|
|
@ -292,10 +292,10 @@
|
||||||
"revisionTime": "2017-07-13T18:40:44Z"
|
"revisionTime": "2017-07-13T18:40:44Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "2gmvVTDCks8cPhpmyDlvm0sbrXE=",
|
"checksumSHA1": "FYM/8R2CqS6PSNAoKl6X5gNJ20A=",
|
||||||
"path": "github.com/naoina/toml",
|
"path": "github.com/naoina/toml",
|
||||||
"revision": "ac014c6b6502388d89a85552b7208b8da7cfe104",
|
"revision": "9fafd69674167c06933b1787ae235618431ce87f",
|
||||||
"revisionTime": "2017-04-10T21:57:17Z"
|
"revisionTime": "2017-09-18T21:04:37Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "xZBlSMT5o/A+EDOro6KbfHZwSNc=",
|
"checksumSHA1": "xZBlSMT5o/A+EDOro6KbfHZwSNc=",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue