Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

Commit

Permalink
Put LFFS in attic since it never worked that great to begin with.
Browse files Browse the repository at this point in the history
  • Loading branch information
adamierymenko committed Sep 5, 2019
1 parent f7d5260 commit 1bfdce0
Show file tree
Hide file tree
Showing 6 changed files with 3 additions and 276 deletions.
55 changes: 2 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
# LF: Fully Decentralized Fully Replicated Key/Value Store

*(c)2018-2019 [ZeroTier, Inc.](https://www.zerotier.com/)*
*Licensed under the [GNU GPLv3](LICENSE.txt)*

**LF is currently beta software!** Until 1.x object formats, APIs, and protocols may change in abrupt and non-backward-compatible ways (though we try to avoid it).

[toc]
*(c)2019 [ZeroTier, Inc.](https://www.zerotier.com/)*
*Licensed under the [ZeroTier LF BSL](LICENSE.txt)*

## Introduction

Expand Down Expand Up @@ -238,53 +234,6 @@ Casual users can choose to trust nodes that are generally trusted. Applications

*Note that you shouldn't run nodes in `-oracle` mode on variable or burstable CPU cloud instances as this could result in a large bill or a surprise failure when the instance uses up its CPU quota. Use reserved CPU or bare metal systems for oracle nodes.*

### LFFS

LF contains a FUSE filesystem that allows sections of the global data store to be mounted. It can be mounted by remote clients or directly by full nodes, with the latter offering much higher performance since all data is local to the process. LFFS requires FUSE on Linux or [OSXFUSE](https://osxfuse.github.io) on Mac.

LFFS is very basic and does not support full POSIX filesystem semantics. It's intended to be used for things like configuration file replication. Don't even try to put a database or something else complex on it. Even a git repository is likely to be too much. Here's a current list of known issues and limitations:

* Hard links, special modes like setuid/setgid, named pipes, device nodes, and extended attributes are not supported.
* Filesystem permission and ownership changes are not supported and chmod/chown are silently ignored.
* File locks are not supported.
* Renaming is a bit clunky and slow and doesn't quite obey POSIX semantics as it's implemented as delete-create.
* Name length is limited to 511 bytes. This is for names within a directory. There is no limit to how many directories can be nested.
* If you must perform proof of work, writes will be extremely slow to propagate and CPU-intensive. There is currently no way to cancel a write.
* A single LF owner is used for all new files under a mount and there's no way to change this without remounting.
* Once a filename is claimed by an owner there is currently no way to transfer ownership even if the file is subsequently deleted.

Some of these might get fixed or improved in the future, but as we said LFFS is intended for very simple use cases and small data.

To try out LFFS try this:

```text
$ ./lf mount -maxfilesize 4096 -passphrase 'The sparrow perches on the steeple in the rain.' /tmp/lffs-public-test com.zerotier.lffs.public-test
```

If everything works you should be able to access a public test LFFS share at `/tmp/lffs-public-test`. **Be aware** that this is a global public test share, so it could contain anything! If you see anything nasty there feel free to delete it. Since the passphrase (which generates both the owner and the masking key) is public, anyone can read and write.

The above command mounts via the HTTP(S) API, which is slow. To mount directly under a running full node, edit a file called `mounts.json` in the node's home directory and then restart it. Here's an example `mounts.json` to mount the same public test share.

```json
[
{
"Path": "/tmp/lffs-public-test",
"RootSelectorName": "com.zerotier.lffs.public-test",
"MaxFileSize": 4096,
"Passphrase": "The sparrow perches on the steeple in the rain."
}
]
```

The complete schema for mount points in `mounts.json` is:

* **Path**: Path to mount FUSE filesystem on host (must have read/write access).
* **RootSelectorName**: LF selector name (without ordinal) of filesystem root.
* **OwnerPrivate**: Owner private key (if omitted the node's identity is used).
* **MaskingKey**: Masking key to encrypt record (file) contents (if omitted root selector name is used).
* **Passphrase**: If present this overrides both **OwnerPrivate** and **MaskingKey** and is used to generate both.
* **MaxFileSize**: This limits the maximum size of locally written files. Files larger than this can appear if someone else wrote them. The hard global maximum is 4mb. Note that large files can take a *very* long time to commit due to proof of work on public networks!

## Sol: The Default Public Database / Network

LF ships with a default configuration for a public network we call *Sol* after our home solar system. This configuration contains a default node URL and default *genesis records* allowing nodes to automatically bootstrap themselves.
Expand Down
File renamed without changes.
File renamed without changes.
120 changes: 0 additions & 120 deletions cmd/lf/lf.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,6 @@ Commands:
list List trusted oracles
add <@oracle> Add trusted oracle
delete <@oracle> Delete trusted oracle
mount <mount point> <selector name> Mount LFFS volume (FUSE required)
-mask <key> Override default masking key
-owner <owner> Use this owner instead of default
-maxfilesize <size> Maximum file size in bytes (writes)
-passphrase <string> Derive owner and mask from string
Global options must precede commands, while command options must come after
the command name.
Expand Down Expand Up @@ -491,118 +486,6 @@ func doNodeStart(cfg *lf.ClientConfig, basePath string, args []string) (exitCode
return
}

func doMount(cfg *lf.ClientConfig, basePath string, args []string) (exitCode int) {
mountOpts := flag.NewFlagSet("mount", flag.ContinueOnError)
ownerName := mountOpts.String("owner", "", "")
maskKey := mountOpts.String("mask", "", "")
passphrase := mountOpts.String("passphrase", "", "")
maxFileSizeStr := mountOpts.String("maxfilesize", "", "")
mountOpts.SetOutput(ioutil.Discard)
err := mountOpts.Parse(args)
if err != nil {
printHelp("")
exitCode = 1
return
}
args = mountOpts.Args()
if len(args) != 2 {
printHelp("")
exitCode = 1
return
}

mountPoint := strings.TrimSpace(args[0])
selectorName := strings.TrimSpace(args[1])
if len(mountPoint) == 0 || len(selectorName) == 0 {
printHelp("")
exitCode = 1
return
}

maxFileSize := 4096
if len(*maxFileSizeStr) > 0 {
mfs, _ := strconv.ParseInt(*maxFileSizeStr, 10, 64)
if mfs <= 0 {
logger.Printf("FATAL: invalid max file size: %d", mfs)
exitCode = 1
return
}
maxFileSize = int(mfs)
}

var owner *lf.Owner
var mk []byte

if len(*passphrase) > 0 {
if len(*ownerName) > 0 || len(*maskKey) > 0 {
logger.Printf("ERROR: you must use either -passphrase or -owner and/or -mask")
exitCode = 1
return
}
owner, mk = lf.PassphraseToOwnerAndMaskingKey(*passphrase)
} else {
var ccOwner *lf.ClientConfigOwner
if len(*ownerName) > 0 {
ccOwner = cfg.Owners[*ownerName]
if owner == nil {
logger.Printf("ERROR: set failed: owner '%s' not found\n", *ownerName)
exitCode = 1
return
}
}
if ccOwner == nil {
for _, o := range cfg.Owners {
if o.Default {
ccOwner = o
break
}
}
}
if ccOwner == nil {
logger.Println("ERROR: set failed: owner not found and no default specified")
exitCode = 1
return
}
owner, _ = ccOwner.GetOwner()
if owner == nil {
logger.Println("ERROR: set failed: owner not found and no default specified")
exitCode = 1
return
}
}

if len(*maskKey) > 0 {
mk = []byte(*maskKey)
}
if len(mk) == 0 {
mk = []byte(selectorName)
}

osSignalChannel := make(chan os.Signal, 2)
signal.Notify(osSignalChannel, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGBUS)
signal.Ignore(syscall.SIGUSR1, syscall.SIGUSR2)

ds := make([]lf.LF, 0, len(cfg.URLs))
for _, u := range cfg.URLs {
ds = append(ds, u)
}
fs, err := lf.NewFS(ds, logger, logger, mountPoint, []byte(selectorName), owner, maxFileSize, mk)
if err != nil {
logger.Printf("FATAL: unable to mount LFFS at %s: %s\n", mountPoint, err.Error())
exitCode = 1
return
}

go func() {
<-osSignalChannel
fs.Close()
}()

fs.WaitForClose()

return
}

func doNodeConnect(cfg *lf.ClientConfig, basePath string, args []string) (exitCode int) {
if len(args) != 3 {
printHelp("")
Expand Down Expand Up @@ -2020,9 +1903,6 @@ func main() {
case "node-connect":
exitCode = doNodeConnect(&cfg, *basePath, cmdArgs)

case "mount":
exitCode = doMount(&cfg, *basePath, cmdArgs)

case "status":
exitCode = doStatus(&cfg, *basePath, cmdArgs)

Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module lf
go 1.12

require (
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669
github.com/andybalholm/brotli v0.0.0-20190821151343-b60f0d972eeb
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/stretchr/testify v1.4.0 // indirect
Expand Down
103 changes: 1 addition & 102 deletions pkg/lf/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ type Node struct {
workFunctionLock sync.Mutex
makeRecordWorkFunction *Wharrgarblr
makeRecordWorkFunctionLock sync.Mutex
mountPoints map[string]*FS
mountPointsLock sync.Mutex
db db

owner *Owner // Owner for commentary, key also currently used for ECDH on link
Expand Down Expand Up @@ -135,7 +133,6 @@ func NewNode(basePath string, p2pPort int, httpPort int, logger *log.Logger, log
n.p2pPort = p2pPort
n.httpPort = httpPort
n.localTest = localTest
n.mountPoints = make(map[string]*FS)
n.knownPeers = make(map[string]*knownPeer)
n.connectionsInStartup = make(map[*net.TCPConn]bool)
n.recordsRequested = make(map[[32]byte]uintptr)
Expand Down Expand Up @@ -386,45 +383,6 @@ func NewNode(basePath string, p2pPort int, httpPort int, logger *log.Logger, log

initOk = true

// Read and apply mounts.json after node is running
go func() {
time.Sleep(time.Second)

var mounts []MountPoint
mj, _ := ioutil.ReadFile(path.Join(basePath, "mounts.json"))
if len(mj) > 0 {
err := json.Unmarshal(mj, &mounts)
if err != nil {
n.log[LogLevelWarning].Printf("WARNING: lffs: ignoring mounts.json due to JSON parse error: %s", err.Error())
}
for _, mp := range mounts {
if atomic.LoadUint32(&n.shutdown) != 0 {
break
}
var owner *Owner
var maskingKey []byte
if len(mp.Passphrase) > 0 {
owner, maskingKey = PassphraseToOwnerAndMaskingKey(mp.Passphrase)
} else {
if len(mp.OwnerPrivate) > 0 {
owner, err = NewOwnerFromPrivateBytes(mp.OwnerPrivate)
if err != nil {
n.log[LogLevelWarning].Printf("WARNING: lffs: cannot mount %s: invalid owner private key: %s", mp.Path, err.Error())
continue
}
}
if len(mp.MaskingKey) > 0 {
maskingKey = mp.MaskingKey
}
}
_, err = n.Mount(owner, mp.MaxFileSize, mp.Path, mp.RootSelectorName, maskingKey)
if err != nil {
n.log[LogLevelWarning].Printf("WARNING: lffs: cannot mount %s: %s", mp.Path, err.Error())
}
}
}
}()

n.log[LogLevelNormal].Print("--- node startup successful ---")

return n, nil
Expand All @@ -435,13 +393,6 @@ func NewNode(basePath string, p2pPort int, httpPort int, logger *log.Logger, log
func (n *Node) Stop() {
n.log[LogLevelNormal].Printf("--- shutting down ---")
if atomic.SwapUint32(&n.shutdown, 1) == 0 {
n.mountPointsLock.Lock()
for mpp, mp := range n.mountPoints {
mp.Close()
delete(n.mountPoints, mpp)
}
n.mountPointsLock.Unlock()

n.connectionsInStartupLock.Lock()
if n.connectionsInStartup != nil {
for c := range n.connectionsInStartup {
Expand Down Expand Up @@ -506,58 +457,6 @@ func (n *Node) WaitForStop() {
n.runningLock.Unlock()
}

// Mount mounts the data store under a given root selector name into the host filesystem using FUSE.
func (n *Node) Mount(owner *Owner, maxFileSize int, mountPoint string, rootSelectorName []byte, maskingKey []byte) (*FS, error) {
n.mountPointsLock.Lock()
defer n.mountPointsLock.Unlock()
if _, have := n.mountPoints[mountPoint]; have {
return nil, ErrAlreadyMounted
}
if owner == nil {
owner = n.owner
}
fs, err := NewFS([]LF{n}, n.log[LogLevelNormal], n.log[LogLevelWarning], mountPoint, rootSelectorName, owner, maxFileSize, maskingKey)
if err != nil {
return nil, err
}
n.mountPoints[mountPoint] = fs
return fs, nil
}

// Unmount unmounts a mount point or does nothing if not mounted.
func (n *Node) Unmount(mountPoint string) error {
n.mountPointsLock.Lock()
defer n.mountPointsLock.Unlock()
fs := n.mountPoints[mountPoint]
delete(n.mountPoints, mountPoint)
if fs != nil {
go fs.Close()
}
return nil
}

// Mounts returns a list of mount points for this node.
func (n *Node) Mounts(includeSecrets bool) (m []MountPoint) {
n.mountPointsLock.Lock()
for p, fs := range n.mountPoints {
var op Blob
var mk Blob
if includeSecrets {
op, _ = fs.owner.PrivateBytes()
mk = fs.maskingKey
}
m = append(m, MountPoint{
Path: p,
RootSelectorName: fs.rootSelectorName,
OwnerPrivate: op,
MaskingKey: mk,
MaxFileSize: fs.maxFileSize,
})
}
n.mountPointsLock.Unlock()
return
}

// GetHTTPHandler gets the HTTP handler for this Node.
// If you want to handle requests via e.g. a Lets Encrypt HTTPS server you can use
// this to get the handler to pass to your server.
Expand Down Expand Up @@ -1618,7 +1517,7 @@ func (n *Node) requestWantedRecords(minRetries, maxRetries int) {
}
}

// getMakeRecordWorkFunction returns a work function that can be used to locally make records for LFFS or MakeRecord requests.
// getMakeRecordWorkFunction returns a work function that can be used to locally make records for MakeRecord requests.
func (n *Node) getMakeRecordWorkFunction() *Wharrgarblr {
n.makeRecordWorkFunctionLock.Lock()
if n.makeRecordWorkFunction == nil {
Expand Down

0 comments on commit 1bfdce0

Please sign in to comment.