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

Participation Metrics #2677

Merged
merged 43 commits into from
Aug 7, 2021

Conversation

winder
Copy link
Contributor

@winder winder commented Aug 2, 2021

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

@winder winder requested a review from tsachiherman August 2, 2021 15:34
@winder winder self-assigned this Aug 2, 2021
Comment on lines 233 to 237
// 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
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

@winder winder Aug 2, 2021

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.

Comment on lines 99 to 101
func (db *participationDB) RecordVote(account basics.Address, round basics.Round) error {
return nil
}
Copy link
Contributor

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.

Copy link
Contributor

@tsachiherman tsachiherman left a 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.


// 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
Copy link
Contributor

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.. )

Copy link
Contributor Author

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.

@@ -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)
Copy link
Contributor

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.

Copy link
Contributor

@tsachiherman tsachiherman left a 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-commenter
Copy link

codecov-commenter commented Aug 5, 2021

Codecov Report

Merging #2677 (8703a00) into feature/partkey (bfeba29) will increase coverage by 0.33%.
The diff coverage is 70.93%.

Impacted file tree graph

@@                 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     
Impacted Files Coverage Δ
agreement/abstractions.go 50.00% <ø> (ø)
agreement/agreementtest/keyManager.go 100.00% <ø> (ø)
config/config.go 47.82% <ø> (ø)
util/db/dbutil.go 41.80% <ø> (+2.25%) ⬆️
node/node.go 26.63% <41.66%> (+22.84%) ⬆️
data/account/participationRegistry.go 68.03% <68.03%> (ø)
data/account/participation.go 36.45% <75.00%> (+3.50%) ⬆️
util/db/initialize.go 92.85% <92.85%> (ø)
agreement/pseudonode.go 72.26% <100.00%> (+0.23%) ⬆️
catchup/peerSelector.go 98.95% <0.00%> (-1.05%) ⬇️
... and 13 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bfeba29...8703a00. Read the comment docs.

Comment on lines +61 to +76
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()))
}
Copy link
Contributor

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 ?

Copy link
Contributor Author

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

Copy link
Contributor

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

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 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
Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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.

@winder winder marked this pull request as ready for review August 6, 2021 14:29
"fmt"

Copy link
Contributor

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.

@winder winder force-pushed the will/participation-metrics branch from f9bd338 to 731feb8 Compare August 6, 2021 18:50
@winder winder force-pushed the will/participation-metrics branch from 731feb8 to 89c2a88 Compare August 6, 2021 18:50
@winder winder merged commit 6029629 into algorand:feature/partkey Aug 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants