-
Notifications
You must be signed in to change notification settings - Fork 12
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(erc20signersvc): add erc20 reward signer svc #1346
Conversation
config/config.go
Outdated
GenesisState string `toml:"genesis_state" comment:"path to the genesis state file, relative to the root directory"` | ||
Migrations MigrationConfig `toml:"migrations" comment:"zero downtime migration configuration"` | ||
Checkpoint Checkpoint `toml:"checkpoint" comment:"checkpoint info for the leader to sync to before proposing a new block"` | ||
Erc20RWSigner Erc20RewardSignerConfig `toml:"erc20_reward_signer" comment:"ERC20 reward signer service configuration"` |
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 not happy with this name, looking for suggestions
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.
Lets start calling it "erc20 bridge".
So ERC20BridgeSignerConfig
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.
sounds good, the name is more accurate
config/config.go
Outdated
PrivateKeys: nil, | ||
Targets: nil, | ||
// the reasonable value is the block time | ||
SyncEvery: types.Duration(1 * time.Minute), |
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.
Technically we don't need this configuration, but it maybe not what user wants.
Since rn the block time is determined by two consensus parameters(proposeTimeout and emptyBlockTimeout), not easy for the signerSvc to generate a good value based on that two parameters for this configuration. So I'll leave to the user.
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.
We should have this use end block hooks, not polling.
87794d1
to
73b23a4
Compare
config/config.go
Outdated
type Erc20RewardSignerConfig struct { | ||
Enable bool `toml:"enable" comment:"enable the ERC20 reward signer service"` | ||
Targets []string `json:"targets" comment:"target reward ext alias for the ERC20 reward"` | ||
PrivateKeys []string `json:"private_keys" comment:"private key for the ERC20 reward target"` |
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.
here the 'PrivateKeys' is an array, so different 'signer' could be configured for different target
56742f2
to
1e66c34
Compare
parts := strings.Split(args[i], ".") | ||
if len(parts) != 3 { | ||
// there are at least 3 parts. | ||
parts := strings.SplitN(args[i], ".", 3) |
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 3rd part can have values like URL
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.
good catch
node/services/erc20signersvc/eth.go
Outdated
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/ethclient" | ||
"github.com/samber/lo" |
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.
Initially I used this libaray for functional operations, but rn this is the only place it's used
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.
Zero deps with it's release tag. 👍
1e66c34
to
a34fbae
Compare
others worth mention:
|
parts := strings.Split(args[i], ".") | ||
if len(parts) != 3 { | ||
// there are at least 3 parts. | ||
parts := strings.SplitN(args[i], ".", 3) |
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.
good catch
config/config.go
Outdated
PrivateKeys: nil, | ||
Targets: nil, | ||
// the reasonable value is the block time | ||
SyncEvery: types.Duration(1 * time.Minute), |
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.
We should have this use end block hooks, not polling.
config/config.go
Outdated
@@ -311,6 +311,13 @@ func DefaultConfig() *Config { | |||
Height: 0, | |||
Hash: types.Hash{}, | |||
}, | |||
Erc20RWSigner: Erc20RewardSignerConfig{ | |||
Enable: false, | |||
PrivateKeys: 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.
Private keys should point to file paths. not the private key itself.
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.
hmm, so all keys in one file?
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.
No, the user can specify many files paths just like how they specify many keys.
config/config.go
Outdated
} | ||
|
||
func (cfg Erc20RewardSignerConfig) Validate() error { | ||
if (len(cfg.PrivateKeys) != len(cfg.Targets)) && (len(cfg.EthRpcs) != len(cfg.Targets)) { |
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.
We should have this tap into the eth rpcs that the user already has configured. Will follow up on this in-person, but users would essentially be doubly configuring RPCs 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.
It seems like we should have a structure like:
type ERC20BridgeSignerConfig struct {
PrivateKeyPath string `toml:"private_key_path" comment:"private key file path for the ERC20 reward target"`
Chain string `toml:"chain" comment:"chain name for the ERC20 reward target"`
}
which would be stored in the top-level node config as:
type Config struct {
// ...
ERC20Bridge map[string]ERC20BridgeSignerConfig `toml:"erc20_bridge" comment:"ERC20 bridge signer service configuration"`
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.
yeah thought abut that, I think we should do it. Will look into it
return app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, ` | ||
{kwil_erc20_meta}UPDATE reward_instances | ||
SET balance = balance - $amount; | ||
SET balance = balance - $amount | ||
WHERE id = $instance_id; |
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.
good catch
node/exts/erc20reward/meta_sql.go
Outdated
} | ||
|
||
// previousEpochConfirmed return whether previous exists and confirmed. | ||
func previousEpochConfirmed(ctx context.Context, app *common.App, instanceID *types.UUID, endBlock int64) (bool, bool, 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.
nit: can you name the returns so that it is clear what each one means? just because there are two bools
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.
will do
} | ||
|
||
return confirmed, 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.
You should probably add an error here where it errors out if the epoch doesn't exist. I'd do this by simply adding a counter than increments on each callback function passed to the engine. If the counter is 0, then the epoch with that ID does not exist.
This seems more correct than simply returning false
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.
yeah make sense
return err | ||
} | ||
|
||
confirmed, err := epochConfirmed(ctx.TxContext.Ctx, app, epochID) |
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.
Seems like there is a bug here.
Epochs have 3 stages:
- collecting
- finalized
- confirmed
Only an epoch in the "finalized" phase can be signed. But this check only ensures that "confirmed" epochs cannot be signed; it does not protect against signers voting for a "collecting" epoch.
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.
yeah you're right
@@ -68,6 +71,8 @@ var ( | |||
// so that we know how to decode them | |||
logTypeTransfer = []byte("e20trsnfr") | |||
logTypeConfirmedEpoch = []byte("cnfepch") | |||
|
|||
mtLRUCache = lru.NewMap[[32]byte, []byte](rewardMerkleTreeLRUSize) // tree root => tree body |
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.
nice
node/services/erc20signersvc/eth.go
Outdated
@@ -0,0 +1,221 @@ | |||
package signersvc |
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.
seems sort've weird to have a services
package, since the erc20 signer service is in no way actually a service of any kind.
We probably should put both this package and the extension package into an erc20-bridge
package
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.
This will allow us to have one common place to store abis, manage a node's configuration of RPCs (which are used both by validators and signers), etc.
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 like that, will move
node/services/erc20signersvc/kwil.go
Outdated
procedure := "info" | ||
input := []any{} | ||
|
||
res, err := k.clt.Call(ctx, k.namespace, procedure, input) |
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.
You essentially have this node calling itself, but that seems like a weird and unnecessary hack?
Why don't we just properly make it a new module that uses the engine?
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.
yeah it becomes weird now the signerSvc could a reference to the engine, gonna change this
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.
This is a little bit related to the polling
, in that we kind want to avoid using RPC service. We've agreed that for polling
we don't want to react on every block, then I don't see much difference between using end_of_block and just use a ticker.
After a bit experiment, IMO, RPC might be the preferred(and easier) way to communicate with erc20 ext, reasons are:
- although we can use
engine
to call ext VIEW methods, but we cannot call methods(i.e., vote_epoch) that change the state(because it's not deterministic); thus we need to use two sets of API handling to communicate with extension - when signeSvc call
vote_epoch
, there is no easy way to call the RPC service method. You need to construct the payload manually, well, if this is the case Idk why we don't just use client SDK to do this.
node/services/erc20signersvc/kwil.go
Outdated
VoteSignatures [][]byte | ||
} | ||
|
||
type FinalizedReward struct { |
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.
It seems weird to doubly declare these. We should really consolidate the signer and extension logic.
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.
previously I define the return types in the extension, and use them in the method return
, later the API could reuse that. Is this what you mean?
c4240fa
to
fba0ec0
Compare
@@ -335,6 +341,7 @@ type Config struct { | |||
GenesisState string `toml:"genesis_state" comment:"path to the genesis state file, relative to the root directory"` | |||
Migrations MigrationConfig `toml:"migrations" comment:"zero downtime migration configuration"` | |||
Checkpoint Checkpoint `toml:"checkpoint" comment:"checkpoint info for the leader to sync to before proposing a new block"` | |||
Erc20Bridge ERC20BridgeConfig `toml:"erc20_bridge" comment:"ERC20 bridge configuration"` |
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.
This config is used in two places:
- evm-sync extension
- erc20-bridge extension
There are few places like oracle still use the general extension config, which I'm not sure if we should move them under erc20_bridge
cofiguration.
app/node/build.go
Outdated
failBuild(err, "Failed to load erc20 bridge signer state file") | ||
} | ||
|
||
rpcUrl := "http://" + d.cfg.RPC.ListenAddress |
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.
Will this always work? Are ways that a user could set their listen address where it may not?
If so, an alternative would be to give the signer access to the local mempool
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.
now you mentioned it, do we expected to configure a public ip in ListenAddress
?
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.
Yes
app/node/build.go
Outdated
@@ -504,6 +508,33 @@ func buildConsensusEngine(_ context.Context, d *coreDependencies, db *pg.DB, | |||
return ce | |||
} | |||
|
|||
func buildErc20RWignerMgr(d *coreDependencies) *signersvc.ServiceMgr { |
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.
Seems like a typo: buildErc20RWignerMgr
-> buildErc20RSignerMgr
return fmt.Errorf("signature is not 65 bytes") | ||
} | ||
|
||
from, err := ethAddressFromHex(ctx.TxContext.Caller) |
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.
Shouldn't we check the signature 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.
I commented at line 1063, basically we don't have enough info to verify
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 created #1399 for this
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 commented at line 1063
The issue seems to be that I cannot read.
err = types.ScanTo(v, &er.ID, &er.StartHeight, &er.StartTimestamp, &er.EndHeight, | ||
&er.RewardRoot, &er.RewardAmount, &er.EndBlockHash, &er.Confirmed, &er.Voters, &er.VoteNonces, &er.VoteSignatures) | ||
// execute mimics client.Client.Execute, without client options. | ||
func (k *signerClient) execute(ctx context.Context, namespace string, txSigner auth.Signer, action string, tuples [][]any) (types.Hash, 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.
still a bit hacky, if it's worth it we should implement a internal TxSvcClient
. For now this is all I needed for signer service
} | ||
|
||
var _ erc20ExtAPI = (*erc20rwExtApi)(nil) | ||
type signerClient struct { |
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.
Another way to implement signerClient is to call the usersvc.Service
. This approach will have unnecessary wrap/unwrap the request/response types, should be easier to implement
Sorry, we removed |
thanks for the reminder, I'll make the change accordingly |
c5d58a6
to
efae332
Compare
This pr adds erc20 reward signer service to kwild's sub service.
Most important change is how erc20 ext process epoch. Now we have three types of epoch:
Except the first epoch, at any given time, there will only be two active epochs: finalized epoch and collecting epoch.
After an finalized epoch is confirmed, when Kwil propose a new epoch: