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

feat: checkpointing/implement BLS signer #104

Merged
merged 7 commits into from
Sep 1, 2022
Merged

Conversation

gitferry
Copy link
Contributor

This is a draft PR for implementing BLS signer. This PR aims to prepare a MsgAddBlsSig and send it to Tendermint at the end of each epoch. I'm not sure if it works. More importantly, I'm not sure how to test if the is actually sent to Tendermint. Is there a light-weight way to do this?

Copy link
Contributor

@aakoshh aakoshh left a comment

Choose a reason for hiding this comment

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

Getting there for sure!

I don't know if this can be tested during the SDK simulation tests.

About unit tests, there is a MockClient in the SDK that returns errors at broadcast attemts, so it's not exactly good for us, but a similar client could be used for testing that broadcasting happens at all during block processing. You could pass a client context to the keeper, and in tests it would be a mock client. Similar things happen in other CLI command tests, but most of them don't use a Client, they just set various properties of the context.

How to test if you pass the right client context in the real program flow? Maybe we can just rely on integration tests for that part. The unit tests ensure that the right thing gets signed and the right broadcast method is used, and the integration test ensures that the configuration is correct, that eventually the checkpoint is created on the ledger.

x/checkpointing/keeper/bls_signer.go Outdated Show resolved Hide resolved
Comment on lines 39 to 40
fs := pflag.NewFlagSet("checkpointing", pflag.ContinueOnError)
err = fs.Set(flags.FlagFrom, k.GetParams(ctx).String())
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this bit doing exactly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest, I'm not sure. I thought this is a necessary ingredient to compose a transaction, but I'm not sure I'm doing it right. See here.

x/checkpointing/keeper/bls_signer.go Show resolved Hide resolved
x/checkpointing/keeper/bls_signer.go Outdated Show resolved Hide resolved
if err != nil {
return err
}
err = tx.GenerateOrBroadcastTxCLI(client.Context{}, fs, msg)
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 you have to set some fields on the client context, such as BroadcastMode and Client?

Basically everything that happens in the CLI to initialize the context. Maybe this can be injected into the keeper.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for sharing the link. I was not sure what to put in the context. Let me run some tests

x/checkpointing/abci.go Outdated Show resolved Hide resolved
@gitferry gitferry marked this pull request as ready for review August 29, 2022 13:34
@gitferry
Copy link
Contributor Author

Thanks, @aakoshh and @vitsalis for the discussion. I have updated the draft PR and it's ready for review. I'm still struggling with injecting the client.Context to the checkpointing keeper. In the unit test, I used some utilities to customize a client.Context and sent it to the keeper SendBlsSig() and It worked.

But I still think we should inject the client.Context when initializing the checkpointing keeper. I left that as a TODO as I haven't figured out how to do that. For example, the client.Context requires a keyring. How do we get the keyring when starting the checkpointing keeper?

@aakoshh
Copy link
Contributor

aakoshh commented Aug 29, 2022

How do we get the keyring when starting the checkpointing keeper?

The same way we do when we run a client CLI command? It has to be in the home directory, like a wallet, doesn't it? The operator has to make sure that the node has the files available. And it should fail during startup, not later during epoch switches.

Copy link
Contributor

@aakoshh aakoshh left a comment

Choose a reason for hiding this comment

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

Nice work with the mocks!

Perhaps it's possible to inject the client context to the app, rather than the config, from the root. See comments.

@@ -120,6 +120,16 @@ $(BUILDDIR)/:

.PHONY: build build-linux

mockgen_cmd=go run github.com/golang/mock/mockgen@v1.6.0

mocks: $(MOCKS_DIR)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe instead of a one-time task that you run manually, the actual output files could be targets that depend on the inputs, so that if the input changes, then the mock is automatically regenerated. And tests could depend on the mock files to force them to be generated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would be cool. But I'm not sure how to do that in Makefile. Could you please show me an example?

Copy link
Contributor

@aakoshh aakoshh Aug 31, 2022

Choose a reason for hiding this comment

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

This is a good post about Make: https://www.olioapps.com/blog/the-lost-art-of-the-makefile/

It would look something like this:

mockgen_cmd=go run github.com/golang/mock/mockgen@v1.6.0 -package mocks

testutil/mocks/checkpointing_expected_keepers.go: x/checkpointing/types/expected_keepers.go $(MOCKS_DIR) 
    $(mockgen_cmd) -source=$< -destination $@

testutil/mocks/bls_signer.go: x/checkpointing/keeper/bls_signer.go $(MOCKS_DIR) 
    $(mockgen_cmd) -source=$< -destination $@

MOCK_FILES = \
	bls_signer.go \
	checkpointing_expected_keepers.go

MOCK_PATHS := $(patsubst %, testutil/mocks/%, $(MOCK_FILES))

run-tests: $(MOCK_PATHS)
    ...

The only difference is the regeneration if the source changes.

Makefile Show resolved Hide resolved
app/app.go Outdated
Comment on lines 342 to 349
pvKeyFile := nodeCfg.PrivValidatorKeyFile()
if err := tmos.EnsureDir(filepath.Dir(pvKeyFile), 0777); err != nil {
panic(err)
}
pvStateFile := nodeCfg.PrivValidatorStateFile()
if err := tmos.EnsureDir(filepath.Dir(pvStateFile), 0777); err != nil {
panic(err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this allow non-validator nodes to run, a.k.a. "full nodes"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes...It's wired that I cannot get the flagWithTendermint from newApp because it is not accessible. Maybe we need to add a new flag for this. I added a TODO.

app/app.go Outdated Show resolved Hide resolved
@gitferry
Copy link
Contributor Author

gitferry commented Aug 31, 2022

Thanks, @aakoshh for your advice! I managed to inject client context into the checkpointing keeper at root.go newApp. Please see the updates.

return nil, err
}
pvStateFile := nodeCfg.PrivValidatorStateFile()
err = tmos.EnsureDir(filepath.Dir(pvStateFile), 0777)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not returning if err != nil?

Comment on lines +98 to +108
nodeCfg := tmconfig.DefaultConfig()
pvKeyFile := nodeCfg.PrivValidatorKeyFile()
err := tmos.EnsureDir(filepath.Dir(pvKeyFile), 0777)
if err != nil {
return nil, err
}
pvStateFile := nodeCfg.PrivValidatorStateFile()
err = tmos.EnsureDir(filepath.Dir(pvStateFile), 0777)
if err != nil {
return nil, err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The same checks seem to happen in InitClientContext, so maybe we don't have to do it here?

if err != nil {
return nil, err
}
privSigner, _ := InitClientContext(client.Context{}, keyring.BackendMemory)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you remind me what the PV keyfile and state file are, versus an in-memory keyring? Why is one backed by files and the other by memory?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PV keyfile and state file are used in Tendermint for consensus. Please see here. We wrap PV to include BLS keys. Keyring is to store account keys. Keyring have alternative backends but we use in-memory backend only for testing.

Comment on lines +109 to +110
privSigner, _ := InitClientContext(client.Context{}, keyring.BackendMemory)
privSigner.WrappedPV.Clean(pvKeyFile, pvStateFile)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this not going to remove something of value by accident?

Copy link
Contributor Author

@gitferry gitferry Sep 1, 2022

Choose a reason for hiding this comment

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

I don't think it will, not in testing. This method is only used to remove the files generated during testing. Otherwise, these key/state files will be generated everywhere after running tests.

signer.EXPECT().GetAddress().Return(addr1)
signer.EXPECT().SignMsgWithBls(gomock.Eq(signBytes)).Return(bls12381.Sign(blsPrivKey1, signBytes), nil)
err := ckptkeeper.SendBlsSig(ctx, epochNum, lch)
require.NoError(t, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

So what't the client in this test, what does it do for broadcast? Is it possible to assert that the right thing got broadcasted?

Copy link
Contributor Author

@gitferry gitferry Sep 1, 2022

Choose a reason for hiding this comment

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

Thanks for pointing this out. I used BroadcastAsync mode in utils.go InitClientContext. See here. Basically, it does not need to wait for any confirmation of the transaction. We can switch to alternative modes to get more information about the transactions that are sent. I just thought Async mode is good for unit test. We can make it configurable for integration test.

Copy link
Contributor

@aakoshh aakoshh left a comment

Choose a reason for hiding this comment

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

This is a tough one, well done! It looks good, but I guess the only way to see if it works is to run the node and check the logs.

@gitferry gitferry merged commit af43849 into main Sep 1, 2022
@gitferry gitferry deleted the checkpointing/bls-signer branch September 1, 2022 02:09
@gitferry gitferry restored the checkpointing/bls-signer branch September 1, 2022 02:10
gitferry added a commit that referenced this pull request Sep 1, 2022
vitsalis pushed a commit that referenced this pull request Jan 21, 2024
* slashing tx validation

* fix

* fix

* fix

* fix

* fix check

* fix tests and checks

* fix

* fix checks

* fix data types

* fix

* update e2e tests

* fix comments

* fix comments

* fix comments

* fix comments

* change to slashing rate

* fix comments and checks

* fix checks

* hardcode slashing rate to 100% to pass tests

* fix docstring

* more comments

* add slashing rate check in checktransactions

* change staker addr to change addr

* pass change addr in msgcreatedelegation

* change address in msgbtcundelegate

* error handling for change addr

* verbose errors

* fix fee checks and validation for slashing tx on rounded slashing rate

* more comments

* fix docstring

* more comments in fee checks

* remove staking output info in msgundelegate

* remove receiver func Validate

* fix tests and include change address in MsgCreateBTCDelegation request

* fix tests and pass change address in MsgBTCUndelegate

* fix naming and remove dead code

* fix e2e tests - pass change addr in types.MsgCreateBTCDelegation

* fix imports

* fix import issue

* fix imports

* fix imports

* fix

* fix args in tx

* fix imports

* change address in undelegation and delegation creation

* keep Validate func

* add more comments

* fix proto comments

* fix slashing rate validation

* fix validation

* more comments

* fix checks in validation and don't depend on change address

* remove change address proto

* remove change address usages

* fix receiver func

* remove todos

* fix args

* fix func sigs

* fix imports

* fix defs

* fix imports

* fix formatting

* change to ceil(round up)

* lock time 0

* fix checks

* move fee checks

* fix doc string

* convert to btcutil.amount

* dust relay tx fee

* fix dust fn

* fix mod

* add todo

* fix docstring

* changes in build logic and fix tests

* rename vars

* fix tests

* fix slashing rate in tests to avoid change amount being too low

* improve slashing rate validity and fix tests

* improvise tests

* fix comments

* fix spacing

* fix slashing rate in tests

* docstrings

* docstring

* more comments

* docstring

* move btcstaking errors to btcstaking/ dir

* move validation fn inside btcstaking/ dir

* enforce checks if slashing/change address are same

* imrpove precision check

* fix slashing rate

* fix tests
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.

2 participants