Skip to content

Commit cef8261

Browse files
Merge pull request #637 from oasisprotocol/martin/feature/trust-height-command
cmd/statesync: Add command displaying recommended trust root
2 parents 49b6f25 + 74d5b57 commit cef8261

File tree

4 files changed

+174
-0
lines changed

4 files changed

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

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)