Skip to content

Commit 78f432b

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

File tree

4 files changed

+168
-0
lines changed

4 files changed

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

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)