-
Notifications
You must be signed in to change notification settings - Fork 467
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
Conversation
@@ -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"), |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this 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. |
There was a problem hiding this comment.
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...) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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{ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
f0d524c
to
9aaa818
Compare
commands/daemon.go
Outdated
}() | ||
|
||
// TODO: this should be passed in from a config file, not an api flag | ||
libp2pOpts := node.Libp2pOptions( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
commands/daemon.go
Outdated
} | ||
|
||
cfg := cmdhttp.NewServerConfig() | ||
cfg.APIPath = "/api" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
utils/lock_test.go
Outdated
err = utils.Lock(lockFile) | ||
Expect(err).To(BeNil()) | ||
|
||
locked, err = utils.IsLocked(lockFile) |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duh, sorry
There was a problem hiding this 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, |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
@whyrusleeping can you rebase on master, now that the previous branch was merged, currently hard to see the changes |
00769d8
to
7947a5e
Compare
7947a5e
to
944a094
Compare
cmd/daemon.go
Outdated
@@ -1,14 +1,16 @@ | |||
package cmd | |||
package commands |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alpha!
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
4305ab2
to
481edd8
Compare
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no. This is wrong.
There was a problem hiding this comment.
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?
7ea14d4
to
67c5228
Compare
Testing still TBD