Skip to content

Commit 8f718ec

Browse files
committed
feat: add configurable signer latency correction
1 parent 7d93d5a commit 8f718ec

File tree

22 files changed

+515
-149
lines changed

22 files changed

+515
-149
lines changed

cmd/zetae2e/local/local.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
362362

363363
if testAdmin {
364364
eg.Go(adminTestRoutine(conf, deployerRunner, verbose,
365-
e2etests.TestOperationalFlagsName,
365+
e2etests.TestZetaclientSignerOffsetName,
366+
e2etests.TestZetaclientRestartHeightName,
366367
e2etests.TestWhitelistERC20Name,
367368
e2etests.TestPauseZRC20Name,
368369
e2etests.TestUpdateBytecodeZRC20Name,

docs/cli/zetacored/cli.md

+29-28
Original file line numberDiff line numberDiff line change
@@ -13347,34 +13347,35 @@ zetacored tx observer update-operational-flags [flags]
1334713347
### Options
1334813348

1334913349
```
13350-
-a, --account-number uint The account number of the signing account (offline mode only)
13351-
--aux Generate aux signer data instead of sending a tx
13352-
-b, --broadcast-mode string Transaction broadcasting mode (sync|async)
13353-
--chain-id string The network chain ID
13354-
--dry-run ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it (when enabled, the local Keybase is not accessible)
13355-
--fee-granter string Fee granter grants fees for the transaction
13356-
--fee-payer string Fee payer pays fees for the transaction instead of deducting from the signer
13357-
--fees string Fees to pay along with transaction; eg: 10uatom
13358-
--file string Path to a JSON file containing OperationalFlags
13359-
--from string Name or address of private key with which to sign
13360-
--gas string gas limit to set per-transaction; set to "auto" to calculate sufficient gas automatically. Note: "auto" option doesn't always report accurate results. Set a valid coin value to adjust the result. Can be used instead of "fees". (default 200000)
13361-
--gas-adjustment float adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored (default 1)
13362-
--gas-prices string Gas prices in decimal format to determine the transaction fee (e.g. 0.1uatom)
13363-
--generate-only Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase only accessed when providing a key name)
13364-
-h, --help help for update-operational-flags
13365-
--keyring-backend string Select keyring's backend (os|file|kwallet|pass|test|memory)
13366-
--keyring-dir string The client Keyring directory; if omitted, the default 'home' directory will be used
13367-
--ledger Use a connected Ledger device
13368-
--node string [host]:[port] to tendermint rpc interface for this chain
13369-
--note string Note to add a description to the transaction (previously --memo)
13370-
--offline Offline mode (does not allow any online functionality)
13371-
-o, --output string Output format (text|json)
13372-
--restart-height int Height for a coordinated zetaclient restart
13373-
-s, --sequence uint The sequence number of the signing account (offline mode only)
13374-
--sign-mode string Choose sign mode (direct|amino-json|direct-aux), this is an advanced feature
13375-
--timeout-height uint Set a block timeout height to prevent the tx from being committed past a certain height
13376-
--tip string Tip is the amount that is going to be transferred to the fee payer on the target chain. This flag is only valid when used with --aux, and is ignored if the target chain didn't enable the TipDecorator
13377-
-y, --yes Skip tx broadcasting prompt confirmation
13350+
-a, --account-number uint The account number of the signing account (offline mode only)
13351+
--aux Generate aux signer data instead of sending a tx
13352+
-b, --broadcast-mode string Transaction broadcasting mode (sync|async)
13353+
--chain-id string The network chain ID
13354+
--dry-run ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it (when enabled, the local Keybase is not accessible)
13355+
--fee-granter string Fee granter grants fees for the transaction
13356+
--fee-payer string Fee payer pays fees for the transaction instead of deducting from the signer
13357+
--fees string Fees to pay along with transaction; eg: 10uatom
13358+
--file string Path to a JSON file containing OperationalFlags
13359+
--from string Name or address of private key with which to sign
13360+
--gas string gas limit to set per-transaction; set to "auto" to calculate sufficient gas automatically. Note: "auto" option doesn't always report accurate results. Set a valid coin value to adjust the result. Can be used instead of "fees". (default 200000)
13361+
--gas-adjustment float adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored (default 1)
13362+
--gas-prices string Gas prices in decimal format to determine the transaction fee (e.g. 0.1uatom)
13363+
--generate-only Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase only accessed when providing a key name)
13364+
-h, --help help for update-operational-flags
13365+
--keyring-backend string Select keyring's backend (os|file|kwallet|pass|test|memory)
13366+
--keyring-dir string The client Keyring directory; if omitted, the default 'home' directory will be used
13367+
--ledger Use a connected Ledger device
13368+
--node string [host]:[port] to tendermint rpc interface for this chain
13369+
--note string Note to add a description to the transaction (previously --memo)
13370+
--offline Offline mode (does not allow any online functionality)
13371+
-o, --output string Output format (text|json)
13372+
--restart-height int Height for a coordinated zetaclient restart
13373+
-s, --sequence uint The sequence number of the signing account (offline mode only)
13374+
--sign-mode string Choose sign mode (direct|amino-json|direct-aux), this is an advanced feature
13375+
--signer-block-time-offset duration Offset from the zetacore block time to initiate signing
13376+
--timeout-height uint Set a block timeout height to prevent the tx from being committed past a certain height
13377+
--tip string Tip is the amount that is going to be transferred to the fee payer on the target chain. This flag is only valid when used with --aux, and is ignored if the target chain didn't enable the TipDecorator
13378+
-y, --yes Skip tx broadcasting prompt confirmation
1337813379
```
1337913380

1338013381
### Options inherited from parent commands

docs/openapi/openapi.swagger.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -58110,6 +58110,11 @@ definitions:
5811058110
description: |-
5811158111
Height for a coordinated zetaclient restart.
5811258112
Will be ignored if missed.
58113+
signer_block_time_offset:
58114+
type: string
58115+
description: |-
58116+
Offset from the zetacore block time to initiate signing.
58117+
Should be calculated and set based on max(zetaclient_core_block_latency).
5811358118
description: Flags for the top-level operation of zetaclient.
5811458119
observerPendingNonces:
5811558120
type: object

docs/spec/observer/messages.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ message MsgUpdateGasPriceIncreaseFlags {
169169
```proto
170170
message MsgUpdateOperationalFlags {
171171
string creator = 1;
172-
OperationalFlags operationalFlags = 2;
172+
OperationalFlags operational_flags = 2;
173173
}
174174
```
175175

e2e/e2etests/e2etests.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ const (
132132
TestMigrateERC20CustodyFundsName = "migrate_erc20_custody_funds"
133133
TestMigrateTSSName = "migrate_tss"
134134
TestSolanaWhitelistSPLName = "solana_whitelist_spl"
135-
TestOperationalFlagsName = "operational_flags"
135+
TestZetaclientRestartHeightName = "zetaclient_restart_height"
136+
TestZetaclientSignerOffsetName = "zetaclient_signer_offset"
136137

137138
/*
138139
Operational tests
@@ -880,10 +881,16 @@ var AllE2ETests = []runner.E2ETest{
880881
TestMigrateERC20CustodyFunds,
881882
),
882883
runner.NewE2ETest(
883-
TestOperationalFlagsName,
884-
"operational flags functionality",
884+
TestZetaclientRestartHeightName,
885+
"zetaclient scheduled restart height",
885886
[]runner.ArgDefinition{},
886-
TestOperationalFlags,
887+
TestZetaclientRestartHeight,
888+
),
889+
runner.NewE2ETest(
890+
TestZetaclientSignerOffsetName,
891+
"zetaclient signer offset",
892+
[]runner.ArgDefinition{},
893+
TestZetaclientSignerOffset,
887894
),
888895
/*
889896
Special tests

e2e/e2etests/test_operational_flags.go

+48-3
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import (
1111
)
1212

1313
const (
14-
startTimestampMetricName = "zetaclient_last_start_timestamp_seconds"
14+
startTimestampMetricName = "zetaclient_last_start_timestamp_seconds"
15+
blockTimeLatencyMetricName = "zetaclient_core_block_latency"
16+
blockTimeLatencySleepMetricName = "zetaclient_core_block_latency_sleep"
1517
)
1618

17-
// TestOperationalFlags tests the functionality of operations flags.
18-
func TestOperationalFlags(r *runner.E2ERunner, _ []string) {
19+
// TestZetaclientRestartHeight tests scheduling a zetaclient restart via operational flags
20+
func TestZetaclientRestartHeight(r *runner.E2ERunner, _ []string) {
1921
_, err := r.Clients.Zetacore.Observer.OperationalFlags(
2022
r.Ctx,
2123
&observertypes.QueryOperationalFlagsRequest{},
@@ -60,3 +62,46 @@ func TestOperationalFlags(r *runner.E2ERunner, _ []string) {
6062

6163
require.Greater(r, currentStartTime, originalStartTime+1)
6264
}
65+
66+
// TestZetaclientSignerOffset tests scheduling a zetaclient restart via operational flags
67+
func TestZetaclientSignerOffset(r *runner.E2ERunner, _ []string) {
68+
startBlockTimeLatencySleep, err := r.Clients.ZetaclientMetrics.FetchGauge(blockTimeLatencySleepMetricName)
69+
require.NoError(r, err)
70+
require.InDelta(r, 0, startBlockTimeLatencySleep, .01, "start block time latency should be 0")
71+
72+
// get starting block time latency.
73+
// we need to ensure it's not zero (if zetaclient just finished a restart)
74+
var startBlockTimeLatency float64
75+
require.Eventually(r, func() bool {
76+
startBlockTimeLatency, err = r.Clients.ZetaclientMetrics.FetchGauge(blockTimeLatencyMetricName)
77+
require.NoError(r, err)
78+
return startBlockTimeLatency > .01
79+
}, time.Second*15, time.Millisecond*100)
80+
81+
desiredSignerBlockTimeOffset := time.Duration(startBlockTimeLatency*float64(time.Second)) + time.Millisecond*200
82+
83+
updateMsg := observertypes.NewMsgUpdateOperationalFlags(
84+
r.ZetaTxServer.MustGetAccountAddressFromName(utils.OperationalPolicyName),
85+
observertypes.OperationalFlags{
86+
SignerBlockTimeOffset: &desiredSignerBlockTimeOffset,
87+
},
88+
)
89+
90+
_, err = r.ZetaTxServer.BroadcastTx(utils.OperationalPolicyName, updateMsg)
91+
require.NoError(r, err)
92+
93+
operationalFlagsRes, err := r.Clients.Zetacore.Observer.OperationalFlags(
94+
r.Ctx,
95+
&observertypes.QueryOperationalFlagsRequest{},
96+
)
97+
require.NoError(r, err)
98+
require.InDelta(r, desiredSignerBlockTimeOffset, *(operationalFlagsRes.OperationalFlags.SignerBlockTimeOffset), .01)
99+
100+
require.Eventually(r, func() bool {
101+
blockTimeLatencySleep, err := r.Clients.ZetaclientMetrics.FetchGauge(blockTimeLatencySleepMetricName)
102+
if err != nil {
103+
return false
104+
}
105+
return blockTimeLatencySleep > .05
106+
}, time.Second*15, time.Second*1)
107+
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
syntax = "proto3";
22
package zetachain.zetacore.observer;
33

4+
import "gogoproto/gogo.proto";
5+
import "google/protobuf/duration.proto";
6+
47
option go_package = "github.com/zeta-chain/node/x/observer/types";
58

69
// Flags for the top-level operation of zetaclient.
710
message OperationalFlags {
811
// Height for a coordinated zetaclient restart.
912
// Will be ignored if missed.
1013
int64 restart_height = 1;
14+
15+
// Offset from the zetacore block time to initiate signing.
16+
// Should be calculated and set based on max(zetaclient_core_block_latency).
17+
google.protobuf.Duration signer_block_time_offset = 2
18+
[ (gogoproto.stdduration) = true ];
1119
}

proto/zetachain/zetacore/observer/tx.proto

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ syntax = "proto3";
22
package zetachain.zetacore.observer;
33

44
import "gogoproto/gogo.proto";
5+
import "google/protobuf/field_mask.proto";
56
import "zetachain/zetacore/observer/blame.proto";
67
import "zetachain/zetacore/observer/crosschain_flags.proto";
78
import "zetachain/zetacore/observer/observer.proto";
@@ -143,7 +144,7 @@ message MsgUpdateGasPriceIncreaseFlagsResponse {}
143144

144145
message MsgUpdateOperationalFlags {
145146
string creator = 1;
146-
OperationalFlags operationalFlags = 2 [ (gogoproto.nullable) = false ];
147+
OperationalFlags operational_flags = 2 [ (gogoproto.nullable) = false ];
147148
}
148149

149150
message MsgUpdateOperationalFlagsResponse {}

testutil/sample/observer.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"math/rand"
66
"testing"
7+
"time"
78

89
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
910
"github.com/cosmos/cosmos-sdk/testutil/testdata"
@@ -14,6 +15,7 @@ import (
1415
"github.com/zeta-chain/node/pkg/chains"
1516
"github.com/zeta-chain/node/pkg/cosmos"
1617
zetacrypto "github.com/zeta-chain/node/pkg/crypto"
18+
"github.com/zeta-chain/node/pkg/ptr"
1719
"github.com/zeta-chain/node/x/observer/types"
1820
)
1921

@@ -287,6 +289,7 @@ func GasPriceIncreaseFlags() types.GasPriceIncreaseFlags {
287289

288290
func OperationalFlags() types.OperationalFlags {
289291
return types.OperationalFlags{
290-
RestartHeight: 1,
292+
RestartHeight: 1,
293+
SignerBlockTimeOffset: ptr.Ptr(time.Second),
291294
}
292295
}

typescript/zetachain/zetacore/observer/operational_pb.d.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/* eslint-disable */
44
// @ts-nocheck
55

6-
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
6+
import type { BinaryReadOptions, Duration, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
77
import { Message, proto3 } from "@bufbuild/protobuf";
88

99
/**
@@ -20,6 +20,14 @@ export declare class OperationalFlags extends Message<OperationalFlags> {
2020
*/
2121
restartHeight: bigint;
2222

23+
/**
24+
* Offset from the zetacore block time to initiate signing.
25+
* Should be calculated and set based on max(zetaclient_core_block_latency).
26+
*
27+
* @generated from field: google.protobuf.Duration signer_block_time_offset = 2;
28+
*/
29+
signerBlockTimeOffset?: Duration;
30+
2331
constructor(data?: PartialMessage<OperationalFlags>);
2432

2533
static readonly runtime: typeof proto3;

typescript/zetachain/zetacore/observer/tx_pb.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ export declare class MsgUpdateOperationalFlags extends Message<MsgUpdateOperatio
694694
creator: string;
695695

696696
/**
697-
* @generated from field: zetachain.zetacore.observer.OperationalFlags operationalFlags = 2;
697+
* @generated from field: zetachain.zetacore.observer.OperationalFlags operational_flags = 2;
698698
*/
699699
operationalFlags?: OperationalFlags;
700700

x/observer/client/cli/tx_update_operational_flags.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import (
1313
)
1414

1515
const (
16-
fileFlag = "file"
17-
restartHeightFlag = "restart-height"
16+
fileFlag = "file"
17+
restartHeightFlag = "restart-height"
18+
signerBlockTimeOffsetFlag = "signer-block-time-offset"
1819
)
1920

2021
func CmdUpdateOperationalFlags() *cobra.Command {
@@ -33,6 +34,7 @@ func CmdUpdateOperationalFlags() *cobra.Command {
3334
flagSet := cmd.Flags()
3435
file, _ := flagSet.GetString(fileFlag)
3536
restartHeight, _ := flagSet.GetInt64(restartHeightFlag)
37+
signerBlockTimeOffset, _ := flagSet.GetDuration(signerBlockTimeOffsetFlag)
3638

3739
if file != "" {
3840
input, err := os.ReadFile(file) // #nosec G304
@@ -45,6 +47,7 @@ func CmdUpdateOperationalFlags() *cobra.Command {
4547
}
4648
} else {
4749
operationalFlags.RestartHeight = restartHeight
50+
operationalFlags.SignerBlockTimeOffset = &signerBlockTimeOffset
4851
}
4952

5053
msg := types.NewMsgUpdateOperationalFlags(
@@ -58,6 +61,7 @@ func CmdUpdateOperationalFlags() *cobra.Command {
5861

5962
cmd.Flags().String(fileFlag, "", "Path to a JSON file containing OperationalFlags")
6063
cmd.Flags().Int64(restartHeightFlag, 0, "Height for a coordinated zetaclient restart")
64+
cmd.Flags().Duration(signerBlockTimeOffsetFlag, 0, "Offset from the zetacore block time to initiate signing")
6165
flags.AddTxFlagsToCmd(cmd)
6266

6367
return cmd

x/observer/keeper/msg_server_update_operational_flags_test.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package keeper_test
22

33
import (
44
"testing"
5+
"time"
56

67
sdk "github.com/cosmos/cosmos-sdk/types"
78
"github.com/stretchr/testify/require"
89

10+
"github.com/zeta-chain/node/pkg/ptr"
911
keepertest "github.com/zeta-chain/node/testutil/keeper"
1012
"github.com/zeta-chain/node/testutil/sample"
1113
authoritytypes "github.com/zeta-chain/node/x/authority/types"
@@ -26,10 +28,12 @@ func TestMsgServer_UpdateOperationalFlags(t *testing.T) {
2628

2729
// test initial set
2830
restartHeight := int64(100)
31+
signerBlockTimeOffset := ptr.Ptr(time.Second)
2932
msg := types.MsgUpdateOperationalFlags{
3033
Creator: admin,
3134
OperationalFlags: types.OperationalFlags{
32-
RestartHeight: restartHeight,
35+
RestartHeight: restartHeight,
36+
SignerBlockTimeOffset: signerBlockTimeOffset,
3337
},
3438
}
3539
keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil)
@@ -39,13 +43,16 @@ func TestMsgServer_UpdateOperationalFlags(t *testing.T) {
3943
operationalFlags, found := k.GetOperationalFlags(ctx)
4044
require.True(t, found)
4145
require.Equal(t, restartHeight, operationalFlags.RestartHeight)
46+
require.Equal(t, signerBlockTimeOffset, operationalFlags.SignerBlockTimeOffset)
4247

4348
// verify that we can set it again
4449
restartHeight = 101
50+
signerBlockTimeOffset = ptr.Ptr(time.Second * 2)
4551
msg = types.MsgUpdateOperationalFlags{
4652
Creator: admin,
4753
OperationalFlags: types.OperationalFlags{
48-
RestartHeight: restartHeight,
54+
RestartHeight: restartHeight,
55+
SignerBlockTimeOffset: signerBlockTimeOffset,
4956
},
5057
}
5158
keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil)
@@ -55,6 +62,7 @@ func TestMsgServer_UpdateOperationalFlags(t *testing.T) {
5562
operationalFlags, found = k.GetOperationalFlags(ctx)
5663
require.True(t, found)
5764
require.Equal(t, restartHeight, operationalFlags.RestartHeight)
65+
require.Equal(t, signerBlockTimeOffset, operationalFlags.SignerBlockTimeOffset)
5866
})
5967

6068
t.Run("cannot update operational flags if not authorized", func(t *testing.T) {

0 commit comments

Comments
 (0)