-
Notifications
You must be signed in to change notification settings - Fork 492
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
Participation Metrics #2677
Participation Metrics #2677
Conversation
agreement/abstractions.go
Outdated
// RecordVote sets the LastVote field for the Account. | ||
RecordVote(account basics.Address, round basics.Round) error | ||
|
||
// RecordBlockProposal sets the LastBlockProposal field for the Account. | ||
RecordBlockProposal(account basics.Address, round basics.Round) 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.
The vote and proposal aren't really that far apart from each other. In fact, tracking the proposal-vote is suffice to indicate a block proposals. I'd collapse these two into a single call, and add a bool indicating whether it's a proposal vote or not.
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 changed this so there's a single Record(basics.Address, basics.Round, <participation type>)
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.
One problem with this was that I had to import the node package in the agreement code in order to get this new participation type.
To avoid that, I moved the current keymanager implementation into the data/account directory for this new type. The current partkey db code is in data/account, so this isn't unprecedented. I'm trying to find a better way, so this is may change.
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.
moving the simpleKeyManager
to a different package makes it a non-testing code - which isn't really what we want.
I'm not sure that I followed the reasoning for that, as adding the account.ParticipationAction
should not change any of the dependencies.
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.
These were 2 different changes.
moving the
simpleKeyManager
to a different package makes it a non-testing code - which isn't really what we want.
I didn't realize that. So public packages that are only imported by _test.go files are excluded from the production binaries?
I'm not sure that I followed the reasoning for that, as adding the
account.ParticipationAction
should not change any of the dependencies.
Previously it was node.ParticipationAction
defined in node/participation.go
, so the agreement abstraction would have had to include node.
I'll continue to think about these two items as I implement the interfaces.
node/participation.go
Outdated
func (db *participationDB) RecordVote(account basics.Address, round basics.Round) error { | ||
return 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.
make sure this one is non-blocking.
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.
looks like a promising start. I'd put all the new node files in node/keymanager/...
since there will be more files around that.
agreement/abstractions.go
Outdated
|
||
// Record indicates that the given participation action has been taken. | ||
// This function is also part of account.ParticipationRegistry. | ||
Record(account basics.Address, round basics.Round, participationType account.ParticipationAction) 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.
I'm wondering if we really need to have this error
return variable. I can't think of anything useful we can do with it. Can you ? i.e. having the the error
between the participationRegistry
and the AlgorandFullNode
makes sense, since we want the node to log all the errors. However, providing that feedback to the agreement isn't very helpful since we don't have any useful thing we can do with that.
( i.e. just ignoring it implies that the interface 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.
I hadn't thought about it like that. I think that the ParticipationRegistry
interface can return the error, and the agreement abstraction can remove it. In the shim function implemented by the FullNode I'll log a warning and the agreement code can ignore that case.
agreement/pseudonode.go
Outdated
@@ -386,6 +386,7 @@ func (t pseudonodeVotesTask) execute(verifier *AsyncVoteVerifier, quit chan stru | |||
|
|||
var totalWeight uint64 | |||
for _, result := range verifiedResults { | |||
t.node.keys.Record(result.v.R.Sender, result.v.R.Round, account.Vote) |
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.
consider moving this to
case t.out <- messageEvent{T: voteVerified, Input: r.message, Err: makeSerErr(r.err)}:
as it would be more CPU efficient as well as more "correct" in the case where we failed to persist.
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.
looks like promising progress.
Codecov Report
@@ Coverage Diff @@
## feature/partkey #2677 +/- ##
===================================================
+ Coverage 47.05% 47.38% +0.33%
===================================================
Files 349 351 +2
Lines 55862 56034 +172
===================================================
+ Hits 26284 26551 +267
+ Misses 26623 26490 -133
- Partials 2955 2993 +38
Continue to review full report at Codecov.
|
func (part Participation) ParticipationID() ParticipationID { | ||
data := new(bytes.Buffer) | ||
|
||
data.Write(part.Parent[:]) | ||
binary.Write(data, binary.LittleEndian, part.FirstValid) | ||
binary.Write(data, binary.LittleEndian, part.LastValid) | ||
binary.Write(data, binary.LittleEndian, part.KeyDilution) | ||
if part.VRF != nil { | ||
data.Write(part.VRF.PK[:]) | ||
} | ||
|
||
// this too? | ||
//part.Write(part.Voting.SubKeyPK[:]) | ||
|
||
return ParticipationID(crypto.Hash(data.Bytes())) | ||
} |
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.
The common way we calculate a hash of a structure is by msgp-ing it. Are you thinking of any particular reason we shouldn't do it here ?
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.
My understanding is that the subkeys will change over time, so this was an attempt to make sure the same keyset wouldn't produce different ParticipationIDs
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.
While what you just wrote is correct, it's not what I meant.
I was thinking of -
serializing := Participation{
FirstValid: part.FirstValid,
...
while omitting the changing parts ( i.e. Voting keys ).
then
return ParticipationID(crypto.Hash(serializing.Marshal(nil)))
}) | ||
} | ||
|
||
func (db *participationDB) Record(account basics.Address, round basics.Round, participationAction ParticipationAction) 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.
don't forget that you'd want to make this into a non-blocking : missing a database update because the disk access is slow, is fine. But you don't want to slow down the agreement due to slow disk performance.
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 you think running this in a goroutine is sufficient? It looks like the synchronization options have a non-zero chance of DB corruption.
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.
@tsachiherman I decided to keep it synchronous at this point and name the agreement abstraction RecordAsync
. The node layer makes it non-blocking.
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 that you'd rather do it here so that you can update the in-memory statistics without blocking, while allowing the persistence take longer.
i.e. Ideally, you would be able to respond to a http query without any blocking on the database.
"github.com/algorand/go-algorand/util/db" | ||
) | ||
|
||
const maxBalLookback = 320 |
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.
@tsachiherman I don't want to bring in a dependency on the consensus params here, so I'd like to remove this and simply record the registration round:
EffectiveFirst
-> RegisteredAt
EffectiveLast
-> DeregisteredAt
Also, I think I have an off-by-one error and EffectiveLast
== EffectiveFirst
when replacing a key. Do you happen to know if EffectiveLast
should be -1 or EffectiveFirst
should be +1?
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.
The RegisteredAt
is very different from EffectiveFirst
, and EffectiveLast
is very different from DeregisteredAt
. While I don't mind storing these, there is no "notable" gains here.
Btw - I always used the term "register/unregister".. so "deregister" sounds a bit weird. I went looking, and found this - https://english.stackexchange.com/questions/25931/unregister-vs-deregister. so, it looks like you're correct : since you're denoting the action rather then the state, it should be Deregister
and it would make it's state unregistered
.
As for the other question, the effective round should be real round numbers where the node can use the voting key. so, having these equal is legit.
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 the second question let me clarify. When I'm overriding key1 with key2, I'm not sure which round to use for key1.EffectiveLast and key2.EffectiveFirst.
Currently I have it so key1.EffectiveLast = key2.EffectiveFirst = currentRound + 320
, I'm confident that at least one of those is wrong.
"fmt" | ||
|
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.
nit: leave this spacing line.
f9bd338
to
731feb8
Compare
731feb8
to
89c2a88
Compare
Summary
Draft PR - Currently have the public interface and plumbing to call functions from the agreement code.
This PR adds a new SQLite DB to store metadata and metrics for participation keys.
Test Plan
New unit tests TBD