-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
cleaning up addrmgr #862
Comments
Hey @dskloet, I saw you asking on IRC, but you ended up leaving before I could answer! That part of the comment was already addressed in the connmgr as a part of the linked PR. The rest of the thread however was referring to the addrmgr package such as functions like its GetAddress and NetAddressKey functions. In particular, the server setup of the connection manager sets up the address function as a closure here, which is conditionally defined just above it here. Notice how in that function there are various conversions in and out of raw strings and then a final conversion to a |
I waited on IRC for several hours! :-D OK, so first (line 2414) it calls s.addrManager.GetAddress("any"), which returns an addrmgr.KnownAddress called addr. Then on addr, it calls NetAddress(), which returns a wire.NetAddress, 3 times. The first one is to create the group key. Should addmgr.GroupKey() be able to get a net.Addr instead of wire.NetAddress? The second is just to get the port. I guess that can be done with net.Addr. The third is to convert to net.Addr so there net.Addr definitely suffices. In addition to NetAddress(), it also calls LastAttempt, so it seems the KnownAddress is still necessary. So is the goal just to replace wire.NetAddress with net.Addr here? By the way, I noticed that newServer() is 330 lines long. What do you think about splitting that up into smaller function? Also s.addrManager.GetAddress("any") takes an argument which is always "any", and is completely ignored. Can we just get rid of that or is it required for a certain interface? |
Stupid question: How do I run all the tests? |
I believe the easiest way to handle it will be to make You're correct to note that there is no need to call I'm prety sure the intention of the argument to As far as the tests, You can also change directory to a specific package and run bare |
Don't you think it's better to remove the unused parameter for now? It can always be added back (with a more appropriate type) when it's going to be used. And what about my suggestion to split the 300+ line function into smaller functions? And it's been a while since I contributed to a project through github. I followed the instructions here and did
but that doesn't work if I want to be able to make pull requests, does it? Should I first clone the repo inside github and git clone that to my local machine? (I could start by adding a document Getting started contributing to collect the answer to all my silly questions about running tests, cloning the repo and making pull requests.) I like to make changes as small as possible. Would, for example, a single PR for just optimizing the order on the if or one for removing the unused parameter, be OK with you? Or would you prefer reviewing several such improvements together in one somewhat larger PR? |
I don't have any objections to either removing the unused parameter or splitting the In terms of your pull request questions, I created a gist some time ago which discusses pull request management here. It could be cleaned up and added to the main repository under some type of getting started as you suggest. Thanks for your help! |
I was looking at making wire.NetAddress implement net.Addr. For that it needs to implement Network() (I think that just means return "tcp") and String() which is a bit trickier. I think it's good for all simple data objects to be immutable so I started looking at where wire.NetAddress instances are being mutated. There are some places but it doesn't seem fundamentally necessary. I thought to make it more strict and more obvious, I could make the fields of wire.NetAddress private and only expose getters. What do you think about that? And then the question of whether to compute the string each time or cache it. Would it be called often? And are there many instances? Are we more CPU constrained or memory constrained or does it not really matter in this case? |
Yes, for As for As for immutability, I too am a fan of it, but unfortunately, Go lacks compiler-enforced immutability (ala EDIT: Fixed the EDIT2: On second thought that really should be using |
Thanks for clarifying. |
Sounds like a good idea to me. Most of the code tries to populate all the fields it can at creation time in general, so any cases with |
One more thing. In NewNetAddressIPPort it explicitly rounds the timestamp to single second precision, but for example in peer.go it creates a wire.NetAddress directly with a high precision timestamp. I'd like to change that to call NewNetAddressIPPort instead so it doesn't have to set the timestamp explicitly, but it would result in a different timestamp precision. Why do we care about the precision in one place but not in the other? |
Btw, I noticed I was using spaces instead of tabs, but goclean.sh passed all the tests. So while it says it runs gofmt, it doesn't seem to be the case. |
Nice catch that it's inconsisent. I agree with changing it to use the function and making it consistent since it really should all be single second precision everywhere. On the plus side, it's not really a big deal here using sub-second precision sometimes since it's not consensus code and it will be truncated to single second precision when sending it over the wire since the wire protocol uses a unix-style timestamp encoded as a uint32 (known issue in Bitcoin that a new wire protocol message will be needed before ~2106 as a result as well as support for longer addresses like I2P addresses). It's strange that you didn't get an error with spaces. I just tried changing a single tab to spaces (and disable the automatic gofmt my editor does which would fix it). Here is the result:
|
Now I just noticed that it's complaining about not finding glide but in the end it returns without error. Do you know where it should be able to find glide? Do I need to add something to my PATH? Here's what I get from goclean.sh:
|
And do you try to replicate the exact same behavior of Bitcoin Core even for non-consensus code? Or do you allow to do non-consensus things different/better from Core? |
I personally just make sure that We do not aim for exact behavior outside of consensus code and other required interoperability points such as the wire protocol. We do aim to provide a compatible RPC API for chain related RPCs although it is definitely not 100% compatible (internal implementation differences prevent some fields from being available). Additionally, we try to maintain a somewhat similar default mempool policy in regards to what transactions are considered "standard" and therefore accepted to the mempool and relayed to other nodes. Outside of those areas, we implement it however we feel best fits the code architecture, Go best practices, tradeoffs in regards to garbage collection, etc. |
Wow with $GOPATH/bin in my path goclean.sh produces waaay more output and takes much longer to run. |
EDIT: That second line tells |
Shouldn't it be listed as a dependency so it automatically gets installed
when I install btcd or something?
And isn't it strange that goclean.sh nevertheless returns success?
|
Well, it's not a dependency for The From speaking to several of the devs, most of us have our editors setup to automatically run The reason for that is, as you noted, it takes a while to run them the full suite of tests and lints. |
It seems I'm missing a bunch of things. golint, vet, gosimple and unconvert. I'm not familiar with
to install all the all the dependencies for developing btcd? Also I saw |
The It's not possible to add a target like you can with with |
I'm now looking at having I have |
Yes, that is part of the reason I mentioned that the In this particular case, the reason there is no test failure is because the However, as you note, there isn't a unit test in btcd proper that specifically checks the function being provided to the connection manager itself works as intended. That definitely should be remedied. I believe the easiest way to accomplish that would be to make the closure independently accessible and write a test which creates a new address manager instance and populates it with known values and then ensures the closure returns one of the correct values. It should also tests error conditions. |
I can try looking into that. Do you think there should also be an integration test that runs the server exercises this code? |
I would love for there to be more integration tests, though I suspect there is quite a bit of work that needs to be done to get to the point you can integration test the server itself. The reason I say that is that even though there has been quite a bit of recent decoupling of things out into packages like EDIT: I should note that the packages themselves (with the exception of |
I was actually talking about running the real thing, maybe several instances, and then poking it with RPCs. Maybe some new RPCs should be introduced to poke at it enough to test it. I'm not familiar enough with Bitcoin and btcd but it seems like it should be possible in general. |
Ah ok, I see. I was thinking you meant the other way since I'm fairly positive there aren't currently any RPCs related to the address manager. For reference, the available RPCs are documented here. So, if you wanted to go that route, you'd end up needing to add some extension RPCs that allow you to add/remove/query the address manager. That said, I personally am in favor of this approach as I think it is definitely the most robust in terms of ensuring all of the pieces fit together properly. I certainly concede it is a fair amount of work, but I do think it would be worth it. |
I have a stupid problem. I'm writing server_test.go and I need to create an instance of
Any idea what I might be missing? |
Tests are only run on a per-package basis, so anything that is only defined is test files is not available outside of the package they're defined in. You would have to provide an exported |
But I'm not sure anymore if I should create |
Correct about I personally would just pass in a type mockLookups struct {
fieldsYouWantToControlBehavior int
...
}
func (m *mockLookups) lookupFunc(host string) ([]net.IP, error) {
// Whatever test-specific things you want to test in here.
// You can check m.foo to alter what is returned based on configured fields in struct.
}
...
func Test....(t *testing.T) {
m := mockLookups{configStuff....}
amgr := addrmgr.New(dataDir, m.lookupFunc)
...
} EDIT: Oh, and about nil on the lookup func, the only time it's used is when a non IP address string is provided and a DNS lookup is needed. That is why the |
But I'm not trying to import So I'm trying to test newAddressFunc from server.go. I've factored out a function that creates the newAddressFunc from an AddrManager and a getOutboundGroupCount function. But now I realize that the behavior depends on knownAddr.LastAttempt(). So either I need to be able to mess with the lastattempt of the known address, or I need to be able to mess with time.Now(). I think instead of passing an AddrManager to createNewAddrressFunc, I could pass just the GetAddress function and stub that out. Then I can pass it any KnowAddress I want. Or I can try to create a fake clock and inject that into the server so I can manipulate time. But that seems like it could be a huge refactoring to do it consistently (though I think it would be good to have an injectable clock everywhere time.Now() is used). What do you think? By the way, the whole behavior of newAddressFunc and GetAddress seems very arbitrary. They both randomly try an address a bunch of times until there's something it likes, based on arbitrarily computed probabilities. The whole thing looks like it's O(n^3) because the buckets of addresses also don't have random access so the algorithm needs to iterate through (either a linked list for addrTried or a map for addrNew) to get a random element. I guess this is all part of "addrmgr needs to be cleaned up"? Or does some of this have good reasons? |
Thinking about it some more, I wonder why In order to test it properly, I'd have to go the fake clock route though but I think that's a good idea anyway. What do you think? |
Dave? |
Hey @dskloet,
Yes, all files that end in
Passing in
This behavior is part of the address manager as it was originally implemented in Bitcoin Core. This paper presents a very comprehensive overview of the behavior of the I think you'll get faster responses and therefore will be able to iterate more quickly on this set of changes if you came over to the I really appreciate the work you've been doing so far within the
|
Thanks for the suggestions. When I started making my small changes, I set out to make wire.NetAddress implement net.Addr as suggested before. I've invested in that now so I'd like to finish that before looking into other things. I also don't think I understand the code well enough yet to implement whole new functionality from those papers. But I think doing refactoring will help me understand the code better. Would you be against introducing a fake clock to allow testing functionality that depends on time.Now()? I think that might be the best way to add enough test coverage to refactor safely. Directly mutating I've connected to IRC several times and waited for hours without response. So I think async communication is the best option. And I'd prefer to agree on a direction before starting on code. I mean, I can start coding the fake clock but if you already know you don't want it, that's just a waste of time. It also suggests that int the contribution guidelines
|
Yeah, admittedly most of the tasks I described above are a bit advanced. It definitely makes sense to ease your way into larger changes like that by refactoring bits here and there, which will allow you to get a better feel for the codebase as a whole. About IRC: yeah most of us idle there using bouncers, or a remote tmux session, as a result we may be seen as online, but won't immediately respond to questions. In that case, if you stick around long enough once of us will catch your question in the scroll-back as we catch up to the history we missed. OK, about the refactor: I think a straightforward route is to add a |
Cool. I'll give that a go next time I have some time. |
Apologies for the silence. Over the December break I lost momentum and I seem to have prioritized other projects since. I'd still like to get back to this eventually but I can't promise anything. I won't get upset if someone else decides to take addrmanager in a different direction in the meantime. |
#840 (comment)
I thought I could maybe have a look at that. But I got confused right away
But as far as I can see GetNewAddress already returns net.Addr. What am I missing?
This is my first look into btcd so any help is appreciated.
The text was updated successfully, but these errors were encountered: