/
light.go
188 lines (167 loc) · 5.13 KB
/
light.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package tendermint
import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"time"
"github.com/avast/retry-go"
dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
"github.com/cometbft/cometbft/light"
lightp "github.com/cometbft/cometbft/light/provider"
lighthttp "github.com/cometbft/cometbft/light/provider/http"
dbs "github.com/cometbft/cometbft/light/store/db"
tmtypes "github.com/cometbft/cometbft/types"
tmclient "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
)
// NOTE: currently we are discarding the very noisy light client logs
// it would be nice if we could add a setting the chain or otherwise
// that allowed users to enable light client logging. (maybe as a hidden prop
// on the Chain struct that users could pass in the config??)
var logger = light.Logger(log.NewTMLogger(log.NewSyncWriter(ioutil.Discard)))
// ErrLightNotInitialized returns the canonical error for a an uninitialized light client
var ErrLightNotInitialized = errors.New("light client is not initialized")
// LightClient initializes the light client for a given chain from the trusted store in the database
// this should be call for all other light client usage
func (pr *Prover) LightClient(db dbm.DB) (*light.Client, error) {
prov := pr.LightHTTP()
return light.NewClientFromTrustedStore(
pr.chain.config.ChainId,
pr.getTrustingPeriod(),
prov,
// TODO: provide actual witnesses!
// NOTE: This requires adding them to the chain config
[]lightp.Provider{prov},
dbs.New(db, ""),
logger,
)
}
// LightHTTP returns the http client for light clients
func (pr *Prover) LightHTTP() lightp.Provider {
cl, err := lighthttp.New(pr.chain.config.ChainId, pr.chain.config.RpcAddr)
if err != nil {
panic(err)
}
return cl
}
func (pr *Prover) NewLightDB() (db *dbm.GoLevelDB, df func(), err error) {
c := pr.chain
if err := retry.Do(func() error {
db, err = dbm.NewGoLevelDB(c.config.ChainId, lightDir(c.HomePath))
if err != nil {
return fmt.Errorf("can't open light client database: %w", err)
}
return nil
}, rtyAtt, rtyDel, rtyErr); err != nil {
return nil, nil, err
}
df = func() {
err := db.Close()
if err != nil {
panic(err)
}
}
return
}
// DeleteLightDB removes the light client database on disk, forcing re-initialization
func (pr *Prover) DeleteLightDB() error {
return os.RemoveAll(filepath.Join(lightDir(pr.chain.HomePath), fmt.Sprintf("%s.db", pr.chain.ChainID())))
}
// LightClientWithTrust takes a header from the chain and attempts to add that header to the light
// database.
func (pr *Prover) LightClientWithTrust(db dbm.DB, to light.TrustOptions) (*light.Client, error) {
prov := pr.LightHTTP()
return light.NewClient(
context.Background(),
pr.chain.config.ChainId,
to,
prov,
// TODO: provide actual witnesses!
// NOTE: This requires adding them to the chain config
[]lightp.Provider{prov},
dbs.New(db, ""),
logger)
}
// LightClientWithoutTrust querys the latest header from the chain and initializes a new light client
// database using that header. This should only be called when first initializing the light client
func (pr *Prover) LightClientWithoutTrust(db dbm.DB) (*light.Client, error) {
var (
height int64
err error
)
prov := pr.LightHTTP()
if err := retry.Do(func() error {
h, err := pr.chain.LatestHeight()
switch {
case err != nil:
return err
case h.GetRevisionHeight() == 0:
return fmt.Errorf("shouldn't be here")
default:
t, err := pr.chain.Timestamp(h)
if err != nil {
return err
}
if time.Since(t) > pr.getTrustingPeriod() {
return fmt.Errorf("trusting period has expired")
}
height = int64(h.GetRevisionHeight())
return nil
}
}, rtyAtt, rtyDel, rtyErr); err != nil {
return nil, err
}
lb, err := prov.LightBlock(context.Background(), height)
if err != nil {
return nil, err
}
return light.NewClient(
context.Background(),
pr.chain.config.ChainId,
light.TrustOptions{
Period: pr.getTrustingPeriod(),
Height: height,
Hash: lb.SignedHeader.Hash(),
},
prov,
// TODO: provide actual witnesses!
// NOTE: This requires adding them to the chain config
[]lightp.Provider{prov},
dbs.New(db, ""),
logger)
}
// GetLatestLightHeader returns the header to be used for client creation
func (pr *Prover) GetLatestLightHeader() (*tmclient.Header, error) {
return pr.GetLightSignedHeaderAtHeight(0)
}
// GetLightSignedHeaderAtHeight returns a signed header at a particular height.
func (pr *Prover) GetLightSignedHeaderAtHeight(height int64) (*tmclient.Header, error) {
// create database connection
db, df, err := pr.NewLightDB()
if err != nil {
return nil, err
}
defer df()
client, err := pr.LightClient(db)
if err != nil {
return nil, err
}
sh, err := client.TrustedLightBlock(height)
if err != nil {
return nil, err
}
valSet := tmtypes.NewValidatorSet(sh.ValidatorSet.Validators)
protoVal, err := valSet.ToProto()
if err != nil {
return nil, err
}
protoVal.TotalVotingPower = valSet.TotalVotingPower()
return &tmclient.Header{SignedHeader: sh.SignedHeader.ToProto(), ValidatorSet: protoVal}, nil
}
func lightDir(home string) string {
return path.Join(home, "light")
}