Skip to content

PrivValidator Interface#637

Merged
ebuchman merged 22 commits intodevelopfrom
feature/hsm
Sep 22, 2017
Merged

PrivValidator Interface#637
ebuchman merged 22 commits intodevelopfrom
feature/hsm

Conversation

@adrianbrink
Copy link
Contributor

@adrianbrink adrianbrink commented Aug 31, 2017

Part of #647

This is the first step towards allowing HSM modules to sign votes.

The next step is to abstract the PrivValidator into an interface, so that an HSM can also keep track of the latest votes and rounds.

@adrianbrink
Copy link
Contributor Author

For now, you can manually instantiate a PrivValidator object and then you don't have to provide the PrivKey as described in the comment.

That would allow you to provide it a custom signer.

@dmjones
Copy link

dmjones commented Sep 1, 2017

@adrianbrink I've created #641 to implement those changes.

Copy link
Contributor

@ebuchman ebuchman left a comment

Choose a reason for hiding this comment

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

Thanks for this. Love exporting the commands. Can we drop the new binary though, I think it will be confusing for now. Folks building HSMs can have repos that compose the Tendermint binary with their custom HSM (doesn't need to be a fork, just a single main file that calls AddCommand for everything). Once things stabilize, we can look to integrate their libs into Tendermint proper, but let's keep it separate for now.

RunE: runNode,
}

// NewRunNodeCmd creates and starts a tendermint node. It allows the user to
Copy link
Contributor

Choose a reason for hiding this comment

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

NewRunNodeCmd returns a cobra Command which ...


var runNodeCmd = &cobra.Command{
// RunNodeCmd creates and starts a tendermint node.
var RunNodeCmd = &cobra.Command{
Copy link
Contributor

Choose a reason for hiding this comment

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

isn't this redundant if we have the function below?

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 wanted to keep it to not break the API, but I did it anyway so now it's gone :-)

Short: "Run the tendermint node",
RunE: func(cmd *cobra.Command, args []string) error {
// Wait until the genesis doc becomes available
// This is for Mintnet compatibility.
Copy link
Contributor

Choose a reason for hiding this comment

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

we don't need this anymore

return LoadPrivValidatorWithSigner(filePath, nil)
}

func LoadPrivValidatorWithSigner(filePath string, signer Signer) *PrivValidator {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why bother with this ? Why not just

pv := LoadPrivValidator(filePath)
pv.SetSigner(signer) 

?

Copy link

Choose a reason for hiding this comment

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

@ebuchman That was the fix for my comment above.

Copy link
Contributor

Choose a reason for hiding this comment

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

But can't we just call the SetSigner method to override ?

Copy link

Choose a reason for hiding this comment

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

If you follow through the logic of LoadPrivValidator, you'll find it eventually operates on the private key using the default signer. The call to setPubKeyAndAddress calls privVal.Signer.PubKey(), which in turn calls ds.priv.PubKey().

Copy link
Contributor

Choose a reason for hiding this comment

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

Right of course. If the priv_key is empty, this won't work. Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, so what I would recommend is to change LoadPrivValidator to check if the privkey is empty. If it is, then it should return, and the caller can then call SetSigner. If it's not empty, then it can do

    privVal.Signer = NewDefaultSigner(privVal.PrivKey)                                                                                       
    privVal.setPubKeyAndAddress()  

using the priv key from the file.

Does that make sense?

I'm just wary of making new exposed functions :)

Copy link

Choose a reason for hiding this comment

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

I had assumed developers would borrow your file format to store their own info. Hence privkey wouldn't be empty, but would be something specific to the crypto implementation (perhaps a PKCS#11 key label).

In case it affects your decision, I've recently decided to leap over this basic implement step and head straight for a new privValidator that runs inside an HSM (as we discussed). So this change we are discussing would be for the benefit of others taking a simpler approach to key management.

Copy link

Choose a reason for hiding this comment

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

It's just occurred to me (now that I've started coding in earnest), that to benefit from your existing file format, the signer would need to be created after the priv_validator.json file has been read. So these changes might need some additional thought. I'll code up something early next week and will share around.

@ebuchman ebuchman added this to the 0.11.0 milestone Sep 5, 2017
@adrianbrink
Copy link
Contributor Author

@ebuchman Should I work on implementing the 2nd abstraction in this PR or can we merge this and open a new one for the next step?

@adrianbrink
Copy link
Contributor Author

@dmjones500 Does this work for you? The next step is to extract PrivValidator as an interface so that the HSM can handle everything (including round tracking).

@dmjones
Copy link

dmjones commented Sep 13, 2017

@adrianbrink Sounds good to me. I've made some progress on a basic implementation, just overriding the signer. As soon as you've extracted an interface, I'll bring more logic into the HSM.

// * Use an external signer for their validators
// * Supply an in-proc abci app
// should import github.com/tendermint/tendermint/node and implement
// their own run_node to call node.NewNode (instead of node.NewNodeDefault)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we need to update this comment?

var VersionCmd = &cobra.Command{
Use: "version",
Short: "Show version info",
Short: "Show version infoooooooo",
Copy link
Contributor

Choose a reason for hiding this comment

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

huh?


func (privVal *PrivValidator) SetSigner(s Signer) {
privVal.Signer = s
privVal.setPubKeyAndAddress()
Copy link
Contributor

Choose a reason for hiding this comment

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

rename to updatePubKeyAndAddressFromSigner or derivePubKeyAndAddressFromSigner ?

@ebuchman ebuchman changed the title Add ability to provide privValidator without forking the codebase WIP: PrivValidator Interface Sep 18, 2017
@ebuchman
Copy link
Contributor

Still working on this. Looking better though!

@ebuchman ebuchman changed the title WIP: PrivValidator Interface PrivValidator Interface Sep 19, 2017
@silasdavis
Copy link
Contributor

To add to my comment on #647

I think that to outside parties it makes more sense to just ask for a Go crypto.Signer. Looking at your PrivValidator implementations in this PR I can see why you have the interface you do with SignVote, SignProposal, and SignHeartbeat.

However I think the fact you need them is a symptom that your SignXXX methods conflate verification, digest, and signing. For example the signByteHRS method: https://github.com/tendermint/tendermint/blob/feature/hsm/types/priv_validator.go#L228-L278 is both verifying a structure, requiring specific knowledge of the structure, and signing it requiring none. This bubble up so that to provide a PrivValidator we need to know the signing secret and arcane knowledge of how to check and produce the digest. I suggest you somehow pull these apart. Do you need different verification logic for different implementations of PrivValidator? Can you make do just be taking a validatorSigner crypto.Signer and using the same logic? If all parts of the process need to vary then you could using interfaces like Digestable, Verifiable, and Signer.

I also think that:

type Signable interface {
	WriteSignBytes(chainID string, w io.Writer, n *int, err *error)
}

Should become:

type Digestable interface {
	Digest(w io.Writer) (int, error)
}

If you want chainID included just close it in to a new Digestable and form a joint digest rather than mandating its extra special status:

func JointDigest(w io.Write, digestables... Digestable) (n int, err error) {
	for _, d := range digestables {
		dn, err := d.Digest(w)
		n += dn
		if err != nil {
			return	
		}
	}
	return
}

@dmjones
Copy link

dmjones commented Sep 19, 2017

@silasdavis Teasing out a separate Signer interface (based on Go's crypto module) sounds sensible. However, there are cases where it would be useful to override normal priv validator behaviour too. The specific example I have in mind is incorporating the validation logic within a trusted component. Rather than simply saying "sign this" to your HSM (or enclave, or whatever), you pass sufficient information to allow an informed decision about performing the signature.

This use case is what is triggering the redesign work to abstract the priv validator. (Or at least it's one of the reasons why).

@silasdavis
Copy link
Contributor

silasdavis commented Sep 20, 2017

@dmjones500 having validation/verification happen in a trusted enclave makes sense and sounds like a nice feature. And I see t(hat you need to stay within that enclave to ensure the bytes that get signed were the same ones it verified, so I suppose having two calls to hardware of Verify(message) and Sign(messageDigest) doesn't work in this case.

This being the case then perhaps the interface you have is closer to what NewNode should take. If we can provide a decorator along the lines of proxy.NewLocalClientCreator and friends that you pass a crypto.Signer and you get a software-based default PrivValidator subsuming the verify, digest, and signing logic:

type softwarePrivValidator struct {
	signer crypto.Signer
}

...

func SoftwarePrivValidator(signer crypto.Signer) types.PrivValidator {
	return &softwarePrivValidator{
		signer: signer,
	}
}

Then HSM-integrators that want to host he verification logic can provide the message-specific signing functions with a custom PrivValidator but others can just provide the Signer and boost it up to a PrivValidator.

@codecov-io
Copy link

codecov-io commented Sep 20, 2017

Codecov Report

Merging #637 into develop will increase coverage by 0.69%.
The diff coverage is 53.33%.

@@             Coverage Diff             @@
##           develop     #637      +/-   ##
===========================================
+ Coverage    55.67%   56.37%   +0.69%     
===========================================
  Files           81       81              
  Lines         8404     8403       -1     
===========================================
+ Hits          4679     4737      +58     
+ Misses        3331     3262      -69     
- Partials       394      404      +10

@ebuchman
Copy link
Contributor

ebuchman commented Sep 20, 2017

@silasdavis take a look at latest commit re NewNode. Does that look good to you?

I think there are two use cases for in-process here:

  1. folks that want the whole CLI and just want to replace the privValidator and/or the in-proc app. These folks can just copy the main file and replace the NewRunNodeCmd. Perhaps we should just let all the NewNode params flow through the NewRunNodeCmd ...

  2. folks that want to control the whole CLI. These folks can use the new NewNode, which now has db and gendoc providers.

I think PrivValidator is fine rather than Signer because we expose a function to make a new PrivValidator from a Signer: LoadPrivValidatorFSWithSigner(filePath, signerFunc)

Does that sound sufficient?

I'll still look into using Go's crypto.Signer

@silasdavis
Copy link
Contributor

silasdavis commented Sep 21, 2017

@ebuchman looks great so far. If 1 is a use case you know about then that sounds reasonable. However, personally my concern would be feature creep, whereby people want a little bit of extra flexibility in configuring the CLI and you end up maintaining half-arsed command line wrapper when you just want to be a good consensus engine. You say they can copy the main.go and change things, I'd say why not have them just copy NewRunNodeCmd and have it in the source tree as an example. Then you have one interface to maintain and you still meet your goal of providing a quick start for your use case 1.

Regarding LoadPrivValidatorFSWithSigner that does almost what I'd want, but again I'd rather not have my library messing around with the filesystem if it doesn't need to. Particularly given the Save() panics: https://github.com/tendermint/tendermint/blob/feature/hsm/types/priv_validator.go#L143!

I understand that the PrivValidator needs access to some blockchain state to do its signing, but why does it need to write it to disk (and why in the priv_validator.json?)? Surely the node has access to such state. I think originally it was a convenient place to dump latest state that would read in on startup. But IMO it's always been ugly that it mixed private key matter and the last signed information. Furthermore for testing it causes various timing issues and what should be a static asset (the keys) has to be reset between runs.

Can we remove the dependence on the filesystem from PrivValidator altogether and just have it as an in-memory object (connected to some signing resource you don't control)? Even for a PrivValidator file storing private keys I don't think it's great to have the block height and last signed information held there. Surely we have the persisted state database for this, and failing that the consensus write-ahead log?

@silasdavis
Copy link
Contributor

So to give a bit of background the motivation on this 'leave the filesystem alone' (and the DBProvider) stuff from me is:

  1. Allows me to track and reason about the totality of the state which makes it easier to:
  • Set up and and tear down test environments
  • Ship around the same logical validator to a different availability zone
  • Perform state rollbacks, recover from fatal errors, restore state
  1. Virtualise the state stored on disk (e.g. store in a database, store on IPFS)

This looks like it will be really useful for me so thanks !

@silasdavis
Copy link
Contributor

silasdavis commented Sep 21, 2017

I suppose what I'm really asking is we separate the verification logic from how the LastSignedInfo is obtained, so that it is not coupled to reading filesystem. So how about something like:

func LoadPrivValidatorDBWithSigner(dbProvider DBProvider, 
	signerFunc func(ValidatorID) Signer) *PrivValidatorDB {

Or just:

func LoadPrivValidatorWithSigner(infoProvider func() *LastSignedInfo, 
	signerFunc func(ValidatorID) Signer) PrivValidator {

@ebuchman
Copy link
Contributor

why not have them just copy NewRunNodeCmd and have it in the source tree as an example.

So they get the rest of the CLI as well - init, unsafe_reset, etc.

Can we remove the dependence on the filesystem from PrivValidator altogether

No, I think the PrivValidator needs a persistent store to write to. We can have it take something more generic though, like a Writer, rather than a file name. But we do need an object that wraps the signer that checks persisted conditions before signing.

As for:

func LoadPrivValidatorWithSigner(infoProvider func() *LastSignedInfo, 
	signerFunc func(ValidatorID) Signer) PrivValidator {

this seems nice, but we need the PrivValidator to persist the LastSignedInfo after every time it changes. So something, either the PrivValidator itself or the LastSignedInfo, needs to expose a Save function, which needs to be able to be backed by something persistent. For testing, it should be fine to use an in memory item.

@ebuchman
Copy link
Contributor

ebuchman commented Sep 21, 2017

As for crypto.Signer, I don't think we should use this yet. We need a go-crypto.Signature in the rest of the codebase, so we have to convert the returned sig []byte into a crypto.Signature, which means the thing returned by a generic signer would need to either be go-wire encoded according to go-crypto type bytes, or we'd need some other scheme. In either case, I think we're better just being explicit about returning go-crypto.Signature for now.

Let me know if you have other/better ideas about this.

As for SignBytes -> Digest ... my understanding is that a Digest is typically the hash of the content, whereas the SignBytes are the actual content, we let the signer handle any hashing for now.

@ebuchman
Copy link
Contributor

Here's an idea ... what if we just move signBytesHRS onto the LastSignedInfo and expose it - then it becomes super easy to implement a PrivValidator using whatever backing you want!

@ethanfrey
Copy link
Contributor

I implemented a wrapper around crypto.PrivKey and crypto.PubKey to use the ledger nano s. It works pretty well, except for a question about error handling. By doing so, I was able to use the cli key manager embedded in basecoin with almost no changes (just registering a CreateLedger function to parallel CreateEd25519.

I am not sure what the specific use-case here is. Which HSM? I assume no human input, but it wants to verify the data before signing as well? As a separate app?

Maybe my work on tendermint/go-crypto#37 can be an inspiration of what (or what not) to do for hardware integration

@ethanfrey
Copy link
Contributor

Nice idea with SignByteHRS, but if the point is we just want to use a custom signer... maybe this inversion would make sense:

current proposal:

func (info *LastSignedInfo) SignBytesHRS(signer Signer,
	height, round int, step int8, signBytes []byte) (crypto.Signature, error) {

alternative:

type Signer interface {
  // ....
  SignBytesHRS(info *LastSignedInfo,
	height, round int, step int8, signBytes []byte) (crypto.Signature, error)
}

But then that doesn't make for full security, as the hardware signer shouldn't trust lastsignedinfo coming from code. maybe this:

type Signer interface {
  // ....
  SignBytesHRS(height, round int, step int8, signBytes []byte) (crypto.Signature, error)
}

and then a signer should do the check on LastSignedInfo itself. Does that make sense?

@silasdavis
Copy link
Contributor

Should LastSignedInfo at least have the possibility of being hosted and stored in the secure enclave/signer then? I can see the sense in that.

@ethanfrey
Copy link
Contributor

Yes, @silasdavis that was my idea.

If the signer doesn't check, just keeps the keys safe, then the current work is fine, and just a new Signer implementation. (Which will need a (Signature, error) return value to detect unplugged signers, etc)

The comments from @dmjones500 suggested that they would like to store the LastSignedInfo on the hardware device, then get enough info before signing to make sure they never double-sign.

Maybe stateless-signer (that trusts no double-sigs come to it), and a stateful-signer that checks for double-sigs

@ebuchman
Copy link
Contributor

Ok, I reverted the changes to PrivValidatorFS pending a more formal write up and further discussion. So this PR is now more just about the new interface


// Defaults to tcp
// ProtocolAndAddress returns the transport protocol
// and the ip address from the given string. Defaults to tcp.
Copy link
Contributor

Choose a reason for hiding this comment

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

This probably shouldn't be here

}
genDoc.Validators = []types.GenesisValidator{types.GenesisValidator{
PubKey: privValidator.PubKey,
PubKey: privValidator.GetPubKey(),
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just PubKey?

Copy link
Contributor

Choose a reason for hiding this comment

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

because it's already a field.

Once we refactor the PrivValidator more fully (eg. into ID, Signer, LastSignedInfo), we can use PubKey()

// ResetAll removes the privValidator files.
// Exported so other CLI tools can use it
func ResetAll(dbDir, privValFile string, logger log.Logger) {
resetPrivValidatorLocal(privValFile, logger)
Copy link
Contributor

Choose a reason for hiding this comment

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

node/node.go Outdated
}
state := sm.LoadState(stateDB)
if state == nil {
genDoc, err := genDocProvider()
Copy link
Contributor

Choose a reason for hiding this comment

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

gen as generate or as genesis?

Copy link
Contributor

Choose a reason for hiding this comment

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

genesis. will fix

}
// FuncSignerAndApp takes a config and returns a PrivValidator and ClientCreator.
// It allows other projects to make Tendermint binaries with custom signers and applications.
type FuncSignerAndApp func(*cfg.Config) (types.PrivValidator, proxy.ClientCreator)
Copy link
Contributor

Choose a reason for hiding this comment

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

SignerAndAppFunc?

Copy link
Contributor

Choose a reason for hiding this comment

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

oops this should have been removed.

n.RunForever()
// NewRunNodeCmd returns the command that allows the CLI to start a
// node. It can be used with a custom PrivValidator and in-process ABCI application.
func NewRunNodeCmd(nodeFunc nm.NodeProvider) *cobra.Command {
Copy link
Contributor

Choose a reason for hiding this comment

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

nodeFunc sounds like it's a (func) type.

If it's a function, how about nodeFn as a convention, or perhaps nodeCb, or in this case, provider?

Copy link
Contributor

Choose a reason for hiding this comment

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

will change to nodeProvider

Copy link
Contributor

Choose a reason for hiding this comment

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

xxxFn sounds good as a convention though. @zramsay want to add to coding repo? function vars should be followed by Fn

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 really like nodeFn

// * Use an external signer for their validators
// * Supply an in-proc abci app
// * Supply a genesis doc file from another source
// * Provide their own DB implementation
Copy link
Contributor

Choose a reason for hiding this comment

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

spacing

Copy link
Contributor

Choose a reason for hiding this comment

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

woops. tabs vs spaces. looks same in editor


// Sign
sig, err := privVal.Sign(signBytes)
sig, err := privVal.Signer.Sign(signBytes)
Copy link
Contributor

Choose a reason for hiding this comment

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

Signer is an anonymous embedded interface, so the following should work:
privVal.Sign(). Why not use that?

Copy link
Contributor

Choose a reason for hiding this comment

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

yep, mistake. thanks

@ebuchman ebuchman merged commit 7f5908b into develop Sep 22, 2017
@ebuchman ebuchman deleted the feature/hsm branch September 22, 2017 04:28
@ebuchman ebuchman mentioned this pull request Sep 22, 2017
cmwaters pushed a commit to cmwaters/tendermint that referenced this pull request Dec 12, 2022
* abci: PrepareProposal (tendermint#6544)

* regenerate mocks, proto, mod/sum, and clean up remaining preprocesstxs

* fix tests and revert to old go.mod

* mockery

* add processproposal proto/boilerplate/logic

* gofmt

* fix test

* move UNKNOWN response behaviour to reject

* fix test and add testing util code

* pass full block data when proposing or processing proposals

* linter

* add the process proposal method to the e2e app

* add missing kvstore abci method

* pass block data and results for bass app

* use correct kvstore process logic for kvstore app

* add new lazy share writers

* linter

* remove unused arg

* sort messages before exporting

* formatting and bug fix

* fix tests

* allow for picking of square size when computing shares for the data square

* remove accidental code duplication

* fix test from using wrong formatting directive

* linter

* ci: backport lint configuration changes (tendermint#7225)

* lint: cleanup pending lint errors  (tendermint#7237)

* linter

* remove unused arg

* sort messages before exporting

* formatting and bug fix

* fix tests

* allow for picking of square size when computing shares for the data square

* fix test from using wrong formatting directive

* linter

* ci: backport lint configuration changes (tendermint#7225)

* lint: cleanup pending lint errors  (tendermint#7237)

* add new lazy share writers

* fix rebase

* linter

* try ci with go 1.17

* Revert "try ci with go 1.17"

This reverts commit 0f76b4d444465cf8209bd98bb18f21b242207436.

* please work, linter gods

* spelling

Co-authored-by: Hlib Kanunnikov <hlibwondertan@gmail.com>

* initialize pending share using the const share size for capacity

* force the last reserve bytes to be zero

* remove todo

* add typecheck back to golang linter

* Revert "ci: backport lint configuration changes (tendermint#7225)"

This reverts commit 35178048f0fef2d80a0346f28c7687be75c3db11.

* Revert "lint: cleanup pending lint errors  (tendermint#7237)"

This reverts commit 5d806709a9dbfed4f7dda2facb49d46604584d36.

* add the link to issue back in

* removed unfinished comment

* switch fuzzer back to one minute

* regenerate proto

* better docs

* fix encoding check to include the hash added to Data

* add docs to CotiguousShareWriter

* fix encoding check

* explain why the share reserve byte is zero

* use punctuation

Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com>

* use punctuation

Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com>

* use punctuation

Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com>

* use punctuation

Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com>

* use punctuation

Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com>

* use punctuation

Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com>

* use clearer wording for compute shares docs

* more accurate docs

Co-authored-by: Marko <marbar3778@yahoo.com>
Co-authored-by: mconcat <monoidconcat@gmail.com>
Co-authored-by: Sam Kleinman <garen@tychoish.com>
Co-authored-by: Hlib Kanunnikov <hlibwondertan@gmail.com>
Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com>
firelizzard18 pushed a commit to AccumulateNetwork/tendermint that referenced this pull request Feb 1, 2024
…ermint#637)

* Add metric

* Fix error message

* add to changelog

* Add metric TxReceivedMoreThanOnce

* observe only when a counter increases

* merge both maps into one

* Rename metric

* Remove metric for priority-mempool, not used anymore

* Update changelog

* Overwrite prevoius value of wasObserved with false

* Undo fix logging

* Apply suggestions from code review

Co-authored-by: Lasaro <lasaro@informal.systems>

* Fix comment

* Remove histogram and update changelog

---------

Co-authored-by: Lasaro <lasaro@informal.systems>
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.

8 participants