Skip to content

Commit 4b96e7d

Browse files
committed
cmd/statesync: Add command calculating recommended trust
1 parent 49b6f25 commit 4b96e7d

File tree

4 files changed

+167
-0
lines changed

4 files changed

+167
-0
lines changed

cmd/network/network.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,5 @@ func init() {
6060
Cmd.AddCommand(setRPCCmd)
6161
Cmd.AddCommand(showCmd)
6262
Cmd.AddCommand(statusCmd)
63+
Cmd.AddCommand(trustCmd)
6364
}

cmd/network/trust.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package network
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/spf13/cobra"
9+
10+
"github.com/oasisprotocol/cli/cmd/common"
11+
cliConfig "github.com/oasisprotocol/cli/config"
12+
"github.com/oasisprotocol/oasis-core/go/consensus/api"
13+
"github.com/oasisprotocol/oasis-core/go/consensus/cometbft/config"
14+
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/connection"
15+
)
16+
17+
const (
18+
// maxVerificationTime defines the upper bound within which the light client should
19+
// be able to fetch and verify light headers and finally detect and penalize a possible
20+
// byzantine behavior. This value intentionally overestimates the actual time required.
21+
maxVerificationTime = 24 * time.Hour
22+
)
23+
24+
var trustCmd = &cobra.Command{
25+
Use: "trust",
26+
Short: "Show the recommended light client trust for consensus state sync",
27+
Long: `Show the recommended light client trust configuration for consensus state sync.
28+
29+
WARNING:
30+
The output is only reliable if the CLI is connected to the RPC endpoint you control.
31+
If using (default) public gRPC endpoint you are encouraged to verify trust parameters
32+
with external sources.`,
33+
Args: cobra.NoArgs,
34+
RunE: func(*cobra.Command, []string) error {
35+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
36+
defer cancel()
37+
38+
// Establish connection with the target network.
39+
cfg := cliConfig.Global()
40+
npa := common.GetNPASelection(cfg)
41+
conn, err := connection.Connect(ctx, npa.Network)
42+
if err != nil {
43+
return fmt.Errorf("failed to establish connection with the target network: %w", err)
44+
}
45+
46+
trust, err := calcTrust(ctx, conn)
47+
if err != nil {
48+
return fmt.Errorf("failed to calculate consensus state sync trust root: %w", err)
49+
}
50+
51+
switch common.OutputFormat() {
52+
case common.FormatJSON:
53+
str, err := common.PrettyJSONMarshal(trust)
54+
if err != nil {
55+
return fmt.Errorf("failed to pretty json marshal: %w", err)
56+
}
57+
fmt.Println(string(str))
58+
default:
59+
fmt.Println("Trust period: ", trust.Period)
60+
fmt.Println("Trust height: ", trust.Height)
61+
fmt.Println("Trust hash: ", trust.Hash)
62+
fmt.Println()
63+
fmt.Println("WARNING: Cannot be trusted unless the CLI is connected to the RPC endpoint you control.")
64+
}
65+
66+
return nil
67+
},
68+
}
69+
70+
// calculateTrustCfg calculates and verifies the recommended trust config.
71+
func calcTrust(ctx context.Context, conn connection.Connection) (config.TrustConfig, error) {
72+
latest, err := conn.Consensus().Core().GetBlock(ctx, api.HeightLatest)
73+
if err != nil {
74+
return config.TrustConfig{}, fmt.Errorf("failed to get latest block: %w", err)
75+
}
76+
77+
cpInterval, err := fetchCheckpointInterval(ctx, conn, latest.Height)
78+
if err != nil {
79+
return config.TrustConfig{}, fmt.Errorf("failed to fetch checkpoint interval: %w", err)
80+
}
81+
if cpInterval > latest.Height {
82+
return config.TrustConfig{}, fmt.Errorf("checkpoint interval exceeds latest height")
83+
}
84+
85+
blkTime, err := calcAvgBlkTime(ctx, conn, latest)
86+
if err != nil {
87+
return config.TrustConfig{}, fmt.Errorf("failed to calculate average block time: %w", err)
88+
}
89+
90+
debondingPeriod, err := calcDebondingPeriod(ctx, conn, latest.Height, blkTime)
91+
if err != nil {
92+
return config.TrustConfig{}, fmt.Errorf("failed to calculate debonding period (height: %d): %w", latest.Height, err)
93+
}
94+
trustPeriod := calcTrustPeriod(debondingPeriod)
95+
96+
// Going back the whole checkpoint interval guarantees there should be at least
97+
// one target checkpoint height inside this interval.
98+
candidate, err := conn.Consensus().Core().GetBlock(ctx, latest.Height-cpInterval)
99+
if err != nil {
100+
return config.TrustConfig{}, fmt.Errorf("failed to get candidate trust (height: %d): %w", candidate.Height, err)
101+
}
102+
103+
// Sanity check: the trusted root must not be older than the sum of the trust
104+
// period and the maximum verification time. If it is, the light client will
105+
// not be able to safely use this block as a trusted root for the state sync.
106+
if candidate.Time.Add(trustPeriod).Add(maxVerificationTime).Before(time.Now()) {
107+
return config.TrustConfig{}, fmt.Errorf("impossible to calculate safe trust with current parameters")
108+
}
109+
110+
return config.TrustConfig{
111+
Period: trustPeriod.Round(time.Hour),
112+
Height: uint64(candidate.Height), // #nosec G115
113+
Hash: candidate.Hash.Hex(),
114+
}, nil
115+
}
116+
117+
func fetchCheckpointInterval(ctx context.Context, conn connection.Connection, height int64) (int64, error) {
118+
params, err := conn.Consensus().Core().GetParameters(ctx, height)
119+
if err != nil {
120+
return 0, fmt.Errorf("failed to get consensus parameters (height: %d): %w", height, err)
121+
}
122+
return int64(params.Parameters.StateCheckpointInterval), nil // #nosec G115
123+
}
124+
125+
func calcAvgBlkTime(ctx context.Context, conn connection.Connection, latest *api.Block) (time.Duration, error) {
126+
var deltaBlocks int64 = 1000
127+
blk, err := conn.Consensus().Core().GetBlock(ctx, latest.Height-deltaBlocks)
128+
if err != nil {
129+
return 0, fmt.Errorf("failed to get block: %w", err)
130+
}
131+
132+
return latest.Time.Sub(blk.Time) / time.Duration(deltaBlocks), nil
133+
}
134+
135+
func calcDebondingPeriod(ctx context.Context, conn connection.Connection, height int64, blkTime time.Duration) (time.Duration, error) {
136+
stakingParams, err := conn.Consensus().Staking().ConsensusParameters(ctx, height)
137+
if err != nil {
138+
return 0, fmt.Errorf("failed to get staking parameters: %w", err)
139+
}
140+
beaconParams, err := conn.Consensus().Beacon().ConsensusParameters(ctx, height)
141+
if err != nil {
142+
return 0, fmt.Errorf("failed to get beacon parameters: %w", err)
143+
}
144+
debondingBlks := int64(stakingParams.DebondingInterval) * beaconParams.Interval() // #nosec G115
145+
return time.Duration(debondingBlks) * blkTime, nil
146+
}
147+
148+
// calcTrustPeriod returns suggested trust period.
149+
//
150+
// According to the CometBFT documentation, the sum of the trust period, the time
151+
// required to verify headers, and the time needed to detect and penalize misbehavior
152+
// should be significantly smaller than the total debonding period.
153+
func calcTrustPeriod(debondingPeriod time.Duration) time.Duration {
154+
return debondingPeriod * 3 / 4
155+
}
156+
157+
func init() {
158+
trustCmd.Flags().AddFlagSet(common.FormatFlag)
159+
trustCmd.Flags().AddFlagSet(common.SelectorNFlags)
160+
}

examples/network/trust.in.static

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
./oasis network trust --network testnet

examples/network/trust.out.static

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Trust period: 240h0m0s
2+
Trust height: 29103886
3+
Trust hash: ecff618ed2e8991e3e81eb37b2b61cb6990104c170f0fe34b4b2268b70f98fb5
4+
5+
WARNING: Cannot be trusted unless the CLI is connected to the RPC endpoint you control.

0 commit comments

Comments
 (0)