From 195cf4f84d86c50b5a10a5e01f04f00035719c23 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Tue, 9 Jul 2019 19:50:48 +0200 Subject: [PATCH 1/6] Implement memrepo License: MIT Signed-off-by: Jakub Sztandera --- go.mod | 2 + node/repo/interface.go | 43 +++++++++++ node/repo/memrepo.go | 160 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 node/repo/interface.go create mode 100644 node/repo/memrepo.go diff --git a/go.mod b/go.mod index 72a21bec5..8a090206b 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/libp2p/go-libp2p-circuit v0.1.0 github.com/libp2p/go-libp2p-connmgr v0.1.0 github.com/libp2p/go-libp2p-core v0.0.6 + github.com/libp2p/go-libp2p-crypto v0.1.0 github.com/libp2p/go-libp2p-discovery v0.1.0 github.com/libp2p/go-libp2p-kad-dht v0.1.1 github.com/libp2p/go-libp2p-mplex v0.2.1 @@ -50,6 +51,7 @@ require ( go.uber.org/dig v1.7.0 // indirect go.uber.org/fx v1.9.0 go.uber.org/goleak v0.10.0 // indirect + golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8 launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect ) diff --git a/node/repo/interface.go b/node/repo/interface.go new file mode 100644 index 000000000..9c1a6849d --- /dev/null +++ b/node/repo/interface.go @@ -0,0 +1,43 @@ +package repo + +import ( + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/multiformats/go-multiaddr" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-lotus/node/config" +) + +var ( + ErrNoAPIEndpoint = xerrors.New("no API Endpoint set") + ErrRepoAlreadyLocked = xerrors.New("repo is already locked") + ErrClosedRepo = xerrors.New("repo is no longer open") +) + +type Repo interface { + // APIEndpoint returns multiaddress for communication with Lotus API + APIEndpoint() (multiaddr.Multiaddr, error) + + // Lock locks the repo for exclusive use. + Lock() (LockedRepo, error) +} + +type LockedRepo interface { + // Close closes repo and removes lock. + Close() error + + // Returns datastore defined in this repo. + Datastore() (datastore.Datastore, error) + + // Returns config in this repo + Config() (*config.Root, error) + + // Libp2pIdentity returns private key for libp2p indentity + Libp2pIdentity() (crypto.PrivKey, error) + + SetAPIEndpoint(multiaddr.Multiaddr) error + + // Wallet returns store of private keys for Filecoin transactions + Wallet() (interface{}, error) +} diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go new file mode 100644 index 000000000..8929cebec --- /dev/null +++ b/node/repo/memrepo.go @@ -0,0 +1,160 @@ +package repo + +import ( + "crypto/rand" + "sync" + + "github.com/filecoin-project/go-lotus/node/config" + "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + crypto "github.com/libp2p/go-libp2p-crypto" + "github.com/multiformats/go-multiaddr" +) + +type MemRepo struct { + api struct { + sync.Mutex + ma multiaddr.Multiaddr + } + + repoLock chan struct{} + token *byte + + datastore datastore.Datastore + configF func() *config.Root + libp2pKey crypto.PrivKey + wallet interface{} +} + +type lockedMemRepo struct { + mem *MemRepo + sync.RWMutex + + token *byte +} + +var _ Repo = &MemRepo{} + +type MemRepoOptions struct { + ds datastore.Datastore + configF func() *config.Root + libp2pKey crypto.PrivKey + wallet interface{} +} + +func NewMemory(opts *MemRepoOptions) *MemRepo { + if opts == nil { + opts = &MemRepoOptions{} + } + if opts.configF == nil { + opts.configF = config.Default + } + if opts.ds == nil { + opts.ds = dssync.MutexWrap(datastore.NewMapDatastore()) + } + if opts.libp2pKey == nil { + pk, _, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + panic(err) + } + opts.libp2pKey = pk + } + + return &MemRepo{ + repoLock: make(chan struct{}, 1), + + datastore: opts.ds, + configF: opts.configF, + libp2pKey: opts.libp2pKey, + wallet: opts.wallet, + } +} + +func (mem *MemRepo) APIEndpoint() (multiaddr.Multiaddr, error) { + mem.api.Lock() + defer mem.api.Unlock() + if mem.api.ma == nil { + return nil, ErrNoAPIEndpoint + } + return mem.api.ma, nil +} + +func (mem *MemRepo) Lock() (LockedRepo, error) { + select { + case mem.repoLock <- struct{}{}: + default: + return nil, ErrRepoAlreadyLocked + } + mem.token = new(byte) + + return &lockedMemRepo{ + mem: mem, + token: mem.token, + }, nil +} + +func (lmem *lockedMemRepo) checkToken() error { + lmem.RLock() + defer lmem.RUnlock() + if lmem.mem.token != lmem.token { + return ErrClosedRepo + } + return nil +} + +func (lmem *lockedMemRepo) Close() error { + if err := lmem.checkToken(); err != nil { + return err + } + lmem.Lock() + defer lmem.Unlock() + + if lmem.mem.token != lmem.token { + return ErrClosedRepo + } + + lmem.mem.token = nil + lmem.mem.api.Lock() + lmem.mem.api.ma = nil + lmem.mem.api.Unlock() + return nil + +} + +func (lmem *lockedMemRepo) Datastore() (datastore.Datastore, error) { + if err := lmem.checkToken(); err != nil { + return nil, err + } + return lmem.mem.datastore, nil +} + +func (lmem *lockedMemRepo) Config() (*config.Root, error) { + if err := lmem.checkToken(); err != nil { + return nil, err + } + return lmem.mem.configF(), nil +} + +func (lmem *lockedMemRepo) Libp2pIdentity() (crypto.PrivKey, error) { + if err := lmem.checkToken(); err != nil { + return nil, err + } + return lmem.mem.libp2pKey, nil +} + +func (lmem *lockedMemRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error { + if err := lmem.checkToken(); err != nil { + return err + } + lmem.mem.api.Lock() + lmem.mem.api.ma = ma + lmem.mem.api.Unlock() + return nil +} + +func (lmem *lockedMemRepo) Wallet() (interface{}, error) { + if err := lmem.checkToken(); err != nil { + return nil, err + } + return lmem.mem.wallet, nil +} From 2b62bb6eab4b3fb4c536340994de7efb8d58fb09 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Tue, 9 Jul 2019 20:11:32 +0200 Subject: [PATCH 2/6] Add tests License: MIT Signed-off-by: Jakub Sztandera --- node/repo/memrepo.go | 10 +++++-- node/repo/memrepo_test.go | 57 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 node/repo/memrepo_test.go diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 8929cebec..9cb42c439 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -4,11 +4,12 @@ import ( "crypto/rand" "sync" - "github.com/filecoin-project/go-lotus/node/config" "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" - crypto "github.com/libp2p/go-libp2p-crypto" + "github.com/libp2p/go-libp2p-core/crypto" "github.com/multiformats/go-multiaddr" + + "github.com/filecoin-project/go-lotus/node/config" ) type MemRepo struct { @@ -35,6 +36,7 @@ type lockedMemRepo struct { var _ Repo = &MemRepo{} +// MemRepoOptions contains options for memory repo type MemRepoOptions struct { ds datastore.Datastore configF func() *config.Root @@ -42,6 +44,9 @@ type MemRepoOptions struct { wallet interface{} } +// NewMemory creates new memory based repo with provided options. +// opts can be nil will be replaced with default +// any filed in opts can be nil, it will be replaced by defaults func NewMemory(opts *MemRepoOptions) *MemRepo { if opts == nil { opts = &MemRepoOptions{} @@ -117,6 +122,7 @@ func (lmem *lockedMemRepo) Close() error { lmem.mem.api.Lock() lmem.mem.api.ma = nil lmem.mem.api.Unlock() + <-lmem.mem.repoLock // unlock return nil } diff --git a/node/repo/memrepo_test.go b/node/repo/memrepo_test.go new file mode 100644 index 000000000..27b6d9f4b --- /dev/null +++ b/node/repo/memrepo_test.go @@ -0,0 +1,57 @@ +package repo + +import ( + "testing" + + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/assert" +) + +func TestMemRepo(t *testing.T) { + repo := NewMemory(nil) + apima, err := repo.APIEndpoint() + if assert.Error(t, err) { + assert.Equal(t, ErrNoAPIEndpoint, err) + } + assert.Nil(t, apima, "with no api endpoint, return should be nil") + + lrepo, err := repo.Lock() + assert.NoError(t, err, "should be able to lock once") + assert.NotNil(t, lrepo, "locked repo shouldn't be nil") + + { + lrepo2, err := repo.Lock() + if assert.Error(t, err) { + assert.Equal(t, ErrRepoAlreadyLocked, err) + } + assert.Nil(t, lrepo2, "with locked repo errors, nil should be returned") + } + + err = lrepo.Close() + assert.NoError(t, err, "should be able to unlock") + + lrepo, err = repo.Lock() + assert.NoError(t, err, "should be able to relock") + assert.NotNil(t, lrepo, "locked repo shouldn't be nil") + + ma, err := multiaddr.NewMultiaddr("/ip4/127.0.0.1/tcp/43244") + assert.NoError(t, err, "creating multiaddr shouldn't error") + + err = lrepo.SetAPIEndpoint(ma) + assert.NoError(t, err, "setting multiaddr shouldn't error") + + apima, err = repo.APIEndpoint() + assert.NoError(t, err, "setting multiaddr shouldn't error") + assert.Equal(t, ma, apima, "returned API multiaddr should be the same") + + err = lrepo.Close() + assert.NoError(t, err, "should be able to close") + + apima, err = repo.APIEndpoint() + + if assert.Error(t, err) { + assert.Equal(t, ErrNoAPIEndpoint, err, "after closing repo, api should be nil") + } + assert.Nil(t, apima, "with closed repo, apima should be set back to nil") + +} From 53952e566b1f6686343c38cc1fd2321e39f20ce3 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Tue, 9 Jul 2019 20:14:03 +0200 Subject: [PATCH 3/6] go mod tidy License: MIT Signed-off-by: Jakub Sztandera --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index 8a090206b..c6e65e25e 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( github.com/libp2p/go-libp2p-circuit v0.1.0 github.com/libp2p/go-libp2p-connmgr v0.1.0 github.com/libp2p/go-libp2p-core v0.0.6 - github.com/libp2p/go-libp2p-crypto v0.1.0 github.com/libp2p/go-libp2p-discovery v0.1.0 github.com/libp2p/go-libp2p-kad-dht v0.1.1 github.com/libp2p/go-libp2p-mplex v0.2.1 From 9248b462dd1cde46694c77f4a02ad1395571b227 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Tue, 9 Jul 2019 20:19:54 +0200 Subject: [PATCH 4/6] Fix spelling License: MIT Signed-off-by: Jakub Sztandera --- node/repo/memrepo.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 9cb42c439..26efdf8e8 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -45,8 +45,8 @@ type MemRepoOptions struct { } // NewMemory creates new memory based repo with provided options. -// opts can be nil will be replaced with default -// any filed in opts can be nil, it will be replaced by defaults +// opts can be nil, it will be replaced with defaults. +// Any field in opts can be nil, they will be replaced by defaults. func NewMemory(opts *MemRepoOptions) *MemRepo { if opts == nil { opts = &MemRepoOptions{} From 51f38e0542dd30938ce3729ed5a0ad6966f76898 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Tue, 9 Jul 2019 20:20:42 +0200 Subject: [PATCH 5/6] Add missing comment License: MIT Signed-off-by: Jakub Sztandera --- node/repo/interface.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/node/repo/interface.go b/node/repo/interface.go index 9c1a6849d..4c9823ab9 100644 --- a/node/repo/interface.go +++ b/node/repo/interface.go @@ -36,6 +36,8 @@ type LockedRepo interface { // Libp2pIdentity returns private key for libp2p indentity Libp2pIdentity() (crypto.PrivKey, error) + // SetAPIEndpoint sets the endpoint of the current API + // so it can be read by API clients SetAPIEndpoint(multiaddr.Multiaddr) error // Wallet returns store of private keys for Filecoin transactions From 34b6da7f0cc51468b34f9bdaad76b4137870db03 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Tue, 9 Jul 2019 21:38:05 +0200 Subject: [PATCH 6/6] Make config fields public License: MIT Signed-off-by: Jakub Sztandera --- node/repo/memrepo.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 26efdf8e8..de2ace933 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -38,10 +38,10 @@ var _ Repo = &MemRepo{} // MemRepoOptions contains options for memory repo type MemRepoOptions struct { - ds datastore.Datastore - configF func() *config.Root - libp2pKey crypto.PrivKey - wallet interface{} + Ds datastore.Datastore + ConfigF func() *config.Root + Libp2pKey crypto.PrivKey + Wallet interface{} } // NewMemory creates new memory based repo with provided options. @@ -51,27 +51,27 @@ func NewMemory(opts *MemRepoOptions) *MemRepo { if opts == nil { opts = &MemRepoOptions{} } - if opts.configF == nil { - opts.configF = config.Default + if opts.ConfigF == nil { + opts.ConfigF = config.Default } - if opts.ds == nil { - opts.ds = dssync.MutexWrap(datastore.NewMapDatastore()) + if opts.Ds == nil { + opts.Ds = dssync.MutexWrap(datastore.NewMapDatastore()) } - if opts.libp2pKey == nil { + if opts.Libp2pKey == nil { pk, _, err := crypto.GenerateEd25519Key(rand.Reader) if err != nil { panic(err) } - opts.libp2pKey = pk + opts.Libp2pKey = pk } return &MemRepo{ repoLock: make(chan struct{}, 1), - datastore: opts.ds, - configF: opts.configF, - libp2pKey: opts.libp2pKey, - wallet: opts.wallet, + datastore: opts.Ds, + configF: opts.ConfigF, + libp2pKey: opts.Libp2pKey, + wallet: opts.Wallet, } }