Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add in basic libp2p host, and commands for establishing connectivity #34

Merged
merged 3 commits into from
Feb 5, 2018

Conversation

whyrusleeping
Copy link
Member

Testing still TBD

@@ -19,6 +21,9 @@ var daemonCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "start the filecoin daemon",
},
Options: []cmdkit.Option{
cmdkit.StringOption("swarmlisten").WithDefault("/ip4/127.0.0.1/tcp/6000"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as noted in the other PR, lets make the option name a constant

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I wonder if swarmaddr or swarmlistenaddr wouldn't be more clear of what this option does.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't even think we will want this option once we have the config file working, What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's true. Mark it with a todo to move to the config file, and I am happy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its already marked as a todo, look a few lines below where we pull the option out.

commands/id.go Outdated
output = strings.Replace(output, "<addrs>", strings.Join(val.Addresses, "\n"), -1)
output = strings.Replace(output, "\\n", "\n", -1)
output = strings.Replace(output, "\\t", "\t", -1)
_, err := fmt.Fprint(w, output)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets put the formatting above into its own function, that can be easily tested

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its copy-pasted from go-ipfs. I'm thinking that we can do some sort of extraction of common commands at some point.

Still want this in its own function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that the id command will evolve in Filecoin to a degree that will not be a 1-1 match with IPFS in the long run, so pulling that into a shared space might not be worth it.

Copy link
Contributor

@phritz phritz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK the main thing I want to ask is whether we are shooting for a world with clean separation between how triggers enter the system (via command, via peer network) and the things they use to get their work done. It would be great if there were loose coupling. That is, if there were a set of abstractions -- call them model objects though that's not quite right -- that manage and manipulate all our state (chain, orderbook, storage, etc). The model objects do work and are separate from the network/command endpoints that receive triggers and encode responses. The models don't really know about the network or commands, the network/command endpoints hide the specifics of what's happening from the models. This as opposed to close coupling where for example the thing that stores our state also embeds the network and knows a lot about it. One advantage of the loose coupling is that we have a seam that we can use to test. For example we were kicking around the idea of presenting a uniform RPC interface to the node. Peer network endpoints and command handlers pass messages in as RPC requests and messages out are just RPC responses (both structured datatypes). The thing receiving the RPC would have no idea about whether it's doing the work for a peer node, a user command, or an automated test; it just knows how to manipulate models and stuff RPC responses full and return them to the caller. Having this seam also makes it easier uniformly enforce access control, rate limiting, to do logging and tracing, etc. It obviously donest have to use RPC as a model, that's just an example, it could be a straight set of Go interfaces. Does that make any sense?

ps You seem to have misplaced your tests.

pps This is presumably in service of #19?

EDIT: spoke with why in person about this. Answer is basically yes, we're shooting for a reasonably clean separation.

"fmt"

"gx/ipfs/QmT68EgyFbnSs5rbHkNkFZQwjdHfqrJiGr3R6rwcwnzYLc/go-libp2p"
"gx/ipfs/QmfCtHMCd9xFvehvHeVxtKVXJTMVTuHhyPRVHEXetn87vL/go-libp2p-host"
)

// Node represents a full Filecoin node.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some more comments about what a node is an its responsibilities? Same with api server? For example it looks like a node is the interface to the rest of the filecoin network and the api server is strictly user-command-facing. Presumably all filecoin network traffic comes through the node. Does the node also contain all the blockchain and associated data structures or do they live elsewhere?

node/node.go Outdated
// Build instantiates a filecoin Node from the settings specified in the
// config.
func (nc *NodeConfig) Build(ctx context.Context) (*Node, error) {
host, err := libp2p.New(ctx, nc.Libp2pOpts...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you instantiate a node if you dont want to enable networking? I guess it only matters if the node is the thingy containing all the important data structures (maybe it is?), but still curious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We should be able to instantiate a node without networking.

commands/id.go Outdated
cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
)

type idOutput struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you comment what this is?

commands/id.go Outdated
PublicKey string
}

var IdCmd = &cmds.Command{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does embedding implementations in these structures make them hard to test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly, In go-ipfs land we've been moving towards pulling everything out into a separate "core api" thingy. Can start doing that here too.

@dignifiedquire dignifiedquire force-pushed the daemon branch 3 times, most recently from f0d524c to 9aaa818 Compare February 1, 2018 11:01
}()

// TODO: this should be passed in from a config file, not an api flag
libp2pOpts := node.Libp2pOptions(
Copy link
Contributor

@phritz phritz Feb 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno if it's true that this should come in via config file. We talked about having a signal to reload the server config, presumably it needs to reset everything that is in the config file. Networking seems like something that might be a PITA to re-initialize while the node is running. Command line flags are fine for stuff we don't want to change while the server is running. Config file (in the model we talked about) is for stuff we do want to change while the server is running, for example what asks to place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have some different thoughts on what a configuration file should be... The networking configuration will definitely be in the config file. and "what bids and asks to place" does not feel like anything that belongs in a config file. Thats process state, not process configuration.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will file a config file issue actually... #45

}

cfg := cmdhttp.NewServerConfig()
cfg.APIPath = "/api"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should pull "/api" out into a constant, it's used two places. Also I think I suggested scoping it to /cmdapi or similar to not preclude some other api.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll pull it out, going to leave it as /api for consistency though.

)

// idOutput is the output of the /id api endpoint
type idOutput struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for naming, is the pattern generally to call things FooInput and FooOutput when it comes to the api? I like the consistency.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we tend to do that. Sometimes multiple commands can share an output type so it can get weird.

That said, there is something to having them be nicer user facing types that get returned by an api client. I'm interested in your thoughts on the current coreapi approach in go-ipfs land:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency let's use FooInput and FooOutput. It's easy enough to declare that type BarInput is FooInput and then you can always separate them if need be.

I'll take a note to look at the apis, likely not before vacation though.

commands/env.go Outdated
}

// GetNode returns the Filecoin node of the environment.
func GetNode(env cmds.Environment) *node.Node {
Copy link
Contributor

@phritz phritz Feb 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to have what are essentially global variables like this? (As in, is there a reason we couldn't have something that does the command work that has a handle on the node?)

Or just because it's easy?
edit: added ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

is there a reason we couldn't have something that does the command work that has a handle on the node?

Thats essentially what this is. We get a handle to the node this way for the commands work to operate on

Copy link
Contributor

@phritz phritz Feb 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant why not have a thingy that implements commands and is instantiated with a Node. That way a command thingy has-a node, instead of all commands necessarily sharing the same node. The node is a global variable with all the normal baggage. For example you can't have two command thingies that use different nodes in a test. Everyone gets the same node. Globals often give rise to concurrency or deadlock issues because of unclear access patterns, globals tend to accumulate more globals, etc. Guess I'm asking whether there is a reason for having a global variable other than that it's an easy thing to do...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind w/the globals question ^, I got it. The context is provided to the command handler.

utils/lock.go Outdated
}

// Lock creates a lockfile if necessary under filename.
func Lock(filename string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should say somewhere that this lock is re-entrant. And I don't think it should be: if you have a binary that is locking the same locking file multiple times to ensure that it is running once that likely means an error -- that the binary is being initialized more than once.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also from @dignifiedquire's PR. I think he has since removed the locking (for now). I'll rebase in a bit.

err = utils.Lock(lockFile)
Expect(err).To(BeNil())

locked, err = utils.IsLocked(lockFile)
Copy link
Contributor

@phritz phritz Feb 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also test the intended behavior if the file is already locked (and you call Lock on it again).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is from @dignifiedquire's PR, i'm just building on top of it to avoid reimplementing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duh, sorry

Copy link
Contributor

@phritz phritz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are unit tests forthcoming? There are something like 6 new go files with ~700 new lines of code and no unit tests for them.

commands/main.go Outdated
var rootSubcmds = map[string]*cmds.Command{
"daemon": daemonCmd,
"version": versionCmd,
"swarm": SwarmCmd,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed this first time around: if the command doesnt require the daemon to be running how can i list a node's peers this way? Or are you thinking that "list a node's peers" comes later or we list the peers of an ephemeral invocation of the command (rather than what the daemon sees)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in the case of filecoin, we should basically require the daemon to be running for all commands. With ipfs its nice to be able to run things locally without the daemon, but here it just adds extra complexity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I can get behind this, given the following setup

  • all CLI commands are eventually modeled as RPC commands (in whatever RPC definition/framework we decide on)
  • there is a separate configuration that controls enable/disable network connections

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently ipfs-commands seems to mix the logic of client and server in the same packages/files. I wonder if we could change this such that the cli client is its own small package, which effectively only wraps RPC calls.

I am not sure how to best approach this right now though. @whyrusleeping do you have an idea on how to do this, given you have been using the commands library for some time now and have a better understanding of it.

@dignifiedquire
Copy link
Contributor

@whyrusleeping can you rebase on master, now that the previous branch was merged, currently hard to see the changes

@whyrusleeping whyrusleeping changed the base branch from daemon to master February 2, 2018 21:37
cmd/daemon.go Outdated
@@ -1,14 +1,16 @@
package cmd
package commands
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was moving things from cmd to commands. (cmd should be reserved for binaries, commands is a better place for all the commands) but git made that really ugly and hard to read the diff on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@whyrusleeping I pushed the rename to master, so the git history is clean

cmd/id.go Outdated
},
Type: idOutput{},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, val *idOutput) error {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this weeks episode of "making it easier to write commands" we have "REFLECTION!"

cmd/main.go Outdated
@@ -26,31 +26,36 @@ var (
ErrMissingDaemon = errors.New("daemon must be started before using this command")
)

func defaultApiAddr() string {
// Until we have a config file, we need an easy way to influence the API
// address for testing
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

injecting "--cmdapiaddr=foobar" into every command invocation gets annoying.

cmd/env.go Outdated

import (
"context"

cmds "gx/ipfs/Qmc5paX4ECBARnAKkcAmUYHBGor228Tkfxeya3Nu2KRL46/go-ipfs-cmds"
cmds "gx/ipfs/QmWGgKRz5S24SqaAapF5PPCfYfLT7MexJZewN5M82CQTzs/go-ipfs-cmds"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@whyrusleeping should we gx-go uw these before commiting like we do for go-ipfs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we don't do that for go-ipfs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, okay I am confused, so it is not done for go-ipfs, but for libp2p (e.g. https://github.com/libp2p/go-libp2p-secio/blob/master/protocol.go) and ipfs packages (e.g. https://github.com/ipfs/go-ipfs-cmds/blob/master/command.go) could you explain why?

cmd/id.go Outdated
)

// idOutput is the output of the /id api endpoint
type idOutput struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmm, it's dumping a bunch of stuff that's not used. Strategy-wise we'd talked about only adding that which is actually used so as to move fast and avoid clutter and to make it clear what is used and what isn't (the policy being if it is implemented it actually does something, even if that thing is not fully functional). For example the person who implements the public key functionality can add it here and wherever else it should go. From the perspective of our strategy, it's just clutter that we now have to talk about. I guess don't rip it out but let's talk about the strategy going forward if it's not one you'd like to embrace (or maybe we didn't talk about it enough?).

cmd/id.go Outdated
@@ -0,0 +1,78 @@
package commands
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably have a simple unit test for this

cmd/main.go Outdated
"daemon": daemonCmd,
"version": versionCmd,
"swarm": SwarmCmd,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alpha!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should decide if the commands are either exported or not, but be consistent about it.

cmd/swarm.go Outdated
@@ -0,0 +1,269 @@
package commands
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any tests for this code?

cmd/daemon.go Outdated
node := node.New()
if err := startNode(node, api); err != nil {
// TODO: this should be passed in from a config file, not an api flag
libp2pOpts := node.Libp2pOptions(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather see the creation of something as specific as libp2p options be encapsulated inside the node, rather than leaking that outside unless we have to.

@whyrusleeping whyrusleeping force-pushed the feat/networking branch 3 times, most recently from 4305ab2 to 481edd8 Compare February 5, 2018 17:41
commands/id.go Outdated
@@ -40,7 +40,9 @@ var idCmd = &cmds.Command{

out := idOutputFromNode(fcn)

re.Emit(out)
if err := re.Emit(out); err != nil {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no. This is wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why/could you explain what the right way is to handle the error?

@whyrusleeping whyrusleeping merged commit 08bfead into master Feb 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants